- Compose Identity, Beat, and Context without mutating camera labels - Move Cast and Legacy details into a hover-only panel - Centralize relation, title, stake, and legacy prose - Make rail clicks toggle Prefer without pinning a Saga subject - Add harness coverage for caption composition and hover behavior
204 lines
11 KiB
Markdown
204 lines
11 KiB
Markdown
# Life-saga roster
|
||
|
||
Dynamic top-10 cast of interesting lives (MCs). Owns the dossier rail and soft camera bias.
|
||
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.
|
||
Durable observed facts live in [`LifeSagaMemory`](../IdleSpectator/Story/LifeSagaMemory.cs) and survive roster churn.
|
||
|
||
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`.
|
||
[`SagaProse`](../IdleSpectator/Story/SagaProse.cs) owns all player-facing relation/stake English.
|
||
|
||
## Ownership
|
||
|
||
| Concern | Owner |
|
||
|---------|--------|
|
||
| Top-10 MCs, diversity, Prefer | `LifeSagaRoster` |
|
||
| Durable observed facts | `LifeSagaMemory` |
|
||
| Adaptive presentation model | `LifeSagaPresentation` |
|
||
| Player-facing saga/caption English | `SagaProse` |
|
||
| 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 top-10
|
||
|
||
- Cap stays 10; 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.
|
||
- Non-admission "nobodies" challenge Cap only via hard-arc chapter stamps (Combat / War / Plot / Love / Grief).
|
||
- Hard-arc admit heat escalates with climax age (re-stamped while the arc holds) so a prolonged stranger duel can displace dull Cap slots and light Active chrome.
|
||
- 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: crisis ownership > active short-arc ownership > Prefer soft > MC soft > MC cast > CausalHeat.
|
||
- `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)` |
|
||
| Beat | owned tip + `SagaProse.BeatClause` | tip enriched with one relation clause | tip as today |
|
||
| 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`.
|
||
Selection/`identity_filter` stay event-first.
|
||
Beat other resolves `RelatedId` then `PairPartnerId` (no living-name parse).
|
||
Status banners still own/suppress Beat; Identity + Context keep updating.
|
||
Hover panel tracks the hovered MC; caption Identity/Beat/Context always track camera focus.
|
||
|
||
## Rail UX
|
||
|
||
- Up to 10 slots with live inspect-UI sprites (refreshed ~3x/sec for animation frames).
|
||
- 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 never swaps the orange tip to the hovered life.
|
||
- Panel is Cast + Legacy only (caption already owns name · title; no portrait / 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 single voice owner. Evidence drives copy; 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.
|
||
|
||
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`)
|
||
- Beat clauses after ` · `: lowercase start
|
||
- Stake / Legacy: Sentence case
|
||
- Unit names and owned tip body: leave as-is
|
||
|
||
## 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 (`Packmate` for Pack, `Kin` otherwise) after lover/friend/parents/children/heir/foe.
|
||
|
||
## 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).
|
||
- 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).
|