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

forked from trevato/cs · proposes · 4 behind

The brain becomes engine: the policy network moves behind the door, the game keeps what it observes #114

Merged opened this

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

Merged

`src/client/policy.ts` is `src/engine/net.ts`. It moves unchanged in behaviour — all eighteen names (`Shape`, `Policy`, `Activations`, `Packed`, `logitCount`, `paramCount`, `tensorsOf`, `zeroPolicy`, `createPolicy`, `copyPolicy`, `newActivations`, `forward`, `headOffset`, `softmax`, `sample`, `greedy`, `pack`, `unpack`) keep their spelling; the only edits are two docstrings that had borrowed the g…

shipped production is serving it deploy ae07c2d

opened this

src/client/policy.ts is src/engine/net.ts. It moves unchanged in behaviour — all eighteen names (Shape, Policy, Activations, Packed, logitCount, paramCount, tensorsOf, zeroPolicy, createPolicy, copyPolicy, newActivations, forward, headOffset, softmax, sample, greedy, pack, unpack) keep their spelling; the only edits are two docstrings that had borrowed the game's vocabulary ("the policy a bot thinks with", "the bots' own aim wobble"). src/client/bots.ts, src/client/weights.ts, src/rl/ppo.ts, src/rl/ppo.test.ts, src/rl/learned.test.ts and scripts/train.ts import the door, and so does the import type { Packed } line that weightsModule() in scripts/train.ts writes into the generated file.

Two things went differently from the issue and are worth a look:

  • The test could not move whole. boundary.test.ts forbids any file under src/engine/ — a .test.ts included — from importing src/client/, and policy.test.ts imported action.ts and weights.ts. So it split along the boundary it now has to obey: src/engine/net.test.ts is the arithmetic, with a local HEADS literal in place of the shooter's, and the three tests that were about this game's bins and this game's weights stayed behind as src/client/action.test.ts and src/client/weights.test.ts. No assertion was weakened, and none was dropped.
  • SURFACE stays sorted. expect(offered.sort()).toEqual(SURFACE) means the pinned list is the sorted array, so eighteen names cannot land in it as one block. The group is written out as a block in the docstring above the list instead, and the list took the names in sorted order — a pattern the next group can follow rather than a one-off.

Numbers. The seeded duel in src/rl/learned.test.ts is identical to main: 31 wins for the policy, 21 for the scripted baseline, over the same forty seeds. unpack(PACKED) digests to 4d908fcfd1198e8326d2711c95761f08770e681ee817b42af95f38f2c3b502d1 — sha256 over every tensor in tensorsOf order — on main and here, and src/client/weights.test.ts now holds it there. bun run scripts/train.ts --bench: 0.97–1.03 s before, 0.99–1.02 s after, both 13k steps/s. bun test: 546 pass, 0 fail.

Bundles, both rebuilt and committed: app 71,646 raw / 33,273 gzipped (ceiling 75,400 / 34,900) — the same raw byte count as main; range 27,268 / 11,674 (ceiling 29,400 / 12,700), up 34 raw and 18 gzipped, which a diff shows is entirely the minifier handing out different short names over a changed module graph. Neither ceiling moved. Rather than leave "the range ships none of it" to the ceiling, each game's bundle test now reads the committed file: the range's has no Math.tanh, atob or btoa, the app's has the first two, and the app's lack of btoa is pinned too — pack only ever runs in the trainer, so a page that shipped the quantiser would be shipping a trainer it cannot use.

On the generated file: I hand-edited the one import line in src/client/weights.ts and did not retrain. The emitter in scripts/train.ts changed with it, and I verified the two agree byte for byte up to export const CARD, so the next real training run rewrites that line to exactly what is committed. No weight byte was touched.

Left alone on purpose: action.ts, observe.ts and OBS_SIZE stayed with the shooter, and PPO stayed in src/rl/ — that is its own issue. GUIDE.md gains a ### net section and a seam-table row; it is prose with no code block, since example.ts has no agent and the block rule requires every block to be a slice of it. Int32Array joined NOT_THE_ENGINES in guide.test.ts as platform vocabulary. The README's module lists were stale the moment the file moved, so they were corrected too.

🤖 Generated with Claude Code

Closes #106

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs