Skip to content

perf: char-boundary-split blitter; raise enemy cap to 8 - #4

Open
mpasternak wants to merge 1 commit into
mainfrom
perf/blitter-split-enemy-cap-8
Open

perf: char-boundary-split blitter; raise enemy cap to 8#4
mpasternak wants to merge 1 commit into
mainfrom
perf/blitter-split-enemy-cap-8

Conversation

@mpasternak

Copy link
Copy Markdown
Contributor

Co i po co

Priorytet projektu to SMOOTH and FAST — ten PR odzyskuje T-states w najgorętszym miejscu klatki (render = ~58% budżetu) i wydaje je na 8. wroga na ekranie (tabela fal prosi o 8 od fali 11; dotąd przycinane do 7).

1. Blitter: char-boundary split (src/blit.asm)

Makro DOWN płaciło ~27 T testu granicy komórki znaku na każdym wierszu, choć 8-wierszowy sprite przekracza granicę co najwyżej raz — po dokładnie 8-(y&7) wierszach, a sprite przycięty na dole (192 % 8 == 0) nie przekracza jej wcale. Pętle wierszy podzielone na dwa segmenty czystego inc h (4 T) z jednym uproszczonym, bezwarunkowym krokiem granicznym pomiędzy; ścieżki "wide" używają inc l/dec l (bezpieczne: bramkowane bx<31).

2. Cap wrogów 7 → 8 (lockstep)

enemy.h + ld b,7→8 w enemy_update.asm i collide.asm + guard w collision.c + harness measure_main.c (9 operacji erase). Maski slotów pozostają u8 (8 slotów = bity 0..7). Test test_spawn_wave16_cap7test_spawn_wave16_full (fala 16 już nie jest przycinana: 8 żywych, 7 hunterów).

Pomiary (z88dk-ticks, worst case: fala 16, mix hunterów, 200 iter)

System 7-cap przed 7-cap po blitterze 8-cap finał
render 23 170 21 438 (−7,5%) 24 007
enemies_update 7 021 7 021 7 974
collide 3 195 3 195 3 432
player_hit 512 512 512
PT3 tick 6 224 6 224 6 224
suma / ~55k budżetu 40 122 38 390 42 149

Netto: +1 wróg za +2,0k T — zapas (~13k T) praktycznie identyczny jak przy starym capie 7.

Weryfikacja

  • Test różnicowy stary-vs-nowy blitter (metodologia jak przy enemy_update.asm): 30 720 wywołań — 4 procedury × bx {0,1,15,30,31} × sh 0..7 × wszystkie y 0..191, w obu wariantach buildu (tabela scld_row_off i ZX128_PAGE_FLIP) — sumy kontrolne bufora identyczne co do bajta.
  • Adwersaryjny review asm (przypadki: klip dolny/prawy, sh==0, y&7==0, pociski 2-wierszowe, liveness rejestrów, zakazy IY/EXX/SP) — zero blockerów/majorów.
  • ./test/run.sh — wszystkie testy hostowe zielone.
  • make all — 3 tapy (timex/zx128/zx48) budują się, walidatory layoutu "safe".
  • Segmenty poza renderem zmierzone co do bajta identycznie z baseline po samym refaktorze blittera (nic obok nie drgnęło).
  • Docs zaktualizowane: docs/perf-budget.md (nowa tabela 8-cap, stare jako historia, poprawiona nieaktualna wzmianka o ZX128_NO_MUSIC), CLAUDE.md (sekcja Performance budget).

Do ręcznego sprawdzenia

  • make run-tc2048 — dograć do fali 11+, zobaczyć 8 wrogów przy stabilnych 50 Hz
  • make run-zx128 / make run-zx48 — sanity flip/single-buffer

🤖 Generated with Claude Code

Blitter (src/blit.asm): replace the per-row DOWN macro (~27 T of
boundary testing on every scanline) with a two-segment split around a
single, simplified boundary step. An 8-row sprite crosses a char-cell
boundary at most once, after exactly 8-(y&7) rows; bottom-clipped
sprites (192 % 8 == 0) never cross at all. Intra-cell advance is now a
bare `inc h`; wide paths use `inc l`/`dec l` (safe: gated on bx<31).
Measured render: 23,170 -> 21,438 T/frame (-7.5%) via z88dk-ticks.

Verified byte-identical to the old blitter by a differential sweep on
z88dk-ticks: 4 routines x bx {0,1,15,30,31} x sh 0..7 x all y 0..191
(30,720 calls) in both the scld_row_off-table and ZX128_PAGE_FLIP
builds -- identical buffer checksums.

Enemy cap (lockstep): MAX_ENEMIES 7 -> 8 in enemy.h,
enemy_update.asm, collide.asm, and the collision.c guard. The wave
table already asks for 8 enemies from wave 11, so the 8th enemy now
actually spawns. measure_main.c erases 1+MAX_ENEMIES sprites;
test_spawn_wave16_cap7 renamed to test_spawn_wave16_full (wave 16 no
longer clamps: 8 alive, 7 hunters).

Worst-case frame (wave-16 hunter mix, 8-cap, 2026-07-08): render
24.0k + enemies_update 8.0k + PT3 6.2k + collide 3.4k + player_hit
0.5k = 42.1k T subtotal, ~13k headroom to the ~55k budget -- the same
margin the old 7-cap had. Docs updated (perf-budget.md, CLAUDE.md),
including the stale ZX128_NO_MUSIC note (ZX128 pays PT3 every frame
since full parity).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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