Skip to main content

The decision model

Everything nexBash does in combat comes down to one idea: on each game prompt it walks a priority lane top-to-bottom and uses the first action that is legal right now.

The pieces

PieceWhat it is
ActionOne ability: { id, queue, canExecute(ctx, tuning), execute(ctx, tuning) }. canExecute is a pure yes/no gate; execute returns commands.
LaneA curated, ordered list of catalog keys. The order is the priority. Strategies have a primary lane and usually a battlerage lane.
StrategyA class expressed as data: its lanes plus optional profile-scoped shared args. See Strategies.
Context (ctx)A fresh per-tick bundle of tactical answers and player-state predicates.
Action tuningA precomputed { strategy, action } envelope containing the active profile's two configuration scopes.
SelectorThe first-valid walk over a lane that produces the chosen action and its tuning.

The loop

On every prompt while in combat:

  1. nexBash builds one fresh decision context.
  2. It walks the active strategy's primary lane in order.
  3. For each catalog key it reads that key's precomputed tuning and asks canExecute(ctx, tuning).
  4. The first action that answers true wins. Its execute(ctx, tuning) receives the exact same tuning object and returns the commands to queue.
  5. If a coupled battlerage is ready, nexBash selects it through the same keyed tuning path and fuses it into the attack command stack.

Autonomous battlerages run an independent first-valid pass when rage/freerage state changes. See Battlerage.

Execution machine

The prompt and battlerage handlers run within the combat state of the core state machine. See the Overview for the complete state chart.

Because selection is a fresh walk each tick, priority is an order of preference, not a fixed script. A high-priority ability that is not currently legal is passed over in favor of the next valid one.

The two tuning scopes

Profile configuration is deliberately separate from the per-tick context. Every catalog key receives an explicit ActionTuning envelope:

{
strategy: { daggerId: "59237", scytheId: "330399" },
action: { hp: 0.3 },
}
  • tuning.strategy contains values owned by the whole strategy/profile, such as shared equipment identities.
  • tuning.action contains values owned by one catalog action, such as that action's HP threshold.

The scopes may use the same property name without colliding. Actions never read nexBash.currentStrategy to find configuration. When a profile is applied, nexBash derives immutable tuning objects and keyed lane entries for the effective lanes. Prompt and rage selection then perform reads over that precomputed state; they do not merge tuning per candidate. The winning reference is passed unchanged to execution.

Actions with no declared values still receive stable empty strategy and action objects. This keeps the invocation contract uniform without adding work to the hot path.

The decision context

canExecute(ctx, tuning) is the entire situational brain of an action, and it is pure: it reads only the context and static tuning, never host globals, and has no side effects. The context is the adapter that reads nexSys, nexGui, GMCP, and the target model and turns them into answers.

FieldMeaning
ctx.targetActive target facts: id, name, hp (0-100%), totalHp, shielded, shouldCC, cc, resistances, damageTypes, and canHeal.
ctx.hasTarget / ctx.targetCountWhether a target exists and how many mobs are in the room.
ctx.aoeTargetIdsUnshielded mob IDs available to multi-target abilities.
ctx.partyParty members, leader, size, isMember, and isLeader.
ctx.enabled(key)Whether a catalog key belongs to an effective lane.
ctx.haveAff / ctx.haveAnyAff / ctx.haveDef / ctx.haveBal / ctx.isClassPlayer-state predicates delegated to nexSys4.
ctx.selfHp / ctx.selfManaSelf vitals as 0-1 ratios.
ctx.rage / ctx.spark / ctx.transcendenceClass resource pools.
ctx.wieldedThe character's currently wielded items.
ctx.battlerageBattlerage balance, live flags, configured buffers, and razeReady.
ctx.roomEnvironment answers such as canFly, canBurrow, and fleeDirection.
ctx.configGlobal player options plus the active area's targetThreshold.

ctx.config remains the adapter for global options and active-area facts. It is not a strategy-profile configuration channel; those values belong in ActionTuning.

A damaging action is rejected before its local gate when the active target resists the action's damage type.

The action catalog

Every ability lives in a flat, namespaced catalog, keyed like magi.erode, battlerage.disintegrate, or general.fly. Lanes reference these keys, and the configuration bench is drawn from keys available to the selected class.

nexBash.actionCatalog.list({ namespace: "magi" });
nexBash.actionCatalog.get("magi.dissolution");

Where rules fit

nexBash does not use a per-tick rules engine to mutate priorities. The condition "use this ability only when these facts hold" lives directly in each pure gate, and first-valid resolves the ordered lane. A general rule registry exists as reserved infrastructure but is not wired into the runtime or public contract.

Seeing why an action was chosen

A decision trace can record each selection pass for debugging. It is off by default so the hot path stays free:

nexBash.trace.enable();
// fight for a bit
nexBash.trace.list();
nexBash.trace.disable();

You can also subscribe to a live stream with nexBash.trace.subscribe(fn).