Skip to content

Perf pass + level-load curtain & cache fixes - #12

Merged
J3vb merged 5 commits into
masterfrom
dev
Jul 5, 2026
Merged

J3vb merged 5 commits into
masterfrom
dev

Conversation

@J3vb

@J3vb J3vb commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Promotes the perf pass and follow-up fixes from dev to master.

Changes

  • perf: settings-slider stutter, save-persist debounce, per-frame churn cleanup
  • perf: town resident scenery cache, dungeon collider broad-phase
  • fix: two regressions from the perf pass (code review)
  • fix: town cache froze the pre-asset procedural fallback (perf regression)
  • fix: loading overlay — opaque full-screen curtain hides the level-load freeze; pre-warm mid-play compile stutters

All 82 tests pass.

🤖 Generated with Claude Code

J3vb and others added 5 commits July 5, 2026 12:00
… cleanup

Phase 1: hoist per-frame allocations (colMap, RK), convert boolean-threshold
Math.hypot checks to squared-distance where the sqrt value isn't otherwise
reused, replace updateDebug's scene.traverse light count with the existing
_plVisN counter.

Phase 2: settings sliders (resolution/bloom/exposure/volume) fired a full
SAVE.persist() plus a full-scene material needsUpdate sweep on every drag
tick instead of once on release, forcing a WebGPU pipeline recompile per
tick. Split each into a cheap oninput (live preview only) and an onchange
that does the expensive commit.

Phase 3: SAVE.persist() serialized all 3 character slots from ~50 call
sites with no throttling. Debounce the non-showNote path in saveProgress()
by 200ms (showNote saves stay synchronous, unchanged UX), with
flushSaveProgress() to commit immediately on tab close. Fixed a
found-in-review regression where beforeunload only flushed when `running`
was true, silently dropping the death-state save since gameOver() clears
`running` before its own saveProgress(false) call.

Phase 4: replace .filter()-based array reallocation with the existing
_compact() idiom for status-effect ticking and monster removal (kill/flee),
and extend the town-only interactables() cache to wild/dungeon zones.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 5: town zone had no resident cache unlike dungeon/wild, so every
enterTown() disposed and fully rebuilt+re-merged scenery even on a repeat
visit to an already-built town. Thread a `group` param through the town
builder helpers (previously hardcoded to townSceneryGroup) so each town id
can be built once into its own sub-group, cached, and re-shown via
.visible toggling on repeat visits — same pattern dungeonBiomeCache/
wildRegionCache already use.

Phase 6: resolveCircles() was a brute-force scan over every dungeon-floor
collider for every monster and the player, every frame, with no spatial
partitioning — the largest uncached CPU cost under load since a floor's
interior walls alone can add several hundred collider circles. Add a
broad-phase bucket (dungeonCollidersNear) keyed on the dungeon layout's own
existing room-grid cells, with incremental add/remove wired through the
"waller" elite affix's temp walls. Scoped to dungeon only; monsters/
projectiles and wild/town collision are unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
killMonster double-drop: Phase 4 deferred monster removal (m.dead +
end-of-frame _compact) but killMonster wasn't idempotent, so a monster
killed by one source could be re-killed by another in the same frame
(deathnova chain, a second volley bolt) or a next-frame AoE before
compaction — re-running its full loot/gold/gem drops. Guard with an
early `if (m.dead) return;`.

Save-on-close: Phase 3's beforeunload flushSaveProgress() returned early
when no debounce was pending (~98% of the time), losing xp/gold/kills
accrued since the last 8s autosave — the old saveProgress(false) always
committed fresh state. flushSaveProgress now copies live player state and
persists unconditionally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion)

Phase 5's townCache had no kit-upgrade check, unlike wild/07-wild.js. The
title-screen buildTown() (16-zones.js) runs before buildings/props/cobble
finish loading, so it cached the procedural box-house fallback — and
rebuildZoneScenery()/the cobble hook, which call buildTown() again once
assets arrive, hit the cache and only toggled .visible, freezing the early
look forever.

Mirror wild: tag each cached sub with a kit signature over the three
monotonic async inputs (BUILDINGS_READY|PROPS_READY|_cobbleBase) and reuse
the cache only while it matches; otherwise tear the sub down (meshes + its
own town:<id> light bucket) and rebuild in place with the real assets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@J3vb
J3vb merged commit 518ca96 into master Jul 5, 2026
2 checks passed
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