9.5 KiB
Contributing
A simulated fly brain that plays Game Boy games on a 24/7 stream. Start with
docs/architecture-tour.md: it walks every layer once, says why each is built the way it is, and
points at the code. Then docs/stream-mvp-plan.md for what has been decided and what state it is in.
There is no game in this repository. ROM-POLICY.md explains what that means and how to run
everything without one — which is almost all of it.
Branches and worktrees
Work happens on a branch, in its own git worktree, never on main directly.
git worktree add -b feat/my-change .claude/worktrees/feat-my-change main
cd .claude/worktrees/feat-my-change
.claude/worktrees/ is gitignored, so the checkouts never enter history. One branch per piece of
work, named for what it does (feat/, fix/, docs/, chore/, publish/). Several can be in
flight at once because each has its own tree; that is the point of using worktrees rather than
switching branches in place.
Merge into main with --no-ff, so a branch stays visible as a unit in the history:
git merge --no-ff feat/my-change
git worktree remove .claude/worktrees/feat-my-change
Never rewrite published history on main.
The suites, before every merge
All four, green, on the branch. Not a subset.
npm ci
npm test # TypeScript: brain, feed, stage units, bridge
npm run typecheck # tsc across every workspace
cd services/flysim && cargo test --workspace # Rust: core, gb, service; ROM-gated tests skip
bash infra/tests/lint.sh # shellcheck + systemd-analyze over infra/
Plus the stage's browser suite when anything visual changed:
cd apps/stage && npm run test:e2e # Playwright: screenshot baselines per fixture and tab,
# text-size lint, phone-downscale legibility, structure
npm run mockups # regenerate the review PNGs
Notes that save time:
- The ROM-gated Rust tests skip with a printed line when
FLY_ROMis unset. That is a normal green run, not a hole. SeeROM-POLICY.md. - The dataset tests read
data/fafb-v783, which is committed; they need no download. - The Rust golden tests compare against
services/flysim/golden/*.flygoldat 0 ulp. If one fails after a change to the Rust side, the Rust side is wrong — see the oracle rule below. Goldens are regenerated from the TypeScript withpackages/brain/tools/golden.ts, never edited by hand. - Screens are reviewed as PNGs under
apps/stage/mockups/, not as prose descriptions. On-screen copy is terse.
Commit messages
Plain and factual. A short imperative subject with a scope prefix, a body when the change needs one, and nothing else.
feat(decoder): add blocked-direction cooldown
Raises the habituation of a channel the sim loop has watched produce no
movement for a whole hold. Does not name a position or an alternative.
No attribution trailers of any kind. No Co-Authored-By, no generated-with lines, no tool or
model credits, no session links. This applies to every commit, including ones written by an agent.
No secrets in a commit, ever — no stream keys, tokens, passwords or credentials. Nothing from a cartridge. Check the diff before committing, not after.
Licences and file headers
The repository is not under a single licence. LICENSES.md at the root says which paths fall under
which terms; the short version is Apache-2.0 for the code, CC BY 4.0 for the documentation,
on-screen copy and stage assets, and CC BY-NC 4.0 — unchanged and not ours to relicense — for the
FlyWire connectome artifacts in data/fafb-v783/.
- No per-file licence headers are required. Do not add them to new files, and do not add them to
existing ones.
LICENSE,LICENSES.mdandNOTICEat the root carry the terms for the whole tree; a header on every file is noise that then has to be kept accurate. NOTICEcarries the attributions. If you add third-party material — a vendored source, a font, a dataset, anything you did not write — put its attribution inNOTICEand its row inLICENSES.mdin the same branch as the material, with its own licence text alongside it in the tree. Material whose upstream licence you cannot establish does not go in; if it does go in,NOTICEsays so in plain words rather than guessing.- Contributions are accepted under Apache-2.0. By submitting a change you license it under the Apache License 2.0, per section 5 of that licence. There is no separate CLA and none is planned.
- Anything derived from
data/fafb-v783/is CC BY-NC 4.0, like the data.services/flysim/golden/real.flygoldis the one such artifact in the tree today. If a change adds another — a recorded run, a prerendered image, a checked-in trace — say so in the commit and add it to section 3 ofLICENSES.md. The four.flyfeedfixtures are generated by the fake simulator and are deliberately not in that category; keep it that way.
The two binding contracts
docs/feed-protocol.md— the WebSocket snapshot format the service publishes and the page reads.docs/control-api.md— the loopback HTTP API the bridge calls.
These are binding. Where any other document, design note or comment disagrees with them, the contracts win and the other document is the thing that is wrong. Changing a contract is its own change, made deliberately, with both sides of it updated in the same branch and the reason written down. Both have a Rust implementation and a TypeScript implementation and a shared fixture that each must pass; a change that only satisfies one side is not done.
One property of the control API is load-bearing: there is no button endpoint. Nothing outside the simulation can press a button, and a test asserts the route table contains no such route. Do not add one.
The oracle rule
packages/brain is the reference implementation. Its semantics define what the neural core does:
the LIF kernel, plasticity, the decoder, the agent loop, the checkpoint envelope.
When the Rust port, the service, the page or anything else disagrees with it, fix the other side.
Never adjust packages/brain to make another implementation's output match — that converts a bug
into the specification. The verbatim prototype modules under packages/brain/tests/legacy/ and the
bit-exact oracle tests against them exist to make this rule enforceable, and so do the Rust goldens.
The default-config version strings lif-1ms-f64-v2 and fly-kc-mbon-rstdp-v2 stay as they are.
Checkpoints are keyed on them; changing one invalidates saved state on the release box.
If the reference itself is genuinely wrong, that is a deliberate change to the reference with a new version string, regenerated goldens, and the reasoning recorded — not a quiet edit.
The honesty rule
Nothing on the stream is scripted. This is not a style preference; it is the point of the project, and it constrains contributions.
- The buttons are always the fly's. Nothing outside the simulation chooses, biases, defaults or times a button press. There is no fallback press, no scripted objective, no nudge on a timeout. A scene the fly ignores waits.
- Rewards are read out of memory after the fact. A reward rule may observe what the game state became and stimulate the modulatory pathway. It may not tell the fly where to go or what to press. Every value is positive by design; there are no penalties.
- The readout is fixed, not learned, and it knows nothing about maps, doors or goals.
- Failure is shown. Stalls, rollbacks, unsupported cartridges and lag appear on screen with the reason. They are not hidden and not smoothed over.
- What is real and what is scaffolding is documented.
docs/limitations.mdis the honest list — the reward modulator is synthetic, the retina is not fly optics, learning has not been shown to improve play.docs/rewards-learning.mdhas its own "Honesty" section stating exactly what a reward can and cannot move. If your change alters where that line falls, update those pages in the same branch and say so plainly. If a claim has not been measured, write that it has not been measured.
A change that makes the demo look better by making it less true will be rejected, however small.
Where the documents are
docs/architecture-tour.md— read first; the whole system, layer by layer, with the reasons.docs/feed-protocol.md,docs/control-api.md— the binding contracts.docs/stream-mvp-plan.md— decisions and current status.docs/model.md,docs/plasticity.md,docs/readout.md,docs/rewards-learning.md,docs/dataset-format.md,docs/verification.md— the neural core, what it learns, how it decides, what it is paid for, the data layout, and how all of it is checked.docs/limitations.md— what this is not.docs/design/— per-feature design notes (flysim.md,stage-bridge.md,fly-avatar.md,animation.md,ladder.md,macros.md,platformer.md,room-escape.md,gpu.md,infra.md, and others). Designs, not contracts: where one differs from a contract, the contract wins.infra/docs/— the runbook, provisioning records, measurements and spike write-ups for the machines that actually run it.docs/publish/— notes prepared for publication, including how the licence decision was reached.LICENSE,LICENSES.md,NOTICE— the terms, the path-by-path split, and the attributions.ROM-POLICY.md— no game here, and how to run everything anyway.