engine/effects/conditions
PURPOSE
Pure read-only predicates that gate effect actions. Each condition function inspects the signal snapshot, game state, ship state, or effect instance values and returns a boolean. The dispatcher evaluates a condition by type via a string-keyed registry; the batch evaluator short-circuits on the first failing condition so callers get all-or-nothing semantics for an effect’s condition list.
OWNS
evaluateCondition— dispatches a singleConditionDefto its handler bycond.typestring, throws on unknown types.evaluateAllConditions— iterates anEffectInstance.def.conditionsarray and returns true only if every condition passes; bails on first false.ConditionFn— internal function shape shared by all condition implementations.CONDITION_MAP— string-to-function registry that is the source of truth for which condition types are supported.- Condition implementations grouped into three buckets:
- Signal context checks:
condRandom,condSignalStr1Eq,condSignalStr1Neq,condSignalNum1Gte,condDamageTagEq. - Ship state checks:
condHpBelow,condHpAbove,condShieldActive,condShieldBroken,condShieldEmpty,condHasArtifact,condHasUpgrade,condHasBuff. - Game state checks:
condKillStreakAbove,condElapsedAbove,condElapsedBelow,condTierAtLeast,condHeatAbove,condSpeedAbove,condSpeedBelow,condBossActive,condBossPhaseGte.
- Signal context checks:
READS FROM
./types—ConditionDef,EffectInstance,SignalSnapshottype imports.../core/types—GameState,ShipStatetype imports../resolve-params—resolveNumberandresolveStringfor resolving condition parameter values againstinst.values.../core/modifiers—Modifiers._modsByTargetmap read lazily viarequireinsidecondHasBuffto avoid a circular dependency.SignalSnapshotfields read:snap.str1,snap.num1.ShipStatefields read:ship.hp,ship.shield,ship.vx,ship.vy, plus untyped casts tohpMax,_base.hpMax,heat, andeid.GameStatefields read:game.artifacts,game.upgradeCounts,game.killStreak,game.time,game.bossArena, plus an untyped cast to_currentLevel.EffectInstance.values— used by every parameter resolution call.EffectInstance.def.conditions— iterated byevaluateAllConditions.
PUSHES TO
- Returns boolean to the caller; no writes, no state mutation, no event emission.
- Throws
Error("Unknown condition type: ...")fromevaluateConditionwhen the type string is missing fromCONDITION_MAP.
DOES NOT
- Does not modify
GameState,ShipState,EffectInstance, or the signal snapshot. - Does not register effects, schedule actions, or evaluate triggers — only checks gating predicates.
- Does not execute the actions of an effect; that responsibility lies in the actions module.
- Does not maintain its own cooldown timers — cooldown semantics for triggers live elsewhere; conditions here are stateless reads.
- Does not implement
condBossPhaseGte— the handler is registered but currently returns false unconditionally. - Does not validate condition definitions at load time; unknown types only surface at evaluation.
Signals
SignalSnapshot.str1is read bysignal_str1_eq,signal_str1_neq, anddamage_tag_eq(the last treats thetagparam as the expected string).SignalSnapshot.num1is read bysignal_num1_gteand compared against the resolvedvaluethreshold.- When
snapis null,signal_str1_eq,signal_num1_gte, anddamage_tag_eqreturn false;signal_str1_neqreturns true (no snapshot means the value cannot equal the expected string). condRandomignores the snapshot entirely and rollsMath.random()against the resolvedchanceparameter (default 1).- Ship and game state conditions ignore the snapshot.
Entry points
evaluateCondition(cond, inst, snap, game, ship)— single-condition evaluation entry; used internally by the all-conditions helper and exported for direct use.evaluateAllConditions(inst, snap, game, ship)— batch evaluator overinst.def.conditions; the standard entry point for effect-trigger gating.CONDITION_MAPis module-private; new condition types must be added by extending this object.
Pattern notes
- Evaluation order in
evaluateAllConditionsis the array order on the effect definition; the file comment instructs authors to put cheapest checks first because the loop short-circuits on first failure. - All handlers are pure reads — conditions never mutate state, matching the file-level invariant.
- Parameter values are resolved through
resolveNumberandresolveStringso that condition params can reference variables inEffectInstance.values; literal string params (artifactId,upgradeId) are coerced viaString(...)without resolution. - HP-ratio conditions (
hp_below,hp_above) fall back throughship.hpMax, thenship._base.hpMax, then1to avoid division by zero on partially-initialized ships. shield_brokenandshield_emptyare aliases — both returnship.shield <= 0.condHasBuffuses a runtimerequire('../core/modifiers')instead of a top-level import to break a circular dependency between effects and modifiers; it reads the privateModifiers._modsByTargetmap keyed by entity id and matches any modifier whosesourceequals the param.condHasArtifactwalksgame.artifactsas anany[]and matches byid;condHasUpgradechecksgame.upgradeCounts[upgradeId] >= minLevel(default minLevel 1).condSpeedAboveandcondSpeedBelowcompute speed assqrt(vx^2 + vy^2)each call rather than relying on a cached value.condElapsedBelowdefaults itssecondsparam to 999999 so an unset threshold effectively passes;condElapsedAbovedefaults to 0.- The dispatcher throws on unknown condition types rather than failing silently, surfacing typos in effect definitions immediately at evaluation time.
condBossPhaseGteis a stub: registered inCONDITION_MAPasboss_phase_gtebut its body returns false, so any effect gated on it never fires.