worldbox-observer-mod/docs/life-saga.md
DazedAnon 0c60f1a2ba feat(saga): stabilize hover dossier layout
- Fix Saga geometry so portraits and glyphs remain aligned during hover
- Bound Cast and Legacy content with overflow and empty states
- Prevent hidden camera beats from reserving Saga layout space
- Add fixed-size regression coverage and document the UX contract
2026-07-22 19:47:38 -05:00

12 KiB
Raw Blame History

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 as chapter beats. Durable observed facts live in LifeSagaMemory and survive 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. SagaProse 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 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 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. 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.

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 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.