# Deepgrain Foundry: instructions for coding agents Deepgrain Foundry is a 2D falling-sand factory game in the style of Sandustry, set in a Terraria-like side-view world. Every grain of sand, ore, water and fire is simulated. The player flies a small bot, digs with a resonance drill, vacuums up loose material, builds furnaces, belts, tubes and machines from blocks, casts metal in molds and sells goods at a trade dock to pay off a land claim. It runs in the browser as one self-contained HTML page, built from small JavaScript files with no dependencies. The owner (the user) directs the work and is the real playtester. You build, test and report. ## Start of every session 1. Run `kandown work`. It prints the user's task board and the rules for working it (see "Working from the task board" below). 2. Read `docs/PROJECT_MAP.md` before searching the code. It says where everything lives. Its index lists every top-level function and variable by file. 3. Before changing how the game plays, read the relevant part of `docs/DESIGN.md`. Many choices were made deliberately and are recorded there with the reason. 4. Look at the top of `docs/agent-log.md` for what was done recently. ## Honesty and accuracy These rules matter more than speed. - **Do not guess or assume.** If you have not checked something, say so. Keep "I tested this and it works" separate from "this should work". - **Prove it with the game.** Run the tests. For anything visible, take a screenshot and look at it before saying it looks right (see "Testing"). A passing test only covers what it checks. - **Never invent names.** Look a function or variable up in the project map's index, or search for it (`rg name src`), before you use it or talk about it. - **When you are not confident, check more than one source.** Use the web for anything outside this repository: browser APIs, Playwright, WebGL, real-world materials and temperatures, physics, how other games do something. Confirm important facts with at least two independent sources, and give the links. - **Report problems plainly.** Say when a test fails, when something is only partly done, or when you could not reproduce a problem. Never weaken or delete a test just to make it pass. If a test's expectation is outdated because the design changed on purpose, update it and say why. - **Ask when it matters.** If a request is unclear and getting it wrong would waste a lot of work, ask first. If the choice is small or easy to undo, take the sensible reading, say which one you took, and carry on. ## Working from the task board The user's to-do list is a kandown board: one Markdown file per task in `tasks/`. **Read `docs/kandown.md` before using it**: it says how the board works here, which commands are safe (never run `kandown --help`: it runs the command), and what a task file looks like. The rules for working the board are in `.kandown/kandown_work.md`, and `kandown work` prints them. In short: - Work on tasks only when asked. Todo tasks are built; Backlog tasks tagged `discussion` get options with trade-offs and a recommendation, and nothing is built until the user decides. - Never reword the user's words in a task's `## Context`. Add your notes as subtasks with `report:` lines, or in the `report` field. - When a task is finished, move it to **Review** with a `report` saying what changed and how to check it in the game. Only the user moves tasks to Done. - **Log your work.** Add an entry at the top of `docs/agent-log.md` with the date, the task, what changed, the files touched, the tests run, and anything left open. - **Record decisions.** When the user decides something about the design, add it to the decisions list in `docs/DESIGN.md` with the date and the reason they gave. ## Commands ```sh python3 build.py # build dist/index.html from src/ (takes a second) python3 -m http.server -d dist # play at http://localhost:8000 (or open dist/index.html) python3 test/run_all.py # all headless test suites, in parallel (about a minute) python3 test/run_all.py pipes # only suites whose name contains "pipes" python3 test/run_all.py --browser # also the browser tests (rebuilds first; about 3 more minutes) node test/t_pipes.js # one suite python3 tools/shot.py shots/x.png --setup tools/scenes/clear-sky.js --js "..." --ticks 60 python3 tools/where.py 7614 # which source line is line 7614 of dist/index.html python3 tools/map.py # regenerate the index in docs/PROJECT_MAP.md ``` You need Node (tested with 22) and Python 3. Playwright for Python and its Chromium live in `.venv/` in the repo (not committed); set up as in README.md → Develop. The browser scripts and `tools/shot.py` switch to `.venv` by themselves (`tools/use_venv.py`), so the commands above work with plain `python3`. ## How the code is put together - `build.py` joins the files listed in `src/manifest.json`, in order, into one classic script inside `src/shell.html`. The output is `dist/index.html`. There are no modules and no import/export: every file shares one global scope. - **Order matters only for code that runs when the page loads.** A function can call a function from any file. But a top-level `const`/`let` must come before top-level code that uses it. New files go into the manifest in the right place: simulation first, then render, then UI. - `'use strict'` at the top of `src/sim/base.js` applies to the whole bundle. - **`src/sim/` must never touch the DOM** (no `document`, `window`, canvas or `localStorage`). The headless tests load only these files, and keeping the simulation pure keeps a later port to Rust or WebAssembly possible. - **Keep files small and on one topic.** Aim for under about 400 lines. Split a file that grows past that along a clear seam, add the new file to the manifest, and rebuild. Then run `python3 tools/map.py` and update the hand-written part of the project map. - **Errors point at real files.** In Node tests, stack traces name the source file and line. In the browser they point into `dist/index.html`; use `tools/where.py` to find the source line (`tools/shot.py` does this for you). ## Rules for the simulation - **Performance.** The simulation runs 60 ticks a second over millions of cells. - Use the existing typed arrays. - Do not allocate objects or arrays inside per-cell loops. - Only chunks that are awake (32×32 grains) are simulated. After changing cells directly, wake them; `setCell` already does. - Measure before and after anything in a hot path (`tools/perf.py`, `tools/experiments/factory_cost.js`). - **Determinism.** Randomness in `src/sim/` comes only from `rnd()` (seeded) or `mulberry32`. Never use `Math.random` there. The headless tests rely on this: the same code gives the same output, line for line. - **Save compatibility.** The user has saved worlds they care about. Material ids, block codes (`BP_*`), part ids (`P_*`) and machine type names are stored in saves. - Never renumber or reuse them. Add new ones at the end (raise `NMAT` for materials). - New saved fields need a default when an older save lacks them. Migrations go in `deserializeWorld` (`src/sim/save.js`). - Only raise `SAVE_VERSION` if old saves truly cannot be read any more, and tell the user first. - **Undo.** Every building action the player takes must be undoable. Wrap it in `undoRecord(label, fn)`. The functions that place and remove things record their own steps with `undoNote`. The bot building ghosts is never an undo step of its own. ## Checklists **A new material.** It needs: - an id constant in `src/sim/materials.js` (at the end, before `NMAT`, and raise `NMAT`); - a `def(...)` entry with a plain description; - its price in `PRICE` (`src/sim/economy.js`) if it can be sold; - its place in `BAG_ORDER` (`src/ui/state.js`) if it can go in the bag; - its place in `MARKET_GROUPS` (`src/ui/notebook.js`) if it is sold; - any special tables it belongs to: `CASTMETAL`, `SOLIDMETAL`, `METAL_TIER`, `BAR_OF`, `WALL_OF`/`WALL_LIST`, `REFINE`, `HOTLIQ`, `DROSS`. Check its colour in both looks. **A new buildable piece.** It needs: - an entry in `PIECES` (`src/sim/pieces.js`) with cost and description; - a block code or machine footprint; - placing and removing that support undo and Ctrl-replace; - a tile in `BUILD_CATS` and an icon in `PIECE_ICONS` (`src/ui/build-menu.js`); - hover info (`hoverInfo` in `src/ui/info.js`); - drawing in `src/render/` (pixel look, and a smooth-look class if it needs one); - saving and loading of any state it keeps; - a line in the controls help (`controlsHtml` in `src/ui/notebook.js`) if it adds controls; - a headless test. **Text the player reads** (descriptions, toasts, hover info, help): - plain words and short sentences; - what it does and how to use it; - °C for temperatures, "grains" for cells, "blocks" for the 4×4 build grid; - no programmer words. Match the existing descriptions in `PIECES` and the material `def`s. ## Testing `docs/TESTING.md` has the details: the harness, how to write a suite, the browser tests, screenshots and the pitfalls. The essentials: - Every change to the simulation gets a headless check in a `test/t_*.js` suite. Write the check first when fixing a bug, and see it fail. - **Test in the pixel look** (the default in the tools). Use the smooth look only when checking graphics, and then check both looks. - Look at your screenshots. Open the PNG and check it shows what you meant, especially after changing anything drawn. - Before you say you are finished, run `python3 test/run_all.py`. After changing anything in `src/ui/` or `src/render/`, run `python3 test/run_all.py --browser` too. - **For a refactor** that should not change behaviour, save the suites' output before and after and diff them. The headless suites are deterministic, so any difference is a real change (lines with timings in ms excepted). ## Git - Small commits, one change each, with a message that says what changed for the player or developer. Run the tests first. - `dist/` and `shots/` are build output and are not committed. - Never commit secrets: tokens, passwords, or keys. - Work happens one agent at a time. If running several agents side by side is set up later, this section will say how they coordinate. This project uses Kandown. Before task work, run `kandown work` and follow its output.