bjcoombs/jet-fighters
GitHub: bjcoombs/jet-fighters
在浏览器中周期精确地模拟 1979 年 Gakken/CGL Jet Fighters 桌面 VFD 游戏的 Hitachi HMCS44 微控制器与真空荧光管硬件的模拟器。
Stars: 0 | Forks: 0
# 喷气式战斗机
这是一款对 1979 年 Gakken/CGL 推出的《Jet Fighters》桌面 VFD 游戏的模拟器,可在浏览器中运行。
**[在此游玩](https://bjcoombs.github.io/jet-fighters/)**
这不是对游戏规则的重新实现,而是对运行该游戏的机器进行了周期精确的模拟:一个执行 2 KB 程序的 Hitachi HMCS44 微控制器,逐一网格地扫描双荧光真空荧光管,并通过切换单个引脚来发声。游戏规则存在于本仓库的汇编源码 `asm/jetfighter.asm` 中;而时序、显示的微光闪烁以及声音,都是在运行过程中自然产生的,而非通过近似模拟得出。有关其背后的完整设计依据和硬件研究,请参阅 `docs/prd/jet-fighters-v2.md`。
## 控制说明
| 操作 | 键盘 | 机身上的控制器 |
| ------------- | ----------------------- | ----------------------------- |
| 移动发射器 | 上 / 下方向键,W / S | 发射器拉杆(3 条轨道) |
| 发射导弹 | 空格键 / Enter 键 | 蓝色开火按钮 |
| 难度等级 | 1 / 2 / 3 | 旋钮式难度刻度盘 (1 / 2 / 3) |
| 电源 | P | 开启/关闭 拨动开关 |
| 静音 | M | 机身上的扬声器按钮 |
拉杆和开火按钮为持续按压式触点:机器会在每次显示扫描时从其输入矩阵中读取它们的状态,这与真实设备完全一致。
**电源开关是唯一的重置方式。** 开机时会重置处理器,此时 RAM 处于未定义状态,随后游戏程序会将其清零;关机时则会停止运行,RAM 中的内容也会随电源断开而消失。真实设备就是通过这种方式开始新游戏的,因此这里也特意没有设置重置按钮。
静音功能屏蔽的是浏览器的输出,而不是机器本身——程序会继续切换扬声器引脚的状态,就像真实设备断开压电元件时一样。
## 架构
分为五层,映射了物理机器的结构。数据流向与当年的电流流向完全一致。
```
flowchart LR
ASM[asm/jetfighter.asm
the game program] -->|assembled by tools/hmasm| ROM[ROM image
2048 x 10 bits] ROM --> CPU[src/machine/cpu/
HMCS44 core, cycle-accurate] SW[src/ui/ case controls] -->|strobe matrix| CPU CPU -->|D0-D9 grids, R-port plates| BOARD[src/machine/board/
grid x plate PWM state] CPU -->|D14 pin edges| SPK[src/machine/audio/
square reconstruction] BOARD --> TUBE[src/machine/tube/
segment atlas + phosphor] MAIN[src/main.ts
the only clock] -.->|steps| CPU MAIN -.->|draws| TUBE MAIN -.->|drains| SPK ``` | 路径 | 包含内容 | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `asm/jetfighter.asm` | 游戏本体——包含所有规则、节奏、音效和分数,使用 HMCS44 汇编语言编写,严格遵守了 2048 字 / 160 位 (nibble) 的真实容量限制。 | | `tools/hmasm/` | 汇编器、其 CLI,以及用于将 `.asm` 文件转换为可导入模块的 Vite 插件。 | | `src/machine/cpu/` | HMCS44 核心:91 条指令、4 位 ALU、4 级堆栈、定时器、R/D 端口。仅在步进时推进。 | | `src/machine/board/` | 电路板:PWM 显示状态、输入频闪矩阵、D14 边沿捕获以及电源开关。 | | `src/machine/tube/` | 真空管:段图集(每个荧光段的形状及其 (grid, plate) 地址)以及渲染器的荧光上升/衰减曲线。 | | `src/machine/audio/` | 扬声器:将带有周期时间戳的边沿放置在采样时间线上,并经过带限处理转化为波形。 | | `src/ui/` | 外壳——包含注塑成型的机身、示波器风格的视窗以及四个控制器。 | | `src/input/` | 键盘与触摸输入,并将其转换为上述四个控制器的移动操作。 | | `src/main.ts` | 帧驱动器,也是程序中唯一的时钟。 | | `tools/probe/` | Headless 机器探针:从终端驱动电路板,并报告硬件的执行情况。 | 以下两条规则是支撑其他所有部分的基础: - **除了 `src/main.ts` 外,没有任何模块拥有自己的时钟。** 电路板仅在步进时推进,因此同一套机器程序在浏览器中以 60 Hz 运行,和在 Node 环境中全速运行的效果完全一致。 - **`src/machine/` 绝不触碰 DOM。** 这使得探针和频谱测试能够在 headless 环境下驱动真实的机器,这一点已由在 `node` 环境下运行的测试所验证。 在模拟的 RAM 之外不存在任何游戏状态。分数、喷气式飞机、剩余生命和难度等级都是程序写入的位 (nibble);控制器的操作只有通过闭合程序在下一次扫描时读取的触点,才能传达给游戏。 ## 开发说明 ``` npm install && npm run dev ``` 脚本: - `npm run build` - 进行类型检查并在 `dist/` 目录中生成生产环境构建 - `npm test` - 运行 Vitest 测试套件 - `npm run lint` - 对源代码进行 lint 检查 - `npm run preview` - 在本地预览生产环境构建 零运行时依赖。上述所有内容均作为应用程序自身的代码提供。 ### 修改游戏 游戏行为的修改需通过编辑 `asm/jetfighter.asm` 完成,而不是修改 TypeScript。`tools/hmasm/vite-plugin.ts` 中的 Vite 插件会在导入时自动对其进行汇编,因此在运行 `npm run dev` 时,一旦汇编代码发生更改,就会立即重新汇编并重新加载——不存在会失效的生成 ROM 文件。 您可以自行汇编,并生成显示地址、操作码和源码行的列表文件: ``` npx vite-node tools/hmasm/cli.ts asm/jetfighter.asm --listing /tmp/jetfighter.lst ``` 汇编器强制执行了真实的容量上限:超出 2048 个程序字或 160 个 RAM 位 (nibble) 会导致报错,而不仅仅是警告。 ### 无需浏览器即可观察机器运行 ``` # 400k cycles (one emulated second): which grids were strobed, which segments # lit and at what duty npx vite-node tools/probe/machine-probe.ts --cycles 400000 # move the lever mid-run and compare the tube before and after npx vite-node tools/probe/machine-probe.ts --cycles 400000 --input lever=up@200000 # capture the D14 transition stream for spectral analysis npx vite-node tools/probe/machine-probe.ts --cycles 400000 --input fire@200000 --emit-edges ``` 探针会向 stdout 输出一个 JSON 对象,其所有读取的数据均直接来源于电路板自身的观察接口,因此它报告的就是硬件真实执行的操作,而不是经过测试辅助工具加工后的摘要。 在开发版本中,正在运行的机器也可以通过浏览器控制台以 `window.jetFighters` 的形式访问(包含 `board`、`renderer` 以及汇编器的符号表),这就是您在调试 ROM 修改时检查 RAM 或对 CPU 核心进行单步执行的方式。 ## 致谢 原版游戏由 Gakken 制作(型号 81582,1979 年),在英国由 Computer Games Limited (CGL) 发行。本项目是非附属的粉丝同人重制作品,未获得 Gakken 或 CGL 的认可或关联。 基于 [MIT License](LICENSE) 授权。
这不是对游戏规则的重新实现,而是对运行该游戏的机器进行了周期精确的模拟:一个执行 2 KB 程序的 Hitachi HMCS44 微控制器,逐一网格地扫描双荧光真空荧光管,并通过切换单个引脚来发声。游戏规则存在于本仓库的汇编源码 `asm/jetfighter.asm` 中;而时序、显示的微光闪烁以及声音,都是在运行过程中自然产生的,而非通过近似模拟得出。有关其背后的完整设计依据和硬件研究,请参阅 `docs/prd/jet-fighters-v2.md`。
## 控制说明
| 操作 | 键盘 | 机身上的控制器 |
| ------------- | ----------------------- | ----------------------------- |
| 移动发射器 | 上 / 下方向键,W / S | 发射器拉杆(3 条轨道) |
| 发射导弹 | 空格键 / Enter 键 | 蓝色开火按钮 |
| 难度等级 | 1 / 2 / 3 | 旋钮式难度刻度盘 (1 / 2 / 3) |
| 电源 | P | 开启/关闭 拨动开关 |
| 静音 | M | 机身上的扬声器按钮 |
拉杆和开火按钮为持续按压式触点:机器会在每次显示扫描时从其输入矩阵中读取它们的状态,这与真实设备完全一致。
**电源开关是唯一的重置方式。** 开机时会重置处理器,此时 RAM 处于未定义状态,随后游戏程序会将其清零;关机时则会停止运行,RAM 中的内容也会随电源断开而消失。真实设备就是通过这种方式开始新游戏的,因此这里也特意没有设置重置按钮。
静音功能屏蔽的是浏览器的输出,而不是机器本身——程序会继续切换扬声器引脚的状态,就像真实设备断开压电元件时一样。
## 架构
分为五层,映射了物理机器的结构。数据流向与当年的电流流向完全一致。
```
flowchart LR
ASM[asm/jetfighter.asmthe game program] -->|assembled by tools/hmasm| ROM[ROM image
2048 x 10 bits] ROM --> CPU[src/machine/cpu/
HMCS44 core, cycle-accurate] SW[src/ui/ case controls] -->|strobe matrix| CPU CPU -->|D0-D9 grids, R-port plates| BOARD[src/machine/board/
grid x plate PWM state] CPU -->|D14 pin edges| SPK[src/machine/audio/
square reconstruction] BOARD --> TUBE[src/machine/tube/
segment atlas + phosphor] MAIN[src/main.ts
the only clock] -.->|steps| CPU MAIN -.->|draws| TUBE MAIN -.->|drains| SPK ``` | 路径 | 包含内容 | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `asm/jetfighter.asm` | 游戏本体——包含所有规则、节奏、音效和分数,使用 HMCS44 汇编语言编写,严格遵守了 2048 字 / 160 位 (nibble) 的真实容量限制。 | | `tools/hmasm/` | 汇编器、其 CLI,以及用于将 `.asm` 文件转换为可导入模块的 Vite 插件。 | | `src/machine/cpu/` | HMCS44 核心:91 条指令、4 位 ALU、4 级堆栈、定时器、R/D 端口。仅在步进时推进。 | | `src/machine/board/` | 电路板:PWM 显示状态、输入频闪矩阵、D14 边沿捕获以及电源开关。 | | `src/machine/tube/` | 真空管:段图集(每个荧光段的形状及其 (grid, plate) 地址)以及渲染器的荧光上升/衰减曲线。 | | `src/machine/audio/` | 扬声器:将带有周期时间戳的边沿放置在采样时间线上,并经过带限处理转化为波形。 | | `src/ui/` | 外壳——包含注塑成型的机身、示波器风格的视窗以及四个控制器。 | | `src/input/` | 键盘与触摸输入,并将其转换为上述四个控制器的移动操作。 | | `src/main.ts` | 帧驱动器,也是程序中唯一的时钟。 | | `tools/probe/` | Headless 机器探针:从终端驱动电路板,并报告硬件的执行情况。 | 以下两条规则是支撑其他所有部分的基础: - **除了 `src/main.ts` 外,没有任何模块拥有自己的时钟。** 电路板仅在步进时推进,因此同一套机器程序在浏览器中以 60 Hz 运行,和在 Node 环境中全速运行的效果完全一致。 - **`src/machine/` 绝不触碰 DOM。** 这使得探针和频谱测试能够在 headless 环境下驱动真实的机器,这一点已由在 `node` 环境下运行的测试所验证。 在模拟的 RAM 之外不存在任何游戏状态。分数、喷气式飞机、剩余生命和难度等级都是程序写入的位 (nibble);控制器的操作只有通过闭合程序在下一次扫描时读取的触点,才能传达给游戏。 ## 开发说明 ``` npm install && npm run dev ``` 脚本: - `npm run build` - 进行类型检查并在 `dist/` 目录中生成生产环境构建 - `npm test` - 运行 Vitest 测试套件 - `npm run lint` - 对源代码进行 lint 检查 - `npm run preview` - 在本地预览生产环境构建 零运行时依赖。上述所有内容均作为应用程序自身的代码提供。 ### 修改游戏 游戏行为的修改需通过编辑 `asm/jetfighter.asm` 完成,而不是修改 TypeScript。`tools/hmasm/vite-plugin.ts` 中的 Vite 插件会在导入时自动对其进行汇编,因此在运行 `npm run dev` 时,一旦汇编代码发生更改,就会立即重新汇编并重新加载——不存在会失效的生成 ROM 文件。 您可以自行汇编,并生成显示地址、操作码和源码行的列表文件: ``` npx vite-node tools/hmasm/cli.ts asm/jetfighter.asm --listing /tmp/jetfighter.lst ``` 汇编器强制执行了真实的容量上限:超出 2048 个程序字或 160 个 RAM 位 (nibble) 会导致报错,而不仅仅是警告。 ### 无需浏览器即可观察机器运行 ``` # 400k cycles (one emulated second): which grids were strobed, which segments # lit and at what duty npx vite-node tools/probe/machine-probe.ts --cycles 400000 # move the lever mid-run and compare the tube before and after npx vite-node tools/probe/machine-probe.ts --cycles 400000 --input lever=up@200000 # capture the D14 transition stream for spectral analysis npx vite-node tools/probe/machine-probe.ts --cycles 400000 --input fire@200000 --emit-edges ``` 探针会向 stdout 输出一个 JSON 对象,其所有读取的数据均直接来源于电路板自身的观察接口,因此它报告的就是硬件真实执行的操作,而不是经过测试辅助工具加工后的摘要。 在开发版本中,正在运行的机器也可以通过浏览器控制台以 `window.jetFighters` 的形式访问(包含 `board`、`renderer` 以及汇编器的符号表),这就是您在调试 ROM 修改时检查 RAM 或对 CPU 核心进行单步执行的方式。 ## 致谢 原版游戏由 Gakken 制作(型号 81582,1979 年),在英国由 Computer Games Limited (CGL) 发行。本项目是非附属的粉丝同人重制作品,未获得 Gakken 或 CGL 的认可或关联。 基于 [MIT License](LICENSE) 授权。
标签:MITM代理, 复古游戏, 嵌入式系统, 微控制器, 快速连接, 汇编语言, 硬件模拟, 自动化攻击