Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions docs/DECISION-LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,33 @@ consequences, owner, and conditions to revisit. Newest first.

---

### 2026-07-31 · D-031 · M0 human-gate readiness: attraction legibility + course-scaled seat duration
- **Status:** provisional · **Owner:** Builder → owners · Branch `fix/m0-human-gate-readiness` off PR #7 head
`e5baa54`; focused PR targets the PR #7 branch. Addresses the PR #7 adjudication's H2 (legibility) + M1 (economics).
- **H-LEG-1 (attraction legibility):** per-segment `SegmentAttraction` (pool/consideration/composition/parties) is
now on `ForecastSnapshot` and `ServiceResult` and rendered pre-service ("Who this plan attracts" table + a
plain-language note, largest-remainder reconciled) and post-service (ATTRACTION funnel). New causal categories
**weak-attraction / wrong-mix / purchase-conversion** (renamed from weak-demand/menu-fit) with disjoint-PState
attribution; wrong-mix is a composition opportunity cost (never double-counted) with a business-consequence line.
- **MED-ECON-1 (course-scaled seat duration):** flat `EatMin=12` replaced by shared `Tuning.SeatEatMin(coursesX10)`
= clamp(6 + 6·courses, 12, 30) → 1/2/3-course = 12/18/24; sim scales by `DistinctCourseCount`, forecast scales
`seatCoverCap` by the captured-mix expected courses. Empirically a premium 3-course meal now turns ~1.85× fewer
tables/seat-hour than a value 1-course meal; per-seat-minute leverage falls ~4× → ~2.5-3×. Only eat time changes
(ticket/kitchen path untouched).
- **Consequence / gate:** `Verdict: Pass-with-notes / Action: Continue` (request focused independent re-review).
146 tests green (added 7 ReadinessTests). No over-correction: premium still wins enthusiast, value wins lunch,
KNOWN_RESIDUAL holds. Favorable side effect: best-generalist regret rose 6%→17% on base 271828182 (reducing
premium seat-leverage shrank the generalist's edge — as the adjudication predicted; not gate-chasing). Goldens
re-baselined (D-032). Report: `reports/m0/M0-HUMAN-GATE-READINESS-REPORT.md`. Do not merge / no human tests / no M1.

### 2026-07-31 · D-032 · Goldens re-baselined for course-scaled seat duration
- **Status:** locked · **Owner:** Builder · Course-scaled dining duration changes seat occupancy → turns/covers/
revenue → checksum. New: Focused Value/lunch `0x53FE6CB73895DFD0`, Premium Craft/enthusiast `0x2DA79E2ACC88CA2F`,
Balanced Competent/social `0x05E65C6E8AD6AF07`. Prior (D-030): `0xFB04479B19656E6B` / `0x198E44A139793975` /
`0x6CBD8F76B46A51E1`. Attraction legibility (H-LEG-1) does NOT affect the checksum (attraction fields and cause
strings are not hashed); the weak-demand→weak-attraction / menu-fit→purchase-conversion renames are cause
strings only.

### 2026-07-30 · D-029 · Menu-responsive demand rewrite: distinct regimes achieved; universal-generalist gate borderline (economic root cause)
- **Status:** provisional · **Owner:** Builder → owners · **Follows** the PR #5 adjudication (D-028's Fail/Rewrite,
refined: the fix is a headless demand-model rewrite, not M1). Branch `rewrite/m0-menu-responsive-demand`.
Expand Down
113 changes: 113 additions & 0 deletions docs/design/M0-HUMAN-GATE-READINESS-CONTRACT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# M0 Human-Gate Readiness Contract (design lock)

**Branch** `fix/m0-human-gate-readiness` off PR #7 head `e5baa54`. Focused PR targets the PR #7 branch
(`rewrite/m0-menu-responsive-demand`). Two authorized corrections only; do NOT reopen `DemandModel.Capture`.
This is committed BEFORE code (§6). Design informed by the read-only A–E investigation (recorded in the PR).

## 1. Authorized findings (from the independent adjudication)
- **H-LEG-1 — Attraction legibility.** Per-segment consideration/captured demand are computed but never shown;
the forecast collapses attraction to one aggregate %, and the autopsy has no "wrong-mix" category.
- **MED-ECON-1 — Course-scaled seat duration.** Flat `EatMin=12` gives premium multi-course covers ~4×
contribution-per-seat-minute (vs ~1.15× per station-minute) because a 3-course dinner holds a seat the same
eating window as a 1-course lunch.

Non-goals (§3 of the assignment) unchanged: no reputation/repeat-visits/marketing/reservations/new
recipes-segments-markets/graphics/etc. No premium-specific penalty. No market/segment NAME branch. Integer/
deterministic only.

## 2. Correction A — attraction legibility

### 2a. Forecast (pre-service)
Add to `ForecastSnapshot` (Result.cs) `IReadOnlyList<SegmentAttraction> SegmentAttraction`, record
`SegmentAttraction(SegmentId Id, string Name, int PoolParties, int ConsiderationBp, int CompositionBp, int AttractedParties)`,
and `IReadOnlyList<string> AttractionNotes`. Populated in `Forecaster.Compute` from the existing `captured` local
(no new `Capture` call): `PoolParties = round(ExpectedArrivals·SegmentMixBp[s]/1e4)`, `ConsiderationBp =
captured.ConsiderationBp[s]`, `CompositionBp = captured.CompositionBp[s]`, `AttractedParties =
round(attempts·CompositionBp[s]/1e4)`. **Reconciliation (largest-remainder):** the last segment's PoolParties =
ExpectedArrivals − Σ(others) and AttractedParties = attempts − Σ(others), so the columns sum EXACTLY to
ExpectedArrivals and to attempts (the aggregate funnel figure the autopsy prints as DemandGenerated).

`TextReport.Forecast` renders a compact "Who this plan attracts (pre-service)" table (segment | pool | appeal% |
mix% | attracted, + total row) using the existing `Pct()` helper and column idiom, placed before Key Assumptions.
At most ONE plain-language attraction note fired (priority order), derived from actual data — never generic.

### 2b. Post-service (autopsy)
Thread the whole `CapturedDemand` into `Finalize` (Simulator currently keeps only `AggregateConversionBp` +
`CompositionBp`; also keep `ConsiderationBp`). Add per-segment attraction to `ServiceResult`: extend
`SegmentOutcome` (or add a parallel `SegmentAttractionActual`) with `PoolParties`, `ConsiderationBp`,
`ArrivedParties` so the autopsy shows **potential → consideration → captured(expected) → arrived → purchased →
covers** per segment and the player can compare forecast captured mix vs actual arrival mix.

### 2c. Causal categories (§11)
Extend `DiagnoseBottleneck` vocabulary from {seating, kitchen, menu-fit, weak-demand} to
{seating, kitchen, **purchase-conversion** (was menu-fit), **weak-attraction** (was weak-demand), **wrong-mix**}.
Attribution reads DISJOINT slices (no double-count): weak-attraction = parties never generated (volume shortfall
below the attraction floor, `Tuning.WeakDemandCaptureFloorBp`); seating = `WalkedSeat`; purchase-conversion =
`NoOrder`; kitchen = `WalkedFood` + comp cents; wrong-mix = a COMPOSITION opportunity cost (below). Every party is
in exactly one terminal PState; wrong-mix is an opportunity signal, not a party bucket, so it cannot double-count.

**wrong-mix** is PRIMARY only when ALL hold: (1) volume adequate (captured ≥ attraction floor — not weak);
(2) seated covers largely ordered (`LostToMenuFit` small — not purchase); (3) kitchen kept pace (`LostToWait`
small, comps modest — not kitchen); (4) the market offered a materially higher-value segment (by pool × budget)
that the plan under-attracted (its `CompositionBp` well below its pool share) while a lower-value segment
dominates arrivals. Business-consequence sentence required (e.g. "you filled the room with value diners in a
market whose enthusiasts would have paid far more"). This uses pool + composition + segment budgets only — no
per-segment contribution accounting, no name branch. Two-tier report preserved: TIER-1 the single largest
measured channel (incl. the new attraction ones); TIER-2 0–2 secondary operational warnings.

## 3. Correction B — course-scaled dining duration
**Model A (base + uniform per DISTINCT course).** Shared primitive in `Tuning`:
`SeatEatMin(int coursesX10) = clamp(EatBaseMin + MulDivRound(EatPerCourseMin, coursesX10, 10), EatMinFloor, EatMaxCeil)`.
Params: **EatBaseMin=6, EatPerCourseMin=6, EatMinFloor=12, EatMaxCeil=30** → 1-course=12 (preserves legacy dwell,
byte-identity for all-1-course services), 2-course=18, 3-course=24 (≈2× the legacy 12, the D4 target). No floats,
no rounding beyond MulDivRound.

- **Sim** (`Simulator.cs`, Cooking→Eating transition, replacing `p.EatUntil = t + EatMin`): `courses =
DistinctCourseCount(p)` = count of distinct `Recipe.Course` among `p.Dishes` (0..3, deterministic, size- and
RNG-independent); `p.EatUntil = t + Tuning.SeatEatMin(courses·10)`. Only the EAT window changes — browse, cook,
ticket, delivery are untouched (`AvgTicketTimeMin` must NOT move).
- **Forecast** (`Forecast.cs`): replace flat `ForecastAvgDwellMin=34` in `seatCoverCap` with
`ForecastNonEatDwellMin + SeatEatMin(expectedCoursesX10)`, where `expectedCoursesX10 = dishesPerCoverBp·10/1e4`
(the existing captured-mix-weighted 1 + P(starter) + P(dessert)). Pick `ForecastNonEatDwellMin` so the
composition-weighted dwell reproduces ~34 at the current markets (calibration preserved); premium/high-course
menus get a longer dwell → fewer seat turns → lower `seatCoverCap` (directional).

Timing separation preserved (§20): kitchen prep / food wait / eating are distinct causes; only eating is scaled.

## 4. Numeric units & rounding (§18)
All timing integer minutes. `SeatEatMin` bounded to [12, 30]. `coursesX10` integer (sim: 10/20/30; forecast:
10 + fractional). `MulDivRound` for the per-course scaling and all bp math. No floating-point authoritative time.

## 5. Authorized files
`Tuning.cs`, `Simulator.cs`, `Forecast.cs`, `Result.cs`, `TextReport.cs`, `M0Content.cs` (only if a Name lookup is
needed — no fixture-economics change), harness `Program.cs` (diagnostics/regression only), and the test files +
new fixtures + reports/docs. **Not** `Demand.cs`/`PriceModel.cs` (no consideration redesign). No new recipes/
segments/markets; no fixture-economics change to prices/costs/appeal.

## 6. Non-goals
Repeat visits, reputation, reviews, loyalty, marketing, reservations, table-turn controls, bussing, course-by-
course service/inter-course stagger (delivery-together stays), per-dish eat animations, new content, graphics,
save, M1. No premium-specific tax; no market/segment-name branch; do not tune to move the generalist past 10%.

## 7. Pass criteria (§28,29,31)
- **Attraction legibility:** per-segment pool/consideration/captured/arrival-mix visible pre- AND post-service;
≥1 plain-language attraction reason; totals reconcile; a wrong-mix/weak-attraction/purchase case each attributes
to the correct stage (controlled fixtures).
- **Directionality:** ≥85% overall / ≥75% per-market & per-family forecast/sim agreement retained; audited price &
seat directions still correct; adding expected courses lowers forecast table turns; more seats help when
seat-bound, neutral/harmful when kitchen-bound.
- **Economics:** premium contribution-per-seat-minute leverage reduced from ~4× toward ~3×; premium still wins
enthusiast; value still wins lunch; a mixed plan still viable in social; multi-course still net-positive.
- **Technical:** 139 prior tests preserved (+ new); determinism; accounting; RNG isolation; forecast immutability;
scope; goldens deliberately re-baselined & documented.

## 8. Fail criteria (§24,27 over-correction)
Premium stops winning enthusiast / goes loss-making; desserts+starters become universally unprofitable;
KNOWN_RESIDUAL flips (generalist destroyed) as a side effect; `AvgTicketTimeMin` moves (change leaked into the
kitchen path); or the duration change is economically inert everywhere (surface honestly, don't force it).

## 9. Timebox & review (§32,35)
12–22 focused hours, ≤2 refinement cycles. If it cannot complete honestly in the box: stop, preserve evidence,
report, request an owner decision — do not auto-start another correction. Deliver the §35 report + open the
focused PR (do not merge; no human tests; no M1). Gate recommendation uses Verdict/Action; `Continue` = request a
focused independent re-review (does not authorize human tests, M1, or merge).
52 changes: 26 additions & 26 deletions reports/balance/distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ _Value-heavy, high-volume, price-sensitive, small parties, early peak. Rewards f

| Strategy | median contrib | p10 | p90 | loss% | avg covers | avg sat | avg ticket | win% |
|---|--:|--:|--:|--:|--:|--:|--:|--:|
| Focused Value | $1682.50 | $1480.00 | $1892.00 | 0% | 170 | 604 | 11m | 93% |
| Premium Craft | $1064.12 | $635.46 | $1549.56 | 1% | 42 | 719 | 17m | 6% |
| Broad Menu | $393.00 | -$121.50 | $933.50 | 15% | 79 | 580 | 19m | 0% |
| Overcapacity | -$572.00 | -$756.00 | -$424.50 | 100% | 23 | 538 | 19m | 0% |
| Focused Value | $1667.50 | $1441.00 | $1826.00 | 0% | 167 | 605 | 11m | 91% |
| Premium Craft | $1071.02 | $635.46 | $1564.16 | 1% | 42 | 717 | 17m | 8% |
| Broad Menu | $550.50 | $19.00 | $1078.00 | 7% | 83 | 576 | 19m | 0% |
| Overcapacity | -$575.00 | -$756.00 | -$424.50 | 100% | 23 | 533 | 19m | 0% |
| Understaffed | -$402.00 | -$529.00 | -$261.50 | 100% | 14 | 512 | 21m | 0% |
| Balanced Competent | -$20.50 | -$429.50 | $388.00 | 53% | 64 | 576 | 19m | 0% |
| Overpriced Weak Execution | -$245.90 | -$522.50 | $219.20 | 76% | 10 | 404 | 26m | 0% |
| Station Bottleneck | -$316.00 | -$473.50 | -$50.50 | 93% | 19 | 603 | 19m | 0% |
| Intentionally Bad | -$118.00 | -$237.50 | $32.00 | 80% | 9 | 548 | 21m | 0% |
| Balanced Competent | $134.00 | -$376.50 | $632.50 | 38% | 69 | 567 | 19m | 0% |
| Overpriced Weak Execution | -$245.90 | -$539.20 | $219.20 | 76% | 10 | 402 | 26m | 0% |
| Station Bottleneck | -$316.00 | -$473.50 | -$44.50 | 92% | 19 | 599 | 19m | 0% |
| Intentionally Bad | -$118.00 | -$237.50 | $32.00 | 80% | 9 | 547 | 21m | 0% |

**Winner:** Focused Value · **profitable strategies (median >= +$150):** 3

Expand All @@ -28,40 +28,40 @@ _Mid-budget social diners in groups, multi-course, moderate patience. Rewards a

| Strategy | median contrib | p10 | p90 | loss% | avg covers | avg sat | avg ticket | win% |
|---|--:|--:|--:|--:|--:|--:|--:|--:|
| Focused Value | $1816.50 | $1461.00 | $2195.00 | 0% | 153 | 722 | 10m | 60% |
| Premium Craft | $1412.94 | $274.46 | $2633.06 | 6% | 70 | 590 | 24m | 36% |
| Broad Menu | $241.50 | -$449.00 | $1153.00 | 30% | 89 | 529 | 25m | 2% |
| Overcapacity | -$578.50 | -$882.50 | -$211.00 | 98% | 33 | 573 | 21m | 0% |
| Focused Value | $1805.00 | $1461.50 | $2174.50 | 0% | 152 | 711 | 10m | 7% |
| Premium Craft | $2623.06 | $769.60 | $3788.42 | 0% | 87 | 579 | 24m | 71% |
| Broad Menu | $1954.00 | $229.00 | $2667.50 | 7% | 119 | 522 | 24m | 16% |
| Overcapacity | -$578.50 | -$882.50 | -$211.00 | 98% | 33 | 556 | 21m | 0% |
| Understaffed | -$421.00 | -$553.00 | -$199.00 | 100% | 17 | 517 | 25m | 0% |
| Balanced Competent | $135.00 | -$419.50 | $943.00 | 36% | 75 | 597 | 22m | 1% |
| Overpriced Weak Execution | -$719.40 | -$986.50 | -$438.50 | 98% | 9 | 402 | 28m | 0% |
| Station Bottleneck | -$521.00 | -$740.00 | -$209.50 | 100% | 20 | 617 | 21m | 0% |
| Intentionally Bad | -$237.50 | -$381.00 | $10.50 | 87% | 11 | 519 | 24m | 0% |
| Balanced Competent | $1226.00 | $59.50 | $2122.00 | 6% | 102 | 567 | 22m | 5% |
| Overpriced Weak Execution | -$687.10 | -$992.50 | -$355.90 | 98% | 9 | 397 | 27m | 0% |
| Station Bottleneck | -$520.50 | -$740.00 | -$209.50 | 100% | 20 | 610 | 21m | 0% |
| Intentionally Bad | -$237.50 | -$381.00 | $10.50 | 87% | 11 | 517 | 24m | 0% |

**Winner:** Focused Value · **profitable strategies (median >= +$150):** 3
**Winner:** Premium Craft · **profitable strategies (median >= +$150):** 4

## Destination Enthusiast Evening (`enthusiast-evening`)

_High-budget enthusiasts, lower volume, high quality expectations, patient. Rewards premium craft and skilled execution._

| Strategy | median contrib | p10 | p90 | loss% | avg covers | avg sat | avg ticket | win% |
|---|--:|--:|--:|--:|--:|--:|--:|--:|
| Focused Value | $394.50 | $95.50 | $691.50 | 2% | 71 | 716 | 10m | 0% |
| Premium Craft | $2313.06 | $845.32 | $3445.88 | 0% | 76 | 583 | 25m | 76% |
| Broad Menu | $1183.00 | -$21.00 | $2072.00 | 10% | 91 | 549 | 25m | 6% |
| Overcapacity | $348.00 | -$106.00 | $998.50 | 13% | 50 | 575 | 21m | 0% |
| Focused Value | $394.50 | $95.50 | $691.50 | 2% | 71 | 715 | 10m | 0% |
| Premium Craft | $3051.48 | $1879.60 | $3839.04 | 0% | 86 | 586 | 24m | 95% |
| Broad Menu | $1860.50 | $997.00 | $2485.50 | 0% | 104 | 543 | 24m | 5% |
| Overcapacity | $348.00 | -$120.00 | $998.50 | 13% | 50 | 562 | 22m | 0% |
| Understaffed | -$188.50 | -$405.50 | $60.00 | 86% | 24 | 480 | 27m | 0% |
| Balanced Competent | $1359.00 | $866.50 | $1695.00 | 0% | 83 | 654 | 18m | 17% |
| Overpriced Weak Execution | -$613.50 | -$903.80 | -$248.80 | 93% | 10 | 344 | 28m | 0% |
| Station Bottleneck | -$153.00 | -$518.00 | $231.00 | 68% | 26 | 588 | 22m | 0% |
| Intentionally Bad | -$166.00 | -$334.00 | $151.50 | 73% | 13 | 468 | 25m | 0% |
| Balanced Competent | $1361.00 | $903.50 | $1759.50 | 0% | 84 | 646 | 18m | 0% |
| Overpriced Weak Execution | -$632.70 | -$932.10 | -$199.50 | 93% | 10 | 340 | 28m | 0% |
| Station Bottleneck | -$149.50 | -$518.00 | $227.50 | 67% | 26 | 582 | 22m | 0% |
| Intentionally Bad | -$166.00 | -$334.00 | $151.50 | 73% | 13 | 466 | 25m | 0% |

**Winner:** Premium Craft · **profitable strategies (median >= +$150):** 5

## Dominance check

- **lunch-rush** best strategy: `Focused Value`
- **social-dinner** best strategy: `Focused Value`
- **social-dinner** best strategy: `Premium Craft`
- **enthusiast-evening** best strategy: `Premium Craft`

Distinct winning strategies across scenarios: **2** of 3. No single strategy wins across all markets: context changes the best plan. ✅
Loading