“Game” (cookgame) — Phase 2 Design — Production Pipelines¶
Status: Phase 2 of 6 approved for spec. Display title: “Game” · Internal slug:
cookgame. Depends on: Phase 1 (complete on branchfeat/cookgame-phase-1). Engine: react-three-fiber + Rapier, reusing Phase 1 primitives.
1. Goal & Premise¶
Phase 1 lets the player buy a finished base (greenstart), mix it for effects, package,
and sell. Phase 2 adds production: instead of only buying base, the player grows strains
across parallel grow plots and cooks premium product at a chemistry station — both gated by
skill/care that produces a quality score, which feeds the existing mixer and economy.
Tone unchanged: purely satirical/fictional. All strains, cooked products, reagents, and the
cook mini-game are invented and abstract. No real-world chemistry, quantities, ratios,
temperatures, or procedures are modelled. Production is game-y stations + data tables + a
ratio-matching puzzle whose only “chemistry” is 1 − distance.
Phase 2 extends Phase 1 rather than rewriting it: the mixer, packaging, selling, heat, and save systems are reused; new pure libs and store fields are added; one small Phase 1 unification is made deliberately (see §6).
2. Mechanics Overview¶
Four keystone decisions (locked during brainstorming):
Growth model — hybrid tend + cooldown. A plot advances a stage only when the player tends it, but each stage has a short real-time cooldown before the next tend is allowed. Pure logic takes
now(ms) as a parameter so it stays deterministic and testable; a thin browser wrapper supplies the real clock. Cooldowns survive save/reload via timestamps.Grow structure — multiple plots + drying rack. Three parallel grow plots, each an independent stage machine, plus one shared drying rack. More tycoon juggling; more state to persist.
Cook mini-game — ratio balancing. Set 3 abstract reagent dials toward a hidden target with hot/warm/cold feedback;
quality = 1 − normalizedDistance.Quality (0–1) drives three levers — a value multiplier, yield (unit count), and a bonus starting effect at high quality.
3. The Grow Pipeline¶
3.1 Stage machine (per plot)¶
EMPTY ──plant(seed+nutrient)──▶ SEEDLING ──tend──▶ VEGETATIVE ──tend──▶ FLOWERING
│
harvest
▼
wet batch ──▶ DRYING RACK
│
dry (cooldown)
▼
finished base → baseStock
Plant (
EMPTYonly): consumes 1 strain seed + 1 nutrient unit; sets the plot toSEEDLING, recordsplantedAt = now, initializescareAccum.Tend (
SEEDLING,VEGETATIVE): the contextual action is flavored water/light, but mechanically it advances one stage iff the per-stage cooldown has elapsed (canTend(plot, now)). Tending within the stage’s grace window adds full care credit; tending late (past grace, “wilted”) adds reduced credit.careAccumaccumulates per tend.Harvest (
FLOWERING): produces a wet batch carrying the strain id and the plot’s final care quality (careAccum / careMax, clamped 0–1); the plot returns toEMPTY; the wet batch is pushed to the drying rack.Dry (drying rack): a wet batch has a
dryStartedAt; after the drying cooldown elapses it can be collected into a finished base stock entry —{ baseId, qualityMult, bonusEffects, units }— via the §5 quality→reward mapping. Quality is the wet batch’s care quality.
3.2 Cooldowns & care (illustrative constants, tune in plan)¶
Per-stage tend cooldown ~30–45s; grace window ~2× cooldown before wilt penalty.
Drying cooldown ~60s.
careMax= number of tends required across the plot’s life (SEEDLING→VEG, VEG→FLOWER = 2 tends); each on-time tend = 1.0 credit, wilted = 0.5 credit.careQuality = careAccum/careMax.
All thresholds live as named constants in cultivation.ts, tunable without touching logic shape.
4. The Cook Mini-Game (chemistry station)¶
Start a cook: pick a cookable base; consumes 1 reagent pack. Opens a
CookSessionwith a hidden target ratio = three values in[0,1]summing to 1 (generated by the store; the pure scorer receives the target explicitly).Play: three dials/sliders, each
[0,1]; UI shows a single feedback band derived from the current distance to target:HOT/WARM/COLD.Submit:
quality = cookQuality(dials, target) = 1 − clamp(dist/MAXDIST, 0, 1)wheredistis Euclidean distance between the normalized dial vector and the target.MAXDISTis the worst-case distance so quality spans 0–1.Output: a finished base-stock entry for the cooked base via §5, pushed to
baseStock.
Pure (chemistry.ts): cookQuality(dials, target), feedbackBand(dials, target) (HOT/WARM/COLD
thresholds), and MAXDIST. Deterministic; the store owns target generation (may use
Math.random in browser/store code — only Workflow scripts forbid it).
6. State & Save¶
6.1 Store additions (store.ts)¶
Inventory gains:
inputs: Record<string, number>— seed (per strain), nutrient, reagent-pack counts.plots: PlotState[]— fixed length 3.dryingRack: WetBatch[].baseStock: BaseStockEntry[]— unified replacement for Phase 1’srawBases. Each entry is{ baseId, qualityMult, bonusEffects: EffectId[], units }. Buyinggreenstartpushes/merges a{ greenstart, 1, [], n }entry; grown/cooked output pushes its own entry. The mixer’sloadBaseToBenchpulls one unit from a chosenbaseStockentry, creatingworkProduct = { baseId, effects: [...bonusEffects], qualityMult }.
New actions: buyInput(id), plantPlot(plotIndex, strainId), tendPlot(plotIndex),
harvestPlot(plotIndex), collectDried(batchIndex), startCook(baseId), setDial(i, value),
submitCook(). Existing buy/mix/package/sell/heat/save actions are reused; buyBase and
loadBaseToBench are adapted to baseStock.
Deliberate Phase 1 touch-up: replacing
rawBases: Record<baseId, number>withbaseStock: BaseStockEntry[]updatesstore.tsand the correspondingstore.test.tsassertions (andsaveSystemshape). This is intentional unification — chosen over carrying two parallel base representations — and is small and localized.
6.2 Save (saveSystem.ts)¶
CURRENT_VERSIONbumps 1 → 2.SaveV2addsinputs,plots,dryingRack, and usesbaseStockin place ofrawBases.parseSaveaccepts a valid v1 payload and migrates forward: emptyinputs/plots/dryingRack, and converts any v1rawBasescounts intobaseStockentries (qualityMult 1,bonusEffects []). Invalid/corrupt/version-too-new payloads still return null.Timestamps (
plantedAt,lastTendedAt,dryStartedAt) are absolute ms so cooldowns resume correctly across reloads.
7. World & Components (reuse Phase 1 primitives)¶
New station meshes +
Interactables placed inTownScene(new anchor positions): 3 grow plots, 1 drying rack, 1 chemistry station.New overlays (all via
OverlayFrame, self-gating onactiveOverlay):GrowPlotOverlay— one overlay parameterized by plot index (active id likeplot:0): shows stage, care so far, plant-with-strain picker (whenEMPTY), tend button with live cooldown countdown, harvest button (whenFLOWERING).DryingRackOverlay— lists wet batches with dry countdowns; collect when ready.ChemistryStationOverlay— the 3-dial ratio mini-game with HOT/WARM/COLD feedback + submit.SupplierShopOverlay— extended with an Inputs section (seeds/nutrient/reagents).
Mixing/packaging overlays adapt their “load base” UI to choose from
baseStockentries (showing quality/bonus). HUD optionally surfaces a compact plots/rack status; not required.Reuses
Interactable,InteractionPrompt,OverlayFrame,PlayerController, save wiring.
8. Testing & Verification¶
Unit (vitest, pure, no DOM):
cultivation.test.ts— plant,canTendcooldown gating via injectednow, stage advancement, care accrual + wilt penalty, harvest output, drying collect.chemistry.test.ts—cookQuality(perfect=1, worst=0, monotonic),feedbackBandbands,MAXDIST.production.test.ts—qualityValueMult/qualityYield/qualityBonusEffectsthresholds.effects.test.ts— extendedproductValuehonorsqualityMult(and Phase 1 cases still pass).saveSystem.test.ts— v1→v2 migration; v2 round-trip; rejects bad/over-version payloads.store.test.ts— full production flows (plant→tend→harvest→dry→load→mix; start→submit cook),baseStockbuy/merge/load, updated for the unification.
Type/build/lint:
./node_modules/.bin/tsc --noEmit,vite build(regenerates route tree if needed),eslint— per repo constraints (pnpm wrappers blocked).Manual: grow a plot end-to-end across cooldowns, wilt one, dry + collect, load a quality base and see value/bonus in the mixer; cook hot/cold and compare outputs; reload mid-grow and confirm cooldowns resume; load a Phase 1 v1 save and confirm migration.
Run
senior-swe-reviewerbefore the eventual single big PR (per project PR strategy: all phases accumulate on the branch, one PR at the end).
9. Out of Scope for Phase 2 (later phases)¶
Rank/XP, additional shops, property purchase/upgrade, the day/night & time-of-day cycle (Phase 3 — Phase 2 uses bare cooldown timestamps, not a world clock), deeper recipe-discovery journal, customer demand simulation, phone/deal system, employees & dealers (production automation), police/cartel/busts, mobile controls. Phase 2 data tables and store are shaped so Phase 3+ extend rather than rewrite.