Imported from Y-R-U/Y-R-U.github.io (
gms/3d/tinpot/AGENTS.md). Install upstream withnpx skills add Y-R-U/Y-R-U.github.io --skill tinpot. Copyright stays with the author.
TINPOT — standing rules for every builder
docs/ART_NOTES.md is mandatory reading before any work in render/.
Read in this order before touching anything: docs/BRIEF.md → docs/ARCHITECTURE.md →
docs/PLAN.md → docs/STATE.md. Then start at the first unticked box in PLAN.md.
This project is built by a relay of agents, several of which will be cut off mid-task by a usage limit without warning. Work accordingly:
- Leave the tree runnable at every step. Never end a work chunk with the page broken.
- Update
docs/STATE.mdas you go, not at the end. Assume you will be killed mid-sentence. The next agent's only inheritance is that file plus the ticked boxes inPLAN.md. - Tick a box only when you have run it and looked at it. An untrue tick costs the next agent more than an unticked one.
Non-negotiable
- Write nothing outside
gms/3d/tinpot/. - No git commands. No add, commit, stash, checkout, rebase. Other sessions have live uncommitted work elsewhere in this repo.
- Never import
threefrom a CDN. Use the vendored importmap indocs/ARCHITECTURE.mdverbatim. A CDN import hangs the game silently and has cost days here before. - No build step, no npm, no dependencies.
js/core/*.mjsstays pure — nothree, no DOM — so the Node harness runs real game code.- Don't add the
projects.jsentry. That is Aaron's session, at ship time. - Don't touch
audio/music/— the soundtrack is already placed and named.
How to know it works
The repo root is already served at http://127.0.0.1:8888 — do not restart that server.
The game is http://127.0.0.1:8888/gms/3d/tinpot/.
node tools/sim.mjs
node tools/campaign.mjs
~/.claude/bin/cdp start --port 9223 -- --use-angle=metal
node tools/browser.mjs teach
node tools/release.mjs
--use-angle=metal is not optional. The cdp launcher hardcodes swiftshader; without the
override this game software-renders at ~9 fps and timing-based gates go flaky.
The suite is a POSITIONAL argument — node tools/browser.mjs v1. Passing --suite v1
silently runs only the generic boot scenario and prints PASS. That false green has already
fooled one session. The suites are shell m2 m3 m4 m5 m6 m7 m8 art v1 v3 v4 v6 teach.
Keep the cdp start and the harness run in one shell execution, separated by a newline.
Disable cache in the CDP driver before navigating — a ?v= query string does not bust a
stale ES module, and stale modules have hidden agents' own edits in this repo more than once.
tools/ in gms/3d/sunwake/ has a working cdp.mjs/browser.mjs to copy from.
Lessons this repo has already paid for
- A green test suite is not evidence the screen looks right. Six times in one project a numeric suite passed while an obvious visual bug filled every frame. Take the screenshot and look at it.
- Falsify your own gate. Run it against a build where the bug still exists. A check never proven to fail is not a check.
- Read detail lines, not pass counts. A pathing test that "passes" because a unit was force-teleported past a gate hid a third of a map being unreachable.
- Isolate before tuning. Force a suspect term to a constant; if the output is identical the experiment failed, not the hypothesis.
Versioning
Milestone headings in PLAN.md and STATE.md are numbered 0.01 (the shipped slice, M0–M9),
0.02 (the playtest pass) and 0.03 (the first human playtest). We are at very early
concept stage on purpose. The numbering is documentation only — suite names, fixture names,
function names and evidence filenames keep their old letters and must not be renamed to match.
The live build number is one string: VERSION in js/version.mjs. It prints on the title screen
as PATTERN 0.03 and is exposed as window.tinpot.version, which tools/release.mjs asserts —
so a deployed build can be identified without a screenshot. Bump it there and nowhere else.
Tone
It is a silly game. Where a choice is between realistic and funny, it is funny. The blood is the colour of jam, the explosions are too big, and the debrief is delighted about the casualties.
Creative freedom
The plan fixes the shape of the game — the controls, the UI positions, the tone, the milestone order. Everything about how it looks and feels is yours, and you are expected to push it.
If you have an idea that would make a frame look genuinely great — a better light rig, a sky that sells the hour of day, heat shimmer over a burning treeline, dust kicked up by boots, canopy shadow dappling the grass corridor, a colour grade, a tracer that actually reads, a death that is funnier — do it, without asking. Same for small bits of character: a soldier who trips, a salute, a helmet that pings off and rolls. Overshoot the brief on polish.
Two limits on that freedom, and only two: it must still run at 60fps on a phone, and it must not
move a control off the edge of the screen or clutter the middle of the battlefield. Write what
you added, and why, into the Decisions log in docs/STATE.md so the next agent keeps it.
This has already gone wrong once — read it
On 2026-09-22 all twelve boxes of the M1.5 art pass were ticked in a single pass, and the frame shot afterwards measured 0.69% different from the frame the critique was about. Nothing had been done. The ticks were the only evidence, and they were false.
So: tools/artgate.py is now the gate, not your own judgement. It is pure stdlib, it prints
JSON, and it already fails the frame you are replacing on five of its six checks. Run it, paste
its output into docs/STATE.md, and only then tick the box.
Do not edit its thresholds, and do not edit it to pass. They were chosen by running it
against the bad frame. If you genuinely believe a threshold is wrong, write the argument in
docs/STATE.md and leave the box unticked for a human.
The general rule, which applies well beyond this one gate: a check that has never been proven to fail is not a check. Before you trust any gate you write, run it against a build where the bug still exists and watch it go red.
