A 2.5D top-down game about a mother pushing a stroller through a city, trying to get her
baby to sleep. Play it in a browser.
- Engine: Godot 4.7
- Genre: Roguelike / route-planning
- Core loop: Walk a route → keep the baby calm → fill the sleepiness meter → return home.
The city is fixed for a whole run, so the map is knowledge you earn and keep. The noise in
it is not.
Watch the gameplay trailer on YouTube.
Download it in full quality (MP4).
https://github.com/user-attachments/assets/778c4d35-5c7c-4d10-92aa-fccd4c434c10
Spoilers: everything under docs/ describes the game's full arc, including things a
player should meet for the first time in play. This README deliberately does not.
Documentation
| Doc | Contents |
|---|
| CLAUDE.md | How to work on this repo — an index; the rules are in .claude/skills/. Codex reads it too, through .codex/config.toml |
| docs/DECISIONS.md | Where the history is — one file per decision under docs/decisions/, found with tools/decisions.sh |
| docs/DESIGN.md | Pillars, core loop, win/lose conditions |
| docs/MECHANICS.md | Meters, movement, tuning constants |
| docs/CITY.md | City generation, tile types, calm zones |
| docs/EVENTS.md | Event catalogue, telegraphing, scheduling |
| docs/GRAPHICS.md | Graphics assets, current runtime uses and prepared milestone parts |
| docs/NARRATIVE.md | Act structure, side content, endings — spoilers |
| docs/ARCHITECTURE.md | Code layout, autoloads, signals |
| docs/TELEMETRY.md | What a run writes down, and how to read it |
| docs/TODO.md | The queue: open work only; each entry is a folder under docs/todo/ whose README.md opens with its priority band, each item a file, and tools/queue.sh prints the order |
| docs/REVIEW.md | What waits on a person; each thing to try is a file under docs/review/ |
docs/playtests/ | One file per playtest, a player's own words on a date. Primary sources, never rewritten |
Running
./tools/run.sh # play
./tools/run.sh --seed 12345 # a specific city
./tools/run.sh --day 9 --overview # look at a later act from above
godot is not on PATH on macOS — the binary lives inside the app bundle, so
tools/run.sh finds it for you. Override with GODOT=/path/to/Godot tools/run.sh.
Or open the project folder in Godot 4.7 directly.
Controls
| Input | Action |
|---|
| Arrow keys / WASD | Walk |
| Hold Shift | Run (raises excitement) |
| Space | Begin, on the title screen; continue, on the between-days screen and from the pause |
| Esc | Pause, and carry on again |
| R | Start the run again — from the pause screen |
| Q | Quit — from the title or the pause screen |
| P (or F9) | Write a screenshot and a line of trace into the telemetry folder. A debug key, not a game feature — see docs/TELEMETRY.md |
| B | Capture a three-second animation burst targeting 12 fps into the telemetry folder (debug only) |
The game also pauses itself, the same way Esc does, the moment the window loses focus — Alt-Tabbing
away, a browser tab going to the background, or a phone sending the app away — and getting focus
back does not carry on for you; press continue when you are back. See --no-focus-pause below for
the override a rig needs.
The game opens on a title screen with the street outside your own front door running behind
it, and a finished run goes back to it. With a save on disk, pressing start brings up the day
brief instead of starting the day outright.
Saving
The run is saved automatically — when a day starts being played, and when the day brief or the
end-of-day message comes up — so closing the game and opening it again picks the run back up at
the title, ready to continue rather than losing anything. There is no save button, no slot and no
menu; a small symbol in the corner marks each write. A game closed in the middle of a day comes
back at that day's dawn and costs a nerve, the same as losing the day outright; a game closed
between days, or at the day brief before continuing past it, comes back at the same screen for
free. Holding restart, on the pause screen or the day summary, clears the save and starts over.
See docs/MECHANICS.md, "Saving and resuming".
Dev flags
tools/trailer.sh records the saved scenes referenced by tools/trailer/shots.json through the
game's frame-locked movie writer, then adds the shot list's dark editorial cards, tracked logo, captions over the footage, ending copy
and deterministic oscillator score before joining them with the captured game audio. Run
tools/trailer.sh --list to inspect the cut, --validate to check its recipes headlessly,
--shot choice to record one scene, or --auditions to capture one clean game-audio base and
build the three original scores in tools/trailer/scores.json against that same picture. A later
score-only audition run reuses the matching retained base. To build the selected full composition
from a fresh checkout, run TRAILER_OUT=build/trailer/fresh tools/trailer.sh --selected.
It captures the tracked scenes, builds Glass Alarm with additive event bass from
tools/trailer/final-score.json, and lets the score's final note decay without an added closing
layer. It records fresh capture and editorial provenance. No historical
movie or ignored build artifact is required. The selected score's synthesized PCM sample hash is
checked before normalization; its target level and peak ceiling are checked after normalization.
Host-specific gain rounding and WAV encoder tags may differ. The unflagged command retains the
shot list's draft score.
When compatible historical footage is available, --selected-reuse refuses any retained-base mismatch (its file hash or the shot timeline it was cut with, which tools/trailer/final-score.json records),
renders the hook, dog and title replacements named by that final composition, and layers its
additive event cues over the source-identical selected score. --selected-remix reuses the
verified selected base and rebuilds only the editorial cards and audio, with no gameplay capture, under the same two refusals.
Selected outputs refuse an existing movie or manifest; use a new output name or fresh output
directory for another review. The completed PCM mix stays beside its source stems for comparison
with decoded delivery audio. The opening background starts immediately, the mother independently
fades in across the whole opening card, and the main title has no fades. Gameplay scenes fade
both out and in through black, including the final shot's fade-out. The auditions' local index.html switches one
player between the choices and can jump to the military turn. The cut uses the selected westward birds scene,
trailer-birds.json, and the park-only circular walk in trailer-park-circle.json.
Each recipe owns its setup and scripted action;
the shot list owns the cut timing and editorial treatment. Every recipe in the cut sets
playback.smooth, so her scripted turns sweep gradually rather than snapping and still end where
the abrupt walk does. Only normal scenes enter the trailer,
with their authored extent and validation scope preserved in the resolved manifest. The editor
reads the native viewport size from , resolves the named font through fontconfig
(a font that resolves to a different family stops the render with an error, never a substitute;
renders nothing and needs neither ffmpeg nor a font), and records the exact font, ffmpeg, source hashes and dimensions in
beside the local video.
tools/record.sh --recipe scene-recipes/trailer-choice.json records a recipe in scripted mode.
It also records ordinary rigs, such as tools/record.sh --route calm,home --seed 4242.
Videos and temporary frames stay under ignored build output. A render is the same every time
by construction: fixed recipes and seeds, the recipe's own simulation clock, the movie writer's fixed
frame rate, and an editor whose inputs (font, ffmpeg, shot list) are recorded. Recordings retain compact frame
hashes, manifests and settings beside the video. tools/trailer.sh --check all compares two
renders of each gameplay shot, including their simulation observations and audio, compares the
editorial cards, then composes both retained passes and compares the decoded ending overlays,
score and final mix; --check-load all makes the second pass under a CPU load worker. Results
remain under build/trailer/checks/. Matching frames establish repeatability for the recorded
recipe, revision, assets, engine, editor and settings.
Everything after -- is passed to the game, gated behind a debug build so none of it does
anything in an exported release:
godot --path . -- --seed 12345 --day 9 --overview
tools/run.sh and tools/shot.sh forward whatever you give them the same way, validated first
against the shapes below before either ever launches Godot — an unknown flag or one missing its
value is rejected on the spot. That accept-list is not a copy of this table: it lives in
src/dev/dev_flags.gd's own DEV_FLAG_TABLE, which both scripts read live, and tools/run.sh --help / tools/shot.sh --help print it back out, so the shell side cannot drift from what the
game actually reads. This table is the semantics — what each flag means — and
tools/test_cli_help.sh (run in CI) asserts that every flag name in DEV_FLAG_TABLE still
appears somewhere below, so an entry added to one and not the other fails the build rather than
going quietly stale.
A rig's window and its wall-clock limit
A run carrying --screenshot, --walk, --flee, --press, --tap or --route — a rig, rather
than a person at the keyboard — gets four things neither a flagless tools/run.sh session nor one
carrying only flags like --seed/--day gets:
- Its window never takes the OS focus, so it never interrupts whatever else is on screen.
- It hears no real key or pointer press — only the script driving it can move her or open a
screen — so a stray press into the wrong window can never reach it.
- It always closes. The game quits itself once its own script's length (an
--after value, or
the day's own length when nothing bounds it more tightly) plus a margin passes, capped at a fixed
ceiling regardless; tools/shot.sh, and tools/run.sh when a rig flag is present, additionally
kill the process from outside if it is somehow still alive a further grace period past that —
loudly, on stderr, with a non-zero exit. The three numbers (src/dev/dev_flags.gd's own
RIG_QUIT_SECONDS): a 15s margin, a 240s ceiling, and a 15s kill grace on top.
- On macOS it hands focus straight back. The window flag above keeps keys out, but macOS still
makes Godot itself the active app once its own startup calls
activateIgnoringOtherApps:, which
nothing launch-side can gate. tools/shot.sh and a rig-flagged tools/run.sh note whichever app
was frontmost right before Godot launches and, for as long as the rig runs, a background watcher
reactivates that app the instant Godot becomes frontmost — so a capture costs a flicker rather
than the player's own focus. Only a switch to this rig's own Godot is undone: moving to a third
app on purpose is left alone. See tools/lib_dev_flags.sh's own rig_focus_note and
rig_focus_watch_start for the mechanism.
--route cannot be combined with --screenshot: RouteRig quits the process itself the moment she
arrives, which can beat --after's own timer, so the combination is refused before Godot ever
launches rather than risking a picture that is silently never written.
A rig's window is usually hidden, and its capture does not need it seen. Handing focus back
puts the window behind whatever the operator has in front, and on macOS a window no one can see —
covered by another app's opaque window, or minimized (both measured); on another Space or behind a
locked screen by the same occlusion test, though neither has been photographed — is not drawn at
all while the game runs on. Every capture the game itself takes (--screenshot,
--quit-when-still, and the run log's stills and snapshot_burst bursts) waits on DrawnFrame.wait(), which draws its one frame on
demand when the window cannot be drawn, and says so on the [AutoScreenshot] wrote line.
| Flag | Effect |
|---|
--recipe path.json | Build a saved exact scene and control it normally; see Scene recipes |
--recipe-mode free|scripted | Use normal controls (the default), or the recipe's saved movement, camera and assertions |
--recipe-validate | Build the recipe and validate its live setup headlessly, then quit |
--recipe-manifest path.json | Write construction context, initial actors and scripted observation results |
--recipe-draft path.json | Walk a scripted recipe on its whole city and write the stretch it walked as a recipe; tools/scene-draft.sh runs it |
--seed N | Regenerate a specific city (also reachable, for a positive integer only, as a release web build's own ?debug=1&seed=N) |
--day N | Start on a later day, to look at a later act (also reachable on a release web build's own ?debug=1&day=N, clamped the same way) |
--day-length N | Compress the day, for dusk and the timeout loss (also reachable on a release web build's own ?debug=1&daylength=N) |
--meters S E | Seed the two meters, to screenshot a UI state (also reachable on a release web build's own ?debug=1&meters=S,E) |
--spawn park|alley|square|playground | Drop the player on a tile type |
--spawn event[:id] | Drop the player beside a live event |
--spawn arterial|zone[:n]|signal|landmark|power_station|closure[:n]|edge[:s|e|w]|corner[:nw|ne|sw|se]|door[:n] | Drop her at one of the places worth photographing: the busiest pavement, a multi-block calm zone (zone:1 for the second one, which is where a 2×1 will be), a signalled junction on the spine, a big building, the power station's front door, a closed street seen from its junction, one of the ways out of the map, a corner of it where two bands of the border meet, or a region-wall checkpoint's hut (from day 9), stood back on the approach side so walking in its own facing direction closes the distance toward it |
|
Run logs
Every run writes a plain-text trace of what happened, in order — what was shut, where the
player went, what came near, how the day ended. ./tools/telemetry.sh prints the newest one
(-f follows a run in progress, -l lists them). docs/TELEMETRY.md
says what the entries mean.
For animation feedback, press B during desktop debug gameplay. The burst saves numbered
PNGs and frame timestamps in a separate asked/burst-<id>/ folder. Run ./tools/clip.sh to
scan the telemetry folder and convert every finished burst missing its sibling MP4, or
./tools/clip.sh "path/to/burst-folder" to
choose one. The MP4 sits beside that folder and all original frames remain available. P still
takes a single screenshot.
The published page also counts anonymous GoatCounter events for how far a run gets — a day begun
and how it ended, a task done or skipped, the ending reached — never a seed, a position or
anything that could tell visitors apart, and nothing at all on a debug build or behind ?debug=1.
See docs/TELEMETRY.md, "The page counts visits".
Verifying a build
Release Web exports use a custom Web engine runtime (Godot export template), built without
threads or unused engine modules. The template archive is a build input; its engine becomes
the .wasm file the browser downloads.
Run ./tools/build-web-template.sh once before ./tools/export-web.sh; the builder downloads
the pinned source and toolchain, and reuses a verified matching template on later runs.
tools/web-template/profile.args lists the build options; pins.env pins their source and
toolchain. Generated downloads and binaries stay under the ignored build/ directory.
Adding a feature can require restoring a removed release-engine module; follow
Turning a module back on before exporting.
The development editor, local checks and ./tools/export-web.sh debug use the full engine.
Changing the profile requires rebuilding and verifying the game in a browser.
.godot/ is gitignored, so a fresh clone needs an import pass before class_name types
resolve. tools/check.sh does the import and then boots the project headless, failing on
any script error; tools/test.sh is the headless suite, and it is what a commit rests on.
./tools/check.sh
./tools/test.sh # everything
./tools/test.sh crowd events # just those suites, for the inner loop
./tools/lint.sh # the docs, for sentences that go stale on their own
./tools/pycheck.sh # the Python under tools/: ruff, mypy, its unit tests (needs uv)
A filtered run prints PARTIAL RUN under its count and is deliberately not a green build.
License
The code — src/, tests/, tools/, the project configuration — is MIT. The
game — the art under art/, the documents under docs/, the title, the narrative and its
characters — is Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International (CC
BY-NC-ND 4.0): read it, share it unmodified with credit, do not sell it, do not
publish a changed version. Build with the code freely; do not re-publish the game. See
LICENSE.md for how the two licenses fit together in plain language.