worldbox-observer-mod/docs/story-planner.md
DazedAnon 58a67a198d feat(saga): replace tabs with character beat captions
- 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
2026-07-22 17:09:13 -05:00

14 KiB
Raw Blame History

Story Planner

IdleSpectator commits to short multi-beat stories instead of channel-surfing ranked tips.

Feeds, sticky ensembles, presentability, and InterestDirector stay in place. StoryPlanner sits above the director and owns which story the next 3090s belongs to.

Short-arc board (not the dossier rail)

Hard storylines (notable duel / mass / war / plot / love / grief) still form a small watching + parked board (cap 4) for short-chapter resume. Cutting away parks a hard arc; soft anonymous scraps still end. There is no rail Commit / hard pin anymore.

  • Lore browse uses browsePause - freezes idle without StoryPlanner.Clear().
  • StoryPlanner.Clear does not wipe the life-saga roster.
  • Combat spine Kind comes from sticky/live ensemble scale + StoryArcKind, not tip Label prefixes.

Dossier life-saga rail is owned by LifeSagaRoster / LifeSagaRail (up to 10 MCs). Click toggles Prefer (soft favorite). AFK caption is Identity + Beat + Context via CaptionComposer (see life-saga.md). Cast/Legacy depth is rail hover only (LifeSagaViewController + LifeSagaPanel). Durable observed facts live in LifeSagaMemory (independent of ChronicleEnabled). Hover temporarily pauses camera switching without disabling Idle Spectator. See life-saga.md.

Crisis camera hold: WarFront always blocks Mass/Battle cut-ins (war_theater_hold). Disaster / outbreak signals use the same class rule while their chapter is live (crisis_theater_hold), classified via live AssetManager.disasters + catalog categories (not tip-string IndexOf).

InterestDirector is a partial class: session orchestrator + StickyMaintain + SwitchPolicy.

Harness: story_lore_pause_keeps, story_board_park_resume (short-arc park/resume via story_resume_parked), story_crisis_war, story_crisis_disaster, war_front_sticky, plus saga_* scenarios.

See also: life-saga.md, scoring-model.md, event-reason.md.

Pipeline

Feeds → InterestRegistry → StoryPlanner (boost / inject)
       → InterestVariety.Pick → InterestDirector → CameraDirector

Director still owns camera, sticky maintain, and switch margins. Planner never calls MoveCamera.

Arc model

Kind Climax Hard hold
CombatDuel sticky pair only notable pairs (2+ notables)
CombatMass Skirmish+ yes
WarFront / Plot / Love / Grief existing sticky / life yes

Phases: Climax → Aftermath → Epilogue → Done.

  • Aftermath injects on climax cold (quiet grace / follow_lost), or claims natural death_* grief.
  • Only grief / love-outcome pending tips block synthesis - cast hatch/food noise does not.
  • Injected story beats bypass FixedDwell beat hard-skip and PreferOver cast life tips while the arc is live.
  • Soft duel Mass↔Duel key flips still cold-hook once per theater anchor.
  • Epilogue once after aftermath if a living lover/friend resolves.
  • Soft anonymous duels still get aftermath tips; they do not use raised arcHoldMargin.
  • Aftermath follow, cast recovery, and epilogue pick Prefer/MC among living theater/cast candidates via LifeSagaRoster.PreferRosterUnit (never MCs outside the cast).
  • Parked-board trim drops anonymous scraps before Prefer/MC/cast arcs when over cap.

Dossier spine

While the current watch tip is the active arc's climax or story aftermath/epilogue beat, ContextLine shows a muted Kind · Phase (e.g. Duel · Aftermath, War front · Climax) via StoryPlanner.FormatSpineLabelCaptionComposer (wins over stake hitch).

Hidden when idle is off, the dossier is hidden, a status banner owns the reason row, or the camera has left the story for an unrelated tip (cast membership alone is not enough). Also hidden when the orange beat already leads with the same Kind at Climax (WatchCaption.FilterRedundantStorySpine filters against the presentable beat before clause enrichment).

Love climax is outcome-only (set_lover / fallen-in-love). find_lover seeking stays camera-worthy but does not open a Love arc. Leaving a climax without aftermath ends the arc (Done).

Sticky combat reframes (Battle ↔ Duel) sync climax Kind in place via SyncActiveClimax so the spine cannot say Mass · Climax on a Duel tip.

arcHoldMargin applies only while the watch tip is the aftermath/epilogue beat. Cast life tips (parenthood, sleep) no longer keep the raised cut-in wall.

Family life chapters (just_had_child, pregnancy, …) share arc:life:family, use a longer beat cool (including world-wide happiness cool), and bleed ledger heat so the village cast cannot monopolize the next picks.

Intimacy chapters (just_kissed / shares intimacy) use arc:life:intimacy with the same soft-life cool class (pair + world) so one couple cannot keep cutting between combat tips.

Settlement finds (just_found_house / finds a home) use arc:life:home with the same soft cool (world + subject) so house crumbs cannot keep cutting between combat beats.

Kill crumbs (milestone_kill / Killed X (species) / just_killed) use arc:life:kill with the same soft cool (subject + world) so hunter kill tips cannot keep cutting between mass beats.

Courtship crumbs (fallen_in_love / smitten / find_lover seeking) use arc:life:love soft cool (subject + world + pair). Bond outcomes (set_lover) stay hotter.

Life-intent decisions (sexual_reproduction_*, decision find_lover, …) share arc:decision:intent soft cool (subject + world). Outcomes (pregnancy, child, seeking) stay hotter.

Ordinary civ construction finishes (building:complete / tents / houses) share arc:construction soft cool. Spectacle wonders stay hotter.

Soft personal status FX (taking_roots, singing, moods, …) share arc:status:fx soft cool. Danger / combat statuses stay hotter. Hatch crumbs use arc:life:hatch.

Sleep / wake / dream cycle (sleeping, just_slept, had_*dream, had_nightmare, …) share arc:life:rest soft cool so rest crumbs cannot monopolize between richer beats.

WorldLog politics meta (king_dead, city_destroyed, favorite fell, …) share arc:worldlog:meta soft cool so sticky death crumbs cannot re-monopolize.

Interrupted resume: already-cooled moment beats are not parked / not resumed after a cut-in (hatch/reproduce cannot bounce back after combat).

Story spine: aftermath tips must match the active arc CorrelationKey (no stale duel aftermath under a Mass spine). Combat Kind word follows tip Label (Battle / Skirmish / Mass / Duel).

Wiped opposing combat camps collapse to a living-side pack label at live scale (never Mass - A (n) vs B (0)).

Combat aftermath names only a dead theater partner (stands over X), else stands over the fallen. Living lover/friend fallbacks are never the fallen.

Spine Kind follows the live tip Label for combat (Duel vs Mass/Skirmish/Battle), not sticky scale-hold alone. Cold climax tips do not show · Aftermath - only the aftermath tip does. Aftermath Follow loss promotes another living cast member before follow_lost.

Harness: story_spine / story_hold_margin / story_family_variety / story_intimacy_cool / story_settlement_cool / story_kill_cool / story_courtship_cool / story_decision_intent_cool / story_decision_intent_live / story_construction_cool / story_status_fx_cool / story_rest_cool / story_resume_cooled / story_worldlog_meta_cool / combat_wiped_side / reason_life_principal (story_arc_combat, story_spine_scoped, story_aftermath_combat, story_family_variety).

Crisis chapters (Phase 4)

Parallel overlay beside short StoryArc - war / disaster / outbreak thresholds open a multi-minute chapter without thrashing when unrelated tips cut in.

Signal Enter when
WarFront live participants ≥ crisisWarParticipantMin (default 8), both sides present (peak lock alone does not enter)
EarthquakeActive / Disaster category live or EventStrength ≥ crisisDisasterStrengthMin (95)
StatusOutbreak EventStrength ≥ disaster floor

While live:

  • crisisOwnershipBoost + crisisHoldMargin on matching tips
  • Character-fill ambient is suppressed (SuppressAmbientFill)
  • Epic peers (≥ disaster floor) may still cut

On exit (signal linger / max duration / harness interest_crisis_end): inject epilogue_crisis ({a} surveys the aftermath of the crisis), then cooldown.

Disaster / outbreak chapters use a shared family anchor (disaster / outbreak), a longer linger after the last WorldLog pulse, and crisisDisasterCooldownSeconds (default 120) armed when the closer injects so stacked tornado tips cannot double-close.

War chapters stamp a kingdom theater pair. Local Mass/Battle on that pair keeps the chapter warm and cannot steal the War tip (war_theater_hold). Crisis closers clear short-arc spine and suppress combat task detail crumbs under the orange reason.

Short-arc related epilogue is skipped when a crisis closer owns the exit. When a short-arc war linger injects while a war crisis is live, the crisis quiet-closes (no second epilogue_crisis). Harness batch end and interest_story_purge_leftovers drop leftover crisis closers so free AFK does not inherit a fake ending. After hard story/crisis ends, softFillQuietSeconds suppresses soft-life crumbs, short interrupt FX (stunned / invincible), and character-fill ambient briefly. FamilyPack does not break that quiet window (see life-saga.md).

Selection discipline

  • InterestVariety.Pick takes #1 when score gap ≥ story.nearTieEpsilon (default 8).
  • Prefer / MC / MC-cast tips may still win when the gap is below story.sagaNearTieEpsilon (default 28).
  • Near-ties among peers still Prefer > MC > cast, else roll among top-3.
  • Causal heat + life-saga Prefer/MC/cast soft bias + cross-MC bonus add into RecalcTotal.
  • See life-saga.md for camera / rival / dual-rail contracts.

Knobs

scoring-model.jsonstory block (loaded by InterestScoringConfig.Story).

Catalog policy ids live under event-catalog.jsonstory (aftermath_survivor, aftermath_mourner, aftermath_war_linger, epilogue_crisis, …).

Harness

Scenario Proves
story_crisis_war WarFront → crisis active → epilogue_crisis
story_crisis_disaster EarthquakeActive → crisis → epilogue_crisis; cool blocks reopen
story_aftermath_combat duel cold → aftermath tip
story_aftermath_partner_truth aftermath never names living lover as fallen
story_aftermath_cast_noise hatch noise pending still gets aftermath
story_aftermath_prefers_mc aftermath follow prefers roster MC
story_recover_follow_mc cast recovery prefers living MC
story_park_trim_keeps_mc parked trim keeps Prefer/MC arcs over scraps
story_crisis_beats_mc_aftermath war crisis cuts MC aftermath; hot peer cannot reclaim
story_pick_opens_stranger_arc empty Prefer roster still opens Duel climax (non-goal)
reason_duel_principal bystander Follow cannot own duel reason
reason_life_principal Follow hijack cannot keep intimacy Label
story_intimacy_cool just_kissed cools; peer wins next pick
story_settlement_cool finds-a-home cools; peer wins next pick
story_kill_cool kill crumbs cool; peer wins next pick
story_courtship_cool smitten/seeking cool; peer wins next pick
story_decision_intent_cool reproduce/find-lover decisions cool as a class
story_decision_intent_live live EmitDecision path cools intent class
story_construction_cool tent/house finishes cool; peer wins
story_status_fx_cool taking_roots FX cool; peer wins
story_rest_cool sleep/wake rest cycle cools; peer wins
story_resume_cooled cooled soft tip does not resume after cut-in
story_worldlog_meta_cool king_dead cools; peer wins next pick
combat_wiped_side wiped camp never prints vs (0)
story_aftermath_war war sticky cold → aftermath; no dual crisis closer
story_crisis_hygiene auto-opened war crisis + short-arc linger; purge leaves no epilogue leak
story_near_tie large gap always picks #1
story_arc_combat climax → aftermath ownership
story_arc_preempt_resume epic cut then fresh scrap aftermath
story_ledger_revisit featured unit wins near-tie
./scripts/harness-run.sh --repeat 3 story_crisis_war
./scripts/harness-run.sh --repeat 3 story_crisis_disaster
./scripts/harness-run.sh --repeat 3 story_aftermath_war
./scripts/harness-run.sh --repeat 3 war_front_sticky
./scripts/harness-run.sh --repeat 3 story_aftermath_combat
./scripts/harness-run.sh --repeat 3 story_aftermath_partner_truth
./scripts/harness-run.sh --repeat 3 story_aftermath_cast_noise
./scripts/harness-run.sh --repeat 3 story_crisis_beats_mc_aftermath
./scripts/harness-run.sh --repeat 3 story_pick_opens_stranger_arc
./scripts/harness-run.sh --repeat 3 reason_duel_principal
./scripts/harness-run.sh --repeat 3 story_near_tie
./scripts/harness-run.sh --repeat 3 story_arc_combat
./scripts/harness-run.sh --repeat 3 story_ledger_revisit
./scripts/harness-run.sh --repeat 3 story_kill_cool
./scripts/harness-run.sh --repeat 3 story_courtship_cool
./scripts/harness-run.sh --repeat 3 story_decision_intent_cool
./scripts/harness-run.sh --repeat 3 story_decision_intent_live
./scripts/harness-run.sh --repeat 3 story_construction_cool
./scripts/harness-run.sh --repeat 3 story_status_fx_cool
./scripts/harness-run.sh --repeat 3 story_rest_cool
./scripts/harness-run.sh --repeat 3 story_resume_cooled
./scripts/harness-run.sh --repeat 3 story_worldlog_meta_cool
./scripts/harness-run.sh --repeat 3 combat_wiped_side

Files

  • IdleSpectator/Story/StoryPlanner.cs - short-arc ownership, cold-hook, inject, spine, crisis
  • IdleSpectator/Story/LifeSagaRoster.cs - durable MC cast + Prefer + chapter stamps
  • IdleSpectator/Story/SagaProse.cs - player-facing relation/stake/title English
  • IdleSpectator/Story/CaptionComposer.cs - Identity + Beat + Context
  • IdleSpectator/Story/LifeSagaOverview.cs / LifeSagaRail.cs - rail Prefer + hover
  • IdleSpectator/Story/CrisisChapter.cs - parallel crisis chapter state
  • IdleSpectator/Story/CausalHeat.cs - decaying cast heat
  • IdleSpectator/Story/StoryReason.cs - presentable aftermath Labels
  • IdleSpectator/WatchCaption.cs - character beat caption + ContextLine