4.7 KiB
4.7 KiB
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)
- Lock opposing identity once when the live scene first has clear sides (kingdom / species / plot cell / family).
- Enroll member ids while the scene is hot; prune dead; allow new matching members.
- Counts may hit 0 on a wiped side; survivors on the other side stay counted.
- Scale / tier hold (~2.5s) so tips do not flap (Mass↔Battle, War↔thin unit line).
- Promote follow from the sticky roster when the camera subject dies - do not end into thin prose.
- Never use unit proper names as collective side labels.
- Never collapse a sticky multi tip to
{name} is fighting/ bare melee crumbs. - 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:
- Add / use
EnsembleKindvalue (already stubbed where possible). - Implement
LiveEnsemble.TryBuild…from live world state. - Add
EventReason.…and wireEventReason.Ensembleswitch (no silent fall-through to Combat unless identical). - Set
Sticky.Kindon lock; enroll rules may differ (combat = fighters; war = kingdom members; plot = plot.units). - Choose
InterestCompletionKindthat stays active while the scene is live (not only while one unit swings). - Director: maintain/handoff path calls
StickyScoreboard.Refresh/TryPromoteFollow/StabilizeScale. - Feed registers
AssetId(live_battle,live_war, …) + completion + presentable Label. - Harness scenario: lock sticky → dip counts / lose follow → tip stays framed; no thin fallback.
- Update this table +
docs/event-reason.md. - Gate: new scenario ×3 + prior combat sticky ×3.
WarFront specifics
- Sides:
main_attackervsmain_defenderkingdom names (pretty viaKingdomDisplay). - 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
WarFrontcompletion + 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.