Numbers from code, words from the model
Every figure on screen comes from a small deterministic engine. Every sentence comes from a game pack: hand-written in testing mode, written by a model in live mode. The two meet through a closed vocabulary of effects and a set of story flags, and nothing else.
The loop
- A board pack for the half-year, assembled from paragraphs whose conditions match what you did before.
- The balance sheet, the half-year P&L and the solvency trend.
- Two decisions, each a request from a named colleague. Four options, one best, some defensible, some poor.
- The resolution: why, what the best call was, what happens next, and the effects the choice sends into the engine.
- The engine rolls the balance sheet forward one half-year. Latent problems surface when they are due.
- At each year end, a letter from the PRA supervisor. After Year 3, the debrief and an ending chosen from your flags and your solvency ratio.
Two channels from a question into the game
A closed list of sixteen keys with a number each. Basis keys book an assumption change as a share of the BEL; latent ones bleed into experience first, then land. Temporary keys run for a number of half-years. Driver keys move a hidden dial. Difficulty scales basis, margin and lapse effects: half size on Easy, three quarters on Medium, full size on Hard.
longevity_basis -0.02 latentTurns: 3
pricing_margin +0.01
capital_addon +5 forTurns: 2
new_business +15 forTurns: 2
dividend_pct 8Each option can set plain-word flags. Later briefings, events, candidate questions, PRA letters, debrief summaries and endings carry conditions over those flags and over engine state.
northgate_cut_matched
"Matched the 12% cut for Northgate."
when: { all: ["northgate_cut_matched"] }
when: { none: ["brightwater_withdrawn"],
state: [{ var: "solvency_ratio",
op: ">=", value: 150 }] }How the questions are kept varied
A cheap model left to choose its own subject writes the same question repeatedly: accuracy against explainability, price against margin, act now against wait for more data. Three things stop that, in order of how much work they do.
- A topic plan, fixed before any model call. Every one of the eighteen question slots is assigned its own sub-topic, drawn without replacement from the areas you ticked and rotated so consecutive questions come from different areas. The two halves of a year are written in parallel and cannot see each other, so a plan made up front is the only thing that can keep them apart. Each half is also told what the other half is covering, and told to leave it alone.
- Every earlier question, quoted back. Years 2 and 3 are written with the full list of stems already asked, and the instruction that a question turning on the same trade-off is a repeat even under different names.
- A deterministic duplicate check. After generation, stems are compared on stemmed content words. Anything above the threshold is dropped, which is why each half-year is written with three candidates when only two are shown.
The same spare candidate is what lets the repair step throw away a question with a broken number in it. Every figure a model writes must be a placeholder the engine can fill, a declared expression over one of those placeholders, or a literal belonging to one treaty or scheme. Anything else is a defect, and a defective question is dropped rather than shown with a hole in it.
Three smaller things are corrected rather than dropped, because they are cheap to fix and expensive to leave. Citations are matched against a vetted list, so a model cannot invent a supervisory statement that does not exist. American spellings are rewritten from a closed word list, chosen over a general rule because a general rule breaks “size” and “analysis”. And a year-end letter that arrives as two letters, each with its own salutation and its own supervisor’s signature, is collapsed back into one.
Exhibits: the thing the question is about
A stem that says “the model is less explainable” asks nothing of the reader. Twelve lines of R with the train/test split below the feature engineering asks them to find it. Some questions therefore carry an exhibit: code with a real bug, an experience table whose excess sits in one band at early durations, a recapture clause that is one-sided, a board paper of true statements chosen to mislead.
Every exhibit is hand-written, and so is its defect and its remedy. That is deliberate. A cheap model asked to write buggy code writes code whose bug is not where it says it is, and nobody notices until someone who knows the field reads it. So the library is fixed; the model is told which exhibit its question is about and what is wrong with it, and writes only the colleague asking and the four options. The exhibit is then attached from the library by slot, never from the reply, so a model cannot change what the reader sees.
The planner deals them out without replacement and at most one per half-year, so no exhibit repeats in a campaign and reading never becomes the whole game. What was wrong, and what a prudent actuary would do about it, appear once you have answered.
Writing your own answer
Each question can carry a box for two or three sentences on what you would actually do. It is marked 0 to 3 at the year end against what the question was testing, not against which option you picked: an answer that argues well for a different call scores as highly as one that agrees. A year averaging 2.5 or better earns a notch of board confidence, and nothing is ever taken away for writing, so the box stays genuinely optional. With no network the marking falls back to an overlap heuristic that says so and caps itself at 2 out of 3.
Everything a model writes is kept in the question log after the game, including the candidates that were never shown and the ones the reviewer threw out, with the repair notes that explain why.
The engine, one page
A stylised UK life reinsurer with a longevity book and a protection book. All amounts in £m. Each half-year the engine does the following, in order, with a seeded random draw so any game replays exactly.
- Latent effects due now land: drivers move, dividends pay, basis gaps book as assumption changes equal to the gap times the book's BEL.
- Hedge ratio h = 0.9 − 0.1 × duration_gap, bounded between 0.2 and 1. Rates follow a mean-reverting walk with a 0.20% half-yearly shock.
- Protection. Premiums 90 × lapse factor × growth. Loss ratio 71% − 200 × unsurfaced morbidity and mortality gaps − 100 × pricing_margin + noise. Claims = premiums × loss ratio. BEL adds 32% of premiums and releases 5%.
- Longevity. Fees 0.40% of BEL. Unwind 1% of both BELs. Payments 4% of BEL, matched by the BEL release. Experience variance = BEL × (0.1 × unsurfaced longevity gap + 0.20% × noise). New business 70 of BEL × growth, valued at (5% + pricing_margin).
- Investments and rates. Income 1.25% on gilts, 1.50% on corporate bonds, 0.50% on cash, plus credit noise. A rate move of Δr changes the longevity BEL by −12 × Δr and the protection BEL by −6 × Δr; assets offset a share h of that. The unhedged remainder hits own funds.
- Expenses 25 × (1 + 0.05 × expense pp) × (1 − 0.05 × expense_efficiency) × staffing drag.
- SCR. Longevity 9% of BEL, protection 25% of BEL, market 7% of bonds plus the unhedged exposure to a 0.50% rate shock, counterparty 10% of retrocession recoverables, expense 10% of annual expenses, operational 2% of BELs scaled by model_risk. Longevity and mortality diversify against each other (correlation −0.25); life and market correlate +0.25. Calibrated so the opening SCR equals the pack's. Capital add-ons scale it. Risk margin is 30% of the SCR.
- Own funds = assets − liabilities. Profit = change in own funds + dividend, by construction: every P&L row is a balance-sheet movement.
The check that keeps it honest
Every P&L row above is also a balance-sheet movement, so one identity has to hold at the end of each half-year:
profit = own funds at the end − own funds at the start + dividend paid
It is not a comment or a convention. The roll-forward computes both sides and throws if they differ by more than 10−6, so a game cannot continue past a half-year whose books do not reconcile. Adding a row to the P&L without a matching movement fails immediately and loudly, which is exactly what you want from an engine that is meant to be trusted with the story’s consequences.
The test suite plays complete games across every company type, every difficulty and many seeds, and every one of those half-years passes through the same assertion. A separate calibration sweep checks that skill still dominates luck: best play and worst play have to end in different places, on every company type, from the same seed.
Sources and testing mode
A game pack is the single data contract: company, characters, strands, flags, turns, letters, endings. Testing mode loads a hand-written pack from the repository and makes no network calls, so every screen can be built and shown offline. In live mode (Phase 2) the chosen model writes a new pack on the fly through OpenRouter, one year at a time, against the same schema; the quality check reviews it; and the hand-written pack is the fallback if a call fails or the token budget is spent.
What this is not
A stylised model for a decision game, not a valuation tool. The formulas are simple on purpose so the consequences of a decision are legible. Company names, characters and figures are invented.