worldbox-observer-mod/docs/life-saga.md
DazedAnon 12e2ba0d01 feat(narrative): refine camera and narrative handling for civilized terminology
- 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
2026-07-24 14:06:23 -05:00

15 KiB
Raw Blame History

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 as chapter beats. Confirmed observations enter through NarrativeEventRecorder; LifeSagaMemory is the compatibility/read projection and survives roster churn. The fixed-layout hover redesign is specified in the Saga UX redesign plan.

AFK caption is a character beat: Identity (who) + Beat (live tip) + Context (spine or stake). CaptionComposer merges saga identity into the live caption without baking titles into InterestCandidate.Label. NarrativeRenderer owns complete orange Beats. SagaProse 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 CaptionComposerWatchCaption
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.