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

forked from trevato/cs · proposes · 4 behind

The trainer becomes engine: PPO through the door, proved on an environment that is not a game #126

Merged opened this

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

Merged

`src/rl/ppo.ts` is now `src/engine/learn.ts`, moved and not improved: the only lines that differ are the import — `./net.ts` rather than the door, because a package does not go through its own door — and comments; `git diff -M` shows 19 added lines and 4 removed, all of them prose. GAE, the clipped surrogate, Adam and the hand-written backward pass are byte for byte what they were, the gradient ch…

shipped production is serving it deploy ae07c2d

opened this

src/rl/ppo.ts is now src/engine/learn.ts, moved and not improved: the only lines that differ are the import — ./net.ts rather than the door, because a package does not go through its own door — and comments; git diff -M shows 19 added lines and 4 removed, all of them prose. GAE, the clipped surrogate, Adam and the hand-written backward pass are byte for byte what they were, the gradient check against finite differences moved with them and passes as written, and bun run scripts/train.ts --bench reports 12–13k steps/s across five runs against 13k before the move (the bench steps the arena and the network, not the trainer, so the two numbers are the same number).

The issue's real half is the new test. src/engine/learn.test.ts trains a two-armed bandit written out inside the test — the observation is which arm pays this pull, one-hot, so a policy that ignores what it sees is stuck at a half however long it trains — reaching the trainer through index.ts and nothing else: no map, no tick, no weapon anywhere in it. It asserts the mean reward of the rollout the updates were collected from rises from ~0.5 to past 0.9 within 40 updates, and that the last five rollouts average past the same floor; under its fixed seed it takes about 40 ms and reaches 1.0 by the eighth update, so the floor has room rather than being tuned to squeak past. A second test pins that the seed reproduces the run exactly and that another seed learns too.

src/rl/ keeps arena.ts, which is CS. scripts/train.ts now reaches the network and the trainer through one import of the door; src/engine/index.ts, the pinned SURFACE in boundary.test.ts and GUIDE.md gain the fourteen names as a group, with the guide saying plainly that a game ships weights and not a trainer and that no browser loads this. That last claim is a test rather than an inference: "no committed bundle carries the trainer" reads all three committed bundles for maxGradNorm, valueCoef, lastValues, clipFraction and policyLoss and finds none of them. No bundle grew — app 72,048 raw / 33,482 gzipped, range 28,978 / 12,320, example 8,754 / 4,227: the raw sizes are identical to the byte and the gzipped ones move by a byte or two where the minifier handed out different short names over a module graph that changed shape, the same thing that happened for #106.

One thing to look at that is not the issue: main arrived with two range bundles committed, range.79e3fb073e.js from #121 and range.f0416248f9.js from #125, neither sweeping the other, and eight tests failed on it — every reader of "exactly one range.<hash>.js". The first commit here is a plain bun run build that resolves them into the one the sources produce, both changes at once, with no source touched. bun test is 596 pass / 0 fail, and the README's three places that named src/rl/ppo.ts now name src/engine/learn.ts.

🤖 Generated with Claude Code

Closes #107

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs