“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 branch feat/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):

  1. 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.

  2. 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.

  3. Cook mini-game — ratio balancing. Set 3 abstract reagent dials toward a hidden target with hot/warm/cold feedback; quality = 1 normalizedDistance.

  4. 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 (EMPTY only): consumes 1 strain seed + 1 nutrient unit; sets the plot to SEEDLING, records plantedAt = now, initializes careAccum.

  • 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. careAccum accumulates 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 to EMPTY; 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 CookSession with 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) where dist is Euclidean distance between the normalized dial vector and the target. MAXDIST is 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).

5. Quality → Reward (shared production.ts)

A single mapping consumed by both grow and cook so the two paths stay consistent:

qualityValueMult(q): number   // lerp 0.7 → 1.3 across q 0→1
qualityYield(q, min, max): number  // round(min + q*(max-min)); grow ~3→9, cook ~2→6
qualityBonusEffects(baseId, q): EffectId[]  // [base.bonusEffect] if q >= 0.8 else []
  • Value multiplier rides on the product as qualityMult; productValue multiplies by it (default 1 → Phase 1 behavior unchanged).

  • Yield sets the finished base-stock entry’s units.

  • Bonus starting effect: at q 0.8, the base’s designated signature effect is pre-seeded into the product’s effects when it’s loaded onto the bench (so production skill flows straight into the signature mixer). greenstart (bought) has no bonus and qualityMult = 1.

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’s rawBases. Each entry is { baseId, qualityMult, bonusEffects: EffectId[], units }. Buying greenstart pushes/merges a { greenstart, 1, [], n } entry; grown/cooked output pushes its own entry. The mixer’s loadBaseToBench pulls one unit from a chosen baseStock entry, creating workProduct = { 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> with baseStock: BaseStockEntry[] updates store.ts and the corresponding store.test.ts assertions (and saveSystem shape). This is intentional unification — chosen over carrying two parallel base representations — and is small and localized.

6.2 Save (saveSystem.ts)

  • CURRENT_VERSION bumps 1 → 2. SaveV2 adds inputs, plots, dryingRack, and uses baseStock in place of rawBases.

  • parseSave accepts a valid v1 payload and migrates forward: empty inputs/plots/ dryingRack, and converts any v1 rawBases counts into baseStock entries (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 in TownScene (new anchor positions): 3 grow plots, 1 drying rack, 1 chemistry station.

  • New overlays (all via OverlayFrame, self-gating on activeOverlay):

    • GrowPlotOverlay — one overlay parameterized by plot index (active id like plot:0): shows stage, care so far, plant-with-strain picker (when EMPTY), tend button with live cooldown countdown, harvest button (when FLOWERING).

    • 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 baseStock entries (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, canTend cooldown gating via injected now, stage advancement, care accrual + wilt penalty, harvest output, drying collect.

    • chemistry.test.tscookQuality (perfect=1, worst=0, monotonic), feedbackBand bands, MAXDIST.

    • production.test.tsqualityValueMult/qualityYield/qualityBonusEffects thresholds.

    • effects.test.ts — extended productValue honors qualityMult (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), baseStock buy/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-reviewer before 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.