fix(video): batchers + texture-cache bug-hunt batch — 15 confirmed findings#1546
Merged
Conversation
…liases, video regions) Seven confirmed findings from the batchers + texture-cache adversarial sweep, atlas/parser cluster — all reproduced by failing-first tests in the new texture-atlas-parsers.spec.js (11 tests): - aseprite parser read trimmed/spriteSourceSize/sourceSize/pivot/rotated off the frame rect instead of the frame entry (siblings, as in the TexturePacker layout) — trim silently ignored, sourceSize missing - aseprite animation speed was 10*(frameCount-1) ms/frame; tags now carry the authored per-frame `duration` as frame-object delays - addUVs coordinate-alias key used texture dims instead of region dims: sub-region lookups never hit (duplicate regions), and a frame at (0,0) poisoned full-image lookups (drew frame 0 stretched) - no-arg createAnimationFromName/getAnimationSettings iterated alias keys (every frame twice) and the aseprite `anims` entry (NaN sizes) - addRegion on an unsized video divided by width=0 → Infinity/NaN UVs (videoWidth fallback, matching uploadTexture/TextureCache.get) - padded spritesheet "truncation" rewrote the UV divisor while the GL texture keeps its physical size — frames sampled shifted under WebGL - `atlases.length === 0` dead guard on a Map — unsupported-format error was unreachable, malformed atlases constructed empty Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019t8kP8vp58ZrvaD2FqJU6A
…c, buffer lifetime) Eight confirmed findings from the batchers + texture-cache adversarial sweep, GL-state cluster — covered by the new webgl_batcher_state.spec.js (9 tests, key fixes verified by mutation): - lit normal maps bound at maxBatchTextures+slot by fixed arithmetic while the unit allocator handed those same units to color textures — whoever bound second clobbered the other (unlit sprites showing the normal map / lit sprites shading with color data). LitQuadBatcher now reserves its normal range on first bind (lazily — unlit games keep the full pool); ShaderEffect extra samplers skip units reserved by others - per-batcher tracking of the global gl.activeTexture state desynced after another batcher/FBO moved it, landing texImage2D re-uploads on foreign textures (video frames overwriting a mesh texture). The active unit now lives on the renderer (shared accessor); createTexture2D force-activates its target unit before uploading - post-effect FBO setup + blitTexture null unit 0's binding but only invalidated the current batcher — other batchers sampled an unbound texture (opaque black). New renderer.invalidateTextureUnit() reaches every batcher - PrimitiveBatcher.drawVertices had no chunking: a filled ellipse at radius ≳ 435px overflowed the 4096-vertex buffer (silently-dropped typed-array writes + GL errors, nothing rendered). Oversized shapes now chunk on primitive boundaries; thick-line expansion checks capacity per pair - NoiseTexture2d.destroy() leaked the lit batcher's cached GL texture + baked canvas (strong-keyed map, only emptied on full reset). New event.TEXTURE2D_DESTROYED emitted by Texture2d.destroy(); the lit batcher evicts in response - module-level mesh depth-clear flag was shared across renderer instances; now per-renderer, and RENDER_TARGET_CHANGED carries the emitting renderer so subscribers ignore foreign broadcasts - quad batchers leaked a GL index buffer on every GAME_RESET (recreate without delete); mesh batchers kept lost-context index/vertex buffers after webglcontextrestored (uploads silently failed). Both families now delete + recreate on reset (WebGLIndexBuffer.destroy/recreate) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019t8kP8vp58ZrvaD2FqJU6A
…atch Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019t8kP8vp58ZrvaD2FqJU6A
…with #1543) #1543 widened Texture2d.getTexture() to the new Texture2dSource union (GPU-resident backings incl. ImageBitmap); the event signature added in this branch predated that and was too narrow to typecheck. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019t8kP8vp58ZrvaD2FqJU6A
bb62cdf to
6e93a06
Compare
…overlay Since the 19.5 examples-shell restyle (#1454), #screen is a fixed overlay covering everything below the topbar; the tiledMapLoader/spine selectors were floated above it (absolute + zIndex 1000) but the aseprite one stayed in normal flow and has been painted over ever since. Float it the same way, match the house control styling, and default the select to "run front" so it agrees with the animation the entity actually starts with. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019t8kP8vp58ZrvaD2FqJU6A
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Beat 2 of the adversarial bug hunt: batchers + texture cache (~4,200 lines swept). One deep-reading finder agent produced 14 findings; all 14 were hand-verified against source with traced call paths before any fix, and a 15th (mesh GL buffers dead after context restore) surfaced during verification. All 15 are fixed here, in two cluster commits.
Atlas / parser cluster (commit 1 — pure JS, 11 failing-first tests)
trimmed/spriteSourceSize/sourceSize/pivot/rotatedone level too deepsourceSizemissing10×(frameCount−1)ms/framedurations ignoredaddUVsalias key used texture dims, not region dims (#1281 workaround)createAnimationFromName/getAnimationSettingsiterated alias keys +animsaddRegionon unsized video divided bywidth = 0atlases.length === 0on aMapGL-state cluster (commit 2 — 9 tests, key fixes mutation-verified)
Light2d+ normal-mapped sprite + ≥9 distinct textures: unlit sprites render the normal map, or lit sprites shade with color data as normals. Reservation is lazy (first lit bind) so unlit games keep the full pool;ShaderEffect.setTexturesamplers skip reserved unitsgl.activeTexturerenderer.invalidateTextureUnit()reaches all batchersPrimitiveBatcher.drawVerticesfillEllipseat radius ≳ 435 px: GL errors, shape (and batch-mates) vanish. Chunked on primitive boundaries, exact for all modesNoiseTexture2d.destroy()leaked its GL normal-map texture + canvasevent.TEXTURE2D_DESTROYED+ lit-batcher evictionRENDER_TARGET_CHANGEDnow carries the emitting rendererGAME_RESET; mesh index/vertex buffers stayed bound to the lost context after restore (uploads silently failed). Delete + recreate on resetVerification
pnpm lint+ biome clean;pnpm buildgreenKnown-open, deliberately not in scope: #1489 (framewidth refinement no-op) and the anchorPoint first-wins atlas contamination — both are shared-atlas semantics questions for the #1410 refactor rather than point fixes.
🤖 Generated with Claude Code
https://claude.ai/code/session_019t8kP8vp58ZrvaD2FqJU6A