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

forked from trevato/cs

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

shipped production is serving it opened this has it
opened this

src/client/policy.ts is 259 lines that name nothing about Counter-Strike: a Shape, a three-matrix MLP with categorical heads, a forward pass, softmax, sample, greedy and an int8 pack/unpack. It is the one module in src/client/ whose import list is empty — it depends on nothing, not even the engine — and it is still filed under the game, so src/rl/ppo.ts has to reach into the shooter to train, and a second game that wanted an agent would copy it. It is the largest generic subsystem still outside the door, and the direction is exactly that such a thing belongs inside it.

Done looks like

  • [ ] src/engine/net.ts is src/client/policy.ts moved, unchanged in behaviour: Shape, Policy, Activations, Packed, logitCount, paramCount, tensorsOf, zeroPolicy, createPolicy, copyPolicy, newActivations, forward, headOffset, softmax, sample, greedy, pack, unpack. Rename only where the concept renames; nothing about aiming, weapons or sides in it.
  • [ ] src/client/policy.test.ts moves with it as src/engine/net.test.ts and passes as written.
  • [ ] Every caller imports the door instead: src/client/bots.ts, src/client/weights.ts, src/rl/ppo.ts, src/rl/ppo.test.ts, src/rl/learned.test.ts, scripts/train.ts — and the import type { Packed } from "./policy.ts" line that weightsModule() in scripts/train.ts writes into the generated file. src/client/policy.ts is gone, not left as a re-export.
  • [ ] src/engine/index.ts gains the group with its one line of comment, and boundary.test.ts's pinned list gains the names in one block a reviewer can read. purity.test.ts picks the module up through the walk it already does — nothing in it may touch a global at import time, which today it does not.
  • [ ] GUIDE.md gains the paragraph naming every new door name, because guide.test.ts fails until it does: what a game brings (an observation array it fills, a head count, weights it trained) and what the engine does with it. A code block there has to be a slice of src/engine/example.ts, so prefer prose and leave the example alone.
  • [ ] Behaviour is pinned across the move rather than assumed: the seeded duel in src/rl/learned.test.ts produces the same outcome as on main, and a test asserts unpack(PACKED) over the committed weights is bit-identical to today. Put the digest in the pull.
  • [ ] Bundles: the shooter still ships the net — bots.ts runs it in the page — and the range still ships none of it, which its ceiling in src/games.ts proves. Both bundles rebuilt and committed, both under their ceilings, sizes measured in the pull.
  • [ ] bun test passes, and bun run scripts/train.ts --bench is no slower than today (both numbers in the pull).

Files

src/engine/net.ts (moved), src/engine/net.test.ts (moved), src/engine/index.ts, src/engine/boundary.test.ts, src/engine/GUIDE.md, src/client/policy.ts and src/client/policy.test.ts (deleted), src/client/bots.ts, src/client/weights.ts, src/rl/ppo.ts, src/rl/ppo.test.ts, src/rl/learned.test.ts, scripts/train.ts, src/games.ts (the measurement comment), static/*.

Risks

src/client/weights.ts is generated: change the emitter in scripts/train.ts, then either regenerate or edit the single import line by hand — say which in the pull, and never hand-edit the bytes. The trainer stays where it is; moving PPO too is its own issue, and doing both in one run is how this lands broken. Nothing about action.ts, observe.ts or OBS_SIZE moves: what a bot sees and what it may do are this game's, and the engine must not learn either. The door stays cheap to import — re-exports only, no work at import time.

Suggested: Torque — this is the numeric half of the repository, and the digest that has to survive the move.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs