engine/player

PURPOSE — Player status-effect taxonomy. Owns the at-most-one exclusive state slot on the ship (currently only starpower), exposes a small apply/query/tick API that other systems hit to grant a state, gate behavior on it, and age the timer each frame.

OWNS

  • The two-category model: a single exclusive-state slot per ship (timer-based, replaces on different grant, stacks duration on same grant) and the convention that additive states live as their own plain booleans on ShipState with no central registry.
  • The PlayerExclusiveState string union — the canonical list of named exclusive states.
  • The exclusive-state fields on ShipState: the active state name, the remaining timer, and the peak-timer high-water mark used as the UI fill denominator.
  • The stack rule for repeated grants of the same exclusive state (timer addition) and the peak-timer bookkeeping that survives subsequent ticks until the state clears.
  • The tick semantics: decrement-then-clear on expiry, including resetting the peak high-water mark when the slot empties.

READS FROM

  • engine/core/types for the ShipState shape it mutates.

PUSHES TO

  • Nothing. The module is a pure mutator over fields on the ship passed in; it does not call into other engine modules, fire signals, or write to any global.

DOES NOT

  • Define what an exclusive state actually does — damage multiplier, invulnerability sync, thrust/max-speed bumps, heat lockout, shader and trail VFX are all applied by the consuming systems (combat, physics, rendering, weapons, vfx) reading the state via the query helper.
  • Decide when to grant a state. Event rewards, shooting-star pickups, and any other trigger source live in their own systems and call in through the apply helper.
  • Own additive (boolean) status effects. Those are plain fields on ShipState written and read directly by the systems that care; this module neither registers them nor ticks them.
  • Sync the ship.invulnerable flag, the rainbow-chrome shader, the rainbow particle trail, or any other observable side-effect of being in Star Power. Consumers read the state and apply their own effects.
  • Drive UI. The HUD reads the timer and peak timer fields to fill its bar; this module only maintains them.
  • Validate the duration argument or clamp against any cap — repeated grants extend without bound.

Signals fired / Signals watched — none. The module is a synchronous helper API; it does not emit or subscribe to engine signals.

Entry points

  • applyExclusiveState — grant or extend an exclusive state on a ship; same-state grants add to the timer and bump the peak high-water mark, different-state grants replace.
  • hasExclusiveState — predicate used by consumer systems to gate behavior on whether a given exclusive state is currently active.
  • tickPlayerStates — once-per-sim-frame timer decrement; clears the slot and resets the peak high-water mark when the timer hits zero.

Pattern notes

  • Asymmetric design between the two categories: exclusive states get a single shared slot plus a string-union type, additive states are intentionally just booleans with no registry. The comment header is explicit that new additive states should be added as their own field rather than routed through this module.
  • The replace-on-different-state branch exists but is unreachable today because the union has one member; it’s left in place as the contract for when a second exclusive state lands.
  • The peak-timer field is a UI affordance baked into the state model: consumers don’t have to remember the original grant duration to render a depleting bar, the module preserves it for them and clears it on expiry.
  • The module-level functions take the ship by parameter rather than reading a shared singleton, so the same helpers would work for any future ship-shaped entity without refactor.