Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
7b4880f
Resolve rolling-buffer test addresses from debug info
manz Sep 3, 2026
e7687d5
Address the drops scroll position in bank $7E
manz Sep 3, 2026
efad9ea
Flush the rolling buffer to VRAM on engine init
manz Sep 3, 2026
8bccea3
Tick the treasure scroll debounce once per frame
manz Sep 3, 2026
fa79ed0
Publish visible row count to the rolling engine
manz Sep 3, 2026
c975ddf
Move the VWF CHR flush off the treasure HDMA channel
manz Sep 3, 2026
281dced
Address the key-item scroll position in bank $7E
manz Sep 3, 2026
cad0be5
Render the key-item picker into its own VWF window
manz Sep 3, 2026
03048bb
Re-record treasure and drops goldens
manz Sep 3, 2026
a7e3db9
Pass the shop's row index to the item-name renderer
manz Sep 3, 2026
14570fd
Keep the shop price out of the name blank run
manz Sep 3, 2026
89f1498
Blank only the cells a VWF name can occupy
manz Sep 3, 2026
203e856
Shift the shop buy cursor with the item rows
manz Sep 3, 2026
9481fa0
Move the VWF tilemap cursor off direct page
manz Sep 3, 2026
919c2f3
Long-address the key-item picker state and arm its HDMA
manz Sep 3, 2026
13664cc
Leave BG3 scroll alone until the picker scroll wiring lands
manz Sep 3, 2026
d20d862
Render the key-item picker through the rolling engine
manz Sep 4, 2026
5a93e64
Re-render the picker when its list scrolls
manz Sep 4, 2026
39ae212
Cover the picker scroll re-render
manz Sep 4, 2026
204d7e0
Scroll the key-item picker over the engine's ring
manz Sep 4, 2026
674dabd
Refresh the picker only against an armed struct
manz Sep 4, 2026
836edc5
Keep the picker's window IRQ alive while rendering
manz Sep 4, 2026
dbccc80
Reserve rolling-menu scratch from a pool
manz Sep 4, 2026
47d97cd
Map the cart SRAM and hold rolling state there
manz Sep 4, 2026
56080c7
Give the map its BG3 CHR back when the picker closes
manz Sep 4, 2026
4bed692
Leave the map's DMA channel as we found it
manz Sep 4, 2026
61c2173
Keep VWF kerning state off the field's MOSAIC shadow
manz Sep 4, 2026
d834942
Render the key-item picker on its own direct page
manz Sep 4, 2026
388c5ff
Draw the missing u-circumflex in the 8x8 VWF font
manz Sep 4, 2026
bfbb5c8
Blank the key-item picker's slot rows before drawing
manz Sep 4, 2026
6f879b6
Give the shop sell list a rolling buffer
manz Sep 4, 2026
e857004
Keep the key-item picker's cursor inside the rolling ring
manz Sep 4, 2026
e734d10
Line the treasure hand cursors up with the single-column rows
manz Sep 4, 2026
cbb83d8
Stop the key-item list where the held items end
manz Sep 5, 2026
ba0898b
Add a Makefile with build, test and lint targets
manz Sep 5, 2026
ecc9ed4
Format the tree and clear the fluff autofixes
manz Sep 5, 2026
20e172a
Pin a816 1.1.0a31
manz Sep 6, 2026
a8131d1
Silence S001 on the rolling-buffer state casts
manz Sep 6, 2026
eee3a11
Document the modules the lint flagged
manz Sep 6, 2026
b60829c
Silence UP001 on the kerning test stubs
manz Sep 6, 2026
f446b33
Track the active character with the battle name highlight
manz Sep 10, 2026
f9f094e
Merge pull request #35 from manz/fix/battle-name-highlight
manz Sep 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
V = 0
Q = $(if $(filter 1,$V),,@)

M = $(shell if [ "$$(tput colors 2> /dev/null || echo 0)" -ge 8 ]; then printf "\033[34;1m▶\033[0m"; else printf "▶"; fi)

# Prefer the checkout's venv, fall back to whatever is on PATH so CI
# (which pip-installs into the job's interpreter) works unchanged.
PY ?= $(shell [ -x .venv/bin/python ] && echo .venv/bin/python || echo python3)

ROM = build/ff4.sfc
IPS = build/ff4.ips
# -o binds looser than -not, so the extension test needs its own group
# or .s files under .venv/ come along.
SOURCES = $(shell find . -path ./.venv -prune -o \( -name '*.s' -o -name '*.i' \) -print)

.SUFFIXES:
.PHONY: all
all: | build tests ## Build the patch and run the tests

.PHONY: build
build: $(IPS) ## Assemble the IPS patch

# The base ROM is deliberately not a prerequisite: as a rule it would be
# something `make -B` tries to remake, and it is an input we can only ask
# the user to provide. CI decrypts ff4.sfc.gz.gpg into place.
$(IPS): $(SOURCES)
$(Q) test -s $(ROM) || { \
echo "$(ROM) missing. Decrypt it with:"; \
echo " gpg --decrypt ff4.sfc.gz.gpg | gunzip > $(ROM)"; \
exit 1; }
$(info $(M) Building patch...)
$(Q) $(PY) ./build.py
$(Q) test -s $(IPS)

.PHONY: tests
tests: $(IPS) ## Run the full suite against a freshly built patch
$(info $(M) Running tests...)
$(Q) $(PY) -m pytest

.PHONY: test
test: tests ## Alias for `tests`

.PHONY: check
check: ## Verify .s/.i formatting and run the a816 fluff lints
$(info $(M) Checking sources...)
$(Q) a816 format --check $(SOURCES)
$(Q) a816 check $(SOURCES)

.PHONY: format
format: ## Rewrite .s/.i sources in a816 canonical form
$(info $(M) Formatting sources...)
$(Q) a816 format $(SOURCES)

.PHONY: clean
clean: ## Remove build products, keeping the base ROM
$(info $(M) cleaning ...)
$(Q) rm -f $(IPS) $(IPS).adbg build/ff4-patched.sfc a.out a.out.adbg
$(Q) rm -rf build/obj __pycache__ .pytest_cache

.PHONY: help
help: ## Display help
@grep -hE '^[ a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-17s\033[0m %s\n", $$1, $$2}'
13 changes: 12 additions & 1 deletion ff4.s
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ Final Fantasy IV the new hack.
----------------
"""

; --- Memory map --------------------------------------------------------
; The default low_rom bus knows ROM and WRAM but not the cart's SRAM, so
; a `bss` pool in bank $70 failed the map check even though nothing is
; emitted there. Declaring the map ourselves adds it: the header is
; patched to 128KB of SRAM and the boot path clears it, so bank $70 is
; ours alone and far safer for state than hand-picked WRAM holes.
.map identifier=1 bank_range=0x00, 0x6f addr_range=0x8000, 0xffff mask=0x8000 mirror_bank_range=0x80, 0xcf
.map identifier=2 bank_range=0x7e, 0x7f addr_range=0x0000, 0xffff mask=0x10000 writable=1
.map identifier=3 bank_range=0x70, 0x70 addr_range=0x0000, 0x7fff mask=0x8000 writable=1

; Auto-prepended: imports must precede .include'd patches
.import "assets"
.import "battle/commands_reloc"
Expand Down Expand Up @@ -363,13 +373,15 @@ signature byte sits at PB:(PC - 1).
.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"
}

; --- Binary text assets -------------------------------------------------


.if TREASURE_INVENTORY_ROLLING {
.include "src/ingame/key_item_picker_patches.s"
.include "src/ingame/shop_sell_rolling_patches.s"
}

.if TRIGGER_ENDING_CUTSCENE {
Expand Down Expand Up @@ -402,4 +414,3 @@ signature byte sits at PB:(PC - 1).
.alloc at 0x238000 {
.incbin "assets/items_unleashed.dat"
}

Binary file modified fonts/8x8vwf.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
a816==1.1.0a30
a816==1.1.0a31
kintsuki[visual]==0.0.0a13
1 change: 0 additions & 1 deletion src/assets.s
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,3 @@ font_table:
.incbin "assets/intro.set"
}
}

7 changes: 5 additions & 2 deletions src/bank20.i
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
"""Shared bank-20 relocated-code pool declaration.
"""
Shared bank-20 relocated-code pool declaration.

Every module that wraps its body in `.alloc <name> in bank20_reloc { ... }`
must include this header so the pool is visible at the module's
codegen pass. Pool decls are idempotent across modules — the
linker dedupes identical ranges via `_merge_one_pool_decl`."""
linker dedupes identical ranges via `_merge_one_pool_decl`.
"""


.pool bank20_reloc {
range 0x208000 0x20FFFF
Expand Down
154 changes: 90 additions & 64 deletions src/battle/commands_patches.s
Original file line number Diff line number Diff line change
Expand Up @@ -7,80 +7,106 @@ command_buffer_ptr = 0x97a6 + 0x601 ; old spell lists buffers
.if BATTLE_CMD_VWF {
command_length = 6
; Command window
.alloc at 0x16fe5a + 6 * 2 {
.db 0x05, 0x00, command_length + 2, 0x0d
}
.alloc at 0x2b990 {
lda.b #0x18 + 8
}
.alloc at 0x16fe5a + 6 * 2 {
.db 0x05, 0x00, command_length + 2, 0x0d
}


.alloc at 0x2b990 {
lda.b #0x18 + 8
}
} else {
command_length = 10

; move command cursor on the moved window
.alloc at 0x2b990 {
lda.b #0x18
}
.alloc at 0x029CD6 {
lda.b #command_length
}
.alloc at 0x029D15 {
lda.b #command_length
}
.alloc at 0x029D42 {
cpy.w #command_length + 2
}
.alloc at 0x029D5A {
cpy.w #command_length + 2
}
.alloc at 0x029D39 {
lda.l assets_battle_commands_dat, x
}
.alloc at 0x029CE0 {
lda.b #command_length * 4
.alloc at 0x2b990 {
lda.b #0x18
}

; patches source & length of battle commands used in display attack window.
}
.alloc at 0x02cb49 {
lda.b #command_length

; attack window kick for example ends up there
}
.alloc at 0x02cb54 {
lda.l assets_battle_commands_dat, x
}
.alloc at 0x02cb5d {
cpy.w #command_length
.alloc at 0x029CD6 {
lda.b #command_length
}

; Command window
}
.alloc at 0x16fe5a + 6 * 2 {
.db 0x04, 0x00, command_length + 2, 0x0d
}

.alloc at 0x029D15 {
lda.b #command_length
}


.alloc at 0x029D42 {
cpy.w #command_length + 2
}


.alloc at 0x029D5A {
cpy.w #command_length + 2
}


.alloc at 0x029D39 {
lda.l assets_battle_commands_dat, x
}


.alloc at 0x029CE0 {
lda.b #command_length * 4

; patches source & length of battle commands used in display attack window.
}


.alloc at 0x02cb49 {
lda.b #command_length

; attack window kick for example ends up there
}


.alloc at 0x02cb54 {
lda.l assets_battle_commands_dat, x
}


.alloc at 0x02cb5d {
cpy.w #command_length

; Command window
}


.alloc at 0x16fe5a + 6 * 2 {
.db 0x04, 0x00, command_length + 2, 0x0d
}
}


{
; ram position of the prebuilt battle windows
.alloc at 0x16FEAD {
cmd_text_buf_ptrs:
battle_data_size = command_length * 4 * 5
.dw command_buffer_ptr
.dw command_buffer_ptr + battle_data_size
.dw command_buffer_ptr + battle_data_size * 2
.dw command_buffer_ptr + battle_data_size * 3
.dw command_buffer_ptr + battle_data_size * 4
}
.alloc at 0x16FE54 {
.db command_length * 2
.db 0x0a
.dw command_buffer_ptr ; read address
.if BATTLE_CMD_VWF {
.dw 0xC1F4 - 2 ; write address
} else {
.dw 0xC1F4 - 4 ; write address
}
}
.alloc at 0x02999F {
ldx.w #battle_data_size
}
.alloc at 0x16FEAD {
cmd_text_buf_ptrs:
battle_data_size = command_length * 4 * 5
.dw command_buffer_ptr
.dw command_buffer_ptr + battle_data_size
.dw command_buffer_ptr + battle_data_size * 2
.dw command_buffer_ptr + battle_data_size * 3
.dw command_buffer_ptr + battle_data_size * 4
}


.alloc at 0x16FE54 {
.db command_length * 2
.db 0x0a
.dw command_buffer_ptr ; read address
.if BATTLE_CMD_VWF {
.dw 0xC1F4 - 2 ; write address
} else {
.dw 0xC1F4 - 4 ; write address
}
}


.alloc at 0x02999F {
ldx.w #battle_data_size
}
}
Loading