KOEI's 6th NES strategy title and the next target after the five decompiled (ro3k / na1 / gk / na2 / rot3k2). Same historical-sim playstyle as the corpus, set in the Water Margin (Liangshan bandits) era — the draw is the unfamiliar setting on a familiar engine. It is the tightest NA2 sibling yet: byte-for-byte the same ROM shape as NA2 (MMC5, 256K PRG, 128K CHR-ROM, 393232 B) and the same
$FFF8private-vector syscall. Thin game repo over the sharedkoei-nesengine library (sibling clone); imports it via../koei-nes.
RE COMPLETE (2026-07-01). All 20 bytecode banks named (988/988 subs, 0 anon), the native fixed-region
floor, the data walk (RAM globals + ROM tables + ~1865 strings), and the one in-bank native routine (the
bank-28 sound engine) — ~3356 symbols, 0 placeholder names. The bytecode VM transfers from NA2 with
zero koei-nes code changes — same machine as na1/ro3k/gk/na2/rot3k2, only the addresses moved; the
family-invariant dispatcher = vm_entry + 0x44 offset holds again. Every constant below was verified
against the ROM (vectors, frame-pull prologue, dispatcher handler-table loads, syscall/ext-op dispatch),
not assumed from NA2. The 8-chapter walkthrough is below; method in walk-method.
| const | value | note |
|---|---|---|
| mapper / CHR | MMC5 / 128K CHR-ROM | == NA2 (rot3k2 was 256K); ROM file = 393232 B, NES 2.0, battery |
| RESET / NMI / IRQ-BRK | $F500 / $F641 / $FFD6 (bare RTI) |
RESET == NA2/rot3k2; NMI byte-identical to NA2; HW IRQ ignored |
| OS entry | jmp $E000 → $E0E6 |
OS main is itself bytecode (jsr vm_entry + inline operands) |
vm_entry / dispatcher |
$E42A / $E46E (= entry+0x44) |
family-invariant offset holds; frame-pull fingerprint verified |
handler_lo / handler_hi |
$EC3F / $ED3F |
read out of the dispatcher body (LDA $EC3F,X / LDA $ED3F,X) |
| syscall | fakebrk_priv via $FFF8 → $F8ED |
NA2's mechanism (rot3k2 moved it to $FFDE); table $F917, invoker $EE3F/$EE52 |
ext-op ($B7) |
dispatch $EE71, table $EE8C |
32-bit-math postfix sub-machine |
| bytecode banks | 8K windows; 17 carry the 20 2a e4 stub |
$8000-lib {0,1,9,12,14,26}, $A000-app {2,3,4,5,6,7,8,10,11}, banks 16/31 → fixed-OS (window resolves in walk) |
| fixed region | banks 30/31 ($C000/$E000) |
fixed_section = prg.bank31, banks_at_c000 = [30] |
Engine constants live in the [bk] block of koei-nes/tools/games.toml; the symbol table (the RE
"linker output") is mesen-labels.toml (seeded with the VM anchors; filled by the walk). Render a
bank's bytecode VM-asm with the shared toolchain:
cd ../koei-nes/tools && py -3 koei_vm.py disasm bk <bank>
- 00 — First-contact recon: header, MMC5 classification, vectors, VM fingerprint, syscall/ext-op, bank map. ✅
- 01 — Boot & dispatch: the OS mode loop + utility library (bank 31). ✅
- 02 — Control flow: the runtime spine — mode loop, annual cycle,
pending_eventsignaling. ✅ - 03 — Player commands: dispatch → the command groups (banks 3/4/5/6/7) → stat-effect + pickers. ✅
- 04 — AI architecture: strategic (8) / economic (14) / tactical (9,11) AI layers. ✅
- 05 — Events & lifecycle:
advance_month, census/migration, aging/defection, calamities, victory. ✅ - 06 — Combat overview: strategic siege vs tactical grid; trigger, roster, aftermath (12/13). ✅
- 07 — Tactical engine: the 14×12 grid battle deep-dive (banks 9/10/11). ✅
- 08 — Sound: the native music engine (bank 28) — the one in-bank native routine. ✅
Reference: MEMORY_MAP · schema (record fields) · data-map (RAM globals + ROM tables) · bank-map (call graph + walk order) · walk-method (how it was done).
The rendered decompilation is committed under source/: source/c/bankNN.c (the fully-labeled
decompiled C — the readable deliverable) and source/asm/bankNN.asm (the VM-bytecode disassembly) for
all 20 bytecode banks, plus source/asm/bank28.asm (the native 6502 sound engine). Regenerate with the
shared toolchain (koei_vm.py disasm/decompile) against mesen-labels.toml.
Cross-game record schemas: koei-nes/tools/RECORD_SCHEMAS.md. The ROM is not committed
(copyright); it lives at bk-decompiler/<rom> per the local-paths resolver.