Skip to content

Flush battle VWF CHR per dirty region instead of the full buffer - #33

Merged
manz merged 3 commits into
masterfrom
feat/battle-vwf-per-region-chr-flush
Sep 3, 2026
Merged

manz merged 3 commits into
masterfrom
feat/battle-vwf-per-region-chr-flush

Conversation

@manz

@manz manz commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Battle command-window input during a stacked-DMA frame could overrun
vblank when the VWF NMI hook blasted the whole 0x1000 CHR buffer on
every dirty frame. Turns the flush into a per-region bitmask (messages/
monsters/names/commands, 0x300 bytes each) so a typical 1-region frame
DMAs ~4.5 lines instead of ~24, and moves the gate state that tracks it
out of the CHR buffer entirely.

That relocation matters on its own: the gate bytes used to live at
$70:3F00, inside VWF_CHR_BUFFER, and inventory slot 4's CHR slice
($70:3E80..$70:3F1F) overwrote them with glyph pixels -- the battle
monster-name window never rendered on current master. Repro'd and
confirmed fixed against a live battle savestate (before/after
screenshots, WRAM/VRAM byte comparison).

A second, unrelated clobber in the same area: two sites in the
inventory-slot pre-render stored the slot's tile_id base into
pending_transfer_mask -- leftover from before that byte became a
dirty-region bitmask. Every inventory slot render wiped CHR_PENDING and
all four region bits, so the battle names window (which only renders
once per battle) lost its one flush and stayed black for the rest of
the fight. Both stray stores only needed A for the allocator thunk that
follows; dropped.

Forced-blank on queued NMI DMA work stays in as a backstop for
pathological stacked frames -- the common case no longer needs it.

manz added 3 commits September 3, 2026 15:11
Inventory slot N's CHR runs $70:3C00 + N*160, so slot 4 spans
$70:3E80..$70:3F1F and overwrote the gate bytes with glyph pixels.
The battle VWF tile + tilemap + inventory-CHR DMA pass can run ~40
scanlines and starts late in the NMI (after OAM DMA and the flying-HDMA
chain), so its tail spilled past the ~37-line vblank into active scan and
tore VRAM. Only the restore half of the forced-blank handshake survived
(vanilla NMI tail $02:837F: lda $6CC1 / sta $2100); the set half was
missing, so the screen was never blanked during the overrun.

Set $2100=$80 at the head of dma_transfer whenever any DMA work is queued
(pending_transfer_mask / tilemap_pending_mask / dma_dirty_slots), letting
the DMA tail blank the first active lines; $02:837F restores brightness.
Idle frames skip the blank so there's no visible black strip.
The NMI VWF tile DMA blasted the whole 0x1000 CHR buffer every dirty
frame (~24 scanlines), which on stacked frames overran vblank. The
abandoned SMART path tried per-region transfers but was dead code (SMART
undefined) and its count was wrong (0x400 vs the real 0x300).

Turn pending_transfer_mask into a dirty-region bitmask: bit 0 = transfer
queued (set by deinit), bits 1-4 = which of the four VWF regions
(messages/monsters/names/commands) was re-rendered. Each init path sets
its region bit; dma_transfer flushes only the flagged regions, each a
0x300-byte slice at buffer_ptr + N*0x300 -> VRAM $B000 + N*0x300. Worst
case (all four) is 0xC00 (~18 lines), comfortably inside vblank.

Inventory CHR already rides the separate dma_dirty_slots path, so
init_inventory_region no longer writes pending_transfer_mask (its old
base store would corrupt the region bits). Two more sites in the slot
pre-render also stored the tile_id base into that byte -- leftover from
before it became a bitmask -- which clobbered CHR_PENDING and every
region bit on every inventory slot render; dropped both, they only
needed A for the allocator thunk.

The per-region bit sets grew the gated init bodies past 8-bit branch
range, so the long intra-block branches move to brl / a near trampoline.

With the typical 1-region frame now ~4.5 lines, the forced-blank set
stays only as a backstop for pathological stacked frames.
@manz
manz merged commit 4532bff into master Sep 3, 2026
1 check failed
@manz
manz deleted the feat/battle-vwf-per-region-chr-flush branch September 3, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant