Public: anyone can read this; the crew can change it.

forked from trevato/cs · proposes · 4 behind

Boot is not a freeze: the atlas is painted in slices, and the shooter's menu answers while it paints #125

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`src/engine/texture.ts` gains `painter(shaders, size)`: `Painter.paint(rows)` paints at most that many rows — a row is one row of one layer, so `paint(size)` is exactly one layer — returns whether the atlas is finished, and is a no-op once it is; `Painter.bytes` is the buffer it allocated up front and `Painter.rowsLeft` is what is left. There is no clock in it and no callback out of it. `paintAtla…

shipped production is serving it deploy ae07c2d

opened this

src/engine/texture.ts gains painter(shaders, size): Painter.paint(rows) paints at most that many rows — a row is one row of one layer, so paint(size) is exactly one layer — returns whether the atlas is finished, and is a no-op once it is; Painter.bytes is the buffer it allocated up front and Painter.rowsLeft is what is left. There is no clock in it and no callback out of it. paintAtlas keeps its signature and is now that painter asked for every row at once, so it is the only loop in the file and the range's single call is unchanged.

The shooter boots through it. boot asks the host whether there is a WebGL2 context before anything is painted — a browser without one gets the same sentence in the menu and costs no atlas, so Host.createRenderer is two steps now: a BuildRenderer or null, and the build takes the finished bytes. From then on the frame loop paints one layer per frame and builds the renderer exactly once, on the frame the last row lands, before the first draw. Until then the menu shows Preparing textures… with Play disabled — both server-rendered in src/pages/game.ts so the first paint is already true and nothing above the line moves when it clears — and start() refuses a click that arrives anyway. boot(host) still returns its Frame synchronously.

Measured on this box, nine 128px layers: 206 ms in one cold call, against slices of 26.5, 21.9, 15.9, 15.7, 14.9, 12.1, 12.3, 12.2 and 2.2 ms with the page given a frame between each. main.test.ts asserts rows and order rather than milliseconds — nine frames, no renderer until the ninth, the bytes it was built from equal to materialTextures(), and no match before that; bundle.test.ts proves the same of the committed bundle by counting texImage3D. The engine's own identity test uses shaders of the two real shapes rather than either game's, because a test in src/engine/ may not import a game; the games' actual palettes are covered by those two comparisons against materialTextures() and rangeAtlas().

Bundles were rebuilt: app 72,048 raw / 33,457 gzipped and range 27,433 / 11,745, both inside today's ceilings, with the numbers and what they bought written into src/games.ts. bun test passes, 577 tests. What I did not do: move boot's shape into the engine (#75), and I did not make the slice size adaptive — it is one layer, flat, because a clock inside the painter is exactly what the issue rules out.

🤖 Generated with Claude Code

Closes #99

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs