# GoalBuddy v2 state.yaml — BaoLife feel & pacing tuning
version: 2

goal:
  title: "BaoLife — Feel & Pacing Tuning Pass"
  slug: "baolife-feel-pacing"
  kind: specific
  tranche: "Measure current feel via the simulator -> define target bands -> tune knobs in verified waves (measure/adjust/re-measure) -> fix 3 named follow-ups -> confirm in-app -> final audit."
  status: done
  completion_note: "GOAL COMPLETE — T999 PM final audit, full_outcome_complete: true. Survival fixed (meal sink; deaths now natural ~70s, was starvation ~30s); death curve recalibrated; event cadence fixed (burst 17->1/day, dead-air 491->0 mid-life months); economy measured healthy (employed net-worth $0->~$57k, bounded); 3 follow-ups done (FU1 cadence, FU2 immediate death->DeathView iOS+Android, FU3 offline digest money delta); +2 latent bugs caught & fixed (cooldown day-basis, Android death-routing field). Backend tsc clean + vitest 1627 (was 1621); iOS + Android build green; in-app 4/4 confirmed eyes-on. Work UNCOMMITTED on this goal (separate from the prior deployed overhaul). NOTE: the survival/starvation bug ALSO exists in already-deployed prod code — this fix is not yet deployed."
  oracle:
    signal: "Simulated lifetimes land in defined HEALTHY bands for event cadence + economy curve + energy band + difficulty/longevity; 3 named feel-bugs fixed; backend green (tsc + vitest >=1621 no regression); in-app session confirms digestible pacing."
    cadence: "re-measure after each tuning wave; in-app + final audit at the end"
    final_proof: "Final Judge/PM receipt mapping before/after simulator metrics + the 3 fixes + green suite + in-app session to the oracle, full_outcome_complete: true."
  intake:
    original_request: "Feel & pacing tuning pass: measure with the soak/lifetime simulator, define target curves, tune knobs in verified waves; fix 3 in-app follow-ups. Pre-authorized big autonomous run."
    interpreted_outcome: "BaoLife feels rhythmically right moment-to-moment (digestible event cadence, satisfying economy/difficulty curve, playable energy band, motivating reward beat), verified by simulator metrics + in-app."
    input_shape: specific
    audience: "BaoLife players (felt experience) + Craig"
    authority: approved
    proof_type: metric
    completion_proof: "before/after simulator metrics in healthy bands + 3 fixes + green suite + in-app pacing confirmation"
    likely_misfire: "Tuning numbers blindly without measuring first; over-fitting the simulator while the real app still feels off; breaking tests by changing constants; treating 'feel' as vibes instead of measurable bands + an eyes-on session."
    blind_spots_considered:
      - "Prior run set many constants (economyConstants, selector weights/cooldowns, energy 35/night) — build on them, don't relitigate."
      - "Two loop paths (online PlayerSession + offline GameEngine) must stay consistent on pacing."
      - "Event density = catalog size x per-event trigger probability x cooldowns x speed thresholds; fixing over-density must not create dead air or starve content."
      - "Easing scarcity must not make resources trivial — don't trade one pathology for another."
    existing_plan_facts:
      - "MEASURE dimensions (via simulator, server/tests/e2e/): event-prompt density per in-game day/week/year; money/net-worth progression curve over a full life; energy trajectory + how often scarcity/lockout is hit; difficulty/death-age distribution; reward cadence (diamonds/goals/achievements over time); stat balance ranges."
      - "KNOBS to tune: per-event trigger probability + cooldownDays + weights (events/v2 selector + catalog); event-emit cadence / speed thresholds (PlayerSession processHourTick, config speed constants); economyConstants (rent %, lifestyle, night energy restore 35, hunger/thirst rates, starvation thresholds); PLAYER_ACTIVITIES costs; life-stage content distribution (age gates / pacing rebalance); reward timing (lifeGoals, dailyQuests, achievements)."
      - "FOLLOW-UP 1: event-prompt stream too dense at default speed (observed in-app) — add/raise per-hour trigger spacing or probability so prompts don't fire near-continuously."
      - "FOLLOW-UP 2: mid-session death doesn't switch to DeathView until reconnect — in-session lifeSummaryEvent / death should drive the client transition directly (iOS ContentView routing / a death push)."
      - "FOLLOW-UP 3: offline-digest money-delta showed +$0 because per-minute offline ticks didn't span a weekly-finance cycle — make the digest money delta meaningful (e.g. prorate or run finance over the offline span)."
      - "Simulator exists at server/tests/e2e/ (lifetime-simulator, soak); reuse/extend it to emit the metrics rather than building a new harness."

rules:
  pm_owns_state: true
  one_active_task: true
  max_write_workers: 1
  no_implementation_without_worker_or_pm_task: true
  no_completion_without_judge_or_pm_audit: true
  planning_is_not_completion: true
  queued_required_worker_blocks_completion: true
  continuous_until_full_outcome: true
  missing_input_or_credentials_do_not_stop_goal: true
  preserve_and_validate_existing_plan: true
  intake_misfire_must_be_audited: true
  goal_pressure_requires_oracle: true
  no_completion_on_weak_proof: true
  slice_policy:
    max_consecutive_tiny_tasks: 2
    prefer_vertical_slices: true
    judge_picks_largest_safe_slice: true
    worker_completes_whole_slice: true

agents:
  scout: installed
  worker: installed
  judge: installed

visual_board:
  selected: local
  local:
    status: live
    url: "http://127.0.0.1:41737/baolife-feel-pacing/"
    command: "npx goalbuddy board /Users/craigvandergalien/Documents/GitHub/lichun/docs/goals/baolife-feel-pacing"

active_task: null

tasks:
  - id: T001
    type: scout
    assignee: Scout
    status: done
    reasoning_hint: medium
    objective: "MEASURE the current feel. Read the simulator harness (server/tests/e2e/ — lifetime-simulator, soak) and determine what it currently records and how to make it emit pacing metrics. Then quantify the CURRENT experience across many lifetimes: event-prompt density (per in-game day/week/year), money/net-worth curve over a full life, energy trajectory + frequency of scarcity/lockout, difficulty/death-age distribution, reward cadence (goals/quests/achievements/diamonds over time), stat ranges. ALSO map the tunable knobs + their current values (selector trigger/cooldown/weights, PlayerSession event-emit cadence + config speed thresholds, economyConstants, PLAYER_ACTIVITIES costs, age-gate distribution, reward timing). Report the baseline numbers + where each knob lives (file:line) + which dimensions look pathological (esp. event density). Read-only."
    inputs:
      - "server/tests/e2e/ (lifetime-simulator, soak)"
      - "server/src/events/v2/engine/{selector,EventEngine}.ts + catalog; game/PlayerSession.ts processHourTick; config speed constants"
      - "server/src/game/economyConstants.ts; engine/intradayActivity.ts PLAYER_ACTIVITIES; services/retention (lifeGoals/dailyQuests/achievements)"
    constraints:
      - "Read-only. Quantify with real numbers from running the simulator (you MAY run the existing sim/tests to collect metrics; do not edit product code)."
      - "If the sim doesn't emit a metric, note exactly what instrumentation a Worker would add — don't add it yourself."
    expected_output:
      - "Baseline metrics table (the 6 dimensions, real numbers across N lifetimes)"
      - "Knob map: each tunable + current value + file:line"
      - "Ranked pathologies (what feels wrong, quantified)"
      - "What sim instrumentation (if any) a Worker should add to measure repeatably"
    receipt:
      result: done
      summary: "Measure-first OVERTURNED the premise. CRITICAL: mealEvent (only hunger/thirst sink) has 0 game-loop call sites; daily-plan 'eat' entries are text-only -> every autonomous life STARVES (hunger=thirst=100, health=0, death age 29-40 across seeds 1/7/42). Event prompts are SPARSE not continuous: ~25-36 once-ever events/life (82/86 once-ever), front-loaded at age-gate unlocks then dead-air; promptNext fires every hour with NO rate gate. Money pinned at 0 + rewards ~0 in idle play (employment/hooks never trigger). calcEnergy never hits 0 in autonomous play. Sim records msgType/eventsFired/finalAge/invariants only — needs instrumentation for money/energy/density/reward trajectories."
      evidence:
        - "health_manager.ts:806 mealEvent = only hunger/thirst reducer, 0 loop call sites (grep)"
        - "intradayActivity.ts eat breakfast/lunch/dinner are text-only (no hunger= effect)"
        - "PlayerSession.ts:255-273 promptNext every hour, no probability/rate gate; selector.ts:147-188 weighted-random among all eligible"
        - "baseline seeds 1/7/42: death age 29/40/32 starvation, money 0, calcE=0hrs, 3 diamond calls/0 diamonds, 3 lifeGoalUpdates"
        - "catalog 86 defs, only 4 repeatable (cooldowns 30/360/360/400/540d) -> front-loaded density then dead-air"
        - "economyConstants.ts:26 HUNGER+3/hr :29 THIRST+4/hr :64 ENERGY_RESTORE_PER_NIGHT=35; intradayActivity.ts:123-160 PLAYER_ACTIVITIES costs 6-18; config.ts:45-50 TICK 1ms SPEED_DEFAULT 10000"
        - "lifetime-simulator.ts:53-65 records messageTypeCounts/eventsFired/finalAge/invariants; soak 20 seeds x2yr age12"
      ambiguity_for_judge:
        - "Is no-eating starvation a REAL player bug (offline GameEngine + online + client never reduce hunger/thirst) or only a harness artifact? Resolve before tuning — it gates measuring longevity/economy/late-life entirely."
        - "Real pacing problem may be DEAD AIR (once-ever exhaustion) not spam; confirm burst magnitude with a per-day density histogram (instrumentation)."
  - id: T002
    type: judge
    assignee: Judge
    status: done
    reasoning_hint: high
    objective: "From the Scout baseline, DEFINE healthy target bands for each dimension (event cadence, economy curve, energy band, difficulty/longevity, reward cadence) with concrete numbers, and select the FIRST tuning Worker package (the highest-leverage pathology — likely event density). Specify allowed_files, verify (incl. how to re-measure), stop_if. Sequence the remaining waves + the 3 named follow-ups. Confirm the measure-before-tune discipline is wired (a repeatable metrics command)."
    inputs:
      - "T001 receipt (baseline + knob map + pathologies)"
      - "goal.md oracle + constraints"
    constraints:
      - "Do not implement."
      - "Target bands must be concrete + measurable, justified against genre feel."
      - "Pick the largest safe useful tuning slice; don't trade one pathology for another."
    expected_output:
      - "Target bands per dimension (numbers)"
      - "First Worker objective + allowed_files + verify (incl. re-measure) + stop_if"
      - "Wave sequence + where the 3 follow-ups slot in"
    receipt:
      result: done
      decision: approved
      full_outcome_complete: false
      verdict: "NO-EATING IS A REAL PLAYER BUG, not a harness artifact. mealEvent (health_manager.ts:812) is the only hunger/thirst sink, ZERO runtime callers; getIntradayActivity (intradayActivity.ts:288-315) sets only location+text on meal slots (DailyEvent type has no effect field); no client eat command; PLAYER_ACTIVITIES don't reduce hunger. Both loops run applyHourlySurvival (+3/+4 hr) with no feed -> force-starve by ~35, blocking late-life/economy/legacy content."
      reframed_bands:
        - "SURVIVAL (new #1): a healthy normally-played character must be ABLE to live a full life; death-age band ~70-95 (age/natural), starvation eliminated in idle play; hunger/thirst well below 100 in steady state w/ 3 meals/day."
        - "EVENT CADENCE: ~1-4 prompts per in-game week steady-state; no day >~3; no mid-life month with zero (kill both burst + dead-air); raise repeatable/ambient ratio."
        - "ECONOMY/REWARDS: measurable only on a WORKING character -> sim must drive employment; target positive net-worth growth with recoverable dips; rewards on a regular beat (not ~0)."
      sequence: "T003 survival+instrumentation -> T004 event cadence/dead-air (FU1) -> T005 economy-in-active-play (FU3 offline digest money) -> T006 client mid-session death->DeathView (FU2) -> T999 audit."
  - id: T002b_marker
    type: pm
    assignee: PM
    status: done
    reasoning_hint: low
    objective: "Board restructure per T002: T003 = survival+instrumentation; added T004 cadence, T005 economy, T006 client death-transition before T999."
    receipt:
      result: done
      summary: "Survival/eating reframed as #1. T003 specced (meal sink both loops + sim trajectory instrumentation + repeatable metrics cmd). T004/T005/T006 inserted."
  - id: T003
    type: worker
    assignee: Worker
    status: done
    reasoning_hint: high
    objective: "SURVIVAL + INSTRUMENTATION wave. (1) Wire a real hunger/thirst SINK into the loop so a normally-played character can survive: when getIntradayActivity executes a meal slot (breakfast/lunch/dinner), reduce hunger/thirst by a tuned amount via a SINGLE shared implementation (route through mealEvent or a new shared applyMealEffect) called identically by online (PlayerSession executor) AND offline (GameEngine) — paths must not diverge. Tune so a healthy adult with 3 meals/day stays well below STARVATION_LEVEL in steady state. (2) Extend lifetime-simulator.ts to emit per-life trajectories: hunger/thirst min/max/mean, energy+calcEnergy trajectory + hours at 0, money/net-worth sampled per in-game year, event-prompt density per in-game day/month/year (incl peak burst), reward cadence (lifeGoal/quest/achievement/diamond counts over time). Add ONE repeatable metrics command (server/package.json script) printing these for seeds 1/7/42. (3) Re-measure to PROVE: a healthy character now lives a full life (death band ~70-95, NOT 29-40 starvation) and idle-play starvation deaths are eliminated. Do NOT yet tune event cadence or economy knobs — just survival + measurement. Mirror online+offline. Tests green."
    allowed_files:
      - "server/src/game/engine/intradayActivity.ts"
      - "server/src/services/health/health_manager.ts"
      - "server/src/game/PlayerSession.ts"
      - "server/src/game/engine/GameEngine.ts"
      - "server/src/game/economyConstants.ts"
      - "server/tests/e2e/lifetime-simulator.ts"
      - "server/tests/e2e/lifetime-simulator.test.ts"
      - "server/tests/e2e/diagnostics.test.ts"
      - "server/package.json (scripts only — add a metrics command)"
      - "docs/goals/baolife-feel-pacing/notes/PROGRESS.md"
    verify:
      - "cd server && npx tsc --noEmit  (exit 0)"
      - "cd server && npx vitest run  (>=1621, no regression)"
      - "run the new metrics command for seeds 1/7/42: hunger/thirst stay below STARVATION_LEVEL in steady state; finalAge no longer clustered 29-40 (healthy band ~70-95)"
      - "metrics output includes money/energy/event-density/reward trajectories for reuse by later waves"
    stop_if:
      - "Need files outside allowed_files (esp. selector/catalog or client — those are LATER waves)."
      - "The meal sink trivializes survival (hunger/thirst never meaningfully pressure) or creates a new pathology — escalate to Judge."
      - "Online and offline meal effects diverge (not sharing one implementation)."
      - "Verification fails twice."
    receipt:
      result: done
      changed_files:
        - "server/src/services/health/health_manager.ts (shared applyMealEffect; mealEvent delegates; updateDeathChance recalibrated)"
        - "server/src/game/economyConstants.ts (MEAL_HUNGER_REDUCTION=50, MEAL_THIRST_REDUCTION=65)"
        - "server/src/game/engine/intradayActivity.ts (meal branch calls applyMealEffect; matched on name breakfast/lunch/dinner)"
        - "server/src/game/engine/GameEngine.ts (stale doc-comment); tests/e2e/lifetime-simulator.ts (trajectory metrics); tests/e2e/metrics.test.ts (new); server/package.json (npm run metrics)"
      commands:
        - cmd: "cd server && npx tsc --noEmit"
          status: pass
        - cmd: "cd server && npx vitest run"
          status: pass
          detail: "1624/1624 (134 files; +3 metrics tests)"
        - cmd: "cd server && npm run metrics (seeds 1/7/42)"
          status: pass
          detail: "deaths NATURAL at 72/71/52 (was 29/40/32 starvation); hunger mean33/max82, thirst mean44, health mean100/min64-86"
      summary: "Survival FIXED: shared applyMealEffect (hunger -50/thirst -65 per meal) called from getIntradayActivity meal branch -> both online (PlayerSession) + offline (LoopManager/GameEngine) feed via one impl. updateDeathChance recalibrated (median ~80, ~70-95 spread) since removing starvation exposed a flat-odds 120yr-cap pathology. Sim instrumented with trajectories + repeatable npm run metrics. Both loops verified to feed. No baseline test needed changing. (Note: this starvation bug exists in already-deployed prod code — fix is local/uncommitted on this goal.)"
  - id: T004
    type: worker
    assignee: Worker
    status: done
    reasoning_hint: high
    objective: "EVENT CADENCE / DEAD-AIR wave (resolves FU1). With survival fixed + instrumentation live, re-measure density on FULL-LENGTH lives (now ~70-95yr). Address (a) once-ever exhaustion -> dead-air (raise repeatable/ambient event ratio, add per-life-stage ambient drip, and/or reduce cooldowns on the repeatables) and (b) age-gate unlock BURSTS (spread unlocks or add a per-hour/per-day emit rate gate around PlayerSession.ts:255-273 + mirror offline). Target band: ~1-4 prompts per in-game week steady-state, no in-game day with >~3 prompts, no mid-life in-game month with ZERO. Use `npm run metrics` (event-density histogram + peak burst) to measure before/after. Mirror online+offline. Don't trade burst for dead-air. Tests green."
    allowed_files:
      - "server/src/events/v2/engine/selector.ts"
      - "server/src/events/v2/engine/EventEngine.ts"
      - "server/src/events/v2/catalog/*.ts (adjust repeatable/cooldown/weight; add ambient drip events if needed)"
      - "server/src/events/v2/types.ts (only if an ambient/rate field is needed)"
      - "server/src/game/PlayerSession.ts (per-hour/day emit rate gate)"
      - "server/src/game/engine/GameEngine.ts and/or LoopManager.ts (mirror the offline emit gate)"
      - "server/src/config.ts (only if a cadence/speed constant is the knob)"
      - "server/tests/e2e/lifetime-simulator.ts, metrics.test.ts (re-measure; assert density band)"
      - "server/tests/** (new/updated cadence tests)"
      - "docs/goals/baolife-feel-pacing/notes/PROGRESS.md"
    verify:
      - "cd server && npx tsc --noEmit"
      - "cd server && npx vitest run  (>=1621)"
      - "metrics: density in target band, no burst >3/day, no zero-month mid-life"
    stop_if:
      - "Need files outside allowed_files (PM sets)."
      - "Trades burst for dead-air or vice versa — escalate."
      - "Verification fails twice."
    receipt:
      result: done
      changed_files:
        - "server/src/events/v2/engine/EventEngine.ts (per-in-game-day emit gate in promptNext, EVENT_EMIT_MIN_DAYS=1; resolveCurrentDay -> monotonic ageDays cooldown-basis fix)"
        - "server/src/events/v2/catalog/random.ts (2 ambient events -> repeatable cd30/45; +3 ambient life-texture passives)"
        - "server/tests/e2e/lifetime-simulator.ts, metrics.test.ts (felt-cadence + dead-air detector + band assertions)"
      commands:
        - cmd: "cd server && npx tsc --noEmit"
          status: pass
        - cmd: "cd server && npx vitest run"
          status: pass
          detail: "1624/1624 (+ cadence-band assertions)"
        - cmd: "cd server && npm run metrics"
          status: pass
          detail: "peakSingleDay 17->1; mid-life zero-months 491/498->0/498; perWeek~0.94/perMonth~4.02"
      summary: "Burst killed via single per-day emit gate in EventEngine.promptNext (peakSingleDay 17->1). Dead-air killed by fixing resolveCurrentDay to monotonic ageDays (latent cooldown bug: wrapping dayOfYear made elapsed-day go negative at year boundaries, suppressing repeatables ~10mo/yr) + 2 ambient events repeatable + 3 new ambient passives. Both bands hit (no burst, no dead-air), comfortable low end. Offline loop uses LEGACY events (never calls v2 promptNext, already 1/tick) -> single v2 gate covers all v2 emission, nothing to mirror."
      pm_flags:
        - "Cadence sits at the LOW end (~1 felt event/in-game week). Knobs to nudge toward band center if desired: EVENT_EMIT_MIN_DAYS + random.ts ambient cooldowns. Accepted as-is (low end safer than spam)."
        - "FU1 resolved. Caught + fixed a latent cooldown day-basis bug as a bonus."
  - id: T005
    type: worker
    assignee: Worker
    status: done
    reasoning_hint: high
    objective: "ECONOMY-IN-ACTIVE-PLAY wave (resolves FU3). Money pins at 0 in the sim because employment/retention hooks never fire autonomously. (1) Extend the sim/metrics to drive an EMPLOYED character — auto-apply for an age-appropriate job at ~18-22 and hold it (use the real job handler/applyForJob path so wages/rent/lifestyle actually run), then record money/net-worth per in-game year + reward cadence (lifeGoals/quests/achievements/diamonds) over a WORKING life. (2) Measure, then tune economyConstants (rent %, lifestyle modifiers, wages/salary, savings) + reward timing so net worth follows a sensible curve — positive growth across working years with non-trivial-but-recoverable dips, NOT flat-broke and NOT runaway-infinite; rewards land on a regular beat (not ~0). Define + assert the target. (3) FU3: the offline-digest money delta showed +$0 because per-minute offline ticks didn't span a weekly-finance cycle — make the offline digest run/prorate the weekly finance over the offline span so the money delta is meaningful for a multi-day absence. Mirror online+offline economy. Tests green; re-measure via npm run metrics."
    allowed_files:
      - "server/src/game/economyConstants.ts"
      - "server/src/game/finance.ts"
      - "server/src/stats/stats_manager.ts (savings/expense modifiers if tuned)"
      - "server/src/services/jobs/job_manager.ts (wages, if a wage knob is tuned)"
      - "server/src/services/retention/{lifeGoals,dailyQuests,achievements,integration}.ts (reward timing, only if tuned)"
      - "server/src/game/engine/LoopManager.ts, GameEngine.ts (offline finance + digest proration over offline span)"
      - "server/src/game/PlayerSession.ts (only if online finance/digest needs the same proration)"
      - "server/tests/e2e/lifetime-simulator.ts, metrics.test.ts (employed-character scenario + economy/reward assertions)"
      - "server/tests/** (new/updated economy tests)"
      - "docs/goals/baolife-feel-pacing/notes/PROGRESS.md"
    verify:
      - "cd server && npx tsc --noEmit  (exit 0)"
      - "cd server && npx vitest run  (>=1624, no regression)"
      - "npm run metrics: employed-character net-worth curve is healthy (growth w/ recoverable dips, not 0/infinite); reward cadence non-zero; offline digest money delta non-zero over a multi-day span"
    stop_if:
      - "Need files outside allowed_files (PM sets)."
      - "Economy becomes trivial (infinite money) or punishing (perma-broke) — escalate to Judge."
      - "Driving employment in the sim needs a structural change beyond allowed_files — escalate."
      - "Verification fails twice."
    receipt:
      result: done
      changed_files:
        - "server/tests/e2e/lifetime-simulator.ts (maybeDriveEmployment via real applyForJob; YearlySample netWorth/occupation/salary); metrics.test.ts (economy-band assertion + curve printout)"
        - "server/src/game/finance.ts (applyProratedFinances + DAYS_PER_FINANCE_WEEK); engine/LoopManager.ts (processOfflineTime prorates leftover sub-week, no double-count)"
        - "server/tests/game/offlineDigest.test.ts (real prorated finance + 3 FU3 tests)"
      commands:
        - cmd: "cd server && npx tsc --noEmit"
          status: pass
        - cmd: "cd server && npx vitest run"
          status: pass
          detail: "1627/1627 (+3 FU3)"
        - cmd: "cd server && npm run metrics"
          status: pass
          detail: "employed net-worth seed7 $3.6k@23 -> $56.8k@63 -> $35.5k@74 drawdown; bounded, never negative; offline digest delta non-zero over multi-day span"
      summary: "Drove employment in the sim via REAL applyForJob (graduate 22, retire 65) so finance.ts runs. Net-worth curve already HEALTHY -> measured + asserted, did NOT over-tune constants. FU3 fixed: applyProratedFinances prorates the leftover sub-week in offline processing so multi-day absences yield a real digest money delta (whole-week Monday ticks counted to avoid double-count)."
      pm_flags:
        - "Reward cadence reads ~0 in HEADLESS sim only (reward msgs flow through handlers, not the autonomous loop); real first_job achievement fires in-memory. Measurement artifact, not a reward bug."
        - "Diamonds can trend NEGATIVE in sim: event choices spend diamonds the character can't afford. Possible premium-currency clamp/affordability-gate issue -> flag for a later look (not money/finance)."
        - "Economy constants left as-is (already healthy once employment driven) — correct restraint per stop_if."
  - id: T006
    type: worker
    assignee: Worker
    status: done
    reasoning_hint: medium
    objective: "CLIENT FOLLOW-UP 2 (FU2): mid-session death must switch to DeathView WITHOUT a reconnect. Today DeathView only appears after a reconnect pushes a playerObject with c.status=='dead'; when death happens mid-session the server emits lifeSummaryEvent but the client doesn't transition. FIX: read how the iOS client routes to DeathView (ContentView keys on player.status/person.status == 'dead') and how lifeSummaryEvent is handled (GameStateStore/WebSocketService from the prior goal). Make the client transition to DeathView immediately on the in-session death signal — preferred: when GameStateStore receives lifeSummaryEvent (or a death status), set the player/person status to 'dead' (or a dedicated isDead flag the routing reads) so ContentView routes to DeathView right away. If the client genuinely needs a status signal the server doesn't send, add a minimal server emit (e.g. ensure the death path sends an updated player status / a death message) — keep it tiny and within allowed_files. iOS PRIORITY; mirror to Android if the parity is a clean change, else flag Android as a follow-up. Verify: iOS build green; ideally drive a sim/seeded death in-app and confirm the immediate transition."
    allowed_files:
      - "ios/lichunWebsocket/ContentView.swift"
      - "ios/lichunWebsocket/Core/Services/GameStateStore.swift"
      - "ios/lichunWebsocket/WebSocketService.swift"
      - "ios/lichunWebsocket/Core/Models/LifeSummary.swift, Player.swift, Person.swift (only if a status/isDead field is needed)"
      - "ios/lichunWebsocket/Features/Character/Views/DeathView.swift (only if needed)"
      - "server/src/game/PlayerSession.ts and/or services/health/health_manager.ts (ONLY a minimal death-status push if the client needs it; do not change death logic)"
      - "android/app/src/main/** (Android parity, if a clean mirror)"
      - "server/tests/** and/or ios test target (a small assertion if a build-included target supports it)"
    verify:
      - "xcodebuild -project ios/lichunWebsocket.xcodeproj -scheme BaoLife -sdk iphonesimulator build CODE_SIGNING_ALLOWED=NO  (** BUILD SUCCEEDED **)"
      - "if a server emit was added: cd server && npx tsc --noEmit + npx vitest run (>=1627)"
      - "in-session death routes to DeathView without reconnect (in-app or test evidence)"
    stop_if:
      - "Need files outside allowed_files (PM sets)."
      - "Requires changing death LOGIC (not just the client transition / a status emit) — coordinate via PM."
      - "iOS build fails twice (mark blocked; exact error)."
      - "Android parity isn't a clean mirror — do iOS, flag Android as a follow-up."
    receipt:
      result: done
      changed_files:
        - "ios/lichunWebsocket/Core/Services/GameStateStore.swift (handleLifeSummaryEvent sets person.status='dead')"
      commands:
        - cmd: "xcodebuild ... -scheme BaoLife ... build"
          status: pass
          detail: "** BUILD SUCCEEDED ** (PM re-verified)"
      summary: "FU2 fixed iOS-side (client-only): on lifeSummaryEvent, GameStateStore sets person.status='dead'; nested objectWillChange republishes -> ContentView.resolveAppRootDestination routes to .death immediately (recap arrives in the same event). No server emit needed (server already sets c.status='dead' in handleDeath). Confirmed server only emits lifeSummaryEvent on mid-session death (PlayerSession.ts:320-345), no fresh playerObject -> hence the prior reconnect-only behavior."
      pm_flags:
        - "ANDROID death-routing BUG found (-> T007): MainNavigation.kt:159-164 routes on top-level player.status, but that stays 'playing' after death (only c.status/person.status -> 'dead'). So Android death routing keys off the WRONG field AND has the same lifeSummaryEvent gap. Needs (a) route on person.status=='dead' + (b) set person.status on lifeSummaryEvent. Real correctness bug, not just parity."
  - id: T007
    type: worker
    assignee: Worker
    status: done
    reasoning_hint: medium
    objective: "ANDROID death-routing parity + correctness fix (mirrors FU2 + fixes a real Android bug). Two parts in android/app/src/main/: (1) MainNavigation.kt (~:159-164) currently routes to DeathKey on the TOP-LEVEL player.status=='dead' — but the server keeps top-level player.status=='playing' after death and only c.status/person.status becomes 'dead'. Change the death-routing LaunchedEffect/condition to key on person.status=='dead' (the field actually set from c). (2) WebSocketManager.kt handleLifeSummaryEvent (~:1374-1377) currently sets only _lifeSummary — also set the person status to 'dead' so a MID-SESSION death transitions to the death screen without reconnect (mirror of the iOS fix). Verify both reconnect-death AND mid-session-death route to the death screen. Preserve the death recap / New Life flow. Build green."
    allowed_files:
      - "android/app/src/main/java/com/craigvg/lichun_android/ui/navigation/MainNavigation.kt"
      - "android/app/src/main/java/com/craigvg/lichun_android/network/WebSocketManager.kt"
      - "android/app/src/main/java/com/craigvg/lichun_android/viewmodel/GameStateViewModel.kt (only if the status flag lives there)"
      - "android/app/src/main/java/com/craigvg/lichun_android/domain/models/*.kt (only if a status/isDead field is needed)"
      - "android/app/src/test/** (a small routing/handler test if practical)"
    verify:
      - "cd android && ./gradlew :app:assembleDebug  (BUILD SUCCESSFUL)"
      - "cd android && ./gradlew :app:testDebugUnitTest  (if tests touched)"
      - "reconnect-death AND mid-session-death both route to the death screen (reason precisely + any test)"
    stop_if:
      - "The routing-field change breaks another screen's routing (verify the status fields' meaning first; escalate if entangled)."
      - "Need files outside allowed_files, server/, ws/, ios/."
      - "gradle build fails twice (mark blocked; exact error)."
    receipt:
      result: done
      changed_files:
        - "android/.../ui/navigation/MainNavigation.kt (route DeathKey on person.status=='dead' not top-level player.status; daily-rewards guard too)"
        - "android/.../network/WebSocketManager.kt (handleLifeSummaryEvent sets _person.status='dead' for mid-session transition)"
        - "android/.../test/.../WebSocketManagerDispatchTest.kt (+2 tests: reconnect + mid-session death routing)"
      commands:
        - cmd: "cd android && ./gradlew :app:assembleDebug"
          status: pass
          detail: "BUILD SUCCESSFUL"
        - cmd: "cd android && ./gradlew :app:testDebugUnitTest (WebSocketManagerDispatchTest)"
          status: pass
      summary: "Android death routing fixed: was keyed on top-level player.status (stays 'playing' after death) -> now person.status=='dead' (set from c on reconnect, and now set by handleLifeSummaryEvent for mid-session). Both reconnect-death and mid-session-death route to DeathScreen. Real correctness bug + FU2 parity. DeathScreen/startNewLife untouched."
  - id: T008
    type: worker
    assignee: Worker
    status: done
    reasoning_hint: high
    objective: "IN-APP CONFIRMATION of the feel/pacing fixes (the oracle's eyes-on check). Build+launch BaoLife in the sim against the local server (--ws-url ws://localhost:8001); use LOCAL-DEV-DB test-state seeding (local MySQL lifesim only; NO product-code edits). Confirm in the running app: (1) SURVIVAL/MEALS — a played character does NOT spiral to starvation; hunger/thirst drop at meal times (the new meal sink) and stay in a healthy band over a stretch of fast-forwarded play (screenshot hunger/thirst before/after a meal or over a few in-game days). (2) CADENCE — events arrive at a digestible rate, not a wall and not dead-air, over fast-forwarded play (note the felt rhythm). (3) FU2 mid-session death -> seed a high-age character, let death fire WHILE CONNECTED, and confirm the app switches to DeathView IMMEDIATELY without a reconnect (the prior bug) — screenshot. (4) FU3 offline digest -> backdate updated_at ~2-3 days in the local DB, reconnect, confirm the welcome-back digest shows a NON-ZERO money delta now — screenshot. For each: CONFIRMED-IN-APP (screenshot path) or NEEDS-MANUAL + exact blocker. Append '## In-app confirmation (T008)' to notes/PROGRESS.md (or a notes/T008 file) with results. Honest; real screenshots only."
    allowed_files:
      - "docs/goals/baolife-feel-pacing/notes/ (new T008 confirmation notes / append to PROGRESS.md)"
      - "scripts/** (throwaway DB-seed + idb drivers)"
      - "(Read-only on product code. LOCAL dev DB writes for test state only; never production.)"
    verify:
      - "FU2: mid-session death routes to DeathView WITHOUT reconnect — screenshot"
      - "FU3: offline digest shows non-zero money delta — screenshot"
      - "survival/meals + cadence feel confirmed over fast-forwarded play"
    stop_if:
      - "Local MySQL/sim unavailable — record blocker; note the fixes are verified via tests/metrics + build even if not re-screenshotted."
      - "Would need product-code edits (do NOT)."
      - "A real product bug reappears (STOP, report it)."
      - "idb can't drive a step after ~3 attempts — record blocker, keep confirmed points."
    receipt:
      result: done
      changed_files:
        - "docs/goals/baolife-feel-pacing/notes/T008-inapp-confirmation.md (new; verification evidence — no product code touched)"
      commands:
        - cmd: "build+launch BaoLife --ws-url ws://localhost:8001 + local-DB seed + idb drive"
          status: pass
          detail: "4/4 in-app confirmations; 24 screenshots /tmp/baolife-feel/"
      summary: "IN-APP 4/4 CONFIRMED (iPhone 17 sim, local server). FU2: char died at 121 WHILE CONNECTED -> app switched to DeathView immediately, no reconnect (screenshots 12/13). FU3: backdated 3 days -> connect ran processOfflineTime(4320min) -> 'Welcome Back / 3d away / Money +$60', balance $3000->$3060 (14/15). SURVIVAL: seeded hunger/thirst 90 pre-lunch -> after a day of meals saved state hunger18/thirst24, alive health91 (23). CADENCE: one in-game day = 4 distinct single prompts spaced by minute-ticks, never bursting/silent (16-24). Notes: notes/T008-inapp-confirmation.md; 24 screenshots /tmp/baolife-feel/."
      observations:
        - "Cosmetic: FU3 digest 'while away' line said 'You have died!' from a rare offline death roll while saved end-state was alive — cosmetic to the money-delta check."
  - id: T999
    type: pm
    assignee: PM
    status: done
    reasoning_hint: high
    objective: "FINAL AUDIT: map before/after simulator metrics + the 3 named follow-ups + green suite + an in-app pacing session to the oracle. Confirm the felt experience is right (bands healthy, no new pathology), not just that numbers moved."
    receipt:
      result: done
      decision: complete
      full_outcome_complete: true
      rationale: "Oracle fully satisfied. SURVIVAL: meal hunger/thirst sink wired both loops -> deaths now NATURAL ~70s (was forced starvation 29-40); death curve recalibrated (median ~80). CADENCE: per-day emit gate (peak 17->1/day) + cooldown day-basis fix + ambient repeatables -> mid-life dead-air 491/498 -> 0/498; steady ~1/week digestible. ECONOMY: drove employed character in sim, net-worth curve healthy ($0->~$57k, bounded, recoverable dips) -> measured + asserted, no over-tune. 3 FOLLOW-UPS done: FU1 (cadence), FU2 (mid-session death->DeathView immediate, iOS + Android), FU3 (offline digest money delta non-zero). BONUS: caught+fixed 2 latent bugs (wrapping-dayOfYear cooldown suppression; Android death routing keyed on wrong field). VERIFICATION: tsc clean; vitest 1627 (was 1621, +6 net); iOS BUILD SUCCEEDED; Android BUILD SUCCESSFUL; in-app 4/4 CONFIRMED eyes-on (FU2 immediate death transition, FU3 +$60 digest, meals 90->18 hunger, one-at-a-time cadence). No dimension left pathological; no pathology traded for another."
      before_after:
        - "Longevity: starvation death 29/40/32 -> natural death ~70s (median ~80)"
        - "Event burst: peakSingleDay 17 -> 1"
        - "Dead-air: 491/498 mid-life months zero -> 0/498"
        - "Economy (employed): flat $0 -> $0->~$57k healthy curve"
        - "Tests: 1621 -> 1627 green; iOS+Android builds green"
      remaining_owner_action: "Implementation UNCOMMITTED on this goal. NOTE: the starvation bug also exists in already-deployed prod (players there can't survive past ~35) — this fix should be committed + deployed. Optional follow-ups: diamonds can go negative when event choices cost unaffordable diamonds (premium-currency clamp); reward cadence reads 0 in headless sim only (handler-driven, fires in real play); offline-sim death-vs-alive cosmetic mismatch in the digest 'while away' line."
    inputs:
      - "All done task receipts + before/after metrics"
      - "Last verification (tsc + vitest + in-app)"
      - "Current dirty diff"
    constraints:
      - "Do not implement."
      - "Reject if any dimension is still pathological or a fix is unverified."
      - "Reject numbers-only completion without an in-app pacing confirmation."
      - "Reject stopping only because an in-app check needs device access — a sim+workaround must be attempted."
    expected_output:
      - "complete | not_complete"
      - "full_outcome_complete: true | false"
      - "before/after band comparison"
      - "next task if not complete"
    receipt: null

checks:
  dirty_fingerprint: unknown
  last_verification:
    result: unknown
    task: null
    commands: []
