# HARD YARDS — rig it, then survive the night *(repo/product history: the project was codenamed SHADES through Sprint ~8; the shipped game is HARD YARDS. Internal docs and the gitea repo name still say shades — that's the same thing, not a second game.)* Geometry/tower-defense: you're a small person rigging shade sails against Australian storms. three.js r175 (vendored, no build step) + a zero-dep Python server. Everything the repo needs is in the repo. ## Run it ``` python3 server.py --port 8801 # any free port # game: http://localhost:8801/web/world/index.html # selftest: http://localhost:8801/web/world/selftest.html (must be all green) # 2D proto: http://localhost:8801/prototype/index.html (the original thesis) ``` No pip, no npm, no build. Python 3.8+ and a browser. ## Read in this order 1. `DESIGN.md` — the design canon (what the game is) 2. `PLAN3D.md` — the build canon (stack, contracts, lane model) · `docs/MANUAL.md` — the HOW: tool map, asset/site cookbooks, earned gotchas · `ROADMAP.md` — the growth canon (the arcs from the week to the business) 3. `THREADS.md` — the append-only lane log. **The project's memory.** Read the last few `[I]` integrator entries to know the current state. 4. `SPRINT15.md` — the current sprint (highest-numbered *fired* sprint wins: SPRINT16 is pre-speced TEETH and fires only when 15 integrates — its own header says so) 5. `LANE_PROMPTS.md` — copy-paste prompts per lane, per sprint (bottom = newest) ## The lane model (how this repo is built) Five parallel Opus agents ("lanes"), each in its OWN clone, on its own branch: | lane | owns | |---|---| | A | main.js, world.js, camera, hud, week, sites, server — shell & integration, merge shepherd | | B | sail.js, rigging — cloth sim, economy, balance.test pen | | C | weather, skyfx, debris, storm/site wind data | | D | player, interact, ladder, broom — and the designated playtester | | E | Blender asset factory (tools/blender), models/, textures | Hard rules (they exist because each was earned — see THREADS): - **One clone per lane.** Never two agents in one working tree. - Rebase onto latest `main` before starting; small commits; push your branch. - File ownership is the merge strategy; cross-lane needs go in THREADS.md. - Selftest green after every landing. Skips must be real. An assert that cannot fail is decoration. - All sim code is deterministic `(dt, t)` — no Date.now, no rAF. - Measure, don't reason: the repo's biggest bugs were all "a number gathered from the wrong harness". When two harnesses disagree, find the variable. ## Setup on a new machine Gitea's SSH runs over tailscale (the public host is Cloudflare-proxied, no direct SSH). Add to `~/.ssh/config`: ``` Host gitea.partly.party HostName 100.71.119.27 Port 222 User git ``` Then: ``` git clone git@gitea.partly.party:monster/hardyards.git ~/Documents/shades for L in A B C D E; do git clone git@gitea.partly.party:monster/hardyards.git ~/Documents/shades-lane$L git -C ~/Documents/shades-lane$L checkout -B lane/$(echo $L | tr 'A-Z' 'a-z') origin/lane/$(echo $L | tr 'A-Z' 'a-z') done ``` `~/Documents/shades` is the integration checkout (main; where merges and sprint docs happen). The five `shades-lane*` clones are where lane agents run. Per clone, once: `cp .claude/launch.example.json .claude/launch.json` and pick a port of your own (integration 8823, lanes A–E 8824–8828). The real launch.json is untracked — six checkouts on one laptop can't share one port. ## Sprint cycle 1. Fire lanes with the newest prompts at the bottom of `LANE_PROMPTS.md` (Lane A usually first if the sprint gives it rulings/blocking items). 2. Lanes work, push their branches, log in THREADS.md. 3. Integrate in the main checkout: merge `origin/lane/{e,c,b,d}` (THREADS.md conflicts resolve keep-both-chronological; real code conflicts get read), run selftest, play the result, write `SPRINT(n+1).md` + prompts, push main AND fast-forward all five `lane/*` branches to main.