Field ports batch 1: timers, scroll, object frame and map position, party input #35
Loading…
Reference in a new issue
No description provided.
Delete branch "field-batch1"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #32
First field ports: six WRAM-only leaves from the field porting order, game-terms C in
core/game/field/, bridges incore/port/field_bridge.c, US and JP registry entries.Ports
Shadow over the three committed routes (title_to_first_battle_win + field_menu_tour + narshe_to_world_map), matched/calls, 0 mismatch and 0 fallback everywhere (the rest are calls skipped by a frame boundary or nesting):
field/DecTimers($C0:BBCC, JP $C0:BB01)field_count_down_timersfield/DecTimersMenuBattle($C0:BB24, JP $C0:BA59)field_count_down_timers_outside_fieldfield/CalcScrollPos($C0:2BD0)field_scroll_layersfield/GetObjMapPtr($C0:7CE1)field_object_update_map_positionfield/UpdateCtrlFlags($C0:BA81, JP $C0:B9B6)field_publish_party_inputfield/UpdateObjFrame($C0:5839)field_object_update_framePer-route counts:
docs/ports.md. The routes never run a timer, a special animation, a vehicle or stairs, so targeted fuzz from a field savestate (drive server, ports in shadow: random timers, scroll speeds/positions, object animation/vehicle/facing, tile property tables, frame counters, walking/A/menu inputs; plus a stairs session poking the tile properties every frame) covers the rest: with routes + fuzz every instruction of the six originals is executed on both versions, 0 mismatch.Every register and flag is reproduced exactly (no don't-care declarations): the bridges compute the leftovers (X, A, C, V, N/Z) path by path and write the DP scratch only the original writes (
$1A,$1B,$1E, new RAM-map entries). Entry states not supported fall back to the interpreter (D != 0, DB not $00/$7E, 8-bit indexes where the original needs 16-bit, B != 0 where aTAXwould read it, offsets not at an object record, facing > 3).Findings
DecTimersMenuBattlepushes and pulls every register yet has outputs: entered with 8-bit A it leaves B = high byte of D (itsTDC;PLArestores only the low byte), and N/Z come from the finalPLA, not from the saved P. Shadow caught the first (517 menu calls), fuzz the second. Not an original bug (all callers clear or restore A afterwards). Recorded in workdocs/lessons.md §10.GetObjMapPtrruns with DB = $7E during map loading; bridges accept DB $00/$7E for these low-RAM routines.re/known-bugs.tomlnot created).route_ports_on (all ports on,
--semantic)These are timing effects, not port logic, opened as #34 (bug, tools, validation): with one port on at a time on JP title, every port moves the first battle by -7..+13 frames (even
DecTimers, 20 instructions per frame), so the battle seed and the party HP change; andafter:routes replay the previous movie frame by frame under ports on. Decision needed there; I did not weaken the checks.Dropped
CheckTimer: its firing path callsCloseMapTitleWindow, which restores the field HDMA tables (hardware buffer) -> after the video service (#33)._c0714a(object sort): writes through the WRAM port$2180-$2183(hardware register, shared counter to model inhw/).Build / tests
release, debug, clang builds without warnings;
ff6-build.sh debug --fast24/24 passed (before the last bridge fixes; ports are off by default so the fast tests do not exercise them). Slow suite not run.🤖 Generated with Claude Code
Final code (head of field-batch1):
ff6-build.sh debug --fast24/24 passed.Updated from
main(merge commit7877fde: #34 route checks and #36 SRAM start points; the merged tree ismainplus exactly the 19 files of this PR, soroute_narshe_to_figaroand everything of #36 is kept), thendocs/ports.mdrefreshed (c8c6638). Not merged: waiting for the independent readability review.Evidence (release build unless noted)
Shadow, all ports, over the 5 routes (title_to_first_battle_win, title_to_mines_save, field_menu_tour, narshe_to_world_map, narshe_to_figaro; movies replayed, every replay matches its end hashes), totals matched/calls, 0 mismatch, 0 fallback on both versions:
(the rest are calls skipped by a frame boundary or nesting). Per route:
docs/ports.md.Fuzz (100 iterations seed 1 from the end of field_menu_tour, plus the stairs session), 0 mismatch, 0 fallback US and JP; routes + fuzz execute every instruction of the six originals on US and JP (17, 80, 261, 20, 17, 130).
ctest release, all labels (-j2), all field ports on in
route_ports_on: 61/61 passed, 5 483 s wall. route_ports_on US/JP: title_to_first_battle_win ok/ok, title_to_mines_save ok/ok, field_menu_tour ok/ok, narshe_to_world_map ok/ok, narshe_to_figaro ok/ok (new definition: goal reached + timing-invariant checkpoints).debug (gcc ASan/UBSan)
ctest -LE slow: 24/24; clangctest -LE slow: 24/24; 0 warnings in release, debug and clang. Slow tests are validated in release only (ASan emulator ~12x slower).rom.ports (report, passes): with the field ports on, the first divergence is now at sites the census does not cover well: US frame 729,
$0630(write-only field CPU meter, E9, classified by PC as E5 because the site table has no E9 entry), JP frame 318,$00FEat$C2:FFB5inside the multi-frameDecompress_extloop ->FINDING. Opened as #45 (not a port bug: same PC/S at a frame end inside a long main-thread loop does not mean same progress).Readability review, must-fix items applied (
b527aff,c839ef0,c8868cf):field_objects.h(FIELD_SPRITE_*,FIELD_ANIM_*,FIELD_TILE_*,FIELD_WALK_CYCLE_STEPS); the bridge'sOBJ_*copies and its hard-coded* 4Uare gone.field_party_slot(g)(returnsFIELD_OBJECT_COUNTwhen the party pointer is not at a record start, so the ladder test stays exact) is used byfield_object_shown_facingandfield_publish_party_input; record offsets stay in the accessor/bridge layer./* SNES: part of $C0:5839 UpdateObjFrame (field/obj.asm) */(andfield_scroll_speed: part of$C0:2BD0 CalcScrollPos);, JP $C0:xxxxonly when the JP address differs (removed from GetObjMapPtr, UpdateObjFrame, CalcScrollPos). CLAUDE.md rule 4 states both conventions.lda $46,field/obj.asmUpdateObjFrame @5897). Tile property bytes have named indexesFIELD_TILE_PROPS_MOVEMENT(0) andFIELD_TILE_PROPS_LADDER_BYTE(1).field_facing_tenum;field_object_shown_facingreturns it.Also:
field_object_special_anim_tickfolded intofield_object_special_anim_offset;prv_count_downshared by both timer routines; CalcScrollPos sets the tile center only on a crossing; 16-bit wrap comment onfield_scroll_speed; bridge comment reflowed. Left as is:PAD_A_BUTTONandBATTLE_END_*(no shared pad or battle-flags header exists yet).Validation (release build,
c8868cf):portline (calls, match, skipped) identical to the previous run (docs/ports.md numbers unchanged).fuzz.py100 iterations seed 1 + stairs session) US/JP: 0 mismatch, counts identical to the previous run (e.g. UpdateObjFrame 51 240 calls, 50 548/50 553 matched).ctest -LE slow: 24/24 each.