worldbox-observer-mod/docs/sticky-ensemble-plan.md

4.7 KiB
Raw Permalink Blame History

Sticky ensemble plan

Consistent multi-actor camera scenes across combat, war, plot, family, and status.

Combat is the reference implementation. Every new kind must reuse the same modules and rules - not invent a parallel tip system.

Shared modules (do not fork)

Piece Role
LiveEnsemble Snapshot + kind-specific builders (TryBuildCombat, TryBuildWar, …)
LiveSceneStickyState Durable side keys, member id rosters, peak / scale-hold on InterestCandidate.Sticky
StickyScoreboard Lock sides once, enroll, refresh counts, promote follow, stabilize scale
EventReason.Ensemble Orange prose dispatch by EnsembleKind
InterestDirector Camera + completion only; calls scoreboard; never authors side labels

Authored catalogs (event-catalog.json, war type seeds) are policy (strength, whether camera-worthy). Live War / Plot / Family / unit status is inventory.

Sticky contract (every kind)

  1. Lock opposing identity once when the live scene first has clear sides (kingdom / species / plot cell / family).
  2. Enroll member ids while the scene is hot; prune dead; allow new matching members.
  3. Counts may hit 0 on a wiped side; survivors on the other side stay counted.
  4. Scale / tier hold (~2.5s) so tips do not flap (Mass↔Battle, War↔thin unit line).
  5. Promote follow from the sticky roster when the camera subject dies - do not end into thin prose.
  6. Never use unit proper names as collective side labels.
  7. Never collapse a sticky multi tip to {name} is fighting / bare melee crumbs.
  8. Builder reads live game objects; director does not hardcode id lists.

Kind roadmap

Priority Kind Live surface Side frame Completion Tip shape Status
0 Combat attack_target / in_combat cluster; NamedPair uses durable PairOwnerId + unordered combat:pair:lo:hi (no A↔B thrash) kingdom → species → camps CombatActive Duel - A vs B / Battle - Humans (4) vs Wolves (0) Done
1 WarFront War.main_attacker / main_defender, kingdom pop kingdom vs kingdom WarFront War - Essiona (120) vs Northreach (80) Done
2 PlotCell Plot.units, target_*, 2+ plotters (solo → unit Plot tip) plotters vs target kingdom/city when useful PlotActive Plot - Plotters (3) vs Essiona (120) Done
3 FamilyPack Family.units, alpha family / species pack FamilyPack Pack - Wolves (5) · gathering (soft cluster - yields to unit events) Done
4 StatusOutbreak lasting affliction clusters only (2+; not love/ambient/combat-FX stun/shield/recovery; never tip at 0/1) status carriers StatusOutbreak Outbreak - Cursed (4) / Outbreak - Drowning (3) Done
later Army / Clan Army.units / Clan.units usually feed WarFront / siege - - Feed only
later Alliance Alliance.getUnits() meta aggregation - - Prefer war sides
later DiscoveryCluster pending first-seens soft cluster - - Optional

Per-kind builder checklist

When adding a kind:

  1. Add / use EnsembleKind value (already stubbed where possible).
  2. Implement LiveEnsemble.TryBuild… from live world state.
  3. Add EventReason.… and wire EventReason.Ensemble switch (no silent fall-through to Combat unless identical).
  4. Set Sticky.Kind on lock; enroll rules may differ (combat = fighters; war = kingdom members; plot = plot.units).
  5. Choose InterestCompletionKind that stays active while the scene is live (not only while one unit swings).
  6. Director: maintain/handoff path calls StickyScoreboard.Refresh / TryPromoteFollow / StabilizeScale.
  7. Feed registers AssetId (live_battle, live_war, …) + completion + presentable Label.
  8. Harness scenario: lock sticky → dip counts / lose follow → tip stays framed; no thin fallback.
  9. Update this table + docs/event-reason.md.
  10. Gate: new scenario ×3 + prior combat sticky ×3.

WarFront specifics

  • Sides: main_attacker vs main_defender kingdom names (pretty via KingdomDisplay).
  • Counts: kingdom countUnits / population; sticky roster seeds kings + nearby war units for promote-follow.
  • Enroll: matching kingdom key in theater radius (not combat-only).
  • City capture / siege: same war sides; do not invent a second sticky identity.
  • Begin/end war beats may stay FixedDwell one-shots; active wars use WarFront completion + sticky scoreboard.

Non-goals

  • Hardcoded complete lists of war types / plot ids as existence checks.
  • Parallel “war tip” string builders outside EventReason.
  • Director-owned side display strings.
  • Quitting the game from harness / mod code.