我用 TypeScript 完全重写了 Pokemon Yellow —— 它是如何在浏览器中运行的
Source: Dev.to
我花了数月时间用 TypeScript 从头重写 Pokémon Yellow。这不是模拟器——它是一次完整的重新实现,逐条指令地将 Z80 汇编移植到 HTML5 Canvas。
在此玩 (自行提供 ROM) | 源代码
概览
整个引擎约 280 KB 的 TypeScript。没有随游戏一起提供任何资源;所有内容都直接从你自己的 ROM 文件在浏览器中提取。ROM 永远不会离开你的机器——生产构建只包含引擎本身,不包含任何受版权保护的内容。
引擎架构
ROM 验证与提取
- 通过 SHA‑1 哈希验证上传的 ROM。
- 运行 14 个专用提取器,解析原始二进制数据:
- 宝可梦属性、招式数据、地图布局、对白文本、训练家队伍、音频序列。
图形解压缩
- 使用原始压缩算法(RLE + 位对编码 + 增量解码)解压第 1 代精灵。
- 将 Game Boy VRAM 格式的 1bpp/2bpp 瓦片图形解码为
ImageData。
缓存
- 将所有提取的数据缓存到 IndexedDB,因此再次访问时可瞬间加载。
汇编到 TypeScript 的翻译
原始游戏运行在 Sharp LR35902(Game Boy CPU)上。移植意味着将类似下面的汇编翻译为 TypeScript:
Audio1_CalculateFrequency:
ld a, [wChannelOctave]
inc a
cp 7
jr z, .done
sra d
rr e
jr .loop
function calculateFrequency(octave: number, freq: number): number {
let d = (freq >> 8) & 0xFF;
let e = freq & 0xFF;
for (let a = octave + 1; a > 1) | (d & 0x80)) & 0xFF; // SRA (arithmetic shift)
const carry = d & 1;
e = ((carry > 1)) & 0xFF; // RR (rotate right through carry)
d = newD;
}
return (d << 8) | e;
}
每个细节都很重要——使用逻辑右移 (SRL) 而不是算术右移 (SRA) 会改变音乐的音高。我曾两次犯下这个错误。
音频合成
- Game Boy 拥有四个声音通道(两个脉冲波,一个可编程波形,一个噪声)。
- 实现了基于
ScriptProcessorNode的合成器,以 44.1 kHz 生成采样,模拟全部四个通道。 - 音乐引擎以 59.7 Hz(匹配 Game Boy 的 VBlank 频率)运行,解释从 ROM 中提取的指令序列(速度变化、音符事件、占空比切换、颤音、音高滑音)。
- 50+ 条音乐轨道和 37 条音效均可完整播放。
战斗系统精确度
战斗系统忠实再现了第 1 代所有臭名昭著的怪癖:
- 聚焦能量 bug – 暴击率被除以 4 而不是乘以。
- 1/256 失误 glitch – 100 % 命中率的招式仍有约 0.4 % 的概率会失误。
- 徽章属性提升叠加 – 烧伤/麻痹的属性下降每次都会重新应用徽章加成,导致溢出。
一套 334 条战斗测试 验证了每个边缘情况都与原版游戏相符。
演示功能
- 带有动画皮卡丘和完整音乐的标题画面。
- 大木博士的介绍序列。
- 真新镇 → 1号道路 → 常磐市(共 12 张地图)。
- 包含完整第 1 代机制的野生和训练家战斗。
- 带有亲密度系统的皮卡丘随行。
- 图鉴、队伍菜单、道具、商店、电脑、存档/读取。
- 为移动端/平板提供的触控控制。
实时演示允许你上传合法获取的 ROM,并在桌面或移动设备的浏览器中进行游戏。
技术细节
- TypeScript + HTML5 Canvas – 无框架、无依赖。
- 使用 Vite 进行构建。
- 使用 Vitest 进行测试(375 条测试)。
- 大约 35 000 行 TypeScript 源码。
仓库与许可证
- GitHub – MIT 许可证。欢迎提交 PR。
- 与 Claude Code 作为编码伙伴共同构建。