Coverage: routines executed by the interpreter per route, and the porting order it gives #30

Closed
opened 2026-10-29 19:07:40 +00:00 by Gatchan · 3 comments
Owner

Brief §7.1 step 5 and §8.3 ("coverage drives route writing"): interpreter entries are counted per movie; the goal is zero.

  1. Coverage in the core debug API (instruction hook, coexisting with the port registry and the RAM trace): per routine entry (JSR/JSL targets, interrupt vectors, RAM-resident routines), calls and instructions executed; per ROM address executed (bitmap) for line coverage. ff6cli --coverage <file> and a drive-server command.
  2. tools/coverage_report.py: aggregate the coverage of all movies (US and JP), resolve routines with tools/ff6sym.py (module, file, name), report per module (field, event, menu, battle, btlgfx, world, cutscene, sound): routines reached / total, instructions executed; list never-executed routines (input for new routes).
  3. A porting order proposal for the field module (boot, main loop, the per-frame routines called by FieldMain), weighted by calls x instructions and by the subsystem plan of the brief; written to docs/coverage.md (method) and posted here (numbers).
  4. Test: coverage of a short run is deterministic and identical between ff6cli and the drive server.
Brief §7.1 step 5 and §8.3 ("coverage drives route writing"): interpreter entries are counted per movie; the goal is zero. 1. Coverage in the core debug API (instruction hook, coexisting with the port registry and the RAM trace): per routine entry (JSR/JSL targets, interrupt vectors, RAM-resident routines), calls and instructions executed; per ROM address executed (bitmap) for line coverage. `ff6cli --coverage <file>` and a drive-server command. 2. `tools/coverage_report.py`: aggregate the coverage of all movies (US and JP), resolve routines with `tools/ff6sym.py` (module, file, name), report per module (field, event, menu, battle, btlgfx, world, cutscene, sound): routines reached / total, instructions executed; list never-executed routines (input for new routes). 3. A porting order proposal for the field module (boot, main loop, the per-frame routines called by `FieldMain`), weighted by calls x instructions and by the subsystem plan of the brief; written to `docs/coverage.md` (method) and posted here (numbers). 4. Test: coverage of a short run is deterministic and identical between ff6cli and the drive server.
Author
Owner

Starting: branch coverage, delegated to a fresh agent (background).

Starting: branch `coverage`, delegated to a fresh agent (background).
Author
Owner

Coverage of the three committed routes (PR #31), US v1.0 and JP v1.0, from ff6cli --movie ... --coverage and tools/coverage_report.py (method in docs/coverage.md, full reports with never-executed routines in re/coverage/route_library.*.md).

Measured (route library, US v1.0 and JP v1.0)

Coverage files of the committed movies (--movie ... --coverage, release build), from power-on;
every replay still matches its end hashes. Reports: re/coverage/route_library.<version>.md
(coverage_report.py <3 files> --never --field-order 40); the coverage files themselves stay
outside the repository. narshe_to_world_map replays title_to_first_battle_win first, so the
aggregated instruction counts include that route twice (routines reached are unaffected).

Route Routines reached / known US JP Instructions US JP
title_to_first_battle_win 1 208 / 4 167 (29.0%) 1 212 / 4 219 (28.7%) 144.6 M 134.8 M
field_menu_tour 1 548 (37.1%) 1 559 (37.0%) 154.2 M 144.5 M
narshe_to_world_map 1 614 (38.7%) 1 624 (38.5%) 1 095.2 M 1 106.8 M
all three 1 885 (45.2%) 1 903 (45.1%) 1 393.9 M 1 386.1 M

Per module, all three routes:

Module Reached / known US JP Share of instructions US JP
field 252 / 334 (75.4%) 250 / 335 (74.6%) 54.3% 52.3%
event 122 / 207 (58.9%) 122 / 207 (58.9%) 0.7% 0.7%
menu 418 / 1 401 (29.8%) 429 / 1 451 (29.6%) 1.5% 1.5%
battle 297 / 526 (56.5%) 295 / 526 (56.1%) 0.5% 0.5%
btlgfx 632 / 1 203 (52.5%) 646 / 1 207 (53.5%) 39.5% 42.3%
world 48 / 273 (17.6%) 48 / 273 (17.6%) 0.1% 0.1%
cutscene 103 / 206 (50.0%) 100 / 203 (49.3%) 1.4% 0.4%
sound 13 / 17 (76.5%) 13 / 17 (76.5%) 2.1% 2.1%
other (trampolines $1500/$1504) - - 0.01% 0.01%

Shares are dominated by busy waits: WaitVblank alone is 491 M of the field's 757 M (US), the
battle graphics WaitVblank ($C1:1A24) 208 M of btlgfx's 550 M. No shadow stack overflow, nothing dropped.

Proposed field porting order

Top 15 of --field-order (US; JP gives the same routines, DrawLetter at 15 and
DecTimersMenuBattle at 11, addresses shifted after $C0:7xxx):

# Routine Address US Role Instructions Activations
1 InitMaskHDMAData C0:3A28 map load, interrupt 15.7 M 68
2 TfrObjGfxSub C0:677B map load, interrupt 11.9 M 52 896
3 UpdateObjFrame C0:5839 map load, main loop 10.3 M 403 033
4 MoveObjs C0:573C map load, main loop 30.7 M 51 714
5 _c0714a (sort objects) C0:714A map load, main loop 9.7 M 13 062
6 UpdateScrollHDMA C0:42B6 map load, interrupt 8.3 M 52 692
7 DrawOverlaySprites C0:CC95 main loop 6.6 M 51 696
8 CalcScrollPos C0:2BD0 main loop 4.4 M 51 696
9 CheckTimer C0:BBF1 main loop 3.0 M 51 761
10 UpdateFixedColor C0:1053 interrupt 2.7 M 52 624
11 DrawLetter C0:88D3 main loop 2.4 M 6 679
12 TfrBGAnimGfx C0:903F interrupt 2.3 M 26 277
13 DecTimersMenuBattle C0:BB24 (menu, battle) 2.2 M 41 426
14 _c0249a C0:249A map load, main loop 1.7 M 2 109
15 SetTopSpriteMSB C0:6721 main loop 1.6 M 99 339

All are leaves (no field callee; MoveObjs calls UpdateObjFrame, ported just before) that
return within the frame. Reading of the full order (374 routines US, 372 JP):

  1. Per-frame leaves first: the object, scroll, HDMA, palette and DMA routines
    of the FieldMain loop and FieldNMI/FieldIRQ, then the text renderer; each runs ~50 000
    times on the routes, so shadow validation gets many calls quickly.
  2. Map-load leaves (LoadMapPal, InitNPCMap, InitBG*VScroll...) and the event commands
    that do not wait, interleaved by weight; the callers (DrawObjSprites 71.7 M, UpdateObjActions,
    FieldNMI with its 20 callees) follow their callees.
  3. Frame-crossing routines last (the last ~35): WaitVblank, the map loaders that run for
    several frames with the screen off (Decompress, LoadMapTiles, InitMapTiles,
    InitObjGfx, LoadMap), the event commands that wait (EventCmd_f0 fades, title
    EventCmd_a9/aa/ab), ExecBattle, OpenMenu, then the interpreter loop (ExecEvent,
    ContinueEvent) and Reset/FieldMain. "Crosses frames" means either a wait or more than one
    frame of CPU time; both need the frame model (§5.1) before they can run as C in mode on.

This order is by measured weight; the subsystem plan (reuse audit: boot and map load first) reads
off the Role column: boot and map-load leaves are early enough to start with, but their callers
(LoadMap) cross frames and come last.

Coverage of the three committed routes (PR #31), US v1.0 and JP v1.0, from `ff6cli --movie ... --coverage` and `tools/coverage_report.py` (method in `docs/coverage.md`, full reports with never-executed routines in `re/coverage/route_library.*.md`). ### Measured (route library, US v1.0 and JP v1.0) Coverage files of the committed movies (`--movie ... --coverage`, release build), from power-on; every replay still matches its end hashes. Reports: `re/coverage/route_library.<version>.md` (`coverage_report.py <3 files> --never --field-order 40`); the coverage files themselves stay outside the repository. `narshe_to_world_map` replays `title_to_first_battle_win` first, so the aggregated instruction counts include that route twice (routines reached are unaffected). | Route | Routines reached / known US | JP | Instructions US | JP | |---|---|---|---|---| | `title_to_first_battle_win` | 1 208 / 4 167 (29.0%) | 1 212 / 4 219 (28.7%) | 144.6 M | 134.8 M | | `field_menu_tour` | 1 548 (37.1%) | 1 559 (37.0%) | 154.2 M | 144.5 M | | `narshe_to_world_map` | 1 614 (38.7%) | 1 624 (38.5%) | 1 095.2 M | 1 106.8 M | | all three | 1 885 (45.2%) | 1 903 (45.1%) | 1 393.9 M | 1 386.1 M | Per module, all three routes: | Module | Reached / known US | JP | Share of instructions US | JP | |---|---|---|---|---| | field | 252 / 334 (75.4%) | 250 / 335 (74.6%) | 54.3% | 52.3% | | event | 122 / 207 (58.9%) | 122 / 207 (58.9%) | 0.7% | 0.7% | | menu | 418 / 1 401 (29.8%) | 429 / 1 451 (29.6%) | 1.5% | 1.5% | | battle | 297 / 526 (56.5%) | 295 / 526 (56.1%) | 0.5% | 0.5% | | btlgfx | 632 / 1 203 (52.5%) | 646 / 1 207 (53.5%) | 39.5% | 42.3% | | world | 48 / 273 (17.6%) | 48 / 273 (17.6%) | 0.1% | 0.1% | | cutscene | 103 / 206 (50.0%) | 100 / 203 (49.3%) | 1.4% | 0.4% | | sound | 13 / 17 (76.5%) | 13 / 17 (76.5%) | 2.1% | 2.1% | | other (trampolines `$1500/$1504`) | - | - | 0.01% | 0.01% | Shares are dominated by busy waits: `WaitVblank` alone is 491 M of the field's 757 M (US), the battle graphics `WaitVblank` (`$C1:1A24`) 208 M of btlgfx's 550 M. No shadow stack overflow, nothing dropped. ### Proposed field porting order Top 15 of `--field-order` (US; JP gives the same routines, `DrawLetter` at 15 and `DecTimersMenuBattle` at 11, addresses shifted after `$C0:7xxx`): | # | Routine | Address US | Role | Instructions | Activations | |---|---|---|---|---|---| | 1 | InitMaskHDMAData | `C0:3A28` | map load, interrupt | 15.7 M | 68 | | 2 | TfrObjGfxSub | `C0:677B` | map load, interrupt | 11.9 M | 52 896 | | 3 | UpdateObjFrame | `C0:5839` | map load, main loop | 10.3 M | 403 033 | | 4 | MoveObjs | `C0:573C` | map load, main loop | 30.7 M | 51 714 | | 5 | _c0714a (sort objects) | `C0:714A` | map load, main loop | 9.7 M | 13 062 | | 6 | UpdateScrollHDMA | `C0:42B6` | map load, interrupt | 8.3 M | 52 692 | | 7 | DrawOverlaySprites | `C0:CC95` | main loop | 6.6 M | 51 696 | | 8 | CalcScrollPos | `C0:2BD0` | main loop | 4.4 M | 51 696 | | 9 | CheckTimer | `C0:BBF1` | main loop | 3.0 M | 51 761 | | 10 | UpdateFixedColor | `C0:1053` | interrupt | 2.7 M | 52 624 | | 11 | DrawLetter | `C0:88D3` | main loop | 2.4 M | 6 679 | | 12 | TfrBGAnimGfx | `C0:903F` | interrupt | 2.3 M | 26 277 | | 13 | DecTimersMenuBattle | `C0:BB24` | (menu, battle) | 2.2 M | 41 426 | | 14 | _c0249a | `C0:249A` | map load, main loop | 1.7 M | 2 109 | | 15 | SetTopSpriteMSB | `C0:6721` | main loop | 1.6 M | 99 339 | All are leaves (no field callee; `MoveObjs` calls `UpdateObjFrame`, ported just before) that return within the frame. Reading of the full order (374 routines US, 372 JP): 1. **Per-frame leaves first**: the object, scroll, HDMA, palette and DMA routines of the `FieldMain` loop and `FieldNMI`/`FieldIRQ`, then the text renderer; each runs ~50 000 times on the routes, so shadow validation gets many calls quickly. 2. **Map-load leaves** (`LoadMapPal`, `InitNPCMap`, `InitBG*VScroll`...) and the event commands that do not wait, interleaved by weight; the callers (`DrawObjSprites` 71.7 M, `UpdateObjActions`, `FieldNMI` with its 20 callees) follow their callees. 3. **Frame-crossing routines last** (the last ~35): `WaitVblank`, the map loaders that run for several frames with the screen off (`Decompress`, `LoadMapTiles`, `InitMapTiles`, `InitObjGfx`, `LoadMap`), the event commands that wait (`EventCmd_f0` fades, title `EventCmd_a9/aa/ab`), `ExecBattle`, `OpenMenu`, then the interpreter loop (`ExecEvent`, `ContinueEvent`) and `Reset`/`FieldMain`. "Crosses frames" means either a wait or more than one frame of CPU time; both need the frame model (§5.1) before they can run as C in mode `on`. This order is by measured weight; the subsystem plan (reuse audit: boot and map load first) reads off the Role column: boot and map-load leaves are early enough to start with, but their callers (`LoadMap`) cross frames and come last.
Author
Owner

Merged (PR #31). Main-session check: release --fast 24/24 incl. rom.coverage.* (identical coverage across runs and between ff6cli and the drive server, hashes unchanged). Next: a field porting milestone driven by the order above.

Merged (PR #31). Main-session check: release `--fast` 24/24 incl. `rom.coverage.*` (identical coverage across runs and between ff6cli and the drive server, hashes unchanged). Next: a field porting milestone driven by the order above.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Gatchan/FF6Portage#30
No description provided.