From 59be3b14a51571ec36e651625cf92ca5f9925b2a Mon Sep 17 00:00:00 2001 From: Emmanuel Peralta Date: Fri, 11 Sep 2026 09:38:39 +0200 Subject: [PATCH 1/7] Share the item layout as a module instead of a header Structs do not reach a module through `.include`: the declaration registers in the including translation unit, and a module compiled by build_with_imports is not that unit. Proved with a three-line probe - `.include` of the header fails the cast, `.import` of a module that declares it succeeds. dq6 has been doing the latter for a while; its config.s shares pools, structs and constants with every module that imports it. So items.i becomes item_layout.s, imported by its five consumers. That unblocks the rest of the module migration, where each module has to carry the headers it uses rather than inheriting them from ff4.s. Named item_layout rather than items because `.import "items"` resolves against the module paths and finds src/ingame/items.s, the patch file, which then fails to link against labels it never imported. Output-neutral: the IPS differs only in the BUILD_DATE stamp, which moves between any two builds of identical source. --- ff4.s | 2 +- src/battle/inventory_rolling.s | 2 +- src/ingame/items_menu_vwf.s | 2 +- src/{items.i => item_layout.s} | 0 src/small_vwf/item_description.s | 2 +- src/small_vwf/render.s | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename src/{items.i => item_layout.s} (100%) diff --git a/ff4.s b/ff4.s index 6679e62..88283c8 100644 --- a/ff4.s +++ b/ff4.s @@ -45,7 +45,7 @@ Final Fantasy IV the new hack. .include "src/libmz.i" -.include "src/items.i" +.import "item_layout" .include "src/lib/rolling_buffer.s" .include "src/menus/system_menus_text.i" .include "src/minimal_vwf_patches.s" diff --git a/src/battle/inventory_rolling.s b/src/battle/inventory_rolling.s index a15ec16..b71c7a3 100644 --- a/src/battle/inventory_rolling.s +++ b/src/battle/inventory_rolling.s @@ -4,7 +4,7 @@ Battle inventory rolling-buffer engine (single column, 5 visible rows + 1 prefet runs the field-menu NMI DMA check. """ .include "config.i" -.include "../items.i" +.import "item_layout" .extern assets_items_dat .extern assets_items_unleashed_dat .extern mult8_trampoline diff --git a/src/ingame/items_menu_vwf.s b/src/ingame/items_menu_vwf.s index e19d0bd..7a06204 100644 --- a/src/ingame/items_menu_vwf.s +++ b/src/ingame/items_menu_vwf.s @@ -41,7 +41,7 @@ Status: """ -.include "src/items.i" +.import "item_layout" .include "../bank20.i" .include "src/battle/inventory_budget.i" diff --git a/src/items.i b/src/item_layout.s similarity index 100% rename from src/items.i rename to src/item_layout.s diff --git a/src/small_vwf/item_description.s b/src/small_vwf/item_description.s index 7eabfce..f8ed70d 100644 --- a/src/small_vwf/item_description.s +++ b/src/small_vwf/item_description.s @@ -1,6 +1,6 @@ """Small-VWF item-description renderer.""" .include "src/vwf_state.i" -.include "src/items.i" +.import "item_layout" .scope items_description { """Small-VWF item-description renderer entry-points.""" diff --git a/src/small_vwf/render.s b/src/small_vwf/render.s index 2cea3bb..44a143f 100644 --- a/src/small_vwf/render.s +++ b/src/small_vwf/render.s @@ -15,7 +15,7 @@ vwf_state.i). .include "config.i" -.include "src/items.i" +.import "item_layout" .include "src/vwf_state.i" VARS_BUFFER = 0x710000 From 391df5cc8b282cc5bf40b6a995610307dff8fdce Mon Sep 17 00:00:00 2001 From: Emmanuel Peralta Date: Fri, 11 Sep 2026 10:03:30 +0200 Subject: [PATCH 2/7] Turn the rolling menus into modules The four rolling-menu surfaces - treasure, drops, key-item picker and the shop sell list - become imported modules rather than files ff4.s inlines. Each now names what it uses: `.import "item_layout"` for the struct, `.extern` for the trampolines it borrows from its neighbours. Two things do not travel through `.import`, and both cost a build to find: `.map` is per translation unit. A module that reserves from the SRAM pool or allocs into bank $20 needs those banks described where it can see them, so the maps now sit beside the pools they describe, in rolling_state.i and bank20.i. The linker dedupes identical decls. Constants shared by inlining had the same problem: INVENTORY_SCROLL_PIXELS_PER_FRAME lived in a .s that ff4.s included, so rolling_buffer.s becomes rolling_buffer.i and the modules that want the constant include it. The S001 suppressions come out with this: the struct arrives by import, so the lint resolves it the way the assembler does. --- ff4.s | 10 ++++----- src/bank20.i | 6 ++++++ src/ingame/drops_rolling.s | 19 ++++++++++++----- src/ingame/key_item_picker.s | 18 +++++++++++----- src/ingame/shop_sell_rolling.s | 18 +++++++++++----- src/ingame/treasure_rolling.s | 21 ++++++++++++++----- .../{rolling_buffer.s => rolling_buffer.i} | 0 src/rolling_state.i | 6 ++++++ 8 files changed, 73 insertions(+), 25 deletions(-) rename src/lib/{rolling_buffer.s => rolling_buffer.i} (100%) diff --git a/ff4.s b/ff4.s index 88283c8..c42a56c 100644 --- a/ff4.s +++ b/ff4.s @@ -46,7 +46,7 @@ Final Fantasy IV the new hack. .include "src/libmz.i" .import "item_layout" -.include "src/lib/rolling_buffer.s" +.include "src/lib/rolling_buffer.i" .include "src/menus/system_menus_text.i" .include "src/minimal_vwf_patches.s" .if BATTLE_ENABLED { @@ -370,10 +370,10 @@ signature byte sits at PB:(PC - 1). } .if TREASURE_INVENTORY_ROLLING { - .include "src/ingame/treasure_rolling.s" - .include "src/ingame/drops_rolling.s" - .include "src/ingame/key_item_picker.s" - .include "src/ingame/shop_sell_rolling.s" + .import "ingame/key_item_picker" + .import "ingame/drops_rolling" + .import "ingame/treasure_rolling" + .import "ingame/shop_sell_rolling" } ; --- Binary text assets ------------------------------------------------- diff --git a/src/bank20.i b/src/bank20.i index 7ad5774..ae4e819 100644 --- a/src/bank20.i +++ b/src/bank20.i @@ -8,6 +8,12 @@ linker dedupes identical ranges via `_merge_one_pool_decl`. """ +; The ROM map travels with the pool, for the same reason: `.map` is per +; translation unit, an imported module does not inherit the +; patch-main's, and a pool in bank $20 needs that bank described where +; the module can see it. Identical decls are deduped by the linker. +.map identifier=1 bank_range=0x00, 0x6f addr_range=0x8000, 0xffff mask=0x8000 mirror_bank_range=0x80, 0xcf + .pool bank20_reloc { range 0x208000 0x20FFFF strategy order diff --git a/src/ingame/drops_rolling.s b/src/ingame/drops_rolling.s index 1c01362..eeb1eca 100644 --- a/src/ingame/drops_rolling.s +++ b/src/ingame/drops_rolling.s @@ -40,6 +40,18 @@ State RAM layout (12 bytes from $1BE0, struct: RollingBufferState): """ +.import "item_layout" + +; Labels borrowed from neighbouring modules. As an include these resolved +; because ff4.s composed one translation unit; a module names what it uses. +.extern check_can_use_item_trampoline +.extern draw_item_slot_inner_trampoline +.extern draw_window_trampoline +.extern drops_select_bg4_trampoline +.extern treasure_drops_window +.extern rolling_engine + + DROPS_VISIBLE_ITEMS := 5 DROPS_BUFFER_SLOTS := 6 DROPS_TOTAL_ITEMS := 8 @@ -51,11 +63,7 @@ DROPS_SCROLL_TOTAL_PIXELS := 16 ; code writes to bytes past $1BEB) to clean $7E:9C30. Engine path needs ; the full 35-byte struct ; the macro path only ever touched the first ; 12 bytes so the original $1BE0 base worked there. -; `RollingBufferState` is declared in items.i, which ff4.s includes -; ahead of this module - the assembler resolves the cast, the lint -; sees one file at a time and cannot. Codes are comma-separated, so -; the reason has to sit here rather than after the marker. -drops_rolling := (0x7E9C30 as RollingBufferState) ; noqa: S001 +drops_rolling := (0x7E9C30 as RollingBufferState) ; Drops scroll position lives one byte past the state block so it ; doesn't collide with the engine's RollingBufferState fields. Other @@ -95,6 +103,7 @@ DROPS_SCROLL_STATE_SCROLLING := 1 .include "../bank20.i" + .alloc drops_rolling_block in bank20_reloc { drops_ensure_hdma_initialized: """Lazy init: pin BG4VOFS shadow to 0 + configure ch4 driving BG4VOFS on first scroll.""" diff --git a/src/ingame/key_item_picker.s b/src/ingame/key_item_picker.s index 899c136..6100ad2 100644 --- a/src/ingame/key_item_picker.s +++ b/src/ingame/key_item_picker.s @@ -45,6 +45,17 @@ State RAM layout (12 bytes from $1BF0, struct: RollingBufferState): """ +.import "item_layout" + +; Labels borrowed from neighbouring modules. As an include these resolved +; because ff4.s composed one translation unit; a module names what it uses. +.extern check_can_use_item_trampoline +.extern draw_item_slot_inner_trampoline +.extern wait_for_vblank_long +.extern rolling_engine +.extern render + + ; Four rows on screen, matching the window vanilla draws ; the engine ; adds the prefetch slot itself, and it stays inside the staging page ; without being pushed. @@ -59,11 +70,7 @@ KEY_ITEM_SCROLL_TOTAL_PIXELS := 16 ; the same reason as treasure ($9C00) + drops ($9C30) : engine path ; needs 35 bytes per instance, $1B00-$1BFF is too small and vanilla ; sprite code stomps past $1BEB. -; `RollingBufferState` is declared in items.i, which ff4.s includes -; ahead of this module - the assembler resolves the cast, the lint -; sees one file at a time and cannot. Codes are comma-separated, so -; the reason has to sit here rather than after the marker. -key_item_rolling := (0x7E9C60 as RollingBufferState) ; noqa: S001 +key_item_rolling := (0x7E9C60 as RollingBufferState) KEY_ITEM_SLIDE_OPEN_DONE := 0x08 @@ -128,6 +135,7 @@ KEY_ITEM_HDMA4_SRC_LO := 0x4342 KEY_ITEM_HDMA4_SRC_BANK := 0x4344 .include "src/rolling_state.i" + .include "../bank20.i" .alloc key_item_picker_block in bank20_reloc { diff --git a/src/ingame/shop_sell_rolling.s b/src/ingame/shop_sell_rolling.s index 38a14fa..e09f4e8 100644 --- a/src/ingame/shop_sell_rolling.s +++ b/src/ingame/shop_sell_rolling.s @@ -26,6 +26,18 @@ the field menu. .include "../bank20.i" .include "config.i" +.import "item_layout" + +; Labels this module borrows from its neighbours. As an include these +; resolved because ff4.s composed one translation unit; a module has to +; name what it uses. +.extern check_can_use_item_trampoline +.extern draw_item_slot_inner_trampoline +.extern draw_window_trampoline +.extern tfr_bg3_tiles_vblank_trampoline +.extern sell_select_bg3_trampoline +.extern wait_for_vblank_long +.extern rolling_engine SELL_VISIBLE_ITEMS := 8 SELL_BUFFER_SLOTS := 9 @@ -33,11 +45,7 @@ SELL_TOTAL_ITEMS := 48 ; State block in the shared $7E:99xx arena, past drops ($9C30) and the ; key-item picker ($9C60); the field profile sits at $9C90. -; `RollingBufferState` is declared in items.i, which ff4.s includes -; ahead of this module - the assembler resolves the cast, the lint -; sees one file at a time and cannot. Codes are comma-separated, so -; the reason has to sit here rather than after the marker. -sell_rolling := (0x7E9CC0 as RollingBufferState) ; noqa: S001 +sell_rolling := (0x7E9CC0 as RollingBufferState) ; Vanilla's own sell scroll position ($1B96, "first visible row") and ; cursor row ($1B94). The profile reads them rather than keeping its diff --git a/src/ingame/treasure_rolling.s b/src/ingame/treasure_rolling.s index e647058..5c243ae 100644 --- a/src/ingame/treasure_rolling.s +++ b/src/ingame/treasure_rolling.s @@ -2,6 +2,20 @@ Treasure inventory rolling-buffer engine (single column, 5 visible, 6 buffer slots, scroll limit 43) ; cloned from `inventory_rolling.s` and tuned for the chest UI. """ + +.import "item_layout" +.include "src/lib/rolling_buffer.i" + +; Labels borrowed from neighbouring modules. As an include these resolved +; because ff4.s composed one translation unit; a module names what it uses. +.extern check_can_use_item_trampoline +.extern draw_item_slot_inner_trampoline +.extern draw_window_trampoline +.extern reset_sprites_trampoline +.extern treasure_inventory_window +.extern rolling_engine +.extern tfr_bg3_tiles_vblank_trampoline + ; Treasure inventory rolling buffer (single-column, 5 visible). ; @@ -16,6 +30,7 @@ from `inventory_rolling.s` and tuned for the chest UI. ; Layout (single column) .include "config.i" + TREASURE_VISIBLE_ITEMS := 5 ; Visible items at once TREASURE_BUFFER_SLOTS := 6 ; 6 slots (5 visible + 1 pre-render) TREASURE_TOTAL_ITEMS := 48 ; Total inventory items @@ -46,11 +61,7 @@ TREASURE_ITEM_LIST_HEIGHT := 80 ; 5 items × 16 pixels ; region. Engine path needs the full 35-byte struct (state + config + ; hook far-ptrs) ; the macro path only ever touched the first 12 bytes ; so the original $1BD0 base worked despite vanilla's later collisions. -; `RollingBufferState` is declared in items.i, which ff4.s includes -; ahead of this module - the assembler resolves the cast, the lint -; sees one file at a time and cannot. Codes are comma-separated, so -; the reason has to sit here rather than after the marker. -treasure_rolling := (0x7E9C00 as RollingBufferState) ; noqa: S001 +treasure_rolling := (0x7E9C00 as RollingBufferState) TREASURE_SCROLL_COOLDOWN_FRAMES := 0x0C ; 12 frames between scrolls while DOWN/UP is held diff --git a/src/lib/rolling_buffer.s b/src/lib/rolling_buffer.i similarity index 100% rename from src/lib/rolling_buffer.s rename to src/lib/rolling_buffer.i diff --git a/src/rolling_state.i b/src/rolling_state.i index 1383b5f..bb8daae 100644 --- a/src/rolling_state.i +++ b/src/rolling_state.i @@ -6,6 +6,12 @@ bodies, and placement directives cannot nest. """ +; The cart SRAM map travels with the pool. `.map` is per translation +; unit and an imported module does not inherit the patch-main's, so a +; module that reserves from this pool needs bank $70 described here or +; the reservation has no region to sit in. +.map identifier=3 bank_range=0x70, 0x70 addr_range=0x0000, 0x7fff mask=0x8000 writable=1 + ; --- Rolling-menu state pool ------------------------------------------- ; ; Hand-picking scratch addresses has cost us repeatedly: bank-$00 From 5926ab3287911c97c9ade8b85f6ca2041848b613 Mon Sep 17 00:00:00 2001 From: Emmanuel Peralta Date: Fri, 11 Sep 2026 10:10:34 +0200 Subject: [PATCH 3/7] Turn the rolling engine and field inventory into modules The engine dispatches into each profile's HDMA builder and the profiles call back into the engine, so the two sides import each other's names and the linker ties them together. Also pins down why the shared header had to be renamed: a module importing a bare name resolves it against its own directory first, so `.import "items"` from src/ingame picked up src/ingame/items.s - the ROM patch file, which declares no struct - and compiled it as the module. The failure surfaced far away, as an unknown struct type. --- ff4.s | 4 ++-- src/ingame/inventory_rolling.s | 12 ++++++++++++ src/lib/rolling_inventory_engine.s | 18 ++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/ff4.s b/ff4.s index c42a56c..78a31b6 100644 --- a/ff4.s +++ b/ff4.s @@ -365,13 +365,13 @@ signature byte sits at PB:(PC - 1). .if INVENTORY_ROLLING_BUFFER { .import "ingame/init_bg_scroll_hdma" - .include "src/ingame/inventory_rolling.s" - .include "src/lib/rolling_inventory_engine.s" } .if TREASURE_INVENTORY_ROLLING { .import "ingame/key_item_picker" .import "ingame/drops_rolling" + .import "ingame/inventory_rolling" + .import "lib/rolling_inventory_engine" .import "ingame/treasure_rolling" .import "ingame/shop_sell_rolling" } diff --git a/src/ingame/inventory_rolling.s b/src/ingame/inventory_rolling.s index 79502cd..26928f9 100644 --- a/src/ingame/inventory_rolling.s +++ b/src/ingame/inventory_rolling.s @@ -2,6 +2,18 @@ Field-menu inventory rolling-buffer engine (single column, 5 visible rows + 1 prefetch): adapts the battle approach for the main menu items list with HDMA-based circular scrolling. """ + +.import "item_layout" +.include "src/lib/rolling_buffer.i" + +; Borrowed from neighbouring modules; inlining used to supply them. +.extern check_can_use_item_trampoline +.extern draw_item_slot_inner_trampoline +.extern draw_window_trampoline +.extern reset_sprites_trampoline +.extern draw_trash_single_column +.extern rolling_engine + ; Rolling Buffer Implementation for Main Menu Inventory (Single Column) ; ; HDMA-based circular buffer scrolling for single-column menu inventory. diff --git a/src/lib/rolling_inventory_engine.s b/src/lib/rolling_inventory_engine.s index 0243dca..e2c234b 100644 --- a/src/lib/rolling_inventory_engine.s +++ b/src/lib/rolling_inventory_engine.s @@ -17,6 +17,24 @@ sites that remain in the per-menu source files. """ +.import "item_layout" + +; The profiles call into the engine and the engine dispatches back into +; their HDMA builders, so the two sides are mutually dependent. That is +; fine across modules: each compiles against the names it declares here +; and the linker resolves them. +.extern update_menu_scroll_hdma +.extern update_treasure_scroll_hdma +.extern update_drops_scroll_hdma +.extern update_key_item_scroll_hdma +.extern update_sell_scroll_hdma +.extern clear_inventory_slot +.extern draw_item_cursors_trampoline +.extern tfr_bg2_tiles_vblank_trampoline +.extern tfr_sprites_vblank_trampoline +.extern update_ctrl_after_scroll_trampoline + + .include "src/rolling_state.i" .include "../bank20.i" From f62c4ad1d5258ed126a5ab3a5bfa8ccc560d30e6 Mon Sep 17 00:00:00 2001 From: Emmanuel Peralta Date: Fri, 11 Sep 2026 10:56:14 +0200 Subject: [PATCH 4/7] Name the shared layout module items again a816 1.1.0a33 resolves `.import` against the configured module paths at both sites that do the resolving, so a file no longer shadows a project-wide module with a same-named neighbour. The header can carry the name that belongs to it; `src/ingame/items.s`, the ROM patch file, is addressed as `ingame/items`. --- ff4.s | 2 +- pyproject.toml | 2 +- src/battle/inventory_rolling.s | 2 +- src/ingame/drops_rolling.s | 2 +- src/ingame/inventory_rolling.s | 2 +- src/ingame/items_menu_vwf.s | 2 +- src/ingame/key_item_picker.s | 2 +- src/ingame/shop_sell_rolling.s | 2 +- src/ingame/treasure_rolling.s | 2 +- src/{item_layout.s => items.s} | 0 src/lib/rolling_inventory_engine.s | 2 +- src/small_vwf/item_description.s | 2 +- src/small_vwf/render.s | 2 +- 13 files changed, 12 insertions(+), 12 deletions(-) rename src/{item_layout.s => items.s} (100%) diff --git a/ff4.s b/ff4.s index 78a31b6..7746825 100644 --- a/ff4.s +++ b/ff4.s @@ -45,7 +45,7 @@ Final Fantasy IV the new hack. .include "src/libmz.i" -.import "item_layout" +.import "items" .include "src/lib/rolling_buffer.i" .include "src/menus/system_menus_text.i" .include "src/minimal_vwf_patches.s" diff --git a/pyproject.toml b/pyproject.toml index e5363e5..1364494 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ authors = [{ name = "Emmanuel Peralta", email = "manz@ringum.net" }] requires-python = ">=3.13" dependencies = [ - "a816==1.1.0a31", + "a816==1.1.0a33", "kintsuki[visual]==0.0.0a13", ] diff --git a/src/battle/inventory_rolling.s b/src/battle/inventory_rolling.s index b71c7a3..f57258c 100644 --- a/src/battle/inventory_rolling.s +++ b/src/battle/inventory_rolling.s @@ -4,7 +4,7 @@ Battle inventory rolling-buffer engine (single column, 5 visible rows + 1 prefet runs the field-menu NMI DMA check. """ .include "config.i" -.import "item_layout" +.import "items" .extern assets_items_dat .extern assets_items_unleashed_dat .extern mult8_trampoline diff --git a/src/ingame/drops_rolling.s b/src/ingame/drops_rolling.s index eeb1eca..1289141 100644 --- a/src/ingame/drops_rolling.s +++ b/src/ingame/drops_rolling.s @@ -40,7 +40,7 @@ State RAM layout (12 bytes from $1BE0, struct: RollingBufferState): """ -.import "item_layout" +.import "items" ; Labels borrowed from neighbouring modules. As an include these resolved ; because ff4.s composed one translation unit; a module names what it uses. diff --git a/src/ingame/inventory_rolling.s b/src/ingame/inventory_rolling.s index 26928f9..c97f5b7 100644 --- a/src/ingame/inventory_rolling.s +++ b/src/ingame/inventory_rolling.s @@ -3,7 +3,7 @@ Field-menu inventory rolling-buffer engine (single column, 5 visible rows + 1 pr approach for the main menu items list with HDMA-based circular scrolling. """ -.import "item_layout" +.import "items" .include "src/lib/rolling_buffer.i" ; Borrowed from neighbouring modules; inlining used to supply them. diff --git a/src/ingame/items_menu_vwf.s b/src/ingame/items_menu_vwf.s index 7a06204..5689fd5 100644 --- a/src/ingame/items_menu_vwf.s +++ b/src/ingame/items_menu_vwf.s @@ -41,7 +41,7 @@ Status: """ -.import "item_layout" +.import "items" .include "../bank20.i" .include "src/battle/inventory_budget.i" diff --git a/src/ingame/key_item_picker.s b/src/ingame/key_item_picker.s index 6100ad2..c6e3878 100644 --- a/src/ingame/key_item_picker.s +++ b/src/ingame/key_item_picker.s @@ -45,7 +45,7 @@ State RAM layout (12 bytes from $1BF0, struct: RollingBufferState): """ -.import "item_layout" +.import "items" ; Labels borrowed from neighbouring modules. As an include these resolved ; because ff4.s composed one translation unit; a module names what it uses. diff --git a/src/ingame/shop_sell_rolling.s b/src/ingame/shop_sell_rolling.s index e09f4e8..559e273 100644 --- a/src/ingame/shop_sell_rolling.s +++ b/src/ingame/shop_sell_rolling.s @@ -26,7 +26,7 @@ the field menu. .include "../bank20.i" .include "config.i" -.import "item_layout" +.import "items" ; Labels this module borrows from its neighbours. As an include these ; resolved because ff4.s composed one translation unit; a module has to diff --git a/src/ingame/treasure_rolling.s b/src/ingame/treasure_rolling.s index 5c243ae..4d8112d 100644 --- a/src/ingame/treasure_rolling.s +++ b/src/ingame/treasure_rolling.s @@ -3,7 +3,7 @@ Treasure inventory rolling-buffer engine (single column, 5 visible, 6 buffer slo from `inventory_rolling.s` and tuned for the chest UI. """ -.import "item_layout" +.import "items" .include "src/lib/rolling_buffer.i" ; Labels borrowed from neighbouring modules. As an include these resolved diff --git a/src/item_layout.s b/src/items.s similarity index 100% rename from src/item_layout.s rename to src/items.s diff --git a/src/lib/rolling_inventory_engine.s b/src/lib/rolling_inventory_engine.s index e2c234b..65d0869 100644 --- a/src/lib/rolling_inventory_engine.s +++ b/src/lib/rolling_inventory_engine.s @@ -17,7 +17,7 @@ sites that remain in the per-menu source files. """ -.import "item_layout" +.import "items" ; The profiles call into the engine and the engine dispatches back into ; their HDMA builders, so the two sides are mutually dependent. That is diff --git a/src/small_vwf/item_description.s b/src/small_vwf/item_description.s index f8ed70d..dd6f9fc 100644 --- a/src/small_vwf/item_description.s +++ b/src/small_vwf/item_description.s @@ -1,6 +1,6 @@ """Small-VWF item-description renderer.""" .include "src/vwf_state.i" -.import "item_layout" +.import "items" .scope items_description { """Small-VWF item-description renderer entry-points.""" diff --git a/src/small_vwf/render.s b/src/small_vwf/render.s index 44a143f..5aea771 100644 --- a/src/small_vwf/render.s +++ b/src/small_vwf/render.s @@ -15,7 +15,7 @@ vwf_state.i). .include "config.i" -.import "item_layout" +.import "items" .include "src/vwf_state.i" VARS_BUFFER = 0x710000 From 16e29238ab240053e90ba8a292e00bf02960c9a4 Mon Sep 17 00:00:00 2001 From: Emmanuel Peralta Date: Fri, 11 Sep 2026 11:01:27 +0200 Subject: [PATCH 5/7] Turn the remaining event-script patches into modules places_names, new_game and credits are pure `.alloc at` patch files. Absolute placement resolves against the maps in scope, and a module does not inherit the patch-main's, so the ROM map moves into its own header for them to include; bank20.i takes it from there too rather than repeating the declaration. Each names the labels it reaches for: the window data next door, the text-module entry points behind the menu-text macro, and the asset symbols the build generates. --- ff4.s | 6 +++--- src/bank20.i | 7 ++----- src/ingame/credits.s | 4 ++++ src/ingame/new_game.s | 13 +++++++++++++ src/ingame/places_names.s | 6 ++++++ src/rom_map.i | 12 ++++++++++++ 6 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 src/rom_map.i diff --git a/ff4.s b/ff4.s index 7746825..a831657 100644 --- a/ff4.s +++ b/ff4.s @@ -30,6 +30,9 @@ Final Fantasy IV the new hack. .import "dialog" .import "ingame/init_bg_scroll_hdma" .import "ingame/items_menu_vwf" +.import "ingame/places_names" +.import "ingame/new_game" +.import "ingame/credits" .import "ingame/places_names_window" .import "intro" .import "kerning" @@ -71,9 +74,6 @@ Final Fantasy IV the new hack. } } -.include "src/ingame/places_names.s" -.include "src/ingame/new_game.s" -.include "src/ingame/credits.s" .include "src/ingame/menus.i" ; item name expansion patches .include "src/ingame/items_menu.s" diff --git a/src/bank20.i b/src/bank20.i index ae4e819..ea2a22f 100644 --- a/src/bank20.i +++ b/src/bank20.i @@ -8,11 +8,8 @@ linker dedupes identical ranges via `_merge_one_pool_decl`. """ -; The ROM map travels with the pool, for the same reason: `.map` is per -; translation unit, an imported module does not inherit the -; patch-main's, and a pool in bank $20 needs that bank described where -; the module can see it. Identical decls are deduped by the linker. -.map identifier=1 bank_range=0x00, 0x6f addr_range=0x8000, 0xffff mask=0x8000 mirror_bank_range=0x80, 0xcf +; The pool sits in bank $20, so the ROM map has to be in scope with it. +.include "src/rom_map.i" .pool bank20_reloc { range 0x208000 0x20FFFF diff --git a/src/ingame/credits.s b/src/ingame/credits.s index 7312d8a..d55ce20 100644 --- a/src/ingame/credits.s +++ b/src/ingame/credits.s @@ -3,6 +3,10 @@ In-place patches for the staff credits screen: re-point the credits text loader `assets_credits_text_bin` block. """ +.include "src/rom_map.i" +.extern assets_credits_text_bin + + .alloc at 0x13d7ef { ldx.w #assets_credits_text_bin & 0xffff } diff --git a/src/ingame/new_game.s b/src/ingame/new_game.s index aecd166..56b48ab 100644 --- a/src/ingame/new_game.s +++ b/src/ingame/new_game.s @@ -7,6 +7,19 @@ geometry and Cecil sprite position that go with them. """ +.include "src/rom_map.i" +.include "src/menus/system_menus_text.i" + +; The macro above expands to a call into the menus text module, and the +; strings it points at live in the start-screen text module. +.extern load_text_with_destination_in_x +.extern display_window_with_text +.extern display_time +.extern display_text_in_menus +.extern newgame +.extern display_build_number + + .include "config.i" .include "src/ingame/macros.i" diff --git a/src/ingame/places_names.s b/src/ingame/places_names.s index 004bf0a..ee7361e 100644 --- a/src/ingame/places_names.s +++ b/src/ingame/places_names.s @@ -2,6 +2,12 @@ Place-name window patches: increase the window length to fit French names and re-route the loader through our pointer table. """ + +.include "src/rom_map.i" +.extern places_bottom_window +.extern places_top_window +.extern assets_places_names_dat + { place_name_length = 0x1A .alloc at 0x00B90E { diff --git a/src/rom_map.i b/src/rom_map.i new file mode 100644 index 0000000..3ad4095 --- /dev/null +++ b/src/rom_map.i @@ -0,0 +1,12 @@ +""" +The cart's ROM bus map. + +`.map` is per translation unit and an imported module does not inherit +the patch-main's, so any module that places code at an absolute address +needs the mapping described where it can see it. Kept in its own header +rather than copied per module: the declaration is identical everywhere +and the linker accepts the repeats. +""" + + +.map identifier=1 bank_range=0x00, 0x6f addr_range=0x8000, 0xffff mask=0x8000 mirror_bank_range=0x80, 0xcf From 0f916645d5963780efad29af9c484333fca724ec Mon Sep 17 00:00:00 2001 From: Emmanuel Peralta Date: Fri, 11 Sep 2026 11:58:26 +0200 Subject: [PATCH 6/7] Turn the field-menu patch files into modules items_menu, the two rolling-menu patch files and the BG-scroll HDMA patches become imports. Each names the implementation labels its `.alloc at` sites jump to, plus the asset symbols the build generates. Macros are compile-time and do not link, so the patch files that expand `pad_nop` include libmz.i for it - the failure a missing macro produces says only `Build failed: 'pad_nop'`, with no file or line. Drops four `\$` escapes from a docstring in message.s while here. a816 parses docstrings with `ast.literal_eval`, so an invalid Python escape in one surfaces as `:1: SyntaxWarning` on every build, pointing at nothing. --- ff4.s | 8 ++++---- src/battle/message.s | 8 ++++---- src/ingame/init_bg_scroll_hdma_patches.s | 4 ++++ src/ingame/items_menu.s | 6 ++++++ src/ingame/key_item_picker_patches.s | 12 ++++++++++++ src/ingame/shop_sell_rolling_patches.s | 8 ++++++++ 6 files changed, 38 insertions(+), 8 deletions(-) diff --git a/ff4.s b/ff4.s index a831657..70db8f9 100644 --- a/ff4.s +++ b/ff4.s @@ -76,13 +76,13 @@ Final Fantasy IV the new hack. .include "src/ingame/menus.i" ; item name expansion patches -.include "src/ingame/items_menu.s" +.import "ingame/items_menu" ; Relocated init_bg_scroll_hdma (was at $01:EBD2, frees 566 bytes in bank $01). ; Blob with internal absolute references - pinned to offset $EBD2 within an ; expansion bank. Caller patch retargets the single JSL at $02:818A. .if INVENTORY_ROLLING_BUFFER { - .include "src/ingame/init_bg_scroll_hdma_patches.s" + .import "ingame/init_bg_scroll_hdma_patches" .include "src/ingame/inventory_rolling_trampolines.s" } @@ -380,8 +380,8 @@ signature byte sits at PB:(PC - 1). .if TREASURE_INVENTORY_ROLLING { - .include "src/ingame/key_item_picker_patches.s" - .include "src/ingame/shop_sell_rolling_patches.s" + .import "ingame/key_item_picker_patches" + .import "ingame/shop_sell_rolling_patches" } .if TRIGGER_ENDING_CUTSCENE { diff --git a/src/battle/message.s b/src/battle/message.s index 1d7a84f..cb41f7b 100644 --- a/src/battle/message.s +++ b/src/battle/message.s @@ -1044,7 +1044,7 @@ toggling battle_flags mid-stream. Caller setup mirrors the vanilla draw_text contract: $EF50: 16-bit format buffer ptr (source bytes) - $EF52: 16-bit destination tilemap-buffer ptr (in bank \$7E) + $EF52: 16-bit destination tilemap-buffer ptr (in bank $7E) $EF54: line length (tiles per row, currently 15) $EF55: palette $7EEF82: rolling_slot_index (0..5) for VWF allocator base @@ -1110,7 +1110,7 @@ _di_clear: bne _di_clear ; X = src (format buffer ptr), Y = dest offset (0-relative into the -; tilemap buffer pointed to by \$32 / \$34). +; tilemap buffer pointed to by $32 / $34). ldx 0xEF50 ldy.w #0x0000 @@ -1151,7 +1151,7 @@ _di_loop: jmp.w _di_loop _di_fixed_char: -; Fixed-mode raw char: write current byte as tile_id at (\$34),y. No +; Fixed-mode raw char: write current byte as tile_id at ($34),y. No ; ora #0x01 on the attr -- the +0x100 high bit is only for VWF tiles ; in the 0x1xx range, fixed font tiles live in 0x00..0xFF. sta (0x34), y @@ -1167,7 +1167,7 @@ _di_fixed_char: _di_fixed: -; 0x03 BB -> write fixed tile_id BB at (\$34),y. Same shape as +; 0x03 BB -> write fixed tile_id BB at ($34),y. Same shape as ; _di_fixed_char (mirror of wram.put_char), no +0x100 bit set. inx lda.w 0x0000, x diff --git a/src/ingame/init_bg_scroll_hdma_patches.s b/src/ingame/init_bg_scroll_hdma_patches.s index fd0a7cb..150bf90 100644 --- a/src/ingame/init_bg_scroll_hdma_patches.s +++ b/src/ingame/init_bg_scroll_hdma_patches.s @@ -2,6 +2,10 @@ Caller-side patches retargeting `JSL $01EBD2` (original `InitBGScrollHDMA`) to the relocated copy in bank $21 after the original's ROM space is reclaimed. """ + +.include "src/rom_map.i" +.extern init_bg_scroll_hdma + ;; Caller patches for init_bg_scroll_hdma after relocation to bank $21. ;; Original `JSL $01EBD2` at $02:818A becomes `JSL $21EBD2`. ;; (In LoROM bank $21 is the only thing that changes; offset $EBD2 within diff --git a/src/ingame/items_menu.s b/src/ingame/items_menu.s index b732ba7..03e3608 100644 --- a/src/ingame/items_menu.s +++ b/src/ingame/items_menu.s @@ -6,6 +6,12 @@ DrawItemSlot at $01:9000, so patching here switches them in one go. """ +.include "src/rom_map.i" +.extern draw_field_item_name_trampoline +.extern assets_items_unleashed_dat +.extern multiply_item_index_17 + + .extern items_menu_vwf.draw_field_item_name ; Item name expansion for menu system ; Patches the multiply-by-9 to multiply-by-17 diff --git a/src/ingame/key_item_picker_patches.s b/src/ingame/key_item_picker_patches.s index fef235a..f2d157f 100644 --- a/src/ingame/key_item_picker_patches.s +++ b/src/ingame/key_item_picker_patches.s @@ -30,6 +30,18 @@ Patched: """ +.include "src/rom_map.i" +.extern assets_items_unleashed_dat +.extern multiply_by_17 +.extern key_item_after_open_impl +.extern key_item_cursor_slot_impl +.extern key_item_scroll_limit_impl +.extern key_item_close_impl +.extern key_item_scroll_down_impl +.extern key_item_scroll_up_impl +.include "src/libmz.i" + + .include "config.i" .if TREASURE_INVENTORY_ROLLING { ; Silence vanilla's own list draw. UpdateItemText ($00:B22B) lays the diff --git a/src/ingame/shop_sell_rolling_patches.s b/src/ingame/shop_sell_rolling_patches.s index 28ac262..dd1e561 100644 --- a/src/ingame/shop_sell_rolling_patches.s +++ b/src/ingame/shop_sell_rolling_patches.s @@ -10,6 +10,14 @@ length. """ +.include "src/rom_map.i" +.extern sell_leave +.extern sell_scroll_down +.extern sell_scroll_up +.extern sell_init +.include "src/libmz.i" + + .include "config.i" .if TREASURE_INVENTORY_ROLLING { ; Sell list draw, on entry and after a sale changes quantities. From 4d005203308997eaaedc4d5d3c730a306c5dd9a4 Mon Sep 17 00:00:00 2001 From: Emmanuel Peralta Date: Fri, 11 Sep 2026 12:04:35 +0200 Subject: [PATCH 7/7] Turn the battle patch files into modules Everything ff4.s used to inline is now imported: the battle patch set (math, graphics, magic, commands, monsters, items, message, sram, the redraw writers, inventory rolling, the drop debug hook), the field-menu trampolines and minimal_vwf_patches. ff4.s keeps five includes, all headers: build config, the libmz macros, the rolling-buffer constants, the menu-text macro and the menus header. The trampolines needed one thing an `.extern` cannot express. A typed cast is compile-time, so `treasure_rolling.scroll_state` does not resolve through a declaration: the module binds its own view over the same address, which is what battle/inventory_rolling.s was already doing. Scopes, by contrast, do import by name - `.extern shops` brings the whole `shops.` namespace with it. --- ff4.s | 26 +++++++------- src/battle/commands_patches.s | 3 ++ src/battle/debug_always_drop.s | 3 ++ src/battle/graphics_patches.s | 4 +++ src/battle/inventory_rolling_patches.s | 4 +++ src/battle/items_patches.s | 4 +++ src/battle/magic/patches.s | 7 ++++ src/battle/math_patches.s | 3 ++ src/battle/message_patches.s | 6 ++++ src/battle/monsters_patches.s | 4 +++ src/battle/redraw_writer_patches.s | 3 ++ src/battle/sram_patches.s | 3 ++ src/ingame/inventory_rolling_trampolines.s | 41 ++++++++++++++++++++++ src/minimal_vwf_patches.s | 4 +++ 14 files changed, 102 insertions(+), 13 deletions(-) diff --git a/ff4.s b/ff4.s index 70db8f9..7b03dd6 100644 --- a/ff4.s +++ b/ff4.s @@ -51,26 +51,26 @@ Final Fantasy IV the new hack. .import "items" .include "src/lib/rolling_buffer.i" .include "src/menus/system_menus_text.i" -.include "src/minimal_vwf_patches.s" +.import "minimal_vwf_patches" .if BATTLE_ENABLED { - .include "src/battle/math_patches.s" - .include "src/battle/graphics_patches.s" + .import "battle/math_patches" + .import "battle/graphics_patches" .if MAGIC_ENABLED { - .include "src/battle/magic/patches.s" - .include "src/battle/commands_patches.s" + .import "battle/magic/patches" + .import "battle/commands_patches" } - .include "src/battle/message_patches.s" - .include "src/battle/sram_patches.s" + .import "battle/message_patches" + .import "battle/sram_patches" .if BATTLE_MONSTERS_VWF { - .include "src/battle/monsters_patches.s" + .import "battle/monsters_patches" } - .include "src/battle/items_patches.s" - .include "src/battle/redraw_writer_patches.s" + .import "battle/items_patches" + .import "battle/redraw_writer_patches" .if INVENTORY_ROLLING_BUFFER { - .include "src/battle/inventory_rolling_patches.s" + .import "battle/inventory_rolling_patches" } .if TREASURE_DEBUG_ALWAYS_DROP { - .include "src/battle/debug_always_drop.s" + .import "battle/debug_always_drop" } } @@ -83,7 +83,7 @@ Final Fantasy IV the new hack. ; expansion bank. Caller patch retargets the single JSL at $02:818A. .if INVENTORY_ROLLING_BUFFER { .import "ingame/init_bg_scroll_hdma_patches" - .include "src/ingame/inventory_rolling_trampolines.s" + .import "ingame/inventory_rolling_trampolines" } diff --git a/src/battle/commands_patches.s b/src/battle/commands_patches.s index 26f31e5..767d737 100644 --- a/src/battle/commands_patches.s +++ b/src/battle/commands_patches.s @@ -2,6 +2,9 @@ In-place patches that resize the battle command-window layout when `BATTLE_CMD_VWF` is enabled (shorter slot stride, command-id base, format-buffer pointer). """ + +.include "src/rom_map.i" + .include "config.i" command_buffer_ptr = 0x97a6 + 0x601 ; old spell lists buffers .if BATTLE_CMD_VWF { diff --git a/src/battle/debug_always_drop.s b/src/battle/debug_always_drop.s index aa52510..d5816ce 100644 --- a/src/battle/debug_always_drop.s +++ b/src/battle/debug_always_drop.s @@ -2,6 +2,9 @@ Debug patch (gated by `TREASURE_DEBUG_ALWAYS_DROP`) that forces every battle to roll a successful drop by NOPing the random-roll gate at $03:ED0D. """ + +.include "src/rom_map.i" + ; Debug: force every battle to drop an item. ; diff --git a/src/battle/graphics_patches.s b/src/battle/graphics_patches.s index dd124bb..7ccdb33 100644 --- a/src/battle/graphics_patches.s +++ b/src/battle/graphics_patches.s @@ -2,6 +2,10 @@ Battle graphics asset patches: MISS sprite glyphs, defend/row text overrides and other small tile fixups that piggyback on `defend_row` data. """ + +.include "src/rom_map.i" +.extern assets_battle_statuses_dat + .extern defend_row ; MISS sprite graphics diff --git a/src/battle/inventory_rolling_patches.s b/src/battle/inventory_rolling_patches.s index da8a778..e97bf1d 100644 --- a/src/battle/inventory_rolling_patches.s +++ b/src/battle/inventory_rolling_patches.s @@ -2,6 +2,10 @@ ROM patches that wire the battle inventory rolling-buffer engine into bank $02 (JSL trampolines for cross-bank calls, JML hooks for the scroll animation, surgical NOPs / RTS overrides). """ + +.include "src/rom_map.i" +.extern draw_window_render_hook + .include "config.i" .extern init_inventory_text_buf_rolling .extern tfr_inventory_list_rolling diff --git a/src/battle/items_patches.s b/src/battle/items_patches.s index cb9d8f0..c87ab5d 100644 --- a/src/battle/items_patches.s +++ b/src/battle/items_patches.s @@ -2,6 +2,10 @@ Patches for 12-byte (instead of 9-byte) item names in battle: rewrites every `cpx`/`cmp` boundary check and every $0F8000 item-data reference to land on `assets_items_dat`. """ + +.include "src/rom_map.i" +.extern assets_items_dat + ; Item name expansion patches for battle graphics ; Changes 9-byte items to 12-byte items ; Redirects 0x0F8000 references to assets_items_dat diff --git a/src/battle/magic/patches.s b/src/battle/magic/patches.s index 266bd7e..6f791b3 100644 --- a/src/battle/magic/patches.s +++ b/src/battle/magic/patches.s @@ -2,6 +2,13 @@ ROM patches that wire the battle magic system to the relocated `draw_magic_list_direct` renderer + the long-form attack-name copier. """ + +.include "src/rom_map.i" +.extern assets_attack_names_dat +.extern assets_attack_names_ptr +.extern assets_magic_dat +.extern battle_magic_length + .extern draw_magic_list_direct .extern magic_list_ptrs diff --git a/src/battle/math_patches.s b/src/battle/math_patches.s index 31d375e..9278b66 100644 --- a/src/battle/math_patches.s +++ b/src/battle/math_patches.s @@ -2,6 +2,9 @@ Patches that re-point the bank-2 hardware multiplier (`Mult8` at $8560) at our reimplementation, plus the JMP trampoline at $83B9 jumping into `_hw_mult16`. """ + +.include "src/rom_map.i" + ; =========================================================================== ; Mult8 Hardware Implementation - Bank 2 version at $8560 ; Input: $26, $28 → Output: $2a = $26 * $28 diff --git a/src/battle/message_patches.s b/src/battle/message_patches.s index 739f361..5bdcd90 100644 --- a/src/battle/message_patches.s +++ b/src/battle/message_patches.s @@ -2,6 +2,12 @@ Pinned-address overlay rewiring original battle-message pointer loads ($02C909, $02CC07, ...) to the relocated translated-string tables. """ + +.include "src/rom_map.i" +.extern msg_window_draw_text_trampoline +.extern assets_battle_text_ptr +.extern assets_battle_messages_ptr + .extern messages_vwf .scope message_patches { diff --git a/src/battle/monsters_patches.s b/src/battle/monsters_patches.s index d775c5f..f06c511 100644 --- a/src/battle/monsters_patches.s +++ b/src/battle/monsters_patches.s @@ -2,6 +2,10 @@ Patches that switch the battle monster-name loader from a fixed-size table to a pointer-indirected one (long names) and forward to `load_monster_pointer`. """ + +.include "src/rom_map.i" +.extern assets_monsters_long_dat + .extern load_monster_pointer .extern initialize_monster_slot .extern tab_escape_code diff --git a/src/battle/redraw_writer_patches.s b/src/battle/redraw_writer_patches.s index 6f73129..bef0b04 100644 --- a/src/battle/redraw_writer_patches.s +++ b/src/battle/redraw_writer_patches.s @@ -12,6 +12,9 @@ follow-up patches. """ +.include "src/rom_map.i" + + .extern set_active_char_and_dirty .extern messages_vwf .extern messages_vwf.init_monsters_gated diff --git a/src/battle/sram_patches.s b/src/battle/sram_patches.s index fc4c9ed..b656b7f 100644 --- a/src/battle/sram_patches.s +++ b/src/battle/sram_patches.s @@ -2,6 +2,9 @@ ROM patches that route every battle-text draw call (commands, monster names, char names, attack names, message window) through our messages-VWF init/deinit trampolines. """ + +.include "src/rom_map.i" + .include "config.i" .extern draw_command_list_for_character .extern battle_display_char diff --git a/src/ingame/inventory_rolling_trampolines.s b/src/ingame/inventory_rolling_trampolines.s index d2a1c60..b3eecaa 100644 --- a/src/ingame/inventory_rolling_trampolines.s +++ b/src/ingame/inventory_rolling_trampolines.s @@ -2,6 +2,47 @@ Bank-$01 trampolines (jsr.l + rts) into the inventory rolling routines that live in bank $21, plus small wrappers around original bank-$01 helpers used by the rolling code. """ + +.include "src/rom_map.i" +.extern drops_start_scroll_up_impl +.extern drops_start_scroll_down_impl +.extern drops_refresh_slots_impl +.extern drops_init_impl +.extern treasure_menu_exit_hook_impl +.extern treasure_menu_entry_hook_impl +.extern drops_finish_scroll_impl +.extern drops_update_scroll_frame_impl +.import "items" +.extern items_description +.extern shops + +; Typed views over the profiles' state blocks. A cast is compile-time, +; so it cannot be imported the way a label is: each module that reads +; these fields binds its own view over the same addresses, the way +; battle/inventory_rolling.s already does. +treasure_rolling := (0x7E9C00 as RollingBufferState) +drops_rolling := (0x7E9C30 as RollingBufferState) +.extern TREASURE_SCROLL_COOLDOWN_FRAMES +.extern treasure_finish_scroll_impl +.extern treasure_update_scroll_frame_impl +.extern treasure_start_scroll_up_impl +.extern treasure_start_scroll_down_impl +.extern treasure_swap_redraw_hook_impl_body +.extern treasure_refresh_slots_impl +.extern init_treasure_rolling_buffer_impl +.extern treasure_check_and_clear_count_impl +.extern sell_disable_hdma +.extern sell_scroll_down_impl +.extern sell_scroll_up_impl +.extern sell_init_impl +.extern finish_scroll_impl +.extern update_scroll_frame_impl +.extern start_scroll_up_impl +.extern start_scroll_down_impl +.extern swap_redraw_hook_impl_body +.extern init_menu_rolling_buffer_impl +.extern check_and_clear_count_impl + .include "config.i" .include "src/ingame/macros.i" diff --git a/src/minimal_vwf_patches.s b/src/minimal_vwf_patches.s index b6634af..9035c85 100644 --- a/src/minimal_vwf_patches.s +++ b/src/minimal_vwf_patches.s @@ -2,6 +2,10 @@ Minimal dialog-VWF wiring: pointer-loading helpers + entry hooks called from the existing dialog routines so the VWF layer kicks in without rewriting the message window. """ + +.include "src/rom_map.i" +.include "src/libmz.i" + ;===================================================================== ; Les Fonctions de chargement de pointeur de dialogue ;=====================================================================