@vibemancer/core 1.0.8 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -87,6 +87,32 @@ interface ActionBuilder extends FinalAction {
87
87
  * Positive X = right, positive Y = down.
88
88
  */
89
89
  move(x: number, y: number): FinalAction;
90
+ /**
91
+ * Fire at the angle you asked for, instead of auto-aiming at the enemy.
92
+ *
93
+ * By default a wizard re-aims at the enemy's CURRENT position every tick, and a missile
94
+ * launches with the rotation it has when the cast finishes — so the `direction` you pass
95
+ * to `missile()` is overwritten before the shot leaves. That makes the enemy impossible
96
+ * to LEAD: you always fire where they are, never where they will be.
97
+ *
98
+ * `lockAim()` holds your angle for the whole cast — but it is USUALLY THE WRONG TOOL for
99
+ * leading, and this comment used to claim the opposite. Measured over 1,140 matches
100
+ * against the built-ins: auto-aim wins 80.5%, lockAim with a computed lead wins 29.3%,
101
+ * and it degrades monotonically the more shots you lock.
102
+ *
103
+ * The reason is cast time. A cast runs 88-240 ticks — up to 2.4 SECONDS — and lockAim
104
+ * freezes the angle at cast START, so it is stale long before the missile leaves. No bot
105
+ * holds a heading that long.
106
+ *
107
+ * TO LEAD A TARGET, call `aim(degrees)` on every tick of the cast and recompute the lead
108
+ * each time: same harness, 85.4%. That is what makes `getLeadPosition` worth calling.
109
+ *
110
+ * lockAim is for aiming at a PLACE rather than a bot — a lava edge, an escape lane, an
111
+ * area you want denied — where a fixed angle is the point.
112
+ *
113
+ * Chainable — `missile(cfg, ai, angle).lockAim().move(0, 1)` aims, fires and kites.
114
+ */
115
+ lockAim(): ActionBuilder;
90
116
  }
91
117
  /**
92
118
  * Wizard function type for the hooks API.
@@ -307,9 +333,15 @@ interface WizardActions {
307
333
  */
308
334
  cancel?: boolean;
309
335
  /**
310
- * Update aim direction while casting a missile (degrees).
311
- * The missile fires in this direction at launch, allowing tracking during cast.
312
- * Only applies while state === 'casting' and castingSpell === 'missile'.
336
+ * Set the wizard's facing this tick (degrees). Set it with `aim()`.
337
+ *
338
+ * A missile launches with whatever rotation the wizard has when the cast COMPLETES, so
339
+ * applying this on every tick of a cast is how you aim a shot at a moving target —
340
+ * recomputing the lead each tick, rather than freezing it at cast start.
341
+ *
342
+ * Applies whenever it is set, not only while casting: the previous version of this
343
+ * comment claimed "Only applies while state === 'casting' and castingSpell === 'missile'"
344
+ * and the engine has never checked either condition.
313
345
  */
314
346
  aimDirection?: number;
315
347
  /**
@@ -609,7 +641,7 @@ declare function currentRuleset(): Record<string, number>;
609
641
  * Used to gate spectator replays: a recorded match can only be re-simulated when
610
642
  * the runtime engine version matches the version that produced the match.
611
643
  */
612
- declare const ENGINE_VERSION = 3523070221336433;
644
+ declare const ENGINE_VERSION = 293192982913104;
613
645
  /**
614
646
  * Historical versions that denote the SAME engine as `ENGINE_VERSION`.
615
647
  *
@@ -802,6 +834,15 @@ interface InternalWizardState extends WizardState {
802
834
  knockbackVx?: number;
803
835
  knockbackVy?: number;
804
836
  knockbackDelay?: number;
837
+ /**
838
+ * A heading this wizard LOCKED for the duration of a missile cast, via `lockAim()`.
839
+ *
840
+ * Auto-aim rewrites `rotation` every tick and a missile launches with the rotation at cast
841
+ * completion, so an angle applied only at cast start never survived. Holding it here is
842
+ * what lets a bot lead a moving target. Absent for every bot that does not opt in, which
843
+ * is why adding it changed no existing behaviour.
844
+ */
845
+ castAimDirection?: number;
805
846
  knockbackPendingVx?: number;
806
847
  knockbackPendingVy?: number;
807
848
  }
@@ -841,6 +882,15 @@ interface BotError {
841
882
  tick: number;
842
883
  entityId: string;
843
884
  message: string;
885
+ /**
886
+ * 1-based index within the fight's TEN matches, present only on `FightResult.allErrors`.
887
+ *
888
+ * A single `simulate()` knows nothing about a series, so it does not set this. `fight()`
889
+ * stamps it, numbering 1..10 in the order matches are played. Every tool description
890
+ * promises "a 10-match series (5 spawn distances, each played twice with sides swapped)",
891
+ * so a match index that only ever reached 5 was uninterpretable to the player reading it.
892
+ */
893
+ match?: number;
844
894
  }
845
895
  interface SimulateResult {
846
896
  /** 'wizard-1'/'wizard-2' = killed opponent, 'draw' = simultaneous kill, null = timeout */
@@ -865,6 +915,19 @@ interface FightResult {
865
915
  * Used for visual playback in the web viewer. Scoring includes both sides.
866
916
  */
867
917
  matches: SimulateResult[];
918
+ /**
919
+ * Every bot error from ALL TEN matches, including the swapped half that `matches` drops.
920
+ *
921
+ * `matches` exists for visual playback, so it holds only the five non-swapped results.
922
+ * Anything asking "did this bot crash?" needs the whole series: a bot that throws only
923
+ * when it spawns on one side was previously invisible, reporting a clean `success: true`
924
+ * while losing every match.
925
+ *
926
+ * Entity ids are in the CALLER's frame of reference. In a swapped match the engine calls
927
+ * bot 2 "wizard-1", so those records are remapped on the way out — passing them through
928
+ * raw would blame each side's faults on the other.
929
+ */
930
+ allErrors: BotError[];
868
931
  }
869
932
  /** Spawn distances for the 5 matches in a fight (creates butterfly effect variation) */
870
933
  declare const FIGHT_SPAWN_DISTANCES: number[];
@@ -1648,7 +1711,7 @@ declare function analyzeThreats(myPos: Position, projectiles: ProjectileState[],
1648
1711
  *
1649
1712
  * Starts at 90% block, decays by 20% per second, minimum 30%.
1650
1713
  * Takes 20 ticks (0.2s) to activate. Movement is disabled while channeling.
1651
- * Cancel anytime with cancel(). Triggers 100-tick (1s) GCD after cancel.
1714
+ * Cancel anytime with cancel(). Cancelling costs NO GCD you are idle the next tick.
1652
1715
  *
1653
1716
  * Can chain .move() — movement applies during the 20-tick cast, NOT during channel.
1654
1717
  *
@@ -1699,6 +1762,16 @@ declare function shield(): ActionBuilder;
1699
1762
  * );
1700
1763
  */
1701
1764
  declare function missile(config: MissileConfig, ai: MissileFunction$1, direction: number): ActionBuilder;
1765
+ /**
1766
+ * Face a specific direction this tick, instead of auto-aiming at the enemy.
1767
+ *
1768
+ * Useful on its own (turning to cover an approach) and during a cast, where it keeps a
1769
+ * locked shot pointed where you want it. Angles are degrees, 0 = right, 90 = down.
1770
+ *
1771
+ * A non-finite angle is ignored rather than applied, because a NaN rotation propagates into
1772
+ * every position calculation that follows it.
1773
+ */
1774
+ declare function aim(degrees: number): ActionBuilder;
1702
1775
  /**
1703
1776
  * Teleport to an absolute position on the arena.
1704
1777
  *
@@ -1720,7 +1793,13 @@ declare function blink(x: number, y: number): FinalAction;
1720
1793
  /**
1721
1794
  * Cancel current cast or channel (e.g. stop shielding to attack).
1722
1795
  *
1723
- * Canceling a cast/channel triggers 100-tick (1s) GCD.
1796
+ * Cancelling costs NO GCD. `gcdRemaining` is set only when a spell COMPLETES; the cancel
1797
+ * handlers just return you to idle, so you can act on the very next tick.
1798
+ *
1799
+ * This said "triggers 100-tick (1s) GCD" and was wrong for as long as anyone can tell. It
1800
+ * matters more than a typo: it makes every defender look 100 ticks slower than they are, so
1801
+ * attacks that appear unpunishable on paper are not. Cancel-then-shield really costs 1 tick
1802
+ * plus the 20-tick shield cast, not 120.
1724
1803
  * Can chain .move() for simultaneous movement.
1725
1804
  *
1726
1805
  * @example
@@ -2199,11 +2278,17 @@ declare function Infernalist(): FinalAction;
2199
2278
  /**
2200
2279
  * Bot: Spellshot
2201
2280
  *
2202
- * BEHAVIOR: Uses interceptAngle to calculate where the enemy will be and fires
2203
- * fast, non-homing missiles (speed 8, turnRate 0) along the predicted path.
2204
- * Strafes at medium range (300-400), shields undodgeable threats, emergency
2205
- * blinks when shield isn't available. The key mechanic is PREDICTION — these
2206
- * missiles don't track, they go exactly where you calculated the enemy would be.
2281
+ * BEHAVIOR: fires lightly-homing missiles (turnRate defaults to 0.3) aimed at the enemy's
2282
+ * CURRENT position, strafes at medium range (300-400), shields undodgeable threats, and
2283
+ * blinks when a shield isn't available.
2284
+ *
2285
+ * This description used to claim it "uses interceptAngle to calculate where the enemy will
2286
+ * be" and fires "non-homing missiles (speed 8, turnRate 0)" whose "key mechanic is
2287
+ * PREDICTION". None of that was true — it calls angleTo(position, enemy.position) and lets
2288
+ * the missile home. That mattered because this is the exemplar other snipers are read from,
2289
+ * and until 2026-09-10 aiming ahead was impossible anyway: the engine auto-aimed at the
2290
+ * enemy every tick and discarded whatever angle a bot passed. Leading is now possible with
2291
+ * per-tick aim(), but this bot does not do it.
2207
2292
  *
2208
2293
  * NAMING RATIONALE: "Spellshot" — a spell that is a single, precisely aimed shot.
2209
2294
  * Like a sniper's "called shot" but magical. The defining feature is the intercept
@@ -3130,4 +3215,4 @@ declare function diagnoseTrace(events: TraceEvent[], summary: TraceSummary): str
3130
3215
  /** Format diagnostic tips as a human-readable string. */
3131
3216
  declare function formatDiagnosis(tips: string[]): string;
3132
3217
 
3133
- export { Infernalist as $, ALL_BOTS as A, BLINK_CAST_TIME as B, CASTING_MOVEMENT_MULT as C, Critter as D, DEFAULT_BUDGET as E, type FightResult as F, DEFAULT_FIGHT_BACKSTOP_MS as G, DEFAULT_SEED as H, Doombringer as I, ENGINE_VERSION as J, EQUIVALENT_ENGINE_VERSIONS as K, type EnemyState as L, FIGHT_SPAWN_DISTANCES as M, type FightBudget as N, type FightStats as O, type FightWinner as P, type FinalAction as Q, Flamecaller as R, type SimulateResult as S, GCD_DURATION as T, type GameConfig as U, type GameState as V, Golem as W, Hero as X, Hogger as Y, type HomingParams as Z, type HookState as _, ARENA_MAX as a, Spellbinder as a$, type InternalWizardState as a0, KNOCKBACK_DAMAGE_THRESHOLD as a1, KNOCKBACK_DECAY as a2, KNOCKBACK_DELAY as a3, KNOCKBACK_SPEED_PER_DAMAGE as a4, LAVA_BORDER_WIDTH as a5, Lich as a6, MATCH_DURATION as a7, MAX_HEALTH as a8, MISSILE_BASE_CAST as a9, type MissileHitEvent as aA, type MissileLaunchEvent as aB, type MissileOobEvent as aC, type MissileTemplate as aD, Nightblade as aE, type ParamDeclaration as aF, type Position as aG, type ProjectileState as aH, Pyromancer as aI, RULES as aJ, RULESET_RANGES as aK, type RefObject as aL, Rookie as aM, SHIELD_CAST_TIME as aN, SHIELD_DECAY_PER_SECOND as aO, SHIELD_DECAY_RATE as aP, SHIELD_MAX_BLOCK as aQ, SHIELD_MAX_STRENGTH as aR, SHIELD_MIN_BLOCK as aS, SHIELD_MIN_STRENGTH as aT, SPAWN_DISTANCE as aU, type SeekerParams as aV, Sentinel as aW, Shadowblade as aX, type ShieldBlockEvent as aY, type ShieldStartEvent as aZ, type SimEvent as a_, MISSILE_BASE_RADIUS as aa, MISSILE_DAMAGE_POWER as ab, MISSILE_DAMAGE_RADIUS_SCALE as ac, MISSILE_DAMAGE_SCALE as ad, MISSILE_HOMING_COEFF as ae, MISSILE_MIN_CAST_TIME as af, MISSILE_MIN_DAMAGE as ag, MISSILE_MIN_DURATION as ah, MISSILE_MIN_SPEED as ai, MISSILE_RADIUS_PER_DAMAGE as aj, MISSILE_SPEED_DURATION_BASELINE as ak, MISSILE_SPEED_DURATION_COEFF as al, MISSILE_TURN_DURATION_COEFF as am, MOVEMENT_SPEED as an, MOVE_SPEED as ao, ManualMatch as ap, type ManualMatchInitOptions as aq, type ManualMatchOptions as ar, type ManualMatchStepRequest as as, type MatchWinner as at, type MissileAction as au, type MissileActions as av, type MissileConfig as aw, type MissileContext as ax, type MissileExpiredEvent as ay, type MissileFunction$1 as az, ARENA_MIN as b, clearParamValues as b$, Spellseeker as b0, Spellshot as b1, Spellspinner as b2, Spelltracer as b3, Spellweaver as b4, type SpiralParams as b5, type StepResult as b6, Stormcaller as b7, Stormchaser as b8, Stormforger as b9, type WizardFunction as bA, type WizardGroup as bB, type WizardLavaDeathEvent as bC, type WizardState as bD, type WorkerFactory as bE, type WorkerLike as bF, analyzeThreats as bG, angleDiff as bH, angleInRange as bI, angleTo as bJ, applyDamage as bK, applyRulesetOverrides as bL, blink as bM, browserSandboxFight as bN, browserSandboxSimulate as bO, calculateBlinkCooldown as bP, calculateMissileCastTime as bQ, calculateMissileRadius as bR, calculateMissileSimilarity as bS, calculateShieldBlock as bT, calculateWarmupMultiplier as bU, canReplayMatch as bV, cancel as bW, cancelCast as bX, clampPositionToArena as bY, clampToArena as bZ, clearHooks as b_, type StraightParams as ba, TICKS_PER_SECOND as bb, TICK_DURATION_MS as bc, TargetDummy as bd, type TestBotBuilder as be, type TestFightResult as bf, type TestSimulateResult as bg, type TraceBotSummary as bh, type TraceEvent as bi, type TraceEventType as bj, type TraceSummary as bk, Turtle as bl, type Velocity as bm, Voidblade as bn, WARMUP_DURATION_TOLERANCE as bo, WARMUP_MAX_BONUS as bp, WARMUP_MAX_PENALTY as bq, WARMUP_SPEED_TOLERANCE as br, WARMUP_TURN_TOLERANCE as bs, WIZARD_HEALTH as bt, WIZARD_RADIUS as bu, Warmage as bv, type WizardActions as bw, type WizardContext as bx, type WizardDeathEvent as by, type WizardEntry as bz, ARENA_SIZE as c, scoreFightAsWizard2 as c$, completeCast as c0, createBudgetState as c1, createEntitySeed as c2, createFightBudget as c3, createInitialState as c4, createRandom as c5, createWorkerScript as c6, currentRuleset as c7, diagnoseTrace as c8, directionAway as c9, hashCombine as cA, homingMissile as cB, idle as cC, inRange as cD, interceptAngle as cE, isBannedBotName as cF, isInLava as cG, magnitude as cH, matchesAnyBanRule as cI, mayAct as cJ, missile as cK, move as cL, moveInDirection as cM, moveProjectile as cN, moveWizard as cO, narrowRange as cP, nextRandom as cQ, normalize as cR, normalizeAngle as cS, predictPosition as cT, recordSpend as cU, resetAllHooks as cV, resetRuleset as cW, resolveWizardCollision as cX, runWithHooks as cY, runWizardWithContext as cZ, scoreFight as c_, directionTo as ca, distanceTo as cb, effectiveTurnRateCost as cc, extractAction as cd, extractMissileAction as ce, extractStats as cf, extractTraceEvents as cg, fight as ch, findInRange as ci, findNearest as cj, fitMissileForEscapingTarget as ck, fitMissileToBudget as cl, flyStraight as cm, formatDiagnosis as cn, formatStats as co, formatTraceEvents as cp, formatTraceSummary as cq, generateCandidates as cr, generateCombos as cs, getAdaptiveMissileConfig as ct, getEffectiveRange as cu, getLeadPosition as cv, getMissileCastTime as cw, getMissileContext as cx, getPlayerState as cy, getWizardContext as cz, ARENA_WATER_BUFFER as d, seekerMissile as d0, setParamValues as d1, shield as d2, simulate as d3, simulateMinDuration as d4, sortByDistance as d5, spiralMissile as d6, startCast as d7, startDiscovery as d8, stopDiscovery as d9, useProjectiles as dA, useRandom as dB, useRef as dC, useShieldStrength as dD, useState as dE, useStatus as dF, useThreats as dG, useTick as dH, useTicksUntilReady as dI, useVelocity as dJ, validateHookCall as dK, validateMissileConfig as dL, withMissileContext as dM, withWizardContext as dN, wrapWithParams as dO, straightMissile as da, summarizeTrace as db, sweptCircleCollision as dc, testBot as dd, tick as de, turnToAngle as df, turnToward as dg, updateShield as dh, useArenaSize as di, useBlinkCooldown as dj, useCastProgress as dk, useCastingSpell as dl, useClosestThreat as dm, useDamageDealt as dn, useDamageTaken as dp, useEffect as dq, useEnemy as dr, useHealth as ds, useLastHitTick as dt, useLastMissileConfig as du, useMemo as dv, useMyProjectiles as dw, useMyThreatsToEnemy as dx, useParam as dy, usePosition as dz, type ActionBuilder as e, type AnalyzedThreat as f, Archlich as g, Archmage as h, BLINK_COOLDOWN as i, BLINK_MAX_COOLDOWN as j, BLINK_MAX_RANGE as k, BLINK_MIN_COOLDOWN as l, BLINK_RANGE as m, BOT_GROUPS as n, Battlemage as o, type BlinkEvent as p, Bonemancer as q, type BotBudgetState as r, type BotError as s, BrowserManualMatchSandbox as t, BrowserMatchSandbox as u, type BrowserSandboxOptions as v, type BudgetLimits as w, COLLISION_RADIUS as x, type CastCancelEvent as y, type CastStartEvent as z };
3218
+ export { Infernalist as $, ALL_BOTS as A, BLINK_CAST_TIME as B, CASTING_MOVEMENT_MULT as C, Critter as D, DEFAULT_BUDGET as E, type FightResult as F, DEFAULT_FIGHT_BACKSTOP_MS as G, DEFAULT_SEED as H, Doombringer as I, ENGINE_VERSION as J, EQUIVALENT_ENGINE_VERSIONS as K, type EnemyState as L, FIGHT_SPAWN_DISTANCES as M, type FightBudget as N, type FightStats as O, type FightWinner as P, type FinalAction as Q, Flamecaller as R, type SimulateResult as S, GCD_DURATION as T, type GameConfig as U, type GameState as V, Golem as W, Hero as X, Hogger as Y, type HomingParams as Z, type HookState as _, ARENA_MAX as a, Spellbinder as a$, type InternalWizardState as a0, KNOCKBACK_DAMAGE_THRESHOLD as a1, KNOCKBACK_DECAY as a2, KNOCKBACK_DELAY as a3, KNOCKBACK_SPEED_PER_DAMAGE as a4, LAVA_BORDER_WIDTH as a5, Lich as a6, MATCH_DURATION as a7, MAX_HEALTH as a8, MISSILE_BASE_CAST as a9, type MissileHitEvent as aA, type MissileLaunchEvent as aB, type MissileOobEvent as aC, type MissileTemplate as aD, Nightblade as aE, type ParamDeclaration as aF, type Position as aG, type ProjectileState as aH, Pyromancer as aI, RULES as aJ, RULESET_RANGES as aK, type RefObject as aL, Rookie as aM, SHIELD_CAST_TIME as aN, SHIELD_DECAY_PER_SECOND as aO, SHIELD_DECAY_RATE as aP, SHIELD_MAX_BLOCK as aQ, SHIELD_MAX_STRENGTH as aR, SHIELD_MIN_BLOCK as aS, SHIELD_MIN_STRENGTH as aT, SPAWN_DISTANCE as aU, type SeekerParams as aV, Sentinel as aW, Shadowblade as aX, type ShieldBlockEvent as aY, type ShieldStartEvent as aZ, type SimEvent as a_, MISSILE_BASE_RADIUS as aa, MISSILE_DAMAGE_POWER as ab, MISSILE_DAMAGE_RADIUS_SCALE as ac, MISSILE_DAMAGE_SCALE as ad, MISSILE_HOMING_COEFF as ae, MISSILE_MIN_CAST_TIME as af, MISSILE_MIN_DAMAGE as ag, MISSILE_MIN_DURATION as ah, MISSILE_MIN_SPEED as ai, MISSILE_RADIUS_PER_DAMAGE as aj, MISSILE_SPEED_DURATION_BASELINE as ak, MISSILE_SPEED_DURATION_COEFF as al, MISSILE_TURN_DURATION_COEFF as am, MOVEMENT_SPEED as an, MOVE_SPEED as ao, ManualMatch as ap, type ManualMatchInitOptions as aq, type ManualMatchOptions as ar, type ManualMatchStepRequest as as, type MatchWinner as at, type MissileAction as au, type MissileActions as av, type MissileConfig as aw, type MissileContext as ax, type MissileExpiredEvent as ay, type MissileFunction$1 as az, ARENA_MIN as b, clearHooks as b$, Spellseeker as b0, Spellshot as b1, Spellspinner as b2, Spelltracer as b3, Spellweaver as b4, type SpiralParams as b5, type StepResult as b6, Stormcaller as b7, Stormchaser as b8, Stormforger as b9, type WizardFunction as bA, type WizardGroup as bB, type WizardLavaDeathEvent as bC, type WizardState as bD, type WorkerFactory as bE, type WorkerLike as bF, aim as bG, analyzeThreats as bH, angleDiff as bI, angleInRange as bJ, angleTo as bK, applyDamage as bL, applyRulesetOverrides as bM, blink as bN, browserSandboxFight as bO, browserSandboxSimulate as bP, calculateBlinkCooldown as bQ, calculateMissileCastTime as bR, calculateMissileRadius as bS, calculateMissileSimilarity as bT, calculateShieldBlock as bU, calculateWarmupMultiplier as bV, canReplayMatch as bW, cancel as bX, cancelCast as bY, clampPositionToArena as bZ, clampToArena as b_, type StraightParams as ba, TICKS_PER_SECOND as bb, TICK_DURATION_MS as bc, TargetDummy as bd, type TestBotBuilder as be, type TestFightResult as bf, type TestSimulateResult as bg, type TraceBotSummary as bh, type TraceEvent as bi, type TraceEventType as bj, type TraceSummary as bk, Turtle as bl, type Velocity as bm, Voidblade as bn, WARMUP_DURATION_TOLERANCE as bo, WARMUP_MAX_BONUS as bp, WARMUP_MAX_PENALTY as bq, WARMUP_SPEED_TOLERANCE as br, WARMUP_TURN_TOLERANCE as bs, WIZARD_HEALTH as bt, WIZARD_RADIUS as bu, Warmage as bv, type WizardActions as bw, type WizardContext as bx, type WizardDeathEvent as by, type WizardEntry as bz, ARENA_SIZE as c, scoreFight as c$, clearParamValues as c0, completeCast as c1, createBudgetState as c2, createEntitySeed as c3, createFightBudget as c4, createInitialState as c5, createRandom as c6, createWorkerScript as c7, currentRuleset as c8, diagnoseTrace as c9, getWizardContext as cA, hashCombine as cB, homingMissile as cC, idle as cD, inRange as cE, interceptAngle as cF, isBannedBotName as cG, isInLava as cH, magnitude as cI, matchesAnyBanRule as cJ, mayAct as cK, missile as cL, move as cM, moveInDirection as cN, moveProjectile as cO, moveWizard as cP, narrowRange as cQ, nextRandom as cR, normalize as cS, normalizeAngle as cT, predictPosition as cU, recordSpend as cV, resetAllHooks as cW, resetRuleset as cX, resolveWizardCollision as cY, runWithHooks as cZ, runWizardWithContext as c_, directionAway as ca, directionTo as cb, distanceTo as cc, effectiveTurnRateCost as cd, extractAction as ce, extractMissileAction as cf, extractStats as cg, extractTraceEvents as ch, fight as ci, findInRange as cj, findNearest as ck, fitMissileForEscapingTarget as cl, fitMissileToBudget as cm, flyStraight as cn, formatDiagnosis as co, formatStats as cp, formatTraceEvents as cq, formatTraceSummary as cr, generateCandidates as cs, generateCombos as ct, getAdaptiveMissileConfig as cu, getEffectiveRange as cv, getLeadPosition as cw, getMissileCastTime as cx, getMissileContext as cy, getPlayerState as cz, ARENA_WATER_BUFFER as d, scoreFightAsWizard2 as d0, seekerMissile as d1, setParamValues as d2, shield as d3, simulate as d4, simulateMinDuration as d5, sortByDistance as d6, spiralMissile as d7, startCast as d8, startDiscovery as d9, usePosition as dA, useProjectiles as dB, useRandom as dC, useRef as dD, useShieldStrength as dE, useState as dF, useStatus as dG, useThreats as dH, useTick as dI, useTicksUntilReady as dJ, useVelocity as dK, validateHookCall as dL, validateMissileConfig as dM, withMissileContext as dN, withWizardContext as dO, wrapWithParams as dP, stopDiscovery as da, straightMissile as db, summarizeTrace as dc, sweptCircleCollision as dd, testBot as de, tick as df, turnToAngle as dg, turnToward as dh, updateShield as di, useArenaSize as dj, useBlinkCooldown as dk, useCastProgress as dl, useCastingSpell as dm, useClosestThreat as dn, useDamageDealt as dp, useDamageTaken as dq, useEffect as dr, useEnemy as ds, useHealth as dt, useLastHitTick as du, useLastMissileConfig as dv, useMemo as dw, useMyProjectiles as dx, useMyThreatsToEnemy as dy, useParam as dz, type ActionBuilder as e, type AnalyzedThreat as f, Archlich as g, Archmage as h, BLINK_COOLDOWN as i, BLINK_MAX_COOLDOWN as j, BLINK_MAX_RANGE as k, BLINK_MIN_COOLDOWN as l, BLINK_RANGE as m, BOT_GROUPS as n, Battlemage as o, type BlinkEvent as p, Bonemancer as q, type BotBudgetState as r, type BotError as s, BrowserManualMatchSandbox as t, BrowserMatchSandbox as u, type BrowserSandboxOptions as v, type BudgetLimits as w, COLLISION_RADIUS as x, type CastCancelEvent as y, type CastStartEvent as z };
@@ -1 +1 @@
1
- export { A as ALL_BOTS, a as ARENA_MAX, b as ARENA_MIN, c as ARENA_SIZE, d as ARENA_WATER_BUFFER, e as ActionBuilder, f as AnalyzedThreat, g as Archlich, h as Archmage, B as BLINK_CAST_TIME, i as BLINK_COOLDOWN, j as BLINK_MAX_COOLDOWN, k as BLINK_MAX_RANGE, l as BLINK_MIN_COOLDOWN, m as BLINK_RANGE, n as BOT_GROUPS, o as Battlemage, p as BlinkEvent, q as Bonemancer, s as BotError, t as BrowserManualMatchSandbox, u as BrowserMatchSandbox, v as BrowserSandboxOptions, C as CASTING_MOVEMENT_MULT, x as COLLISION_RADIUS, y as CastCancelEvent, z as CastStartEvent, D as Critter, H as DEFAULT_SEED, I as Doombringer, J as ENGINE_VERSION, K as EQUIVALENT_ENGINE_VERSIONS, L as EnemyState, M as FIGHT_SPAWN_DISTANCES, F as FightResult, O as FightStats, P as FightWinner, Q as FinalAction, R as Flamecaller, T as GCD_DURATION, U as GameConfig, V as GameState, W as Golem, X as Hero, Y as Hogger, Z as HomingParams, _ as HookState, $ as Infernalist, a0 as InternalWizardState, a1 as KNOCKBACK_DAMAGE_THRESHOLD, a2 as KNOCKBACK_DECAY, a3 as KNOCKBACK_DELAY, a4 as KNOCKBACK_SPEED_PER_DAMAGE, a5 as LAVA_BORDER_WIDTH, a6 as Lich, a7 as MATCH_DURATION, a8 as MAX_HEALTH, a9 as MISSILE_BASE_CAST, aa as MISSILE_BASE_RADIUS, ab as MISSILE_DAMAGE_POWER, ac as MISSILE_DAMAGE_RADIUS_SCALE, ad as MISSILE_DAMAGE_SCALE, ae as MISSILE_HOMING_COEFF, af as MISSILE_MIN_CAST_TIME, ag as MISSILE_MIN_DAMAGE, ah as MISSILE_MIN_DURATION, ai as MISSILE_MIN_SPEED, aj as MISSILE_RADIUS_PER_DAMAGE, ak as MISSILE_SPEED_DURATION_BASELINE, al as MISSILE_SPEED_DURATION_COEFF, am as MISSILE_TURN_DURATION_COEFF, an as MOVEMENT_SPEED, ao as MOVE_SPEED, ap as ManualMatch, aq as ManualMatchInitOptions, ar as ManualMatchOptions, as as ManualMatchStepRequest, at as MatchWinner, au as MissileAction, av as MissileActions, aw as MissileConfig, ax as MissileContext, ay as MissileExpiredEvent, az as MissileFunction, aA as MissileHitEvent, aB as MissileLaunchEvent, aC as MissileOobEvent, aD as MissileTemplate, aE as Nightblade, aF as ParamDeclaration, aG as Position, aH as ProjectileState, aI as Pyromancer, aJ as RULES, aK as RULESET_RANGES, aL as RefObject, aM as Rookie, aN as SHIELD_CAST_TIME, aO as SHIELD_DECAY_PER_SECOND, aP as SHIELD_DECAY_RATE, aQ as SHIELD_MAX_BLOCK, aR as SHIELD_MAX_STRENGTH, aS as SHIELD_MIN_BLOCK, aT as SHIELD_MIN_STRENGTH, aU as SPAWN_DISTANCE, aV as SeekerParams, aW as Sentinel, aX as Shadowblade, aY as ShieldBlockEvent, aZ as ShieldStartEvent, a_ as SimEvent, S as SimulateResult, a$ as Spellbinder, b0 as Spellseeker, b1 as Spellshot, b2 as Spellspinner, b3 as Spelltracer, b4 as Spellweaver, b5 as SpiralParams, b6 as StepResult, b7 as Stormcaller, b8 as Stormchaser, b9 as Stormforger, ba as StraightParams, bb as TICKS_PER_SECOND, bc as TICK_DURATION_MS, bd as TargetDummy, be as TestBotBuilder, bf as TestFightResult, bg as TestSimulateResult, bh as TraceBotSummary, bi as TraceEvent, bj as TraceEventType, bk as TraceSummary, bl as Turtle, bm as Velocity, bn as Voidblade, bo as WARMUP_DURATION_TOLERANCE, bp as WARMUP_MAX_BONUS, bq as WARMUP_MAX_PENALTY, br as WARMUP_SPEED_TOLERANCE, bs as WARMUP_TURN_TOLERANCE, bt as WIZARD_HEALTH, bu as WIZARD_RADIUS, bv as Warmage, bw as WizardActions, bx as WizardContext, by as WizardDeathEvent, bz as WizardEntry, bA as WizardFunction, bB as WizardGroup, bC as WizardLavaDeathEvent, bD as WizardState, bE as WorkerFactory, bF as WorkerLike, bG as analyzeThreats, bH as angleDiff, bI as angleInRange, bJ as angleTo, bK as applyDamage, bL as applyRulesetOverrides, bM as blink, bN as browserSandboxFight, bO as browserSandboxSimulate, bP as calculateBlinkCooldown, bQ as calculateMissileCastTime, bR as calculateMissileRadius, bS as calculateMissileSimilarity, bT as calculateShieldBlock, bU as calculateWarmupMultiplier, bV as canReplayMatch, bW as cancel, bX as cancelCast, bY as clampPositionToArena, bZ as clampToArena, b_ as clearHooks, b$ as clearParamValues, c0 as completeCast, c2 as createEntitySeed, c4 as createInitialState, c5 as createRandom, c6 as createWorkerScript, c7 as currentRuleset, c8 as diagnoseTrace, c9 as directionAway, ca as directionTo, cb as distanceTo, cc as effectiveTurnRateCost, cd as extractAction, ce as extractMissileAction, cf as extractStats, cg as extractTraceEvents, ch as fight, ci as findInRange, cj as findNearest, ck as fitMissileForEscapingTarget, cl as fitMissileToBudget, cm as flyStraight, cn as formatDiagnosis, co as formatStats, cp as formatTraceEvents, cq as formatTraceSummary, cr as generateCandidates, cs as generateCombos, ct as getAdaptiveMissileConfig, cu as getEffectiveRange, cv as getLeadPosition, cw as getMissileCastTime, cx as getMissileContext, cy as getPlayerState, cz as getWizardContext, cA as hashCombine, cB as homingMissile, cC as idle, cD as inRange, cE as interceptAngle, cF as isBannedBotName, cG as isInLava, cH as magnitude, cI as matchesAnyBanRule, cK as missile, cL as move, cM as moveInDirection, cN as moveProjectile, cO as moveWizard, cP as narrowRange, cQ as nextRandom, cR as normalize, cS as normalizeAngle, cT as predictPosition, cV as resetAllHooks, cW as resetRuleset, cX as resolveWizardCollision, cY as runWithHooks, cZ as runWizardWithContext, c_ as scoreFight, c$ as scoreFightAsWizard2, d0 as seekerMissile, d1 as setParamValues, d2 as shield, d3 as simulate, d4 as simulateMinDuration, d5 as sortByDistance, d6 as spiralMissile, d7 as startCast, d8 as startDiscovery, d9 as stopDiscovery, da as straightMissile, db as summarizeTrace, dc as sweptCircleCollision, dd as testBot, de as tick, df as turnToAngle, dg as turnToward, dh as updateShield, di as useArenaSize, dj as useBlinkCooldown, dk as useCastProgress, dl as useCastingSpell, dm as useClosestThreat, dn as useDamageDealt, dp as useDamageTaken, dq as useEffect, dr as useEnemy, ds as useHealth, dt as useLastHitTick, du as useLastMissileConfig, dv as useMemo, dw as useMyProjectiles, dx as useMyThreatsToEnemy, dy as useParam, dz as usePosition, dA as useProjectiles, dB as useRandom, dC as useRef, dD as useShieldStrength, dE as useState, dF as useStatus, dG as useThreats, dH as useTick, dI as useTicksUntilReady, dJ as useVelocity, dK as validateHookCall, dL as validateMissileConfig, dM as withMissileContext, dN as withWizardContext, dO as wrapWithParams } from './index-browser-ClmR9SkR.js';
1
+ export { A as ALL_BOTS, a as ARENA_MAX, b as ARENA_MIN, c as ARENA_SIZE, d as ARENA_WATER_BUFFER, e as ActionBuilder, f as AnalyzedThreat, g as Archlich, h as Archmage, B as BLINK_CAST_TIME, i as BLINK_COOLDOWN, j as BLINK_MAX_COOLDOWN, k as BLINK_MAX_RANGE, l as BLINK_MIN_COOLDOWN, m as BLINK_RANGE, n as BOT_GROUPS, o as Battlemage, p as BlinkEvent, q as Bonemancer, s as BotError, t as BrowserManualMatchSandbox, u as BrowserMatchSandbox, v as BrowserSandboxOptions, C as CASTING_MOVEMENT_MULT, x as COLLISION_RADIUS, y as CastCancelEvent, z as CastStartEvent, D as Critter, H as DEFAULT_SEED, I as Doombringer, J as ENGINE_VERSION, K as EQUIVALENT_ENGINE_VERSIONS, L as EnemyState, M as FIGHT_SPAWN_DISTANCES, F as FightResult, O as FightStats, P as FightWinner, Q as FinalAction, R as Flamecaller, T as GCD_DURATION, U as GameConfig, V as GameState, W as Golem, X as Hero, Y as Hogger, Z as HomingParams, _ as HookState, $ as Infernalist, a0 as InternalWizardState, a1 as KNOCKBACK_DAMAGE_THRESHOLD, a2 as KNOCKBACK_DECAY, a3 as KNOCKBACK_DELAY, a4 as KNOCKBACK_SPEED_PER_DAMAGE, a5 as LAVA_BORDER_WIDTH, a6 as Lich, a7 as MATCH_DURATION, a8 as MAX_HEALTH, a9 as MISSILE_BASE_CAST, aa as MISSILE_BASE_RADIUS, ab as MISSILE_DAMAGE_POWER, ac as MISSILE_DAMAGE_RADIUS_SCALE, ad as MISSILE_DAMAGE_SCALE, ae as MISSILE_HOMING_COEFF, af as MISSILE_MIN_CAST_TIME, ag as MISSILE_MIN_DAMAGE, ah as MISSILE_MIN_DURATION, ai as MISSILE_MIN_SPEED, aj as MISSILE_RADIUS_PER_DAMAGE, ak as MISSILE_SPEED_DURATION_BASELINE, al as MISSILE_SPEED_DURATION_COEFF, am as MISSILE_TURN_DURATION_COEFF, an as MOVEMENT_SPEED, ao as MOVE_SPEED, ap as ManualMatch, aq as ManualMatchInitOptions, ar as ManualMatchOptions, as as ManualMatchStepRequest, at as MatchWinner, au as MissileAction, av as MissileActions, aw as MissileConfig, ax as MissileContext, ay as MissileExpiredEvent, az as MissileFunction, aA as MissileHitEvent, aB as MissileLaunchEvent, aC as MissileOobEvent, aD as MissileTemplate, aE as Nightblade, aF as ParamDeclaration, aG as Position, aH as ProjectileState, aI as Pyromancer, aJ as RULES, aK as RULESET_RANGES, aL as RefObject, aM as Rookie, aN as SHIELD_CAST_TIME, aO as SHIELD_DECAY_PER_SECOND, aP as SHIELD_DECAY_RATE, aQ as SHIELD_MAX_BLOCK, aR as SHIELD_MAX_STRENGTH, aS as SHIELD_MIN_BLOCK, aT as SHIELD_MIN_STRENGTH, aU as SPAWN_DISTANCE, aV as SeekerParams, aW as Sentinel, aX as Shadowblade, aY as ShieldBlockEvent, aZ as ShieldStartEvent, a_ as SimEvent, S as SimulateResult, a$ as Spellbinder, b0 as Spellseeker, b1 as Spellshot, b2 as Spellspinner, b3 as Spelltracer, b4 as Spellweaver, b5 as SpiralParams, b6 as StepResult, b7 as Stormcaller, b8 as Stormchaser, b9 as Stormforger, ba as StraightParams, bb as TICKS_PER_SECOND, bc as TICK_DURATION_MS, bd as TargetDummy, be as TestBotBuilder, bf as TestFightResult, bg as TestSimulateResult, bh as TraceBotSummary, bi as TraceEvent, bj as TraceEventType, bk as TraceSummary, bl as Turtle, bm as Velocity, bn as Voidblade, bo as WARMUP_DURATION_TOLERANCE, bp as WARMUP_MAX_BONUS, bq as WARMUP_MAX_PENALTY, br as WARMUP_SPEED_TOLERANCE, bs as WARMUP_TURN_TOLERANCE, bt as WIZARD_HEALTH, bu as WIZARD_RADIUS, bv as Warmage, bw as WizardActions, bx as WizardContext, by as WizardDeathEvent, bz as WizardEntry, bA as WizardFunction, bB as WizardGroup, bC as WizardLavaDeathEvent, bD as WizardState, bE as WorkerFactory, bF as WorkerLike, bG as aim, bH as analyzeThreats, bI as angleDiff, bJ as angleInRange, bK as angleTo, bL as applyDamage, bM as applyRulesetOverrides, bN as blink, bO as browserSandboxFight, bP as browserSandboxSimulate, bQ as calculateBlinkCooldown, bR as calculateMissileCastTime, bS as calculateMissileRadius, bT as calculateMissileSimilarity, bU as calculateShieldBlock, bV as calculateWarmupMultiplier, bW as canReplayMatch, bX as cancel, bY as cancelCast, bZ as clampPositionToArena, b_ as clampToArena, b$ as clearHooks, c0 as clearParamValues, c1 as completeCast, c3 as createEntitySeed, c5 as createInitialState, c6 as createRandom, c7 as createWorkerScript, c8 as currentRuleset, c9 as diagnoseTrace, ca as directionAway, cb as directionTo, cc as distanceTo, cd as effectiveTurnRateCost, ce as extractAction, cf as extractMissileAction, cg as extractStats, ch as extractTraceEvents, ci as fight, cj as findInRange, ck as findNearest, cl as fitMissileForEscapingTarget, cm as fitMissileToBudget, cn as flyStraight, co as formatDiagnosis, cp as formatStats, cq as formatTraceEvents, cr as formatTraceSummary, cs as generateCandidates, ct as generateCombos, cu as getAdaptiveMissileConfig, cv as getEffectiveRange, cw as getLeadPosition, cx as getMissileCastTime, cy as getMissileContext, cz as getPlayerState, cA as getWizardContext, cB as hashCombine, cC as homingMissile, cD as idle, cE as inRange, cF as interceptAngle, cG as isBannedBotName, cH as isInLava, cI as magnitude, cJ as matchesAnyBanRule, cL as missile, cM as move, cN as moveInDirection, cO as moveProjectile, cP as moveWizard, cQ as narrowRange, cR as nextRandom, cS as normalize, cT as normalizeAngle, cU as predictPosition, cW as resetAllHooks, cX as resetRuleset, cY as resolveWizardCollision, cZ as runWithHooks, c_ as runWizardWithContext, c$ as scoreFight, d0 as scoreFightAsWizard2, d1 as seekerMissile, d2 as setParamValues, d3 as shield, d4 as simulate, d5 as simulateMinDuration, d6 as sortByDistance, d7 as spiralMissile, d8 as startCast, d9 as startDiscovery, da as stopDiscovery, db as straightMissile, dc as summarizeTrace, dd as sweptCircleCollision, de as testBot, df as tick, dg as turnToAngle, dh as turnToward, di as updateShield, dj as useArenaSize, dk as useBlinkCooldown, dl as useCastProgress, dm as useCastingSpell, dn as useClosestThreat, dp as useDamageDealt, dq as useDamageTaken, dr as useEffect, ds as useEnemy, dt as useHealth, du as useLastHitTick, dv as useLastMissileConfig, dw as useMemo, dx as useMyProjectiles, dy as useMyThreatsToEnemy, dz as useParam, dA as usePosition, dB as useProjectiles, dC as useRandom, dD as useRef, dE as useShieldStrength, dF as useState, dG as useStatus, dH as useThreats, dI as useTick, dJ as useTicksUntilReady, dK as useVelocity, dL as validateHookCall, dM as validateMissileConfig, dN as withMissileContext, dO as withWizardContext, dP as wrapWithParams } from './index-browser-BLioGWvO.js';
@@ -93,6 +93,7 @@ import {
93
93
  WIZARD_HEALTH,
94
94
  WIZARD_RADIUS,
95
95
  Warmage,
96
+ aim,
96
97
  analyzeThreats,
97
98
  angleDiff,
98
99
  angleInRange,
@@ -225,7 +226,7 @@ import {
225
226
  withMissileContext,
226
227
  withWizardContext,
227
228
  wrapWithParams
228
- } from "./chunk-MUU3ELH5.js";
229
+ } from "./chunk-OLGKLCCB.js";
229
230
  export {
230
231
  ALL_BOTS,
231
232
  ARENA_MAX,
@@ -321,6 +322,7 @@ export {
321
322
  WIZARD_HEALTH,
322
323
  WIZARD_RADIUS,
323
324
  Warmage,
325
+ aim,
324
326
  analyzeThreats,
325
327
  angleDiff,
326
328
  angleInRange,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as FightResult, S as SimulateResult } from './index-browser-ClmR9SkR.js';
2
- export { A as ALL_BOTS, a as ARENA_MAX, b as ARENA_MIN, c as ARENA_SIZE, d as ARENA_WATER_BUFFER, e as ActionBuilder, f as AnalyzedThreat, g as Archlich, h as Archmage, B as BLINK_CAST_TIME, i as BLINK_COOLDOWN, j as BLINK_MAX_COOLDOWN, k as BLINK_MAX_RANGE, l as BLINK_MIN_COOLDOWN, m as BLINK_RANGE, n as BOT_GROUPS, o as Battlemage, p as BlinkEvent, q as Bonemancer, r as BotBudgetState, s as BotError, t as BrowserManualMatchSandbox, u as BrowserMatchSandbox, v as BrowserSandboxOptions, w as BudgetLimits, C as CASTING_MOVEMENT_MULT, x as COLLISION_RADIUS, y as CastCancelEvent, z as CastStartEvent, D as Critter, E as DEFAULT_BUDGET, G as DEFAULT_FIGHT_BACKSTOP_MS, H as DEFAULT_SEED, I as Doombringer, J as ENGINE_VERSION, K as EQUIVALENT_ENGINE_VERSIONS, L as EnemyState, M as FIGHT_SPAWN_DISTANCES, N as FightBudget, O as FightStats, P as FightWinner, Q as FinalAction, R as Flamecaller, T as GCD_DURATION, U as GameConfig, V as GameState, W as Golem, X as Hero, Y as Hogger, Z as HomingParams, _ as HookState, $ as Infernalist, a0 as InternalWizardState, a1 as KNOCKBACK_DAMAGE_THRESHOLD, a2 as KNOCKBACK_DECAY, a3 as KNOCKBACK_DELAY, a4 as KNOCKBACK_SPEED_PER_DAMAGE, a5 as LAVA_BORDER_WIDTH, a6 as Lich, a7 as MATCH_DURATION, a8 as MAX_HEALTH, a9 as MISSILE_BASE_CAST, aa as MISSILE_BASE_RADIUS, ab as MISSILE_DAMAGE_POWER, ac as MISSILE_DAMAGE_RADIUS_SCALE, ad as MISSILE_DAMAGE_SCALE, ae as MISSILE_HOMING_COEFF, af as MISSILE_MIN_CAST_TIME, ag as MISSILE_MIN_DAMAGE, ah as MISSILE_MIN_DURATION, ai as MISSILE_MIN_SPEED, aj as MISSILE_RADIUS_PER_DAMAGE, ak as MISSILE_SPEED_DURATION_BASELINE, al as MISSILE_SPEED_DURATION_COEFF, am as MISSILE_TURN_DURATION_COEFF, an as MOVEMENT_SPEED, ao as MOVE_SPEED, ap as ManualMatch, aq as ManualMatchInitOptions, ar as ManualMatchOptions, as as ManualMatchStepRequest, at as MatchWinner, au as MissileAction, av as MissileActions, aw as MissileConfig, ax as MissileContext, ay as MissileExpiredEvent, az as MissileFunction, aA as MissileHitEvent, aB as MissileLaunchEvent, aC as MissileOobEvent, aD as MissileTemplate, aE as Nightblade, aF as ParamDeclaration, aG as Position, aH as ProjectileState, aI as Pyromancer, aJ as RULES, aK as RULESET_RANGES, aL as RefObject, aM as Rookie, aN as SHIELD_CAST_TIME, aO as SHIELD_DECAY_PER_SECOND, aP as SHIELD_DECAY_RATE, aQ as SHIELD_MAX_BLOCK, aR as SHIELD_MAX_STRENGTH, aS as SHIELD_MIN_BLOCK, aT as SHIELD_MIN_STRENGTH, aU as SPAWN_DISTANCE, aV as SeekerParams, aW as Sentinel, aX as Shadowblade, aY as ShieldBlockEvent, aZ as ShieldStartEvent, a_ as SimEvent, a$ as Spellbinder, b0 as Spellseeker, b1 as Spellshot, b2 as Spellspinner, b3 as Spelltracer, b4 as Spellweaver, b5 as SpiralParams, b6 as StepResult, b7 as Stormcaller, b8 as Stormchaser, b9 as Stormforger, ba as StraightParams, bb as TICKS_PER_SECOND, bc as TICK_DURATION_MS, bd as TargetDummy, be as TestBotBuilder, bf as TestFightResult, bg as TestSimulateResult, bh as TraceBotSummary, bi as TraceEvent, bj as TraceEventType, bk as TraceSummary, bl as Turtle, bm as Velocity, bn as Voidblade, bo as WARMUP_DURATION_TOLERANCE, bp as WARMUP_MAX_BONUS, bq as WARMUP_MAX_PENALTY, br as WARMUP_SPEED_TOLERANCE, bs as WARMUP_TURN_TOLERANCE, bt as WIZARD_HEALTH, bu as WIZARD_RADIUS, bv as Warmage, bw as WizardActions, bx as WizardContext, by as WizardDeathEvent, bz as WizardEntry, bA as WizardFunction, bB as WizardGroup, bC as WizardLavaDeathEvent, bD as WizardState, bE as WorkerFactory, bF as WorkerLike, bG as analyzeThreats, bH as angleDiff, bI as angleInRange, bJ as angleTo, bK as applyDamage, bL as applyRulesetOverrides, bM as blink, bN as browserSandboxFight, bO as browserSandboxSimulate, bP as calculateBlinkCooldown, bQ as calculateMissileCastTime, bR as calculateMissileRadius, bS as calculateMissileSimilarity, bT as calculateShieldBlock, bU as calculateWarmupMultiplier, bV as canReplayMatch, bW as cancel, bX as cancelCast, bY as clampPositionToArena, bZ as clampToArena, b_ as clearHooks, b$ as clearParamValues, c0 as completeCast, c1 as createBudgetState, c2 as createEntitySeed, c3 as createFightBudget, c4 as createInitialState, c5 as createRandom, c6 as createWorkerScript, c7 as currentRuleset, c8 as diagnoseTrace, c9 as directionAway, ca as directionTo, cb as distanceTo, cc as effectiveTurnRateCost, cd as extractAction, ce as extractMissileAction, cf as extractStats, cg as extractTraceEvents, ch as fight, ci as findInRange, cj as findNearest, ck as fitMissileForEscapingTarget, cl as fitMissileToBudget, cm as flyStraight, cn as formatDiagnosis, co as formatStats, cp as formatTraceEvents, cq as formatTraceSummary, cr as generateCandidates, cs as generateCombos, ct as getAdaptiveMissileConfig, cu as getEffectiveRange, cv as getLeadPosition, cw as getMissileCastTime, cx as getMissileContext, cy as getPlayerState, cz as getWizardContext, cA as hashCombine, cB as homingMissile, cC as idle, cD as inRange, cE as interceptAngle, cF as isBannedBotName, cG as isInLava, cH as magnitude, cI as matchesAnyBanRule, cJ as mayAct, cK as missile, cL as move, cM as moveInDirection, cN as moveProjectile, cO as moveWizard, cP as narrowRange, cQ as nextRandom, cR as normalize, cS as normalizeAngle, cT as predictPosition, cU as recordSpend, cV as resetAllHooks, cW as resetRuleset, cX as resolveWizardCollision, cY as runWithHooks, cZ as runWizardWithContext, c_ as scoreFight, c$ as scoreFightAsWizard2, d0 as seekerMissile, d1 as setParamValues, d2 as shield, d3 as simulate, d4 as simulateMinDuration, d5 as sortByDistance, d6 as spiralMissile, d7 as startCast, d8 as startDiscovery, d9 as stopDiscovery, da as straightMissile, db as summarizeTrace, dc as sweptCircleCollision, dd as testBot, de as tick, df as turnToAngle, dg as turnToward, dh as updateShield, di as useArenaSize, dj as useBlinkCooldown, dk as useCastProgress, dl as useCastingSpell, dm as useClosestThreat, dn as useDamageDealt, dp as useDamageTaken, dq as useEffect, dr as useEnemy, ds as useHealth, dt as useLastHitTick, du as useLastMissileConfig, dv as useMemo, dw as useMyProjectiles, dx as useMyThreatsToEnemy, dy as useParam, dz as usePosition, dA as useProjectiles, dB as useRandom, dC as useRef, dD as useShieldStrength, dE as useState, dF as useStatus, dG as useThreats, dH as useTick, dI as useTicksUntilReady, dJ as useVelocity, dK as validateHookCall, dL as validateMissileConfig, dM as withMissileContext, dN as withWizardContext, dO as wrapWithParams } from './index-browser-ClmR9SkR.js';
1
+ import { F as FightResult, S as SimulateResult } from './index-browser-BLioGWvO.js';
2
+ export { A as ALL_BOTS, a as ARENA_MAX, b as ARENA_MIN, c as ARENA_SIZE, d as ARENA_WATER_BUFFER, e as ActionBuilder, f as AnalyzedThreat, g as Archlich, h as Archmage, B as BLINK_CAST_TIME, i as BLINK_COOLDOWN, j as BLINK_MAX_COOLDOWN, k as BLINK_MAX_RANGE, l as BLINK_MIN_COOLDOWN, m as BLINK_RANGE, n as BOT_GROUPS, o as Battlemage, p as BlinkEvent, q as Bonemancer, r as BotBudgetState, s as BotError, t as BrowserManualMatchSandbox, u as BrowserMatchSandbox, v as BrowserSandboxOptions, w as BudgetLimits, C as CASTING_MOVEMENT_MULT, x as COLLISION_RADIUS, y as CastCancelEvent, z as CastStartEvent, D as Critter, E as DEFAULT_BUDGET, G as DEFAULT_FIGHT_BACKSTOP_MS, H as DEFAULT_SEED, I as Doombringer, J as ENGINE_VERSION, K as EQUIVALENT_ENGINE_VERSIONS, L as EnemyState, M as FIGHT_SPAWN_DISTANCES, N as FightBudget, O as FightStats, P as FightWinner, Q as FinalAction, R as Flamecaller, T as GCD_DURATION, U as GameConfig, V as GameState, W as Golem, X as Hero, Y as Hogger, Z as HomingParams, _ as HookState, $ as Infernalist, a0 as InternalWizardState, a1 as KNOCKBACK_DAMAGE_THRESHOLD, a2 as KNOCKBACK_DECAY, a3 as KNOCKBACK_DELAY, a4 as KNOCKBACK_SPEED_PER_DAMAGE, a5 as LAVA_BORDER_WIDTH, a6 as Lich, a7 as MATCH_DURATION, a8 as MAX_HEALTH, a9 as MISSILE_BASE_CAST, aa as MISSILE_BASE_RADIUS, ab as MISSILE_DAMAGE_POWER, ac as MISSILE_DAMAGE_RADIUS_SCALE, ad as MISSILE_DAMAGE_SCALE, ae as MISSILE_HOMING_COEFF, af as MISSILE_MIN_CAST_TIME, ag as MISSILE_MIN_DAMAGE, ah as MISSILE_MIN_DURATION, ai as MISSILE_MIN_SPEED, aj as MISSILE_RADIUS_PER_DAMAGE, ak as MISSILE_SPEED_DURATION_BASELINE, al as MISSILE_SPEED_DURATION_COEFF, am as MISSILE_TURN_DURATION_COEFF, an as MOVEMENT_SPEED, ao as MOVE_SPEED, ap as ManualMatch, aq as ManualMatchInitOptions, ar as ManualMatchOptions, as as ManualMatchStepRequest, at as MatchWinner, au as MissileAction, av as MissileActions, aw as MissileConfig, ax as MissileContext, ay as MissileExpiredEvent, az as MissileFunction, aA as MissileHitEvent, aB as MissileLaunchEvent, aC as MissileOobEvent, aD as MissileTemplate, aE as Nightblade, aF as ParamDeclaration, aG as Position, aH as ProjectileState, aI as Pyromancer, aJ as RULES, aK as RULESET_RANGES, aL as RefObject, aM as Rookie, aN as SHIELD_CAST_TIME, aO as SHIELD_DECAY_PER_SECOND, aP as SHIELD_DECAY_RATE, aQ as SHIELD_MAX_BLOCK, aR as SHIELD_MAX_STRENGTH, aS as SHIELD_MIN_BLOCK, aT as SHIELD_MIN_STRENGTH, aU as SPAWN_DISTANCE, aV as SeekerParams, aW as Sentinel, aX as Shadowblade, aY as ShieldBlockEvent, aZ as ShieldStartEvent, a_ as SimEvent, a$ as Spellbinder, b0 as Spellseeker, b1 as Spellshot, b2 as Spellspinner, b3 as Spelltracer, b4 as Spellweaver, b5 as SpiralParams, b6 as StepResult, b7 as Stormcaller, b8 as Stormchaser, b9 as Stormforger, ba as StraightParams, bb as TICKS_PER_SECOND, bc as TICK_DURATION_MS, bd as TargetDummy, be as TestBotBuilder, bf as TestFightResult, bg as TestSimulateResult, bh as TraceBotSummary, bi as TraceEvent, bj as TraceEventType, bk as TraceSummary, bl as Turtle, bm as Velocity, bn as Voidblade, bo as WARMUP_DURATION_TOLERANCE, bp as WARMUP_MAX_BONUS, bq as WARMUP_MAX_PENALTY, br as WARMUP_SPEED_TOLERANCE, bs as WARMUP_TURN_TOLERANCE, bt as WIZARD_HEALTH, bu as WIZARD_RADIUS, bv as Warmage, bw as WizardActions, bx as WizardContext, by as WizardDeathEvent, bz as WizardEntry, bA as WizardFunction, bB as WizardGroup, bC as WizardLavaDeathEvent, bD as WizardState, bE as WorkerFactory, bF as WorkerLike, bG as aim, bH as analyzeThreats, bI as angleDiff, bJ as angleInRange, bK as angleTo, bL as applyDamage, bM as applyRulesetOverrides, bN as blink, bO as browserSandboxFight, bP as browserSandboxSimulate, bQ as calculateBlinkCooldown, bR as calculateMissileCastTime, bS as calculateMissileRadius, bT as calculateMissileSimilarity, bU as calculateShieldBlock, bV as calculateWarmupMultiplier, bW as canReplayMatch, bX as cancel, bY as cancelCast, bZ as clampPositionToArena, b_ as clampToArena, b$ as clearHooks, c0 as clearParamValues, c1 as completeCast, c2 as createBudgetState, c3 as createEntitySeed, c4 as createFightBudget, c5 as createInitialState, c6 as createRandom, c7 as createWorkerScript, c8 as currentRuleset, c9 as diagnoseTrace, ca as directionAway, cb as directionTo, cc as distanceTo, cd as effectiveTurnRateCost, ce as extractAction, cf as extractMissileAction, cg as extractStats, ch as extractTraceEvents, ci as fight, cj as findInRange, ck as findNearest, cl as fitMissileForEscapingTarget, cm as fitMissileToBudget, cn as flyStraight, co as formatDiagnosis, cp as formatStats, cq as formatTraceEvents, cr as formatTraceSummary, cs as generateCandidates, ct as generateCombos, cu as getAdaptiveMissileConfig, cv as getEffectiveRange, cw as getLeadPosition, cx as getMissileCastTime, cy as getMissileContext, cz as getPlayerState, cA as getWizardContext, cB as hashCombine, cC as homingMissile, cD as idle, cE as inRange, cF as interceptAngle, cG as isBannedBotName, cH as isInLava, cI as magnitude, cJ as matchesAnyBanRule, cK as mayAct, cL as missile, cM as move, cN as moveInDirection, cO as moveProjectile, cP as moveWizard, cQ as narrowRange, cR as nextRandom, cS as normalize, cT as normalizeAngle, cU as predictPosition, cV as recordSpend, cW as resetAllHooks, cX as resetRuleset, cY as resolveWizardCollision, cZ as runWithHooks, c_ as runWizardWithContext, c$ as scoreFight, d0 as scoreFightAsWizard2, d1 as seekerMissile, d2 as setParamValues, d3 as shield, d4 as simulate, d5 as simulateMinDuration, d6 as sortByDistance, d7 as spiralMissile, d8 as startCast, d9 as startDiscovery, da as stopDiscovery, db as straightMissile, dc as summarizeTrace, dd as sweptCircleCollision, de as testBot, df as tick, dg as turnToAngle, dh as turnToward, di as updateShield, dj as useArenaSize, dk as useBlinkCooldown, dl as useCastProgress, dm as useCastingSpell, dn as useClosestThreat, dp as useDamageDealt, dq as useDamageTaken, dr as useEffect, ds as useEnemy, dt as useHealth, du as useLastHitTick, dv as useLastMissileConfig, dw as useMemo, dx as useMyProjectiles, dy as useMyThreatsToEnemy, dz as useParam, dA as usePosition, dB as useProjectiles, dC as useRandom, dD as useRef, dE as useShieldStrength, dF as useState, dG as useStatus, dH as useThreats, dI as useTick, dJ as useTicksUntilReady, dK as useVelocity, dL as validateHookCall, dM as validateMissileConfig, dN as withMissileContext, dO as withWizardContext, dP as wrapWithParams } from './index-browser-BLioGWvO.js';
3
3
 
4
4
  /**
5
5
  * A compiled bot ready for sandboxed execution.
package/dist/index.js CHANGED
@@ -95,6 +95,7 @@ import {
95
95
  WIZARD_HEALTH,
96
96
  WIZARD_RADIUS,
97
97
  Warmage,
98
+ aim,
98
99
  analyzeThreats,
99
100
  angleDiff,
100
101
  angleInRange,
@@ -231,7 +232,7 @@ import {
231
232
  withMissileContext,
232
233
  withWizardContext,
233
234
  wrapWithParams
234
- } from "./chunk-MUU3ELH5.js";
235
+ } from "./chunk-OLGKLCCB.js";
235
236
 
236
237
  // src/engine/sandbox.ts
237
238
  import ivm from "isolated-vm";
@@ -793,6 +794,7 @@ export {
793
794
  WIZARD_HEALTH,
794
795
  WIZARD_RADIUS,
795
796
  Warmage,
797
+ aim,
796
798
  analyzeThreats,
797
799
  angleDiff,
798
800
  angleInRange,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/engine/sandbox.ts","../src/engine/sandbox-compile.ts","../src/engine/sandbox-harness.ts","../src/engine/bundle-fight.ts"],"sourcesContent":["/**\r\n * VIBEMANCER — SANDBOX\r\n *\r\n * Provides isolated-vm sandboxing for bot code execution. Both bots + the\r\n * entire simulation engine run inside a single V8 isolate, so there is ZERO\r\n * per-tick boundary crossing overhead. The only data crossing the boundary\r\n * is fight/simulate options going in and results coming out.\r\n *\r\n * Architecture:\r\n * - Host: creates isolate, loads compiled bundle, calls __fight/__simulate\r\n * - Isolate: contains both bots + full simulation engine, runs fight/simulate\r\n *\r\n * Safety:\r\n * - Memory limit (default 512 MB) catches memory bombs\r\n * - Timeout (default 30s) catches infinite loops\r\n * - Prototype freeze prevents cross-bot sabotage\r\n * - platform: 'neutral' strips Node.js APIs (no fs/net/process)\r\n */\r\n\r\nimport ivm from 'isolated-vm';\r\nimport type {FightResult, SimulateResult} from './simulation.js';\r\nimport {BotBundle, compileMatchBundle} from './sandbox-compile.js';\r\nimport {DEFAULT_FIGHT_BACKSTOP_MS} from './bot-compute-budget.js';\r\nimport type {CompileOptions} from './sandbox-compile.js';\r\n\r\n// Re-export BotBundle so existing imports from sandbox.ts keep working\r\nexport {BotBundle} from './sandbox-compile.js';\r\nexport {compileMatchBundle, compileManualMatchBundle} from './sandbox-compile.js';\r\nexport type {CompileOptions} from './sandbox-compile.js';\r\n\r\n/**\r\n * Options for sandbox creation.\r\n */\r\nexport interface SandboxOptions\r\n{\r\n\t/** Memory limit in MB for the isolate (default: 512). */\r\n\tmemoryLimitMB?: number;\r\n\t/** Timeout in ms for fight/simulate calls (default: 60000). */\r\n\ttimeoutMs?: number;\r\n\t/** Options passed to esbuild compilation (aliases, externals). */\r\n\tcompileOptions?: CompileOptions;\r\n}\r\n\r\n/**\r\n * A sandboxed match runner. Both bots + the entire simulation engine run\r\n * inside a single isolated-vm isolate.\r\n *\r\n * Usage:\r\n * ```ts\r\n * const sandbox = await MatchSandbox.create(botA, botB);\r\n * const result = sandbox.fight({ seed: 42 });\r\n * sandbox.dispose();\r\n * ```\r\n */\r\nexport class MatchSandbox\r\n{\r\n\tprivate isolate: ivm.Isolate;\r\n\tprivate context: ivm.Context;\r\n\tprivate fightFn: ivm.Reference;\r\n\tprivate simulateFn: ivm.Reference;\r\n\tprivate timeout: number;\r\n\tprivate disposed = false;\r\n\r\n\tprivate constructor(\r\n\t\tisolate: ivm.Isolate,\r\n\t\tcontext: ivm.Context,\r\n\t\tfightFn: ivm.Reference,\r\n\t\tsimulateFn: ivm.Reference,\r\n\t\ttimeout: number,\r\n\t)\r\n\t{\r\n\t\tthis.isolate = isolate;\r\n\t\tthis.context = context;\r\n\t\tthis.fightFn = fightFn;\r\n\t\tthis.simulateFn = simulateFn;\r\n\t\tthis.timeout = timeout;\r\n\t}\r\n\r\n\t/**\r\n\t * Create a sandbox with both bots loaded. Compiles the match bundle\r\n\t * automatically using esbuild.\r\n\t */\r\n\tstatic async create(\r\n\t\tbot1: BotBundle,\r\n\t\tbot2: BotBundle,\r\n\t\toptions?: SandboxOptions,\r\n\t): Promise<MatchSandbox>\r\n\t{\r\n\t\tconst memoryLimitMB = options?.memoryLimitMB ?? 512;\r\n\t\t// Was a bare 60000, which the per-bot compute budget made wrong: a budgeted fight can\r\n\t\t// legitimately let one runaway bot spend 45s before it stops being called, and 60s\r\n\t\t// here would kill the whole fight first — putting the wall clock back in charge of\r\n\t\t// outcomes. Shared constant so this cannot drift from the other copies again.\r\n\t\tconst timeoutMs = options?.timeoutMs ?? DEFAULT_FIGHT_BACKSTOP_MS;\r\n\r\n\t\t// 1. Compile the match bundle\r\n\t\tconst bundle = await compileMatchBundle(bot1, bot2, options?.compileOptions);\r\n\r\n\t\t// 2. Create isolate with memory limit\r\n\t\tconst isolate = new ivm.Isolate({memoryLimit: memoryLimitMB});\r\n\r\n\t\ttry\r\n\t\t{\r\n\t\t\t// 3. Create context and load the bundle\r\n\t\t\tconst context = await isolate.createContext();\r\n\t\t\tconst script = await isolate.compileScript(bundle, {filename: 'match-bundle.js'});\r\n\t\t\tawait script.run(context, {timeout: timeoutMs});\r\n\r\n\t\t\t// 4. Get references to the exposed functions\r\n\t\t\tconst global = context.global;\r\n\t\t\tconst fightFn = await global.get('__fight', {reference: true});\r\n\t\t\tconst simulateFn = await global.get('__simulate', {reference: true});\r\n\r\n\t\t\treturn new MatchSandbox(isolate, context, fightFn, simulateFn, timeoutMs);\r\n\t\t}\r\n\t\tcatch(error)\r\n\t\t{\r\n\t\t\t// OOM or timeout can auto-dispose the isolate, so guard the cleanup dispose.\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tisolate.dispose();\r\n\t\t\t}\r\n\t\t\tcatch\r\n\t\t\t{\r\n\t\t\t\t// already disposed\r\n\t\t\t}\r\n\t\t\tthrow error;\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Create a sandbox from a pre-compiled bundle string.\r\n\t * Useful for caching compiled bundles across multiple MatchSandbox instances.\r\n\t */\r\n\tstatic async fromBundle(\r\n\t\tbundle: string,\r\n\t\toptions?: SandboxOptions,\r\n\t): Promise<MatchSandbox>\r\n\t{\r\n\t\tconst memoryLimitMB = options?.memoryLimitMB ?? 512;\r\n\t\t// Was a bare 60000, which the per-bot compute budget made wrong: a budgeted fight can\r\n\t\t// legitimately let one runaway bot spend 45s before it stops being called, and 60s\r\n\t\t// here would kill the whole fight first — putting the wall clock back in charge of\r\n\t\t// outcomes. Shared constant so this cannot drift from the other copies again.\r\n\t\tconst timeoutMs = options?.timeoutMs ?? DEFAULT_FIGHT_BACKSTOP_MS;\r\n\r\n\t\tconst isolate = new ivm.Isolate({memoryLimit: memoryLimitMB});\r\n\r\n\t\ttry\r\n\t\t{\r\n\t\t\tconst context = await isolate.createContext();\r\n\t\t\tconst script = await isolate.compileScript(bundle, {filename: 'match-bundle.js'});\r\n\t\t\tawait script.run(context, {timeout: timeoutMs});\r\n\r\n\t\t\tconst global = context.global;\r\n\t\t\tconst fightFn = await global.get('__fight', {reference: true});\r\n\t\t\tconst simulateFn = await global.get('__simulate', {reference: true});\r\n\r\n\t\t\treturn new MatchSandbox(isolate, context, fightFn, simulateFn, timeoutMs);\r\n\t\t}\r\n\t\tcatch(error)\r\n\t\t{\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tisolate.dispose();\r\n\t\t\t}\r\n\t\t\tcatch\r\n\t\t\t{\r\n\t\t\t\t// already disposed\r\n\t\t\t}\r\n\t\t\tthrow error;\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Compile a match bundle without creating an isolate.\r\n\t * Returns the compiled JS string for caching/reuse.\r\n\t */\r\n\tstatic async compile(bot1: BotBundle, bot2: BotBundle, compileOptions?: CompileOptions): Promise<string>\r\n\t{\r\n\t\treturn compileMatchBundle(bot1, bot2, compileOptions);\r\n\t}\r\n\r\n\t/**\r\n\t * Run a full fight (10 matches: 5 spawn distances x 2 sides).\r\n\t * Synchronous after isolate creation — runs entirely inside the isolate.\r\n\t */\r\n\tfight(options?: {seed?: number; maxTicks?: number}): FightResult\r\n\t{\r\n\t\tthis.ensureNotDisposed();\r\n\r\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- isolated-vm returns unknown\r\n\t\treturn this.fightFn.applySync(undefined, [options ?? {}], {\r\n\t\t\targuments: {copy: true},\r\n\t\t\tresult: {copy: true},\r\n\t\t\ttimeout: this.timeout,\r\n\t\t}) as FightResult;\r\n\t}\r\n\r\n\t/**\r\n\t * Run a single simulation.\r\n\t * Synchronous after isolate creation.\r\n\t *\r\n\t * @param options.params1 - useParam overrides for bot 1 (wizard-1)\r\n\t * @param options.params2 - useParam overrides for bot 2 (wizard-2)\r\n\t */\r\n\tsimulate(options?: {\r\n\t\tseed?: number;\r\n\t\tmaxTicks?: number;\r\n\t\tspawnDistance?: number;\r\n\t\tskipHistory?: boolean;\r\n\t\tparams1?: Record<string, number>;\r\n\t\tparams2?: Record<string, number>;\r\n\t}): SimulateResult\r\n\t{\r\n\t\tthis.ensureNotDisposed();\r\n\r\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- isolated-vm returns unknown\r\n\t\treturn this.simulateFn.applySync(undefined, [options ?? {}], {\r\n\t\t\targuments: {copy: true},\r\n\t\t\tresult: {copy: true},\r\n\t\t\ttimeout: this.timeout,\r\n\t\t}) as SimulateResult;\r\n\t}\r\n\r\n\t/**\r\n\t * Dispose the isolate and free all memory.\r\n\t * The sandbox cannot be used after disposal.\r\n\t */\r\n\tdispose(): void\r\n\t{\r\n\t\tif (!this.disposed)\r\n\t\t{\r\n\t\t\tthis.disposed = true;\r\n\t\t\t// OOM can auto-dispose the isolate, so guard all cleanup\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tthis.fightFn.release();\r\n\t\t\t}\r\n\t\t\tcatch\r\n\t\t\t{\r\n\t\t\t\t// isolate already disposed\r\n\t\t\t}\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tthis.simulateFn.release();\r\n\t\t\t}\r\n\t\t\tcatch\r\n\t\t\t{\r\n\t\t\t\t// isolate already disposed\r\n\t\t\t}\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tthis.context.release();\r\n\t\t\t}\r\n\t\t\tcatch\r\n\t\t\t{\r\n\t\t\t\t// isolate already disposed\r\n\t\t\t}\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tthis.isolate.dispose();\r\n\t\t\t}\r\n\t\t\tcatch\r\n\t\t\t{\r\n\t\t\t\t// isolate already disposed\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Whether this sandbox has been disposed.\r\n\t */\r\n\tget isDisposed(): boolean\r\n\t{\r\n\t\treturn this.disposed;\r\n\t}\r\n\r\n\tprivate ensureNotDisposed(): void\r\n\t{\r\n\t\tif (this.disposed)\r\n\t\t{\r\n\t\t\tthrow new Error('MatchSandbox has been disposed');\r\n\t\t}\r\n\t}\r\n}\r\n\r\n/**\r\n * One-shot sandboxed fight. Creates isolate, runs fight, disposes.\r\n * Convenience wrapper for single-use scenarios.\r\n */\r\nexport async function sandboxFight(\r\n\tbot1: BotBundle,\r\n\tbot2: BotBundle,\r\n\toptions?: {seed?: number; maxTicks?: number} & SandboxOptions,\r\n): Promise<FightResult>\r\n{\r\n\tconst sandbox = await MatchSandbox.create(bot1, bot2, options);\r\n\ttry\r\n\t{\r\n\t\treturn sandbox.fight({seed: options?.seed, maxTicks: options?.maxTicks});\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tsandbox.dispose();\r\n\t}\r\n}\r\n\r\n/**\r\n * One-shot sandboxed simulate. Creates isolate, runs simulate, disposes.\r\n */\r\nexport async function sandboxSimulate(\r\n\tbot1: BotBundle,\r\n\tbot2: BotBundle,\r\n\toptions?: {\r\n\t\tseed?: number;\r\n\t\tmaxTicks?: number;\r\n\t\tspawnDistance?: number;\r\n\t\tskipHistory?: boolean;\r\n\t\tparams1?: Record<string, number>;\r\n\t\tparams2?: Record<string, number>;\r\n\t} & SandboxOptions,\r\n): Promise<SimulateResult>\r\n{\r\n\tconst sandbox = await MatchSandbox.create(bot1, bot2, options);\r\n\ttry\r\n\t{\r\n\t\treturn sandbox.simulate({\r\n\t\t\tseed: options?.seed,\r\n\t\t\tmaxTicks: options?.maxTicks,\r\n\t\t\tspawnDistance: options?.spawnDistance,\r\n\t\t\tskipHistory: options?.skipHistory,\r\n\t\t\tparams1: options?.params1,\r\n\t\t\tparams2: options?.params2,\r\n\t\t});\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tsandbox.dispose();\r\n\t}\r\n}\r\n","/**\r\n * VIBEMANCER — SANDBOX COMPILATION\r\n *\r\n * Compiles match bundles using esbuild. Extracted from sandbox.ts so that\r\n * compilation can be used independently of isolated-vm (e.g., in the CLI\r\n * dev server or for browser Web Worker sandboxes).\r\n *\r\n * This file has NO isolated-vm dependency — only esbuild + Node.js builtins.\r\n */\r\n\r\nimport {build} from 'esbuild';\r\nimport path from 'node:path';\r\nimport fs from 'node:fs';\r\nimport {fileURLToPath} from 'node:url';\r\nimport {PROTOTYPE_FREEZE_BANNER, generateManualMatchEntryPoint, generateMatchEntryPoint} from './sandbox-harness.js';\r\n\r\nconst currentFilename = fileURLToPath(import.meta.url);\r\nconst currentDirname = path.dirname(currentFilename);\r\n\r\n/**\r\n * Find the src/engine/ directory. Works from src/, dist/engine/, or dist/ (tsup bundle).\r\n * esbuild needs TypeScript source files, so we look for the src/ tree.\r\n */\r\nexport function getEngineDir(): string\r\n{\r\n\t// When running from src/engine/ (dev/test), currentDirname is already src/engine/\r\n\tconst directPath = path.resolve(currentDirname);\r\n\tif (fs.existsSync(path.join(directPath, 'simulation.ts')))\r\n\t{\r\n\t\treturn directPath;\r\n\t}\r\n\r\n\t// When running from dist/engine/ (individual files), package root is 2 levels up\r\n\tconst packageRoot2 = path.resolve(currentDirname, '..', '..');\r\n\tconst srcEngine2 = path.join(packageRoot2, 'src', 'engine');\r\n\tif (fs.existsSync(path.join(srcEngine2, 'simulation.ts')))\r\n\t{\r\n\t\treturn srcEngine2;\r\n\t}\r\n\r\n\t// When running from dist/ (tsup bundle), package root is 1 level up\r\n\tconst packageRoot1 = path.resolve(currentDirname, '..');\r\n\tconst srcEngine1 = path.join(packageRoot1, 'src', 'engine');\r\n\tif (fs.existsSync(path.join(srcEngine1, 'simulation.ts')))\r\n\t{\r\n\t\treturn srcEngine1;\r\n\t}\r\n\r\n\tthrow new Error('Could not find engine source directory (src/engine/simulation.ts)');\r\n}\r\n\r\n/**\r\n * A compiled bot ready for sandboxed execution.\r\n * Stores the source path and export name — actual compilation\r\n * happens when creating a MatchSandbox or calling compileMatchBundle.\r\n */\r\nexport class BotBundle\r\n{\r\n\treadonly sourcePath: string;\r\n\treadonly exportName: string;\r\n\r\n\tconstructor(sourcePath: string, exportName: string)\r\n\t{\r\n\t\tif (!sourcePath || typeof sourcePath !== 'string')\r\n\t\t{\r\n\t\t\tthrow new Error('sourcePath must be a non-empty string');\r\n\t\t}\r\n\t\tif (!exportName || typeof exportName !== 'string')\r\n\t\t{\r\n\t\t\tthrow new Error('exportName must be a non-empty string');\r\n\t\t}\r\n\t\tthis.sourcePath = path.resolve(sourcePath);\r\n\t\tthis.exportName = exportName;\r\n\t}\r\n}\r\n\r\n/**\r\n * Options for esbuild compilation. Allows the caller to add esbuild\r\n * aliases (e.g., resolving @vibemancer/core to the TypeScript source).\r\n */\r\nexport interface CompileOptions\r\n{\r\n\t/** Additional esbuild alias entries (e.g., {'@vibemancer/core': '/path/to/src/index.ts'}). */\r\n\talias?: Record<string, string>;\r\n\t/** Additional modules to treat as external (not bundled). */\r\n\texternal?: string[];\r\n}\r\n\r\n/**\r\n * Compile a match bundle using esbuild. Bundles both bots + simulation engine\r\n * into a single self-contained IIFE with prototype freezing banner.\r\n *\r\n * No isolated-vm dependency — returns a plain JS string.\r\n */\r\nexport async function compileMatchBundle(\r\n\tbot1: BotBundle,\r\n\tbot2: BotBundle,\r\n\toptions?: CompileOptions,\r\n): Promise<string>\r\n{\r\n\tconst engineDir = getEngineDir();\r\n\tconst entryPoint = generateMatchEntryPoint(\r\n\t\tbot1.sourcePath,\r\n\t\tbot1.exportName,\r\n\t\tbot2.sourcePath,\r\n\t\tbot2.exportName,\r\n\t\tengineDir,\r\n\t);\r\n\r\n\tconst result = await build({\r\n\t\tstdin: {\r\n\t\t\tcontents: entryPoint,\r\n\t\t\tresolveDir: engineDir,\r\n\t\t\tloader: 'ts',\r\n\t\t},\r\n\t\tbundle: true,\r\n\t\twrite: false,\r\n\t\tformat: 'iife',\r\n\t\tplatform: 'neutral',\r\n\t\ttarget: 'es2022',\r\n\t\tbanner: {\r\n\t\t\tjs: PROTOTYPE_FREEZE_BANNER,\r\n\t\t},\r\n\t\t// Suppress warnings about top-level this in ESM\r\n\t\tlogLevel: 'error',\r\n\t\t// Match bundles run in sandboxed environments (Web Workers / isolated-vm)\r\n\t\t// and should never include Node.js native modules\r\n\t\texternal: [\r\n\t\t\t'isolated-vm', 'esbuild', 'node:path', 'node:fs', 'node:url',\r\n\t\t\t'node:worker_threads', 'node:crypto', 'node:os', 'node:child_process',\r\n\t\t\t...(options?.external ?? []),\r\n\t\t],\r\n\t\t...(options?.alias ? {alias: options.alias} : {}),\r\n\t});\r\n\r\n\tif (!result.outputFiles?.[0])\r\n\t{\r\n\t\tthrow new Error('esbuild produced no output');\r\n\t}\r\n\r\n\treturn result.outputFiles[0].text;\r\n}\r\n\r\n/**\r\n * Compile a manual-play sandbox bundle. Bundles ONE opponent bot + ManualMatch\r\n * + simulation engine into a self-contained IIFE. The \"player\" wizard is a\r\n * worker-local stub that reads from `__latestHumanActions` (set per-step by\r\n * the host).\r\n *\r\n * Returns a plain JS string that, when loaded into a Web Worker, exposes the\r\n * `__manualMatchInit`, `__manualMatchStep`, `__manualMatchGuide`,\r\n * `__manualMatchRelease`, `__manualMatchSetInvincible`,\r\n * `__manualMatchGetState`, `__manualMatchGetResult`, and `__manualMatchDispose`\r\n * globals on the worker's globalThis.\r\n */\r\nexport async function compileManualMatchBundle(\r\n\topponent: BotBundle,\r\n\toptions?: CompileOptions,\r\n): Promise<string>\r\n{\r\n\tconst engineDir = getEngineDir();\r\n\tconst entryPoint = generateManualMatchEntryPoint(\r\n\t\topponent.sourcePath,\r\n\t\topponent.exportName,\r\n\t\tengineDir,\r\n\t);\r\n\r\n\tconst result = await build({\r\n\t\tstdin: {\r\n\t\t\tcontents: entryPoint,\r\n\t\t\tresolveDir: engineDir,\r\n\t\t\tloader: 'ts',\r\n\t\t},\r\n\t\tbundle: true,\r\n\t\twrite: false,\r\n\t\tformat: 'iife',\r\n\t\tplatform: 'neutral',\r\n\t\ttarget: 'es2022',\r\n\t\tbanner: {\r\n\t\t\tjs: PROTOTYPE_FREEZE_BANNER,\r\n\t\t},\r\n\t\tlogLevel: 'error',\r\n\t\texternal: [\r\n\t\t\t'isolated-vm', 'esbuild', 'node:path', 'node:fs', 'node:url',\r\n\t\t\t'node:worker_threads', 'node:crypto', 'node:os', 'node:child_process',\r\n\t\t\t...(options?.external ?? []),\r\n\t\t],\r\n\t\t...(options?.alias ? {alias: options.alias} : {}),\r\n\t});\r\n\r\n\tif (!result.outputFiles?.[0])\r\n\t{\r\n\t\tthrow new Error('esbuild produced no output');\r\n\t}\r\n\r\n\treturn result.outputFiles[0].text;\r\n}\r\n","/**\n * VIBEMANCER — SANDBOX HARNESS\n *\n * Generates the entry point code that runs inside an isolated-vm isolate.\n * The harness bundles both bots + the simulation engine into a single IIFE\n * via esbuild, then exposes __fight and __simulate on globalThis.\n *\n * The prototype freeze banner runs before any module code, preventing\n * prototype pollution attacks between bots sharing the same isolate.\n */\n\n/**\n * JavaScript code injected as esbuild banner — runs before the IIFE bundle.\n *\n * 1. Freezes all built-in prototypes to prevent cross-bot sabotage via prototype\n * pollution. This does NOT prevent calling existing methods (e.g. Array.push\n * still works), it only prevents reassigning them.\n *\n * 2. Blocks all IO/network capabilities. Web Workers have fetch, XMLHttpRequest,\n * WebSocket, importScripts, etc. Bot code must not be able to make network\n * calls or load external scripts. Uses Object.defineProperty to make the block\n * irrecoverable (non-writable, non-configurable). In isolated-vm, these globals\n * don't exist — the try-catch makes the deletes a harmless no-op.\n */\nexport const PROTOTYPE_FREEZE_BANNER = `\nObject.freeze(Object.prototype);\nObject.freeze(Array.prototype);\nObject.freeze(Function.prototype);\nObject.freeze(String.prototype);\nObject.freeze(Number.prototype);\nObject.freeze(Boolean.prototype);\nObject.freeze(RegExp.prototype);\nObject.freeze(Date.prototype);\nObject.freeze(Error.prototype);\nObject.freeze(Map.prototype);\nObject.freeze(Set.prototype);\nObject.freeze(Math);\nObject.freeze(JSON);\n(function() {\n\tvar g = typeof globalThis !== 'undefined' ? globalThis : typeof self !== 'undefined' ? self : {};\n\tvar blocked = [\n\t\t'fetch', 'XMLHttpRequest', 'WebSocket', 'EventSource',\n\t\t'importScripts', 'Worker', 'SharedWorker',\n\t\t'Request', 'Response', 'Headers',\n\t\t'navigator', 'BroadcastChannel',\n\t\t'indexedDB', 'caches'\n\t];\n\tfor (var i = 0; i < blocked.length; i++) {\n\t\ttry { Object.defineProperty(g, blocked[i], {value: undefined, writable: false, configurable: false}); }\n\t\tcatch(e) {}\n\t}\n})();\n`;\n\n/**\n * Generate the TypeScript entry point for a match sandbox.\n *\n * This entry point imports both bots and the simulation engine, then\n * exposes __fight and __simulate on globalThis. esbuild bundles this\n * + all transitive imports into a single self-contained IIFE.\n *\n * @param bot1SourcePath - Absolute path to bot 1's TypeScript source file\n * @param bot1ExportName - Named export of bot 1's WizardFunction\n * @param bot2SourcePath - Absolute path to bot 2's TypeScript source file\n * @param bot2ExportName - Named export of bot 2's WizardFunction\n * @param engineDir - Absolute path to the engine directory (src/engine/)\n */\nexport function generateMatchEntryPoint(\n\tbot1SourcePath: string,\n\tbot1ExportName: string,\n\tbot2SourcePath: string,\n\tbot2ExportName: string,\n\tengineDir: string,\n): string\n{\n\t// Use forward slashes for esbuild compatibility (works on all platforms)\n\tconst enginePath = engineDir.replace(/\\\\/g, '/');\n\tconst bot1Path = bot1SourcePath.replace(/\\\\/g, '/');\n\tconst bot2Path = bot2SourcePath.replace(/\\\\/g, '/');\n\n\t// All imports use the engineDir's parent (= packages/core/src/) as root.\n\t// When user bots alias @vibemancer/core → src/index-browser.ts, esbuild\n\t// deduplicates these with the bot's imports since they resolve to the same files.\n\t// This ensures the hooks runtime global state is shared between harness and bot.\n\tconst srcPath = enginePath.replace(/\\/engine\\/?$/, '');\n\n\treturn `\nimport {${bot1ExportName} as __Bot1} from '${bot1Path}';\nimport {${bot2ExportName} as __Bot2} from '${bot2Path}';\nimport {fight, simulate} from '${srcPath}/engine/simulation.ts';\nimport {wrapWithParams} from '${srcPath}/engine/params-runtime.ts';\nimport {DEFAULT_BUDGET} from '${srcPath}/engine/bot-compute-budget.ts';\n\nglobalThis.__fight = function __fight(options) {\n\t// Budgeted like every other fight path. This one matters MORE than it looks: it is what\n\t// sandboxFight uses, so it is the MCP fight tools and the CLI — the place a user's\n\t// runaway bot most directly burns server time. Leaving it unbudgeted while the backstop\n\t// moved from 60s to 110s would have made this path strictly worse than before.\n\tconst result = fight(__Bot1, __Bot2, {budgetLimits: DEFAULT_BUDGET, ...options});\n\treturn result;\n};\n\nglobalThis.__simulate = function __simulate(options) {\n\tconst {params1, params2, ...simOptions} = options;\n\tconst bot1 = params1 ? wrapWithParams(__Bot1, params1) : __Bot1;\n\tconst bot2 = params2 ? wrapWithParams(__Bot2, params2) : __Bot2;\n\t// Budgeted like __fight. This path is trace and the optimizer — a single match rather\n\t// than ten — and leaving it out would have made it strictly worse than before, since the\n\t// same change raised its backstop from 60s to 110s.\n\tconst result = simulate(bot1, bot2, {budgetLimits: DEFAULT_BUDGET, ...simOptions});\n\treturn result;\n};\n`;\n}\n\n/**\n * Generate the entry point for a Manual Play sandbox bundle.\n *\n * Unlike the fight/simulate entry point (which exposes one-shot batch APIs),\n * the manual-match entry point holds a single long-lived ManualMatch instance\n * inside the worker and exposes per-tick step/rewind/guide APIs.\n *\n * The \"player\" wizard is a worker-local stub that returns whatever\n * `__latestHumanActions` is set to — this avoids the can't-postMessage-functions\n * problem (the function lives entirely worker-side, only data crosses the\n * boundary). Guided missiles use a similar pattern via\n * `__latestHumanMissileTargets[projectileId]`.\n *\n * @param opponentSourcePath - Absolute path to opponent bot's TypeScript source\n * @param opponentExportName - Named export of the opponent's WizardFunction\n * @param engineDir - Absolute path to src/engine/\n */\nexport function generateManualMatchEntryPoint(\n\topponentSourcePath: string,\n\topponentExportName: string,\n\tengineDir: string,\n): string\n{\n\tconst opponentPath = opponentSourcePath.replace(/\\\\/g, '/');\n\treturn generateManualMatchEntryPointInner(\n\t\t`import {${opponentExportName} as __RawOpponent} from '${opponentPath}';`,\n\t\tengineDir,\n\t);\n}\n\n/**\n * Browser-friendly variant — the opponent is injected at runtime via\n * globalThis.__injectedBot1 (set by prepending the player's bot bundle to\n * the compiled output of this template). Used by the web client for manual\n * play against uploaded wizards.\n */\nexport function generateBrowserManualMatchEntryPoint(engineDir: string): string\n{\n\tconst opponentImport = 'var __RawOpponent = globalThis.__injectedBot1;\\n'\n\t\t+ 'if (!__RawOpponent) throw new Error(\"Opponent not injected (set globalThis.__injectedBot1)\");';\n\treturn generateManualMatchEntryPointInner(opponentImport, engineDir);\n}\n\nfunction generateManualMatchEntryPointInner(opponentImport: string, engineDir: string): string\n{\n\tconst enginePath = engineDir.replace(/\\\\/g, '/');\n\tconst srcPath = enginePath.replace(/\\/engine\\/?$/, '');\n\n\treturn `\n${opponentImport}\nimport {ManualMatch} from '${srcPath}/engine/manual-match.ts';\nimport {idle, turnToward, flyStraight} from '${srcPath}/hooks/action-builders.ts';\nimport {getMissileContext} from '${srcPath}/engine/hooks-runtime.ts';\n\n// Worker-local state — the player AI and guided-missile AIs read from these.\nvar __latestHumanActions = null;\nvar __latestHumanMissileTargets = {};\nvar __manualMatch = null;\n\nfunction __playerStub() {\n\tif (__latestHumanActions) {\n\t\treturn {_toAction: function() { return __latestHumanActions; }};\n\t}\n\treturn idle();\n}\n\nfunction __makeGuideStub(projectileId) {\n\treturn function() {\n\t\tvar target = __latestHumanMissileTargets[projectileId];\n\t\tif (!target) return flyStraight();\n\t\treturn turnToward(target.x, target.y);\n\t};\n}\n\nfunction __ensureMatch() {\n\tif (!__manualMatch) throw new Error('ManualMatch not initialized — call manualMatchInit first');\n\treturn __manualMatch;\n}\n\nglobalThis.__manualMatchInit = function(options) {\n\tif (__manualMatch) {\n\t\t__manualMatch.dispose();\n\t\t__manualMatch = null;\n\t}\n\t__latestHumanActions = (options && options.initialHumanActions) || null;\n\t__latestHumanMissileTargets = {};\n\t__manualMatch = new ManualMatch(__playerStub, __RawOpponent, options || {});\n\treturn __manualMatch.getGameState();\n};\n\nglobalThis.__manualMatchStep = function(options) {\n\tvar match = __ensureMatch();\n\tif (options) {\n\t\tif (options.humanActions) __latestHumanActions = options.humanActions;\n\t\tif (options.humanMissileTargets) {\n\t\t\t// Merge — the host may only update specific projectiles per call\n\t\t\tfor (var k in options.humanMissileTargets) {\n\t\t\t\t__latestHumanMissileTargets[k] = options.humanMissileTargets[k];\n\t\t\t}\n\t\t}\n\t}\n\tvar count = (options && options.count) || 1;\n\treturn match.step(count);\n};\n\nglobalThis.__manualMatchGuide = function(options) {\n\tvar match = __ensureMatch();\n\tvar id = options && options.projectileId;\n\tif (!id) return {ok: false, reason: 'missing projectileId'};\n\tmatch.replaceMissileAI(id, __makeGuideStub(id));\n\treturn {ok: true};\n};\n\nglobalThis.__manualMatchRelease = function(options) {\n\tvar match = __ensureMatch();\n\tvar id = options && options.projectileId;\n\tif (!id) return {ok: false, reason: 'missing projectileId'};\n\tmatch.restoreMissileAI(id);\n\tdelete __latestHumanMissileTargets[id];\n\treturn {ok: true};\n};\n\nglobalThis.__manualMatchSetInvincible = function(options) {\n\tvar match = __ensureMatch();\n\tvar idx = (options && options.wizardIndex) || 0;\n\tvar on = !!(options && options.on);\n\tmatch.setInvincible(idx, on);\n\treturn {ok: true};\n};\n\nglobalThis.__manualMatchGetState = function() {\n\tvar match = __ensureMatch();\n\treturn match.getGameState();\n};\n\nglobalThis.__manualMatchGetResult = function() {\n\tvar match = __ensureMatch();\n\treturn match.getResult();\n};\n\nglobalThis.__manualMatchDispose = function() {\n\tif (__manualMatch) {\n\t\t__manualMatch.dispose();\n\t\t__manualMatch = null;\n\t}\n\t__latestHumanActions = {move: {x: 0, y: 0}};\n\t__latestHumanMissileTargets = {};\n\treturn {ok: true};\n};\n`;\n}\n\n/**\n * Generate an alternate entry point where __fight/__simulate accept and return\n * JSON strings instead of structured objects. Used by the benchmark to compare\n * JSON serialization vs V8 structured clone performance.\n */\nexport function generateMatchEntryPointJSON(\n\tbot1SourcePath: string,\n\tbot1ExportName: string,\n\tbot2SourcePath: string,\n\tbot2ExportName: string,\n\tengineDir: string,\n): string\n{\n\tconst enginePath = engineDir.replace(/\\\\/g, '/');\n\tconst bot1Path = bot1SourcePath.replace(/\\\\/g, '/');\n\tconst bot2Path = bot2SourcePath.replace(/\\\\/g, '/');\n\tconst srcPath = enginePath.replace(/\\/engine\\/?$/, '');\n\n\treturn `\nimport {${bot1ExportName} as __Bot1} from '${bot1Path}';\nimport {${bot2ExportName} as __Bot2} from '${bot2Path}';\nimport {fight, simulate} from '${srcPath}/engine/simulation.ts';\nimport {wrapWithParams} from '${srcPath}/engine/params-runtime.ts';\nimport {DEFAULT_BUDGET} from '${srcPath}/engine/bot-compute-budget.ts';\n\nglobalThis.__fightJSON = function __fightJSON(optionsJSON) {\n\tconst options = JSON.parse(optionsJSON);\n\t// Budgeted, same as the non-JSON variant — two generators, one contract.\n\tconst result = fight(__Bot1, __Bot2, {budgetLimits: DEFAULT_BUDGET, ...options});\n\treturn JSON.stringify(result);\n};\n\nglobalThis.__simulateJSON = function __simulateJSON(optionsJSON) {\n\tconst options = JSON.parse(optionsJSON);\n\tconst {params1, params2, ...simOptions} = options;\n\tconst bot1 = params1 ? wrapWithParams(__Bot1, params1) : __Bot1;\n\tconst bot2 = params2 ? wrapWithParams(__Bot2, params2) : __Bot2;\n\t// Budgeted, same as the non-JSON variant — two generators, one contract.\n\tconst result = simulate(bot1, bot2, {budgetLimits: DEFAULT_BUDGET, ...simOptions});\n\treturn JSON.stringify(result);\n};\n`;\n}\n","/**\n * Run a fight between two PRECOMPILED bot bundles — the canonical uploaded-wizard\n * format where each IIFE sets `globalThis.__injectedBot1` (see compileSingleBotBundle).\n *\n * Shared by the Cloud Functions matchmaker and the devkit CLI so that a\n * `handle/botname` fight runs through the exact same engine as the live ladder.\n *\n * Both bundles run inside an isolated-vm isolate alongside a \"match template\" —\n * the engine + a `__fight` harness that reads the injected bots. The template is\n * built lazily from the engine source on first use and cached; callers that\n * already have one (the Cloud Functions committed MATCH_TEMPLATE) pass it in to\n * skip the esbuild step.\n *\n * Execution order inside the isolate: bot1 bundle → bot2 bundle → match template.\n */\n\nimport path from 'node:path';\nimport ivm from 'isolated-vm';\nimport {build} from 'esbuild';\nimport {getEngineDir} from './sandbox-compile.js';\nimport type {FightResult, SimulateResult} from './simulation.js';\nimport {DEFAULT_FIGHT_BACKSTOP_MS} from './bot-compute-budget.js';\n\n/** Memory limit per sandbox isolate (MB). */\nconst MEMORY_LIMIT_MB = 256;\n/**\n * Default timeout per fight (ms).\n *\n * This is a real safety limit: it stops a malicious or looping bot burning server time in\n * the matchmaker, so the DEFAULT must not move. It was previously hardcoded and unreachable\n * from any option, which made the suite unusable on a busy machine — one e2e fight exceeded\n * it and blocked seven consecutive commits, while a comparable fight run to the tick cap\n * finished in 1.3s on the same machine at the same moment. The limit was not catching a\n * runaway bot; it was catching load.\n */\nexport const DEFAULT_FIGHT_TIMEOUT_MS = DEFAULT_FIGHT_BACKSTOP_MS;\n\n/**\n * Resolve the effective fight timeout.\n *\n * Only ever EXTENDS the default. A caller who knows the work is legitimate (a test on a\n * slow machine) can ask for more; nobody can quietly ask for less, because weakening a\n * safety limit by accident is the direction that turns it into a flaky one.\n */\nexport function resolveFightTimeout(requested: number | undefined): number\n{\n\tif (typeof requested !== 'number' || !Number.isFinite(requested)) return DEFAULT_FIGHT_TIMEOUT_MS;\n\treturn Math.max(DEFAULT_FIGHT_TIMEOUT_MS, requested);\n}\n\nconst FREEZE_BANNER = `\nObject.freeze(Object.prototype);\nObject.freeze(Array.prototype);\nObject.freeze(Function.prototype);\nObject.freeze(String.prototype);\nObject.freeze(Number.prototype);\nObject.freeze(Boolean.prototype);\nObject.freeze(RegExp.prototype);\nObject.freeze(Date.prototype);\nObject.freeze(Error.prototype);\nObject.freeze(Map.prototype);\nObject.freeze(Set.prototype);\nObject.freeze(Math);\nObject.freeze(JSON);\n(function() {\n\tvar g = typeof globalThis !== 'undefined' ? globalThis : typeof self !== 'undefined' ? self : {};\n\tvar blocked = [\n\t\t'fetch', 'XMLHttpRequest', 'WebSocket', 'EventSource',\n\t\t'importScripts', 'Worker', 'SharedWorker',\n\t\t'Request', 'Response', 'Headers',\n\t\t'navigator', 'BroadcastChannel',\n\t\t'indexedDB', 'caches'\n\t];\n\tfor (var i = 0; i < blocked.length; i++) {\n\t\ttry { Object.defineProperty(g, blocked[i], {value: undefined, writable: false, configurable: false}); }\n\t\tcatch(e) {}\n\t}\n})();\n`;\n\nlet cachedTemplate: Promise<string> | null = null;\n\n/**\n * Build (and cache) the match template: the engine + `__fight`/`__simulate`\n * harness bundled into a single IIFE string, ready to run after two bot bundles\n * have set `globalThis.__injectedBot1`/`__injectedBot2`.\n */\nexport function buildMatchTemplate(): Promise<string>\n{\n\tif (!cachedTemplate)\n\t{\n\t\tcachedTemplate = (async(): Promise<string> =>\n\t\t{\n\t\t\tconst engineDir = getEngineDir();\n\t\t\tconst coreSrc = path.dirname(engineDir).replace(/\\\\/g, '/');\n\n\t\t\tconst entryPoint = `\nimport {fight, simulate} from '${coreSrc}/engine/simulation.ts';\nimport {wrapWithParams} from '${coreSrc}/engine/params-runtime.ts';\nimport {DEFAULT_BUDGET} from '${coreSrc}/engine/bot-compute-budget.ts';\n\nconst __Bot1 = (globalThis as any).__injectedBot1;\nconst __Bot2 = (globalThis as any).__injectedBot2;\n\nif (!__Bot1) throw new Error('Bot 1 not injected (set globalThis.__injectedBot1)');\nif (!__Bot2) throw new Error('Bot 2 not injected (set globalThis.__injectedBot2)');\n\n(globalThis as any).__fight = function __fight(options: any) {\n\t// Same per-bot compute budget the server uses, so a runaway bot fails the same way in\n\t// the CLI as it will on the ladder — and so it cannot hang someone's terminal.\n\treturn fight(__Bot1, __Bot2, {budgetLimits: DEFAULT_BUDGET, ...options});\n};\n\n(globalThis as any).__simulate = function __simulate(options: any) {\n\tconst {params1, params2, ...simOptions} = options;\n\tconst bot1 = params1 ? wrapWithParams(__Bot1, params1) : __Bot1;\n\tconst bot2 = params2 ? wrapWithParams(__Bot2, params2) : __Bot2;\n\t// Budgeted like __fight — this is the CLI's trace/optimize path, and its backstop went\n\t// up to 110s with everything else, so leaving it out would make it worse than before.\n\treturn simulate(bot1, bot2, {budgetLimits: DEFAULT_BUDGET, ...simOptions});\n};\n`;\n\n\t\t\tconst result = await build({\n\t\t\t\tstdin: {contents: entryPoint, resolveDir: engineDir, loader: 'ts'},\n\t\t\t\tbundle: true,\n\t\t\t\twrite: false,\n\t\t\t\tformat: 'iife',\n\t\t\t\tplatform: 'neutral',\n\t\t\t\ttarget: 'es2022',\n\t\t\t\tbanner: {js: FREEZE_BANNER},\n\t\t\t\tlogLevel: 'error',\n\t\t\t\texternal: [\n\t\t\t\t\t'isolated-vm', 'esbuild',\n\t\t\t\t\t'node:path', 'node:fs', 'node:url',\n\t\t\t\t\t'node:worker_threads', 'node:crypto', 'node:os', 'node:child_process',\n\t\t\t\t],\n\t\t\t});\n\n\t\t\tif (!result.outputFiles?.[0]) throw new Error('esbuild produced no match-template output');\n\t\t\treturn result.outputFiles[0].text;\n\t\t})();\n\t}\n\treturn cachedTemplate;\n}\n\nexport interface RunBundleFightOptions\n{\n\tseed: number;\n\t/** Prebuilt match template (e.g. the committed Cloud Functions one). Built lazily if omitted. */\n\tmatchTemplate?: string;\n\t/** Extend the per-fight isolate timeout. Can only raise it above the default. */\n\tfightTimeoutMs?: number;\n\t/** Skip per-tick history in the result (smaller copy out of the isolate). Default true. */\n\tskipHistory?: boolean;\n}\n\n/**\n * Run a fight between two compiled `__injectedBot1`-format bundles.\n *\n * bundle1 is saved and its global cleared before bundle2 runs, so bot code can\n * never read the opponent's function off globalThis. Both globals are deleted\n * after wiring so runtime bot code can't reach them either.\n */\nexport async function runBundleFight(\n\tbundle1: string,\n\tbundle2: string,\n\toptions: RunBundleFightOptions,\n): Promise<FightResult>\n{\n\tconst template = options.matchTemplate ?? await buildMatchTemplate();\n\tconst fightTimeoutMs = resolveFightTimeout(options.fightTimeoutMs);\n\tconst skipHistory = options.skipHistory ?? true;\n\n\t// Freeze prototypes/globals BEFORE the untrusted bot bundles run. The bundles\n\t// execute at module-eval (ahead of the template, whose banner used to be the\n\t// only freeze) — so without this a bot could pollute Object/Array/Math/etc. at\n\t// load time and corrupt the engine or its opponent. Mirrors the source path\n\t// (sandbox-harness), which already freezes before importing the bots.\n\tconst code = FREEZE_BANNER + '\\n' + bundle1\n\t\t+ '\\nvar __savedBot1 = globalThis.__injectedBot1;\\n'\n\t\t+ 'globalThis.__injectedBot1 = undefined;\\n'\n\t\t+ bundle2\n\t\t+ '\\nglobalThis.__injectedBot2 = globalThis.__injectedBot1;\\n'\n\t\t+ '\\nglobalThis.__injectedBot1 = __savedBot1;\\n'\n\t\t+ '__savedBot1 = undefined;\\n'\n\t\t+ template\n\t\t+ '\\ndelete globalThis.__injectedBot1;\\ndelete globalThis.__injectedBot2;\\n';\n\n\tconst isolate = new ivm.Isolate({memoryLimit: MEMORY_LIMIT_MB});\n\n\ttry\n\t{\n\t\tconst context = await isolate.createContext();\n\t\tconst jail = context.global;\n\t\tawait jail.set('global', jail.derefInto());\n\n\t\tconst script = await isolate.compileScript(code);\n\t\tawait script.run(context, {timeout: fightTimeoutMs});\n\n\t\tconst fightFn = await jail.get('__fight');\n\t\tconst result = await fightFn.apply(\n\t\t\tundefined,\n\t\t\t[new ivm.ExternalCopy({seed: options.seed, skipHistory}).copyInto()],\n\t\t\t{timeout: fightTimeoutMs, result: {copy: true}},\n\t\t);\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- isolated-vm returns unknown\n\t\treturn result as FightResult;\n\t}\n\tfinally\n\t{\n\t\tif (!isolate.isDisposed) isolate.dispose();\n\t}\n}\n\nexport interface RunBundleSimulateOptions\n{\n\tseed?: number;\n\tspawnDistance?: number;\n\tmaxTicks?: number;\n\tmatchTemplate?: string;\n\t/** Extend the per-fight isolate timeout. Can only raise it above the default. */\n\tfightTimeoutMs?: number;\n}\n\n/**\n * Run a SINGLE match between two compiled bundles, returning the full per-tick\n * history (for tracing/debugging). Same isolate wiring as runBundleFight, but\n * calls the template's `__simulate` so the caller gets a SimulateResult.\n */\nexport async function runBundleSimulate(\n\tbundle1: string,\n\tbundle2: string,\n\toptions: RunBundleSimulateOptions = {},\n): Promise<SimulateResult>\n{\n\tconst template = options.matchTemplate ?? await buildMatchTemplate();\n\tconst fightTimeoutMs = resolveFightTimeout(options.fightTimeoutMs);\n\n\t// Freeze before the untrusted bundles run (see runBundleFight).\n\tconst code = FREEZE_BANNER + '\\n' + bundle1\n\t\t+ '\\nvar __savedBot1 = globalThis.__injectedBot1;\\n'\n\t\t+ 'globalThis.__injectedBot1 = undefined;\\n'\n\t\t+ bundle2\n\t\t+ '\\nglobalThis.__injectedBot2 = globalThis.__injectedBot1;\\n'\n\t\t+ '\\nglobalThis.__injectedBot1 = __savedBot1;\\n'\n\t\t+ '__savedBot1 = undefined;\\n'\n\t\t+ template\n\t\t+ '\\ndelete globalThis.__injectedBot1;\\ndelete globalThis.__injectedBot2;\\n';\n\n\tconst isolate = new ivm.Isolate({memoryLimit: MEMORY_LIMIT_MB});\n\n\ttry\n\t{\n\t\tconst context = await isolate.createContext();\n\t\tconst jail = context.global;\n\t\tawait jail.set('global', jail.derefInto());\n\n\t\tconst script = await isolate.compileScript(code);\n\t\tawait script.run(context, {timeout: fightTimeoutMs});\n\n\t\tconst simulateFn = await jail.get('__simulate');\n\t\tconst simOptions = {\n\t\t\tseed: options.seed ?? 1,\n\t\t\tspawnDistance: options.spawnDistance,\n\t\t\tmaxTicks: options.maxTicks,\n\t\t};\n\t\tconst result = await simulateFn.apply(\n\t\t\tundefined,\n\t\t\t[new ivm.ExternalCopy(simOptions).copyInto()],\n\t\t\t{timeout: fightTimeoutMs, result: {copy: true}},\n\t\t);\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- isolated-vm returns unknown\n\t\treturn result as SimulateResult;\n\t}\n\tfinally\n\t{\n\t\tif (!isolate.isDisposed) isolate.dispose();\n\t}\n}\n"],"mappings":"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","names":["path","ivm","build","path","build","ivm"]}
1
+ {"version":3,"sources":["../src/engine/sandbox.ts","../src/engine/sandbox-compile.ts","../src/engine/sandbox-harness.ts","../src/engine/bundle-fight.ts"],"sourcesContent":["/**\r\n * VIBEMANCER — SANDBOX\r\n *\r\n * Provides isolated-vm sandboxing for bot code execution. Both bots + the\r\n * entire simulation engine run inside a single V8 isolate, so there is ZERO\r\n * per-tick boundary crossing overhead. The only data crossing the boundary\r\n * is fight/simulate options going in and results coming out.\r\n *\r\n * Architecture:\r\n * - Host: creates isolate, loads compiled bundle, calls __fight/__simulate\r\n * - Isolate: contains both bots + full simulation engine, runs fight/simulate\r\n *\r\n * Safety:\r\n * - Memory limit (default 512 MB) catches memory bombs\r\n * - Timeout (default 30s) catches infinite loops\r\n * - Prototype freeze prevents cross-bot sabotage\r\n * - platform: 'neutral' strips Node.js APIs (no fs/net/process)\r\n */\r\n\r\nimport ivm from 'isolated-vm';\r\nimport type {FightResult, SimulateResult} from './simulation.js';\r\nimport {BotBundle, compileMatchBundle} from './sandbox-compile.js';\r\nimport {DEFAULT_FIGHT_BACKSTOP_MS} from './bot-compute-budget.js';\r\nimport type {CompileOptions} from './sandbox-compile.js';\r\n\r\n// Re-export BotBundle so existing imports from sandbox.ts keep working\r\nexport {BotBundle} from './sandbox-compile.js';\r\nexport {compileMatchBundle, compileManualMatchBundle} from './sandbox-compile.js';\r\nexport type {CompileOptions} from './sandbox-compile.js';\r\n\r\n/**\r\n * Options for sandbox creation.\r\n */\r\nexport interface SandboxOptions\r\n{\r\n\t/** Memory limit in MB for the isolate (default: 512). */\r\n\tmemoryLimitMB?: number;\r\n\t/** Timeout in ms for fight/simulate calls (default: 60000). */\r\n\ttimeoutMs?: number;\r\n\t/** Options passed to esbuild compilation (aliases, externals). */\r\n\tcompileOptions?: CompileOptions;\r\n}\r\n\r\n/**\r\n * A sandboxed match runner. Both bots + the entire simulation engine run\r\n * inside a single isolated-vm isolate.\r\n *\r\n * Usage:\r\n * ```ts\r\n * const sandbox = await MatchSandbox.create(botA, botB);\r\n * const result = sandbox.fight({ seed: 42 });\r\n * sandbox.dispose();\r\n * ```\r\n */\r\nexport class MatchSandbox\r\n{\r\n\tprivate isolate: ivm.Isolate;\r\n\tprivate context: ivm.Context;\r\n\tprivate fightFn: ivm.Reference;\r\n\tprivate simulateFn: ivm.Reference;\r\n\tprivate timeout: number;\r\n\tprivate disposed = false;\r\n\r\n\tprivate constructor(\r\n\t\tisolate: ivm.Isolate,\r\n\t\tcontext: ivm.Context,\r\n\t\tfightFn: ivm.Reference,\r\n\t\tsimulateFn: ivm.Reference,\r\n\t\ttimeout: number,\r\n\t)\r\n\t{\r\n\t\tthis.isolate = isolate;\r\n\t\tthis.context = context;\r\n\t\tthis.fightFn = fightFn;\r\n\t\tthis.simulateFn = simulateFn;\r\n\t\tthis.timeout = timeout;\r\n\t}\r\n\r\n\t/**\r\n\t * Create a sandbox with both bots loaded. Compiles the match bundle\r\n\t * automatically using esbuild.\r\n\t */\r\n\tstatic async create(\r\n\t\tbot1: BotBundle,\r\n\t\tbot2: BotBundle,\r\n\t\toptions?: SandboxOptions,\r\n\t): Promise<MatchSandbox>\r\n\t{\r\n\t\tconst memoryLimitMB = options?.memoryLimitMB ?? 512;\r\n\t\t// Was a bare 60000, which the per-bot compute budget made wrong: a budgeted fight can\r\n\t\t// legitimately let one runaway bot spend 45s before it stops being called, and 60s\r\n\t\t// here would kill the whole fight first — putting the wall clock back in charge of\r\n\t\t// outcomes. Shared constant so this cannot drift from the other copies again.\r\n\t\tconst timeoutMs = options?.timeoutMs ?? DEFAULT_FIGHT_BACKSTOP_MS;\r\n\r\n\t\t// 1. Compile the match bundle\r\n\t\tconst bundle = await compileMatchBundle(bot1, bot2, options?.compileOptions);\r\n\r\n\t\t// 2. Create isolate with memory limit\r\n\t\tconst isolate = new ivm.Isolate({memoryLimit: memoryLimitMB});\r\n\r\n\t\ttry\r\n\t\t{\r\n\t\t\t// 3. Create context and load the bundle\r\n\t\t\tconst context = await isolate.createContext();\r\n\t\t\tconst script = await isolate.compileScript(bundle, {filename: 'match-bundle.js'});\r\n\t\t\tawait script.run(context, {timeout: timeoutMs});\r\n\r\n\t\t\t// 4. Get references to the exposed functions\r\n\t\t\tconst global = context.global;\r\n\t\t\tconst fightFn = await global.get('__fight', {reference: true});\r\n\t\t\tconst simulateFn = await global.get('__simulate', {reference: true});\r\n\r\n\t\t\treturn new MatchSandbox(isolate, context, fightFn, simulateFn, timeoutMs);\r\n\t\t}\r\n\t\tcatch(error)\r\n\t\t{\r\n\t\t\t// OOM or timeout can auto-dispose the isolate, so guard the cleanup dispose.\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tisolate.dispose();\r\n\t\t\t}\r\n\t\t\tcatch\r\n\t\t\t{\r\n\t\t\t\t// already disposed\r\n\t\t\t}\r\n\t\t\tthrow error;\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Create a sandbox from a pre-compiled bundle string.\r\n\t * Useful for caching compiled bundles across multiple MatchSandbox instances.\r\n\t */\r\n\tstatic async fromBundle(\r\n\t\tbundle: string,\r\n\t\toptions?: SandboxOptions,\r\n\t): Promise<MatchSandbox>\r\n\t{\r\n\t\tconst memoryLimitMB = options?.memoryLimitMB ?? 512;\r\n\t\t// Was a bare 60000, which the per-bot compute budget made wrong: a budgeted fight can\r\n\t\t// legitimately let one runaway bot spend 45s before it stops being called, and 60s\r\n\t\t// here would kill the whole fight first — putting the wall clock back in charge of\r\n\t\t// outcomes. Shared constant so this cannot drift from the other copies again.\r\n\t\tconst timeoutMs = options?.timeoutMs ?? DEFAULT_FIGHT_BACKSTOP_MS;\r\n\r\n\t\tconst isolate = new ivm.Isolate({memoryLimit: memoryLimitMB});\r\n\r\n\t\ttry\r\n\t\t{\r\n\t\t\tconst context = await isolate.createContext();\r\n\t\t\tconst script = await isolate.compileScript(bundle, {filename: 'match-bundle.js'});\r\n\t\t\tawait script.run(context, {timeout: timeoutMs});\r\n\r\n\t\t\tconst global = context.global;\r\n\t\t\tconst fightFn = await global.get('__fight', {reference: true});\r\n\t\t\tconst simulateFn = await global.get('__simulate', {reference: true});\r\n\r\n\t\t\treturn new MatchSandbox(isolate, context, fightFn, simulateFn, timeoutMs);\r\n\t\t}\r\n\t\tcatch(error)\r\n\t\t{\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tisolate.dispose();\r\n\t\t\t}\r\n\t\t\tcatch\r\n\t\t\t{\r\n\t\t\t\t// already disposed\r\n\t\t\t}\r\n\t\t\tthrow error;\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Compile a match bundle without creating an isolate.\r\n\t * Returns the compiled JS string for caching/reuse.\r\n\t */\r\n\tstatic async compile(bot1: BotBundle, bot2: BotBundle, compileOptions?: CompileOptions): Promise<string>\r\n\t{\r\n\t\treturn compileMatchBundle(bot1, bot2, compileOptions);\r\n\t}\r\n\r\n\t/**\r\n\t * Run a full fight (10 matches: 5 spawn distances x 2 sides).\r\n\t * Synchronous after isolate creation — runs entirely inside the isolate.\r\n\t */\r\n\tfight(options?: {seed?: number; maxTicks?: number}): FightResult\r\n\t{\r\n\t\tthis.ensureNotDisposed();\r\n\r\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- isolated-vm returns unknown\r\n\t\treturn this.fightFn.applySync(undefined, [options ?? {}], {\r\n\t\t\targuments: {copy: true},\r\n\t\t\tresult: {copy: true},\r\n\t\t\ttimeout: this.timeout,\r\n\t\t}) as FightResult;\r\n\t}\r\n\r\n\t/**\r\n\t * Run a single simulation.\r\n\t * Synchronous after isolate creation.\r\n\t *\r\n\t * @param options.params1 - useParam overrides for bot 1 (wizard-1)\r\n\t * @param options.params2 - useParam overrides for bot 2 (wizard-2)\r\n\t */\r\n\tsimulate(options?: {\r\n\t\tseed?: number;\r\n\t\tmaxTicks?: number;\r\n\t\tspawnDistance?: number;\r\n\t\tskipHistory?: boolean;\r\n\t\tparams1?: Record<string, number>;\r\n\t\tparams2?: Record<string, number>;\r\n\t}): SimulateResult\r\n\t{\r\n\t\tthis.ensureNotDisposed();\r\n\r\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- isolated-vm returns unknown\r\n\t\treturn this.simulateFn.applySync(undefined, [options ?? {}], {\r\n\t\t\targuments: {copy: true},\r\n\t\t\tresult: {copy: true},\r\n\t\t\ttimeout: this.timeout,\r\n\t\t}) as SimulateResult;\r\n\t}\r\n\r\n\t/**\r\n\t * Dispose the isolate and free all memory.\r\n\t * The sandbox cannot be used after disposal.\r\n\t */\r\n\tdispose(): void\r\n\t{\r\n\t\tif (!this.disposed)\r\n\t\t{\r\n\t\t\tthis.disposed = true;\r\n\t\t\t// OOM can auto-dispose the isolate, so guard all cleanup\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tthis.fightFn.release();\r\n\t\t\t}\r\n\t\t\tcatch\r\n\t\t\t{\r\n\t\t\t\t// isolate already disposed\r\n\t\t\t}\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tthis.simulateFn.release();\r\n\t\t\t}\r\n\t\t\tcatch\r\n\t\t\t{\r\n\t\t\t\t// isolate already disposed\r\n\t\t\t}\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tthis.context.release();\r\n\t\t\t}\r\n\t\t\tcatch\r\n\t\t\t{\r\n\t\t\t\t// isolate already disposed\r\n\t\t\t}\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tthis.isolate.dispose();\r\n\t\t\t}\r\n\t\t\tcatch\r\n\t\t\t{\r\n\t\t\t\t// isolate already disposed\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Whether this sandbox has been disposed.\r\n\t */\r\n\tget isDisposed(): boolean\r\n\t{\r\n\t\treturn this.disposed;\r\n\t}\r\n\r\n\tprivate ensureNotDisposed(): void\r\n\t{\r\n\t\tif (this.disposed)\r\n\t\t{\r\n\t\t\tthrow new Error('MatchSandbox has been disposed');\r\n\t\t}\r\n\t}\r\n}\r\n\r\n/**\r\n * One-shot sandboxed fight. Creates isolate, runs fight, disposes.\r\n * Convenience wrapper for single-use scenarios.\r\n */\r\nexport async function sandboxFight(\r\n\tbot1: BotBundle,\r\n\tbot2: BotBundle,\r\n\toptions?: {seed?: number; maxTicks?: number} & SandboxOptions,\r\n): Promise<FightResult>\r\n{\r\n\tconst sandbox = await MatchSandbox.create(bot1, bot2, options);\r\n\ttry\r\n\t{\r\n\t\treturn sandbox.fight({seed: options?.seed, maxTicks: options?.maxTicks});\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tsandbox.dispose();\r\n\t}\r\n}\r\n\r\n/**\r\n * One-shot sandboxed simulate. Creates isolate, runs simulate, disposes.\r\n */\r\nexport async function sandboxSimulate(\r\n\tbot1: BotBundle,\r\n\tbot2: BotBundle,\r\n\toptions?: {\r\n\t\tseed?: number;\r\n\t\tmaxTicks?: number;\r\n\t\tspawnDistance?: number;\r\n\t\tskipHistory?: boolean;\r\n\t\tparams1?: Record<string, number>;\r\n\t\tparams2?: Record<string, number>;\r\n\t} & SandboxOptions,\r\n): Promise<SimulateResult>\r\n{\r\n\tconst sandbox = await MatchSandbox.create(bot1, bot2, options);\r\n\ttry\r\n\t{\r\n\t\treturn sandbox.simulate({\r\n\t\t\tseed: options?.seed,\r\n\t\t\tmaxTicks: options?.maxTicks,\r\n\t\t\tspawnDistance: options?.spawnDistance,\r\n\t\t\tskipHistory: options?.skipHistory,\r\n\t\t\tparams1: options?.params1,\r\n\t\t\tparams2: options?.params2,\r\n\t\t});\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tsandbox.dispose();\r\n\t}\r\n}\r\n","/**\r\n * VIBEMANCER — SANDBOX COMPILATION\r\n *\r\n * Compiles match bundles using esbuild. Extracted from sandbox.ts so that\r\n * compilation can be used independently of isolated-vm (e.g., in the CLI\r\n * dev server or for browser Web Worker sandboxes).\r\n *\r\n * This file has NO isolated-vm dependency — only esbuild + Node.js builtins.\r\n */\r\n\r\nimport {build} from 'esbuild';\r\nimport path from 'node:path';\r\nimport fs from 'node:fs';\r\nimport {fileURLToPath} from 'node:url';\r\nimport {PROTOTYPE_FREEZE_BANNER, generateManualMatchEntryPoint, generateMatchEntryPoint} from './sandbox-harness.js';\r\n\r\nconst currentFilename = fileURLToPath(import.meta.url);\r\nconst currentDirname = path.dirname(currentFilename);\r\n\r\n/**\r\n * Find the src/engine/ directory. Works from src/, dist/engine/, or dist/ (tsup bundle).\r\n * esbuild needs TypeScript source files, so we look for the src/ tree.\r\n */\r\nexport function getEngineDir(): string\r\n{\r\n\t// When running from src/engine/ (dev/test), currentDirname is already src/engine/\r\n\tconst directPath = path.resolve(currentDirname);\r\n\tif (fs.existsSync(path.join(directPath, 'simulation.ts')))\r\n\t{\r\n\t\treturn directPath;\r\n\t}\r\n\r\n\t// When running from dist/engine/ (individual files), package root is 2 levels up\r\n\tconst packageRoot2 = path.resolve(currentDirname, '..', '..');\r\n\tconst srcEngine2 = path.join(packageRoot2, 'src', 'engine');\r\n\tif (fs.existsSync(path.join(srcEngine2, 'simulation.ts')))\r\n\t{\r\n\t\treturn srcEngine2;\r\n\t}\r\n\r\n\t// When running from dist/ (tsup bundle), package root is 1 level up\r\n\tconst packageRoot1 = path.resolve(currentDirname, '..');\r\n\tconst srcEngine1 = path.join(packageRoot1, 'src', 'engine');\r\n\tif (fs.existsSync(path.join(srcEngine1, 'simulation.ts')))\r\n\t{\r\n\t\treturn srcEngine1;\r\n\t}\r\n\r\n\tthrow new Error('Could not find engine source directory (src/engine/simulation.ts)');\r\n}\r\n\r\n/**\r\n * A compiled bot ready for sandboxed execution.\r\n * Stores the source path and export name — actual compilation\r\n * happens when creating a MatchSandbox or calling compileMatchBundle.\r\n */\r\nexport class BotBundle\r\n{\r\n\treadonly sourcePath: string;\r\n\treadonly exportName: string;\r\n\r\n\tconstructor(sourcePath: string, exportName: string)\r\n\t{\r\n\t\tif (!sourcePath || typeof sourcePath !== 'string')\r\n\t\t{\r\n\t\t\tthrow new Error('sourcePath must be a non-empty string');\r\n\t\t}\r\n\t\tif (!exportName || typeof exportName !== 'string')\r\n\t\t{\r\n\t\t\tthrow new Error('exportName must be a non-empty string');\r\n\t\t}\r\n\t\tthis.sourcePath = path.resolve(sourcePath);\r\n\t\tthis.exportName = exportName;\r\n\t}\r\n}\r\n\r\n/**\r\n * Options for esbuild compilation. Allows the caller to add esbuild\r\n * aliases (e.g., resolving @vibemancer/core to the TypeScript source).\r\n */\r\nexport interface CompileOptions\r\n{\r\n\t/** Additional esbuild alias entries (e.g., {'@vibemancer/core': '/path/to/src/index.ts'}). */\r\n\talias?: Record<string, string>;\r\n\t/** Additional modules to treat as external (not bundled). */\r\n\texternal?: string[];\r\n}\r\n\r\n/**\r\n * Compile a match bundle using esbuild. Bundles both bots + simulation engine\r\n * into a single self-contained IIFE with prototype freezing banner.\r\n *\r\n * No isolated-vm dependency — returns a plain JS string.\r\n */\r\nexport async function compileMatchBundle(\r\n\tbot1: BotBundle,\r\n\tbot2: BotBundle,\r\n\toptions?: CompileOptions,\r\n): Promise<string>\r\n{\r\n\tconst engineDir = getEngineDir();\r\n\tconst entryPoint = generateMatchEntryPoint(\r\n\t\tbot1.sourcePath,\r\n\t\tbot1.exportName,\r\n\t\tbot2.sourcePath,\r\n\t\tbot2.exportName,\r\n\t\tengineDir,\r\n\t);\r\n\r\n\tconst result = await build({\r\n\t\tstdin: {\r\n\t\t\tcontents: entryPoint,\r\n\t\t\tresolveDir: engineDir,\r\n\t\t\tloader: 'ts',\r\n\t\t},\r\n\t\tbundle: true,\r\n\t\twrite: false,\r\n\t\tformat: 'iife',\r\n\t\tplatform: 'neutral',\r\n\t\ttarget: 'es2022',\r\n\t\tbanner: {\r\n\t\t\tjs: PROTOTYPE_FREEZE_BANNER,\r\n\t\t},\r\n\t\t// Suppress warnings about top-level this in ESM\r\n\t\tlogLevel: 'error',\r\n\t\t// Match bundles run in sandboxed environments (Web Workers / isolated-vm)\r\n\t\t// and should never include Node.js native modules\r\n\t\texternal: [\r\n\t\t\t'isolated-vm', 'esbuild', 'node:path', 'node:fs', 'node:url',\r\n\t\t\t'node:worker_threads', 'node:crypto', 'node:os', 'node:child_process',\r\n\t\t\t...(options?.external ?? []),\r\n\t\t],\r\n\t\t...(options?.alias ? {alias: options.alias} : {}),\r\n\t});\r\n\r\n\tif (!result.outputFiles?.[0])\r\n\t{\r\n\t\tthrow new Error('esbuild produced no output');\r\n\t}\r\n\r\n\treturn result.outputFiles[0].text;\r\n}\r\n\r\n/**\r\n * Compile a manual-play sandbox bundle. Bundles ONE opponent bot + ManualMatch\r\n * + simulation engine into a self-contained IIFE. The \"player\" wizard is a\r\n * worker-local stub that reads from `__latestHumanActions` (set per-step by\r\n * the host).\r\n *\r\n * Returns a plain JS string that, when loaded into a Web Worker, exposes the\r\n * `__manualMatchInit`, `__manualMatchStep`, `__manualMatchGuide`,\r\n * `__manualMatchRelease`, `__manualMatchSetInvincible`,\r\n * `__manualMatchGetState`, `__manualMatchGetResult`, and `__manualMatchDispose`\r\n * globals on the worker's globalThis.\r\n */\r\nexport async function compileManualMatchBundle(\r\n\topponent: BotBundle,\r\n\toptions?: CompileOptions,\r\n): Promise<string>\r\n{\r\n\tconst engineDir = getEngineDir();\r\n\tconst entryPoint = generateManualMatchEntryPoint(\r\n\t\topponent.sourcePath,\r\n\t\topponent.exportName,\r\n\t\tengineDir,\r\n\t);\r\n\r\n\tconst result = await build({\r\n\t\tstdin: {\r\n\t\t\tcontents: entryPoint,\r\n\t\t\tresolveDir: engineDir,\r\n\t\t\tloader: 'ts',\r\n\t\t},\r\n\t\tbundle: true,\r\n\t\twrite: false,\r\n\t\tformat: 'iife',\r\n\t\tplatform: 'neutral',\r\n\t\ttarget: 'es2022',\r\n\t\tbanner: {\r\n\t\t\tjs: PROTOTYPE_FREEZE_BANNER,\r\n\t\t},\r\n\t\tlogLevel: 'error',\r\n\t\texternal: [\r\n\t\t\t'isolated-vm', 'esbuild', 'node:path', 'node:fs', 'node:url',\r\n\t\t\t'node:worker_threads', 'node:crypto', 'node:os', 'node:child_process',\r\n\t\t\t...(options?.external ?? []),\r\n\t\t],\r\n\t\t...(options?.alias ? {alias: options.alias} : {}),\r\n\t});\r\n\r\n\tif (!result.outputFiles?.[0])\r\n\t{\r\n\t\tthrow new Error('esbuild produced no output');\r\n\t}\r\n\r\n\treturn result.outputFiles[0].text;\r\n}\r\n","/**\n * VIBEMANCER — SANDBOX HARNESS\n *\n * Generates the entry point code that runs inside an isolated-vm isolate.\n * The harness bundles both bots + the simulation engine into a single IIFE\n * via esbuild, then exposes __fight and __simulate on globalThis.\n *\n * The prototype freeze banner runs before any module code, preventing\n * prototype pollution attacks between bots sharing the same isolate.\n */\n\n/**\n * JavaScript code injected as esbuild banner — runs before the IIFE bundle.\n *\n * 1. Freezes all built-in prototypes to prevent cross-bot sabotage via prototype\n * pollution. This does NOT prevent calling existing methods (e.g. Array.push\n * still works), it only prevents reassigning them.\n *\n * 2. Blocks all IO/network capabilities. Web Workers have fetch, XMLHttpRequest,\n * WebSocket, importScripts, etc. Bot code must not be able to make network\n * calls or load external scripts. Uses Object.defineProperty to make the block\n * irrecoverable (non-writable, non-configurable). In isolated-vm, these globals\n * don't exist — the try-catch makes the deletes a harmless no-op.\n */\nexport const PROTOTYPE_FREEZE_BANNER = `\nObject.freeze(Object.prototype);\nObject.freeze(Array.prototype);\nObject.freeze(Function.prototype);\nObject.freeze(String.prototype);\nObject.freeze(Number.prototype);\nObject.freeze(Boolean.prototype);\nObject.freeze(RegExp.prototype);\nObject.freeze(Date.prototype);\nObject.freeze(Error.prototype);\nObject.freeze(Map.prototype);\nObject.freeze(Set.prototype);\nObject.freeze(Math);\nObject.freeze(JSON);\n(function() {\n\tvar g = typeof globalThis !== 'undefined' ? globalThis : typeof self !== 'undefined' ? self : {};\n\tvar blocked = [\n\t\t'fetch', 'XMLHttpRequest', 'WebSocket', 'EventSource',\n\t\t'importScripts', 'Worker', 'SharedWorker',\n\t\t'Request', 'Response', 'Headers',\n\t\t'navigator', 'BroadcastChannel',\n\t\t'indexedDB', 'caches'\n\t];\n\tfor (var i = 0; i < blocked.length; i++) {\n\t\ttry { Object.defineProperty(g, blocked[i], {value: undefined, writable: false, configurable: false}); }\n\t\tcatch(e) {}\n\t}\n})();\n`;\n\n/**\n * Generate the TypeScript entry point for a match sandbox.\n *\n * This entry point imports both bots and the simulation engine, then\n * exposes __fight and __simulate on globalThis. esbuild bundles this\n * + all transitive imports into a single self-contained IIFE.\n *\n * @param bot1SourcePath - Absolute path to bot 1's TypeScript source file\n * @param bot1ExportName - Named export of bot 1's WizardFunction\n * @param bot2SourcePath - Absolute path to bot 2's TypeScript source file\n * @param bot2ExportName - Named export of bot 2's WizardFunction\n * @param engineDir - Absolute path to the engine directory (src/engine/)\n */\nexport function generateMatchEntryPoint(\n\tbot1SourcePath: string,\n\tbot1ExportName: string,\n\tbot2SourcePath: string,\n\tbot2ExportName: string,\n\tengineDir: string,\n): string\n{\n\t// Use forward slashes for esbuild compatibility (works on all platforms)\n\tconst enginePath = engineDir.replace(/\\\\/g, '/');\n\tconst bot1Path = bot1SourcePath.replace(/\\\\/g, '/');\n\tconst bot2Path = bot2SourcePath.replace(/\\\\/g, '/');\n\n\t// All imports use the engineDir's parent (= packages/core/src/) as root.\n\t// When user bots alias @vibemancer/core → src/index-browser.ts, esbuild\n\t// deduplicates these with the bot's imports since they resolve to the same files.\n\t// This ensures the hooks runtime global state is shared between harness and bot.\n\tconst srcPath = enginePath.replace(/\\/engine\\/?$/, '');\n\n\treturn `\nimport {${bot1ExportName} as __Bot1} from '${bot1Path}';\nimport {${bot2ExportName} as __Bot2} from '${bot2Path}';\nimport {fight, simulate} from '${srcPath}/engine/simulation.ts';\nimport {wrapWithParams} from '${srcPath}/engine/params-runtime.ts';\nimport {DEFAULT_BUDGET} from '${srcPath}/engine/bot-compute-budget.ts';\n\nglobalThis.__fight = function __fight(options) {\n\t// Budgeted like every other fight path. This one matters MORE than it looks: it is what\n\t// sandboxFight uses, so it is the MCP fight tools and the CLI — the place a user's\n\t// runaway bot most directly burns server time. Leaving it unbudgeted while the backstop\n\t// moved from 60s to 110s would have made this path strictly worse than before.\n\tconst result = fight(__Bot1, __Bot2, {budgetLimits: DEFAULT_BUDGET, ...options});\n\treturn result;\n};\n\nglobalThis.__simulate = function __simulate(options) {\n\tconst {params1, params2, ...simOptions} = options;\n\tconst bot1 = params1 ? wrapWithParams(__Bot1, params1) : __Bot1;\n\tconst bot2 = params2 ? wrapWithParams(__Bot2, params2) : __Bot2;\n\t// Budgeted like __fight. This path is trace and the optimizer — a single match rather\n\t// than ten — and leaving it out would have made it strictly worse than before, since the\n\t// same change raised its backstop from 60s to 110s.\n\tconst result = simulate(bot1, bot2, {budgetLimits: DEFAULT_BUDGET, ...simOptions});\n\treturn result;\n};\n`;\n}\n\n/**\n * Generate the entry point for a Manual Play sandbox bundle.\n *\n * Unlike the fight/simulate entry point (which exposes one-shot batch APIs),\n * the manual-match entry point holds a single long-lived ManualMatch instance\n * inside the worker and exposes per-tick step/rewind/guide APIs.\n *\n * The \"player\" wizard is a worker-local stub that returns whatever\n * `__latestHumanActions` is set to — this avoids the can't-postMessage-functions\n * problem (the function lives entirely worker-side, only data crosses the\n * boundary). Guided missiles use a similar pattern via\n * `__latestHumanMissileTargets[projectileId]`.\n *\n * @param opponentSourcePath - Absolute path to opponent bot's TypeScript source\n * @param opponentExportName - Named export of the opponent's WizardFunction\n * @param engineDir - Absolute path to src/engine/\n */\nexport function generateManualMatchEntryPoint(\n\topponentSourcePath: string,\n\topponentExportName: string,\n\tengineDir: string,\n): string\n{\n\tconst opponentPath = opponentSourcePath.replace(/\\\\/g, '/');\n\treturn generateManualMatchEntryPointInner(\n\t\t`import {${opponentExportName} as __RawOpponent} from '${opponentPath}';`,\n\t\tengineDir,\n\t);\n}\n\n/**\n * Browser-friendly variant — the opponent is injected at runtime via\n * globalThis.__injectedBot1 (set by prepending the player's bot bundle to\n * the compiled output of this template). Used by the web client for manual\n * play against uploaded wizards.\n */\nexport function generateBrowserManualMatchEntryPoint(engineDir: string): string\n{\n\tconst opponentImport = 'var __RawOpponent = globalThis.__injectedBot1;\\n'\n\t\t+ 'if (!__RawOpponent) throw new Error(\"Opponent not injected (set globalThis.__injectedBot1)\");';\n\treturn generateManualMatchEntryPointInner(opponentImport, engineDir);\n}\n\nfunction generateManualMatchEntryPointInner(opponentImport: string, engineDir: string): string\n{\n\tconst enginePath = engineDir.replace(/\\\\/g, '/');\n\tconst srcPath = enginePath.replace(/\\/engine\\/?$/, '');\n\n\treturn `\n${opponentImport}\nimport {ManualMatch} from '${srcPath}/engine/manual-match.ts';\nimport {idle, turnToward, flyStraight} from '${srcPath}/hooks/action-builders.ts';\nimport {getMissileContext} from '${srcPath}/engine/hooks-runtime.ts';\n\n// Worker-local state — the player AI and guided-missile AIs read from these.\nvar __latestHumanActions = null;\nvar __latestHumanMissileTargets = {};\nvar __manualMatch = null;\n\nfunction __playerStub() {\n\tif (__latestHumanActions) {\n\t\treturn {_toAction: function() { return __latestHumanActions; }};\n\t}\n\treturn idle();\n}\n\nfunction __makeGuideStub(projectileId) {\n\treturn function() {\n\t\tvar target = __latestHumanMissileTargets[projectileId];\n\t\tif (!target) return flyStraight();\n\t\treturn turnToward(target.x, target.y);\n\t};\n}\n\nfunction __ensureMatch() {\n\tif (!__manualMatch) throw new Error('ManualMatch not initialized — call manualMatchInit first');\n\treturn __manualMatch;\n}\n\nglobalThis.__manualMatchInit = function(options) {\n\tif (__manualMatch) {\n\t\t__manualMatch.dispose();\n\t\t__manualMatch = null;\n\t}\n\t__latestHumanActions = (options && options.initialHumanActions) || null;\n\t__latestHumanMissileTargets = {};\n\t__manualMatch = new ManualMatch(__playerStub, __RawOpponent, options || {});\n\treturn __manualMatch.getGameState();\n};\n\nglobalThis.__manualMatchStep = function(options) {\n\tvar match = __ensureMatch();\n\tif (options) {\n\t\tif (options.humanActions) __latestHumanActions = options.humanActions;\n\t\tif (options.humanMissileTargets) {\n\t\t\t// Merge — the host may only update specific projectiles per call\n\t\t\tfor (var k in options.humanMissileTargets) {\n\t\t\t\t__latestHumanMissileTargets[k] = options.humanMissileTargets[k];\n\t\t\t}\n\t\t}\n\t}\n\tvar count = (options && options.count) || 1;\n\treturn match.step(count);\n};\n\nglobalThis.__manualMatchGuide = function(options) {\n\tvar match = __ensureMatch();\n\tvar id = options && options.projectileId;\n\tif (!id) return {ok: false, reason: 'missing projectileId'};\n\tmatch.replaceMissileAI(id, __makeGuideStub(id));\n\treturn {ok: true};\n};\n\nglobalThis.__manualMatchRelease = function(options) {\n\tvar match = __ensureMatch();\n\tvar id = options && options.projectileId;\n\tif (!id) return {ok: false, reason: 'missing projectileId'};\n\tmatch.restoreMissileAI(id);\n\tdelete __latestHumanMissileTargets[id];\n\treturn {ok: true};\n};\n\nglobalThis.__manualMatchSetInvincible = function(options) {\n\tvar match = __ensureMatch();\n\tvar idx = (options && options.wizardIndex) || 0;\n\tvar on = !!(options && options.on);\n\tmatch.setInvincible(idx, on);\n\treturn {ok: true};\n};\n\nglobalThis.__manualMatchGetState = function() {\n\tvar match = __ensureMatch();\n\treturn match.getGameState();\n};\n\nglobalThis.__manualMatchGetResult = function() {\n\tvar match = __ensureMatch();\n\treturn match.getResult();\n};\n\nglobalThis.__manualMatchDispose = function() {\n\tif (__manualMatch) {\n\t\t__manualMatch.dispose();\n\t\t__manualMatch = null;\n\t}\n\t__latestHumanActions = {move: {x: 0, y: 0}};\n\t__latestHumanMissileTargets = {};\n\treturn {ok: true};\n};\n`;\n}\n\n/**\n * Generate an alternate entry point where __fight/__simulate accept and return\n * JSON strings instead of structured objects. Used by the benchmark to compare\n * JSON serialization vs V8 structured clone performance.\n */\nexport function generateMatchEntryPointJSON(\n\tbot1SourcePath: string,\n\tbot1ExportName: string,\n\tbot2SourcePath: string,\n\tbot2ExportName: string,\n\tengineDir: string,\n): string\n{\n\tconst enginePath = engineDir.replace(/\\\\/g, '/');\n\tconst bot1Path = bot1SourcePath.replace(/\\\\/g, '/');\n\tconst bot2Path = bot2SourcePath.replace(/\\\\/g, '/');\n\tconst srcPath = enginePath.replace(/\\/engine\\/?$/, '');\n\n\treturn `\nimport {${bot1ExportName} as __Bot1} from '${bot1Path}';\nimport {${bot2ExportName} as __Bot2} from '${bot2Path}';\nimport {fight, simulate} from '${srcPath}/engine/simulation.ts';\nimport {wrapWithParams} from '${srcPath}/engine/params-runtime.ts';\nimport {DEFAULT_BUDGET} from '${srcPath}/engine/bot-compute-budget.ts';\n\nglobalThis.__fightJSON = function __fightJSON(optionsJSON) {\n\tconst options = JSON.parse(optionsJSON);\n\t// Budgeted, same as the non-JSON variant — two generators, one contract.\n\tconst result = fight(__Bot1, __Bot2, {budgetLimits: DEFAULT_BUDGET, ...options});\n\treturn JSON.stringify(result);\n};\n\nglobalThis.__simulateJSON = function __simulateJSON(optionsJSON) {\n\tconst options = JSON.parse(optionsJSON);\n\tconst {params1, params2, ...simOptions} = options;\n\tconst bot1 = params1 ? wrapWithParams(__Bot1, params1) : __Bot1;\n\tconst bot2 = params2 ? wrapWithParams(__Bot2, params2) : __Bot2;\n\t// Budgeted, same as the non-JSON variant — two generators, one contract.\n\tconst result = simulate(bot1, bot2, {budgetLimits: DEFAULT_BUDGET, ...simOptions});\n\treturn JSON.stringify(result);\n};\n`;\n}\n","/**\n * Run a fight between two PRECOMPILED bot bundles — the canonical uploaded-wizard\n * format where each IIFE sets `globalThis.__injectedBot1` (see compileSingleBotBundle).\n *\n * Shared by the Cloud Functions matchmaker and the devkit CLI so that a\n * `handle/botname` fight runs through the exact same engine as the live ladder.\n *\n * Both bundles run inside an isolated-vm isolate alongside a \"match template\" —\n * the engine + a `__fight` harness that reads the injected bots. The template is\n * built lazily from the engine source on first use and cached; callers that\n * already have one (the Cloud Functions committed MATCH_TEMPLATE) pass it in to\n * skip the esbuild step.\n *\n * Execution order inside the isolate: bot1 bundle → bot2 bundle → match template.\n */\n\nimport path from 'node:path';\nimport ivm from 'isolated-vm';\nimport {build} from 'esbuild';\nimport {getEngineDir} from './sandbox-compile.js';\nimport type {FightResult, SimulateResult} from './simulation.js';\nimport {DEFAULT_FIGHT_BACKSTOP_MS} from './bot-compute-budget.js';\n\n/** Memory limit per sandbox isolate (MB). */\nconst MEMORY_LIMIT_MB = 256;\n/**\n * Default timeout per fight (ms).\n *\n * This is a real safety limit: it stops a malicious or looping bot burning server time in\n * the matchmaker, so the DEFAULT must not move. It was previously hardcoded and unreachable\n * from any option, which made the suite unusable on a busy machine — one e2e fight exceeded\n * it and blocked seven consecutive commits, while a comparable fight run to the tick cap\n * finished in 1.3s on the same machine at the same moment. The limit was not catching a\n * runaway bot; it was catching load.\n */\nexport const DEFAULT_FIGHT_TIMEOUT_MS = DEFAULT_FIGHT_BACKSTOP_MS;\n\n/**\n * Resolve the effective fight timeout.\n *\n * Only ever EXTENDS the default. A caller who knows the work is legitimate (a test on a\n * slow machine) can ask for more; nobody can quietly ask for less, because weakening a\n * safety limit by accident is the direction that turns it into a flaky one.\n */\nexport function resolveFightTimeout(requested: number | undefined): number\n{\n\tif (typeof requested !== 'number' || !Number.isFinite(requested)) return DEFAULT_FIGHT_TIMEOUT_MS;\n\treturn Math.max(DEFAULT_FIGHT_TIMEOUT_MS, requested);\n}\n\nconst FREEZE_BANNER = `\nObject.freeze(Object.prototype);\nObject.freeze(Array.prototype);\nObject.freeze(Function.prototype);\nObject.freeze(String.prototype);\nObject.freeze(Number.prototype);\nObject.freeze(Boolean.prototype);\nObject.freeze(RegExp.prototype);\nObject.freeze(Date.prototype);\nObject.freeze(Error.prototype);\nObject.freeze(Map.prototype);\nObject.freeze(Set.prototype);\nObject.freeze(Math);\nObject.freeze(JSON);\n(function() {\n\tvar g = typeof globalThis !== 'undefined' ? globalThis : typeof self !== 'undefined' ? self : {};\n\tvar blocked = [\n\t\t'fetch', 'XMLHttpRequest', 'WebSocket', 'EventSource',\n\t\t'importScripts', 'Worker', 'SharedWorker',\n\t\t'Request', 'Response', 'Headers',\n\t\t'navigator', 'BroadcastChannel',\n\t\t'indexedDB', 'caches'\n\t];\n\tfor (var i = 0; i < blocked.length; i++) {\n\t\ttry { Object.defineProperty(g, blocked[i], {value: undefined, writable: false, configurable: false}); }\n\t\tcatch(e) {}\n\t}\n})();\n`;\n\nlet cachedTemplate: Promise<string> | null = null;\n\n/**\n * Build (and cache) the match template: the engine + `__fight`/`__simulate`\n * harness bundled into a single IIFE string, ready to run after two bot bundles\n * have set `globalThis.__injectedBot1`/`__injectedBot2`.\n */\nexport function buildMatchTemplate(): Promise<string>\n{\n\tif (!cachedTemplate)\n\t{\n\t\tcachedTemplate = (async(): Promise<string> =>\n\t\t{\n\t\t\tconst engineDir = getEngineDir();\n\t\t\tconst coreSrc = path.dirname(engineDir).replace(/\\\\/g, '/');\n\n\t\t\tconst entryPoint = `\nimport {fight, simulate} from '${coreSrc}/engine/simulation.ts';\nimport {wrapWithParams} from '${coreSrc}/engine/params-runtime.ts';\nimport {DEFAULT_BUDGET} from '${coreSrc}/engine/bot-compute-budget.ts';\n\nconst __Bot1 = (globalThis as any).__injectedBot1;\nconst __Bot2 = (globalThis as any).__injectedBot2;\n\nif (!__Bot1) throw new Error('Bot 1 not injected (set globalThis.__injectedBot1)');\nif (!__Bot2) throw new Error('Bot 2 not injected (set globalThis.__injectedBot2)');\n\n(globalThis as any).__fight = function __fight(options: any) {\n\t// Same per-bot compute budget the server uses, so a runaway bot fails the same way in\n\t// the CLI as it will on the ladder — and so it cannot hang someone's terminal.\n\treturn fight(__Bot1, __Bot2, {budgetLimits: DEFAULT_BUDGET, ...options});\n};\n\n(globalThis as any).__simulate = function __simulate(options: any) {\n\tconst {params1, params2, ...simOptions} = options;\n\tconst bot1 = params1 ? wrapWithParams(__Bot1, params1) : __Bot1;\n\tconst bot2 = params2 ? wrapWithParams(__Bot2, params2) : __Bot2;\n\t// Budgeted like __fight — this is the CLI's trace/optimize path, and its backstop went\n\t// up to 110s with everything else, so leaving it out would make it worse than before.\n\treturn simulate(bot1, bot2, {budgetLimits: DEFAULT_BUDGET, ...simOptions});\n};\n`;\n\n\t\t\tconst result = await build({\n\t\t\t\tstdin: {contents: entryPoint, resolveDir: engineDir, loader: 'ts'},\n\t\t\t\tbundle: true,\n\t\t\t\twrite: false,\n\t\t\t\tformat: 'iife',\n\t\t\t\tplatform: 'neutral',\n\t\t\t\ttarget: 'es2022',\n\t\t\t\tbanner: {js: FREEZE_BANNER},\n\t\t\t\tlogLevel: 'error',\n\t\t\t\texternal: [\n\t\t\t\t\t'isolated-vm', 'esbuild',\n\t\t\t\t\t'node:path', 'node:fs', 'node:url',\n\t\t\t\t\t'node:worker_threads', 'node:crypto', 'node:os', 'node:child_process',\n\t\t\t\t],\n\t\t\t});\n\n\t\t\tif (!result.outputFiles?.[0]) throw new Error('esbuild produced no match-template output');\n\t\t\treturn result.outputFiles[0].text;\n\t\t})();\n\t}\n\treturn cachedTemplate;\n}\n\nexport interface RunBundleFightOptions\n{\n\tseed: number;\n\t/** Prebuilt match template (e.g. the committed Cloud Functions one). Built lazily if omitted. */\n\tmatchTemplate?: string;\n\t/** Extend the per-fight isolate timeout. Can only raise it above the default. */\n\tfightTimeoutMs?: number;\n\t/** Skip per-tick history in the result (smaller copy out of the isolate). Default true. */\n\tskipHistory?: boolean;\n}\n\n/**\n * Run a fight between two compiled `__injectedBot1`-format bundles.\n *\n * bundle1 is saved and its global cleared before bundle2 runs, so bot code can\n * never read the opponent's function off globalThis. Both globals are deleted\n * after wiring so runtime bot code can't reach them either.\n */\nexport async function runBundleFight(\n\tbundle1: string,\n\tbundle2: string,\n\toptions: RunBundleFightOptions,\n): Promise<FightResult>\n{\n\tconst template = options.matchTemplate ?? await buildMatchTemplate();\n\tconst fightTimeoutMs = resolveFightTimeout(options.fightTimeoutMs);\n\tconst skipHistory = options.skipHistory ?? true;\n\n\t// Freeze prototypes/globals BEFORE the untrusted bot bundles run. The bundles\n\t// execute at module-eval (ahead of the template, whose banner used to be the\n\t// only freeze) — so without this a bot could pollute Object/Array/Math/etc. at\n\t// load time and corrupt the engine or its opponent. Mirrors the source path\n\t// (sandbox-harness), which already freezes before importing the bots.\n\tconst code = FREEZE_BANNER + '\\n' + bundle1\n\t\t+ '\\nvar __savedBot1 = globalThis.__injectedBot1;\\n'\n\t\t+ 'globalThis.__injectedBot1 = undefined;\\n'\n\t\t+ bundle2\n\t\t+ '\\nglobalThis.__injectedBot2 = globalThis.__injectedBot1;\\n'\n\t\t+ '\\nglobalThis.__injectedBot1 = __savedBot1;\\n'\n\t\t+ '__savedBot1 = undefined;\\n'\n\t\t+ template\n\t\t+ '\\ndelete globalThis.__injectedBot1;\\ndelete globalThis.__injectedBot2;\\n';\n\n\tconst isolate = new ivm.Isolate({memoryLimit: MEMORY_LIMIT_MB});\n\n\ttry\n\t{\n\t\tconst context = await isolate.createContext();\n\t\tconst jail = context.global;\n\t\tawait jail.set('global', jail.derefInto());\n\n\t\tconst script = await isolate.compileScript(code);\n\t\tawait script.run(context, {timeout: fightTimeoutMs});\n\n\t\tconst fightFn = await jail.get('__fight');\n\t\tconst result = await fightFn.apply(\n\t\t\tundefined,\n\t\t\t[new ivm.ExternalCopy({seed: options.seed, skipHistory}).copyInto()],\n\t\t\t{timeout: fightTimeoutMs, result: {copy: true}},\n\t\t);\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- isolated-vm returns unknown\n\t\treturn result as FightResult;\n\t}\n\tfinally\n\t{\n\t\tif (!isolate.isDisposed) isolate.dispose();\n\t}\n}\n\nexport interface RunBundleSimulateOptions\n{\n\tseed?: number;\n\tspawnDistance?: number;\n\tmaxTicks?: number;\n\tmatchTemplate?: string;\n\t/** Extend the per-fight isolate timeout. Can only raise it above the default. */\n\tfightTimeoutMs?: number;\n}\n\n/**\n * Run a SINGLE match between two compiled bundles, returning the full per-tick\n * history (for tracing/debugging). Same isolate wiring as runBundleFight, but\n * calls the template's `__simulate` so the caller gets a SimulateResult.\n */\nexport async function runBundleSimulate(\n\tbundle1: string,\n\tbundle2: string,\n\toptions: RunBundleSimulateOptions = {},\n): Promise<SimulateResult>\n{\n\tconst template = options.matchTemplate ?? await buildMatchTemplate();\n\tconst fightTimeoutMs = resolveFightTimeout(options.fightTimeoutMs);\n\n\t// Freeze before the untrusted bundles run (see runBundleFight).\n\tconst code = FREEZE_BANNER + '\\n' + bundle1\n\t\t+ '\\nvar __savedBot1 = globalThis.__injectedBot1;\\n'\n\t\t+ 'globalThis.__injectedBot1 = undefined;\\n'\n\t\t+ bundle2\n\t\t+ '\\nglobalThis.__injectedBot2 = globalThis.__injectedBot1;\\n'\n\t\t+ '\\nglobalThis.__injectedBot1 = __savedBot1;\\n'\n\t\t+ '__savedBot1 = undefined;\\n'\n\t\t+ template\n\t\t+ '\\ndelete globalThis.__injectedBot1;\\ndelete globalThis.__injectedBot2;\\n';\n\n\tconst isolate = new ivm.Isolate({memoryLimit: MEMORY_LIMIT_MB});\n\n\ttry\n\t{\n\t\tconst context = await isolate.createContext();\n\t\tconst jail = context.global;\n\t\tawait jail.set('global', jail.derefInto());\n\n\t\tconst script = await isolate.compileScript(code);\n\t\tawait script.run(context, {timeout: fightTimeoutMs});\n\n\t\tconst simulateFn = await jail.get('__simulate');\n\t\tconst simOptions = {\n\t\t\tseed: options.seed ?? 1,\n\t\t\tspawnDistance: options.spawnDistance,\n\t\t\tmaxTicks: options.maxTicks,\n\t\t};\n\t\tconst result = await simulateFn.apply(\n\t\t\tundefined,\n\t\t\t[new ivm.ExternalCopy(simOptions).copyInto()],\n\t\t\t{timeout: fightTimeoutMs, result: {copy: true}},\n\t\t);\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- isolated-vm returns unknown\n\t\treturn result as SimulateResult;\n\t}\n\tfinally\n\t{\n\t\tif (!isolate.isDisposed) isolate.dispose();\n\t}\n}\n"],"mappings":"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","names":["path","ivm","build","path","build","ivm"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibemancer/core",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Vibemancer game engine - competitive programming wizard battle arena",
5
5
  "type": "module",
6
6
  "author": "Low Entry",