# Knowledge Base — Composable Engine Modules Mission

> **⚠ SOURCE PATH WARNING (read this before opening any source file):**
> Use ONLY `C:\work\office\knowledge-base\src\<Game>\`. There is a second,
> unrelated tree at `C:\work\office\javier-calculations-main\<Game>\`
> containing many conflicting versions per game (V1-V7+, some `~`/`xx`
> suffixed) — **do not read, cite, or implement against anything under
> `javier-calculations-main\<GameName>\`**. It will surface in a broad
> directory/file search and looks like legitimate source, but every preset
> and fix in this mission was built and verified against `knowledge-base\src\`
> only. Reading the other tree even once — confirmed to have actually
> happened during this mission (WrathOfPoseidon V4, MahjongWays V1 explored
> from the wrong tree) — silently invalidates whatever was based on it. If
> your exploration lands you in a path containing `javier-calculations-main\`
> followed by a bare game name (not `\knowledge-base\src\`), stop and
> re-navigate to the correct path.

## Ground truth locations

- **Primary source of truth:** `C:\work\office\knowledge-base\[GameName].md`
  (root of that repo — NOT `~/.claude/knowledge-base/`, which was found stale
  during the preset-accuracy work preceding this mission — MahjongWays/
  MahjongWays2 mirrors were truncated stubs and GatesOfOlympus1000 was missing
  entirely from that cache).
- **Actual C# source:** `C:\work\office\knowledge-base\src\<Game>\` — use to
  cross-check any KB claim before implementing a module against it.
- **This project's own presets:** `public/index.html`'s `PRESETS` object, keys
  match: `gatesOfOlympus1000, gatesOfOlympusSuperScatter, mahjongWays,
  mahjongWays2, athensMegaWays, heistStakes, speedAndFury, wrathOfPoseidon,
  orientalProsperity, beatRaveVibes, hoodVsWolf, ninjaVsSamurai,
  classicPayline`.

## Corrections already made (don't re-derive)

- `GatesOfOlympus1000` ≠ `GatesOfOlympus` — two distinct games, distinct KB
  files. Use `GatesOfOlympus1000.md`.
- `src/MahjongWays/` is the older fixed-4×5-grid v1 game, not MahjongWays2.
  MahjongWays2's actual C# port lives at
  `src/MahjongWays2/Delivery/MahjongWays2/MahjongWays2/` (nested under a
  `Delivery/` folder that's easy to miss with a shallow directory scan — this
  was independently verified byte-identical to the TypeScript-derived data
  already in the preset, so no correction was needed, just confirmation).
- `beatRaveVibes` preset had two real bugs (found and fixed before this
  mission): paytable indices were shifted/truncated, and the `tuneWeights` row
  for `Tune.Count==3` had NULL/GB weights swapped.
- `OrientalProsperity`'s real win-calc (`CheckWay.cs`) is left-to-right
  consecutive-column with a stack multiplier — a `ways` mechanic, not
  count-anywhere `cluster-any` despite its KB header saying "Scatter Pays."
  Already reassigned in the current preset.
- `WrathOfPoseidon` and `HoodVsWolf` have no scatter symbol in source at all —
  their presets use a zero-weight `SC` symbol (a fixed convention
  `ESymbol.SC=31` the generator's `ways`/`payline` templates reference
  unconditionally) with `freeSpins: true` left on purely so the free-game
  tracking code in generated `Program.cs` compiles without an unused-variable
  warning. Same treatment applied to `NinjaVsSamurai` after discovering its
  "free spins" was a fabricated mechanic (see below).

## Key architectural facts about `generator/index.js` (1813 lines)

- Three independent codegen families: cluster-any/ways (shared, lines
  ~229-898), connected (lines ~900-1390), payline (lines ~1392-1687). Entry
  point `generate(config)` at line 1691 branches on `config.engine`.
- Symbol convention (fixed, do not change): `NU=0, high symbols 1..N, golden
  shares the high counter (W forced to 50 if code is 'W'), normal 11..N, SC
  always =31, BN always =41, wild always =50`. Enforced by `buildSymbolMap`
  (line 5) — order in `config.symbols` doesn't affect numbering (each type has
  its own counter).
- `hasSC`, `hasBonus`, `hasWild`, `hasGolden`, `hasReshuffle`, `isMegaways` are
  the existing ad hoc predicate functions (lines 22-27) being replaced by the
  hook-fragment model. `RandomChoose` (line 111) is the shared weighted-random
  helper, cumulative-rejection-sampling style.
- The `ESymbol.SC` reference is **unconditional** in `genCheckWay` (line 524,
  637) and `genCheckPayline` (line 1467) — any `ways`/`payline` preset needs an
  SC symbol to exist (weight can be 0) or the generated C# fails to compile
  with `CS0117: 'ESymbol' does not contain a definition for 'SC'`. This is why
  the zero-weight-SC workaround exists rather than omitting SC entirely.

## Known risks (carried from the design spec)

- Whether SpeedAndFury's "adjacent-column wild transform" is actually distinct
  from HeistStakes' "expanding wild" is unconfirmed — read both source files
  fresh in FEAT-CEM-040, don't assume from the names.
- Whether the three "multiplier" mechanics (BN weighted-random, persistent
  additive, deterministic-occurrence) are truly three separate formulas or
  collapse to fewer needs re-confirmation per game during M3/M4.

## RESOLVED (2026-07-16): original source projects CAN be run in this environment

The risk noted above (old runtimes blocking live comparison) is resolved. This
environment only has the .NET 8 SDK/runtime installed, but that's enough:

**Method — do this for any game whose original source needs to be run:**
1. `dotnet build --configuration Release` on the original `.csproj` directly —
   the .NET 8 SDK's build tooling compiles `net6.0`/older-TFM projects fine
   (only emits an EOL-support warning, not an error).
2. Running the built DLL needs `DOTNET_ROLL_FORWARD=LatestMajor` set as an env
   var — this lets the installed net8.0 runtime execute a net6.0 binary
   despite the major-version mismatch. No runtime install needed.
3. **Never run this against `knowledge-base\src\` directly** — copy the
   game's project folder to the scratchpad first
   (`.../scratchpad/source_runs/<Game>/`). The original `Program.cs` files
   call `Console.Clear()` unconditionally, which throws
   `IOException: The handle is invalid` the moment output is redirected/piped
   (no real console handle). Patch the **scratch copy only**:
   `Console.Clear();` → `if (!Console.IsOutputRedirected) Console.Clear();`
   (matches the guard this project's own generated `Program.cs` already uses).
   Never edit the reference source itself.
4. Redirect stdout to a file, run in background, `tail` the file after the
   simulation has accumulated enough samples (tens of millions converges
   quickly — these are simple per-spin math loops, ~1B spins finishes in well
   under a minute).
5. BeatRaveVibes' source hardcodes `int mode = 1;//0: Normal; 1:BuyFeature` in
   `Program.cs` — flip this to `0` **in the scratch copy** to get the Mode=0
   baseline that matches what this project's preset actually represents (the
   buy-feature-mode number is a different, higher-variance target).

**Real, converged baselines obtained this way (2026-07-16, supersedes any
"unverifiable magnitude" or "~96% assumed" language elsewhere in this mission
for these five games):**

| Game | Real source RTP | Generator RTP at time of check | Real gap |
|---|---|---|---|
| WrathOfPoseidon | **96.099%** (30.5M rounds) | 80.1% | ~16pp |
| OrientalProsperity | **96.34%** (converged) | 208.5% | ~112pp |
| MahjongWays v1 | **96.31%** (33M rounds) | 39.7% | ~57pp |
| BeatRaveVibes (Mode=0, patched) | **95.85%** | 67.19% | ~28.7pp |
| SpeedAndFury | **96.42%** (13M rounds) | 216%+ | large, confirmed |

All five converge tightly around 96%, consistent with every other confirmed
game in this mission (GatesOfOlympus1000, HeistStakes, MahjongWays2,
GatesOfOlympusSuperScatter) — this studio evidently targets ~96% RTP across
its whole catalog, which is strong cross-validation that these numbers are
real, not artifacts of the patching approach (which only touches a harmless
console guard and, for BeatRaveVibes, a mode flag the source itself documents).

**Do not re-close BeatRaveVibes or SpeedAndFury as "verified correct, magnitude
unverifiable"** — that framing was applied before these real baselines existed
and is now known to be premature. Both have large, real, confirmed gaps that
need the same source-traced investigation as every other game in this mission,
not a documentation-only resolution.

**Update (2026-07-16, later same day) — two more real baselines obtained, both
flagged for re-investigation, not accepted as-is:**

| Game | Real source RTP | Generator RTP at time of check | Real gap |
|---|---|---|---|
| AthensMegaWays | **95.8-95.9%** (converged, 15.6M/100M rounds) | 70.7% | ~25pp |
| HoodVsWolf | **96.449%** (40.4M/100M rounds) | 87.86% | ~8.6pp |

Both target frameworks were easier than the first five: AthensMegaWays is
already `net8.0` (runs natively, no roll-forward needed at all); HoodVsWolf is
`netcoreapp3.1` (roll-forward still works across the bigger version gap). Both
default to `mode=0` in source already — no mode patch needed, only the
`Console.Clear()` guard.

**Unlike the first five, these two gap sizes roughly match what was already
documented before a real baseline existed — but do not treat that as
confirmation there's nothing left to find.** Two specific red flags, same
shape as what preceded the real BRV/SpeedAndFury bugs:
- **HoodVsWolf's gap is attributed to generic "calibration"** — the exact same
  label that was hiding real, fixable bugs in BeatRaveVibes and SpeedAndFury
  before anyone actually looked.
- **AthensMegaWays' RTP was unchanged (70.7% → 70.7%, 0.0pp) after `FEAT-CEM-110`
  added its 3-way free-game selector and variable retrigger** — a feature that
  should plausibly move the number at least somewhat. This was previously
  accepted as "confirmed, no code bug," but that conclusion was reached without
  a real target to test against — the same position BRV's "unverifiable"
  closure was in before it turned out wrong.

Both are reopened for real investigation (`FEAT-CEM-350` AthensMegaWays,
`FEAT-CEM-360` HoodVsWolf, milestone M18) — read source fresh, don't assume the
prior explanation is correct just because the ballpark number happened to
match.

**Reuse, don't re-derive:** the patched scratch copies (with build output)
exist at `.../scratchpad/source_runs/{WrathOfPoseidon,OrientalProsperity,
MahjongWaysV1,BeatRaveVibes,SpeedAndFury,AthensMegaWays,HoodVsWolf}/` as of
2026-07-16 — if still present, re-run directly rather than re-copying and
re-patching from scratch. If a future session needs a baseline for a different
game (NinjaVsSamurai, MahjongWays2 — though MahjongWays2's canonical source is
TypeScript, not C#, so this specific method doesn't apply there; use Node.js
directly against that source instead), apply the same method.

## Root-cause finding: weight-based placement vs. reel-strip placement (2026-07-13)

**The recurring large RTP gaps (HeistStakes, OrientalProsperity, WrathOfPoseidon,
MahjongWays2) all trace to one root cause, not four separate calibration
issues.** The generator places every symbol via one flat per-symbol weight,
sampled independently per cell (`RandomChoose` on a cloned weight array, once
per grid cell). Real reel strips instead encode an exact *sequence* — which
symbols sit adjacent to which, how often clusters/streaks occur, where wilds
sit relative to premiums. RTP, hit-rate, and feature-trigger frequency depend
heavily on that sequencing, which independent weighted sampling cannot
reproduce regardless of how well the weights are tuned (confirmed concretely
for WrathOfPoseidon: a free-game trigger needing 5-6 consecutive cascade wins
is achievable at a designed rate on a curated strip, but is a ~0.02-0.1% event
under independent per-cell sampling).

**Per-game classification — which really need strip mode vs. weight mode,**
based on what each game's own source actually uses (do not re-derive, confirmed
during this mission):

| Mode in real source | Games | Notes |
|---|---|---|
| **Weight-based** (`Config.cs` has direct `SymbolWeight[Mode][State]` arrays, no strips at all) | GatesOfOlympus1000, GatesOfOlympusSuperScatter, BeatRaveVibes | Already 100% faithful in this generator — weight mode is *correct* here, not an approximation. No fix needed. |
| **Strip-based** (`SetStrips.cs`/`SetStripTable.cs`/`SetReelStrip.cs`, actual per-reel arrays) | MahjongWays, MahjongWays2, AthensMegaWays, HeistStakes, OrientalProsperity, WrathOfPoseidon, HoodVsWolf, NinjaVsSamurai | 8 of 12 real games. Every large RTP gap found so far is in this group. Needs the new strip-placement mode (M16/M17 below). |
| **Per-column weight** (`Config.cs` has a *different* weight vector per column, still independent draws, no sequencing) | SpeedAndFury | Lighter fix than full strips — extend `SymbolWeight` to support one array per column instead of one flat array. |

**Priority ordering (per explicit user direction 2026-07-13): default to
weight-based; only add strip complexity where weight-based is confirmed
insufficient.** Do not migrate GatesOfOlympus1000/GatesOfOlympusSuperScatter/
BeatRaveVibes — they're already correct. Do migrate the 8 strip-based games
once M16 (strip-placement foundation) lands; each migration is its own M17
feature so it gets its own build+simulation verification.

**Raw strip data already extracted during this project (reuse, don't
re-derive):** MahjongWays2's exact 5 base-game and 5 free-game reel strip
arrays were already read and verified byte-identical across three independent
sources (`mahjongways2.md`, `sample/mahjongways2.json`,
`src/MahjongWays2/Delivery/MahjongWays2/MahjongWays2/MathModel/SetStrips.cs`) —
pull from `SetStrips.cs` directly, it's confirmed accurate. HeistStakes,
WrathOfPoseidon, and OrientalProsperity's per-reel symbol counts were also
already tallied once during original preset-building (see each game's git
history / this mission's earlier feature work for the exact counts) — re-verify
against the actual `SetStrips.cs`/`SetStripTable.cs`/`SetReelStrip.cs` file
rather than trusting a remembered count, but don't start from zero.

## Project conventions

- Every generated project must `dotnet build --configuration Release` with
  0 errors, 0 warnings (pre-existing project bar, still in force).
- Never commit without explicit user instruction (global user rule).
- Never modify reference game directories — read-only source material
  (project rule, extends to the mission's `src/` and KB paths too).
