- Implement checks for civilized terminology in camera focus events - Update event handling to suppress inappropriate pack language for civilized characters - Enhance narrative presentation to differentiate between family and pack language - Introduce new scenarios to validate significance of beats and relationships - Improve documentation to clarify narrative structure and event significance
249 lines
15 KiB
Markdown
249 lines
15 KiB
Markdown
# Life-saga roster
|
||
|
||
Dynamic five-character cast of interesting lives (MCs). Owns the dossier rail and camera priority.
|
||
Chronicle stays a history book and never drives the camera.
|
||
Short combat/love/war arcs stay in [`StoryPlanner`](../IdleSpectator/Story/StoryPlanner.cs) as chapter beats.
|
||
Confirmed observations enter through
|
||
[`NarrativeEventRecorder`](../IdleSpectator/Narrative/NarrativeEventRecorder.cs); `LifeSagaMemory`
|
||
is the compatibility/read projection and survives roster churn.
|
||
The fixed-layout hover redesign is specified in the [Saga UX redesign plan](saga-ux-redesign-plan.md).
|
||
|
||
AFK caption is a **character beat**: Identity (who) + Beat (live tip) + Context (spine or stake).
|
||
[`CaptionComposer`](../IdleSpectator/Story/CaptionComposer.cs) merges saga identity into the live caption without baking titles into `InterestCandidate.Label`.
|
||
[`NarrativeRenderer`](../IdleSpectator/Narrative/NarrativePresentation.cs) owns complete orange
|
||
Beats. [`SagaProse`](../IdleSpectator/Story/SagaProse.cs) owns saga identity, Cast, and stake voice.
|
||
|
||
## Ownership
|
||
|
||
| Concern | Owner |
|
||
|---------|--------|
|
||
| Five MCs, diversity, Prefer | `LifeSagaRoster` |
|
||
| Confirmed event identity and idempotency | `NarrativeEventRecorder` + `NarrativeEventStore` |
|
||
| Durable projection/index facade | `LifeSagaMemory` + `NarrativeStoryStore` |
|
||
| Versioned per-world persistence | `NarrativePersistence` + `NarrativePersistenceMapper` |
|
||
| Adaptive presentation model | `LifeSagaPresentation` |
|
||
| Orange Beat English | `NarrativeRenderer` |
|
||
| Saga Identity/Cast/stake English | `SagaProse` |
|
||
| Legacy chapters | `LegacyChapterBuilder` |
|
||
| Identity + Beat + Context caption | `CaptionComposer` → `WatchCaption` |
|
||
| Hover Cast/Legacy depth | `LifeSagaViewController` + `LifeSagaPanel` |
|
||
| Rail icons / Prefer click | `LifeSagaRail` |
|
||
| Camera read-pause lease | `InterestDirector` |
|
||
| Short climax → aftermath → epilogue | `StoryPlanner` (thinned) |
|
||
| Context spine (`Kind · Phase`) | `StoryPlanner.FormatSpineLabel` (via ContextLine) |
|
||
| Full history | Chronicle / Lore |
|
||
|
||
## Admission
|
||
|
||
Live discovery (no tip-string allowlists):
|
||
|
||
- Game favorite, king, city leader, kills/renown floors (`InterestScoring.IsNotable`)
|
||
- Pack alpha, clan chief, army captain, active plot author, founder, species singleton
|
||
|
||
Standing is recomputed from live roles each refill.
|
||
Heat comes from chapter stamps / featured tips and decays with idle time.
|
||
Each slot keeps its original admission reason even when its live role later changes.
|
||
Hard-arc admissions also retain the arc kind and chapter context that brought the life onto the roster.
|
||
|
||
## Dynamic five
|
||
|
||
- Cap is 5; order follows Effective = Standing + Heat + pin bonuses.
|
||
- Prefer and WorldBox favorites are hard pins and fully count toward Cap.
|
||
- Admission-role units always compete on the periodic scan.
|
||
- Ordinary non-notables challenge the Cap through decaying story potential earned from confirmed
|
||
semantic developments.
|
||
- Prolonged spectacle alone does not admit a character; coherent relationship, lineage, power,
|
||
home, conflict, war, plot, or survival momentum can.
|
||
- Soft `NoteFeatured` heats existing MCs only.
|
||
- Newcomers need a small challenger margin over the weakest non-pin so the rail does not thrash.
|
||
|
||
## Camera (soft)
|
||
|
||
- No rail camera lock. Prefer is a toggle soft-favorite for idle scoring.
|
||
- Score ladder: critical interrupt > narrative episode > Prefer soft > MC soft > MC cast > bounded
|
||
ambient/world context.
|
||
- `nearTieEpsilon` (8) picks #1 for ordinary gaps; Prefer/MC/cast may still win within `sagaNearTieEpsilon` (28).
|
||
- Soft ranks: Prefer > CrossMC (>1 roster MC) > MC > MC cast (lover/friend/kin/earned rival of a roster MC).
|
||
- Tips that touch two or more roster MCs get `sagaCrossMcBonus` (~11); scoring touch inventory matches rail Involved ids.
|
||
- Sticky combat/war/plot maintain heats both MCs early when two roster principals share the tip.
|
||
- Mid-fight follow uses Prefer/MC/cast soft weight in `CombatFocusWeight` (spectacle floors can still win).
|
||
- Ambient / character-fill yields immediately to Prefer/MC tips (not vs crisis/combat/short-arc holds).
|
||
- Soft-fill quiet still suppresses anonymous crumbs; Prefer/MC tips may break quiet.
|
||
- Glyph hover acquires a camera read-pause lease: feeds/story/roster continue, but selection and focus repair are deferred until release.
|
||
|
||
## Character beat caption
|
||
|
||
Always-on AFK chrome (no Dossier/Saga tabs):
|
||
|
||
| Row | Source | MC | Nobody |
|
||
|-----|--------|----|--------|
|
||
| Identity | `CaptionComposer` | `Name · Title` (real roles only; else species/job) | `Name (Species/Job)` |
|
||
| Connection | `CameraEligibility` | hidden | MC anchor/relation when this non-MC shot was admitted through MC Cast/thread context |
|
||
| Beat | candidate `NarrativePresentationModel` + `BeatSignificance` | one complete event sentence; an exact participant may carry an MC-relative qualifier | one complete semantic sentence |
|
||
| Context | muted | spine if live short-arc; else unresolved stake hitch (omits kill/role/founding under love/rest tips; RoleChange that echoes Identity always omitted) | spine only |
|
||
|
||
Hard rule: saga titles and stake slogans never enter `InterestCandidate.Label`.
|
||
Character-led grounding never owns Beat: species, job, traits, city, and ambient activity remain
|
||
Identity/task metadata. An actual event may explain the relevance of its exact named participant,
|
||
for example `Kills Omya (child of Waf)`, when the relation to a current MC is backed by live or
|
||
durable typed evidence. Broad participants, proximity, and name parsing never create this qualifier.
|
||
The story scheduler owns selection; `identity_filter` remains a safety gate against presenting
|
||
identity text as a development.
|
||
Beat participants come from the exact presentation/event receipt (no latest-similar lookup and no
|
||
living-name parse).
|
||
Status banners still own/suppress Beat; Identity + Context keep updating.
|
||
Outside hover, caption Identity/Beat/Context track camera focus. During Saga hover, the
|
||
primary nametag temporarily binds to the hovered MC (species, name/title, sex, favorite),
|
||
while camera Beat/Context are hidden to prevent false attribution. Camera composition keeps
|
||
running underneath and restores immediately when hover ends.
|
||
Outside hover, the visible order is Identity, Connection, Beat, Context, then portrait/history and
|
||
status/trait details, so the reason for the current shot is never stranded below biography chrome.
|
||
|
||
## Rail UX
|
||
|
||
- Up to 10 slots with unit-keyed inspect-UI portraits. Each character is captured on first
|
||
display, refreshed while watched, and retains that latest unique frame after focus leaves.
|
||
- Egg forms and tiny/blank atlas crops fall back to the species icon (letter last).
|
||
- Active chrome = camera follow MC; Involved chrome = other tip-touched MCs (subject/related/follow/pair/short-arc cast only - not mass `ParticipantIds`).
|
||
- Prefer uses a star pip / distinct border.
|
||
- Click toggles saga Prefer only (does not mutate WorldBox unit favorite; does not pin a Saga subject).
|
||
- Rail sits at the top of caption chrome (no tabs).
|
||
- Hovering a glyph opens Cast/Legacy depth (`LifeSagaPanel`) and pauses camera switching.
|
||
- Leaving the glyph hides the panel, restores dossier body rows, and releases the pause lease.
|
||
- Hover temporarily swaps the primary nametag to the hovered life; it does not change camera focus.
|
||
- The orange camera Beat and Context are hidden during hover rather than attributed to the hovered life.
|
||
- Hover restores the hovered character's live stone-frame dossier portrait beside the panel
|
||
(species fallback only when no living actor is available).
|
||
- Panel is Cast + Legacy only (the primary nametag owns hovered name/title; no duplicate identity row or stake chrome).
|
||
- Cast budget is 8 (two rows); Legacy prefers turning points over Cast-echo kin crumbs.
|
||
- No Evidence stats dump. No Open Lore button (press L).
|
||
- ParentChild is capped (one named beat, or a lineage summary when there are 3+ children).
|
||
- Living Cast lover/friend/kin are not repeated as Legacy relationship lines.
|
||
- Legacy lines may prefix the world date (`Month Year · beat`).
|
||
- Context spine (`Kind · Phase`) is hidden when the orange beat already names the same kind and phase is Climax.
|
||
- Never show live task/status/trait rows or call a live attack target a rival.
|
||
|
||
## Saga prose
|
||
|
||
`SagaProse` is the saga Identity/Cast/stake voice owner. `NarrativeRenderer` separately owns the
|
||
complete orange Beat. Evidence drives both; enum names never appear in UI.
|
||
|
||
Relation classes: Bond, Friend, Kin (Parent/Child/Heir/Packmate/Kin), Foe (Old Foe/War Enemy/Plot Enemy/Blood Foe), Plot Partner, Grief. `Packmate` and `Pack Alpha` are creature-only language;
|
||
civilized actors use `Kin` and family wording even though WorldBox stores both through `Family`.
|
||
|
||
Ban list (player-facing): `earned rival`, `Earned a rival`, `Rivalry with`, `saga hero`, `fellow saga hero`, `War foe`, `Plot ally`, `Likely successor`, `A life in motion` on Identity, mood wallpaper stakes.
|
||
|
||
Capitalization:
|
||
|
||
- Identity titles / Cast labels: Title Case (`Pack Alpha`, `Old Foe`, `Best Friend`)
|
||
- Orange Beats: Sentence case, rendered as one complete phrase
|
||
- Stake / Legacy: Sentence case
|
||
- Unit names and owned tip body: leave as-is
|
||
|
||
## Durable narrative state
|
||
|
||
Schema-v1 per-world sidecars persist semantic events, developments, threads, consequences,
|
||
identity snapshots, and Prefer choices. Writes are atomic and only promoted at a confirmed game
|
||
save completion. Load rejects corrupt/newer schemas safely, preserves unreadable data, ignores
|
||
facts newer than the loaded world time, and rebuilds live actor bindings without replaying restored
|
||
events as fresh camera developments. Camera focus, active episodes, cooldowns, and session time are
|
||
never serialized.
|
||
|
||
Legacy is authored only by `LegacyChapterBuilder`: 3+ births consolidate into lineage, repeated
|
||
kills consolidate into a combat chapter, and current Cast/Identity facts do not echo unchanged.
|
||
|
||
## Opposition facts (memory)
|
||
|
||
Notable only:
|
||
|
||
- Living rematches (≥ `sagaRivalEncounterThreshold`, default 3) while both are alive (sticky combat hook; kill/death do not count).
|
||
- Kin blood (`killed_kin` / `kin_slain`).
|
||
- War / plot opposition (opposing principals on the same war/plot tip).
|
||
- MC↔MC living rematches earn rivalry one encounter sooner; a single MC kill is crossover Legacy, not automatic RivalEarned.
|
||
- Cast shows living credible foes only; weak kill-cycle `repeated_encounters` facts are pruned.
|
||
- Heir cast label only when a game-authored heir API probe succeeds (`Heir`, not `Likely successor`).
|
||
- Unresolved `WarJoin` / `PlotJoin` Others fill Cast as `War Enemy` / `Plot Partner` when still living.
|
||
- Crown / Combat / Intrigue / Pack / Bond lenses fill remaining Cast slots with live family peers
|
||
after lover/friend/parents/children/heir/foe. Non-civilized Pack lenses use `Packmate`;
|
||
civilized family peers always use `Kin`.
|
||
|
||
## Stake
|
||
|
||
- Primary stake: strongest unresolved war/plot/rival/role/founding/grief (and McCrossover beats of those kinds).
|
||
- Fallback stake when nothing stronger exists: unresolved `HardArcCombat`, open `BondFormed`, or McCrossover `Kill`.
|
||
- War/plot prose names the opposing principal and kingdom/plot display when known (`Still at war with…`).
|
||
- Role rises (king/leader/alpha/clan chief/army captain) emit `RoleChange` from live roster deltas.
|
||
- Current role/founding facts live in Identity and are omitted from Context, Legacy, and rail tooltip stakes when they would only repeat the title.
|
||
- Titleless MCs (hard-arc, no crown) keep species/job Identity; Context stake hitch is required when memory has unresolved facts.
|
||
|
||
## Cross-MC moments
|
||
|
||
When subject and other are both roster MCs, facts are flagged `McCrossover` (named stake/legacy prose + heat on both).
|
||
Prose stays plain (`Bound with X`, `Slew X`) - no “saga hero” wording.
|
||
Both rail glyphs light (Active + Involved) when the current tip touches multiple MCs.
|
||
|
||
## Soft-fill quiet (AFK hygiene)
|
||
|
||
After hard story/crisis closers, soft-fill quiet suppresses soft-life crumbs and short interrupt FX (`stunned` / `invincible`).
|
||
`FamilyPack` does not break quiet (ambient cluster fill).
|
||
Combat / war / plot / outbreak / earthquake / story beats / epic strength / Prefer/MC still cut in.
|
||
Quiet can refresh briefly when a soft crumb ends (bounded).
|
||
|
||
## Performance (idle)
|
||
|
||
- Roster role refresh runs on the ~3.5s amortized scan (not every frame). Soft heat does not force a world Refill.
|
||
- Dirty Prefer / membership changes re-rank existing slots only; full world admission stays on the timed cadence.
|
||
- World admission walks ~260 units/frame, then BuildSlots only the best ~2× Cap challengers (skips when pins fill Cap).
|
||
- Dossier `Relayout` runs only when chrome geometry changes; leaving hover restores dossier rows once.
|
||
- Live portrait tile refresh is ~5 Hz; rail live sprites are only for the watched glyph (others use species icons).
|
||
- Hover identity is primed before Cast/Legacy binding and uses direct actor sex metadata; it does
|
||
not call the population-counting dossier builder.
|
||
- Rail face animation refreshes the Active glyph ~1/s; other glyphs stay static between structure changes.
|
||
- Plot join memory records the joiner only; full member snapshots stay on create/finish/cancel.
|
||
- RelationEvidence caches invalidate on memory revision + live lover/friend change.
|
||
- Hitch probe: harness `hitch_probe` / load save via `load_save_slot` (slot 2 = Box of Magic). Optional `.harness/autoload_slot`.
|
||
|
||
## Session
|
||
|
||
- `StoryPlanner.Clear` does not wipe the roster or saga memory.
|
||
- Lore browse / brief idle-off keeps the roster and memory.
|
||
- World replacement, map load-complete (`MapBox.finishingUpLoading`), `clearWorld`, harness batch end, and `interest_saga_clear` / `saga_session_reset` wipe roster + memory (+ `CaptionComposer.Clear`).
|
||
- Bind key uses live `map_stats.name`, `current_world_seed_id`, `map_stats` identity, `_load_counter`, and map size - not world reference alone.
|
||
- Same-map spectator re-enable does not clear when the bind key is unchanged.
|
||
- Read-only soak dumps use `soak_probe` (or `snapshot` with `value=preserve`) and skip the batch-end wipe.
|
||
|
||
## Harness
|
||
|
||
- `saga_roster_diversity`
|
||
- `saga_camera_prefers_mc`
|
||
- `saga_prefer_soft_bias`
|
||
- `saga_wins_moderate_gap`
|
||
- `saga_combat_follow_mc`
|
||
- `saga_cast_soft_bias`
|
||
- `saga_cross_mc_light`
|
||
- `saga_cross_mc_wins_gap`
|
||
- `saga_cross_mc_kill`
|
||
- `saga_rival_not_single_kill`
|
||
- `saga_rival_rematch`
|
||
- `saga_prefer_cuts_ambient`
|
||
- `saga_hard_arc_escalates`
|
||
- `saga_overview_has_mc`
|
||
- `saga_adaptive_dossier` (hover depth; no tabs / empty-pick)
|
||
- `saga_memory_survives`
|
||
- `saga_war_memory_both_sides`
|
||
- `saga_plot_cast_members`
|
||
- `saga_cast_kin_non_pack_lens`
|
||
- `saga_stake_from_role_live`
|
||
- `saga_legacy_quality`
|
||
- `saga_session_reset`
|
||
- `saga_hover_read_pause`
|
||
- `saga_replace_on_death`
|
||
- `saga_admit_roles`
|
||
- `saga_replace_weaker`
|
||
- `saga_rail_active_highlight`
|
||
- `saga_rail_prefer_click`
|
||
- `saga_soft_fill_no_pack`
|
||
- `caption_character_beat`
|
||
- `caption_prose_voice`
|
||
|
||
See also: [story-planner.md](story-planner.md).
|