docs(spec): character reuse + the seven-campaign arc
A direction spec: one character, seven campaigns (one per demon king), one saga — then it ends. The character, companions, deeds and level carry between campaigns; the world does not. Deliberately adds no milestone and no BOM line. Its real payload is a set of guardrails on milestones that are about to be built blank and would otherwise foreclose the shape: - M5 owes a level curve — design it to a ceiling of 20 across 7 tiers, with tiered enemy stat blocks and never a scaling multiplier. - M9 owes a save format — two objects (Saga + Campaign), not one blob. Humiliations move to the Saga or they die with the campaign. - M3/M4 — don't foreclose a saga entry path; don't couple construct() to the creation scene. - Build tool — add tier:1..7 when the item/quest namespaces land. Difficulty is fixed at world-construction time from the campaign index, never during play. That prohibition is the whole design; a world that re-levels with the player is a treadmill. The canon-log contract does not change: party[].disposition, humiliations[] and established_facts[] already exist, and player still admits no number. The Saga becomes the source; the canon log a projection of it. The seam is one optional fourth arg to NewGame.construct(). Greywater at tier 1 under Ghaul is, unintentionally, already campaign 1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user