Skip to content

Ability authoring working notes

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.

For the complete current hook and scope list, see the ability hooks reference.

These notes describe a partial, unverified migration. The new hook system is not complete; some supporting access and lifecycle contracts still remain, while gameplay hook stages now use player-local subscriptions. This is not the final authoring guide. Compilation, Unity import and tests have not been run.

Dash participation

An ability that participates in the migrated dash stages implements IConnectPlayerHooks. Its per-player behavior connects through the supplied scope; the runtime tracks that scope and disconnects it when the last acquired entry for the current ability definition script type is removed or the player owner is disposed.

public void Connect(PlayerHookScope scope)
{
    scope.Listen(MovementAbilityHooks.BeforeDash, BeforeDash, 7, "before-dash");
    scope.Modify(MovementAbilityHooks.DashCost, ModifyCost, 0, "dash-cost");
}

The example combines two stages to show their forms. Notifications take a PlayerDashFeedback; cost modifiers receive and return a float. Groups 0–99 run in ascending order; equal groups sort by behavior type name and subscription id. That tie-breaker is an interim implementation: renaming a type changes it, and current behavior identity is keyed by the definition script type. Definitions that use the same script type and duplicate acquisitions share the first behavior under the existing runtime. Serialized AbilityType values remain for other legacy consumers. Replacing that identity with a durable scheme remains open. An ability may choose a stable id within each stage, and identical ids on a single stage for the same behavior are rejected.

Dash subscriptions were assigned distinct groups to preserve their former relative sequence; do not copy the old negative values or values above 99 to the new hub. A dispatch uses a snapshot. Removal prevents future dispatches but a callback already in a snapshot can still run. Running coroutines are managed separately from subscriptions.

Damage, pickups and weapon notifications

Damage reactions now use CombatAbilityHooks.DamageReaction; the 12 participating ability behaviors register in their own scripts. The former consecutive damage-reaction ranges have been replaced by one ordered snapshot after damage reflection. Their relative order was translated to groups 0–11. This changes the snapshot boundary: an ability added or removed during an earlier reaction no longer changes which handlers run later in that same dispatch; reentrant dispatch takes a new snapshot. That behavior needs later gameplay review.

CollectibleAbilityHooks exposes effect-count modification and distinct chest, coin, potion-collected and potion-applied notifications. Alchemist Touch, Stalwart Body, Invigorate, Super Magnet and Opportunist subscribe to their corresponding stages. Existing collection/application placement and count calculation remain in the pickup caller. Invigorate still uses its separate capacity-bypass contract.

WeaponAbilityHooks.BeforeFire now connects 12 weapon-fire behaviors. The old consecutive ranges have been replaced with one ordered snapshot before projectile spawning; as with damage reactions, mutations during dispatch affect the next dispatch, not a later range in the same dispatch. The fireWeapon flag remains part of the context for companion-triggered calls.

WeaponAbilityHooks.ExtraShots connects Multishoot, and WeaponAbilityHooks.Excavation connects Ground Breaker's excavation to Lucky Find. Their old runtime forwarding methods and old individual hook classes have been removed. Multishoot's separate attack-construction behavior and other Ground Breaker stages still use the earlier contracts.

Shields, health damage and criticals

Shield abilities subscribe to CombatAbilityHooks.AbsorbShield through scope.Decide. The player damage caller checks them in groups 0–4 and stops at the first handler that absorbs the hit. Health damage uses an ordered scope.Modify with both hit context and the current damage value; Unique Constitution and Emergency Shield apply in that sequence. The former shield and health-damage range dispatchers and their runtime forwarders were removed.

Critical eligibility uses separate guaranteed-critical and forced-critical decision channels. The distinction matters: Soul Wager consults the guaranteed channel but not Rebuke's forced-critical channel. Critical processing publishes a stage before the guaranteed-critical reset and one after it; Sure Fire participates before the reset, while Ground Breaker, Fatal Flow Knowledge, Piercing Bullet and Concussive Impact participate after it in their former relative order. The old critical dispatchers and runtime forwarders were removed.

Removal now disconnects shield and critical subscriptions with the ability scope. Previously shield registrations, Sure Fire's critical registration and guaranteed/forced critical registrations could remain after the last ability entry was removed. Their continued behavior is intentionally no longer retained as a fallback; active shield visual state and in-flight work are not automatically cancelled by subscription removal and need a separate lifecycle policy. Existing mutation-during-dispatch behavior also changes from multiple range snapshots to one snapshot per new stage. These changes are uncompiled and untested.

Weapon, combat and automatic stages

Reload Shield, Star Burst, Unsteady Hand, Soul Wager, Double Edge and Proto Barrier now register the reload, charge, base-damage, post-critical damage, weapon-lifecycle and companion-interval stages in their own scripts. The old runtime forwarders and hook classes for these stages have been removed. Star Burst clears charge, Unsteady Hand restores its multiplier and Soul Wager clears its per-weapon records when their subscription scopes are disposed. Previously base-damage and weapon-lifecycle registrations could remain after removal; they now disconnect, so reacquisition and in-flight behavior need later gameplay review.

Before-immunity reflection, damage reflection, thorn retaliation and death prevention also use ability-owned subscriptions. The reflection decision deliberately stops after the first registered reflection ability, preserving the existing Electrical Thorn Armor-before-Thorn Armor selection. Last Stand uses a handled decision. Thirteen automatic abilities now subscribe to a single ordered player tick; the old tick contract and registration list were removed. The runtime now connects one ability-owned scope instead of maintaining individual gameplay contract registrations.

Scope cleanup runs all registered cleanup callbacks even when one fails and reports failures afterward. This does not yet define a complete cancellation policy for active coroutine work or visuals on ability removal. All these paths are source-level work and remain uncompiled and untested.

Attack preparation and hit effects

Weapon attack preparation now publishes three player-local stages: before SpellAmp, before the critical roll, and after damage calculation. Fifteen previous attack-construction participants were moved to subscriptions in their own ability scripts, with groups chosen from their previous relative sequence. Old attack-construction hook classes and runtime forwarding were removed. Blood Thirst, Ground Breaker and Neo Sword previously retained the attack-construction registration after removal; they now disconnect with their ability scope. This is an intentional change to lifetime policy and needs later gameplay review. Multiple old range snapshots were replaced by one snapshot per new stage.

Freezing Pendant, Concussive Impact, Exploding Enchantment, Ricochet, Hydra Barrage, Poison Vial, Vacuum Dodge, Eclipse, War Cry, Emergency Shield and Electrical Thorn Armor attach their own static hit effects to an individual attack. A shared vampiric effect handles the common effect granted by Blood Thirst and Vampire BE. Hit effects run in group order and reset on pooled attack enable. Freeze, poison and concussive effects copy to explosions and child projectiles; vampiric copies to ricochet and Hydra children but not to explosions. Other effects do not propagate automatically. The old ability-specific branches and corresponding runtime flags were removed from PlayerAttack. An existing Shrapnel branch contained no implemented effect and was removed; its asset remains and its behavior needs a future design.

The attacker still carries its own effect entries after the ability is removed. The effects access attack/player data at hit time where the old path did so; they are not complete immutable snapshots, and a destroyed player or stale pooled references remain unresolved. Companion attack construction now uses the new player-local hook. Proto Barrier now attaches an ordered per-attack damage modifier; its enemy-type damage choice lives in AB_ProtoBarrier. Sky Ying Sword marking and lightning are now ability-owned and the enemy only toggles its mark. Its sword summoning, upgrade settings, projectile override and related holder state are still pending. Attack effects and the player hub are separate ordered collections; no combined dispatch exists yet. No runtime correctness is claimed.

Companion, ownership and removal review

Five companion attack behaviors now subscribe through CompanionAbilityHooks.AttackPrepared; the old companion-attack contract, hook and runtime forwarding method were removed. The existing companion-attack-interval contract is separate and remains.

Sky Ying Sword's authored asset now references AB_SkyYingSword; only its script GUID was changed in the asset text. Weapon and carrier attack preparation attach the marking effect; the attack keeps it after removal. The ability owns the lightning spawn, and the enemy toggles its mark. Summoning, sword counts, upgrade configuration, visuals and the ricochet projectile override still use holder/character paths and are not a full Sky Ying Sword migration.

On pooled attack reset, effect lists, owner, damage, SpellAmp, speed, damage type and character flags are cleared. Companion, explosion, ricochet and Hydra producers now explicitly assign the owning player, as do Emergency Shield and Electrical Thorn Armor attacks. Secondary effect propagation uses distinct child and explosion flags; a missing owner safely suppresses the effects that require that owner. Live player values at impact and player destruction/pool reuse are not runtime-qualified.

The three attack-preparation stages run ordered snapshots with groups 0–99, using behavior type name and subscription id as the current tie-breaker. Type names are not a durable identity. Scope disposal stops future preparation immediately after removal; captured projectile effects persist. This intentionally differs from the prior retained attack-construction registrations for Blood Thirst, Ground Breaker and Neo Sword. The exact impact of this policy, repeated acquisition and shared AbilityType aliases remains unverified and needs later design and gameplay review.

Dash movement state

PlayerMovementState now owns dash speed, direction and active state. Movement, weapons, run setup and the existing scene input adapter use that state instead of mutable AbilityHolder dash fields. The prefab's dash audio binding moved from AbilityHolder to Player without changing its asset reference; the former serialized dash-state defaults were removed from the prefab. Character ScriptableObject dash speeds were not changed. Old editor fixtures were updated to refer to the new state but were not run.

The scene input polling still lives in AbilityHolder and the movement-driver route remains separate. Their timing, cancellation and ownership need review as part of the deferred input integration; this move alone does not establish a single dash entry route. The scene initializer supplies the character dash speed and the run participant supplies it for its route. Unity prefab import and runtime behavior are unverified.

Identity and removal update

The runtime now keys behavior objects, hook scopes, activation and owned work by concrete ability definition script type, rather than AbilityType. An ability activates its own definition with UseAbility<AB_Name>(...). Definitions with the same script type intentionally share a behavior; distinct authored assets still have distinct runtime copies, and reacquisition of the same asset reuses that copy. The current project asset scan found no nonzero serialized ability type that maps to multiple ability scripts; this is text evidence, not Unity validation. Existing AbilityType checks and serialized values remain in other systems and have not been removed. Script type names and behavior type names can change on rename, so durable cross-version identity and shared-script evolution semantics remain unresolved.

When the last acquired entry for a script type is removed, the runtime disconnects its scope, cancels ability-owned execution/cooldown work, and resets the affected cooldown display/flag. Enrage and Panic Frenzy now identify their manually started work with their own ability definition. Existing general player work using Run is not tied to an ability. Attack effects already captured on a projectile remain independent. Shield abilities register cleanup to hide and reset their own views; Jet Propulsion resets its state and pending start. Scope and cancellation errors are collected; callbacks already executing can complete their current synchronous step before cleanup. Behavior objects remain retained for reacquisition/configuration, so this is not yet a full release of per-ability state.

The player now owns the serialized starting-definition list and exposes read-only starting/current views; the runtime does not alias that list. Scene ability-list overrides were retargeted to the player component while preserving their recorded values. Dispose/reinitialization now starts from authored definitions rather than retaining the previous run’s acquired entries. PlayerModifier and PlayerStatProjection still contain explicit ability-derived inputs and formulas. Their additive/multiplicative operation order, captured health samples and shared potion inputs must be mapped before moving contributions. No asset values were changed by this identity update. The runtime, cancellation, reentry, shared views and identity changes remain uncompiled and untested.

Character ability and player setup integration

Sky Ying Sword and Abyss Orb now own their summon counts, initial spawning, upgrade actions and removal cleanup in their respective ability scripts. Summon requests made before runtime activation are queued until their tick; initial spawning also occurs on that tick. Their existing prefab references still live in AbilityHolder. Shared Celestial sword damage is held in player character settings because both sword paths use it. CompanionOrb reads the Abyss ability's orbit state; weapon-fire suppression is registered by each character ability instead of named weapon checks. The Abyss Orb asset's script reference was changed to its AB_ definition; its other serialized fields were unchanged in a text comparison. The older notes above that say these summon paths still belong to AbilityHolder are historical.

The scene initializer now waits for each player's weapon and stat setup coroutine, initializes the player ability owner and acquires the character ability before setting characterInit. The runtime's acquired entries are no longer the same list as the serialized starting definitions. UI and reporting readers use the player’s read-only current view; the prefab's starting list and 28 existing scene overrides were retargeted to the player component without changing override values in text. These setup, pool reuse, co-op, acquisition/reacquisition, upgrade-before-activation and serialized-scene changes require later Unity verification. Each summoned companion records its pool activation generation; cleanup only returns an object if that generation still matches. Suspension clears the companions and schedules initial companions on reactivation. This pooling and revival policy needs Unity verification.

Progression and potion ownership

Future Pills and RPG Buff now own their level-up effects in their AB_ scripts. PlayerManager publishes two player-local progression phases in the previous all-players-first order, rather than checking those ability names. The two existing asset files retain their authored fields; only their script references were replaced. Future Pills owns its ten-use counter. RPG Buff's integer roll still selects only the three reachable outcomes in the previous code.

Potion active flags now belong to each player, rather than AbilityHolder. Timed potion workers are keyed by both player and potion type, so one player's potion no longer cancels the other player's timer. This intentionally changes co-op behavior from the old shared-per-type timer. Potion timing still uses the global effect service, and owner disable/reuse and timer cleanup remain to be designed and verified. No Unity/runtime checks have been run.

Health recovery and companion suspension

Health recovery asks ability hooks whether to block recovery and then collects ordered contributions. Sudden Death also blocks direct healing through a separate hook; RPG Buff retains its existing Last Stand exception. Vitamin, petrification, Survival Instinct and Meditate recovery now live in their ability behaviors. Petrified Skin and De-Petrify share one recovery contribution when both are present.

Sky Ying Sword and Abyss Orb return their summoned companions on suspension and restore the previous count on reactivation. Final removal clears the saved count and queued summons. Pool reuse, repeated activation and upgrade ordering still need Unity verification.

Stat state and removal

Stat formulas remain together in PlayerStatProjection, as requested in the two-part stat design. PlayerModifier and the status display read behavior state only while that ability is acquired. Runtime behavior objects can still be retained for upgrade configuration, so their existence alone does not mean the player has the ability. Berserker Blood, Blood Thirst, Survival Instinct and Sure Fire clear their sampled or temporary state when their scope is removed. Other retained counters and upgrade settings still need a per-ability lifecycle review.

Equipping an ability from an upgrade now publishes an ability lifecycle event after acquisition. Multishot responds to its own equip and requests extra companions through a companion hook; sword and orb abilities handle their own summons. Queued equips retain the distinction from normal acquisition. Initial ability ordering and upgrade-before-character-setup behavior still require review.

Additive upgrade values now use an ordered ability hook. Adept Learner, Mage Apprentice and the character reload adjustments register their own rules. The normal reward path must prepare the player ability runtime before applying a stat upgrade; an unprepared call is rejected before changing the upgrade count. Both converted passive ability assets retain their other authored fields. Initialization and reward ordering remain unverified.

Evolution thresholds now come from the six applicable ability definitions instead of an ability-type switch. Timed and counter-based evolutions still run on the existing evolution service, and its counters are shared game data. Per-player counter ownership and cancellation on ability removal need a separate behavior decision; the migration does not claim those are solved.

Next implementation

Migrate the remaining hook stages and their callers in connected batches; remove their old runtime registration and forwarding paths as each stage is replaced. Address attack ownership and pooled payload state, remove ability-specific impact logic, and migrate remaining holder/dash state and serialized references. Do not silently alter ScriptableObject data. Do not treat this partial example as evidence of runtime correctness.