Fix textbox rendering: composite window over sprites, clamp to box width

Two PPU fixes for the dialog/menu window layer:

- Render the window layer AFTER sprites (was before) so the textbox
  occludes NPCs standing under it. This inverts GB hardware layer order
  on purpose: the window here is only the bottom dialog/menu box, and the
  artifact is port-specific to our extended 40x25 player-centered viewport
  exposing NPCs the 20x18 GB screen never put under the box.

- Clamp the window blit to SCREEN_W (the 20-tile box content width) instead
  of the 256px row_buf size, so the TILE_SPC filler in tilemap cols 20-31
  no longer paints blank tiles over the BG to the right of the box.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Happyarch
2026-06-24 21:27:23 -04:00
parent 2da109f666
commit f518bbcbab
2 changed files with 30 additions and 9 deletions

View File

@@ -560,7 +560,13 @@ render_sprites:
; This prevents visual drift when the window starts mid-frame. The implementation
; stores WLY in win_line_ctr (BSS) and increments it after each active scanline.
;
; Call after render_bg and before render_sprites.
; Call after render_bg AND render_sprites. NOTE: this inverts the GB hardware
; layer order — on real DMG/CGB, OBJ sprites draw over the window, so the HW
; order is BG → window → sprites. We diverge on purpose: the window here is only
; the bottom dialog/menu box (WY=152), which must occlude NPCs under it. The
; bug only shows up because this port's extended 40×25 player-centered viewport
; renders NPCs in the bottom rows that the GB's 20×18 screen never put under the
; box. See the DIVERGENCE note in src/video/frame.asm:DelayFrame.
; In: EBP = GB memory base. All registers preserved.
; ---------------------------------------------------------------------------
render_window:
@@ -642,10 +648,16 @@ render_window:
push ecx
mov ecx, RENDER_W
sub ecx, edx ; pixels to reach the right edge
cmp ecx, 256
; Clamp to SCREEN_W (160px = 20 tiles), the dialog/menu box's content width.
; row_buf holds 256 decoded px (32 tiles), but only cols 0-19 carry the box;
; cols 20-31 are TILE_SPC filler added to pad the 32-wide GB tilemap row, and
; must NOT be blitted — otherwise they paint blank tiles over the BG to the
; right of the box (the box is 160px centered in our 320px viewport, so BG is
; visible past its right edge). 160 < 256, so this also stays within row_buf.
cmp ecx, SCREEN_W
jbe .do_right_copy
mov ecx, 256 ; clamp — row_buf holds only 256 decoded px
.do_right_copy: ; (32 tiles); never read past it into BSS
mov ecx, SCREEN_W
.do_right_copy:
add edi, edx ; advance dest to screen_x_start
rep movsb
pop ecx
@@ -657,11 +669,11 @@ render_window:
lea esi, [row_buf + edx]
push ecx
mov ecx, RENDER_W
mov eax, 256
sub eax, edx ; bytes remaining in row_buf after the clip
mov eax, SCREEN_W ; box content width (see right-copy clamp above)
sub eax, edx ; content px remaining after the clip
cmp ecx, eax
jbe .do_left_copy
mov ecx, eax ; clamp — never read past row_buf end
mov ecx, eax ; clamp — only blit real box content, not filler
.do_left_copy:
rep movsb
pop ecx

View File

@@ -57,8 +57,17 @@ DelayFrame:
call update_oam ; PrepareOAMData → shadow OAM, then DMA to OAM
call joypad_update
call render_bg
call render_window ; composite window layer over BG (before sprites)
call render_sprites ; composite OAM sprites over BG+window
call render_sprites ; composite OAM sprites over BG
; DIVERGENCE FROM GB HARDWARE (intentional): on real DMG/CGB, OBJ sprites
; draw OVER the window layer, so the GB order is BG → window → sprites. We
; deliberately invert that here (window LAST, over sprites) because the
; window layer in this port is only ever the bottom dialog/menu box (WY=152),
; and that box must occlude NPCs standing under it. The artifact this guards
; against is port-specific: our extended 40×25 player-centered viewport shows
; far more of the map than the GB's 20×18 screen, exposing NPCs in the bottom
; rows that the original camera never placed under the textbox. The player
; sprite sits at screen center (well above WY=152), so it is never occluded.
call render_window ; composite window layer (textbox/menu) OVER sprites
call draw_player_marker ; legacy placeholder (no-op unless explicitly enabled)
call present
cmp byte [pad_quit], 0