# T003 — Engaged-worker economy curve (measurement)

Goal: measure the late-game money curve for an ENGAGED worker (one who actually
picks "Work Hard" so `handleJob`'s promotion mechanic can fire) versus the
PASSIVE floor the metrics suite already covers ($1200/mo entry tier, frozen,
~$97k net-worth peak over a 42-year career).

Harness: `server/tests/e2e/lifetime-simulator.ts` (new opt-in `workFocus` option,
wired through the real `setJobFocus` right after `applyForJob`) +
`server/tests/e2e/economy-curve.test.ts`. Seeds [1, 42], start age 18, 160
in-game-year horizon (same shape as `metrics.test.ts`).

## HEADLINE FINDING: the engaged curve is UNMEASURABLE — the promotion mechanic is DEAD CODE in all live paths

Setting `workFocus: 'Work Hard'` does NOT change the curve. The engaged worker's
salary stays pinned at the entry tier for the entire career, byte-for-byte
identical to the passive worker. Root cause confirmed by tracing every path that
touches jobs:

- The promotion/raise/termination mechanic lives in **`job_manager.handleJob()`**
  (`src/services/jobs/job_manager.ts:666`): `>90 performance → promote → raise
  salary`; `<10 → fired`. Work Hard adds +2/week to performance, so it SHOULD
  promote in ~20-40 weeks.
- `job_manager.handleJob` is re-exported via `src/services/jobs/index.ts:35`,
  **but it is NEVER CALLED in any live code path under `src/`.** Verified:
  `grep -rn "handleJob(" src/` finds only the definition and a doc-comment; the
  only live importer of `services/jobs` is `src/handlers/activities.ts`, which
  imports just `applyForJob` and `quitJob` — not `handleJob`. The function is
  exercised ONLY by unit tests (`tests/services/jobs/*.test.ts`).
- ONLINE path (what the simulator drives via `PlayerSession`):
  `PlayerSession.processWeekTick()` (`src/game/PlayerSession.ts:499`) is the only
  weekly hook and calls **just `applyWeeklyFinances(character)`** — no job step.
- OFFLINE path: `LoopManager` → `GameEngine.handleWeeklyUpdates` →
  `GameEngine.handleJob` (`src/game/engine/GameEngine.ts:879`). That private
  method only nudges `happiness` (`±5`); it does **not** call the
  promotion-bearing `job_manager.handleJob`.

Net effect on BOTH live paths: `setJobFocus` correctly persists the focus on the
JobRecord, but nothing ever consumes it. Performance never updates, never crosses
the >90 ceiling, and salary is frozen at the entry rung for life — regardless of
focus or career.

This is a likely PRODUCT BUG: the entire career-progression / raise loop appears
inert for real players (promotions never fire in normal play). Surfacing it is
exactly the value of this measurement. Confirming it against the live app and
fixing it (wiring `job_manager.handleJob` into a weekly tick) is a gated
production change that belongs to a later task — out of scope here.

## Captured curves (per-age salary / money / netWorth)

### ENGAGED Accountant (Work Hard), seed 1 — finalAge 75
| age | salary | money | netWorth | occ |
|----:|-------:|------:|---------:|-----|
| 18 | 0 | 0 | 0 | - |
| 23 | 1200 | 4492 | 4492 | work |
| 33 | 1200 | 18836 | 18836 | work |
| 43 | 1200 | 42577 | 42577 | work |
| 53 | 1200 | 66539 | 66539 | work |
| 63 | 1200 | 92980 | 92980 | work |
| 68 | 0 | 92614 | 92614 | retired |
| 75 | 0 | 79864 | 79864 | retired |

entrySalary=1200, peakSalary=1200, salaryTierProgression=[1200],
working-life netWorth trough=0 peak=97764 (age 65) final(~65)=97764.

### ENGAGED Accountant (Work Hard), seed 42 — finalAge 51 (died mid-career)
entrySalary=1200, peakSalary=1200, salaryTierProgression=[1200],
working-life netWorth trough=0 peak=61734 (age 51) final=61734.

### ENGAGED Software Engineer (Work Hard), seed 1 — finalAge 75
Highest-ceiling career (CTO tops at $10000/mo). Top two rungs are prestige-gated
(Software Engineering Manager $6000 minPrestige 100; CTO $10000 minPrestige 300 +
intelligence 75). The character never leaves the ENTRY rung anyway (dead-wired
promotion mechanic).

| age | salary | money | netWorth | occ |
|----:|-------:|------:|---------:|-----|
| 23 | 2000 | 5220 | 5220 | work |
| 33 | 2000 | 24660 | 24660 | work |
| 43 | 2000 | 55695 | 55695 | work |
| 53 | 2000 | 86965 | 86965 | work |
| 63 | 2000 | 120700 | 120700 | work |
| 68 | 0 | 121790 | 121790 | retired |
| 75 | 0 | 109040 | 109040 | retired |

entrySalary=2000, peakSalary=2000, salaryTierProgression=[2000],
working-life netWorth trough=0 peak=126940 (age 65) final(~65)=126940.

### ENGAGED Software Engineer (Work Hard), seed 42 — finalAge 51
entrySalary=2000, peakSalary=2000, salaryTierProgression=[2000],
working-life netWorth trough=0 peak=80690 (age 51) final=80690.

### PASSIVE Accountant (no focus) — control
Byte-for-byte identical to the engaged Accountant (same salary [1200], same
money/netWorth at every age, same peak 97764 @ age 65 seed 1 / 61734 seed 42).
This is the direct proof that `workFocus: 'Work Hard'` currently has NO effect on
the online curve — nothing consumes the focus.

## Salary tier progression — did it climb?

NO. For every trajectory the salary tier progression is a single value:
- Engaged/Passive Accountant: `[1200]` (entry "Junior Accountant"; never reaches
  Accountant $1600 / Senior $2000 / Manager $2400 / CFO $3000).
- Engaged Software Engineer: `[2000]` (entry "Junior Software Engineer"; never
  reaches SWE $3000 / Senior $4000 / Manager $6000 / CTO $10000).

No promotion ever fires. Peak net worth tops out at ~$98k (Accountant) /
~$127k (Software Engineer) at age 65 — driven entirely by the HIGHER ENTRY
salary of SWE ($2000 vs $1200), not by any career progression.

## Catalog reference (salary ceilings, for the tuning discussion)

- Highest cap in the catalog: Software Engineer → CTO **$10000/mo** (prestige-gated).
- Highest entry rung: Software Engineer Junior **$2000/mo**.
- Best NON-prestige-gated ladder top: Registered Nurse Director $5000, Physician
  is prestige-gated, Lawyer is prestige-gated, Accountant CFO $3000, Dentist
  Chief $4200, Pharmacist Director $3600.
- Prestige-gated occupations (top two rungs require minPrestige 100/300, plus
  intelligence 75 on the very top): Software Engineer, Lawyer, Physician.

## Takeaway

Engaged late-game wealth does NOT become meaningful in the current sim — it
plateaus at the same low entry-tier floor as the passive worker (~$60k-$130k
lifetime peak net worth), at the very low end of the $100-$5M shop. But the cause
is NOT economy tuning: the intended "work hard → promotions → raises → meaningful
late-game money" loop is **not wired into any live weekly tick at all.**
`job_manager.handleJob` (promotions/raises) is exported but never called outside
unit tests; `PlayerSession.processWeekTick` only runs finances; and the offline
`GameEngine.handleJob` only nudges happiness. Until that mechanic is connected,
the engaged raise curve cannot be measured or tuned.

Most relevant lever IF action is warranted (ordered):
1. **Wire the promotion mechanic into a live weekly tick (SOURCE-side, the actual
   blocker — likely a product BUG, not tuning):** call `job_manager.handleJob`
   (player, character) from `PlayerSession.processWeekTick` (online) and have the
   offline `GameEngine.handleJob` delegate to it too. Without this, focus / Work
   Hard / career choice have ZERO effect on income, and the entire raise curve is
   inert. This is the gating fix and should precede any economy-constant tuning.
   (Confirm against the live app first via an in-app walkthrough — verify whether
   real players ever actually get promoted before treating this as a hard bug.)
2. **Re-measure the curve AFTER wiring (re-run this probe):** only then can we see
   whether promotions produce meaningful late-game money. Catalog ceilings suggest
   a healthy non-gated career tops at $3-5k/mo (CFO/Director), with $10k/mo CTO
   behind prestige+intelligence gates — that is where the "meaningful vs the shop"
   question actually gets answered.
3. **Salary ceiling / sink balance (secondary, only after 1+2):** if even a fully
   promoted non-gated career ($3-5k/mo) still plateaus low against the $100-$5M
   shop, the lever is either raising top-tier salaries (source) or making
   prestige reachable so the $10k CTO tier is attainable (source/unlock).

## Verification status

- `npx tsc --noEmit`: clean.
- `npx vitest run tests/e2e/economy-curve.test.ts`: passes, prints curves.
- Default (no `workFocus`) harness behavior is unchanged — `metrics.test.ts`
  passive economy assertions still pass (startNetWorth==0, trough>=0,
  peak in ($5k,$5M)).
