Egerion/open-atomic-bomberman-97

GitHub: Egerion/open-atomic-bomberman-97

用现代 C++20 净室重写 1997 年经典游戏 Atomic Bomberman,忠实还原玩法核心并新增了基于回滚 netcode 的互联网联机功能。

Stars: 1 | Forks: 0

# 打开 Bomberman 对 **Atomic Bomberman**(Interplay, 1997)的一次净室、现代 **C++20** 重写——这是一个确定性的重新实现,使用*你自己的*游戏副本资源来渲染原版游戏,并为这款 1997 年的游戏添加了**互联网多人游戏**功能,这是原版从未以能在现代网络中存续的形式提供的。

Open Bomberman's main menu

**状态:** 可玩。包含忠实还原的确定性模拟(移动、炸弹、踢/拳/抓/投、喷射器、九种疾病、HURRY 墙壁收缩、传送带/传送洞/蹦床、头部命中),在运行时加载原始美术/声音/音乐,随机关卡轮换,以及方案编辑器——此外还支持**通过互联网进行多人游戏**:分享 6 个字符的大厅代码或公开列出你的比赛,对端会打通直接的 UDP 路径(当 NAT 拒绝时回退到中继服务器),并在基于 GGPO 风格的回滚 netcode 上进行游戏,没有输入延迟。房主在游戏自带的名册和关卡界面中进行设置,客机可以观看,因此地图选择和 AI 插槽在网上的工作方式与本地完全相同,并且大厅最多可容纳**十台机器**——如果人数较少,可以用 AI 填满棋盘的其余部分。

Six computer players fighting through one round

## 完全使用 Claude Code 构建——无需人工手写代码 这是一次完全由 AI 编写软件的实验。**本仓库中的每一行代码——逆向工程笔记、净室 C++ 移植、确定性模拟、SDL3 表现层、测试套件、在线 netcode 和构建系统——都是由 Claude(由 [Claude Code](https://claude.com/claude-code) 驱动的 Anthropic 编码代理)根据自然语言指令编写的。** 没有任何源码是手写的。 人类的职责是产品方向和逆向工程指导——“原版是这么做的,让我们的跟它一致”、“外墙渲染错了”、“让多人模式更流畅”——以及运行结果并在游戏内进行验证。Claude 完成了逆向工程信息提炼、原版算法的忠实移植、锁定行为的测试,以及 netcode 的编写。使这一切变得可行的工作流程与任何团队都会使用的方法相同:带有黄金哈希测试的确定性核心、在移植前记录下来的逆向工程事实 (`docs/re/`),以及作为 ADR 记录的决策 (`docs/adr/`)。 ## 功能 - **忠实的确定性游戏核心**——仅使用整数的 20 Hz 模拟,移植过程完全镜像原始二进制文件的算法(不是改写),带有黄金哈希测试以防止意外修改导致行为变化。 - **运行时使用原始资源**——从你自己的安装中读取 1997 年的 ANI/PCX/SCH/RES/RSS 格式;未捆绑任何内容。 - **在线多人游戏**——在 UDP 上实现确定性锁步**和** GGPO 风格的回滚,带有逐 tick 的 `state_hash` 失步检测和丢包容忍;可从菜单(*Start / Join Network Game*)或命令行进行游戏。 - **表现层附加功能**,这在 1997 年的版本中是没有的——HD 画质切换、原生节奏的“creamy”低延迟模式 (F9)、FPS/vsync 切换,以及可选的平滑(双线性)放大功能(适合喜欢现代显示器缩放器赋予 640x480 图像的那种平滑外观的人)——全部默认关闭,并且都避开了忠实还原的 Options 屏幕(它们位于移植版自带的 F10 面板中)。 - **工具**——无头资源检查器/提取器 (`abtool`)、动画查看器 (`bomber_viewer`) 和方案编辑器。 ## 布局 不含 SDL 的核心: ``` libs/core shared vocabulary: fixed-point unit, field geometry, limits — header-only, no deps libs/assets loaders for the original formats (ANI, PCX, SCH, RES, RSS) — SDL-free libs/sim deterministic 20 Hz gameplay core (state + systems) — dependency-free libs/match scheme + VALUELST -> MatchConfig glue (header-only) libs/net online netcode: input codec, lockstep + rollback sessions, UDP transport, lobby client — SDL-free libs/audio AudioEngine + SoundBank (the ported selection engine) + SoundDirector libs/platform engine base: SDL3 frame clock + an SDL-free, unit-tested frame pacer ``` 表现层堆栈——九个包,箭头仅单向指向,从一个原先长达 1.3 万行的 `libs/game` 中拆分出来: ``` libs/game_util SDL-FREE models: front-end state machine, results bookkeeping, pixel/pacing rules libs/input keyboard, gamepad, DOS-scancode bridge (SDL-free public headers) libs/render AssetStore, sprites, sequences, the world Renderer libs/ui Screen/ScreenContext, the .BM text viewer, window/dialog/list chrome libs/netui net widgets drawn inside another screen: chat overlay, setup link, F3 panel libs/editor the .SCH scheme editor's SDL surface libs/frontend every screen the player sees, plus MatchRunner libs/netplay the online session end to end: lobby, connect/host, best-of-N match libs/game the application shell: GameApp — SDL/window lifetime and the AppState loop ``` ``` apps/ bomber_game (OPEN-BM95), bomber_viewer, abtool (thin mains) services/ matchmaker (Go, separate build): lobby control plane + STUN + relay tests/ doctest suites, one directory per module, incl. golden-hash behaviour pins + netcode determinism ``` 有关完整的架构和项目规则,请参见 `CLAUDE.md`;有关研究笔记和决策,请参见 `docs/re/facts.md` 和 `docs/adr/`。 ## 需求 - 原版的 Atomic Bomberman 安装包(游戏数据**不**包含在内,且绝不能提交到仓库——参见 `.gitignore`) - CMake ≥ 3.25 - Windows:Visual Studio 2022;通过 FetchContent 获取 SDL3(`windows-fetch` 预设,无需 vcpkg)或通过 vcpkg(`windows-msvc` 预设,已设置 `VCPKG_ROOT`) - Linux/macOS:任何 C++20 编译器;通过 FetchContent 获取 SDL3(`linux` / `macos` 预设)。Linux 还需要 SDL3 构建其 backend 所需的 X11/Wayland/ALSA 开发头文件——请参见*构建*部分。 ## 构建 Windows(游戏 + 查看器 + 工具,无需 vcpkg): ``` cmake --preset windows-fetch cmake --build --preset windows-fetch ``` Linux 和 macOS(结构相同——SDL3 从源码获取并构建): ``` cmake --preset linux # or: --preset macos cmake --build --preset linux ``` 在 Linux 上,SDL3 需要 X11/Wayland/音频开发头文件来构建其 backend;CI 安装的确切软件包列表位于 [`.github/workflows/c-cpp.yml`](.github/workflows/c-cpp.yml) 的顶部。在 macOS 上, `scripts/package_macos.sh /path/to/your/BOMBRMAN` 会更进一步,组装出一个可双击、独立运行的 `dist/Open Bomberman.app`——SDL3 是静态链接的,因此无需打包任何 dylib,你指定的游戏数据会被复制到 app 的 `Resources/` 目录中。在 Mac 上运行它;由于它未签名,首次启动需要右键点击 → 打开。 无头模式——不含 SDL,因此不需要显示、X11 或音频头文件。这是 pre-push 门禁构建的配置,但它**不是**没有依赖项:它仍然会构建在线大厅堆栈,该堆栈会获取 IXWebSocket、nlohmann/json 和 mbedTLS 并从源码编译 mbedTLS。 ``` cmake --preset headless cmake --build --preset headless ``` 便捷包装命令:`make run`、`make viewer`、`make test`、`make survey`、`make deploy`(Windows 需要 GNU make:`winget install ezwinports.make`)。 ## 首次设置——将引擎指向你的安装目录 引擎不附带任何游戏数据:它会在运行时从*你自己的* Atomic Bomberman 安装中读取原始的 ANI/PCX/SCH/RES/RSS 文件。因此,需要设置的就是**该安装目录所在的位置**。 **最简单的方法:将 `OPEN-BM95.exe` 复制到你的安装文件夹中并运行。** 可执行文件是完全独立的——不需要 SDL DLL,也不需要 Visual C++ 可再发行组件(MSVC 运行时是静态链接的),因此在 Windows 上它是一个不需要安装任何其他内容的单一文件。放入游戏文件中后,无论文件夹叫什么名字,它都能自动找到它们。 如果你更愿意把它放在其他地方,安装目录将按以下顺序解析,先到先得——显式放置总是优先于机器全局猜测: 1. 命令行上的显式路径——`OPEN-BM95 "D:\...\BOMBRMAN"` 2. `BOMBER_GAME_DIR` 环境变量 3. **`gamedir.txt`**——只有一行,即路径,没有其他内容;在工作目录中查找,然后在可执行文件旁边查找 4. 可执行文件所在的文件夹,如果它包含 `DATA\`(即上面的“直接复制进去”的情况),或者它旁边的 `BOMBRMAN` 文件夹 5. 相对于工作目录的相同两种结构 6. 标准安装路径(`C:\Program Files (x86)\INTRPLAY\BOMBRMAN`,以及 `D:` 盘上的相同路径) 如果找不到安装目录,它现在会在对话框中说明,而不是静默退出。对于构建树,最简单的方法是在 repo 根目录中放置 `gamedir.txt`—— 它已被 gitignored,因此你的路径永远不会进入提交: ``` echo D:\Games\BOMBRMAN> gamedir.txt ``` 端到端验证它——这会遍历整个安装目录并报告找到的内容: ``` make survey ``` ### make 目标以及你需要哪一个 | 目标 | 它的作用 | 需要你的安装路径吗? | |---|---|---| | `make build` | 编译到 `build//` 中——**不会触碰你的安装** | 否 | | `make run` | 构建,然后从构建目录运行 | 否(自动检测) | | `make deploy` | 构建,然后将 `OPEN-BM95.exe` 复制**到你的安装目录中**,以便它在原始资源旁边运行(加上 `SDL3.dll`,仅当你配置了动态 SDL 时) | 是 | | `make survey` | 使用 `abtool` 验证你安装的资源 | 是 | | `make test` | 构建**完整**游戏(与 `make build` 使用相同的预设——*不是*无头模式)并运行测试套件 | 否 | `deploy` 和 `survey` 如果你传递了路径,则从 `GAME_DIR=` 获取路径,否则 从 `gamedir.txt` 获取。如果两者都没有提供,它们会停止并告诉你,而不是猜测目录并写入令人意外的地方。 上面的每个目标共享一个构建树——Windows 上是 `windows-fetch`, 其他地方是 `build/make`——因此所有这些目标(包括 `make test`)都会构建 SDL3 游戏和查看器。无头配置是你直接调用的单独预设 (`cmake --preset headless`);这就是 `bash scripts/test.sh` 和 pre-push 钩子所使用的配置。 ## 运行 `OPEN-BM95`(游戏)——可玩本地 + 在线模式: ``` OPEN-BM95 # auto-detects the game install, boots to the menu OPEN-BM95 # explicit install + scheme ``` 玩家 0:方向键 + Right Ctrl/Space(炸弹),Right Shift(投掷/抓取/触发/拳击) · 玩家 1:WASD + Left Ctrl/E,Left Shift · Esc:退出。如果找不到你的安装目录,它会提示你——请参见上面的*首次设置*。 ### 在线多人游戏 **从菜单:** **Start Network Game** 会打开包含六行的 *Network Game* 列表: | 行 | 需要服务器? | 它的作用 | |---|---|---| | **Host Private Game** | 是 | 创建大厅并显示 6 个字符的**代码**。将其读给朋友听。 | | **Host Public Game** | 是 | 同上,但也会列在 *Browse Public Games* 下供任何人查找。仍然可以通过代码加入。 | | **Join by Code** | 是 | 输入房主的 6 个字符代码。 | | **Browse Public Games** | 是 | 列出开放的**公开**大厅——名称、玩家数/座位数、代码。按 `Enter` 加入,`R` 刷新,`Esc` 返回。构建版本与你不同的行会变灰并标记为 `VERSION`;它们无法加入(服务器会拒绝)。 | | **Host LAN Game** | 否 | 在 UDP 端口 8000 上托管并等待(原版的直接路径)。 | | **Join by IP Address** | 否 | 输入房主的 `IP:port`(默认为 `127.0.0.1:8000`)。 | 这四个**在线**行都会进入同一个**等候室**:大厅代码显示在窗口标题中,名册列出每个座位(名称、房主标记、准备状态),按 `Space` 可切换你的准备标志,房主按 `Enter` 开始,按 `Esc` 离开。房主开始后,对端会打通直接的 UDP 路径(STUN/打洞)——如果它们的 NAT 拒绝,则回退到服务器中继——比赛以**服务器**的种子和座位分配开始。在比赛开始前,直接路径必须在**双向**都证明自己:如果一方能听到你但你听不到对方,则双方将被发送到中继,而不是开始一场只有单方面能玩的比赛。在顺畅打洞的情况下,该检查只需几分之一秒;在恶劣的 NAT 环境下,这就是为什么你现在会多看一会儿连接对话框,而不是看着比赛开始后却纹丝不动。**Join Network Game**(下方的菜单行)仍然是直接的 `IP:port` 加入,保持不变。 #### 匹配器配置 在线行与信令服务器(`services/matchmaker`,一个小型的 Go 二进制文件)通信,该服务器从不模拟游戏——它只负责在对端之间进行介绍,并在它们的 NAT 拒绝直接路径时中继它们的数据包。**已部署公共实例**,因此在线行可以开箱即用。该 URL 的解析顺序为 CLI 标志 → 环境变量 → 编译时默认值: ``` OPEN-BM95 --matchmaker ws://127.0.0.1:8080/ws # 1. CLI flag (e.g. your own server) set BOMBER_MATCHMAKER_URL=ws://127.0.0.1:8080/ws # 2. environment # 3. wss://…fly.dev/ws — the deployed # default (game_app.cpp) ``` UDP **STUN** 回显的解析方式相同(`--matchmaker-stun `、`BOMBER_MATCHMAKER_STUN_HOST` / `BOMBER_MATCHMAKER_STUN_PORT`),并默认指向匹配器 URL 本身所在主机的 **808 端口**。要在本地运行服务器: ``` go build -o mm.exe ./services/matchmaker/cmd/matchmaker && ./mm.exe ``` 当设置了 `BOMBER_MATCHMAKER_URL` 时,`ctest -R net_lobby_live` 会对正在运行的服务器测试整个大厅堆栈(否则跳过)。 **从 CLI**(两个对端,相同的种子 → 完全相同的竞技场): ``` OPEN-BM95 --host 8000 127.0.0.1 8001 --seed 0x1234 OPEN-BM95 --join 8001 127.0.0.1 8000 --seed 0x1234 ``` 对于跨机器游戏,请将 `127.0.0.1` 替换为另一台机器的局域网 IP(端口/种子相同)。对端会在每个 tick 交叉检查 `state_hash`,如果它们的构建版本或配置不同,会大声报告失步。 `abtool`——无头资源检查器/提取器;`bomber_viewer`——SDL3 动画查看器。有关用法,请参见它们的 `--help` / 源码头文件。 ## 测试 `ctest` 运行 doctest 套件:确定性(1 万次 tick 锁步)、游戏规则、移动(忠实的 `sub_41EC84` 移植)、疾病、netcode(环回锁步、回滚、真实 UDP 往返、种子握手),以及锁定模拟行为的黄金哈希。针对原始安装的完整验证:`abtool survey` 和 `bomber_viewer --selftest`。 有一个锁定值得一提,因为很容易做出不同的假设:**`visual_golden` 对渲染帧进行哈希处理,它不是 pre-push 门禁的一部分。** 它需要 `headless` 预设不构建的 SDL `bomber_game` 目标,并且由于缺少原始安装而在 CI 上被跳过——因此它实际上只有在人类针对自己的副本运行时才会真正运行。在此之前,渲染器的更改是未锁定的。 ## Git 钩子 `lefthook.yml` 串联了一个 pre-push 门禁:完整的 `headless` 构建 + `ctest`、覆盖整个 repo 的 `clang-tidy` 检查(配置在 `.clang-tidy` 中)、针对 push 所更改行的 `clang-format` 检查(配置在 `.clang-format` 中),以及函数复杂度检查。每个 clone/worktree 必须启用一次: ``` winget install evilmartians.lefthook # if not already installed lefthook install ``` 可以通过 `bash scripts/test.sh` / `bash scripts/lint.sh` / `bash scripts/format.sh` / `bash scripts/complexity.sh` 手动运行任何检查,或通过 `lefthook run pre-push --force` 运行整个门禁。 其中两个检查特意限定在 push 实际涉及的内容上,原因相同:树中的大部分内容早于这些检查,如果进行全仓库扫描,会将每个真正的更改淹没在机械性的噪音中。 **格式**检查是按行限定的,而不是按文件限定的——只有你编写的行才必须匹配。`scripts/format.sh [base]` 默认指向与 `origin/main` 的合并基点。 **复杂度**检查是一种棘轮机制。它测量 clang-tidy 的*认知*复杂度(它会对嵌套收费,但对于扁平的 `switch` 不收费——对于充满移植调度表的代码库来说,这是正确的倾向),阈值为 25,并与 `scripts/complexity-baseline.txt` 进行比较,后者记录了在添加该检查时已经超标的内容。那些可以保留;没有任何内容可以变得更糟;任何新内容都必须满足该阈值。该列表只会缩小:它最初有 70 个函数(最严重的为 251),现在已减少到 6 个(最严重的为 58)。在重构真正改善了情况后,使用 `scripts/complexity.sh --update` 重新记录——而不是为了清除未通过的门禁。 ## 路线图 自 netcode 核心(ADR-0010、ADR-0011)发布以来已交付的内容:可共享的**大厅代码** 和**公开比赛列表**、**跨 NAT 的互联网游戏**(STUN + UDP 打洞,对于打洞无法到达的对称 NAT 情况提供服务器中继)、通过游戏自身屏幕进行的房主权威 **比赛设置**、在房主中继星型拓扑下**单个大厅最多可容纳十台机器**、轮换回合、确定性的 **对端掉线 → AI 接管**,以及大厅聊天。促成这一切的匹配 服务位于 `services/matchmaker` 中并且已部署;游戏 无需配置即可连接到它。它的控制平面**仅限 `wss://`** (已于 2026-07-26 关闭)——请参见 [`services/matchmaker/SECURITY.md`](services/matchmaker/SECURITY.md) 中的安全审查,该文档 的编写初衷是作为记录而非检查清单。 仍然有待解决: - **中继仅支持双人游戏。** 当打洞失败时,2 座位的比赛 会回退到服务器转发器;更大的比赛则不能,因为 `RelayedTransport` 仅寻址单个目标座位,而星型拓扑需要 扇出以及客机↔客机反射。如果超过 2 个座位的大厅打洞失败,它会说明情况 并返回菜单,而不是半连接状态。 - **房主迁移**——如果房主掉线,比赛将结束,而不是重新选举 新的中心。设计详见 ADR-0011(§ Risks),并且服务器端已构建完成。 - **无法检测比赛中途断开的路径。** 上述相互检查运行在 *tick 0 之前*;过了那个时间点就没有任何内容会重新验证,因此交接窗口的“两军问题”尾部 仍然是悬而未决的。修复方案的形式(客机 keepalive 加上可切换的传输方式)写在 `docs/online-multiplayer-design.md` § 4.1 中,而不是隐含不说。 - **netcode 在测试中从未遇到过真实的 NAT。** `tests/net` 中的所有内容都 通过环回运行;不对称性是*建模出来的*(黑洞地址),而不是由 硬件产生的。这证明了状态机在给定特定 不对称性的情况下能够收敛——但并不能证明真实 NAT 产生的每一种不对称性看起来都是 那样的。实时游戏目前是这一半证据的唯一来源。 **跨平台**是结构性的,而不是奢望:模拟是 纯整数运算,网络传输是小端序的,并且编译时的 `build_hash` 会在 大厅门口进行检查,并在 tick 0 之前再次检查,因此不匹配的构建会 被大声拒绝,而不是导致失步。对该保证的客观限制:该 摘要是固定参考场景的折叠 `state_hash`,因此它只能准确捕获 这些场景执行的行为。如果在未扩展这些场景的情况下添加新机制,就会敞开大门——这 曾经发生过,也是为什么 `build_hash.cpp` 包含测量过程和一个命名的未覆盖类别的原因。所有三个平台都会在 CI 中构建并运行测试 套件——该矩阵包含 Linux (GCC)、macOS (Clang) 和 Windows (MSVC),每个 预设一个作业。*未*声明的是同等的游戏测试:Windows 是唯一一个 每天针对真实安装进行手动演练的平台,因此请将 Linux 和 macOS 视为“能够构建并通过测试”,而不是“已经打磨完善”。请参见 `docs/adr/`、 `docs/re/network-screens.md` 和 `services/matchmaker/PROTOCOL.md`。 ## 法律 Open Bomberman 是一个独立的**净室重新实现**。它**不**附属于、不受认可或与 Interplay Entertainment 或 Konami 有任何关联。 - **不包含任何原始代码或游戏数据。** 本仓库仅包含由贡献者编写的原创源码,这些源码是通过观察数据格式和行为编写的。它不包含任何 Interplay/Konami 代码、音频、关卡数据或任何类型的游戏数据,并且逆向工程工作材料(二进制文件、反汇编、反编译器输出)也从未被提交过(参见 `.gitignore`)。有一点值得一提而不是一带而过:本页面顶部的屏幕截图和动画是*本*引擎运行时的抓图,因此它们确实在屏幕上显示了原作的美术作品。它们是移植版外观的文档——本仓库中的任何内容都不能让你在没有自己游戏副本的情况下进行游戏。 - **你必须拥有原版游戏。** 引擎会在运行时从*你自己的*合法获取的副本中加载 Atomic Bomberman 的数据;它不提供任何此类数据。没有原始安装包,就无法进行游戏。 - **商标。** “Atomic Bomberman”和“Bomberman”以及所有相关名称、徽标、角色和艺术作品均为其各自所有者(Interplay / Konami)的财产。此处仅出于指代目的使用它们,用于描述本软件兼容的内容。 - **许可证。** 本仓库中的原始源码和文档基于 MIT 许可证发布——参见 [`LICENSE`](LICENSE)。该许可证**仅**涵盖贡献者的代码;它不授予对任何第三方名称、商标或资源的任何权利。 如果你是版权持有者并有任何疑虑,请提交 issue。
标签:AI生成代码, Bash脚本, C++20, SDL3, 云资产清单, 回滚网络代码, 日志审计, 游戏开发, 网络多人游戏, 逆向工程