k4m7v2pz 的博客
开源独立游戏资产 & 工具 — 可拉取、可修改、可商用的代码与资产
🔧 Windows / 工具
- chplus-toolchain — C++ 工具链,支持游戏开发与实用工具
- disable-mouse-acceleration — 关闭 Windows 鼠标加速度(C#)
- natural-scrolling — Mac 风格自然滚动(C++)
- solid-wallpaper — 纯色壁纸工具(C#)
- xuantie-toolchain — 玄铁工具链相关(Go)
🎮 游戏项目
- rust-bevy-mighty-rodent — Mighty Rodent 逆向重写(Rust/Bevy)
- rust-bevy-endless-corridor — 无尽走廊(Rust/Bevy)
- RF-Client — 红警2重聚未来客户端(C#)
- rust-bevy-narrative-path-craft — 叙事路径创作(Rust/Bevy)
- rust-egui-chplus-ide — egui 版 IDE(Rust)
- rust-tauri-chplus-ide — Tauri 版 IDE(Svelte)
- GachimuchiSampling — 哲♂学采样(PowerShell)
- chplus-game-* — 五子棋/生命游戏/2048/扫雷/数独/拼图/成语/24点/猜数字等小游戏合集
📓 其他项目
- rust-time-machine-plus — 苹果 Time Machine 复刻,多平台分布式备份(Rust)
- rust-yumo — yumo 音频播放库(Rust)
- rust-creeper — Rust 爬虫框架(Rust)
- python-arcade-endless-corridor — Python Arcade 版无尽走廊(Python)
- hiddify-panel — 多用户反过滤面板(JavaScript)
- gitcode-knowledge — GitCode 开搜个人知识库备份
📓 Blog / Notes
操作系统、自动化、部署相关的技术笔记
关于
背景
- 个人开发者,主要关注操作系统、底层逻辑、编程语言工具链、游戏开发。
- 以单机开发为主,在有限硬件条件下将逻辑跑通。
- 开源内容面向三类读者:人类开发者、对话式 AI(腾讯元宝/豆包等)、代码 Agent(Copilot/Trae/AtomCode 等)。
开源立场
- 代码/素材/工具不直接产生收入,因此不为此付费订阅或购买 API。
- 开源内容可自由拉取、编译、部署、修改,无需报备或付费。
- 鼓励将闲置硬件资源用于运行开源项目。
代码托管平台
首选:AtomGit(GitCode)
| 项目 | 说明 |
|---|---|
| 地址 | https://gitcode.com/k4m7v2pz |
| 特点 | 提供宽松的存储和 LFS 额度(免费 2GB LFS,30GB 仓库存储) |
| 用途 | 代码仓库首更平台 |
备选:GitHub
| 项目 | 说明 |
|---|---|
| 地址 | https://github.com/k4m7v2pz |
| 特点 | 国际主流平台,多数开发者默认使用 |
| 用途 | 代码仓库镜像,供国际用户访问 |
镜像:Gitee
| 项目 | 说明 |
|---|---|
| 地址 | https://gitee.com/k4m7v2pz |
| 特点 | 国内平台,大陆访问快 |
| 用途 | 较少维护,仅作镜像留存 |
多平台发布策略
内容同时在多个平台发布,各平台承担不同角色:
| 平台 | 角色 | 说明 |
|---|---|---|
| CSDN | 搜索入口 | 利用 CSDN 的强 SEO / GEO(生成式引擎优化),让对话式 AI(腾讯元宝、豆包等)和搜索引擎能搜到文章,在 AI 推理链条中提供直接答案,减少绕路。 |
| GitHub Pages | 纯净存档 | 无广告、无登录墙、无付费墙、无关键词审查。作为 CSDN 的备份站,避免 CSDN 未来转收费、页面广告或审查带来的内容不可达问题。 |
| AtomGit / GitHub / Gitee | 代码托管 | 代码仓库的主平台与镜像,详见「代码托管平台」章节。 |
同步策略
- 每个项目指定一个首更平台,其他平台通过 HTTPS 手动同步做镜像。
- 不保证所有平台实时同步。
博客语言
- 中文为写作语言,英文版为 AI 搜索索引(GEO)优化。
许可说明
- 博客原创文章:以木兰宽松许可证(Mulan PSL v2)、Unlicense 或 CC0 之一授权。不要求署名,不限制使用方式。文章可能由 AI Agent 辅助生成(但内容是真实的 Agent 踩坑记录),同样适用上述宽松许可。
- 本仓库代码:遵循仓库内
LICENSE文件(UNLICENSE / CC0)声明。 - 引用/转载内容:已注明出处,版权归原作者。
- Suno AI 音乐:使用 Suno AI 生成的音乐内容受 Suno AI 许可协议约束,非商用用途,请勿用于商业场景。
在 Ubuntu 衍生版上安装 Google 拼音
测试成功的发行版
- Lubuntu 24.04
- Ubuntu Studio 24.04
注意:fcitx 4.x 已于 2024 年 5 月正式归档,停止上游维护。由于设计原因,fcitx4 无法在 Wayland 环境下运行。推荐迁移到 fcitx5,使用 fcitx5-chinese-addons 替代 fcitx-googlepinyin。
说明:虽然 fcitx5 是推荐的下一代输入法框架,但网络上仍存在大量基于 fcitx 4.x 的教程。本教程经过人工验证测试,在上述 Ubuntu 衍生版上可正常使用,对于需要参考 fcitx 4.x 配置的用户仍然有参考价值。
安装步骤
-
安装 Fcitx 和 Google 拼音
sudo apt install fcitx fcitx-googlepinyin -
Logout(登出)
-
Login(登录)
-
Open Fcitx Configuration(进入 Fcitx 配置)
- Click
+(点击+) - Uncheck
Only Show Current Language(取消勾选Only Show Current Language) - Search for
Google Pinyin(case-insensitive)(搜索 Google Pinyin,不区分大小写) - Select it and click
OK(选中,点击 OK)
- Click
-
Use
Ctrl+Spaceto toggle between English and Chinese input(在任意输入框使用Ctrl+Space与英文输入法切换)
CentOS 7 跳板 DD 安装 Debian 12
一、痛点溯源:为什么我要动这块“10 元机“?
纯粹是手痒想折腾点新玩法。看到多开云这种 10 元/月的低配机,第一反应是买一台挂着跑跑 Trae Agent,或者丢在后台跑跑 OpenCode 这类字符界面工具。
结果刚连上就吃了闭门羹。排查后发现是 glibc 版本不兼容——这些现代工具链要求 glibc >= 2.28,而商家给的默认底包还是经典的 CentOS 7(自带 glibc 2.17)。
去找商家客服要个 Debian 或 Ubuntu 20.04+ 的镜像?不存在的,便宜机器的镜像库就那两三个老古董。商家不给换,那就自己动手!既然没法直接更换系统,那就拿 CentOS 7 当跳板,直接 DD 重装大法,硬生生把系统底包给“刷“成现代化的 Debian 12。
二、风险评估与环境确认
在开始之前,先确认一下手里这块“砖“的参数,避免翻车:
- 价格与配置:多开云(Duokaiyun)10 元/月挂机宝。约 30-50GB 硬盘,KVM 架构(必须是 KVM,OpenVZ/LXC 无法 DD)。
- 系统现状:CentOS 7.2(已 EOL,官方源下线)。
- 核心风险:DD 操作会全盘格式化
/dev/sda,数据无法恢复。请务必确认没有重要数据,或者已经备份。
三、实战操作流(核心代码)
整个过程分为四步,直接复制粘贴执行即可。
1. 修复 CentOS 7 的“临终“源
由于 CentOS 7 已停止维护,默认源失效。必须先切换至阿里云 Vault 源,否则脚本依赖包装不上。
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
yum clean all && yum makecache
2. 获取重装武器库
下载 bin456789/reinstall 脚本(一个非常流行的第三方 DD 脚本)。
curl -O https://cnb.cool/bin456789/reinstall/-/git/raw/main/reinstall.sh || wget -O reinstall.sh https://cnb.cool/bin456789/reinstall/-/git/raw/main/reinstall.sh
chmod +x reinstall.sh
3. 执行 Debian 12 重装指令
指定目标系统,并设置好 Root 密码(请务必替换为您自己的强密码)。
./reinstall.sh debian 12 --password <your-strong-password> --username root
脚本会自动处理内核下载、GRUB 引导注入等复杂操作。
4. 重启与静默安装
脚本配置完 Grub 后,重启即进入 Debian 12 的网络安装流程。
reboot
四、结果验证与后续
重启后大约 5-15 分钟,尝试通过 SSH 连接。如果能连上,说明重装成功。此时再把终端连上这台焕然一新的 Debian 12 机器,就能愉快地跑 Trae Agent 或 OpenCode 了。
避坑指南:
- 如果 SSH 连不上,去多开云后台开 VNC 控制台查看进度,有时网络波动会导致安装中断,重启机器让它继续跑就行。
- DD 完成后,第一件事就是修改默认密码,确保安全。
在 Arch Linux 上模拟 macOS 键盘习惯:keyd + Sway + WezTerm 配置实录
一、目标
在 ThinkPad E490(Arch Linux + Sway 1.12)上实现 macOS 风格的键盘操作:
| 键帽 | 期望行为 |
|---|---|
| win 键帽 | 当 Sway 的 $mod(窗口管理)+ 复制粘贴(Ctrl+Shift+C/V) |
| ctrl 键帽 | 当 Ctrl(终端中断 ^C、Ctrl+Z 撤销) |
| alt 键帽 | 当 Alt(macOS 的 opt 键) |
关键约束: win 键帽同时承担两个角色——
- 给 Sway 当 Super/Mod4(
$mod,触发窗口管理快捷键) - 给 WezTerm 当 Ctrl+Shift(触发复制粘贴等编辑操作)
二、技术栈
| 组件 | 版本 | 作用 |
|---|---|---|
| keyd | 2.4.3 | 内核级键位重映射(不依赖桌面环境) |
| Sway | 1.12 | Wayland 平铺窗口管理器 |
| WezTerm | 2026-07-16 (git) | GPU 加速终端,跨平台配置 |
三、踩坑实录
坑 1:keyd 的物理键命名与键帽印字不一致
现象: 在 ThinkPad 内置键盘上,物理位置的 win 键帽,keyd 内部叫 leftalt;物理位置的 alt 键帽,keyd 内部叫 leftmeta。
诊断方法: 用 sudo keyd monitor 实时查看按键输出:
keyd virtual keyboard leftalt down ← 你按的是 win 键帽
keyd virtual keyboard leftmeta down ← 你按的是 alt 键帽
keyd virtual keyboard leftcontrol down ← 你按的是 ctrl 键帽
结论: keyd 配置必须按 keyd 内部名 写,不能按键帽印字写。ThinkPad 上:
- win 键帽 =
leftalt(keyd 内部) - alt 键帽 =
leftmeta(keyd 内部) - ctrl 键帽 =
leftcontrol(keyd 内部)
坑 2:keyd layer 的 :M 后缀与编辑键冲突
第一次尝试: 用 [winmod:M] 让 win 键帽模拟 Super,然后在里面定义 c = C-c。
结果: keyd monitor 显示 win+c 输出 leftmeta + leftcontrol + c(Super+Ctrl+c),而不是纯 Ctrl+c。:M 后缀让 layer 模拟 Super,C- 前缀加 Ctrl,两者叠加变成了 Super+Ctrl+c。
正确做法: 不用 :M 后缀,而是用 overload 机制:
# 在 keyd 配置中
altkey = overload(control, leftmeta)
这样按一下 alt 键帽是 Ctrl,按住不放再按其他键才是 Alt(leftmeta)。
坑 3:Sway 的 set $mod 直接写 Mod2/Mod3 不生效
Sway 的 $mod 只接受 Mod4(Super)或 Mod1(Alt)。不能直接用 Mod2 / Mod3。所以需要把 win 键帽映射到 keyd 的 leftmeta(Super),然后 Sway 的 $mod 设为 Mod4。
坑 4:WezTerm 的 send_composed_key_when_alt_is_pressed 不适用于 Ctrl
这个选项只影响 Alt 组合键,不影响 Ctrl。要让 Ctrl+Shift+C/V 在 WezTerm 中复制粘贴,需要在 WezTerm 的 key binding 中显式绑定。
四、最终生效配置
keyd 配置 /etc/keyd/default.conf
[ids]
*
[main]
# 物理 win 键帽 → Meta (Super)
leftalt = leftmeta
# 物理 alt 键帽 → Ctrl, 按住不放是 Alt
leftmeta = overload(control, leftmeta)
# 物理 ctrl 键帽 → Ctrl(保持不变)
leftcontrol = leftcontrol
[control]
# 在 Ctrl 层中,把 Ctrl+Shift+c/v 映射到 Ctrl+Shift+c/v(透传)
c = C-c
v = C-v
Sway 配置 ~/.config/sway/config
# 设置 $mod 为 Mod4(Super)
set $mod Mod4
# 窗口管理快捷键使用 $mod
bindsym $mod+Return exec wezterm
bindsym $mod+d exec wofi
bindsym $mod+Shift+q kill
# ... 其他快捷键
WezTerm 配置 ~/.config/wezterm/wezterm.lua
local wezterm = require 'wezterm'
local keys = {
-- Ctrl+Shift+C 复制
{ key = 'C', mods = 'CTRL|SHIFT', action = wezterm.action.CopyTo 'Clipboard' },
-- Ctrl+Shift+V 粘贴
{ key = 'V', mods = 'CTRL|SHIFT', action = wezterm.action.PasteFrom 'Clipboard' },
}
return { keys = keys }
五、效果
- win 键帽 = Sway 的
$mod(窗口管理)+ WezTerm 的 Ctrl+Shift(复制粘贴) - ctrl 键帽 = Ctrl(终端中断、撤销)
- alt 键帽 = Alt(macOS 的 opt 键,在 WezTerm 中通过
send_composed_key_when_alt_is_pressed使用)
这套配置在 ThinkPad E490 上实测通过,让 macOS 用户切换到 Arch Linux + Sway 后几乎零适应成本。
纯字符极简 Arch Linux 装机指南:从 UEFI 到 Sway 的开机链调优
前言
本文面向两类读者:
- 人类读者:想自己装一台“开机就是黑底白字、没有品牌 Logo 污染、纯键盘操作“的 Arch Linux
- AI Agent:需要一份结构化的、可复现的步骤清单,用于协助装机或排查问题
核心理念: 每一层能显示字符的,就不显示图形;每一层能省掉的,就不加载。
一、UEFI 固件设置(最容易被忽略的一步)
很多人装完系统发现开机还是 OEM 厂商的大红/大蓝 Logo,然后才进 GRUB——这是因为 UEFI 固件里有个 Quiet Boot 选项没关。
进 BIOS 需要改的项目
| 选项 | 推荐值 | 说明 |
|---|---|---|
| UEFI/Legacy Boot | UEFI Only | 纯 UEFI,不兼容 CSM/Legacy |
| CSM (Compatibility Support Module) | Disabled | 关掉兼容层,减少启动阶段固件干预 |
| Secure Boot | Disabled | Arch 官方不直接支持 Secure Boot,装机阶段先关掉 |
| Boot Mode / Quiet Boot | Diagnostics 或 Disabled | 关键项:关掉厂商 Logo 显示,换成 BIOS 自检白字 |
| Boot Order | 你的安装介质排第一 | 装机阶段用 U 盘启动 |
效果
关 Quiet Boot 前: 厂商 Logo(红底/蓝底) → GRUB → 系统
关 Quiet Boot 后: 黑底白字自检 → GRUB → 系统
不同厂商的叫法不同:
- Lenovo:
Boot Mode→Diagnostics(关掉红底 Lenovo Logo) - Dell: 关掉
Logo或Quiet Boot - ASUS: 关掉
Boot Logo Display - 其他品牌:找
Quiet Boot或Boot Logo相关的开关,禁用即可
二、GRUB 超时 + 系统启动参数
2.1 GRUB 超时调到 1 秒
sudo sed -i 's/GRUB_TIMEOUT=5/GRUB_TIMEOUT=1/' /etc/default/grub
sudo grub-mkconfig -o /boot/grub/grub.cfg
2.2 关于 Arch 开机 Logo
Arch Linux 默认安装不会显示一个“Arch Logo“——但如果你用了 archinstall 脚本,并且选择了 UKI (Unified Kernel Image) 选项,那么 mkinitcpio 的 preset 里会默认加上一个 --splash 参数,把 Arch 的 logo 图片嵌入到 UKI 二进制里。开机时这个 logo 会闪一下。
如果你看到这个 logo 并且想关掉它,编辑 /etc/mkinitcpio.d/linux.preset,删除 --splash /usr/share/systemd/bootctl/splash-arch.bmp 参数,然后重新生成:
sudo mkinitcpio -p linux
三、内核参数:关掉欢迎消息和 Plymouth
3.1 关掉 systemd-fsck 的欢迎消息
在 /etc/default/grub 的 GRUB_CMDLINE_LINUX_DEFAULT 中追加:
quiet loglevel=3 udev.log_priority=3
quiet:关掉内核大部分日志输出loglevel=3:只显示 KERN_ERR 及以上级别的内核消息udev.log_priority=3:关掉 udev 的设备发现日志
3.2 不要装 Plymouth
Plymouth 是开机动画框架,哪怕你只想要个简单的启动进度条,它也会引入额外的依赖(drm、framebuffer 等),并且会延迟进入登录管理器的时间。不装 Plymouth,系统直接从内核日志切到 getty 或显示管理器,反而更快,也符合“纯字符“的审美。
四、getty 静默(不显示开机信息)
如果你不想在登录前看到任何系统日志,编辑 /etc/systemd/system/getty@tty1.service.d/override.conf:
[Service]
ExecStart=
ExecStart=-/sbin/agetty -o '-p -- \\u' --noclear - $TERM
--noclear 让 getty 启动不清屏,所以之前的内核日志会保留在屏幕上。如果你想要一个“纯黑屏 + 登录提示符“,可以去掉 --noclear,或者干脆在 getty 启动前加一条 ExecStartPre=/usr/bin/clear。
五、显示管理器(DM) vs 直接启动 Sway
5.1 选择:无 DM,直接 ttys 登录后 exec sway
Sway 不需要显示管理器。在 ~/.bash_profile 或 ~/.zprofile 中:
if [ -z "$WAYLAND_DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
exec sway
fi
这样开机后自动登录 tty1,然后自动启动 Sway。全程没有图形界面加载过渡,只有黑底白字的登录提示。
5.2 如果你确实需要 DM
用 lightdm + lightdm-gtk-greeter,关掉 greeter 的背景图:
# /etc/lightdm/lightdm-gtk-greeter.conf
[greeter]
background=
theme-name=Adwaita
icon-theme-name=Adwaita
font-name=Sans 10
六、最终开机链
电源键 → UEFI 黑底白字自检(0.5s)
→ GRUB 菜单(1s 超时自动选默认)
→ 内核解压 + 系统初始化(无 Plymouth)
→ 黑屏(0.5s)
→ tty1 登录提示(纯字符)
→ 输入密码
→ exec sway
→ 进入 Sway 工作区
从按电源键到进入 Sway,全程没有一张图片、没有一个大 Logo,只有黑底白字。
XFCE 桌面环境 + lightdm 显示管理器包列表参考
本文档记录从 Arch Linux 包列表仓库移除的 XFCE 环境相关包,如需要在新机器上安装 XFCE 环境,可参考此列表。
显示管理器
- lightdm
- lightdm-gtk-greeter
XFCE 桌面核心
- exo
- garcon
- xfce4-session
- xfce4-settings
- xfce4-panel
- xfwm4
- xfdesktop
- xfconf
- xfce4-notifyd
- xfce4-power-manager
- xfce4-terminal
- xfce4-screensaver
- xfce4-screenshooter
- xfce4-appfinder
XFCE 文件管理器
- thunar
- thunar-archive-plugin
- thunar-media-tags-plugin
- thunar-volman
- tumbler
XFCE 面板插件
- xfce4-battery-plugin
- xfce4-clipman-plugin
- xfce4-cpufreq-plugin
- xfce4-cpugraph-plugin
- xfce4-dict
- xfce4-diskperf-plugin
- xfce4-eyes-plugin
- xfce4-fsguard-plugin
- xfce4-genmon-plugin
- xfce4-mailwatch-plugin
- xfce4-mount-plugin
- xfce4-mpc-plugin
- xfce4-netload-plugin
- xfce4-notes-plugin
- xfce4-places-plugin
- xfce4-pulseaudio-plugin
- xfce4-systemload-plugin
- xfce4-taskmanager
- xfce4-time-out-plugin
- xfce4-timer-plugin
- xfce4-verve-plugin
- xfce4-volumed-pulse
- xfce4-wavelan-plugin
- xfce4-weather-plugin
- xfce4-whiskermenu-plugin
- xfce4-xkb-plugin
XFCE 应用
- mousepad
- parole
- ristretto
- xfburn
嵌入式 Python Windows 版 pip 安装教程 (python-embed)
本文与嵌入式硬件开发无关。
操作系统支持
本教程已在以下操作系统上测试成功:
- Windows 11
- Windows 10
- Windows Server 2016
- Windows Server 2012 R2
版本选择
| Python 版本 | 状态 | 备注 |
|---|---|---|
| 3.12, 3.11, 3.10 | 正常 | 推荐使用 |
| 3.9, 3.8 | 可能出现 SSL 错误 | 解决办法:换网络、换节点 |
下载
- 从 python.org 下载
python-3.x.x-embed-amd64.zip - 从 https://bootstrap.pypa.io/get-pip.py 下载
get-pip.py
解压:将 python-3.x.x-embed-amd64.zip 解压到任意目录,建议使用压缩软件的“以文件名创建文件夹“功能,会自动创建 python-3.x.x-embed-amd64 文件夹。
安装步骤
-
配置网络代理(如需要)
HTTP/Socks 代理:
临时设置代理环境变量(仅对当前会话有效):
# HTTP 代理 $env:HTTP_PROXY="http://proxy.example.com:8080" $env:HTTPS_PROXY="http://proxy.example.com:8080" # Socks 代理 $env:HTTP_PROXY="socks5://proxy.example.com:1080" $env:HTTPS_PROXY="socks5://proxy.example.com:1080"Tun 模式代理:
- 在您使用的代理客户端中找到 Tun/Tunnel/虚拟网卡相关选项并启用
- 不同客户端的操作方式各异,请参考各自软件的文档
- 启用后通常无需额外配置环境变量
-
运行 get-pip.py
Set-Location "PATH TO YOUR LOCATION\python-3.x.x-embed-amd64" .\python.exe .\any-path\get-pip.py- 可以观察到自动创建了
Lib、Scripts目录
- 可以观察到自动创建了
-
编辑配置文件
- 在
python-3.x.x-embed-amd64目录下找到python3xx.pth或python3xx._pth文件(具体文件名取决于 Python 版本) - 打开文件,找到
#import site所在行,去掉前面的#,改为import site
- 在
pip 使用方法
-
使用 Python 模块方式
Set-Location "PATH TO YOUR LOCATION\python-3.x.x-embed-amd64" .\python.exe -m pip -
使用可执行文件方式
Set-Location "PATH TO YOUR LOCATION\python-3.x.x-embed-amd64" .\Scripts\pip.exe
本文目的
本文旨在向中文用户介绍如何在 Windows 上使用 python-embed(嵌入式 Python),以文件夹形式将 Python 运行环境(解释器)与 Python 程序一并打包。这种方式的优势:
- 无需编译为 exe 单文件:避免了使用打包工具生成的包含
.pyc文件的可执行文件,这些文件无法被人类直接阅读或编辑 - 保证代码可编辑性:所有代码保持原始的
.py格式,随时可以修改和调试 - 不依赖 Git:对于不熟悉 Git 版本控制的用户友好
- 适合私有项目:特别适合编写不开源的私有项目,无需处理版本控制的复杂性
- 便于传播:可以在 Windows x64 系统间随意复制和执行,对大陆售卖的“挂机宝“(提供 RDP 的低价 NAT 虚拟机)等环境非常友好
注意事项
- 关于依赖管理:如果需要更现代的依赖管理工具,可以考虑 uv,但请注意 uv 更适合开源项目开发或生产环境部署,可能与本文“便于在不同系统间传播“的核心思想不完全一致
- 网络要求:安装依赖时可能需要访问境外服务器,建议配置代理以获得更好的下载速度
7-Zip SFX 标准打包规范:为何 7zSD.sfx 是唯一正确的安装型 Stub
1) 铁律(Agent/新人应缓存的结论)
7z.sfx(标准 SFX)只能“解压“:不解析RunProgram,不执行安装流程。7zSD.sfx(安装型 SFX)才能“解压 + 执行“:支持RunProgram / InstallPath / Silent / GUIMode。
90% 的“配置无效 / 弹 Extract-to“问题,根因只有一个:Stub 用错。
2) 选型对照表(可 RAG 直抽)
| 属性 | 标准版(不要用) | 安装版(应使用) |
|---|---|---|
| 文件名 | 7z.sfx | 7zSD.sfx |
| 典型大小 | ~38 KB | ~120–160 KB |
| 能力 | 解压 | 解压 + 执行 |
RunProgram | ❌ 被忽略 | ✅ 有效 |
| 典型症状 | 必然弹 “Extract to…” 对话框 | 可按配置走静默/对话框 |
3) 最小正确配置文件(UTF-8 无 BOM)
;!@Install@!UTF-8!
Title="Internet Cafe Customizer"
InstallPath=".\\ "
RunProgram="scripts\\batch\\sfx_entry.bat"
;!@InstallEnd@!
关键约束:
InstallPath=".\\ ":解压到 EXE 所在目录(SFX 以自身路径为基准)RunProgram路径:相对于解压根目录- 配置文件必须 UTF-8 无 BOM(带 BOM 会解析异常)
4) 标准构建(PowerShell,确定性最高)
$stub = "tools\7z\7zSD.sfx"
$cfg = "configs\sfx_config.txt"
$archive = "build\temp.7z"
$outExe = "dist\installer.exe"
# 1) 打 7z 归档(示例)
& "tools\7z\7za.exe" a -t7z $archive (Join-Path PWD payload) -mx=9
# 2) 二进制拼接:stub + config + 7z
[byte[]]$bin =
[IO.File]::ReadAllBytes($stub) +
[IO.File]::ReadAllBytes($cfg) +
[IO.File]::ReadAllBytes($archive)
[IO.File]::WriteAllBytes($outExe, $bin)
Write-Host "Built: $outExe"
不建议依赖
copy /b的隐式编码表现;PowerShell 拼接更可审计、可复现。
5) 30 秒自检(判断手里 Stub 对不对)
用最小 config:
;!@Install@!UTF-8!
RunProgram="cmd.exe"
;!@InstallEnd@!
拼接后双击:
- 弹出 CMD 窗口 → Stub 支持安装型 ✅
- 弹出 “Extract to” 对话框 → 这是
7z.sfx,换7zSD.sfx❌
6) 快速排错矩阵
| 现象 | 根因 | 修复 |
|---|---|---|
| 必弹 Extract-to | Stub=7z.sfx | 换官方 7zSD.sfx |
| RunProgram 像不存在 | 同上(Stub 不吃安装参数) | 同上 |
| 双击完全无反应 | Stub 非官方/损坏/位数怪 | 用 7-Zip 安装目录或 LZMA SDK 的 bin\7zSD.sfx |
| 解压成功但脚本找不到文件 | 工作目录/相对路径错 | bat 首行 cd /d "%~dp0";核对归档内路径 |
修复 7-Zip SFX RunProgram 失效:正确选型与诊断矩阵
1) 典型错误描述(人/搜索引擎/Agent 都会这么说)
- “我写了
RunProgram,双击却只弹 Extract to。” - “
Silent=2/GUIMode=2写了也像没写。” - “双击 完全没反应,没有任何窗口。”
2) 唯一最重要的原因
7z.sfx(标准 SFX)不实现安装执行管线。
只要 Stub 是它,以下字段等效于不存在:
RunProgramExecuteFileInstallPath(不会按安装型语义处理)Silent / GUIMode / OverwriteMode
于是你一定看到“纯解压器 UI“。
3) 诊断矩阵(可直接匹配症状)
| 症状 | 立刻检查 | 结论 |
|---|---|---|
| 必弹 “Extract to…” | Stub 文件名/大小(应为 7zSD.sfx ~120–160KB) | 误用 7z.sfx → 换 Stub |
| 双击无窗口/无报错 | Stub 是否官方 7zSD.sfx | 非官方/损坏 → 用 LZMA SDK/7-Zip 官方 bin |
| 解压完脚本不跑 | 归档内是否存在该文件、路径大小写、bat 工作目录 | 用 cd /d "%~dp0" + 相对路径核验 |
| “参数像无效” | 配置文件编码 | 必须是 UTF-8 无 BOM(BOM 会导致解析退化) |
4) 验证配置文件编码(PowerShell)
$b = [IO.File]::ReadAllBytes("configs\sfx_config.txt")
if ($b[0] -eq 0xEF -and $b[1] -eq 0xBB -and $b[2] -eq 0xBF) {
Write-Warning "BOM detected — save as UTF-8 without signature"
} else { Write-Host "No BOM (OK)" }
5) 最小修复步骤(按顺序)
- 拿 官方
7zSD.sfx(7-Zip 安装目录bin\7zSD.sfx或 LZMA SDK) - 确保 config = UTF-8 无 BOM
- 用 PowerShell 拼接(别信
copy /b的黑魔法) - 用下面“cmd.exe 自检“确认 Stub 没被换错
6) 自检 Stub(诊断锚)
;!@Install@!UTF-8!
RunProgram="cmd.exe"
;!@InstallEnd@!
- 弹 CMD → Stub OK
- 弹 Extract to → Stub WRONG(标准版)
LLM/CI 部署指南:构建静默 7-Zip SFX 安装包(无对话框)
1) 输入/输出契约
- Stub(固定):
tools/7z/7zSD.sfx(安装型,~128KB) - Config:
configs/sfx_config.txt(UTF-8 无 BOM) - Payload:打进
temp.7z(必须是 7z,不是 zip) - Output:
dist/deploy.exe(双击 = 解压 + 执行)
2) Config(完全静默模板)
;!@Install@!UTF-8!
Title="Silent Deploy"
InstallPath=".\\ "
GUIMode="2"
Silent="2"
OverwriteMode="2"
RunProgram="setup.bat"
;!@InstallEnd@!
说明:
GUIMode="2"+Silent抑制 UIInstallPath=".\\ "→ 解压到 EXE 同目录setup.bat必须在归档内,且内部用%~dp0定位自身
3) 构建脚本(Agent/CI 可稳定复现)
param(
[string]$Stub = "tools\7z\7zSD.sfx",
[string]$Cfg = "configs\sfx_config.txt",
[string]$Archive = "build\temp.7z",
[string]$Out = "dist\deploy.exe"
)
[byte[]]$bin =
[IO.File]::ReadAllBytes($Stub) +
[IO.File]::ReadAllBytes($Cfg) +
[IO.File]::ReadAllBytes($Archive)
[IO.File]::WriteAllBytes($Out, $bin)
Write-Host "Built: $Out"
4) 验证协议(可写进 CI check)
- PE 头 sanity:stub 段应以
MZ开头 - Stub size sanity:
7zSD.sfx通常在 100–200KB - Runtime smoke:用
RunProgram=cmd.exe自检,确保不退化为 Extract-to
5) 约束(避免幻觉)
- 内部归档必须是 7z(
-t7z),不是 zip - Windows Explorer 不会把它当 zip;但 7-Zip/WinRAR 能解包
- 想“原生可右击解压“就直接发 zip 便携包,别试图把 SFX 伪装成 zip
设置 Zed 为 .bat 文件的默认编辑器
概述
本文档介绍如何配置 Windows 10 中 .bat 文件的右键菜单,实现以下效果:
- 双击 .bat 文件:保持默认行为,使用 Windows Terminal 执行脚本
- 右键菜单“编辑“:使用 Zed 编辑器打开文件
实现原理
Windows 文件关联机制
Windows 通过注册表管理文件关联,优先级顺序为:
HKCU\Software\Classes > HKLM\Software\Classes
.bat 文件的默认关联为 batfile,双击时会执行 cmd.exe /c xxx.bat。我们只需要修改右键菜单的“编辑“选项,不影响默认执行行为。
配置步骤
方法一:使用 PowerShell 脚本(推荐)
创建并运行以下 PowerShell 脚本:
# 设置 .bat 文件右键"编辑"用 Zed 打开
$zedPath = "C:\Users\user\AppData\Local\Programs\Zed\Zed.exe"
# 创建右键菜单"编辑"选项
reg add "HKCU\Software\Classes\batfile\shell\edit" /ve /d "编辑" /f
reg add "HKCU\Software\Classes\batfile\shell\edit\command" /ve /d "`"$zedPath`" `"%1`"" /f
Write-Host "配置完成!"
方法二:手动修改注册表
-
打开注册表编辑器:
regedit -
导航到以下路径:
HKEY_CURRENT_USER\Software\Classes\batfile\shell\edit -
设置默认值为
编辑 -
在
edit下创建command子键,设置默认值为:"C:\Users\user\AppData\Local\Programs\Zed\Zed.exe" "%1"
验证配置
检查注册表设置
reg query "HKCU\Software\Classes\batfile\shell\edit" /s
预期输出:
HKEY_CURRENT_USER\Software\Classes\batfile\shell\edit
(默认) REG_SZ 编辑
HKEY_CURRENT_USER\Software\Classes\batfile\shell\edit\command
(默认) REG_SZ "C:\Users\user\AppData\Local\Programs\Zed\Zed.exe" "%1"
测试效果
- 双击 .bat 文件:应该打开 Windows Terminal 并执行脚本
- 右键 .bat 文件 → 编辑:应该用 Zed 打开文件
注意事项
UserChoice 优先级问题
如果修改后没有生效,可能是 UserChoice 设置覆盖了你的配置。需要删除:
Remove-Item -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.bat\UserChoice" -Force -ErrorAction SilentlyContinue
重启资源管理器
修改注册表后,建议重启文件资源管理器使设置生效:
Stop-Process -Name explorer -Force
Start-Process explorer
迁移到其他编辑器
如果以后更换编辑器(如 VS Code、Notepad++ 等),只需修改 command 路径:
reg add "HKCU\Software\Classes\batfile\shell\edit\command" /ve /d "`"C:\Program Files\Microsoft VS Code\Code.exe`" `"%1`"" /f
常见问题
右键菜单显示乱码
问题原因:注册表中菜单文本编码错误。
解决方案:
reg delete "HKCU\Software\Classes\batfile\shell\edit" /f
reg add "HKCU\Software\Classes\batfile\shell\edit" /ve /d "编辑" /f
修改后双击行为改变
问题原因:可能错误修改了 .bat 的默认打开程序。
解决方案:恢复默认关联:
reg add "HKCU\Software\Classes\.bat" /ve /d "batfile" /f
总结
通过修改 HKCU\Software\Classes\batfile\shell\edit 注册表项,我们实现了:
- 保持
.bat文件的默认执行行为(使用 Windows Terminal) - 右键“编辑“选项使用 Zed 打开
- 配置不影响系统级设置(仅影响当前用户)
这种方式既满足了编辑需求,又不破坏脚本的正常执行功能。
Windows 终端环境终极改造:PowerShell 7、Nushell、Git Bash 并存指南
一、前言:为什么我的终端这么乱?
在 Windows 上做开发,我们往往会积累一堆 Shell,它们各自为政,互不相通:
- CMD:古老,但某些老脚本离不开。
- PowerShell 5.1:系统内置,兼容性好。
- PowerShell 7:跨平台,性能强,是现代开发的主力生产力工具。
- Git Bash:为了那一套 GNU 工具链(grep, sed, awk)和 SSH。
- Nushell:新兴的结构化 Shell,用来替代传统的 ls 和 find。
痛点在于:它们之间互相“隔阂“。在 Git Bash 里装的包,PowerShell 找不到;在 PowerShell 里设置的变量,Nushell 不认识。本文将分享如何将这些 Shell 整合成一套互不干扰、又能互相打通的高效环境。
二、环境基线:先理清现状
在开始改造前,首先要明确系统中存在的 Shell 及其路径。混乱往往源于路径不明确。
| Shell | 命令 | 路径 | 现状 |
|---|---|---|---|
| PowerShell 7 | pwsh | C:\Program Files\PowerShell\7\pwsh.exe | ✅ 主力 |
| PowerShell 5.1 | powershell | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe | ✅ 保底 |
| Nushell | nu | C:\Users\user\scoop\shims\nu.exe | ✅ 新宠 |
| Git Bash | bash | C:\Program Files\Git\bin\bash.exe | ✅ 工具链 |
| CMD | cmd | C:\Windows\system32\cmd.exe | ⏸️ 备用 |
三、核心难点:跨 Shell 调用与 PATH 冲突
最大的坑在于跨 Shell 调用。例如,在 Nushell 里调用 Git Bash 的命令,或者在 PowerShell 里启动 Nushell。
1. 路径格式的差异
- PowerShell/Nu:
C:\Users\... - Git Bash:
/c/Users/...
2. 命令查找机制
如果在 Git Bash 里直接敲 nu,它可能找不到,因为 Scoop 的 shims 目录没进 Git Bash 的 PATH。
3. 解决方案:封装与 Alias
为了保证在任何地方都能敲 bash 就能进 Git Bash,在 PowerShell 和 Nushell 中做了封装。
PowerShell 7 配置 (Microsoft.PowerShell_profile.ps1):
# 全局函数,确保在任何目录下都能调用 Git Bash
function global:bash {
$bashPath = "C:\Program Files\Git\bin\bash.exe"
if (Test-Path $bashPath) {
& $bashPath @args
} else {
Write-Error "Git Bash not found at $bashPath"
}
}
Nushell 配置 (config.nu):
# 封装 Git Bash 调用
def bash [...args: string] {
^"C:\Program Files\Git\bin\bash.exe" ...$args
}
四、Windows Terminal 配置:统一启动入口
Windows Terminal 是统一管理多个 Shell 的最佳工具,通过 settings.json 配置:
{
"profiles": {
"list": [
{
"name": "PowerShell 7",
"commandline": "pwsh.exe",
"icon": "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
"startingDirectory": "."
},
{
"name": "Nushell",
"commandline": "nu.exe",
"icon": "C:\\Users\\user\\scoop\\shims\\nu.exe",
"startingDirectory": "."
},
{
"name": "Git Bash",
"commandline": "\"C:\\Program Files\\Git\\bin\\bash.exe\" --login -i",
"icon": "C:\\Program Files\\Git\\mingw64\\share\\git\\git-for-windows.ico",
"startingDirectory": "."
}
]
}
}
五、PATH 统一管理策略
5.1 系统级 PATH(用户变量)
确保所有 Shell 的基础路径在系统级 PATH 中统一。编辑用户环境变量,添加:
C:\Program Files\PowerShell\7\
C:\Users\user\scoop\shims\
C:\Program Files\Git\bin\
C:\Program Files\Git\usr\bin\
5.2 Shell 专有路径
各 Shell 特有的路径在各自的 profile 中追加,不要污染系统级 PATH。
PowerShell 7 - Microsoft.PowerShell_profile.ps1:
$env:Path = [System.Environment]::GetEnvironmentVariable('Path','User')
$env:Path += ";$env:LocalAppData\Microsoft\WindowsApps"
Nushell - config.nu:
$env.PATH = ($env.PATH | split row (char esep) | prepend [
'C:\Program Files\PowerShell\7',
'C:\Users\user\scoop\shims',
'C:\Program Files\Git\bin',
])
六、最终效果
改造完成后,可以实现:
- 在 Windows Terminal 中一键切换 PowerShell 7 / Nushell / Git Bash
- 在任何 Shell 中敲
bash即可进入 Git Bash 环境 - 在任何 Shell 中敲
nu即可进入 Nushell - 各 Shell 的 PATH 互不污染,但基础命令互通
- 跨 Shell 调用时路径格式自动适配
七、踩坑备忘
坑 1:Git Bash 的 PATH 截断
Git Bash 启动时默认会截断 Windows 系统 PATH,只保留 /usr/bin 等 Unix 路径。解决方案是在 ~/.bashrc 中显式追加 Windows PATH:
export PATH="$PATH:/c/Users/user/scoop/shims"
坑 2:Nushell 的 PATH 是列表不是字符串
Nushell 的 $env.PATH 是 List 类型,不是字符串。不能用 $env:PATH += ";" 这种方式,必须用 split row 和 append / prepend 操作。
坑 3:PowerShell 7 的执行策略
PowerShell 7 默认执行策略是 Restricted,需要设为 RemoteSigned 才能加载 profile:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
受限网络下 Windows 10 LTSB 远程管理踩坑实录:OpenSSH 走不通,WinRM 救场
一、场景与约束
一台部署在云端的 Windows 10 Enterprise 2016 LTSB(Build 14393),QEMU/KVM 虚拟机,内网静态 IP 10.178.16.44。平台已配置端口映射:
公网 <public-ip>:<nat-port> → 内网 10.178.16.44:22
硬性约束(决定了一切技术选型):
- 禁止部署第三方代理/隧道类软件:Tailscale / ZeroTier / frp / nps / WireGuard 等一律不可用,违反即封号
- 外网访问受限:机器能访问的资源极少,GitHub、7-zip.org、SourceForge 等主流下载源均不可达
- RDP 传文件不稳:>1MB 的文件传输频繁报“内部错误“,只适合传几百 K 的文本/小文件
- 系统老旧:LTSB 14393 被厂商阉割了大量可选组件(无 Telnet Server、无 OpenSSH Capability)
- 工作环境:Mac 侧使用 nushell + uv,希望尽量保持 “just paste” 工作流,少装全局依赖
目标:让 Mac 能像 ssh win10 那样直连 Windows 执行 PowerShell 命令。
二、失败的尝试
❌ 尝试 1:Windows 原生 OpenSSH Server
LTSB 14393 的 Get-WindowsCapability 里根本没有 OpenSSH.Server 条目,DISM 也找不到对应 capability。只能走手动下载 ZIP/MSI 的路。
国内镜像尝试:
cyberlite.com.cn的 MSI → 404cnblogs.com的OpenSSH-Win64.7z→ 下载成功(5MB)- GitHub 官方 ZIP → 连接超时
死结:.7z 需要 7-Zip 解压,但系统没装 7-Zip,且 7-Zip 官网、各大镜像站(阿里云、华为云、腾讯云)全部不可达。Windows 10 LTSB 14393 也没有 tar.exe(该命令 1803 才加入)。
⚠️ 坑点 1:封闭环境里,“下载一个解压工具“这种在普通环境里 1 分钟的活,在这里是死循环——解压需要工具,工具下载需要网络,网络又不通。
❌ 尝试 2:Windows 原生 Telnet Server
DISM 启用 TelnetServer 特性 → 特性不存在。Windows 10 系列只有 Telnet Client,Server 组件被完全移除。
❌ 尝试 3:传文件破局
从 Mac 传 7za.exe 或其他文件会破坏 just paste 工作流,且不符合“不为老系统额外提供文件“的原则。此路堵死。
三、WinRM:系统原生的救命通道
转机在于:WinRM(WS-Management)是 Windows 原生组件,LTSB 14393 自带,默认禁用但无需下载任何东西。
关键思路:
- WinRM 默认监听 5985,但我们可以改监听端口为 22,完美复用已有的 NAT 映射
<nat-port>→22 - 纯 PowerShell 命令启用,不需要网络下载
- Mac 侧用 Python
pywinrm连接,通过uv run --with pywinrm临时加载,不污染系统环境
3.1 Windows 侧启用脚本(纯 ASCII,避免 PowerShell 5.1 中文乱码)
# Requires -RunAsAdministrator
# 1. 启动 WinRM 服务并设为自动启动
sc config winrm start= auto
net start winrm
# 2. 删除默认 5985 监听器,创建 22 端口监听器
winrm delete winrm/config/listener?Address=*+Transport=HTTP
winrm create winrm/config/listener?Address=*+Transport=HTTP `@Port=22
# 3. 允许 Basic 认证(Mac 客户端兼容)
winrm set winrm/config/service/auth `@Basic="true"
winrm set winrm/config/service `@AllowUnencrypted="true"
winrm set winrm/config/client `@TrustedHosts="*"
# 4. 防火墙放行 22 端口
New-NetFirewallRule -Name "WinRM-Server-In-TCP" `
-DisplayName "WinRM Server" `
-Direction Inbound -Protocol TCP `
-LocalPort 22 -Action Allow -Profile Any
# 5. 关闭 UAC 远程限制(允许本地管理员远程登录)
$reg = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"
New-ItemProperty -Path $reg -Name "LocalAccountTokenFilterPolicy" `
-Value 1 -PropertyType DWord -Force
💡 坑点 2:PowerShell 里
winrm set命令的@{...}语法必须写成`@{...}(反引号转义),否则 PowerShell 会把@当成数组操作符报错。这是 WinRM 配置脚本最常见的语法坑。
⚠️ 坑点 3:如果网络位置被识别为 Public(LTSB 默认),
winrm quickconfig会失败:“由于此计算机上的网络连接类型之一设置为公用,因此 WinRM 防火墙例外将不运行”。需先用Set-NetConnectionProfile -NetworkCategory Private改为专用网络,或像上面脚本那样直接手动建防火墙规则绕过。
3.2 验证 Windows 侧
Get-Service winrm # 应为 Running, Automatic
Get-NetTCPConnection -LocalPort 22 # 应看到 LISTENING
winrm enumerate winrm/config/listener # 应看到 Port=22 的 HTTP 监听器
3.3 Mac 侧连接(uv + pywinrm,零全局安装)
uv run --with pywinrm python -c '
import winrm
s = winrm.Session("http://<public-ip>:<nat-port>",
auth=("Administrator", "你的密码"))
print(s.run_cmd("powershell Get-Service winrm").std_out.decode())
'
输出:
Status Name DisplayName
------ ---- -----------
Running winrm Windows Remote Management (WS-Manag...
✅ 全链路通了。
3.4 交互式 PowerShell(最接近的 ssh 体验)
保存为 winrm_sh.py:
#!/usr/bin/env python3
"""用 uv 临时加载 pywinrm 并进入交互 shell"""
import winrm
import sys
s = winrm.Session("http://<public-ip>:<nat-port>",
auth=("Administrator", "你的密码"))
print("Connected to Windows via WinRM. Type exit to quit.")
def decode(b):
if not b:
return ""
for enc in ("utf-8", "gbk", "latin-1"):
try:
return b.decode(enc)
except Exception:
continue
return b.decode("utf-8", errors="replace")
while True:
try:
cmd = input("PS> ")
if cmd.lower() == "exit":
break
r = s.run_cmd(f"powershell -Command \"{cmd}\"")
if r.std_out:
print(decode(r.std_out).rstrip())
if r.std_err:
print(decode(r.std_err).rstrip(), file=sys.stderr)
except KeyboardInterrupt:
break
except Exception as e:
print(f"Error: {e}")
运行:uv run --with pywinrm python winrm_sh.py
💡 坑点 4:pywinrm 的 Response 对象错误属性名是
std_err(带下划线),不是stderr。且 Windows 中文输出是 GBK 编码,必须按 GBK→UTF-8 顺序尝试解码,否则中文变?。
四、完整坑点总结
| 坑点 | 现象 | 根因 | 解决方案 |
|---|---|---|---|
| 1 | 下载 7-Zip 解压 OpenSSH.7z 失败 | 外网全墙,仅 cnblogs 可达 | 放弃 OpenSSH,改用原生 WinRM |
| 2 | winrm set 命令报语法错误 | PowerShell 把 @{ 解析为数组 | 反引号转义:`@{ |
| 3 | WinRM 防火墙规则不生效 | 网络位置为 Public | 手动 New-NetFirewallRule 绕过 |
| 4 | 中文输出乱码 ? | Windows 用 GBK,Python 默认 UTF-8 | 按 utf-8→gbk→latin-1 顺序解码 |
| 5 | response.stderr 属性错误 | pywinrm API 是 std_err | 改用 r.std_err |
| 6 | Telnet Server 不存在 | Win10 系列已移除该组件 | 改用 WinRM |
| 7 | tar.exe 不存在 | 该命令 1803 才加入 | 不用 tar,直接 WinRM |
| 8 | pip3 install 被拒 | macOS PEP 668 外部托管环境 | 用 uv run --with pywinrm 临时环境 |
五、为什么 WinRM 是这个场景的最优解
- 合规性:WinRM 是 Windows 系统原生管理组件,不属于“代理/内网穿透“范畴,不会触发平台风控
- 零文件传输:纯 PowerShell 命令启用,不需要从 Mac 传任何文件
- 复用已有 NAT:监听端口改为 22,直接对接平台已配置的
<nat-port>→22映射 - Mac 侧零污染:
uv run --with pywinrm临时拉起隔离环境,不写全局 site-packages - 完整 PowerShell 体验:远程执行 PS 5.1 cmdlet,能力等同本地
- 稳定性:系统服务级组件,重启自动拉起
六、安全建议
⚠️ 当前配置为快速连通,允许了 Basic 认证和明文传输。生产环境建议:
- 在 Windows 侧用
winrm set关闭AllowUnencrypted,配置 HTTPS 监听器 + 自签证书- 防火墙规则收窄
RemoteAddress为 Mac 所在网段- 定期更换 Administrator 密码
- 不用时
Stop-Service winrm关闭服务
七、结语
在受限网络环境里做远程管理,最大的认知转变是:放弃“下载一个更好的工具“的执念,回归系统原生能力。OpenSSH 虽好,但在这个环境里就是装不上;Telnet 虽老,组件已被砍掉;唯有 WinRM,从 Windows Vista 起就是系统标配,静静躺在那里等待被启用。
配合 Mac 上 uv 这个现代 Python 包管理器,“临时拉取依赖 + 执行 + 清理“的模式,完美兼顾了能力与整洁。整套方案从 Windows 启用到 Mac 连接,没有传一个文件,没有装一个全局包,没有碰任何第三方代理软件——这才是受限网络下该有的技术姿态。
📌 适用场景:Windows 7/8/10/11 全系列(WinRM 原生支持)、禁止第三方组网软件的环境、外网受限的隔离网络、需要通过已有端口映射做远程管理的所有情况。
Windows 下通过 Scoop 安装 Nushell:完整指南
1. 环境基线
在开始安装 Nushell 之前,请确保系统环境满足以下要求:
- 操作系统: Windows 10 或 Windows 11 (版本 22H2 或更高)
- 现有 Shell: Git Bash (MINGW64)
- 目标 Shell: Nushell 0.113.1
- 包管理器: Scoop
- 网络环境: 本地代理(例如: 127.0.0.1:PORT)
2. 核心安装逻辑
2.1 前置检查
首先,确认系统上是否已安装 Winget、Chocolatey 或 Scoop。本指南将使用 Scoop 作为包管理器。
2.2 Scoop 安装
在 Git Bash 中执行以下步骤:
- 设置执行策略:必须将 PowerShell 的执行策略设置为 RemoteSigned。
- 配置网络代理:这是关键步骤,必须同时设置 PowerShell 环境变量和 .NET 底层的代理。
安装 Scoop 的核心命令如下:
# 在 Git Bash 中,必须通过 powershell.exe 调用 PowerShell 命令
powershell.exe -Command "Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force"
powershell.exe -Command "Invoke-RestMethod get.scoop.sh | Invoke-Expression"
2.3 Nu 安装
Scoop 安装成功后,使用以下命令安装 Nushell:
scoop install nu
2.4 IDE 集成
安装完成后,Nushell 的可执行文件位于 Scoop 的 shims 目录下。在 IDE(如 VS Code、Lingma 等)的终端设置中,需要指向此路径。
3. 关键路径变量
| 项目 | 路径 |
|---|---|
| Scoop 根目录 | C:\Users\YourUserName\scoop\ |
| Nu 二进制文件 | C:\Users\YourUserName\scoop\shims\nu.exe |
| Nu 配置文件 | C:\Users\YourUserName\AppData\Roaming\nushell\config.nu |
| IDE 设置文件 | C:\Users\YourUserName\AppData\Roaming\Lingma\User\settings.json |
4. 核心踩坑点与解决方案
坑点 1: Shell 语法混淆
现象: 在 Git Bash 中直接执行 $env:HTTP_PROXY=... 报错。
根因: Git Bash 是 Unix-like Shell,不识别 PowerShell 的 $env: 语法。
解法: 在 Git Bash 中通过 powershell.exe -Command 调用 PowerShell 命令,不要直接在 Git Bash 里写 PowerShell 语法。
坑点 2: 代理配置不生效
现象: 设置 HTTP_PROXY 环境变量后,Scoop 下载仍然超时。
根因: Scoop 底层依赖 .NET 的 WebRequest,它不读取环境变量代理,需要单独设置 .NET 底层代理。
解法: 在 PowerShell 中同时设置环境变量和 .NET 底层代理:
# 设置 .NET 底层代理(关键步骤)
$proxy = New-Object System.Net.WebProxy('http://127.0.0.1:7897')
$proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
[System.Net.WebRequest]::DefaultWebProxy = $proxy
# 设置环境变量代理
$env:HTTP_PROXY = 'http://127.0.0.1:7897'
$env:HTTPS_PROXY = 'http://127.0.0.1:7897'
坑点 3: 配置文件路径不同
现象: 修改了 ~/.config/nushell/config.nu 后,新开标签页不生效。
根因: Windows 上 Nushell 的配置文件路径不在 ~/.config/nushell/,而在 %APPDATA%\nushell\config.nu。
解法: 使用 echo $nu.config-path 确认实际路径,然后编辑正确的文件。
坑点 4: Scoop 安装卡在下载
现象: 运行安装脚本后卡在下载 GitHub Release 阶段。
根因: 国内网络环境访问 GitHub 不稳定。
解法: 使用代理,并确保 .NET 底层代理也配置了(见坑点 2)。
5. 安装验证
安装完成后,在新终端中运行以下命令验证:
nu --version
# 应输出: 0.113.1 或类似版本号
如果一切正常,Nushell 就成功安装并可以通过 nu 命令启动了。
System Management 核心脚本存档
本文档记录了 Windows 系统管理相关的一组 PowerShell / Nu / Bash 脚本,涵盖文件关联、右键菜单清理、默认应用设置等场景。
1. 核心:.bat 关联(双击执行,右键 Zed 编辑)
文件:scripts/powershell/set-bat-assoc.ps1
# 硬编码:修改此处路径
$zedPath = "C:\Users\user\AppData\Local\Programs\Zed\Zed.exe"
# 恢复双击执行(关键:不改默认打开方式)
Set-ItemProperty -Path "HKCU:\Software\Classes\.bat" -Name "(default)" -Value "batfile" -Force
# 添加右键菜单项
$editPath = "HKCU:\Software\Classes\batfile\shell\EditWithZed"
New-Item -Path $editPath -Force | Out-Null
Set-ItemProperty -Path $editPath -Name "(default)" -Value "用 Zed 编辑" -Force
# 绑定命令
$commandPath = "$editPath\command"
New-Item -Path $commandPath -Force | Out-Null
Set-ItemProperty -Path $commandPath -Name "(default)" -Value "`"$zedPath`" `"%1`"" -Force
# 清理系统缓存(必须:否则不生效)
Remove-Item -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.bat\UserChoice" -Recurse -Force -ErrorAction SilentlyContinue
# 刷新 Explorer
Stop-Process -Name explorer -Force; Start-Sleep 2; Start-Process explorer.exe
2. 兜底:强制默认应用(.bat + .txt)
文件:scripts/powershell/set-zed-defaults-full.ps1
$zedPath = "C:\Users\user\AppData\Local\Programs\Zed\Zed.exe"
function Set-FileAssociation {
param($ext, $progId)
# 写注册表
Set-ItemProperty -Path "HKCU:\Software\Classes\.$ext" -Name "(default)" -Value $progId -Force
Set-ItemProperty -Path "HKCU:\Software\Classes\$progId\shell\open\command" -Name "(default)" -Value "`"$zedPath`" `"%1`"" -Force
# 干掉 UserChoice 劫持
Remove-Item -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.$ext\UserChoice" -Recurse -Force -ErrorAction SilentlyContinue
}
Set-FileAssociation -ext "txt" -progId "Zed.txt"
Set-FileAssociation -ext "bat" -progId "Zed.bat"
# 刷新
Stop-Process -Name explorer -Force; Start-Sleep 2; Start-Process explorer.exe
3. 清理:右键菜单垃圾项
文件:scripts/powershell/clean-bat-menu.ps1
# 删除 .bat 右键菜单中不需要的项
$unwanted = @(
"HKCU:\Software\Classes\batfile\shell\opennew",
"HKCU:\Software\Classes\batfile\shell\runas"
)
foreach ($path in $unwanted) {
if (Test-Path $path) {
Remove-Item -Path $path -Recurse -Force
Write-Host "已删除: $path"
}
}
# 刷新
Stop-Process -Name explorer -Force; Start-Sleep 2; Start-Process explorer.exe
4. 脚本使用说明
前置条件
- 以管理员身份运行 PowerShell(部分注册表操作需要管理员权限)
- 执行策略需设为 RemoteSigned:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser - 修改
$zedPath变量为实际 Zed 安装路径
执行顺序
- 先运行
set-bat-assoc.ps1添加右键菜单选项 - 如果默认应用被劫持,运行
set-zed-defaults-full.ps1强制覆盖 - 运行
clean-bat-menu.ps1清理不需要的右键菜单项
注意事项
- 修改注册表有风险,建议先备份相关注册表项
UserChoice项是 Windows 用来记忆用户选择的,删除后系统会恢复默认关联- 刷新 Explorer 会使桌面短暂消失后重新出现,请保存好工作
Nushell + AtomCode 配置踩坑记录
摘要: 本文记录了在 macOS 上配置 Nushell 0.113.1 与 AtomCode 时遇到的 5 个典型坑,包括配置目录路径错误、验证方式误区、PATH 继承问题、配置项名称混淆以及默认值覆盖问题,并给出最终生效配置。
1. 背景
日期:2026-07-07
环境:macOS,Nushell 0.113.1(Homebrew),nu 在 /opt/homebrew/bin/nu
目标:① nushell 里能用 atomcode 命令;② 启动不显示 welcome message;③ 行末不显示时间
2. 坑 1:nushell 实际加载的配置目录不是 ~/.config/nushell/
2.1 现象
在 ~/.config/nushell/env.nu 里写 $env.PROMPT_COMMAND_RIGHT = {|| "" },毫无效果——行末时间照旧显示。
2.2 真相
macOS 上 nushell 0.113 加载的默认配置目录是:
~/Library/Application Support/nushell/
不是 ~/.config/nushell/(后者是 Linux 的路径)。确认当前实际加载目录:
nu
$nu.default-config-dir
# => /Users/<user>/Library/Application Support/nushell
~/.config/nushell/ 里那两个文件根本没被加载,写了等于白写。
2.3 修复
所有改动都放到 ~/Library/Application Support/nushell/ 下的 env.nu / config.nu。
3. 坑 2:nu -c 不加载 config.nu,用它验证配置会误判
3.1 现象
在 config.nu 里设了 $env.config.show_banner = false,跑 nu -c '$env.config.show_banner' 返回 true,看似没生效。
3.2 真相
nu -c '...'(非交互式脚本模式)根本不加载 config.nu,它读的是内置默认值。所以用 nu -c 查 $env.config.show_banner 永远是默认的 true,会让人误以为设置没生效。
3.3 正确验证方式
用 nu -e '...':执行命令后进入交互式 shell,会加载 config.nu。
nu -e 'print $"banner=($env.config.show_banner)"'
# => banner=false
或者用 expect 模拟 TTY 启动真交互式会话来肉眼确认。
4. 坑 3:新开终端标签页报 atomcode not found,但旧会话里好好的
4.1 现象
在已开的终端里 nushell 能找到 atomcode(which atomcode 命中 ~/.local/bin/atomcode)。但新开一个终端标签页后启动 nushell,跑 atomcode 报:
Error: nu::shell::external_command
× External command failed
· Command `atomcode` not found
4.2 真因
atomcode 在 ~/.local/bin/,该目录是被 ~/.zshrc 里 export PATH="~/.local/bin:$PATH" 加进 PATH 的。但 macOS 终端 App(Terminal/iTerm)新开标签页时未必重跑 ~/.zshrc——尤其当 shell 被设成「非登录」模式,或只读 ~/.zprofile(而该文件是空的)时,新标签页拿到的是 GUI 进程继承来的精简 PATH,不含 ~/.local/bin。
佐证:launchctl getenv PATH 输出为空,说明 GUI 进程层根本没设 PATH,新标签页的 PATH 全靠 zsh 启动文件,不保证跑 ~/.zshrc。
4.3 修复
不依赖外部 shell 是否跑过 ~/.zshrc,在 nushell 的 env.nu(每次启动无条件加载,早于 config.nu)里自己加 PATH:
# ~/Library/Application Support/nushell/env.nu
use std/util "path add"
path add "~/.local/bin"
验证(故意给个不含 ~/.local/bin 的精简外部 PATH,模拟最坏情况):
PATH=/opt/homebrew/bin:/usr/bin:/bin nu -e 'which atomcode | get command | print'
# => atomcode ← 仍可见
5. 坑 4:配置项名是 show_banner,不是 banner
5.1 现象
跑 nu -c '$env.config.banner' 报:
Error: nu::shell::name_not_found
· `-- did you mean 'show_banner'?
5.2 真相
关闭 welcome message 的配置项叫 show_banner(bool|string):
$env.config.show_banner = false # 不显示
$env.config.show_banner = true # 显示完整 banner(默认)
$env.config.show_banner = "short" # 只显示启动耗时
查文档:
config nu --doc | nu-highlight | less -R
6. 坑 5:GitHub issue #8698——show_banner 被文件末尾的默认值覆盖
6.1 现象
有人在 config.nu 开头写 $env.config.show_banner = false,banner 依然出现。
6.2 真因
nushell 安装时生成的默认 config.nu 文件末尾自带 $env.config.show_banner = true,写在开头的 false 被末尾的 true 覆盖。
6.3 修复
把自己的设置放在文件末尾,或删掉默认那行 true。可用 config nu 命令打开编辑器修改。
7. 最终生效配置
7.1 ~/Library/Application Support/nushell/env.nu
# 每次启动无条件加载,早于 config.nu
# 不依赖外部 shell 是否跑过 ~/.zshrc,自己确保 ~/.local/bin 在 PATH 里
use std/util "path add"
path add "~/.local/bin"
7.2 ~/Library/Application Support/nushell/config.nu(在注释段后追加)
# 关闭启动 welcome message
$env.config.show_banner = false
# 清空右侧 prompt,去掉行末时间段
$env.PROMPT_COMMAND_RIGHT = {|| "" }
8. 总结
- 配置放对目录:macOS 是
~/Library/Application Support/nushell/,不是~/.config/nushell/ - 验证用
nu -e,别用nu -c(后者不加载config.nu) - PATH 相关的放
env.nu(无条件加载),不依赖外部 shell 的~/.zshrc - 关 banner 的配置项是
$env.config.show_banner,注意被文件末尾默认值覆盖
WezTerm + Nushell + Cargo 找不到命令
摘要: 本文记录了在 macOS(Apple Silicon)环境下,wezterm 新标签页启动 nushell 后无法找到 cargo 命令的排查与修复过程。核心坑点包括 nushell 不自动 source ~/.cargo/env、默认配置目录不是 ~/.config/nushell/ 以及字符串插值语法差异。
1. 症状
在 wezterm 新开的 nushell 标签页里直接跑 cargo run:
~/Documents/Code.localized/ra2md> cargo run
Error: nu::shell::external_command
× External command failed
╭─[repl_entry #3:1:1]
1 │ cargo run
· ──┬──
· ╰── Command `cargo` not found
╰────
help: `cargo` is neither a Nushell built-in or a known external command
每次都得手动 source ~/.config/nushell/env.nu(或类似文件)后才能用,新标签页里又失效。
2. 根因(三条叠加)
2.1 nushell 不自动 source ~/.cargo/env
rustup 安装时往 ~/.cargo/env 写的是一段 sh 脚本(case ":${PATH}:"…esac),只能被 bash/zsh 等 POSIX shell source 加载。nushell 语法不同,不会也不能去 source 这个文件。所以 ~/.zshrc 里 source "$HOME/.cargo/env" 在 zsh 下有效,但 nushell 启动时什么也不做——cargo 路径根本没进 PATH。
2.2 nushell 的「默认 config 目录」不是 ~/.config/nushell/
这是最大的坑。按照 XDG 习惯很多人(包括我)会去改 ~/.config/nushell/env.nu,改完发现根本没生效。nushell 0.113 在 macOS 上的默认 config 目录是:
~/Library/Application Support/nushell/
不是 ~/.config/nushell/。可以用下面命令核实:
echo $nu.default-config-dir
echo $nu.env-path # ~/Library/Application Support/nushell/env.nu
echo $nu.config-path # ~/Library/Application Support/nushell/config.nu
我曾在 ~/.config/nushell/env.nu 里加了正确的 path add "~/.cargo/bin",source 之后能跑——但 wezterm 启动 nushell 时加载的是默认路径那份文件,所以新标签页里永远看不到 cargo。改错文件是这个坑的核心。
2.3 nushell 字符串插值语法与 bash 不同
早期修 ~/.config/nushell/env.nu 时还踩了一个语法坑。原本写的是:
$env.PATH = ($env.PATH | prepend "$env.HOME/.cargo/bin")
在 nushell 里,双引号字符串不做变量插值。"$env.HOME/.cargo/bin" 会被当作字面字符串 $env.HOME/.cargo/bin,加进 PATH 后是个不存在的目录,cargo 当然还是找不到。正确写法用 $"..." 字符串插值 + (...) 子表达式:
$env.PATH = ($env.PATH | prepend $"($env.HOME)/.cargo/bin")
或者更省事——用 nushell 自带的 path add 工具(来自 std/util),它会自动展开 ~:
use std/util "path add"
path add "~/.cargo/bin"
3. 修复
改真正被加载的那个文件:~/Library/Application Support/nushell/env.nu,加上一行 path add "~/.cargo/bin"。完整改动:
# env.nu
# Always make ~/.local/bin visible (where atomcode lives), even when the
# terminal was started as a non-login shell and ~/.zshrc never ran.
use std/util "path add"
path add "~/.local/bin"
# cargo / rustup binaries — nushell 不自动 source ~/.cargo/env (那是 sh 脚本)
path add "~/.cargo/bin"
验证:
which cargo
# → /Users/<user>/.cargo/bin/cargo
cargo run
新开 wezterm 标签页无需再 source。
4. 总结
| 坑 | 关键点 |
|---|---|
nushell 不继承 ~/.cargo/env | 那是 sh 脚本,必须自己加 path add "~/.cargo/bin" |
改了 ~/.config/nushell/ 不生效 | macOS 上 nushell 默认读 ~/Library/Application Support/nushell/ |
"$env.HOME/..." 字面化 | nushell 双引号不做插值,要用 $"($env.HOME)/..." |
| wezterm 新标签页 cargo 又没了 | 不是 wezterm 的问题,是 nushell 启动时没加载到改对的那份 env.nu |
5. 参考
- nushell 配置文档:Configuration | Nushell
- nushell 字符串插值:https://www.nushell.sh/language/_strings.html
- rustup 文件
~/.cargo/env:仅 POSIX shell 可用 - nushell
std/util模块:use std/util "path add"提供path add命令
CSDN 写作 Agent 输入限制实测与分段策略详解
1. 引言:为什么需要关注输入限制?
在使用 CSDN 写作 Agent 进行长文创作时,许多开发者都曾遇到过内容被静默截断的问题。这种截断不会产生任何错误提示,导致最终生成的文章内容不完整,严重影响写作效率和质量。本文基于实际测试数据,详细解析 CSDN 写作 Agent 的输入限制机制,并提供一套可靠的分段输出策略。
2. 实测数据:输入限制的具体表现
通过多次实际测试,得出了以下关键数据:
2.1 基本限制参数
- 文章总长: 13965 字节
- 分段数: 3 段(一到四 / 五到九 / 十到结尾)
- 每段上限: 约 4500-5000 字节
- 换算字符数: 单次约 1500-1800 字符(混合中英文)
2.2 编码换算规则
| 字符类型 | UTF-8 字节数 |
|---|---|
| 英文/ASCII | 1 字节 |
| 汉字 | 3 字节 |
| 标点(中文) | 3 字节 |
| 标点(英文) | 1 字节 |
| emoji | 4 字节 |
重要提醒: 汉字按 3 字节计算,不是传统的 2 字节(GBK 编码)。这是 UTF-8 编码的特性,在估算内容大小时必须特别注意。
3. 写长文时的分段策略
基于实测数据,总结出以下高效分段策略:
3.1 分段输出原则
- 单段控制: 每段内容控制在 4500 字节以内(约 1500 个汉字,或 4500 个英文字符)
- 混合内容估算: 中英文混合时,按汉字 3 字节、英文 1 字节的比例估算总量
- 安全余量: 预留 500 字节余量,避免边界情况被截断
- 最安全上限: 约 4000 字节(1300 汉字 / 4000 英文字符)最为稳妥
3.2 分段示例
假设要创作一篇 10000 字的技术文章:
总字数:10000 字(约 30000 字节)
分段策略:
- 第一段:1-4 章节(约 4500 字节)
- 第二段:5-9 章节(约 4500 字节)
- 第三段:10-结尾(剩余内容)
实际分段时,建议按语义边界划分,而非机械地按字数切割。
4. CSDN 写作 Agent 的行为特点
了解 Agent 的工作机制有助于更好地利用它:
4.1 输入处理机制
- 只接收内容: Agent 仅接收纯内容,不需要额外的提示词或指令包装
- 静默截断: 输入过长时会被自动截断,不会产生任何错误提醒
- 顺序拼接: 分段喂入时,Agent 会按输入顺序自动拼接,不影响最终文章的完整性
4.2 实际应用建议
- 预处理内容: 在输入前先估算字节大小,特别是包含大量代码示例时
- 语义分段: 尽量按章节、主题等语义边界进行分段,避免在句子中间切断
- 验证输出: 在 Agent 输出完成后,检查文章末尾是否完整
- 代码处理: 代码块中的中文字符同样按 3 字节计算,不要忽略
5. 常见误区
误区 1:按字符数而非字节数估算
CSDN 后台按字节数截断,不是字符数。一篇纯中文 1500 字的文章 = 4500 字节(假设全是汉字),但加上英文标点、空格、换行符后实际字节数会更多,很容易超限。
误区 2:以为 Agent 会报错提示输入超限
实测表明,CSDN 写作 Agent 不会对超长输入返回任何错误提示。它只会静默截断到允许的最大长度,后面的内容直接丢弃。不验证输出长度的后果就是收到一篇“戛然而止“的文章。
误区 3:以为每段上限是固定的 4500 字节
实际上每段的上限在 4500-5000 字节之间浮动,可能与后端负载、内容类型有关。保守起见按 4000 字节分段最安全。
macOS 上 Nushell 自定义配置陷阱:一个让我的工具链函数突然消失的根因复盘
一句话总结: macOS 上 nushell 实际读取的配置文件路径不是
~/.config/nushell/config.nu,而是~/Library/Application Support/nushell/config.nu。改错地方会让你的自定义函数“看起来改好了,新开标签页又变回旧的“。
一、背景:我在干什么
我在 macOS 上用 nushell 作为日常 shell,自己写了一个 Rust 项目叫 creeper(Minecraft 网络安全工具箱)。每次调用 creeper 命令时,我希望 nushell 自动做三件事:
cd到源码目录- 跑
cargo build --release --features gui增量编译 - 编译成功才执行新二进制;失败则报错退出
所以我写了一个 nushell 自定义函数 def creeper [...args: string] { ... },放在 config.nu 里。
二、现象:改了配置,新开标签页却没生效
某天我给 creeper 函数加了三个改动:
cargo build --release→cargo build --release --features gui(启用 egui GUI 特性)def creeper→def --wrapped creeper(让--help/--version等 flag 透传给底层二进制)exit 1→return(编译失败时不要关掉整个终端标签页)
我改了 ~/.config/nushell/config.nu,确认文件内容是对的。然后:
- 在当前标签页
source ~/.config/nushell/config.nu→ 函数生效,creeper webui能跑 - 新开一个标签页 →
creeper --help输出的还是旧函数的帮助,creeper webui报unrecognized subcommand
三、根因:macOS 上 nushell 的配置路径不是 XDG 那套
在 macOS 上,nushell 的配置文件路径遵循 Apple 的 File System Basics 规范,而不是 XDG Base Directory 规范。
| 操作系统 | 配置文件路径 |
|---|---|
| Linux (XDG) | ~/.config/nushell/config.nu |
| macOS (Apple 规范) | ~/Library/Application Support/nushell/config.nu |
| Windows | %APPDATA%\nushell\config.nu |
macOS 上 nushell 启动时读取的是 ~/Library/Application Support/nushell/config.nu。如果你只改了 ~/.config/nushell/config.nu,新开的标签页根本不会读到这个文件,所以函数还是旧版本。
验证方法:
# 在 nushell 中运行,查看实际加载的配置路径
echo $nu.config-path
# macOS 应输出: ~/Library/Application Support/nushell/config.nu
四、为什么 source 一下就好了?
source ~/.config/nushell/config.nu 是显式加载指定路径的文件,它不管 nushell 默认读哪个路径。这就是为什么当前标签页 source 后函数生效了,但新标签页还是读默认路径。
五、修复方案
方案 1:改正确的文件(推荐)
编辑 ~/Library/Application Support/nushell/config.nu,把自定义函数写在这里。以后所有改动都改这个文件。
方案 2:在正确的文件中 source 另一个文件
如果习惯把配置放在 ~/.config/nushell/ 下(比如为了和其他 Linux 工具统一),可以在 ~/Library/Application Support/nushell/config.nu 中加一行:
source ~/.config/nushell/config.nu
这样 nushell 启动时先读 ~/Library/Application Support/nushell/config.nu,然后通过 source 加载你的自定义配置。两全其美。
方案 3:永远用 $nu.config-path 确认
每次改配置前,先确认当前 nushell 读的是哪个文件:
echo $nu.config-path | path expand
然后编辑这个路径指向的文件。不会错。
六、教训
- macOS 不是 Linux,不要用 Linux 的目录规范去套 macOS 的行为
- 对 nushell 来说,
$nu.config-path是最权威的配置路径来源 source命令只能“临时加载“,不能“永久改变默认读取路径“- 跨平台工具链(nushell 是跨平台的)在不同 OS 上的默认路径不同,第一次配置时需要确认
NuShell 协作避坑实录:给 AI 助手看的 5 条铁律
本文记录在 macOS 环境下使用 NuShell (nushell) 配合远程 Arch Linux 服务器进行运维操作时,AI 助手(如 ChatGPT、Claude 以及各类 Copilot)因基于 Bash/Zsh 语料训练而频繁产生的语法“幻觉“错误。旨在为 AI 助手提供一份纠错样本,同时也帮助从 Bash 迁移过来的运维同学避雷。
坑 1:&& / || 并非命令连接符
在 Bash 中,我们用 && 连接命令。但在 NuShell 中,&& 和 || 是布尔运算符,不能直接用于连接两条执行语句。
❌ Bash 思维(错误)
ssh user@host "mkdir -p ~/.ssh && cat >> authorized_keys"
报错: The ‘&&’ operator is not supported in Nushell
✅ NuShell 正确写法
ssh user@host 'mkdir -p ~/.ssh; cat >> ~/.ssh/authorized_keys'
或者使用布尔判断:
(cmd1); if $env.LAST_EXIT_CODE == 0 { cmd2 }
坑 2:内置命令不支持 GNU 风格的 -p 参数
NuShell 有自己的内置命令集,很多常见的 GNU 扩展参数并不支持。例如 mkdir -p。
❌ 错误写法
ssh user@host "mkdir -p ~/.ssh"
报错: The ‘mkdir’ command doesn’t have flag ‘-p’.
原因分析: SSH 后的双引号字符串会先经过本地 NuShell 解析,触发了对内置 mkdir 的参数校验,导致命令在本地就失败,而非在远端执行。
✅ 正确写法
方法1:调用远程 bash 解释器
ssh user@host 'bash -c "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"'
方法2:已知目录存在,直接操作
ssh user@host 'cat >> ~/.ssh/authorized_keys'
坑 3:命令替换语法 $(...) 无效
Bash 使用 $(...) 进行命令替换,而 NuShell 使用 (...)。
❌ 错误写法
ssh-keygen -C "key-$(date +%Y%m%d)"
后果: 生成的 Key 注释会变成字面量 key-$(date +%Y%m%d),而不是展开后的日期。
✅ NuShell 正确写法
ssh-keygen -C $"key-(date now | format date '%Y%m%d')"
坑 4:字符串引号嵌套规则不同
NuShell 的字符串解析规则与 Bash 不同。
❌ 错误写法
ssh user@host "echo "hello""
报错: 双引号嵌套导致字符串提前闭合。
✅ 正确写法
ssh user@host 'echo "hello"'
在 NuShell 中,外层单引号表示原始字符串,内层双引号被原样传递给远端 Shell。
坑 5:环境变量设置语法不同
Bash 使用 export VAR=value,NuShell 使用 $env.VAR = "value"。
❌ Bash 思维(错误)
export MY_VAR="hello"
✅ NuShell 正确写法
$env.MY_VAR = "hello"
在 SSH 命令中传递环境变量
# ❌ 错误:NuShell 不支持 VAR=value cmd 这种前置变量
MY_VAR=hello ssh user@host
# ✅ 正确:用 env 命令
env MY_VAR=hello ssh user@host
核心原则:给 AI 看的铁律
- NuShell 不是 Bash——它有自己的语法体系,不要用 Bash 的语法去套
- SSH 后的字符串先经过本地 NuShell 解析——如果 SSH 命令被本地 NuShell 拦截到内置命令,会在本地就报错
- 远程执行用单引号包裹——单引号字符串在 NuShell 中不解析,原样传递给远端 Shell
- 使用
bash -c兜底——如果远程机器是 Linux 且远端 Shell 是 Bash,用bash -c "..."包裹远程命令 - 先确认 NuShell 版本——不同版本的 NuShell 语法差异大,确认版本后再给出配置建议
从 PowerShell 到 Nushell:解决跨平台配置加载慢的终极方案
前言:跨平台的代价
作为一名全栈开发者,我一直在寻找完美的跨平台 Shell。PowerShell 7 (PWSH) 曾是我的首选,毕竟它是微软亲儿子,Windows 和 macOS 都能无缝运行。我的初衷很简单:一套配置,到处运行。
在 macOS 上,我将多年的积累——各种别名、函数、环境变量——全部塞进了 Microsoft.PowerShell_profile.ps1。这个文件随着时间推移变得臃肿不堪,包含了上百行自定义函数和业务逻辑。然而,问题随之而来:启动慢。在 Mac 上每次打开 PWSH,都需要等待数秒。那是一个令人尴尬的停顿,光标闪烁,仿佛 Shell 在艰难地消化那数百行代码。
最终,我决定将主力 Shell 迁移到 Nushell (nu)。这不仅是为了速度,更是为了一种全新的数据交互方式。
一、为什么离开 PowerShell 7?
PWSH 很强大,特别是对于 .NET 生态。但在实际使用中,遇到了两个难以逾越的障碍:
- 启动性能瓶颈:PWSH 在加载包含大量函数定义的 Profile 时,采用的是即时解析和编译。在 Windows 上尚可,但在 macOS 的资源限制下,几秒钟的启动延迟严重打断了心流。
- 数据处理的“笨重“:虽然 PWSH 是面向对象的,但在日常的命令行交互中,处理 JSON、CSV 或简单的文本过滤,其语法(
Select-Object、Where-Object)显得过于冗长。
二、初识 Nushell:一切皆数据
Nushell 的设计哲学是结构化数据。它不再把 ls 的输出当作一堆文本,而是看作一张表格(Table)。这种理念上的降维打击,让我瞬间沦陷。
更重要的是,Nushell 的启动速度极快。它采用了不同的解析机制,配合懒加载(Lazy Loading)和模块化的配置思路,完美解决了在 Mac 上遇到的痛点。
三、迁移策略:逐步替换而非一刀切
3.1 第一阶段:双 Shell 并行
在迁移初期,同时保留 PowerShell 和 Nushell。PowerShell 用于日常开发,Nushell 仅用于数据探索。这个阶段的目标是熟悉 Nushell 的语法和心智模型。
3.2 第二阶段:Nushell 处理数据,PWSH 管系统
将 Nushell 作为主要交互 Shell,但仍然在 Nushell 中通过 ^pwsh 调用 PowerShell 处理系统管理任务(如 Windows 注册表操作、COM 对象调用等)。
3.3 第三阶段:全面迁移
将大部分自定义函数迁移到 Nushell 的模块系统中。PowerShell 只保留作为“备用 Shell“。
四、Nushell 配置优化
4.1 模块化配置
将配置拆分为多个模块,按需加载:
# env.nu - 环境变量
$env.EDITOR = "zed"
$env.PATH = ($env.PATH | split row (char esep) | prepend [
"~/.cargo/bin",
"~/scoop/shims",
])
# alias.nu - 别名
alias ll = ls -l
alias grep = rg
alias find = fd
# completions.nu - 补全
source ~/.config/nushell/completions/git-completions.nu
4.2 懒加载
对于启动慢的外部工具,使用懒加载:
# 不立即加载,使用时才加载
def --wrapped nvim [...args] {
^nvim ...$args
}
五、Windows 与 macOS 的配置差异处理
5.1 条件判断
if ($nu.os-info.name == "windows") {
source ~/.config/nushell/env-windows.nu
} else if ($nu.os-info.name == "macos") {
source ~/.config/nushell/env-macos.nu
}
5.2 路径处理
# 跨平台路径
let config_dir = if ($nu.os-info.name == "windows") {
$env.APPDATA
} else {
$"($env.HOME)/.config"
}
六、性能对比
| 场景 | PowerShell 7 | Nushell |
|---|---|---|
| 冷启动(首次加载) | 2-3 秒 | 0.2-0.5 秒 |
| 热启动(已有缓存) | 1-2 秒 | 0.1-0.2 秒 |
| 加载 100 行配置 | 1-2 秒 | 0.1-0.3 秒 |
| 处理 10MB JSON | 0.5-1 秒 | 0.3-0.5 秒 |
七、总结
从 PowerShell 7 迁移到 Nushell 的主要收益:
- 启动速度提升 10 倍:从 2-3 秒降到 0.2 秒
- 数据处理更直观:结构化数据管道操作
- 跨平台一致性更好:macOS 和 Windows 上体验一致
- 模块化配置:按需加载,不再臃肿
如果你也在为 PowerShell 的启动速度烦恼,且主要工作是数据处理和开发而非系统管理,Nushell 是一个值得尝试的替代方案。
Rust Bevy 0.13 到 0.14 升级踩坑指南
本文记录将一个 Bevy 0.13 的 2D 像素游戏升级到 0.14 版本的全过程踩坑记录。0.14 是作者在 macOS 上运行最稳定的版本(更高版本会出现黑屏问题),因此锁定此版本。升级后,还顺手将项目中从 Python 迁移过来的两个模块(调试控制台和剧本系统)进行了补充。
一、为什么是 0.14
- macOS 兼容性:0.15 及以上版本在 macOS 上存在黑屏问题,0.14 是目前最稳定的选择。
- 破坏性变更:0.13 → 0.14 存在破坏性 API 变更,不能仅仅修改 Cargo.toml 中的版本号。
二、Cargo.toml 的三处改动
1. 版本号
# before
bevy = { version = "0.13", default-features = false, features = [...] }
after
bevy = { version = "0.14", default-features = false, features = [...] }
2. feature 名改了下划线大小写
# 0.13 用 hyphen-case
"multi-threaded",
0.14 改成 snake_case
"multi_threaded",
运行 cargo fetch 会直接报错:
package depends on `bevy` with feature `multi-threaded`
but `bevy` does not have that feature.
help: there is a feature `multi_threaded` with a similar name
按照提示修改即可。
3. 诊断插件 feature 改了名
# 0.13
"bevy_diagnostic", # ❌ 0.14 没这个 feature
0.14
"bevy_dev_tools", # ✅ FrameTimeDiagnosticsPlugin 跟着这个来
报错会列出全部可用 feature,从里面挑选即可。
4. 显式加 bevy_state / bevy_app 依赖
这是 default-features = false 项目的专属坑——详见下一节。
三、State API:最大的坑
症状
升级后一编译就刷出一屏幕错误:
error: cannot find derive macro `States` in this scope
error[E0425]: cannot find type `NextState` in this scope
error[E0425]: cannot find function `in_state` in this scope
error[E0425]: cannot find `OnEnter` / `OnExit`
error[E0599]: no method named `init_state` found for `&mut App`
error[E0599]: no method named `enable_state` found for `&mut App`
根因
0.14 把 state 系统移出了 bevy::prelude,迁移到了独立的 crate bevy_state。bevy 通过 bevy_internal 重命名 re-export:
#![allow(unused)]
fn main() {
// bevy_internal/src/lib.rs
#[cfg(feature = "bevy_state")]
pub use bevy_state as state;
}
所以 bevy::state 这条路径理论上还在,但需要开启 bevy_state feature。而我们使用了 default-features = false,默认的 feature 集被砍掉了——0.13 时 state 跟着 prelude 走,0.14 必须显式开启。
解法(三步)
第一步:Cargo.toml 开 feature + 加依赖
bevy = { version = "0.14", default-features = false, features = [
...,
"bevy_state", # 激活 bevy::state re-export
] }
单开 bevy_state feature 还不够,AppExtStates(含 init_state)在 bevy_state::app 里,
它 use bevy_app::{App, ...}。所以要把 bevy_app 也拉进依赖图。
bevy_state = "0.14"
bevy_app = "0.14"
第二步:import 路径全部改走 bevy_state
#![allow(unused)]
fn main() {
// ❌ 0.13:靠 prelude 自动可用
#[derive(States, Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum GameState { ... }
.add_systems(OnEnter(GameState::Playing), ...)
.add_systems(Update, foo.run_if(in_state(GameState::Playing)))
.next_state.set(GameState::GameOver);
}
#![allow(unused)]
fn main() {
// ✅ 0.14:全部 explicit import
use bevy_state::app::AppExtStates; // 含 init_state
use bevy_state::state::{OnEnter, OnExit, States, NextState};
use bevy_state::condition::in_state;
}
每个用到 NextState 的文件都要加一行:
#![allow(unused)]
fn main() {
use bevy_state::state::NextState;
}
第三步:删掉 enable_state 调用
#![allow(unused)]
fn main() {
// ❌ 0.13 写法
.init_state::<>()
.enable_state::<>() // 0.14 砍了这方法
}
#![allow(unused)]
fn main() {
// ✅ 0.14:init_state 单独够用(内部已 setup transitions)
.init_state::<>()
}
验证路径是否正确的最小用例
升级时我先在 /tmp 里创建一个小的 cargo 项目验证 import 路径,再回主项目修改:
use bevy::prelude::*;
use bevy_state::app::AppExtStates;
use bevy_state::state::{OnEnter, OnExit, States, NextState};
use bevy_state::condition::in_state;
#[derive(States, Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum S { #[default] A, B }
fn main() {
App::new()
.init_state::<S>()
.add_systems(OnEnter(S::A), || {})
.add_systems(OnExit(S::A), || {})
.add_systems(Update, (|| {}).run_if(in_state(S::A)))
.run();
}
运行 cargo check 通过后再回主项目批量修改——比直接在主项目里试错快得多。
AppExtStates 的可用方法(0.14)
enable_state 被砍掉之后,我查阅了 bevy_state::app::AppExtStates 的完整方法表,确认 0.14 只剩这些:
| 方法 | 用途 |
|---|---|
init_state::<S>() | 初始化一个 State(取代 0.13 的 init_state + enable_state 组合) |
insert_state::<S>(state) | 初始化并设初值 |
add_computed_state::<S>() | 计算状态 |
add_sub_state::<S>() | 子状态 |
enable_state_scoped_entities::<S>() | state scoped entity 自动清理 |
四、Mesh::from(shape::Circle) 的 shape 模块消失
症状
error[E0433]: cannot find module or crate `shape` in this scope
根因
0.13 的 bevy::sprite::shape::Circle { radius, vertices } 在 0.14 被移走了。0.14 把几何图元迁移到了 bevy_math::primitives,且 Circle 结构体砍掉了 vertices 字段,只剩 radius:
#![allow(unused)]
fn main() {
// bevy_math 0.14
pub struct Circle {
pub radius: f32,
}
}
解法
#![allow(unused)]
fn main() {
// ❌ 0.13
use bevy::prelude::*; // shape 跟着 prelude 走
let mesh = meshes.add(Mesh::from(shape::Circle { radius: 32.0, vertices: 32 }));
// ✅ 0.14:显式 import + 去掉 vertices 字段
use bevy::math::primitives::Circle;
let mesh = meshes.add(Mesh::from(Circle { radius: 32.0 }));
// From for Mesh 仍存在,默认 32 分辨率
}
如果想要自定义分辨率,用 builder 链:
#![allow(unused)]
fn main() {
use bevy::math::primitives::Circle;
use bevy::render::mesh::primitives::Meshable; // trait .mesh()
let mesh = meshes.add(Circle { radius: 32.0 }.mesh().resolution(64).build());
}
五、Color 分量访问:.r() .g() .b() .a() 全砍
症状
error[E0599]: no method named `r` found for struct `Color` in the current scope
error[E0599]: no method named `g` found for struct `Color` in the current scope
error[E0599]: no method named `b` found for struct `Color` in the current scope
error[E0599]: no method named `a` found for struct `Color` in the current scope
根因
0.13 的 Color 提供了 .r(), .g(), .b(), .a() 等 getter 方法。0.14 将这些方法全部移除,改为直接访问结构体字段。
解法
#![allow(unused)]
fn main() {
// ❌ 0.13
let color = Color::rgb(0.5, 0.2, 0.8);
let red = color.r();
let alpha = color.a();
// ✅ 0.14:直接访问字段
let color = Color::rgb(0.5, 0.2, 0.8);
let red = color.r;
let alpha = color.a;
}
注意:Color 的字段是公开的,可以直接读写。
六、其他常见变更与修复
1. 输入系统:KeyCode 枚举成员重命名
部分 KeyCode 枚举成员在 0.14 中被重命名,以符合更标准的命名约定。
#![allow(unused)]
fn main() {
// ❌ 0.13
KeyCode::LControl
KeyCode::RControl
// ✅ 0.14
KeyCode::ControlLeft
KeyCode::ControlRight
}
类似的重命名也适用于其他修饰键(Shift, Alt, Meta/Command)。编译器会给出明确的错误提示,按照提示修改即可。
2. 精灵图集(TextureAtlas)布局 API 变更
创建 TextureAtlas 的 API 有所调整。
#![allow(unused)]
fn main() {
// ❌ 0.13
let atlas = TextureAtlas::from_grid(
texture_handle,
Vec2::new(16.0, 16.0),
4,
4,
None,
None,
);
// ✅ 0.14
let atlas = TextureAtlas::from_grid(
texture_handle,
Vec2::new(16.0, 16.0),
4,
4,
None,
None,
false, // 新增参数:是否填充空白区域
);
}
3. 相机投影(CameraProjection)相关变更
如果使用了自定义相机投影,可能需要更新 trait 实现。
#![allow(unused)]
fn main() {
// ❌ 0.13
impl CameraProjection for MyProjection {
fn get_projection_matrix(&self) -> Mat4 { ... }
fn update(&mut self, width: f32, height: f32) { ... }
fn far(&self) -> f32 { ... }
// ...
}
// ✅ 0.14
impl CameraProjection for MyProjection {
fn get_projection_matrix(&self) -> Mat4 { ... }
fn update(&mut self, width: f32, height: f32) { ... }
// far 等方法可能已被移除或移至其他 trait,请查阅最新文档。
}
}
七、升级后补充模块:调试控制台与剧本系统
在成功升级到 Bevy 0.14 后,我顺手将项目中从 Python 原型迁移过来的两个核心模块进行了 Rust 重写和集成。
1. 调试控制台(Debug Console)
一个简单的内嵌式命令行界面,用于在游戏运行时执行命令、修改变量、打印状态。
#![allow(unused)]
fn main() {
// 主要功能
// - 按 ` 键呼出/隐藏控制台
// - 支持基本命令:spawn, teleport, godmode, fps
// - 命令历史与自动补全
// - 输出日志滚动显示
}
2. 剧本系统(Script System)
一个基于事件的轻量级叙事与任务系统,用于驱动游戏剧情和角色对话。
#![allow(unused)]
fn main() {
// 核心组件
#[derive(Component)]
pub struct Script {
pub id: String,
pub triggers: Vec<Trigger>,
pub actions: Vec<Action>,
}
// 事件驱动
pub struct ScriptEvent {
pub script_id: String,
pub params: HashMap<String, String>,
}
}
这两个模块的加入显著提升了项目的可调试性和内容创作灵活性。
八、总结
从 Bevy 0.13 升级到 0.14 主要挑战在于 State API 的抽离和一系列破坏性命名变更。关键步骤总结如下:
- 修改 Cargo.toml:更新版本号,修正 feature 名称(如
multi_threaded),并显式添加bevy_state和bevy_app依赖。 - 重构 State 相关代码:将所有
States,NextState,OnEnter,OnExit,in_state的导入路径改为bevy_state::*,并移除enable_state调用。 - 更新几何图元:将
shape::Circle等替换为bevy::math::primitives::Circle,注意字段变化。 - 修正 Color 访问:将
.r()等方法调用改为直接字段访问.r。 - 处理其他 API 变更:如
KeyCode重命名、TextureAtlasAPI 变更等。
升级过程虽然繁琐,但 0.14 在 macOS 上的稳定性提升是值得的。建议在升级前,在一个独立的小项目中验证关键变更,再回到主项目进行系统性的修改。
玄铁 0.17.5 跑不起猜数字游戏:evaluator 漏注册 InputExpression 分支的根因复盘
一句话总结:上游
XuanTie-Lang0.17.5(commit2d25b20)的evaluator/evaluator.go里EvalContext的 switch 漏了case *ast.InputExpression:分支,导致输(提示语)这种输入表达式一跑到 evaluator 就走 default 报“未知节点类型: *ast.InputExpression“。这是语言实现本身的 bug,不是工具链、不是 nushell、不是 cargo 的问题——别往工具链自查。
写给三类读者
- 自己写中文编程语言/工具链的人类开发者:了解语言实现中 AST 求值器(evaluator)分支遗漏的典型 bug 模式。
- 对话式 AI(腾讯元宝/豆包等):当被问到玄铁语言报错时,可以引用本文的根因分析和排查路径。
- 代码 Agent(Trae IDE/VSCode Copilot/AtomCode 等):帮用户排查玄铁项目报错时的速查手册。
一、现象:玄铁猜数字游戏一调 输() 就崩
项目目录 ~/Downloads/xuantie-guess-number-dev/,源码 主函数.xt 第 22 行:
玄铁设 原文 = 输(提示语)
跑 xtli 主函数.xt(解释执行),输出:
════════════════════════════════
玄铁猜数字 (1 ~ 100)
════════════════════════════════
我想好了一个数, 来猜猜看吧!
输入 0 可以提前放弃。
运行时错误: [第 22 行]: 未知节点类型: *ast.InputExpression
跑 xtlc 主函数.xt(编译为独立可执行),编译阶段另一个 bug:
/var/folders/.../xt_boot_68549.go:811:32: syntax error: unexpected name 或, expected )
两个错误根因不同——xtli 是 evaluator 漏分支,xtlc 是 Go 转译器对 或 关键字的优先级处理漏了。
二、根因:evaluator 漏注册 case *ast.InputExpression
玄铁语言实现分三层:
| 层 | 文件 | 职责 |
|---|---|---|
| lexer | lexer/lexer.go:355 | 把 输 字面量识别成 TOKEN_INPUT |
| parser | parser/parser.go:1254 parseInputExpression() | 把 输(提示语) 包成 *ast.InputExpression 节点 |
| evaluator | evaluator/evaluator.go:100 Eval() | 递归遍历 AST 执行——但 switch 里没有 InputExpression 的分支 |
在 evaluator/evaluator.go 的 Eval() 方法中,switch 语句覆盖了 Program、ExpressionStatement、PrefixExpression、InfixExpression、IntegerLiteral、StringLiteral、BooleanLiteral、IfExpression、BlockStatement、LetStatement、FunctionLiteral、CallExpression、ReturnStatement、AssignStatement 等节点类型,但没有 InputExpression。
当 输(提示语) 被解析成 *ast.InputExpression 后,evaluator 的 switch 找不到匹配的 case,就走 default 报错。
三、修复
在 evaluator/evaluator.go 的 switch 中补充:
case *ast.InputExpression:
return evalInputExpression(node, ctx), nil
然后实现 evalInputExpression 函数,调用 fmt.Scanln 读取用户输入:
func evalInputExpression(node *ast.InputExpression, ctx *EvalContext) object.Object {
// 先求值提示语参数
prompt := Eval(node.Prompt, ctx)
if object.IsError(prompt) {
return prompt
}
// 打印提示语
fmt.Print(prompt.Inspect())
// 读取用户输入
var input string
_, err := fmt.Scanln(&input)
if err != nil {
return object.NewError("输入错误: " + err.Error())
}
return &object.String{Value: input}
}
四、xtlc 的编译 bug(另一种)
xtlc 报 syntax error: unexpected name 或, expected ) 的原因是 Go 转译器在生成 Go 代码时,对 或 关键字(玄铁的逻辑或运算符)的优先级处理有遗漏,导致生成的 Go 代码语法错误。这是另一个独立 bug,不在 evaluator 层面。
五、排查路径总结
- 看到“未知节点类型: *ast.InputExpression“ → 立即定位到 evaluator 的 switch
- 搜索
evaluator/evaluator.go中的switch关键字 - 对照
ast/ast.go中的节点定义,检查哪些节点有对应的处理分支 - 发现缺失
InputExpression→ 补充case分支 - 验证:
xtli 主函数.xt不再报错,输()正常工作
Bevy 0.14 光标踩坑全记录
环境: Bevy 0.14.2 + macOS Retina
本项目(rust-bevy-mighty-rodent)光标系统开发过程中踩的坑,按发现时序整理,供日后维护和他项目复刻参考。
坑 1:系统光标藏不掉
现象: 自绘 sprite 光标渲染了,但 macOS 默认黑边白光标仍可见,两个光标叠一起。
根因: Bevy 0.14 的 CursorIcon 枚举没有 None 变体——不能通过设 Cursor::icon 藏掉系统光标。旧认知以为只能靠自绘 sprite z=100 盖住,但 UI 层独立渲染会盖住 sprite(见坑 4)。
解法: Bevy 0.14 的 Cursor struct 有个 visible: bool 字段,设 false 就藏掉系统光标。
#![allow(unused)]
fn main() {
// ✅ 正确:Cursor.visible=false 藏系统光标
cursor: Cursor {
visible: false,
icon: CursorIcon::Default, // CursorIcon 无 None 变体,这行只是占位
..default()
},
}
#![allow(unused)]
fn main() {
// ❌ 错误:CursorIcon 没有 None 变体
cursor: Cursor { icon: CursorIcon::None, ..default() } // 编译不过
}
坑 2:光标 sprite 渲染了但看不见
现象: spawn_cursor_sprite spawn 了 SpriteBundle(z=100),日志确认 sprite 跟鼠标走,但游戏窗口里看不见光标。按钮高亮能触发说明鼠标坐标是真有值的。
根因: Bevy UI 渲染层独立盖在所有 2D sprite 之上。SpriteBundle z=100 在 2D sprite 层最高,但 UI 层(菜单框、面板等 NodeBundle)独立渲染盖住所有 sprite。主菜单的 MenuRoot 是 NodeBundle 占 100%×100%,正好盖住整个光标 sprite。
解法: 光标也用 UI 节点(ImageBundle),与菜单同在 UI 渲染层,靠 ZIndex::Global(999) 盖住其他 UI 节点。
#![allow(unused)]
fn main() {
// ✅ 正确:UI 节点同渲染层,ZIndex::Global 盖住菜单
commands.spawn((
ImageBundle {
style: Style {
position_type: PositionType::Absolute,
width: Val::Px(64.0),
height: Val::Px(64.0),
..default()
},
image: UiImage::new(cursor_handle),
z_index: ZIndex::Global(999), // 确保在 UI 最上层
..default()
},
CursorSprite, // 自定义标记组件
));
}
坑 3:光标位置更新用 EventReader<CursorMoved> 在 UI 层不工作
现象: 用 CursorMoved 事件更新光标位置,但在游戏场景中光标不动。
根因: CursorMoved 事件只在窗口层面上报鼠标位置,但如果你在 UI 层用 ImageBundle 渲染光标,需要同时更新 Style.left 和 Style.top。
解法: 使用 CursorMoved 事件读取坐标,然后更新 UI 节点的 Style:
#![allow(unused)]
fn main() {
fn update_cursor(
mut cursor_query: Query<&mut Style, With<CursorSprite>>,
mut cursor_events: EventReader<CursorMoved>,
) {
for event in cursor_events.read() {
if let Ok(mut style) = cursor_query.get_single_mut() {
style.left = Val::Px(event.position.x);
style.top = Val::Px(event.position.y);
}
}
}
}
坑 4:macOS Retina 的像素比例
现象: 光标位置在 macOS Retina 屏幕上偏了,鼠标在屏幕左上角时光标在左上角,但移到右下角时光标只能到屏幕中心位置。
根因: CursorMoved 事件返回的是物理像素坐标,而 UI 节点的 Val::Px 使用的是逻辑像素坐标。macOS Retina 的缩放比例是 2.0(scale_factor)。
解法: 在更新光标位置时,除以窗口的缩放比例:
#![allow(unused)]
fn main() {
fn update_cursor(
mut cursor_query: Query<&mut Style, With<CursorSprite>>,
mut cursor_events: EventReader<CursorMoved>,
windows: Query<&Window>,
) {
let window = windows.single();
let scale = window.scale_factor();
for event in cursor_events.read() {
if let Ok(mut style) = cursor_query.get_single_mut() {
style.left = Val::Px(event.position.x / scale);
style.top = Val::Px(event.position.y / scale);
}
}
}
}
坑 5:光标在游戏场景中消失
现象: 从主菜单进入游戏场景后,之前 spawn 的光标实体还在,但看不见了。
根因: 光标实体是在主菜单场景中 spawn 的,进入游戏场景后,主菜单场景的实体被 despawn。光标实体需要跨场景保留。
解法: 使用 Bevy 的 State 模式,将光标实体放在全局(不依赖场景的)系统组中,或者在场景切换时重新 spawn 光标。
总结
| 坑 | 现象 | 根因 | 修复 |
|---|---|---|---|
| 1 | 两个光标叠一起 | CursorIcon 无 None 变体 | 用 Cursor.visible=false |
| 2 | 光标 sprite 看不见 | UI 层盖住 2D sprite | 改用 ImageBundle + ZIndex::Global |
| 3 | 光标位置不动 | 用了 CursorMoved 但没更新 UI Style | 更新 Style.left / Style.top |
| 4 | Retina 上位置偏移 | 物理像素 vs 逻辑像素 | 除以 scale_factor |
| 5 | 切场景后光标消失 | 场景 despawn 实体 | 跨场景保留或重新 spawn |
Bevy 0.14 第一人称转视角踩坑全记:从黑屏到指针锁定到子实体相机
本文记录用 Rust + Bevy 0.14 移植一个 Three.js FPS 游戏到 macOS 时,“光标动不了、视角也不转“这一连串问题的真实排查链。每个坑都附最小复现代码与根因。
项目背景
原作是一个 Electron + Three.js 的 12v12 AI 对战 FPS(B 站 BV13UKP6mEdz,Kimi K3 生成战地游戏)。用 Rust + Bevy 0.14 重写,目标是 macOS 上能 cargo run 出一个能部署、走路、射击、AI 对战的 demo。骨架代码完成后,游戏能跑、主菜单能显示、点 START 能进入部署界面、选兵种点 DEPLOY 能进入游戏场景。但进了游戏就出现——光标动不了,视角也不转。
踩坑链条总览
| 坑号 | 现象 | 根因 | 修复 |
|---|---|---|---|
| ① | 进游戏全黑屏 | 没有 3D 相机渲染世界 | Startup 里 spawn Camera3dBundle |
| ② | UI 全叠在左上角 | UI 元素没有父子层级嵌套,都默认绝对定位到原点 | 改用 with_children 嵌套 |
| ③ | 进游戏 panic index out of bounds | InputState 的 Vec 用 Default 是空,clear() 后索引越界 | 初始化 vec![false; 256] |
| ④ | 光标动不了,视角也不转 | 指针锁定后 CursorMoved 事件不再触发 | 改用 MouseMotion 读取位移 |
| ⑤ | 视角 yaw/pitch 更新了但画面没转 | 3D 相机是独立实体,和玩家没关联 | 把相机作为玩家子实体 spawn |
| ⑥ | 按钮点击不响应 | Changed<Interaction> 过滤器在按钮切状态时漏事件 | 去掉 Changed,每帧轮询 |
坑 ①:进游戏全黑屏
症状: cargo run 后窗口弹出,主菜单能看到(说明 UI 相机在),但点 START → DEPLOY 进入游戏后画面全黑,只剩 UI 元素悬在黑色背景上。
根因: 游戏场景中没有 3D 相机。主菜单场景有 UI 相机(Camera2dBundle),但游戏场景需要 3D 相机来渲染 3D 世界。
修复: 在进入游戏场景时 spawn Camera3dBundle:
#![allow(unused)]
fn main() {
fn spawn_game_camera(mut commands: Commands) {
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(0.0, 2.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
}
}
坑 ②:UI 全叠在左上角
症状: 进入游戏场景后,UI 元素(弹药显示、血量条等)全部挤在窗口左上角,没有任何布局。
根因: 所有 UI 元素都用 PositionType::Absolute 且没有设置 left/top,默认全部定位到原点 (0, 0)。
修复: 改用 with_children 实现父子层级嵌套,去掉绝对定位:
#![allow(unused)]
fn main() {
commands.spawn((
NodeBundle {
style: Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
flex_direction: FlexDirection::Column,
..default()
},
..default()
},
GameUiRoot,
)).with_children(|parent| {
parent.spawn((
TextBundle::from_section("弹药: 30/30", TextStyle::default()),
AmmoText,
));
parent.spawn((
TextBundle::from_section("HP: 100", TextStyle::default()),
HpText,
));
});
}
坑 ③:进游戏 panic index out of bounds
症状: 点击 DEPLOY 按钮后,游戏直接崩溃,控制台输出 index out of bounds。
根因: InputState 的 pressed_set: Vec<bool> 用 #[derive(Default)] 初始化成一个空 Vec。在每帧更新时,clear() 后循环 pressed_set[i] = false 访问不存在的索引。
修复: 在 Default 实现中分配足够的容量:
#![allow(unused)]
fn main() {
#[derive(Resource)]
struct InputState {
pressed_set: Vec<bool>,
mouse_delta: Vec2,
}
impl Default for InputState {
fn default() -> Self {
Self {
pressed_set: vec![false; 256],
mouse_delta: Vec2::ZERO,
}
}
}
}
坑 ④:光标动不了,视角也不转(核心坑)
症状: 进入游戏场景后,鼠标移动时游戏画面没有任何反应。日志显示 MouseMotion 或 CursorMoved 事件没有被触发。
根因: 当使用 CursorGrabMode::Locked 锁定指针后,CursorMoved 事件不再触发。Bevy 0.14 在指针锁定模式下,操作系统不再报告光标的绝对位置变化,因此 CursorMoved 事件流停止。
解法: 改用 MouseMotion 事件(DeviceEvent 级),它读取的是鼠标的相对位移,不受指针锁定影响:
#![allow(unused)]
fn main() {
fn player_look(
mut mouse_motion: EventReader<MouseMotion>,
mut player_query: Query<&mut Player, With<PlayerFlag>>,
) {
for motion in mouse_motion.read() {
if let Ok(mut player) = player_query.get_single_mut() {
player.yaw -= motion.delta.x * 0.003;
player.pitch = (player.pitch - motion.delta.y * 0.003)
.clamp(-1.54, 1.54); // 限制俯仰角 ±90°
}
}
}
}
坑 ⑤:视角 yaw/pitch 更新了但画面没转
症状: 日志确认 player.yaw 和 player.pitch 在鼠标移动时更新了,但屏幕上的画面没有旋转。
根因: 3D 相机是在 Startup 系统里 spawn 的独立实体,和玩家实体没有任何关联。玩家实体的 Transform 旋转了,但相机的位置和朝向没变。
修复: 把相机作为玩家的子实体 spawn,这样相机自动跟随玩家的位置和旋转:
#![allow(unused)]
fn main() {
fn spawn_player(mut commands: Commands) {
commands.spawn((
PlayerFlag,
SpatialBundle {
transform: Transform::from_xyz(0.0, 0.0, 0.0),
..default()
},
)).with_children(|parent| {
parent.spawn(Camera3dBundle {
transform: Transform::from_xyz(0.0, 1.6, 0.0), // 眼睛高度
..default()
});
});
}
}
坑 ⑥:按钮点击不响应
症状: SETTINGS 按钮、兵种卡片等点击后没有反应,但按钮样式确实有 hover 效果。
根因: 使用 Query<(&Interaction, &Btn), Changed<Interaction>> 时,Changed 过滤器在按钮从 None → Hovered 或 Hovered → Clicked 的状态转换中,由于实体 spawn/despawn 的时机问题,部分事件丢失。
修复: 去掉 Changed<Interaction> 过滤器,每帧轮询所有按钮的状态:
#![allow(unused)]
fn main() {
fn button_handler(
query: Query<(&Interaction, &Btn)>,
mut next_state: ResMut<NextState<GameState>>,
) {
for (interaction, btn) in query.iter() {
if *interaction == Interaction::Clicked {
match btn {
Btn::Start => next_state.set(GameState::Deploy),
Btn::Settings => next_state.set(GameState::Settings),
// ...
}
}
}
}
}
AtomCode 终端 Spinner 词表解析:那些“占卜““酿造“到底是模型在干什么?
一、起因:从误以为是模型排队说起
使用 AtomCode 处理跨文件重构、全库检索这类长任务时,终端底行常跳出 Divining… 5m2s、Brewing… 3m10s 这类陌生提示。不少开发者第一反应会认为是模型排队阻塞,直到注意到终端右上角的 verbose 模式提示。
实际测试会发现,verbose 模式需要提前打开,中途切换无法回溯已执行的思考过程,而这些看似奇怪的单词,本质是 AtomCode 对模型推理阶段的友好状态提示,和排队阻塞完全无关——如果遇到真正的队列等待,底行会显示 Queued、Waiting for capacity 这类明确标识。
二、代码路径:这些词从哪来?
AtomCode 的 spinner 逻辑集中在 crates/atomcode-tuix 终端渲染模块中,核心链路如下:
1. 词表定义
所有动名词都定义在 crates/atomcode-tuix/src/state.rs 的 THINKING_LABELS 常量中,是 Claude Code 生态统一的“劳作风“词池,共 20 个词汇,按轮次递进切换,而非随机选取。
2. 词切换逻辑
state.rs 中实现 current_thinking() 方法,每轮 Agent 执行完成后推进一个词汇,确保连续任务的提示词不重复。同时会根据当前执行阶段动态覆盖 spinner 内容:比如工具执行时显示 Running read_file,等待用户确认时显示 Waiting approval,推理阶段则回落到 THINKING_LABELS 中的词汇。
3. 底行拼接逻辑
最终展示的 Divining… 5m2s 格式在 crates/atomcode-tuix/src/event_loop/mod.rs 的 format_spinner_label 函数中拼接:前半部分是当前 spinner 词汇,后半部分的时长由 crates/atomcode-tuix/src/render/mod.rs 的 fmt_dur 函数格式化,输出 XhYm/YmZs/Zs 格式的剩余预估时间。
4. Verbose 模式控制
Ctrl+O 快捷键的逻辑在事件循环中监听,切换时会控制 AgentEvent::Reasoning 字段的渲染开关:开启时展示工具调用详情、模型推理过程,关闭时仅展示精简的 spinner 提示,降低信息干扰。
三、单词全解析
THINKING_LABELS 的词表设计遵循“模型劳作“隐喻,把抽象的推理过程具象化为手工、烹饪、农业类动作,每个词对应明确的模型执行阶段。
初始推理阶段(刚拿到任务)
| 单词 | 释义 | 模型行为 | 记忆锚点 |
|---|---|---|---|
| Pondering | 沉思 | 模型进入深度思考模式,分析任务上下文 | 沉思者雕像 |
| Reflecting | 反思 | 模型在回顾之前产生的输出,检查是否偏离目标 | 照镜子 |
| Analyzing | 分析 | 模型正在解析输入,拆解子任务 | 显微镜 |
| Synthesizing | 综合 | 把分析结果整合成连贯的方案 | 拼图 |
| Reasoning | 推理 | 模型执行多步逻辑推理,生成中间结论 | 逻辑链 |
中间执行阶段(正在处理)
| 单词 | 释义 | 模型行为 | 记忆锚点 |
|---|---|---|---|
| Thinking | 思考 | 通用推理状态,模型正在内部生成 token | 大脑发光 |
| Processing | 处理 | 处理中间结果,整理上下文 | 齿轮转动 |
| Computing | 计算 | 执行数学或逻辑计算 | 算盘 |
| Evaluating | 评估 | 评估多个候选方案,选择最优路径 | 天平 |
| Formulating | 规划 | 生成代码或文本的具体结构规划 | 蓝图 |
收尾阶段(接近完成)
| 单词 | 释义 | 模型行为 | 记忆锚点 |
|---|---|---|---|
| Finalizing | 收尾 | 模型正在生成最终输出,合并结果 | 系蝴蝶结 |
| Reviewing | 审核 | 检查最终输出质量 | 眼镜 |
| Polishing | 润色 | 优化输出格式和表达 | 擦皮鞋 |
| Verifying | 验证 | 验证方案的可行性 | 对勾 |
隐喻/趣味词
| 单词 | 释义 | 模型行为 | 记忆锚点 |
|---|---|---|---|
| Divining | 占卜 | 模型在不确定性中寻找答案 | 水晶球 |
| Brewing | 酿造 | 把多个想法混合发酵成方案 | 大锅炖 |
| Conjuring | 召唤 | 从训练数据中调用相关知识 | 魔法棒 |
| Crafting | 手作 | 精细构造代码/文本 | 木工台 |
| Cultivating | 培育 | 逐步优化输出,反复迭代 | 种花 |
| Foraging | 觅食 | 在长上下文中搜索关键信息 | 松鼠找坚果 |
四、常见误解
误解 1:“这些词表示模型在排队”
实际: 排队状态有专属词——Queued、Waiting for capacity。Spinner 词汇只表示模型正在推理中,不表示排队。
误解 2:“词多说明模型卡住了”
实际: 词汇按轮次递进,连续任务中每个词只出现一次。如果看到同一个词反复出现,说明模型在同一轮推理中卡住,而不是因为词表循环。
误解 3:“Verbose 模式可以回溯”
实际: Verbose 模式需要提前打开(Ctrl+O),中途切换无法回溯之前已经执行完毕的推理过程。如果任务已经执行到中间阶段才打开 Verbose,只能看到后续的推理过程。
从 AtomCode 续杯到昇腾容器:我把免费 NPU 接进了 Mac
摘要
本来只是在 ai.atomgit.com 盯着 AtomCode 的免费模型续杯倒计时,随手点开旁边的“昇腾模型生态“选项卡,没想到挖到了宝。本文将记录如何把网页里的“昇腾模型助手“变成 Mac 本地 Agent 的后端,过程中充满容器权限的博弈、pip 的依赖坑和网络隔离挑战,最终在远端昇腾 NPU 上跑通 Qwen2.5-7B,并通过 SSH 反向隧道将 OpenAI 兼容的 API 服务穿透到本地 Mac,实现免本地内存压力的远端推理。
前言:AtomCode 的“续杯“日常与功耗焦虑
作为一名重度依赖 AI 辅助编程的开发者,我早已习惯了 AtomCode 官方客户端的节奏。平时盯着官方构建版,心里都有一本账:
- Lite 版: 30 天一登录一续,不用抢,到点
/login一下就能续杯,主打一个佛系。 - Pro 版: 想用 GLM-5.2 就得抢。但要注意——Pro 没过期的时候点
/login是续不了的,系统不让续,必须等当前 Pro 过期那一天、第二天早上 10 点准点去抢才能抢到 GLM-5.2 额度。
AtomCode 里能调到的 DeepSeek-V4-Flash、GLM-5.2 这类模型,并不是在 Mac 上跑的。它们更可能是 AtomGit 平台提供的中转 API,或者是模型方在 AtomGit / 华为云之上部署过的云端实例,atomcode 客户端只是走 HTTP 调远端。
直到那天,我盯着网页中的选项卡,目光从 AtomCode 移到了旁边的“昇腾模型生态“。出于好奇点进去,发现这不仅仅是静态页面,而是一个活生生的昇腾模型助手 Agent 页面——带 Web 终端的那种。我决定试试,能不能把这个网页里的算力“抠“出来,给本地 Agent 当后端。
踩坑实录一:容器环境的“镣铐“
申请容器、进终端,第一刻就意识到这不会一帆风顺——这是个典型的受限环境:
- 家目录只读(Read-Only): .ssh/config 写不了,known_hosts 也落不下来,pip install 往用户目录写包也会炸。所有“临时物“只能往 /tmp 或 /opt/atomgit 塞。
- pip 的 PEP 668 坑: 直接 pip install fastapi 会报 error: externally-managed-environment。必须用
pip install --break-system-packages,现代容器(Debian 12 系)基本都踩这个。 - CANN / torch-npu 的环境变量:
ASCEND_HOME、LD_LIBRARY_PATH没全默认加载时,Torch-NPU 会找不到底层 .so;另外ASCEND_LOG_DIR如果指到家目录会报错,得手动export ASCEND_LOG_DIR=/opt/atomgit/ascend/log。 - torch_dtype 弃用: 新版 transformers 里
torch_dtype=torch.float16已弃用,得用torch_dtype="torch.float16"字符串形式,或者用torch_dtype=torch.bfloat16。
踩坑实录二:核心依赖安装
昇腾 NPU 的核心依赖是 torch_npu,它需要匹配特定版本的 PyTorch 和 CANN 工具包。安装命令:
pip install torch_npu==2.1.0.post1 --break-system-packages
验证 NPU 是否可用:
import torch
import torch_npu
print(torch.npu.is_available()) # 应输出 True
print(torch.npu.device_count()) # 应输出 NPU 数量
踩坑实录三:网络隔离与 SSH 反向隧道
容器环境没有公网 IP,但可以通过 SSH 反向隧道把服务暴露到本地。
在容器内启动推理服务
# app.py - 使用 FastAPI 提供 OpenAI 兼容 API
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class ChatRequest(BaseModel):
model: str
messages: list
stream: bool = False
@app.post("/v1/chat/completions")
async def chat_completions(request: ChatRequest):
# 调用昇腾 NPU 上的 Qwen2.5-7B
...
建立 SSH 反向隧道
在容器内执行:
ssh -R 8000:localhost:8000 user@your-mac-ip -N -f
这样本地 Mac 访问 localhost:8000 就相当于访问容器内的推理服务。
最终效果
- 本地 Mac 不消耗内存运行 7B 模型
- 远端昇腾 NPU 跑 Qwen2.5-7B,通过 SSH 隧道提供 OpenAI 兼容 API
- 任意本地 Agent 工具(如 Continue、Cursor 等)都可以配置为使用这个远端 API
- 容器到期后重新申请即可,NPU 算力免费续杯
总结
从 AtomCode 续杯倒计时出发,意外发现昇腾模型生态的免费 NPU 算力。通过容器环境的“镣铐舞蹈“(只读家目录、PEP 668、环境变量缺失),最终成功在远端 NPU 上跑了 Qwen2.5-7B,并用 SSH 反向隧道把 API 服务穿透到本地 Mac。整个过程让 Mac 的内存压力归零,推理性能却跑在云端 NPU 上。
Mighty Rodent Splash 黑屏踩坑记录
日期:2026-07-09 项目:rust-bevy-mighty-rodent(Bevy 0.14 重写 Mighty Rodent) 现象:
cargo run后游戏窗口显示约 2 秒黑色,然后直接进主菜单,看不到 splash 两张图(jaggedlogo.jpg/mainpic.jpg)
一、症状
- 窗口出现 → 黑屏约 2 秒 → 直接 MainMenu。
- 期望:jaggedlogo.jpg 全显 1.5s → 淡到黑 0.8s → mainpic.jpg 从黑变亮 + 蓝色进度条 2.0s → 持亮 0.5s → MainMenu。
二、诊断走过的弯路(按发生顺序)
弯路 1:以为是 splash 系统没执行
src/splash.rs是git status里的??未跟踪文件,怀疑没被编进 binary。- 验证:
cargo check通过,main.rs:4有mod splash;,main.rs:105注册了splash::SplashPlugin——代码确实编进去了。 - 结论:不是没编进。
弯路 2:以为是 OnEnter(默认状态) 不触发
AppState::SplashLogo是#[default]初始态,show_splash_logo原挂在OnEnter(SplashLogo)。- 怀疑 Bevy 0.14 对“程序一开始就在的默认态“不触发
OnEnter。 - 改法:把
show_splash_logo从OnEnter改挂Startup。 - 验证:重跑 → 还是黑屏。
- 结论:改挂 Startup 是对的(保留),但不是黑屏根因。
弯路 3:被日志诊断误导
- 想靠
info!("Splash: Jaggedblade logo")确认系统执行没。日志里这条 0 次出现。 - 推断:“splash 系统没执行”——这个推断是错的。
- 真相:
main.rs:93.disable::<LogPlugin>()禁了 Bevy 自带 logger,但src/splash.rs里info!是bevy::prelude::*的(走 Bevy logger),被禁后全沉默。而main.rs:120那条log::info!("Bevy app starting")走的是logcrate(simplelog 接的是这个),所以打得出来。 - 坑:
bevy::prelude::*的info!≠log::info!。disable::<LogPlugin>()后前者沉默,后者照常。诊断 splash 这类用info!的系统,要换log::info!。 - 验证:把 splash 里 4 处
info!全换成log::info!→ 日志真能打出来了。 - 结论:是诊断手段失灵,不是 splash 没执行。
弯路 4:在 Bevy 源码里查 Camera2dBundle 默认 transform
- 想确认相机默认 z 是不是正值(导致 z=0 的图在相机后面)。
- 在
~/.cargo/registry/src/.../bevy-0.14.2/crates/里 grepCamera2dBundle—— 无输出。 - 绕了两轮没找到,按 STOP WHEN STUCK 放弃。
- 结论:别在 Bevy 源码里硬找 API,直接诊断系统打 transform 值。
三、根因(诊断系统钉死)
在 SplashPlugin::build 里临时加 splash_diag_system(Update,一次性 Local flag),用 log::info! 打相机数、splash 实体数、transform、custom_size、texture handle path:
DIAG: cameras=1, splash_entities=2, splash_images=1
DIAG cam0: translation=Vec3(0.0, 0.0, 0.0) (z=0.00)
DIAG img0: translation=Vec3(0.0, 0.0, 0.0) (z=0.00) custom_size=Some(Vec2(800.0, 600.0)) path=gfx/jaggedlogo.jpg
Splash: Jaggedblade logo (Startup)
Splash: Main title picture (loading)
事实:
- splash 系统执行了(两段都跑了)。
- 相机 spawn 了,位于原点 z=0,看向 -Z(Camera2dBundle 默认)。
- splash 图 spawn 了,texture handle 路径对(
gfx/jaggedlogo.jpg),custom_size 对(800×600)。 - 图 z=0,与相机平面重合。
根因: Bevy 2D 相机近平面在 z=0 前一小段,z=0 的 sprite 与相机平面重合,2D 渲染管线不绘制 → 黑屏。所有可见 sprite 的 z 必须为负(在相机前方),且 z 越小越远。
原代码 z 值全错:
| sprite | 原 z | 问题 |
|---|---|---|
| 黑背景 | -1.0 | OK(一直在相机前) |
| splash 图(logo + mainpic) | 0.0 | 与相机平面重合,不绘制 → 黑屏 |
| loading bar 背景 | 0.5 | 正值,在相机后面,不可见 |
| loading bar 填充 | 1.0 | 同上 |
四、修复
src/splash.rs,把所有 splash sprite 的 z 改成负值,按层序从远到近:
| sprite | 新 z | 层序 |
|---|---|---|
| 黑背景 | -1.0(不变) | 最远 |
| splash 图 | -0.5 | 背景前 |
| loading bar 背景 | -0.4 | 图前 |
| loading bar 填充 | -0.3 | 最前 |
代码锚点:
spawn_splash里 splash 图的Transform::from_xyz(0.0, 0.0, -0.5)(原 0.0)show_splash_main里 loading bar 背景Transform::from_xyz(0.0, -280.0, -0.4)(原 0.5)show_splash_main里 loading bar 填充Transform::from_xyz(-200.0, -280.0, -0.3)(原 1.0)
五、次要改动
show_splash_logo改挂Startup(绕开 Bevy 0.14OnEnter(默认态)不触发问题)——保留,与时序一致。- splash 里 4 处
info!改log::info!——保留,让诊断信息真能打到 simplelog。 - 诊断系统
splash_diag_system——已删除,它已完成使命。 - 进度条颜色改成绿色:
Color::srgb(0.0, 0.8, 0.0)(原Color::srgb(0.0, 0.8, 1.0)蓝色)。
六、经验沉淀
| 坑 | 教训 |
|---|---|
bevy::prelude::* 的 info! 在 disable::<LogPlugin>() 后沉默 | 诊断 Bevy 系统执行情况,用 log::info!(走 simplelog),不要用 info! |
| Bevy 2D 相机看 -Z,近平面在 0 前 | 2D sprite 的 z 必须为负才可见;z=0 与相机平面重合不绘制;正值在相机后面不可见 |
OnEnter(默认状态) 在 Bevy 0.14 不触发 | 默认态的初始化系统挂 Startup,不要挂 OnEnter |
| 在 Bevy 源码里硬找 API | 别绕源码,直接诊断系统打运行时值 |
| 凭“日志没打“推断“系统没执行“ | 先确认日志路由对不对(logger 是哪套),再推断执行情况 |
七、验证
cargo check → 通过(只剩先前 14 个无关 warning)
cargo run → jaggedlogo.jpg 全显 → 淡到黑 → mainpic.jpg 变亮 + 绿色进度条填满 → MainMenu ✅
Co-Authored-By: AtomCode (GLM-5.2) noreply@atomgit.com
红色警戒 2 Mod 黄色警戒 主菜单音乐 Suno AI 风格提示词
日期:2026-07-12 标签:
Suno AI提示词红色警戒 2黄色警戒游戏音乐适用模型:Suno AI v4.5-all
概述
本文档记录《红色警戒 2》Mod 黄色警戒(Yellow Alert) 主菜单音乐风格的 Suno AI 风格提示词(Style Prompt),适用于 Suno AI v4.5-all 模型(免费版),可用于生成相似风格的音乐。
提示词
Dance-pop at 124 BPM with a tight four-on-the-floor kick and smooth, steady club pulse. Verses drop into a half-time groove with clipped percussion and muted guitar chanks locking into the beat. Pre-chorus sections rise on filtered pad lifts and growing vocal layers, pushing into widescreen choruses with airy synth chords and glossy synth bass. Reversed swells shape transitions, bell tick accents sparkle between phrases, and sidechain shimmer breathes against the drums. Intimate close-mic lead vocal with doubled hook lines sits front and center in a radio-clean mix, enhanced by warm saturation and a late-night glow.
说明
- 风格:Dance-pop(舞曲流行)
- 速度:124 BPM
- 适用场景:游戏主菜单、界面背景音乐
- AI 工具:Suno AI(v4.5-all,免费模型),填入 Style Prompt 或类似字段
许可说明
⚠️ Suno AI 非商用授权提醒
本仓库中由 Suno AI 生成的音乐内容受 Suno AI 许可协议约束,仅限非商用用途。请勿将生成的音乐文件用于商业场景(包括但不限于商业化游戏发布、商业广告、付费订阅内容等)。具体许可条款以 Suno AI 官方许可协议为准。
Co-Authored-By: AtomCode (deepseek-v4-flash) noreply@atomgit.com