# DMAIC 之路 → a world-class e-learning — spec & scope of work

**Date:** 2026-09-02 · **Owner:** Wai Kit (method) / engineering (build) · **Status:** FULL SPEC FOR APPROVAL. All decisions taken (§11). Build is paused at the
foundations listed in §0.2 (nothing member-visible has changed; the D station is untouched) until the
founder confirms §0.1.
**Inputs:** the three review passes of 2026-09-02 (terms · reasoning · eight per-station reports — artifact
「DMAIC 之路 · 首读审稿」), the D-chapter design (「D · 财务规划 章节设计」), the five-day training strategy
(「五天训练 · 整体策略」), and the founder's core-strategy statement quoted in §2.
**Rules this spec obeys:** [content-rules.md](/docs/modules_handbook/main/dmaic-road/content-rules.md)
(12 rules), the road handbook ([readMe.md](/docs/modules_handbook/main/dmaic-road/readMe.md)), GUIDELINES §13–15.

---

## 0 · What this delivers

One product, three layers, one build:

1. **The method, written as rules a screen can enforce** (§2) — ROI ≥ 7–8 %, never above market value,
   positive cash flow, surplus rent → principal, location tiers, RRR — and **one worked case (Ryan v2)** that
   obeys every rule (§3). Today the copy contradicts itself on five of these and the worked case breaks two.
2. **The road, rebuilt as a five-day training** (§4): 起 shrinks to four screens, **D becomes the teaching
   version of the Wealth Planning canvas** (ten screens, one per canvas field), M · A · I teach the core
   strategy's answer (dual-key condo, ROI ≥ 7 %, BMV, Cochrane–Maluri / TRX), C and T lose their
   contradictions, D′ opens for everyone. Every screen is a **sequence of points with one growing diagram**
   (§5), every term defined on first use (§6), every number pinned by a test (§8).
3. **The portal wired to the road** (§7): the D card auto-creates and fills the Wealth Plan; the I card fills
   Step 3; tools unlock per completed card for enrolled trainees (`TrainingAccess`), never for existing
   members; Analyze Property gains the ROI sieve; the Area Guide gains Cochrane–Maluri and the Monash TRX
   campus.

Scope boundaries: no new video course content (the lessons shelf keeps pointing at course 4 / 8); no change
to Landlord Management, Wealth Planning's maths, or PPP; no new payment products (pricing of the training is
a decision, not a build item — §11).

### 0.1 · Requirements traceability — what you said → what gets built

Every requirement stated in the 2026-09-02 conversation, in the founder's words, with the build item that
satisfies it and where the detail lives. This table is the contract: if a line here is wrong, the outcome
will be wrong — say so before the build resumes.

| # | You said | What gets built | Where |
|---|---|---|---|
| T1 | 「first time user … how would they know what 小明's story is? … E stands for what? … what is T? what is 2A1P? … assume the portal user is first time learn about this」 | The first-time reader test + 12 copy rules; every term defined on first use; no forward references; one cast introduced before use; a `terms.js` registry with a CI test that fails on a violation | §1 P1, §6, `content-rules.md` |
| T2 | 「you just simply follow the book but didnt use your reasoning … do you really go through it and understand the logic?」 | Reasoning audit of every paragraph (claim → reason → why? → example to its end → arithmetic → contradiction → 所以); the eight per-station reports' 拦路 items are the P0 list; 26 cross-station contradictions closed; a `roadConsistency.test.js` so they cannot return | §1 P2, §4.2 deltas, §8.1, Appendix A |
| T3 | 「why you cant do like typeform style, every slide show few points with animation and picture」 | Every screen = a sequence of points (headline ≤ 14 chars + one sentence) with ONE diagram that grows point by point; `PointStepper` (`?beat=N&p=M`); ~40 diagram components with a `state` prop; photos are mood bands only; T's 12 unused illustrations placed | §1 P3, §5 |
| T4 | 「each section/chapter to have a detailed report and what/how to improve, not only contents but the way you show」 | Delivered (artifact 首读审稿, eight chapters); its findings are folded into §4.2 station specs and Appendix A | §4.2, Appendix A |
| T5 | 「define?beat=8 suppose to combine with wealth-planning … user go through … understand … fill up in the road stage, then wealthplanning side auto create one profile … auto fill up for him」 | D card completion auto-creates and pins the Wealth Plan (profile + goal filled from the card); Steps 1 · 2 show 「来自你的 D 卡」; later edits update via an explicit button | §7.2, §4.2 D10 |
| T6 | 「under define … talk about ur age (and explain why age is important, eg: DSR impact) … Who signs your paycheck? … An employer / My own business / Resign Mode … salary」 | D1 你几岁 (tenure 35 y vs 20 y, DSR, rounds before the target age) · D2 谁付你薪水 (employer / own business / Resign Mode with its three extra fields; ESBI moves here) · D3 你赚多少、欠多少 (DSR, commitments in four buckets, expenses, cash) | §4.2 D1–D3 |
| T7 | 「theory must highlight and teach what is "What are you chasing?" Passive income / Net equity / Debt-free … let user understand the concept and example, then only goto beat=8」 | D4 你在追什么: three goals, one screen + one example each, and why the course chases passive income (RRR runs on rent) — before the card | §4.2 D4 |
| T8 | 「teach how to fill up "How much passive income per month?" … cover all your expense, then set when is the target」 + 「use 1.5 1st」 | D5: target = monthly living expenses × 1.5, by an age that fits the rounds D1 allows; the card readout and the seed use it | §4.2 D5, R12 |
| T9 | 「when comes to improve, now it is related back to wealth-planning step=3」 | I card completion writes the real unit into the plan's road row (price = psf × sqft, rent, maintenance, completion); Step 3 opens with the I card | §7.3, §7.5 |
| T10 | 「at define, you also need to cover financial projection … step=4 like early settlement of loan principal due to extra rental pay (that's why it is important to make sure u buy a right property)」 | D7 多出来的租金去哪里: recycle 100 % → loan cleared in ~18 y (Ryan v2), interest saved RM 207k; the negative-cash-flow unit has nothing to recycle | §4.2 D7, R4 |
| T11 | 「elearning need to cover DSR and planning which cover step 5 and 6」 | D8 银行肯不肯 (DSR, two ceilings, three dials, KYS / WhoPay) · D9 银行会怎么批 (sequential approval, seasoning, resign line) | §4.2 D8–D9 |
| T12 | 「initial chapter is very important … comprehensive financial planning chapters, then once user understand, then only goto section on what to buy and how to buy」 | 起 shrinks to 4 screens; D grows to 10 screens (≈ 35 min) and is Day 1 on its own; M · A · I · C follow | §4.1, §4.2 |
| T13 | 「we encourage RRR, so cashflow is the key, so we need buy condo dual key, then u wan high growth, buy at a place near to upcoming financial district and education hub (TRX & TRX Monash)」 | Rules R3 (positive cash flow, one standard), R5 (dual-key default), R6 (location tiers, TRX + Monash TRX campus); M · A · I rewritten; Area Guide gains Cochrane, Maluri and the Monash paragraph | §2, §4.2 M · A · I, §7.7 |
| T14 | 「core strategy … minimum ROI 7-8%, then early reduction for loan principle … demonstrate through step=3 … % surplus rent → principal 100%」 | R1 ROI ≥ 7 % (gross yield, target 8 %) taught on I and enforced by an `roi` sieve in Analyze Property + the I card readout; R4 recycle 100 % (canvas default kept, taught on D7) | §2 R1, R4, §7.6 |
| T15 | 「location wise either choose existing very mature, or existing mature and in future gonna be very mature (like Cochrane-Maluri which is near to TRX)」 | R6 two tiers on the A card (`tier`), the four-rung evidence ladder (R7) so an APPROVED future driver counts, Ryan v2's areas = the Cochrane–Maluri corridor | §2 R6–R7, §4.2 A |
| T16 | 「regardless how, you shall not buy above market value and ROI principle still maintain to meet minimum 7-8%」 | R2 hard line at market value (BMV ≥ 0, preferred ≥ 5 %); R1 floor 7 % — both are sieves and card readouts, and Ryan v2 passes both (−9.5 %, 7.68 %) | §2 R1–R2, §3 |
| T17 | 「in the end i wanna do 5 days training, day 1 cover define … wealth plan section 2 4 can access, section 3 cannot access … slowly unlock once they complete this whole thing」 + 「all open first」 | Five-day program in `config/training.php`; enrolment table; `TrainingAccess`; after Day 1 the canvas opens EXCEPT Step 3; Step 3 with the I card; Area Guide / Analyze Property / Landlord / 下一轮 unlock per card; graduation opens everything | §4.1, §7.5, §11.2 |
| T18 | 「the idea is i would have a session to tell what property buy then use this section, then i can upsell property」 | The demo unit in Step 4 is the cohort's admin-chosen catalogue project (synthetic dual-key fallback); Step 3 opens on Day 3 pre-filled with it; Deal Day / Concierge open the same day | §7.2, §11.3, §4.1 |
| T19 | 「all free」 | Self-serve enrolment for any verified lead; no payment product | §7.5, §11.4 |
| T20 | 「AI vibe coding follow ur recommendation」 | AI pages readable from Day 1, taught on Day 5 — not gated | §11.5 |
| T21 | 「Monash TRX = https://www.monash.edu/… future campus」 | Facts (RM 2.8b, 1.5M sq ft, 22,500 students, 2032) in the A case card with sources; the TRX Area Guide entry; the evidence ladder's third rung | §7.7, §4.2 A |
| T22 | 「reading the road stays free; existing members must not lose anything」 (agreed principle) | No stop is ever gated; only enrolled leads are gated; admins bypass; graduates never re-locked | §1 P5, §7.5 |

### 0.2a · Build completed (2026-09-03)

Everything this spec scopes is built on `dev-wk` (one overnight run, seven parallel workstreams +
the core PHP layer; PHP + vitest suites green): R1–R12, Ryan v2 (`utils/road/cast.js` /
`math.js` v2), every station rebuilt typeform-style (`PointStepper` · `VizHost` · `Term` ·
per-point `viz` states · objectives / recap / key terms · check-before-card), D = the Wealth-Plan
canvas (17-field card, auto-open on first completion, 「更新到 Wealth Plan」), the demo unit (cohort
project / synthetic dual-key), the I-card mirror, the five-day program (`config/training.php`,
`training_enrolments`, `TrainingAccess` verdicts on Wealth Planning Step 3 · Area Guide · Analyze ·
New Projects · Landlord Management · D′), the ROI sieve on the New Project listing, the Manage
slot's *Demo project* picker, ActivityLog + Notifier events, handbooks. The section below is kept as
the record of where the build started from.

**Decisions after the verifier's pass (2026-09-03, founder: "follow ur recommendation" ×2):**
- **T's tank is 6 months, not 10.** The tank divides savings by everything that leaves the account
  each month (living expenses + car loan + personal loan = RM 5,800), because the loans do not stop
  when the job does — the same rule D teaches. The book's 10 (savings ÷ expenses) is acknowledged in
  ONE point right after the tank (`takeoffContent.js` `tank-book`), so a reader who remembers the
  book is not handed two numbers without the reason. §3's "10-month tank" row is superseded.
- **§7.5 ops extras:** the Portal Engagement 「训练」 pivot is BUILT (Manage → Portal Engagement →
  Activity → 训练 — the Day-3 session's name list); the two reminder templates
  (day-before-card-missing, 30-days-no-round-two) are DEFERRED until the first cohort has a date —
  they need a cohort (an EventSeries) and a chosen channel before there is anyone to send to.
- Notifier events are `training.enrolled` / `training.graduated` (enrolment is the sales signal;
  D-card completion is already visible on the lead's Road tab).

### 0.2 · Build state right now (so nothing is assumed) — *historical, see 0.2a*

Written on `dev-wk`, **not committed**, and **not wired to any page** — the live site behaves exactly as
before and the D station's content is untouched:

- `config/training.php` (the five-day program + unlock rules) — loaded (config cache refreshed).
- `database/migrations/2026_09_02_180000_create_training_enrolments_table.php` — **run on the live
  database**: `training_enrolments` exists and is empty; nothing reads or writes it yet.
- `src/Journey/TrainingEnrolment.php`, `src/Journey/Repositories/TrainingEnrolmentRepository.php` (+ facade)
  — the model and its only writer; no caller yet.
- `src/Journey/Support/AccessVerdict.php`, `src/Journey/Support/TrainingAccess.php` — the read-only
  verdict service; no gate calls it yet.

Everything else in this spec — the gates, the auto-created plan, the D station, M · A · I · C · T · D′
rewrites, the presentation system, the Area Guide entries, the ROI sieve, the tests — is NOT started. The
build resumes only after §0.1 is confirmed, in the order of §9.

---

## 1 · Principles

| # | Principle | Consequence |
|---|---|---|
| P1 | **The first-time reader test.** A member who has read nothing else must be able to answer every term, letter, name and number from this screen or an earlier one on the path. | Every screen ships through the checklist in `content-rules.md`; the `terms.test.js` first-use test is CI-red on a violation (§6). |
| P2 | **Audit the reasoning, not the vocabulary.** Every paragraph: claim → reason in the previous sentence → where a reader asks "why?" → example carried to its conclusion incl. the downside → arithmetic re-done → no contradiction with an earlier stop → every 「所以」 a real consequence. | The per-station reports' 拦路 items are the P0 list (§9); the cross-station table is a test fixture (§8.4). |
| P3 | **One point per screen, and the picture moves with it.** A screen is a sequence of points (headline ≤ 14 chars + one sentence + one diagram state); a photograph is mood, never explanation. | `PointStepper` + the diagram `state` protocol (§5). |
| P4 | **Cash flow first.** The method's answer to "which property" is the one that pays for itself from day one and pays its own loan down; growth is bought by location, never by accepting a monthly loss. | The rules in §2; the I stage's cash-flow verdict has ONE standard (≥ 0 after maintenance); Ryan v2. |
| P5 | **The road is the key; reading is free, doing unlocks.** No stop is ever gated; tools open per completed card for enrolled trainees only; admins bypass; existing members keep everything they have today. | `TrainingAccess` (§7.5). |

---

## 2 · The core strategy, as rules

> *"our core strategy is, buy something minimum ROI 7-8%, then early reduction for loan principle … choose %
> surplus rent → principal 100% … location wise either choose existing very mature, or existing mature and in
> future gonna be very mature (like Cochrane-Maluri which is near to TRX), regardless how, you shall not buy
> above market value and ROI principle still maintain to meet minimum 7-8%."* — founder, 2026-09-02

Each rule below states the formula, the stop that TEACHES it, the surface that ENFORCES it, and the test that
pins it. Where today's copy or code disagrees, the disagreement is named so the P0 list (§9) can close it.

| # | Rule | Formula / definition | Taught at | Enforced by | Today |
|---|---|---|---|---|---|
| R1 | **Minimum ROI 7–8 %** | **ROI = gross rental yield = annual rent ÷ purchase price** (net price after rebate). Floor **7 %**; target **8 %**. Net yield (rent − maintenance) shown beside it, never instead of it. *Assumption to confirm: "ROI" = gross yield, the figure the market quotes; see §11.* | I · concept (new screen 「ROI 是什么」), D6 (why one property is not enough) | I card readout `roi`; Analyze Property sieve `roi` (new, `config('road.quality.roi_floor') = 7`); WP Step 3 `yieldHint` (exists) | Not defined anywhere on the road; the listing has no ROI sieve. |
| R2 | **Never above market value** | Asking PSF ≤ transacted median PSF (BMV ≥ 0 %); the course's *preferred* entry is ≥ 5 % below. Compare against the area median AND the project's own median (two levels). | I · concept (exists), A · 「成熟区必须 BMV」 | I card `marketValue.result` (exists: pass at ≤ −5 %); listing sieve `bmv` (exists) | Consistent; add the 0 % hard line (above market = fail regardless of the 5 % preference). |
| R3 | **Positive cash flow from month one** | rent − instalment − maintenance ≥ 0, instalment at the member's own MoF, 4 % / 35 y. ONE standard. | I · concept; D7 (why) | I card `cashflow.result` (`gap ≥ 0`, exists); listing sieve `cashflow` (exists) | Copy also says 「租金 ≥ 月供 80 %」 and quiz Q3 marks 80 % wrong — three standards. Delete the 80 % floor. |
| R4 | **Surplus rent → principal, 100 %** | Every month's positive cash flow prepays principal (`recyclePct = 100`, `recycleOn = true`); the loan clears in ~18 y instead of 35 (Ryan v2: 213 months, RM 207k interest saved). | D7 (new screen 「多出来的租金去哪里」), C · 养房 | WP Step 3 recycle control (exists — default already 100 / on); `WealthPlanSeed` keeps it on | Taught nowhere on the road; the road never mentions recycle. |
| R5 | **Dual-key first** | The default type is a residential high-rise **dual-key** unit (two tenants, one title): the only unit type that clears R1 + R3 at KL prices. | M · answer (rewritten) | Listing sieve `dual_key` (exists); M card `type` gains `highrise_dual_key` (default) | M's default answer is 「居住型高楼新项目」 with no dual-key; the road's three sieves omit it while the listing has it. |
| R6 | **Location tiers** | Buy in (a) an **existing very mature** area (demand and price both in place — must be BMV to enter) or (b) an **existing mature area that will be very mature** (demand landed today + an *approved* future driver — Cochrane–Maluri beside TRX: financial district landed, Monash TRX campus approved for 2032). Never in an area whose demand is only sales talk. | A · concept + answer (rewritten with the four-rung evidence ladder) | A card `demand` + new `tier` field (`very_mature` / `maturing`); Area Guide entries | A teaches three rungs (「宣布的不算」) which rejects the course's own default area; Cochrane / Maluri are not in the Area Guide. |
| R7 | **Evidence ladder** | 已落地 (counts) · 在建 (half) · **已批准的宣布** — land + money + partner + date (half, *and* the timeline is priced in) · 销售话术的宣布 (zero). | A · concept | A card `demand_evidence` (new enum) | Three rungs today. |
| R8 | **Supply is a circle, not a name** | Count same-layout units handing over within 3 years inside a 1–2 km circle; two stops one station apart share a circle. Supply-heavy = entry price must be lower; oversupplied = walk away. | A · concept, mistakes | A card `supply` (exists: two tiers already) + 「合并为走廊」 affordance | Ryan's Cochrane 「供应少」 / Maluri 「几千个」 contradict the rule. |
| R9 | **DSR lines** | Bank line 70 %; the course's own: **≤ 60 % at signing for a new project, ≤ 40 % once rented** (the worked case must sit inside them); the bigger lever during construction is clearing commitments, not the raise. | D3, D8, C · 熬 | D card readouts (`dsr`, `dsrAtLine`); C playground `commitments` slider (new) | Ryan signs at 64.5 %; C says 「唯一能动的是收入」. |
| R10 | **RRR — Rent · Recover · Repeat** | Recognised rent (80 % after 12 months' seasoning) repairs DSR → next purchase; a round ≈ 4.5 y (new launch) / ~1.5 y (subsale); age bounds the number of rounds (tenure ≤ 35 y and ≤ age 65–70). | D1, D6, D9, C, D′ | WP Step 6 simulator (exists); D′ unlock | Taught in C; never named on the road; 起 · 4's DMAIC analogy claims a bottleneck method the road does not use. |
| R11 | **Buy before you resign** | The bank lends to a payslip; every purchase in the plan must precede the resign date; A2 income does not enter DSR for ~2 years. | D2, T 7.2 / 7.4 | WP Resign Mode (exists) | C says 「做第二份主动收入 → DSR 下降」— false. |
| R12 | **Passive-income target** | target = monthly living expenses × **1.5** (founder, 2026-09-02); by an age that fits the rounds R10 allows. | D5 | D card readout; `WealthPlanSeed` goal | Ryan's 8,000 has no derivation. |

---

## 3 · The worked case — Ryan v2

One person, one set of numbers, every stop, obeying every rule in §2. Pinned in `math.test.js`,
`takeoffMath.test.js`, `CompletenessTest`, `WealthPlanSeedTest`.

| Field | Value | Rule |
|---|---|---|
| Ryan | 30, engineer, employer-paid (`careerMode = stay_employed`) | R11 |
| Net income · commitments · loans | RM 6,500 · RM 1,800 (car 1,000 + personal 800) · 0 residential | — |
| Living expenses · liquid savings | RM 4,000 · RM 40,000 | R12, cash ceiling |
| Goal | passive income **RM 6,000 / month** (4,000 × 1.5) by **45** | R12 |
| DSR today | 27.7 % · headroom 2,750 · borrowable ≈ 550,000 · ceiling (90 %) ≈ 611,000 | D3 |
| Own line (new project ≤ 60 %) | instalment ≤ 2,100 → loan ≤ 474,000 → **price ≤ 527,000** | R9 |
| Cash ceiling | 40,000 ÷ 10 % = 400,000 (subsale); new launch: rebate covers entry cash | D3 |
| Type · route | residential high-rise **dual-key** · new launch (3 y to VP) | R5 |
| Areas | **Cochrane–Maluri corridor** (tier b: TRX landed + Monash TRX approved 2032; supply heavy → must be BMV) and one very-mature comparator (tier a; rejected on price) | R6–R8 |
| Unit | 650 sq ft dual-key, asking **RM 500,000 = 769 psf** vs area median **850 psf** → **−9.5 %** | R2 |
| Loan · instalment | 450,000 @ 4 % / 35 y → **RM 1,992** | — |
| Rent | 1,700 + 1,500 = **RM 3,200** (two tenants) · maintenance ≈ 250 | — |
| **ROI** | **7.68 % gross** (3,200 × 12 ÷ 500,000) · 7.08 % net | R1 ✓ |
| **Cash flow** | 3,200 − 1,992 − 250 = **+RM 957 / month** | R3 ✓ |
| DSR at SPA | (1,800 + 1,992) ÷ 6,500 = **58.3 %** | R9 ✓ |
| Recycle 100 % | loan cleared in **≈ 213 months (17.8 y)** vs 35 y; interest **RM 180k vs 387k, saved ≈ RM 207k** | R4 |
| Construction | 2026-09 SPA → 2029-12 VP → 2030-03 tenant → 2031-03 rent recognised (2,560 = 3,200 × 80 %); DSR at SPA 58.3 % → cleared commitments 30.6 % → recognised rent 41.8 % | R10 |
| Round 2 | D′ from 2031-03: borrowable ≈ (9,060 × 0.7 − 3,792) × 200 ≈ 510,000; 2nd loan still 90 % | R10 |
| Age lesson | the same 450k loan at 45 (20 y tenure) = 2,727 / month → DSR 69.6 % → declined | D1 |
| T (Takeoff) | same Ryan: expenses 4,000, savings 40,000 → 10-month tank; simulators run *this* unit (rent 3,200 from VP + 3 months, maintenance 250, entry fees included) | T |

Rounding: quote to the nearest RM 1 in copy where the maths produces it (1,992 / 957 / 58.3 %), never
re-round by hand; `math.js` is the source of truth and `content.js` quotes it.

The book's second case (net 4,000, DSR 75 %) keeps its numbers but loses the name: 「课程书里的案例：净收入
RM 4,000 的上班族」. 小明 stays the ESBI restaurant story only. 阿强 stays Ryan's cousin.

---

## 4 · Curriculum — five days, eight stops

### 4.1 The day map

| Day | Stops | Member does | Card(s) | Unlocks (enrolled trainees) | Time |
|---|---|---|---|---|---|
| 1 | 起 (4 screens) + **D (10 screens)** | learns every Wealth Plan field, fills the D card | 起 · D | Wealth Plan auto-created; WP Steps 1 · 2 · 4 (demo unit) · 5 · 6 — everything but Step 3 | 50 min |
| 2 | **M** + **A** | picks type (dual-key), two areas with evidence + supply | M · A | Area Guide; Analyze Property 类型 → 地段 strip | 40 min |
| 3 | **I** | shortlists one unit through four sieves; 带进 I 卡 | I | Analyze Property project list; WP Step 3 (auto-filled); Deal Day / Concierge | 40 min |
| 4 | **C** | nine steps, SPA lines, construction levers, seasoning | C (dates may stay blank) | Landlord Management | 40 min |
| 5 | **T** + **D′** | 1A0P → 2A1P, two OPMs, order law; round-two numbers | T | Vibe Coding · Grant Application; D′; graduation → everything | 45 min |

Cohort delivery adds a live session per day (examples from three trainees' cards + Q&A, taught from
Manage → Leads → Road tab); the unlock rule is identical for self-paced learners.

### 4.2 Station specs

Each station lists: objectives (printed on its first screen with 「约 N 分钟」), screens as **point counts**
(the full point tables live in the per-station reports and the D design; this spec fixes the *set*), the card,
hand-offs, the diagrams, the quiz changes, and the deltas from today. Beat structure: for D the nine-beat
frame is replaced by the ten screens; for M · A · I · C the nine beats stay but every beat is rendered as
points, and the rail order becomes **… 7 真实案例 → 8 检查 → 9 你的答案卡** (check before commit).

#### 起 · Mindset — 4 screens (was 5)

- **Objectives:** say why you walk this road in one number; say why a salaried person can invest without
  resigning (payslip → bank → 1 : 10 leverage → no margin call); say how the road works (8 stops, an answer
  card each, later stops only use earlier cards, Ryan walks with you).
- **Screens:** 1 为什么走这条路 (12 pts: choice → the four things money pressure squeezes → passive income →
  one number → formula 租金 − 月供 − 管理费 → appreciation is a safety margin → **Ryan's character card** →
  「几间才够 → Wealth Planning」 → promise) · 2 杠杆 (11 pts: durian ledger → 10 durians → interest → loss
  → house 1 : 10 → stocks 1 : 2.5 + margin call → no margin call → tree → when the bank gives roots →
  carry what you can) · 3 这条路怎么走 (9 pts: route strip lighting D → M·A·I funnel → C loop → 起 + T → cards
  → Ryan → ~2 h → start) · 4 工具 (10 pts; lock-aware: a locked tool says 「第 N 天打开」).
- **Removed:** the ESBI screen (moves to D2 in full), the DMAIC-as-Six-Sigma screen (the letters are named
  once in screen 3 as an ORDER, with the one transferable discipline 「一次只动一个」).
- **Card:** `acknowledged` (unchanged).
- **Diagrams:** `FreedomLedger`, `DurianLedger`, `LeverageVsStocks` (+ `state`, + drop slider), `DurianTree`,
  `RoadIntro` (PathMap `highlight` + `CardHandoff`), `CharacterCard`.
- **Quiz:** none (a promise, not a number).

#### D · 财务规划 — 10 screens + card (was 9 beats)

- **Objectives:** state your DSR ceiling and your cash ceiling; write financial freedom as a number and a year;
  say why the property must pay for itself.
- **Screens** (points): D0 你会得到一份 Wealth Plan (3) · D1 你几岁 (5, `TenureClock`: 35 y vs 20 y, rounds
  before the target age) · D2 谁付你薪水 (6, `EsbiQuadrant` + state: employer / own business / Resign Mode
  with its three extra fields) · D3 你赚多少、欠多少 (10 + 2 screens: the existing DSR teaching as points;
  commitments in four buckets; **clearing commitments > the raise**; living expenses; cash ceiling) · D4 你在
  追什么 (6, `ThreeGoals`: passive / equity / debt-free, the course chases passive because RRR runs on rent) ·
  D5 每月多少、几岁前 (5, `PassiveTarget`: expenses × 1.5) · D6 几间、几年 (5, `StackTimeline`: one is not
  enough → DSR spent at signing → Recover → Repeat → 3 rounds by 45) · D7 多出来的租金去哪里 (5,
  `RecycleFlow`: +957 → principal → 17.8 y → the negative-cash-flow unit has nothing to recycle) · D8 银行肯不肯
  (the existing MoF / ceiling / quota teaching + three dials + KYS: CTOS / CCRIS / WhoPay; `DsrGauge` with
  40 / 60 / 70 lines + `TwoCeilings`) · D9 银行会怎么批 (5, `DsrTimeline`: sequential approval, seasoning,
  the resign line) · D10 你的卡 = 你的 Wealth Plan.
- **Card (new fields):** `age` (exists, now taught) · `career_mode` · `resign_age` · `living_expenses` ·
  `liquid_savings` · `goal_type` · `car_loan` · `pers_loan` · `cc_min` · `existing_housing` (with
  `commitments` kept as the total for compatibility) · `whopay_url`. Readouts: DSR, headroom, borrowable,
  bank ceiling, **own-line ceiling**, **cash ceiling**, rounds that fit, target = expenses × 1.5.
- **On first completion:** the Wealth Plan is created and pinned (§7.2); the card's save button becomes
  「打开我的 Wealth Plan」; later edits offer 「用这张卡更新 You / Goal」 with a diff.
- **Quiz:** Q1 keep (rename the book's Ryan); Q4 add the cash question (RM 30,000 → subsale max 300,000);
  new Q5 (own line → price ≤ 527,000); new Q6 (clear 1,800 vs raise 1,000 — which lifts borrowable more).
- **Deltas from today:** the 40 % band 「367k–611k」 goes (replaced by the own-line ceiling); 「书里的第一个
  Ryan」 renamed; SPA / BNM / Letter of Offer / PCB glossed at first use; the gauge draws the 40 / 60 lines
  its caption apologises for; `target_income` now drives D5 / D6 and the seed.

#### M · 量类型 — 9 beats as points

- **Objectives:** say the default answer (dual-key high-rise, new launch) and the mechanism behind each choice
  (tenants pay for an address; land earns no rent; the rebate is the only real new-launch advantage and it
  sits inside the price); compute, at the SAME price, what a terrace and a dual-key high-rise each return;
  land the price band inside D's own-line ceiling and write who rents.
- **Concept / answer (rewritten):** three choices → `ChoiceTree` (每条分支标它的代价); 「新项目先于二手」
  keeps ONE reason (rebate → entry cash ≈ 0) and states its cost (financed for 35 y, valuation may lag);
  HDA is why the risk is *controllable*, not why new is better; **dual-key** gets its own screen (what it
  is · two tenant pools · Ryan: 3,200 vs 2,300 → 7.7 % vs 5.5 %); same-price comparison (600k terrace rent
  1,700 vs 600k dual-key 3,200).
- **Card:** `type` options `highrise_dual_key` (default) · `highrise` · `landed` · `commercial`; `route`
  label 「新项目」 (HDA no longer baked into the name); price band checked against the own-line ceiling;
  tenant persona in three fields (works where · commutes how · how many). `HANDS_OVER.measure` gains the
  price band.
- **Playground:** new kind `holding` (same-price landed vs high-rise waterfalls + route switch: cash vs time);
  the old `mof` playground becomes a link back to D.
- **Diagrams:** `ChoiceSwitches`, `TenantPoolDots`, `SameAddressRents`, `TitleVsNameFlip`, `ChoiceTree`,
  `PriceBands`, `MistakeBeforeAfter`, `CashLine`, `OverhangQueue`; `CashflowWaterfall` side-by-side;
  `ConstructionTimeline.guides`.
- **Quiz:** Q2 → the "why cheapest unit" mechanism; new same-price five-year question; new rebate-interest
  question; Q4 → the three price bands (course / D / yours) and who governs.
- **Deltas:** 「名字不是类型」 taught once (five times today); Kajang example once; Ryan's 「留一点空间」
  paragraph replaced by the own-line arithmetic (≤ 527k).

#### A · 选地段 — 9 beats as points

- **Objectives:** sort any "future demand" onto the four-rung ladder; count same-layout supply inside a
  station-centred circle; give each candidate area a tier and an entry condition (按市价 / 须 BMV / 不看).
- **Concept (rewritten):** 先选区，再选项目 → the five sieves named (`FunnelDiagram.sieves`) → demand = people
  earning and spending there → 站的另一头有工作 (`MrtLineStrip`) → **four-rung evidence ladder**
  (`EvidenceLadder`) → tier (a) very mature vs (b) maturing (`TierCards`) → 三年后的租客，今天的需求 → supply is a
  circle (`SupplyRadiusMap`, draggable radius) → Empire Damansara in two beats (demand stuck at 宣布, supply
  landed first) → supply-heavy ≠ walk away; oversupplied = walk away.
- **Ryan v2 (rewritten):** tenant persona → TRX landed (Exchange 106, HSBC · Prudential · Affin, MRT
  interchange) → Monash TRX **approved** (RM 2.8b, 22,500 students, 2032; sources in the case card) →
  Cochrane–Maluri as ONE corridor (1.4 km apart, one circle, supply heavy → 须 BMV) → a very-mature
  comparator rejected on price (600k buys no two-bed in KLCC) → A card done. 「铁律二」 deleted.
- **Card:** `areas[]` gains `tier` and `demand_evidence`; the note field is labelled 「进场条件」; adjacent
  stations prompt 「合并为走廊」; data source everywhere = TEDUH + NAPIC (Area Guide where covered).
- **Area Guide:** add entries **Cochrane**, **Maluri** (KL & Selangor region) and a Monash TRX paragraph +
  2032 timeline to the TRX entry; `config('road.growth_areas')` unchanged (the `trx` pin carries both
  drivers).
- **Cases:** Empire Damansara (BMV measured against transacted median, cash flow after maintenance, the
  ~4.5 % / 30 y rate labelled); TRX + Monash (the corridor case, with the timeline risk stated).
- **Quiz:** new A (two areas one station apart — what's wrong), new B (pipeline-high vs oversupply), Q3
  「课程观察到…」.
- **Playground:** supply-pressure presets (少 / 正常 / 多 → rent 3,400 / 3,200 / 2,900) driving the waterfall;
  intro no longer says the instalment is 「定死的」.

#### I · 选项目 — 9 beats as points

- **Objectives:** separate the three prices and judge BMV; compute ROI and cash flow at your own MoF and get
  ONE verdict per sieve; run the developer checks and write the two predictions.
- **Concept (rewritten):** four sieves named on beat 1 (市价 · ROI · 现金流 · dual-key — developer is the
  final check, not a sieve); `ThreePrices`; BMV is a safety margin, not 「第一天就赚」; **ROI screen** (gross
  yield, the 7 % floor and 8 % target, why gross, net beside it); cash flow with ONE verdict; where rent
  comes from (listing × 0.9, transactions; never GRR); developer last, and why.
- **Ryan v2:** the 650 sq ft dual-key at 769 psf (−9.5 %) · ROI 7.68 % · +957 · developer 观察 · predictions
  rent 3,200 / valuation 552,500 (650 × 850).
- **Card:** `sqft` (number) + `rooms` + `floor` replace the free-text `unit`; price = `asking_psf × sqft`;
  readouts `roi`, `cashflow.gap` (ONE verdict), `marketValue.diffPct`; `predict.valuation` auto-fills from
  sqft × median psf.
- **Playground:** four 「试试看」 presets (negotiate → instalment moves; rent; maintenance; back to M's cap)
  then free play; every slider drives `ThreePrices` + `CashflowWaterfall` + `SieveVerdict` from one
  `improveComputed()` (price from psf × sqft).
- **Cases:** Titiwangsa three owners (one rent per owner; rate/tenure labelled; lesson = the entry price sets
  the instalment that decides whether you can wait); GRR (the RM 250,000 price stated).
- **Quiz:** Q3 → the one-verdict question; new Q6 (negotiate 1 % ≈ RM 24; −391 → −200 needs 8 % — kept as a
  general lesson even though Ryan v2 is positive); new Q7 (ROI: 3,200 on 500k = ?).
- **Deltas:** the 80 % floor, 「接受贴钱」, 「calculated risk」 paragraph and the two-rent Titiwangsa
  paragraph go; 「作者」 → Wai Kit; aiPrompt uses price × MoF, not borrowable.

#### C · 执行 · 养房 — 9 beats as points (answer split into three screens)

- **Objectives:** the nine steps and the question each carries; why DSR jumps the month you sign and the TWO
  construction levers (salary, commitments); the unlock date for the second purchase and what you can borrow
  then.
- **Screens:** concept (8 pts on `PurchaseTimeline` = ConstructionTimeline + DsrTimeline on one axis, gauge
  beside) · answer A 怎么买 (6: booking question, pre-approval, **net price vs median**, showroom, SPA four
  lines, no markup) · answer B 怎么熬 (6: allowance vs actual outflow, progressive interest 180 → 1,992, salary
  lever, **commitments lever 58.3 % → 30.6 %**, A2 rescues cash not DSR, the tightest three months at VP with
  the cash panel) · answer C 怎么守、为什么等 (5: inspect → renovate to the persona, seasoning 80 %, record
  chain, RPGT stairs, not buying is a legitimate next step) · mistakes (以为 / 算式 / 价钱 × 3) · Ryan v2 (8
  pts with a scrubber) · playground (+ `commitments` slider, presets) · cases (`LadCounter`, OPR line) ·
  check · card.
- **Card:** nine steps carry their question as helper text (`BuyingSteps` shared with answer A); the seasoned
  readout is labelled 「假设交楼当月租出」 until `rented_at` is filled.
- **Deltas:** 「唯一能动的是收入」 and 「副业 → DSR 下降」 go; 「第三间起 70 %」 leaves the round-two
  paragraphs; 「全额算承担」 gets 「多数银行如此，课程按最保守的」; Compress Loan and the Bonus methods
  are either explained in one line each or dropped; quiz Q1 → the commitments-lever question; new Q6 (cash
  left at VP).

#### T · 第二引擎 — 7 screens as points

- **Objectives:** compute your tank in months and say whether you are 1A0P; name the two entry tickets and
  why they expire the day you resign; write your cover number.
- **Deltas:** Ryan stays Ryan (expenses 4,000, savings 40,000 → 10 months; `CountdownViz` params); the two
  simulators run Ryan v2's unit (rent 3,200 from VP + 3, maintenance 250, entry fees included, one
  `T_PROPERTY`); 7.5's 18-month wait is about **cash**, not payslips; the badge rule per decision §11.4;
  「只有 property 能用 OPM」 → 「只有房产拿得到 90 %、35 年、没有 margin call 的 OPM」; 「两扇门同时关上」 →
  the quiz's accurate version; 「P 引擎点着了」 → 「P 引擎的说明书读完了」; Pro → 进阶资源; the **12 unused
  illustrations** placed per the T report (ai-office 7.1 · aqiang-rush/fail/nod 7.2 · three-engines 7.3 ·
  pro-* 7.7 · graduation); 7.1's quiz options move after 7.3; KYS moves to D8 (T's card keeps candidates ·
  cover number · chosen path and READS D's expenses / savings).
- **Widgets:** every one of the seven gains a `state` prop and reveals per point; the A2 ramp becomes a slider.

#### D′ · 下一轮 + the hub

- **Hub:** hero rewritten as the nine-point 「这条路怎么走」 (one-time, `localStorage`), chapter rows =
  「站名 · 一句问题 ≤ 16 字 · 你会得到 · 约 N 分钟」 with a badge legend; the D′ row unlocked per
  `TrainingAccess('next_round')` (never locked for non-trainees); count 8 everywhere.
- **Terminal screen:** one name 「下一轮 · D′」 with the subtitle 「买了之后，你的数字会变成什么」; the
  construction outflow shown as TWO lines (bank allowance vs actual progressive interest — new
  `progressiveInterest()` in `math.js`); 「实际」 only on values that are actual (C card gains `loan_amount`
  and `instalment` from the Letter of Offer — decision §11.3); `roundTwoSeed()` adds seasoned rent (80 %)
  once `rented_at + 12` has passed; the goal line reads the member's `target_income`; `RoundTwoDiff` card;
  the four-point DSR curve labelled as four; the not-bought state's buttons land on the C card and Wealth
  Planning.

---

## 5 · Presentation system

### 5.1 `PointStepper` (new, `resources/js/Components/Road/PointStepper.vue`)

- Props: `points: [{ key, title, line, viz?: { kind, state, props? }, image?, interaction? }]`, `screenKey`,
  `modelValue` (active index), `dots: true`.
- URL: `?beat=N&p=M` (extends `Stage.vue`'s beat handling; `p` clamps to the point count; `replaceState` on
  change, `pushState` on beat change — the GlossaryCourse pattern).
- Input: click / tap, → ← , Space, swipe on touch; never when the target is a field or slider (the
  MindsetStepper `isTyping` rule).
- Motion: `<Transition mode="out-in">` on the text; the diagram receives `state` and transitions internally
  via `useVizReveal`; `prefers-reduced-motion` lands on the final state immediately.
- Phone: title + line ≤ 3 lines on top, diagram ≤ 55 vh below, photo (when any) a 120 px band.
- Progress: dots; the beat rail above still shows the beat.

### 5.2 Diagram state protocol

Every viz component that a screen advances takes `state: string` and renders deterministically per state
(no internal timers except the reveal easing). New components (by station):

| Station | New | Existing + `state` |
|---|---|---|
| 起 | `FreedomLedger` · `DurianLedger` · `DurianTree` · `RoadIntro` (+ `CardHandoff`) · `CharacterCard` | `EsbiQuadrant` · `LeverageVsStocks` (+ drop slider) · `DmaicMap` (`letters` / `discipline` / `cards` / `loop` / `tools`) · `PathMap` (`highlight`, `showNext`, `compact`) |
| D | `TenureClock` · `ThreeGoals` · `PassiveTarget` · `StackTimeline` · `RecycleFlow` · `DsrFraction` · `TwoCeilings` | `DsrGauge` (`lines: [{at, label}]`) · `LeverageBars` (`mof`) · `DsrTimeline` |
| M | `ChoiceSwitches` · `TenantPoolDots` · `SameAddressRents` · `TitleVsNameFlip` · `ChoiceTree` · `PriceBands` · `MistakeBeforeAfter` · `CashLine` · `OverhangQueue` | `CashflowWaterfall` (side-by-side, `highlight`) · `ConstructionTimeline` (`guides`, `reveal`) |
| A | `MrtLineStrip` · `SupplyRadiusMap` · `EvidenceLadder` (+ `interactive` sort game) · `DemandDriverIcons` · `TierCards` · `EmpireMap` | `FunnelDiagram` (`sieves`, `state`) |
| I | `ThreePrices` · `SieveVerdict` · `RentSources` · `DeveloperChecks` · `ThreeOwners` / `OwnerBill` · `GrrReveal` · the 谈价 slider | `PsfCompare` · `CashflowWaterfall` (`flow` / `verdict`) |
| C | `PurchaseTimeline` (merges ConstructionTimeline + DsrTimeline on one axis) · `BuyingSteps` (answer + card) · `MarkupBar` · `QuotaTokens` · `LadCounter` · `CashVsDsrBuckets` · `RecordChain` · `RpgtStairs` | `DsrGauge` |
| T | A2 ramp slider · `RoundTwoDiff` (D′) | `CountdownViz` · `DeathValleyChart` · `EngineIgnition` · `TwoDoorsSim` · `EngineSimulator` · `PEnginePuzzle` (engine silhouette, member's own card figures per tile) |

Rules carried over: hand SVG / CSS grid, the validated palette, `role="img"` + `aria-label`, facts in words
never colour alone, `useVizReveal`, all wrapping text in HTML not SVG `<text>`.

### 5.3 Illustration & photo policy

- A photograph is a mood band (point 1 of a screen, 120 px on phone); it never carries an argument.
- T's twelve drawn illustrations are placed exactly as the T report assigns them; no new photography is needed
  for launch. New brief (optional): Ryan's character illustration for `CharacterCard`.
- A diagram states every fact in words on the drawing (the caption never says something the drawing does not
  show — the D gauge's 40 / 60 lines are the canonical example).

---

## 6 · Copy system

- **Rules:** `content-rules.md` (12 rules) is the standard; its pre-ship checklist is applied per screen and
  recorded in the PR description.
- **Terms registry** `resources/js/utils/road/terms.js`: `{ key, en, zh, def, definedAt: '<stage>/<screen>' }`
  for ~45 terms (DSR, MoF, HDA, SPA, VP, LAD, GRR, RPGT, OPR, psf, BMV, ROI, BNM, NAPIC, TEDUH, KPKT,
  CCRIS/CTOS, OPM, 2A1P, 1A0P, RRR, recycle, seasoning, dual-key, Letter of Offer, Deal Day, vibe coding,
  D′, 答案卡 …). Inline `<Term k="dsr">` (dotted underline, popover with en · zh · def).
- **First-use test** `terms.test.js`: walks `CONTENT` in road order (起 → D → … → T → D′ + hub strings) and
  fails when a registered term's first occurrence precedes its `definedAt`; prints
  「SPA 第一次出现在 define/2，定义在 control/2」.
- **Cast registry** `resources/js/utils/road/cast.js`: Ryan v2 (the only protagonist), 阿强, 小明 (ESBI only),
  the unnamed book case; every stop imports from here — no numbers typed twice.
- **Naming table** (one thing, one name): 月供 · 交楼 (VP) · 成数 (MoF) · 签 SPA · 净收入 · 可借额 · 天花板 ·
  答案卡 · 站 (with 「格」 retired from member copy) · 下一轮 · D′.
- **Sources:** 起 · 1 names the book, the course and the author once; case cards cite the article / statute /
  judgement (Monash TRX: The Edge, Grimshaw, Bernama, Monash Malaysia; LAD: PJD Regency 2021; BNM LTV
  2010-11).
- **Course map:** 起 · 3 shows the video course's seven units + Bonus against the eight stops once; lesson
  references thereafter use lesson TITLES (the lessons shelf already prints them).

---

## 7 · Data & integration

### 7.1 Cards (`Src\Journey\JourneyCard::PAYLOAD_FIELDS`, `SaveCardRequest`, `Completeness`, `math.js`)

| Stage | Added / changed | Completeness rule change |
|---|---|---|
| define | `career_mode`, `resign_age`, `living_expenses`, `liquid_savings`, `goal_type`, `car_loan`, `pers_loan`, `cc_min`, `existing_housing`, `whopay_url` (+ keep `commitments`, `age`) | + `goal_type`, `living_expenses`, `career_mode` |
| measure | `type` gains `highrise_dual_key`; tenant persona split into `tenant_works`, `tenant_commute`, `tenant_household` (+ keep `tenant`) | unchanged |
| analyze | `areas[].tier`, `areas[].demand_evidence`; note labelled 进场条件 | unchanged |
| improve | `sqft`, `rooms`, `floor` (+ keep `unit`); readouts `roi`, single cash-flow verdict | + `sqft` |
| control | `loan_amount`, `instalment` (Letter of Offer — decision §11.3) | unchanged |
| takeoff | drop `expenses_monthly`, `savings` (read D); keep the rest | adjusted |

`math.js`: `improveComputed()` takes price from `asking_psf × sqft`; adds `roi`; `cashflow.result` is
`gap >= 0`; `defineComputed()` adds `ownLineCeiling` (60 / 40 by route), `cashCeiling`, `roundsBefore(targetAge)`;
new `progressiveInterest(loan, month, monthsToVp)`; `controlComputed()` reads commitments after the clear-
commitments lever; `roundTwoSeed()` adds seasoned rent. PHP twins (`Completeness`, `Instalment`) updated in
step; `CompletenessTest` fed the same payloads as `math.test.js`.

### 7.2 Wealth Plan auto-creation

- `LearningJourneyRepository::saveCard()`: when the Define card is completed and the cycle has no
  `wealth_plan_id`, build the state with `WealthPlanSeed::fromCards()` inside the same transaction
  (`lockForUpdate()` re-read as the handoff does today) and pin it. The seed maps the new fields
  (careerMode / resignAge / livingExpenses / liquidSavings / four commitment buckets / goal type / goal =
  expenses × 1.5) and adds the **demo unit** row (`source: 'demo'`, `recycle` on) flagged so Step 4 prints
  the 「示范单位 · 第 3 天换成你选的」 banner. The demo unit is **the cohort's chosen catalogue project**
  when the trainee is enrolled in a cohort whose `EventSeries` carries `demo_catalog_project_id` (price /
  psf / rent estimate / maintenance / completion from `ProjectPrefill`, `dealId` set — this is the unit the
  founder presents on Day 3 and the trainee then buys through Step 3 → Deal Day); otherwise the synthetic
  dual-key unit (price = own-line ceiling × 0.9, rent at 7 % gross, maintenance 0.35 psf + 10 %,
  completionYear +3). Admin sets the project on the series (Manage → Events → series edit, a catalogue
  picker).
- Later Define edits never overwrite the canvas; the road shows 「打开我的 Wealth Plan」 and 「用这张卡更新
  You / Goal」 (explicit, with a diff modal) — `POST …/handoff/wealth` becomes that update endpoint.
- `WealthPlanningController@edit` receives `training` (§7.5) and `roadSource` chips per field.

### 7.3 I card → Step 3

On Improve completion, `WealthPlanSync::mirrorImprove()` replaces the demo row (found by
`ROW_JOURNEY_KEY`) with the real unit: `name`, `location` (area), `price` (psf × sqft), `rent`,
`maintenance`, `type`, `completionYear` (new launch +3, subsale 0), `dealId` when the unit came from the
catalogue (`?project=` prefill carries it). Same transaction discipline and `equivalent()` no-op test as the
Control mirror.

### 7.4 C → Step 6 · D′

Unchanged mirror for status / actual; `loan_amount` / `instalment` (when filled) become the row's loan
figures so 「实际」 is actual. D′ reads the pinned plan's Reality run for the round-two seed where present.

### 7.5 Training access

- `config/training.php`: program `five-day` → `days[]` (stages, live-session title) + `unlocks[]`
  (`feature → requires_card, day, locked_copy`) exactly as the strategy's matrix.
- `training_enrolments` (migration `2026_09_xx_create_training_enrolments_table`): `id`, `lead_id`
  (index), `program` (string 30), `event_series_id` (nullable, index — the cohort), `started_at`,
  `graduated_at`, blame columns, timestamps; UNIQUE(`lead_id`, `program`). Model `Src\Journey\TrainingEnrolment`
  (extends `Diver\Database\Eloquent\Model`, `RecordsBlame`); writes only through
  `TrainingEnrolmentRepository` (`enrol()`, `graduate()`), each in `DB::transaction`.
- `Src\Journey\Support\TrainingAccess::verdict(Lead $lead, string $feature): AccessVerdict` — allowed for
  admins, for leads without an enrolment, for graduates; otherwise checks the required card's
  `completed_at` via `RoadPresenter::cards()`; memoised per request. `graduate()` fires when all seven cards
  are complete (checked in `saveCard()`).
- Gates: `LockAnalyzeProperty` (two features: `analyze.strip`, `analyze.projects`), `CoursesController::
  areaGuideState()` (`area_guide`), `WealthPlanningController@edit` (**`wp.step.3` only** → `Edit.vue`
  `stepState` gains `locked` + reason; `StepProperties` renders the demo row read-only when locked; Steps
  1 · 2 · 4 · 5 · 6 open from Day 1), `LandlordManagementController` (`landlord`), `RoadOverview` /
  `RoadController@next` (`next_round`). AI pages are NOT gated (decision §11.5). The two global flags remain
  as emergency switches, default `=0`.
- Enrolment is **free and self-serve**: a 「开始五天训练」 button on the hub (any verified lead) creates the
  enrolment; a cohort registration (EventSeries) creates it with `event_series_id`. No payment product.
- Cohort: an `EventSeries` of five events; enrolling from a registration sets `event_series_id`; the hub
  prints 「Day N · 今晚 8 点直播」 from the series schedule; reminders reuse funnel automation
  (`FunnelAutomationMessage`) with two new templates (day-before-card-missing, 30-days-no-round-two).
- Records: `ActivityLog::TYPE_TRAINING_DAY_DONE` (Learning, coalesced); `Notifier` events
  `training.define_completed`, `training.graduated`.
- Manage: Leads → Show → Road tab header line 「训练 · Day n / 5 · 已开：…」; Portal Engagement → Activity gains a
  「训练」 pivot (enrolled → day 1 … → graduated funnel, median minutes per stop).

### 7.6 Analyze Property

- Sieve `roi` (asking price from psf × sqft, rent estimate from the listing's own rental estimate) with
  `config('road.quality.roi_floor') = 7`; the strip's third step reads 市价 · ROI · 现金流 · dual-key; per-card
  verdict badges show the four; 带进 I 卡 carries `sqft`.
- Both the SQL filter and the PHP badge use one rule (the existing discipline).

### 7.7 Area Guide

- New areas `cochrane` and `maluri` in `utils/areaGuide/malaysia.js` (KL & Selangor): story, facts (MRT
  Kajang line, MyTOWN / IKEA, Sunway Velocity, supply pipeline note), tier (b), the TRX / Monash driver;
  the TRX entry gains the Monash TRX campus paragraph (RM 2.8b · 1.5M sq ft · 22,500 students · 2032) with
  sources. `areaGuideTarget()` then resolves Ryan's areas directly.

---

## 8 · Tests & QA

### 8.1 Automated

- **Vitest:** `math.test.js` (Ryan v2 pins: 1,992 / 957 / 7.68 % / 58.3 % / 213 months / 527k / 400k /
  510k round two), `takeoffMath.test.js` (Ryan v2 params, fees included, both simulators agree),
  `terms.test.js` (first-use), `cast.test.js` (no stop redefines a character's numbers), `stages.test.js`
  (8 stops, minutes present), `Stage.test.js` (`?p=` routing), `PointStepper.test.js`, one render test per
  new viz (every `state` renders without throwing, aria-label present), `RoadOverview.test.js` (lock states,
  legend), `road.test.js` (PathMap count 8, SummaryBar ceiling chip), `newProjects.test.js` (roi sieve).
- **PHPUnit:** `TrainingAccessTest` (feature × admin / non-enrolled / trainee-per-card / graduate),
  `TrainingEnrolmentTest`, `RoadCardTest` (new D fields, sqft), `RoadHandoffTest` (auto-create on first
  completion only; update endpoint diffs), `WealthPlanSeedTest` (new mapping, demo row, ×1.5 goal),
  `WealthPlanSyncTest` (Improve mirror, loan figures), `CompletenessTest` (new rules, same payloads as JS),
  `AnalyzePropertyLockdownTest` → per-trainee, `AreaGuideTest` (Cochrane / Maluri resolve),
  `RoadListingStripTest` (roi sieve = badge), `GrowthAreasConfigTest` unchanged.
- **Cross-station consistency test (new, `roadConsistency.test.js`):** asserts the single passive-income
  formula string, the single protagonist numbers, one timeline (M's route ⇒ T's simulator), one data-source
  phrase in A, no 「铁律」 without a list, hub/PathMap totals equal.

### 8.2 Content QA (manual, per screen, recorded in the PR)

1. First-time reader test (a reviewer who has not read the source lists every term they had to know).
2. Reasoning checklist (claim → reason → why? → example to its end → arithmetic → contradiction → 所以).
3. Points: ≤ 14-char titles, one sentence each, one diagram state each, phone fit.
4. Numbers: every figure quoted from `math.js` output, not typed.
5. Accessibility: keyboard path through the stepper, aria on every diagram, reduced-motion run.

### 8.3 Acceptance

A station is DONE when: all its screens are points; every 拦路 item from its report is closed or explicitly
deferred with a reason; `terms.test.js` and the consistency test pass; a first-time reader (someone outside
the team) completes it without asking what a word means; the card round-trips (save → reload → identical);
its unlock verdicts are covered in `TrainingAccessTest`.

---

## 9 · Rollout — five phases, ~6 weeks

| Phase | Weeks | Deliverables | Acceptance |
|---|---|---|---|
| **0 · Spec approval** | 0 | §0.1 confirmed line by line; §2 rules and Ryan v2 numbers accepted; the ROI definition (§11.1) confirmed | Founder says 「按这份做」 |
| **1 · The gates + the data** | 1 | `config/training.php`, `training_enrolments`, `TrainingAccess`, WP `locked` step state, three gates per-trainee, grandfather rule; D card fields, seed mapping, auto-create with demo row, goal × 1.5; I `sqft`; tests | A pilot trainee sees Steps 3 · 6 locked and Step 4 with the demo unit; an existing member sees no change; all PHPUnit green |
| **2 · Day 1 content** | 2 | 起 (4 screens) + D (10 screens) written as points into `content.js`, rendered with the existing beat/screen components (paragraph fallback) ; Ryan v2 across 起 / D; `terms.js` + first-use test; `cast.js` | First-time reader passes 起 + D; terms test green; D card → plan created |
| **3 · Day 2–3 content + tools** | 3 | M · A · I rewritten (dual-key, ladder, corridor, four sieves, one verdict); Area Guide Cochrane / Maluri / Monash; Analyze `roi` sieve; I → Step 3 mirror | A trainee reaches a real Step 3 row from a listing card; the reports' 拦路 lists for M · A · I closed |
| **4 · Day 4–5 content + business** | 4 | C three-screen answer + commitments lever; T on Ryan v2 + illustrations; D′ two-line outflow, actual loan figures, seeded rent; hub onboarding; cohort = EventSeries; reminders; notifications; Manage pivot | End-to-end: enrol → five cards → graduated → everything open; advisor notified twice |
| **5 · Presentation** | 3–6 (parallel from week 3) | `PointStepper`, the diagram `state` protocol, ~40 components (§5.2), T widget states, phone layouts | Every station's screens advance point by point with a moving diagram; reduced-motion verified; Lighthouse a11y ≥ 95 on a stage page |

Pilot: one cohort of ~20 members starts at the end of week 1 on the OLD content with the NEW gates (the
gates and the auto-created plan are the product; content swaps in a day at a time as phases 2–4 land).

---

## 10 · Metrics (Portal Engagement → 训练 pivot)

- Funnel: enrolled → 起 → D → M → A → I → C → T → graduated; per-day drop; median minutes per stop.
- Plans auto-created; Step 3 rows that are real (vs demo); Deal Day bookings and Concierge briefs from
  trainees; purchases; round-two starts within 90 days of graduation.
- Quality: first-time-reader failures logged per screen during pilot; AI Coach questions per stop (which
  screens generate questions = which screens are unclear).

---

## 11 · Decisions — taken (founder, 2026-09-02) and assumptions

| # | Decision | Answer |
|---|---|---|
| 1 | **ROI definition** | *Assumption still to confirm:* gross rental yield (annual rent ÷ net price), the figure the market quotes; net yield shown beside it. Floor 7 %, target 8 %. If "ROI" means cash-on-cash, R1 and Ryan v2 change. |
| 2 | Which Wealth Plan steps open after Day 1 | **All open first — Steps 1 · 2 · 4 · 5 · 6 — except Step 3 (Properties).** Step 3 opens with the I card (Day 3). |
| 3 | The demo unit in Step 4 | **Yes.** And it is the SALES vehicle: the founder runs a live "which property to buy" session on Day 3, then the trainee uses Step 3 with that unit — the upsell. So the demo unit is an **admin-chosen catalogue project per cohort** (`training_enrolments.event_series_id` → the series' `demo_catalog_project_id`), with the synthetic dual-key unit as the fallback when no cohort / no choice. See §7.2. |
| 4 | Pricing / who can enrol | **All free.** Any verified lead can enrol (self-serve button on the hub, or a cohort registration); no payment product. |
| 5 | AI pages (Vibe Coding · Grant) | **Readable from Day 1, taught on Day 5.** Not gated. |
| 6 | C card gains Letter of Offer loan / instalment so 「实际」 is actual on D′ | Recommended yes; taken as yes (small). |
| 7 | T 7.5 badge rule | (a) 12 consecutive months ≥ expenses with P ≥ 0 — taken as yes. |
| 8 | Supply sieve: gate or condition | Two tiers as `SUPPLY_RISKS` already has them: pipeline-high = condition (BMV), oversupply = gate. |
| 9 | Passive-income multiplier 1.5; Monash TRX = the TRX campus (2032); plan auto-creates on first D completion | Decided earlier the same day. |

## 12 · File map (what changes)

**Backend** — `src/Journey/JourneyCard.php` · `src/Journey/Support/{Completeness,WealthPlanSeed,WealthPlanSync,
RoadPresenter}.php` · `src/Journey/Repositories/LearningJourneyRepository.php` · new `src/Journey/
TrainingEnrolment.php`, `src/Journey/Repositories/TrainingEnrolmentRepository.php`, `src/Journey/Support/
TrainingAccess.php` · `app/Http/Requests/Main/Portal/Road/SaveCardRequest.php` · `app/Http/Controllers/Main/
Portal/{RoadController,WealthPlanningController,CoursesController,AnalyzePropertyController}.php` · `app/Http/
Middleware/LockAnalyzeProperty.php` · `app/Actions/BuildNewProjectListing.php` (roi sieve) · `config/
{road,training}.php` · `database/migrations/2026_09_xx_create_training_enrolments_table.php` · `src/Common/
ActivityLog.php` · Notify event registry.

**Frontend** — `resources/js/utils/road/{content,takeoffContent,stages,math,takeoffMath}.js` · new `terms.js`,
`cast.js` · `Pages/Main/Portal/Road/**` (Stage.vue `?p=`, beats order, Mindset 4 screens, D 10 screens,
Takeoff states) · new `Components/Road/PointStepper.vue` + `Partials/Viz/**` (§5.2) · `Pages/Main/Portal/
WealthPlanning/{Edit,Partials/StepProperties,Partials/StepYou,Partials/StepGoal}.vue` (locked state, demo row,
road-source chips) · `Pages/Main/Portal/Lms/Partials/Road/RoadOverview.vue` (onboarding, rows, legend) ·
`Components/Road/{PathMap,SummaryBar}.vue` · `utils/areaGuide/malaysia.js` · `Pages/Main/Site/newProjects.js`
· Manage: `Pages/Manage/Leads/Partials/Tabs/RoadTab.vue`, Portal Engagement Activity pivot.

**Docs** — `docs/modules_handbook/main/dmaic-road/readMe.md` (sections: five-day training, TrainingAccess,
auto-created plan, the rules table), `docs/modules_handbook/main/wealth-planning/readMe.md` (locked steps,
demo row, road-source chips), `docs/modules_handbook/main/analyze-property/readMe.md` (roi sieve),
`docs/modules_handbook/main/area-guide/readMe.md` (new areas), this spec.

---

## Appendix A · Review findings → where they are closed

| Finding (review artifact) | Closed by |
|---|---|
| Passive-income formula drifts (起 / I / D′) | §6 naming table; `roadConsistency.test.js` |
| 「涨不算」 vs 「第一天就赚」 | R2 wording (safety margin); I rewrite |
| Ryan at 64.5 % vs the 60 % line | Ryan v2 (58.3 %); R9 |
| 3 % negotiation arithmetic | I quiz Q6 (8 %); playground linked to sqft |
| Cash-flow sieve with three standards | R3 (one verdict) |
| M new launch vs T completed unit | one `T_PROPERTY` = Ryan v2's unit; T simulators from VP + 3 |
| 「只有 property 能用 OPM」 | T 7.3 wording |
| Two 小明 / renamed Ryan | `cast.js`; T on Ryan |
| DMAIC analogy vs the funnel | 起 · 3 (letters as ORDER) |
| ESBI story argues for the ladder | D2 with the pivot line |
| Cochrane / Maluri in one circle | R8; A Ryan v2 (one corridor) |
| Area Guide does not cover Ryan's areas | §7.7 |
| 「铁律二」 | deleted |
| 「唯一能动的是收入」 / A2 → DSR | C answer B; R9 / R11; commitments slider |
| D never asks for cash; 40 % band | D3 cash ceiling; own-line ceiling |
| Target RM 8,000 unused | D5 / D6; seed; R12 |
| Terminal screen mislabelled numbers | §4.2 D′ |
| Hub lock vs three open doors | `TrainingAccess('next_round')`; open for non-trainees |
| 「下一格只吃上一格的卡」 | 起 · 3 / hub: 「后面的站只用前面的卡」 |
| 26 first-use terms · 20 glosses · 3 cast collisions | `terms.js` + test; `cast.js` |
| T's 12 unused illustrations | §4.2 T |
| No objectives / minutes / recap | every station's screen 1 and last screen; `stages.js minutes` |

## Appendix B · The review's 15 stances and 34 cross-station conflicts → where each is closed

The review artifact (「DMAIC 之路 · 首读审稿」) is the evidence; this spec is the contract. Every stance
and conflict it raised lands somewhere below. Where the review's own recommendation differs from the
decision, the decision wins (the review page carries the same table under 「决定后的状态」).

### B.1 · Stances

| # | Stance | Decision | Spec |
|---|---|---|---|
| 1 | May the first property be cash-flow negative? | No — cash flow is the key; I's 「接受贴钱」 and the 80 % floor go | R3, P4 |
| 2 | What 「涨不算」 means | Appreciation = safety margin, never income; 「第一天就赚」 → 「安全边际」 | R2 |
| 3 | Ryan vs the 60 % line | Ryan v2: RM 500k dual-key, DSR at SPA 58.3 % | §3, R9 |
| 4 | 40 / 60 as lines; the 「往下 40 %」 band | Lines; the band is replaced by the own-line ceiling (≤ 527k) | §4.2 D8 |
| 5 | Cash on the D card | Yes — `liquid_savings` + cash-ceiling readout | §4.2 D3, §7.1 |
| 6 | Cash-flow verdict standards | One: rent − instalment − maintenance ≥ 0 (the review's three-state idea is dropped) | R3 |
| 7 | T's protagonist | Ryan (v2 numbers); 小明 only in the ESBI story | §3, §6 |
| 8 | T 7.5 badge rule | (a) 12 consecutive months ≥ expenses with P ≥ 0 | §11.7 |
| 9 | A's supply circle | Cochrane–Maluri as one corridor (须 BMV) | R8, §4.2 A |
| 10 | Supply sieve: gate or condition | Two tiers: pipeline-high = condition, oversupply = gate | §11.8 |
| 11 | The two tool locks | Replaced by per-trainee unlocks; non-trainees never locked; flags stay as emergency switches | §7.5, P5 |
| 12 | The 「贴钱」 boundary | Not needed — the first property must be cash-flow positive | R3 |
| 13 | Lock on 下一轮 | Open for non-trainees; trainees after the T card | §7.5 |
| 14 | Which construction number | Two lines: bank allowance + actual outflow (`progressiveInterest()`) | §4.2 D′ |
| 15 | 「实际」 on D′ | C card gains Letter of Offer loan / instalment | §11.6 |

### B.2 · Conflicts

| # | Conflict (who vs who) | Closed by |
|---|---|---|
| 1 | Passive-income formula ×3 (起 / I / D′) | §6 naming table; `roadConsistency.test.js` |
| 2 | 「涨不算」 vs 「果实是租金和增值」 vs 「第一天就赚」 vs refinance | R2 wording; 起 · 1 point 7; I concept |
| 3 | Ryan 64.5 % vs the 60 % line (D / M / I / C) | Ryan v2 (§3) |
| 4 | I narrative 「3 %」 vs playground 「550k → −192」 | I quiz Q6 (8 %); playground driven by psf × sqft |
| 5 | I copy 80 % vs `math.js` ≥ 0 vs quiz | R3 one standard; quiz Q3 rewritten |
| 6 | M new launch vs T completed unit | One `T_PROPERTY` = Ryan v2's unit; simulators rent from VP + 3 |
| 7 | T 7.2 vs 7.5 two numbers for one unit | Same `T_PROPERTY` incl. maintenance + vacancy |
| 8 | T simulators omit M's legal fees | Entry fees included in both simulators (§4.2 T) |
| 9 | 「只有 property 能用 OPM」 vs 起 · 3 stock margin | T 7.3 wording |
| 10 | Two 小明 / Ryan renamed | `cast.js` (§6) |
| 11 | 「P 引擎点着了」 vs D′ 「绝大多数没买」 | T question → 「P 引擎的说明书读完了」 |
| 12 | 7.6 tile hangs 2+2+1 under I; A owns two sieves | T 7.6 tiles: A 选跑道（供应 · 需求）· I 选机位（市价 · ROI · 现金流 · dual-key） |
| 13 | Rebate: M's advantage vs I's GRR / C's markup trap | M answer: rebate's cost stated (financed 35 y, valuation may lag) |
| 14 | A playground 「月供定死」 vs C's OPR lesson | A playground intro rewritten |
| 15 | Data source ×3 (TEDUH / Area Guide / NAPIC); guide gaps | TEDUH + NAPIC everywhere; Area Guide gains Cochrane / Maluri (§7.7) |
| 16 | Cochrane 「供应少」 / Maluri 「几千个」 in one circle | R8; Ryan v2 corridor |
| 17 | 「铁律二」 with no list | Deleted |
| 18 | 「新项目 (HDA)」 label vs the title lesson | M route label 「新项目」 |
| 19 | Three price bands, no governor | M quiz Q4 + `PriceBands` (course band = market · D = ability · yours = intersection) |
| 20 | `HANDS_OVER.measure` omits the price band | M hand-over gains the band |
| 21 | 起 · 4 DMAIC analogy vs the funnel | 起 · 3: letters as ORDER; the one discipline kept |
| 22 | 起 · 5 promises locked tools | Per-trainee unlocks; locked copy names the day (§7.5) |
| 23 | C 「唯一能动的是收入」 vs D's commitments lever | C answer B; C playground `commitments` slider |
| 24 | C 「副业 → DSR 下降」 vs 起 · 2 / T 7.5 | C answer B: A2 rescues cash, not DSR (R11) |
| 25 | 「第三间起 70 %」 inside round-two paragraphs | C deltas (removed from D′ paragraphs) |
| 26 | C durations 3–5 / 4–5 / 4.5 y; salary assumptions ×3 | One timeline (4–5 y); the salary raise labelled an assumption; D′ seed 「用今天的薪水，涨了再改」 |
| 27 | Hub lock vs three open doors | `TrainingAccess('next_round')`; open for non-trainees |
| 28 | D′ 「建筑期净现金流 −2,691」 vs C's progressive interest | D′ two lines (`progressiveInterest()`) |
| 29 | D′ 「实际数字」 on D-card estimates | C card LO fields (§11.6); labels follow the source |
| 30 | 「只预填已认可的租金」 vs `roundTwoSeed()` never adds rent | `roundTwoSeed()` adds seasoned rent once `rented_at + 12` passes |
| 31 | 「用 I 的项目价推算」 vs instalment from M's cap | I `sqft` → price → instalment (§7.1, §7.3) |
| 32 | 「下一格只吃上一格的卡」 is false | 起 · 3 / hub / QuestionBeat: 「后面的站只用前面的卡」 |
| 33 | Total 7 vs 8 | 8 everywhere (hub, PathMap aria, chapter list) |
| 34 | Ryan's rent-recognition month: C 2031-03 vs D′ 2030-12 | One rule: `rented_at + 12` when known, else VP + 3 + 12, labelled 「假设交楼后三个月租出」 |

