worldbox-observer-mod/docs/life-saga.md
DazedAnon 44999fb653 feat(actor-relation): enhance parent-child relationship tracking and event handling
- Introduce methods to observe and remember parent-child links for actors
- Implement caching for observed relationships to improve performance
- Update narrative event store to index child events by parent and child
- Refactor LifeSagaPanel to ensure stable cast membership during saga sessions
- Enhance documentation to clarify relationship handling and event indexing
2026-07-24 16:21:49 -05:00

18 KiB
Raw Permalink 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, portrait/history, status/trait details, then Connection, Beat, and Context. Narrative explanation remains grouped at the bottom while the portrait keeps the same body anchor used by Saga hover.

Rail UX

  • Up to 5 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 temporarily fall back to the species icon (letter last). Fallbacks are never stored as live snapshots; one uncaptured inactive slot retries per second, round-robin, until a valid live frame can be retained through focus changes and death.
  • Non-civilized creatures prefer the palette-resolved generated frame from their current animation (calculateColoredSprite(calculateMainSprite())) over the raw atlas frame. This avoids palette-key colors while still bypassing shared species icons. Their inspect-UI composite is never accepted because WorldBox may synthesize a new texture containing the standing species portrait, which cannot be caught by atlas-region identity. Any candidate pointing at the species icon's atlas region is also rejected as a live capture.
  • Live-frame usability is based on non-degenerate area, not an 8x8 square floor: short horizontal animations such as fox movement frames and genuine 3x2 fly/beetle frames remain valid. saga_rail_species_frame_sweep checks every registered asset one per frame, temporarily spawning and immediately cleaning up missing representatives, and records the exhaustive results in saga-rail-species-frames.tsv. Assets that only expose a separate special renderer rather than an actor sprite (currently Crabzilla) are recorded as not applicable and keep the species icon.
  • 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 hovered name, font, portrait, and Cast/Legacy body commit as one visual transition. The name keeps the dossier's entry font ceiling for the full hover session, and pointer-enter stages the portrait hidden until its fixed position is assigned. Steady-state refresh preserves visibility when unchanged geometry correctly skips relayout.
  • 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.

Cast membership is a stable Saga snapshot. It does not disappear merely because the orange camera beat mentions the same relative; the beat is hidden while Saga is open. Recorded parent/child facts and indexed confirmed birth events also backfill living relatives when WorldBox's live family enumeration is temporarily incomplete. Generic family membership is only labeled Kin after these directional sources have been exhausted. For older saves, a bounded reverse lookup also recognizes a family peer as a child when that peer's own parent data points to the displayed character.

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.
  • Hover fitting reads its own display headline rather than the camera dossier's cached identity; repeated relayouts therefore cannot briefly restore the wrong name or enlarge the font.
  • Saga owns the shared nametag/species/sex/portrait chrome exclusively for the whole hover session. Camera dossier callbacks may update their semantic snapshot but cannot repaint that chrome.
  • Glyph exit is deferred for 50 ms; entering another glyph cancels the exit, producing an atomic cross-glyph handoff with no intermediate dossier name or typography frame.
  • Rail face animation refreshes the Active glyph ~1/s; other glyphs stay static between structure changes.
  • Missing live rail frames retry at most one inactive slot per ~1s rail tick; successful captures replace the temporary species icon and unsuccessful attempts never discard an older live frame.
  • 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: run harness scenario narrative_scheduler_health (it enables the hitch_probe action) / 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.