Player ability event and cleanup plan¶
Historical working record: The details below describe decisions and source state at the time they were written, including superseded plans and verification status. For current scope use the playable-game roadmap; for implemented hook usage use the hook reference; for observed checks use the CLI report and playable-path report.
Status: proposal for discussion, 2026-10-29. No implementation is authorized by this document. The current system remains unverified; tests, compilation, builds and Unity import are deferred by the owner. Recreate the player ability system directly and remove superseded logic; do not retain an old-system fallback or require both systems to work together. Preserve authored ScriptableObject values and references during the change.
Goal¶
An ability's AB_ script should make its participation and behavior easy to find. An ability declares the events it listens to and implements its own response. Gameplay systems publish the moments they own. The runtime manages player-specific ability ownership and lifetime without an ability-name factory or a separate forwarding method for every event.
This direction supersedes the earlier idea of merely retaining and deduplicating the current runtime hook registration. Names and snippets here describe a possible design, not existing APIs.
Proposed shape¶
The more concrete player ability hook design supersedes the early sketches below wherever they disagree, especially for captured attack effects and subscription ownership.
- Each player has one event collection shared by that player's ability runtime and gameplay systems. It holds typed event channels; channels and subscriptions are not shared between players.
- Gameplay systems own the meaning and timing of their events. Movement publishes before-dash; weapons publish weapon stages. A genuinely new moment must be published where it occurs, once.
- Each ability's per-player behavior explicitly subscribes to the channels it needs, with its subscription and response in the same
AB_file. Add concise comments there when they explain the ability’s purpose, a non-obvious condition, ordering or an important interaction; avoid comments that merely repeat the code. The ScriptableObject definition remains shared; mutable ability state remains per player. - The runtime invokes a common ability setup operation and owns a subscription scope for each behavior. It does not list every event or check every ability name. A common channel implementation handles ordering and registration mechanics. Event declarations and their publication sites are still explicit; no reflection-based discovery or invisible auto-registration is required.
- Events are strongly typed so a publisher and subscriber agree on data and purpose. A channel can represent a notification or an ordered value transformation; one generic broadcast should not hide their different meanings.
Conceptual shape, with names to be chosen later:
// In the Bomb Roll behavior, in AB_BombRoll.cs
void Connect(PlayerEvents events, SubscriptionScope scope)
{
scope.Add(events.BeforeDash.Subscribe(OnBeforeDash, order: /* translate existing 600 */));
}
void OnBeforeDash(DashContext dash) { /* activate Bomb Roll */ }
// In the Survival Fitness behavior, in AB_SurvivalFitness.cs
void Connect(PlayerEvents events, SubscriptionScope scope)
{
scope.Add(events.DashCost.Subscribe(ReduceDashCost, order: 0));
scope.Add(events.DamageReaction.Subscribe(RecoverStamina, order: /* translate existing -100 */));
}
float ReduceDashCost(float cost) => cost - 5;
The examples are illustrative; they do not assert the exact API, all current arguments, or final location of the event collection. An ability may subscribe to several stages, and many abilities may subscribe to one stage. The subscription scope records each connection separately.
Agreed ordering design¶
Each subscription declares an integer priority group in its AB_ script. Valid groups are 0–99; negative values and values above 99 are invalid. Lower groups run before higher groups, and 99 is reserved for the last group. Multiple subscriptions may use the same group. The hook sorts the subscriptions and assigns their actual run positions automatically; it does not overwrite the declared group on the ability or asset.
For example, three subscriptions in group 0, two in group 1, two in group 2 and three in group 99 are assigned positions 1–3, 4–5, 6–7 and 8–10 respectively. An ability whose effect must run before another must use a lower group within the same stage. Separate stages represent different gameplay moments, not priority values.
Ties within a group must be deterministic and independent of ability acquisition order. Select a stable per-subscription identifier when the API is designed, including how multiple subscriptions from one ability are distinguished and how shared ability identities are handled. Do not depend on mutable display names, random values, or scene iteration order. Equal group numbers do not promise a particular meaningful order; if the ordering matters, use different groups.
Current negative order values and current unique-order checks belong to the old system. Translate them deliberately during migration while tracing real ordering dependencies; do not copy them mechanically or change authored ScriptableObject values. This is a design decision for the replacement system, not an implementation claim.
Worked design: Exploding Enchantment¶
See the updated worked example. The earlier live hit-subscription sketch did not cover a marked projectile hitting after the ability was removed; the new proposal captures an ability-owned effect on that attack and dispatches it at the correct hit stage. The logic inventory records the source evidence and remaining traces.
Event rules to settle and preserve¶
- Order and timing: inventory each current hook, order and invocation point before replacing it. Preserve the stages around dash admission, launch, attack construction, damage and weapon firing; do not flatten distinct stages into a single event. Follow the priority-group rules below. Current hooks generally reject duplicate order values; that is current behavior, not the new design.
- Value changes: a modifier channel passes each result to the next subscriber in order; it is distinct from a notification. Preserve current behavior such as Survival Fitness subtracting five from dash cost before the stamina check. Some other stages short-circuit, veto, or mutate context and need their own documented semantics.
- Dispatch behavior: specify snapshots, subscription changes during dispatch, reentry, exceptions and ordering. Current hooks commonly dispatch a snapshot; preserve that rule unless an explicit change is recorded.
- Lifetime: create subscriptions on acquisition; remove only the relevant ability's subscriptions when it is removed; dispose all remaining subscriptions with its player. Define duplicate assets, shared
AbilityTypevalues, removal and reacquisition before changing those semantics. Some existing registrations intentionally remain after removal: inventory each one and decide how to handle its lasting effects rather than silently dropping it. Failed setup must clean up subscriptions already created. - Work and cooldown: subscription cleanup and cancellation of running work are separate decisions. Preserve normal, bypassed, timed and count-based admission and document the fate of ongoing work on removal, suspension, death and disposal. Do not assume all effects share one cooldown model.
- Initialization: runtime and gameplay systems must receive the same per-player channels before either subscribes or publishes; define behavior for acquisition during setup and for later-created gameplay owners. No global/static event bus that mixes players.
- Ability identity: current acquisition and cooldown use
AbilityType, including shared values. Trace aliases and serialized routes before replacing identity; do not assume an asset uniquely identifies every existing behavior.
Organization pass · 2026-10-29¶
Player scripts have been grouped into Core, Movement, Combat, Weapons, Companions, Stats, Collectibles and Presentation; the ability definitions remain together. These are responsibility labels, not evidence that the implementations are final. AbilityHolder and the current scene dash path remain active transitional integration; the movement driver has a separate run-participant caller. Damage and dash hooks still contain transitional range calls, and attack impact still contains ability-specific logic. No behavior was removed in this pass.
CircularRotator remains at the player root pending a decision about its shared orbit responsibility; the player prefab references it. Wisp also remains at the root: a text scan found no serialized references in prefabs, scenes or assets, which is not proof that it is unused. Trace its creation and intended ownership before moving or deleting it. Existing historical CSV inventories may record pre-move source paths; Markdown source links were updated.
Work order after design discussion¶
- Inventory existing hooks, their publishers, subscribers, ordering and removal exceptions. Classify player scripts as active, transitional, or candidates for retirement from their callers; filenames alone are not evidence.
- Settle the event API, ownership and lifecycle above. Trace Bomb Roll's before-dash stage and Survival Fitness's dash-cost and damage stages as design examples, including both existing scene and movement-driver paths. Record differences and dependencies rather than introducing fallback paths.
- Organize player scripts by responsibility (the first cleanup outcome): keep
AB_definitions together; group movement, combat, weapons, companions, stats, collectibles, presentation and player core as appropriate. Preserve script and folder metadata, inspect serialized references, and avoid moving scripts into a category solely by name. Preserve authored ScriptableObject values and references; inspect serialized field and script-reference changes when connecting assets to new definitions. - Replace repetitive hook mechanics with the shared channel/subscription mechanism (the second cleanup outcome) and migrate event publishers and existing abilities in coherent groups. For each stage, replace registration, dispatch and all its callers together; move ability-specific decisions and effects into their
AB_scripts and remove the superseded runtime doorway, branches and hook code. For example, move Exploding Enchantment’s impact explosion out of PlayerAttack into AB_ExplodingEnchantment, while PlayerAttack publishes the appropriate collision stage. Preserve per-attack state and stage timing where understood. Do not keep legacy dispatch as a permanent fallback. Record any dependency that cannot be migrated in the same pass. - Review legacy player paths and consolidate dash around movement-owned state (the third cleanup outcome) and execution, preserving ability admission and event ordering. Trace both scene and movement-driver routes and their callers before retiring a route. Leave input redesign to its separate workload; retain a simple integration point for existing input.
- Reduce
AbilityHolderas its remaining responsibilities move: dash state, visual references and authored ability lists need suitable owners; Sky Ying Sword/Abyss Orb behavior and other remaining abilities belong to the later ability migration. Retire the holder only after its actual callers and serialized references have moved. - Write a practical ability-authoring guide with a notification and a value-modifier example after the API settles. Record unresolved issues and better design suggestions for discussion while continuing authorized work.
Scope and completion¶
This is a design and player-system cleanup plan, not the separate goal to migrate every remaining ability. GameManager, menu, scene, input and enemy redesign remain separate workloads. Existing call sites may require narrow integration edits. Do not run tests, compilation, builds or Unity import until the owner changes that instruction. Source inspection and metadata review do not establish gameplay correctness.