Deep Dive: Building a Canada Goose Simulator
A deadpan text adventure about mild chaos, built on a time budget: 79 tests, procedural honk audio, and a migration ending instead of a game over screen.
On this page
The bug was in the desensitization curve. Picnickers who’d been honked at twice should shrug, and they were still fleeing. I logged every honk, every NPC reaction, every decay tick, and found the counter resetting on zone transitions. Fixing it took one line.
Working out why that counter mattered took longer, and it changed how I thought about the game. If the honk never loses power, there’s no reason to move and no reason to plan. Underneath the jokes, this is a time budget wrapped around a single verb: 600 time units (30 per day, 20 days), six zones, five victory paths. Once I stopped treating it as a comedy game and started treating it as resource management with deadpan narration on top, the rest of the design followed.
The Time Budget
The core constraint is time, not health or inventory. Every action costs units: waddling costs 1, honking costs 1, flying costs 4, resting costs 5. When the budget ends, the run ends. That one rule is what makes the goals legible.
The five victory contracts:
- The Nested Life: collect 15 specific nesting materials
- Agent of Chaos: annoy 50 unique NPCs
- Dynasty Builder: raise 3 goslings to independence
- The Hoard: stash 30 food items in your nest
- Territorial Supremacy: claim all 6 zones simultaneously
The names are flavour. The numbers are the contract, and each one shapes which zones you visit and how aggressively you spend time.
Honking
Honking is the main verb, so I treat it like a small protocol rather than a flavour text toggle. Each honk affects NPCs within a 3-tile radius, up to 4 targets depending on crowd density. The reaction depends on who you’re honking at, how many times you’ve honked them before, and whether they happen to enjoy it.
The desensitization system tracks per-NPC honk counts. Fresh targets flee or drop items; repeated targets shrug. That turns “honk everyone constantly” into a losing strategy and rewards zone-hopping to find fresh victims.
Some NPCs flip the response entirely. Children, elders, other geese, and ducks like being honked at. They honk back, or laugh, or drop food out of friendliness. A likesHonking flag on the NPC definition handles it. Small inversion, but it keeps the interaction model from flattening out.
Procedural Goose Audio
I wanted the honk to sound like a honk, not a synthesizer demo. The audio generator is built on Tone.js with deliberately retro constraints:
- Honk: descending sawtooth (320 Hz → 220 Hz → 180 Hz) with 8 Hz vibrato and a bandpass filter at 600 Hz for the nasal edge
- Flap: white noise through a 400 Hz lowpass, quick attack and medium decay
- Waddle: 80 Hz sine wave, barely audible soft thump
- Splash: pink noise through a resonant bandpass, frequency sweep from 1200 Hz down to 400 Hz
- Peck: white noise through a 2000 Hz highpass, 30 ms envelope
- Victory: three ascending sawtooth honks (220 → 280 → 350 Hz)
- Damage: square wave descending from 500 Hz to 200 Hz
The pitch drop and the nasal filter carry most of the honk’s character. The audio also works as a mechanic cue: it tells you the action landed when the text scrolls past too fast to read.
Goslings
Goslings are a daily loop with real constraints, not decoration. A gosling hatches after 2 days of incubation. It ages each day, gets hungry (+15 hunger per day), and can only become independent after 5 days if it has learned at least 3 behaviours and its HP is above 20.
The learning system watches what the player does. After observing a behaviour twice, a gosling learns it. Behaviours include honking, flapping, stealing, and swimming — anything the player models while the gosling is following. Personalities (aggressive, resourceful, brave, cautious, aquatic) give bonuses to related behaviours, which adds narrative flavour without changing the core math.
“Raise 3 goslings” is a planning problem: hatch enough eggs, keep them fed, and demonstrate enough varied behaviours before day 20 runs out. It’s the slowest path to victory and the most vulnerable to time pressure.
Migration Instead of Death
The end state is framed as migration, not a corpse. When day 20 ends without victory, the game prints a migration epilogue and picks a destination from a list of 100 Canadian town names and goose puns: “Happy Valley-Goose Bay,” “Honkerton,” “Bread or Consequences,” “Peace Was Never An Option.”
HP reaching zero ends the run too, framed as the goose fleeing rather than dying. The loop is meant to be replayed, so nothing about the ending should feel final.
The Test Harness
This game has 79 unit tests, which sounds excessive for a text adventure. They exist so I can rewrite narrative and pacing without breaking the foundation underneath. They cover goal completion (can I actually annoy 50 NPCs? can I actually hoard 30 food items?), action costs, stat bounds, state transitions, NPC spawning, save/load consistency, and edge cases like waddling into walls or eating from an empty inventory.
The stress tests run 500 random commands and 5 full day cycles to confirm nothing crashes. The deterministic tests confirm each goal is reachable through actual gameplay rather than just arithmetic.
What Changed
A text adventure can carry humour, but it still needs a model underneath. The model here is a time budget, a fixed map, and a handful of verbs with real consequences. With those pinned down by tests, I can rewrite flavour text, adjust NPC dialogue, or add items without regression.
What surprised me was how much the desensitization system changed the feel. Early playtests with unlimited honk power were a power fantasy: mildly funny, quickly boring. Forcing the player to move, find fresh targets, and weigh aggression against the clock made the comedy something you work for. The goose isn’t overpowered, just persistent.
See also: Deep Dive: Balancing a Goose Game — the tuning pass that made it playable.