diff --git a/docs/superpowers/specs/2026-07-12-character-reuse-seven-arc-design.md b/docs/superpowers/specs/2026-07-12-character-reuse-seven-arc-design.md new file mode 100644 index 0000000..257447d --- /dev/null +++ b/docs/superpowers/specs/2026-07-12-character-reuse-seven-arc-design.md @@ -0,0 +1,323 @@ +# Character reuse & the seven-campaign arc — design + +**Date:** 2026-07-12 +**Status:** approved (direction); implementation = guardrails inside existing milestones, no new milestone work + +Commits the **shape** of character reuse across campaigns, the seven-king meta-arc, +and generation-time difficulty tiering — and names the cheap decisions that must +land inside **M3/M4/M5/M9** and the content build tool so the shape stays +reachable. It authors no content, adds no BOM line, and does not unpark v2 +world generation. + +--- + +## Problem + +A character is born at level 1, plays one authored campaign, and stops. There is +no structure that lets a character grow *across* worlds, and nothing in the +roadmap forbids one — but several unbuilt milestones are about to make choices +that would quietly foreclose it. + +Specifically: **M5 owes a level curve** (the races/classes design says only +"level curve → M5") and **M9 owes a save format**. Both are blank. If M5 picks an +open-ended curve and M9 picks a single save blob, character reuse becomes a +retrofit through the two most expensive systems in the game — the same class of +mistake §10 warns about with the RNG seed. + +This is a **direction spec**. Its deliverable is a shape plus a short list of +constraints, not a feature. + +## Approach + +**One character, seven campaigns, one saga — then it ends.** + +Each campaign faces one of the Seven (`content/lore/the-seven.md`). The character, +their companions, their deeds and their level carry from campaign to campaign; the +*world* does not. Difficulty is chosen once, at world-construction time, from the +campaign index — never during play. + +Replayability lives at the **character** level, not the campaign level: a new +character is a new saga, a new sequence of worlds, potentially a different order +of kings. Seven campaigns is a finite, satisfying arc that a level curve can +actually be designed against. + +Rejected alternatives: + +- **Kings as milestones, not chapters** (campaigns not 1:1 with kings) — no natural + level ceiling; "am I making progress" goes fuzzy. +- **Kings unkillable, truly infinite** (canon-compatible: you only break a king's + grip) — makes the level curve unsolvable, and the running joke turns bleak. +- **Full carry of gear and gold** — the tier-N economy has nothing to sell a party + hauling four campaigns of loot; §3's "counting coppers for an inn bed" grit dies. +- **Event-sourced saga** (deeds are the log; the sheet is derived) — elegant, + overkill. + +--- + +## 1. The state model + +**The canon-log contract does not change.** No schema bump, no API change, the +proxy never notices. Every field this design needs already exists in the log — +`party[].disposition`, `humiliations[]`, `established_facts[]` — and `player` still +admits only `name` / `class_id` / `luck_descriptor`, so the AI never sees a level. +The §7 boundary holds for free. + +What changes is **who fills those fields**. + +### Two objects + +**`Saga`** — durable, survives campaigns. Its own schema, its own save file. + +``` +schema_version, saga_id +character: name, race_id, calling_id, level (1..20), + stats {str, dex, con, fth, mag}, luck_base +companions: [{id, approval}] # Cadwyn, Brannoc +humiliations: [{id, text, weight, turn}] # STACKING across campaigns (§9) +deeds: ["…"] # what the world knows you did +kings_slain: ["ghaul_the_yoke", …] # subset of the Seven +campaigns_completed: 0..7 # tier = this + 1 +signature_item_id: string | null # the one thing that survives the Interlude +``` + +**`Campaign`** — per-campaign, discarded between them: `CanonLog` + `GameState` + +the world content set + the RNG seed (§10). Exactly what M9's save/load was +already going to hold. + +Rejected: **one save file with two sections** (the boundary becomes a naming +convention rather than a structure). The `Saga` is *also* the future input to +world generation — tier, deeds, kings already slain, party composition — so it +wants to be a standalone serializable object on its own merits. + +### Saga invariants + +- **One campaign, one king:** `len(kings_slain) == campaigns_completed`. The saga + advances only by killing a king. +- **The saga is complete at `campaigns_completed == 7`.** There is no campaign 8 and + no tier 8. A complete saga is read-only: it can be viewed, not continued. That + character is done, and the retirement finally takes. + +### The thesis + +> **The `Saga` is the source. The canon log is a projection of the `Saga` into the +> current campaign.** + +This is §11's own rule one level up — the canon log is never the source of truth +for the `Saga`, exactly as AI prose is never the source of truth for state. It +gives every carried thing one uniform mechanism instead of five special cases: + +| Carried | Projected into | Note | +|---|---|---| +| Companion approval | `party[].disposition` | The `Saga` overrides the origin's `disposition_overrides` | +| Humiliations | `humiliations[]` | **Move:** they currently live in the log and would die with it | +| Deeds | `established_facts[]` | Appended alongside the origin's `opening_facts` | +| Level, stats, `luck_base` | `GameState` | Never the log — the AI sees no number (§7) | + +### The seam + +`client/scripts/newgame/new_game.gd` already reads three immutable inputs and +writes two products. It gains **one optional fourth input**: + +```gdscript +static func construct(origin, world, creation, rng, saga: Saga = null) -> Dictionary +``` + +- **`saga == null`** → campaign 1. Roll stats, roll Luck, read dispositions from the + origin. **Today's behaviour, byte-for-byte unchanged.** +- **`saga != null`** → campaign N. Stats / level / `luck_base` come from the `Saga` + instead of the dice; companion approval overrides the origin; deeds and + humiliations are injected; the signature item is the only grant that is not the + origin's. + +One function, one new parameter, a null default that preserves every existing test. +The character-creation UI is simply not shown on the saga path. + +*§2: state. The `Saga` is code-owned. The AI never reads or writes it.* + +--- + +## 2. Tier + +`tier = campaigns_completed + 1`, an integer **1..7** (there is no tier 8 — see +*Saga invariants*). It is an input to world construction, evaluated **exactly +once**, at campaign start. + +> ### The hard rule +> +> **Difficulty is fixed at generation. Nothing scales during play.** +> +> This is the difference between the design and the Oblivion trap. Once the world +> is built at tier 5 it is *fixed* — this dungeon is deadly, that road is safe, and +> the gradient means something. A world that re-levels as the player levels is a +> treadmill, and it makes leveling meaningless. **This is a prohibition, not a +> preference.** + +What tier selects: + +- **Which enemies exist.** Deterministic damage (§7) forbids multiplying a stat + block, and that is a gift: a tier-6 world has *different monsters*, not a rat with + 40× HP. The cosmology already supplies the ladder — **proximity to a king is the + power scale.** Ghaul's tax collector, then his yoked, then a possessed magistrate, + then one of the Sent, then Ghaul. +- **The level band.** Tier N ≈ levels 3N−2 … 3N, capped at 20. The exact numbers are + M5's to tune; the *bands* are the guardrail. +- **Loot, shop stock, and the price table** (§18: the loop is code, the numbers are + content). +- **The king** — one of the Seven, drawn from those not in `kings_slain`. +- **The story-shape.** Tier 1 is a cult cell in a town. Tier 4 is a cult that owns a + city. Tier 7 is the king himself. + +**Content-side cost: one field.** Content entries (`item`, `quest`, enemy units) +carry `tier: 1..7`, validated by the build tool. This is schema, not content — it +does not violate the content-track spec's rule ("never author anything that is not +in the Bill of Materials"). + +*§2: state.* + +--- + +## 3. The Interlude + +A state transition between campaigns: `Interlude(saga, completed_campaign) → saga'`. + +In fiction: **they retired, and it did not take.** They thought campaign 1 was the +end. Here they are at campaign 5, still at it. Brannoc keeps expecting to die on a +job like this one and keeps not dying, and being dragged back out is both the joke +and the tragedy — §9 deepened, not undercut. + +Per §18 this is **two systems**: the transition is *state* (code owns it), the +retirement-that-did-not-take is *text* (the DM narrates it, with an authored +fallback per §13). + +What it does: + +- **Gold:** mostly gone. **Inventory:** gone — except the one **signature item** the + player chooses to keep. +- **Luck drift** resets to `luck_base`. They slept in real beds for a year. +- **Deeds** harvested from the finished campaign's canon log; `kings_slain` appended. +- **Humiliations merged, never overwritten** (§9 is explicit) — now stacking across + *lives*, not just hours. +- `campaigns_completed += 1`. +- **Level, stats, companions, approval: untouched.** + +Two things make this more than bookkeeping. + +**It is an Improviser surface, already §7-compliant by construction.** *How* they +lost the money is a Luck-selected outcome. Bad luck: Cadwyn lost the purse at dice +and there was a fire. Good luck: you kept the good armour. The Interlude's validator +whitelist is exactly the §7 line — **gear and coin are dignity; level, deeds, +companions and the signature item are progress.** It is structurally incapable of +taking progress, which is the same guarantee §7 demands of the Improviser, reused +verbatim. + +**The signature-item choice is the cursed-blade decision, seven times.** §7 asks +that "the decision to keep or discard a cursed item" be meaningful. *You may keep +exactly one thing* turns the +4 STR / −5 LCK blade into a genuinely bad night's +sleep. + +*§2: both — the transition is state, the interlude prose is text.* + +--- + +## 4. Guardrails — what lands in existing milestones + +Nothing below adds a milestone or a BOM line. Each item constrains work already +sequenced. + +### M3 — Title screen *(unbuilt, next — cheapest possible moment)* + +A saga adds a third entry path alongside new-game / continue / load: **begin the +next campaign of an existing saga.** Do not build it. Only avoid hardcoding the flow +as `new game → creation → world` such that a fourth path cannot be added. + +### M4 — Character creation + +`NewGame.construct` already takes `creation` as a plain `Dictionary`. Keep it that +way: the creation *scene* produces that dict but must not become the only thing that +can. On a saga path the dict is synthesized from the `Saga` and the UI is skipped. +The guardrail is simply: **do not couple `construct` to the creation scene.** + +M4 must emit `race_id` and `calling_id` into the creation dict (it will anyway) — +they are `Saga` fields. + +### M5 — Tactical combat + +- **Design the level curve to a ceiling of 20 across 7 tiers.** The races/classes + design currently says only "level curve → M5". It needs a number, and it needs to + be this one, or campaign 7 discovers the math ran out. +- **Enemy stat blocks carry `tier`. There is no scaling multiplier, and there never + will be.** The M5 BOM line (1 enemy family + boss) is **tier 1** — all the slice + needs. +- Class abilities are planned to L20, not open-ended. +- This design **depends on** the damage-determinism reconcile already flagged for M5 + (§7/§10 vs the Combat HUD mock's ranges). Tiering by stat block is what makes + "no scaling" possible. + +### M9 — Save / load + +- **Two objects, not one:** `Saga` (durable) + `Campaign` (canon log + `GameState` + + world + seed). This is the retrofit that genuinely hurts later — the §10 RNG-seed + argument applied to persistence. +- **Humiliations move to the `Saga`**, projected into the canon log. Today they live + in the log and would die with the campaign. + +### Content build tool + +When the `item` and `quest` namespaces land (already a sequenced M7/M8 prerequisite +in the content-track design), add **`tier: 1..7`** to the entry envelope, validated. +One field, at a moment the model is already being opened. + +### Content roadmap + +Mark each BOM line **fixed substrate** (currency, price table, abilities, callings, +cosmology, the Seven, disposition ladder) vs **per-campaign variable** (town, NPCs, +gated knowledge, quest chain, king). The distinction now has a concrete consumer. + +--- + +## 5. The vertical slice is already campaign 1 + +Greywater Docks, tier 1, patron **Ghaul the Yoke** — exactly what the content-track +design authored, before this spec existed. **Zero change to that spec, zero change to +the slice.** The saga is what happens *after* it. + +--- + +## 6. Testing + +- **Regression gate:** `construct(saga = null)` → every existing test passes + byte-for-byte. +- `construct(saga = …)` → level / stats / approval / humiliations / deeds project + correctly; the `Saga` overrides the origin's `disposition_overrides`; only the + signature item is granted. +- **The Interlude invariant, as a property test:** level, deeds, companions and the + signature item can never be reduced. This is the §7 Improviser whitelist expressed + as an assertion. +- **The anti-Oblivion test:** no code path reads `tier` after construction. +- **The saga invariants:** `len(kings_slain) == campaigns_completed`; a `Saga` at + `campaigns_completed == 7` cannot begin a campaign. +- `Saga` round-trips through save/load. + +--- + +## Consequences + +- **M5 gains a hard level ceiling (20) and a tier model** — it can no longer pick a + curve that forecloses the saga. +- **M9 gains a two-object save format** before any save exists in the wild. +- **Humiliations become durable** — Brannoc acquires an anthology across lives, not + just a repertoire across hours (§9). +- **The cosmology supplies the difficulty ladder for free** — proximity to a king is + the power scale, so no scaling multiplier is ever needed. +- **The `Saga` is the object v2 world generation will consume.** That is the whole + point of writing this now. +- Difficulty scaling is **structurally prohibited** from becoming a treadmill. + +## Out of scope + +- **AI world generation stays v2.** This spec does not unpark it. It only guarantees + the `Saga` is its input when it arrives. +- **Tiers 2–7 of content**, the **other six kings**, and the **seven story-shapes** → + Backlog. +- Authoring any content at all. This spec builds a container and a set of + prohibitions.