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

forked from trevato/cs

The shipped weights stop riding in the entry bundle: 9.5 kB of the 44 kB a player downloads becomes its own cached asset #223

Merged opened this

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

Merged

The packed policy no longer rides in the entry bundle. It is `src/client/weights.json`, declared as the shooter's first `Asset` in `src/games.ts`, copied to `static/weights.<hash>.json` by `scripts/build.ts` exactly the way a bundle is, and named for the page in a `<link rel="preload" as="fetch" crossorigin id="weights">` the shell renders in the head — so the browser starts that download alongsid…

shipped production is serving it deploy ae07c2d

opened this

The packed policy no longer rides in the entry bundle. It is src/client/weights.json, declared as the shooter's first Asset in src/games.ts, copied to static/weights.<hash>.json by scripts/build.ts exactly the way a bundle is, and named for the page in a <link rel="preload" as="fetch" crossorigin id="weights"> the shell renders in the head — so the browser starts that download alongside the bundle and the client never spells out a path.

Measured, at bun run scripts/build.ts. app 97,956 → 88,990 raw and 44,073 → 35,220 gzipped; the weights asset is 11,120 raw / 8,251 gzipped; range 34,776 / 14,545 and example 9,735 / 4,618, both unchanged to the byte and both declaring assets: []. A cold visit is 100,110 raw / 43,471 gzipped across the two — 602 gzipped bytes less than before — and the next deploy of the game re-downloads only the bundle. The ceilings come down for the first time on that page: app 100,000/45,000 → 98,000/39,000, weights 12,300/9,100, each today's size plus about ten per cent. About 1,900 raw bytes came back as the reader, the two sentences and the load, which is why the saving is not the full 10,878.

The seam. src/client/packed.ts holds SHAPE (so makeBrain sizes activations before any weights exist) and parsePacked, which validates a document field by field and hands back its own constant shape — a truncated file, a proxy's HTML error page or a half-written deploy is null, not a bot thinking in zeros. src/client/bots.ts is handed a policy through usePolicy and policy() throws rather than falling back to the scripted fight. In a browser src/client/main.ts downloads and hands one in; in Bun src/client/shipped.ts reads the committed file through the same validator and createArena calls it, so the arena, the trainer and src/rl/learned.test.ts are unchanged in behaviour. src/client/weights.ts keeps CARD and nothing else, still rendered on the first paint.

Loudness. A declared asset missing from static/ throws in findAssets before Bun.serve binds, the same contract a missing bundle got in #198. In the page, a policy that does not arrive writes NO_POLICY into the menu's line and leaves Play disabled; on a slow connection the same line reads LOADING_POLICY once the textures are done, and Play turns on when the later of the two lands. start() refuses a click that arrives anyway.

After training, a person now runs bun run scripts/train.ts (which writes weights.json and weights.ts, and prints the next step), then bun run scripts/build.ts, and commits both sources and what static/ then holds. --policy names the JSON the way --out names the card.

Worth a look: scripts/build.ts, where a bundle and an asset became one concept that differs only in where its bytes come from; the load callback on Host in src/client/main.ts, which is a callback rather than a promise so the stub page answers on the test's clock the way it already decides when a frame happens; and src/client/stage.test-helper.ts, which can now serve the committed weights, lose them, corrupt them or hold them. bun test is 983 green, including that the committed bundle contains no packed literal, that the asset is byte-for-byte its source and exactly what parsePacked accepts, and that a round still plays with bots that shoot.

Not done: src/client/weights.ts keeps its name though it now holds only the card — it reads as the card for weights.json beside it, and renaming it would have churned eight import sites for no behaviour. I also did not touch #182.

🤖 Generated with Claude Code

Budget reached at $8.58; this is where it stopped.

Closes #214

commented

The packed policy no longer rides in the entry bundle. It is src/client/weights.json, declared as the shooter's first Asset in src/games.ts, copied to static/weights.<hash>.json by scripts/build.ts exactly the way a bundle is, and named for the page in a <link rel="preload" as="fetch" crossorigin id="weights"> the shell renders in the head — so the browser starts that download alongside the bundle and the client never spells out a path.

Measured after merging main, at bun run scripts/build.ts: app 98,617 → 89,037 raw and 44,304 → 35,268 gzipped, which is 9,580 raw and 9,036 gzipped gone. The weights asset is 11,120 raw / 8,251 gzipped; range 34,810 / 14,569 and example 21,115 / 9,189 are the same numbers #216 measured, to the byte, and both declare no assets. A cold visit is 100,157 raw / 43,519 gzipped across the two — 785 gzipped bytes less than before — and the next deploy of the game re-downloads only the bundle. The ceilings come down for the first time on that page: app 100,000/45,000 → 98,000/39,000, weights 12,300/9,100, each today's size plus about ten per cent. About 1,900 raw bytes came back as the reader, the two sentences and the load, which is why the saving is not the full 10,878.

The merge. main's #216 and this branch touch src/games.ts in the same two places. Both sides are kept: the log carries #216's paragraph and then #214's, re-measured on top of it; the example keeps its 23,500/10,200 ceiling and gains the assets: [] every entry now declares. static/ was rebuilt, so it holds one bundle per game again plus the weights.

The seam. src/client/packed.ts holds SHAPE (so makeBrain sizes activations before any weights exist) and parsePacked, which validates a document field by field and hands back its own constant shape — a truncated file, a proxy's HTML error page or a half-written deploy is null, not a bot thinking in zeros. src/client/bots.ts is handed a policy through usePolicy and policy() throws rather than falling back to the scripted fight. In a browser src/client/main.ts downloads and hands one in; in Bun src/client/shipped.ts reads the committed file through the same validator and createArena calls it, so the arena, the trainer and src/rl/learned.test.ts are unchanged in behaviour. src/client/weights.ts keeps CARD and nothing else, still rendered on the first paint.

Loudness. A declared asset missing from static/ throws in findAssets before Bun.serve binds, the same contract a missing bundle got in #198. In the page, a policy that does not arrive writes NO_POLICY into the menu's line and leaves Play disabled; on a slow connection the same line reads LOADING_POLICY once the textures are done, and Play turns on when the later of the two lands. start() refuses a click that arrives anyway.

After training, a person now runs bun run scripts/train.ts (which writes weights.json and weights.ts, and prints the next step), then bun run scripts/build.ts, and commits both sources and what static/ then holds. --policy names the JSON the way --out names the card.

Worth a look: scripts/build.ts, where a bundle and an asset became one concept that differs only in where its bytes come from; the load callback on Host in src/client/main.ts, which is a callback rather than a promise so the stub page answers on the test's clock the way it already decides when a frame happens; and src/client/stage.test-helper.ts, which can now serve the committed weights, lose them, corrupt them or hold them. bun run test is 989 green, including that the committed bundle contains no packed literal, that the asset is byte-for-byte its source and exactly what parsePacked accepts, and that a round still plays with bots that shoot.

Not done: src/client/weights.ts keeps its name though it now holds only the card — it reads as the card for weights.json beside it, and renaming it would have churned eight import sites for no behaviour. I also did not touch #182.

🤖 Generated with Claude Code

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs