@vibemancer/core 1.0.11 → 1.0.13
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.
- package/dist/{chunk-EO7JO2RZ.js → chunk-KGRG7TZS.js} +80 -34
- package/dist/chunk-KGRG7TZS.js.map +1 -0
- package/dist/{index-browser-CM0uuzWp.d.ts → index-browser-Dc-Vl1HI.d.ts} +11 -5
- package/dist/index-browser.d.ts +1 -1
- package/dist/index-browser.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/engine/bot-error-capture.ts +33 -13
- package/src/engine/simulation.ts +82 -0
- package/src/engine/spells.ts +13 -1
- package/src/engine-version.ts +1 -1
- package/src/hooks/action-builders.ts +35 -15
- package/src/hooks/state-hooks.ts +5 -1
- package/src/hooks/threat-analysis.ts +30 -8
- package/src/types.ts +1 -0
- package/dist/chunk-EO7JO2RZ.js.map +0 -1
|
@@ -241,6 +241,10 @@ interface WizardState {
|
|
|
241
241
|
lastMissileConfig?: MissileConfig;
|
|
242
242
|
warmupMultiplier?: number;
|
|
243
243
|
invincible?: boolean;
|
|
244
|
+
advised?: {
|
|
245
|
+
nonFinite?: boolean;
|
|
246
|
+
unfinishableCast?: boolean;
|
|
247
|
+
};
|
|
244
248
|
}
|
|
245
249
|
/**
|
|
246
250
|
* Projectile (missile) state.
|
|
@@ -654,7 +658,7 @@ declare function currentRuleset(): Record<string, number>;
|
|
|
654
658
|
* Used to gate spectator replays: a recorded match can only be re-simulated when
|
|
655
659
|
* the runtime engine version matches the version that produced the match.
|
|
656
660
|
*/
|
|
657
|
-
declare const ENGINE_VERSION =
|
|
661
|
+
declare const ENGINE_VERSION = 4256327861353618;
|
|
658
662
|
/**
|
|
659
663
|
* Historical versions that denote the SAME engine as `ENGINE_VERSION`.
|
|
660
664
|
*
|
|
@@ -873,9 +877,6 @@ interface InternalWizardState extends WizardState {
|
|
|
873
877
|
* Initialize a new match state.
|
|
874
878
|
*/
|
|
875
879
|
declare function createInitialState(_seed: number, spawnDist?: number): GameState;
|
|
876
|
-
/**
|
|
877
|
-
* Process one game tick.
|
|
878
|
-
*/
|
|
879
880
|
declare function tick(currentTick: number, wizard1AI: WizardFunction, wizard2AI: WizardFunction, config: GameConfig, wizards: InternalWizardState[], projectiles: ProjectileState[], missileAIs: Map<string, MissileFunction$1>, matchSeed: number, budgets?: {
|
|
880
881
|
states: [BotBudgetState, BotBudgetState];
|
|
881
882
|
limits: BudgetLimits;
|
|
@@ -1810,9 +1811,14 @@ declare function useMyThreatsToEnemy(): AnalyzedThreat[];
|
|
|
1810
1811
|
* @param projectiles - All projectiles in the game
|
|
1811
1812
|
* @param myProjectiles - Only the bot's own projectiles (used for filtering)
|
|
1812
1813
|
* @param ticksUntilReady - Ticks until wizard can start a new action
|
|
1814
|
+
* @param options - `canCancelCurrentCast` when the wizard is mid-CAST and could cancel()
|
|
1815
|
+
* out of it for one tick's cost. Defaults to false, which is the safe
|
|
1816
|
+
* reading for a caller that does not know its own state.
|
|
1813
1817
|
* @returns Array of analyzed threats sorted by ticksToImpact (soonest first)
|
|
1814
1818
|
*/
|
|
1815
|
-
declare function analyzeThreats(myPos: Position, projectiles: ProjectileState[], myProjectiles: ProjectileState[], ticksUntilReady: number
|
|
1819
|
+
declare function analyzeThreats(myPos: Position, projectiles: ProjectileState[], myProjectiles: ProjectileState[], ticksUntilReady: number, options?: {
|
|
1820
|
+
canCancelCurrentCast?: boolean;
|
|
1821
|
+
}): AnalyzedThreat[];
|
|
1816
1822
|
|
|
1817
1823
|
/**
|
|
1818
1824
|
* VIBEMANCER - ACTION BUILDERS
|
package/dist/index-browser.d.ts
CHANGED
|
@@ -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, w as BudgetExhaustion, C as CASTING_MOVEMENT_MULT, y as COLLISION_RADIUS, z as CastCancelEvent, D as CastStartEvent, E as Critter, I as DEFAULT_SEED, J as Doombringer, K as ENGINE_VERSION, L as EQUIVALENT_ENGINE_VERSIONS, M as EnemyState, N as FIGHT_SPAWN_DISTANCES, F as FightResult, P as FightStats, Q as FightWinner, R as FinalAction, T as Flamecaller, U as GCD_DURATION, V as GameConfig, W as GameState, X as Golem, Y as Hero, Z as Hogger, _ as HomingParams, $ as HookState, a0 as Infernalist, a1 as InternalWizardState, a2 as KNOCKBACK_DAMAGE_THRESHOLD, a3 as KNOCKBACK_DECAY, a4 as KNOCKBACK_DELAY, a5 as KNOCKBACK_SPEED_PER_DAMAGE, a6 as LAVA_BORDER_WIDTH, a7 as Lich, a8 as MATCH_DURATION, a9 as MAX_HEALTH, aa as MISSILE_BASE_CAST, ab as MISSILE_BASE_RADIUS, ac as MISSILE_DAMAGE_POWER, ad as MISSILE_DAMAGE_RADIUS_SCALE, ae as MISSILE_DAMAGE_SCALE, af as MISSILE_HOMING_COEFF, ag as MISSILE_MIN_CAST_TIME, ah as MISSILE_MIN_DAMAGE, ai as MISSILE_MIN_DURATION, aj as MISSILE_MIN_SPEED, ak as MISSILE_RADIUS_PER_DAMAGE, al as MISSILE_SPEED_DURATION_BASELINE, am as MISSILE_SPEED_DURATION_COEFF, an as MISSILE_TURN_DURATION_COEFF, ao as MOVEMENT_SPEED, ap as MOVE_SPEED, aq as ManualMatch, ar as ManualMatchInitOptions, as as ManualMatchOptions, at as ManualMatchStepRequest, au as MatchWinner, av as MissileAction, aw as MissileActions, ax as MissileConfig, ay as MissileContext, az as MissileExpiredEvent, aA as MissileFunction, aB as MissileHitEvent, aC as MissileLaunchEvent, aD as MissileOobEvent, aE as MissileTemplate, aF as Nightblade, aG as ParamDeclaration, aH as Position, aI as ProjectileState, aJ as Pyromancer, aK as RULES, aL as RULESET_RANGES, aM as RefObject, aN as Rookie, aO as SHIELD_CAST_TIME, aP as SHIELD_DECAY_PER_SECOND, aQ as SHIELD_DECAY_RATE, aR as SHIELD_MAX_BLOCK, aS as SHIELD_MAX_STRENGTH, aT as SHIELD_MIN_BLOCK, aU as SHIELD_MIN_STRENGTH, aV as SPAWN_DISTANCE, aW as SeekerParams, aX as Sentinel, aY as Shadowblade, aZ as ShieldBlockEvent, a_ as ShieldStartEvent, a$ as SimEvent, S as SimulateResult, b0 as Spellbinder, b1 as Spellseeker, b2 as Spellshot, b3 as Spellspinner, b4 as Spelltracer, b5 as Spellweaver, b6 as SpiralParams, b7 as StepResult, b8 as Stormcaller, b9 as Stormchaser, ba as Stormforger, bb as StraightParams, bc as TICKS_PER_SECOND, bd as TICK_DURATION_MS, be as TargetDummy, bf as TestBotBuilder, bg as TestFightResult, bh as TestSimulateResult, bi as TraceBotSummary, bj as TraceEvent, bk as TraceEventType, bl as TraceSummary, bm as Turtle, bn as Velocity, bo as Voidblade, bp as WARMUP_DURATION_TOLERANCE, bq as WARMUP_MAX_BONUS, br as WARMUP_MAX_PENALTY, bs as WARMUP_SPEED_TOLERANCE, bt as WARMUP_TURN_TOLERANCE, bu as WIZARD_HEALTH, bv as WIZARD_RADIUS, bw as Warmage, bx as WizardActions, by as WizardContext, bz as WizardDeathEvent, bA as WizardEntry, bB as WizardFunction, bC as WizardGroup, bD as WizardLavaDeathEvent, bE as WizardState, bF as WorkerFactory, bG as WorkerLike, bH as aim, bI as analyzeThreats, bJ as angleDiff, bK as angleInRange, bL as angleTo, bM as applyDamage, bN as applyRulesetOverrides, bO as blink, bP as browserSandboxFight, bQ as browserSandboxSimulate, bR as calculateBlinkCooldown, bS as calculateMissileCastTime, bT as calculateMissileRadius, bU as calculateMissileSimilarity, bV as calculateShieldBlock, bW as calculateWarmupMultiplier, bX as canReplayMatch, bY as cancel, bZ as cancelCast, b_ as clampPositionToArena, b$ as clampToArena, c0 as clampToSafeZone, c1 as clearHooks, c2 as clearParamValues, c3 as completeCast, c5 as createEntitySeed, c7 as createInitialState, c8 as createRandom, c9 as createWorkerScript, ca as currentRuleset, cb as diagnoseTrace, cc as directionAway, cd as directionTo, ce as distanceTo, cf as effectiveTurnRateCost, cg as extractAction, ch as extractMissileAction, ci as extractStats, cj as extractTraceEvents, ck as fight, cl as findInRange, cm as findNearest, cn as fitMissileForEscapingTarget, co as fitMissileToBudget, cp as flyStraight, cq as formatDiagnosis, cr as formatStats, cs as formatTraceEvents, ct as formatTraceSummary, cu as generateCandidates, cv as generateCombos, cw as getAdaptiveMissileConfig, cx as getEffectiveRange, cy as getLeadPosition, cz as getMissileCastTime, cA as getMissileContext, cB as getPlayerState, cC as getWizardContext, cD as hashCombine, cE as homingMissile, cF as idle, cG as inRange, cH as interceptAngle, cI as isBannedBotName, cJ as isInLava, cK as magnitude, cL as matchesAnyBanRule, cN as missile, cO as move, cP as moveInDirection, cQ as moveProjectile, cR as moveTowardSafely, cS as moveWizard, cT as narrowRange, cU as nextRandom, cV as normalize, cW as normalizeAngle, cX as predictPosition, cZ as resetAllHooks, c_ as resetRuleset, c$ as resolveWizardCollision, d0 as runWithHooks, d1 as runWizardWithContext, d2 as scoreFight, d3 as scoreFightAsWizard2, d4 as seekerMissile, d5 as setParamValues, d6 as shield, d7 as simulate, d8 as simulateMinDuration, d9 as sortByDistance, da as spiralMissile, db as startCast, dc as startDiscovery, dd as stopDiscovery, de as straightMissile, df as summarizeTrace, dg as sweptCircleCollision, dh as testBot, di as tick, dj as turnToAngle, dk as turnToward, dl as updateShield, dm as useArenaSize, dn as useBlinkCooldown, dp as useCastProgress, dq as useCastingSpell, dr as useClosestThreat, ds as useDamageDealt, dt as useDamageTaken, du as useEffect, dv as useEnemy, dw as useHealth, dx as useLastHitTick, dy as useLastMissileConfig, dz as useMemo, dA as useMyProjectiles, dB as useMyThreatsToEnemy, dC as useParam, dD as usePosition, dE as useProjectiles, dF as useRandom, dG as useRef, dH as useShieldStrength, dI as useState, dJ as useStatus, dK as useThreats, dL as useTick, dM as useTicksUntilReady, dN as useVelocity, dO as validateHookCall, dP as validateMissileConfig, dQ as withMissileContext, dR as withWizardContext, dS as wrapWithParams } from './index-browser-
|
|
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, w as BudgetExhaustion, C as CASTING_MOVEMENT_MULT, y as COLLISION_RADIUS, z as CastCancelEvent, D as CastStartEvent, E as Critter, I as DEFAULT_SEED, J as Doombringer, K as ENGINE_VERSION, L as EQUIVALENT_ENGINE_VERSIONS, M as EnemyState, N as FIGHT_SPAWN_DISTANCES, F as FightResult, P as FightStats, Q as FightWinner, R as FinalAction, T as Flamecaller, U as GCD_DURATION, V as GameConfig, W as GameState, X as Golem, Y as Hero, Z as Hogger, _ as HomingParams, $ as HookState, a0 as Infernalist, a1 as InternalWizardState, a2 as KNOCKBACK_DAMAGE_THRESHOLD, a3 as KNOCKBACK_DECAY, a4 as KNOCKBACK_DELAY, a5 as KNOCKBACK_SPEED_PER_DAMAGE, a6 as LAVA_BORDER_WIDTH, a7 as Lich, a8 as MATCH_DURATION, a9 as MAX_HEALTH, aa as MISSILE_BASE_CAST, ab as MISSILE_BASE_RADIUS, ac as MISSILE_DAMAGE_POWER, ad as MISSILE_DAMAGE_RADIUS_SCALE, ae as MISSILE_DAMAGE_SCALE, af as MISSILE_HOMING_COEFF, ag as MISSILE_MIN_CAST_TIME, ah as MISSILE_MIN_DAMAGE, ai as MISSILE_MIN_DURATION, aj as MISSILE_MIN_SPEED, ak as MISSILE_RADIUS_PER_DAMAGE, al as MISSILE_SPEED_DURATION_BASELINE, am as MISSILE_SPEED_DURATION_COEFF, an as MISSILE_TURN_DURATION_COEFF, ao as MOVEMENT_SPEED, ap as MOVE_SPEED, aq as ManualMatch, ar as ManualMatchInitOptions, as as ManualMatchOptions, at as ManualMatchStepRequest, au as MatchWinner, av as MissileAction, aw as MissileActions, ax as MissileConfig, ay as MissileContext, az as MissileExpiredEvent, aA as MissileFunction, aB as MissileHitEvent, aC as MissileLaunchEvent, aD as MissileOobEvent, aE as MissileTemplate, aF as Nightblade, aG as ParamDeclaration, aH as Position, aI as ProjectileState, aJ as Pyromancer, aK as RULES, aL as RULESET_RANGES, aM as RefObject, aN as Rookie, aO as SHIELD_CAST_TIME, aP as SHIELD_DECAY_PER_SECOND, aQ as SHIELD_DECAY_RATE, aR as SHIELD_MAX_BLOCK, aS as SHIELD_MAX_STRENGTH, aT as SHIELD_MIN_BLOCK, aU as SHIELD_MIN_STRENGTH, aV as SPAWN_DISTANCE, aW as SeekerParams, aX as Sentinel, aY as Shadowblade, aZ as ShieldBlockEvent, a_ as ShieldStartEvent, a$ as SimEvent, S as SimulateResult, b0 as Spellbinder, b1 as Spellseeker, b2 as Spellshot, b3 as Spellspinner, b4 as Spelltracer, b5 as Spellweaver, b6 as SpiralParams, b7 as StepResult, b8 as Stormcaller, b9 as Stormchaser, ba as Stormforger, bb as StraightParams, bc as TICKS_PER_SECOND, bd as TICK_DURATION_MS, be as TargetDummy, bf as TestBotBuilder, bg as TestFightResult, bh as TestSimulateResult, bi as TraceBotSummary, bj as TraceEvent, bk as TraceEventType, bl as TraceSummary, bm as Turtle, bn as Velocity, bo as Voidblade, bp as WARMUP_DURATION_TOLERANCE, bq as WARMUP_MAX_BONUS, br as WARMUP_MAX_PENALTY, bs as WARMUP_SPEED_TOLERANCE, bt as WARMUP_TURN_TOLERANCE, bu as WIZARD_HEALTH, bv as WIZARD_RADIUS, bw as Warmage, bx as WizardActions, by as WizardContext, bz as WizardDeathEvent, bA as WizardEntry, bB as WizardFunction, bC as WizardGroup, bD as WizardLavaDeathEvent, bE as WizardState, bF as WorkerFactory, bG as WorkerLike, bH as aim, bI as analyzeThreats, bJ as angleDiff, bK as angleInRange, bL as angleTo, bM as applyDamage, bN as applyRulesetOverrides, bO as blink, bP as browserSandboxFight, bQ as browserSandboxSimulate, bR as calculateBlinkCooldown, bS as calculateMissileCastTime, bT as calculateMissileRadius, bU as calculateMissileSimilarity, bV as calculateShieldBlock, bW as calculateWarmupMultiplier, bX as canReplayMatch, bY as cancel, bZ as cancelCast, b_ as clampPositionToArena, b$ as clampToArena, c0 as clampToSafeZone, c1 as clearHooks, c2 as clearParamValues, c3 as completeCast, c5 as createEntitySeed, c7 as createInitialState, c8 as createRandom, c9 as createWorkerScript, ca as currentRuleset, cb as diagnoseTrace, cc as directionAway, cd as directionTo, ce as distanceTo, cf as effectiveTurnRateCost, cg as extractAction, ch as extractMissileAction, ci as extractStats, cj as extractTraceEvents, ck as fight, cl as findInRange, cm as findNearest, cn as fitMissileForEscapingTarget, co as fitMissileToBudget, cp as flyStraight, cq as formatDiagnosis, cr as formatStats, cs as formatTraceEvents, ct as formatTraceSummary, cu as generateCandidates, cv as generateCombos, cw as getAdaptiveMissileConfig, cx as getEffectiveRange, cy as getLeadPosition, cz as getMissileCastTime, cA as getMissileContext, cB as getPlayerState, cC as getWizardContext, cD as hashCombine, cE as homingMissile, cF as idle, cG as inRange, cH as interceptAngle, cI as isBannedBotName, cJ as isInLava, cK as magnitude, cL as matchesAnyBanRule, cN as missile, cO as move, cP as moveInDirection, cQ as moveProjectile, cR as moveTowardSafely, cS as moveWizard, cT as narrowRange, cU as nextRandom, cV as normalize, cW as normalizeAngle, cX as predictPosition, cZ as resetAllHooks, c_ as resetRuleset, c$ as resolveWizardCollision, d0 as runWithHooks, d1 as runWizardWithContext, d2 as scoreFight, d3 as scoreFightAsWizard2, d4 as seekerMissile, d5 as setParamValues, d6 as shield, d7 as simulate, d8 as simulateMinDuration, d9 as sortByDistance, da as spiralMissile, db as startCast, dc as startDiscovery, dd as stopDiscovery, de as straightMissile, df as summarizeTrace, dg as sweptCircleCollision, dh as testBot, di as tick, dj as turnToAngle, dk as turnToward, dl as updateShield, dm as useArenaSize, dn as useBlinkCooldown, dp as useCastProgress, dq as useCastingSpell, dr as useClosestThreat, ds as useDamageDealt, dt as useDamageTaken, du as useEffect, dv as useEnemy, dw as useHealth, dx as useLastHitTick, dy as useLastMissileConfig, dz as useMemo, dA as useMyProjectiles, dB as useMyThreatsToEnemy, dC as useParam, dD as usePosition, dE as useProjectiles, dF as useRandom, dG as useRef, dH as useShieldStrength, dI as useState, dJ as useStatus, dK as useThreats, dL as useTick, dM as useTicksUntilReady, dN as useVelocity, dO as validateHookCall, dP as validateMissileConfig, dQ as withMissileContext, dR as withWizardContext, dS as wrapWithParams } from './index-browser-Dc-Vl1HI.js';
|
package/dist/index-browser.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as FightResult, S as SimulateResult } from './index-browser-
|
|
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 BudgetExhaustion, x as BudgetLimits, C as CASTING_MOVEMENT_MULT, y as COLLISION_RADIUS, z as CastCancelEvent, D as CastStartEvent, E as Critter, G as DEFAULT_BUDGET, H as DEFAULT_FIGHT_BACKSTOP_MS, I as DEFAULT_SEED, J as Doombringer, K as ENGINE_VERSION, L as EQUIVALENT_ENGINE_VERSIONS, M as EnemyState, N as FIGHT_SPAWN_DISTANCES, O as FightBudget, P as FightStats, Q as FightWinner, R as FinalAction, T as Flamecaller, U as GCD_DURATION, V as GameConfig, W as GameState, X as Golem, Y as Hero, Z as Hogger, _ as HomingParams, $ as HookState, a0 as Infernalist, a1 as InternalWizardState, a2 as KNOCKBACK_DAMAGE_THRESHOLD, a3 as KNOCKBACK_DECAY, a4 as KNOCKBACK_DELAY, a5 as KNOCKBACK_SPEED_PER_DAMAGE, a6 as LAVA_BORDER_WIDTH, a7 as Lich, a8 as MATCH_DURATION, a9 as MAX_HEALTH, aa as MISSILE_BASE_CAST, ab as MISSILE_BASE_RADIUS, ac as MISSILE_DAMAGE_POWER, ad as MISSILE_DAMAGE_RADIUS_SCALE, ae as MISSILE_DAMAGE_SCALE, af as MISSILE_HOMING_COEFF, ag as MISSILE_MIN_CAST_TIME, ah as MISSILE_MIN_DAMAGE, ai as MISSILE_MIN_DURATION, aj as MISSILE_MIN_SPEED, ak as MISSILE_RADIUS_PER_DAMAGE, al as MISSILE_SPEED_DURATION_BASELINE, am as MISSILE_SPEED_DURATION_COEFF, an as MISSILE_TURN_DURATION_COEFF, ao as MOVEMENT_SPEED, ap as MOVE_SPEED, aq as ManualMatch, ar as ManualMatchInitOptions, as as ManualMatchOptions, at as ManualMatchStepRequest, au as MatchWinner, av as MissileAction, aw as MissileActions, ax as MissileConfig, ay as MissileContext, az as MissileExpiredEvent, aA as MissileFunction, aB as MissileHitEvent, aC as MissileLaunchEvent, aD as MissileOobEvent, aE as MissileTemplate, aF as Nightblade, aG as ParamDeclaration, aH as Position, aI as ProjectileState, aJ as Pyromancer, aK as RULES, aL as RULESET_RANGES, aM as RefObject, aN as Rookie, aO as SHIELD_CAST_TIME, aP as SHIELD_DECAY_PER_SECOND, aQ as SHIELD_DECAY_RATE, aR as SHIELD_MAX_BLOCK, aS as SHIELD_MAX_STRENGTH, aT as SHIELD_MIN_BLOCK, aU as SHIELD_MIN_STRENGTH, aV as SPAWN_DISTANCE, aW as SeekerParams, aX as Sentinel, aY as Shadowblade, aZ as ShieldBlockEvent, a_ as ShieldStartEvent, a$ as SimEvent, b0 as Spellbinder, b1 as Spellseeker, b2 as Spellshot, b3 as Spellspinner, b4 as Spelltracer, b5 as Spellweaver, b6 as SpiralParams, b7 as StepResult, b8 as Stormcaller, b9 as Stormchaser, ba as Stormforger, bb as StraightParams, bc as TICKS_PER_SECOND, bd as TICK_DURATION_MS, be as TargetDummy, bf as TestBotBuilder, bg as TestFightResult, bh as TestSimulateResult, bi as TraceBotSummary, bj as TraceEvent, bk as TraceEventType, bl as TraceSummary, bm as Turtle, bn as Velocity, bo as Voidblade, bp as WARMUP_DURATION_TOLERANCE, bq as WARMUP_MAX_BONUS, br as WARMUP_MAX_PENALTY, bs as WARMUP_SPEED_TOLERANCE, bt as WARMUP_TURN_TOLERANCE, bu as WIZARD_HEALTH, bv as WIZARD_RADIUS, bw as Warmage, bx as WizardActions, by as WizardContext, bz as WizardDeathEvent, bA as WizardEntry, bB as WizardFunction, bC as WizardGroup, bD as WizardLavaDeathEvent, bE as WizardState, bF as WorkerFactory, bG as WorkerLike, bH as aim, bI as analyzeThreats, bJ as angleDiff, bK as angleInRange, bL as angleTo, bM as applyDamage, bN as applyRulesetOverrides, bO as blink, bP as browserSandboxFight, bQ as browserSandboxSimulate, bR as calculateBlinkCooldown, bS as calculateMissileCastTime, bT as calculateMissileRadius, bU as calculateMissileSimilarity, bV as calculateShieldBlock, bW as calculateWarmupMultiplier, bX as canReplayMatch, bY as cancel, bZ as cancelCast, b_ as clampPositionToArena, b$ as clampToArena, c0 as clampToSafeZone, c1 as clearHooks, c2 as clearParamValues, c3 as completeCast, c4 as createBudgetState, c5 as createEntitySeed, c6 as createFightBudget, c7 as createInitialState, c8 as createRandom, c9 as createWorkerScript, ca as currentRuleset, cb as diagnoseTrace, cc as directionAway, cd as directionTo, ce as distanceTo, cf as effectiveTurnRateCost, cg as extractAction, ch as extractMissileAction, ci as extractStats, cj as extractTraceEvents, ck as fight, cl as findInRange, cm as findNearest, cn as fitMissileForEscapingTarget, co as fitMissileToBudget, cp as flyStraight, cq as formatDiagnosis, cr as formatStats, cs as formatTraceEvents, ct as formatTraceSummary, cu as generateCandidates, cv as generateCombos, cw as getAdaptiveMissileConfig, cx as getEffectiveRange, cy as getLeadPosition, cz as getMissileCastTime, cA as getMissileContext, cB as getPlayerState, cC as getWizardContext, cD as hashCombine, cE as homingMissile, cF as idle, cG as inRange, cH as interceptAngle, cI as isBannedBotName, cJ as isInLava, cK as magnitude, cL as matchesAnyBanRule, cM as mayAct, cN as missile, cO as move, cP as moveInDirection, cQ as moveProjectile, cR as moveTowardSafely, cS as moveWizard, cT as narrowRange, cU as nextRandom, cV as normalize, cW as normalizeAngle, cX as predictPosition, cY as recordSpend, cZ as resetAllHooks, c_ as resetRuleset, c$ as resolveWizardCollision, d0 as runWithHooks, d1 as runWizardWithContext, d2 as scoreFight, d3 as scoreFightAsWizard2, d4 as seekerMissile, d5 as setParamValues, d6 as shield, d7 as simulate, d8 as simulateMinDuration, d9 as sortByDistance, da as spiralMissile, db as startCast, dc as startDiscovery, dd as stopDiscovery, de as straightMissile, df as summarizeTrace, dg as sweptCircleCollision, dh as testBot, di as tick, dj as turnToAngle, dk as turnToward, dl as updateShield, dm as useArenaSize, dn as useBlinkCooldown, dp as useCastProgress, dq as useCastingSpell, dr as useClosestThreat, ds as useDamageDealt, dt as useDamageTaken, du as useEffect, dv as useEnemy, dw as useHealth, dx as useLastHitTick, dy as useLastMissileConfig, dz as useMemo, dA as useMyProjectiles, dB as useMyThreatsToEnemy, dC as useParam, dD as usePosition, dE as useProjectiles, dF as useRandom, dG as useRef, dH as useShieldStrength, dI as useState, dJ as useStatus, dK as useThreats, dL as useTick, dM as useTicksUntilReady, dN as useVelocity, dO as validateHookCall, dP as validateMissileConfig, dQ as withMissileContext, dR as withWizardContext, dS as wrapWithParams } from './index-browser-
|
|
1
|
+
import { F as FightResult, S as SimulateResult } from './index-browser-Dc-Vl1HI.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 BudgetExhaustion, x as BudgetLimits, C as CASTING_MOVEMENT_MULT, y as COLLISION_RADIUS, z as CastCancelEvent, D as CastStartEvent, E as Critter, G as DEFAULT_BUDGET, H as DEFAULT_FIGHT_BACKSTOP_MS, I as DEFAULT_SEED, J as Doombringer, K as ENGINE_VERSION, L as EQUIVALENT_ENGINE_VERSIONS, M as EnemyState, N as FIGHT_SPAWN_DISTANCES, O as FightBudget, P as FightStats, Q as FightWinner, R as FinalAction, T as Flamecaller, U as GCD_DURATION, V as GameConfig, W as GameState, X as Golem, Y as Hero, Z as Hogger, _ as HomingParams, $ as HookState, a0 as Infernalist, a1 as InternalWizardState, a2 as KNOCKBACK_DAMAGE_THRESHOLD, a3 as KNOCKBACK_DECAY, a4 as KNOCKBACK_DELAY, a5 as KNOCKBACK_SPEED_PER_DAMAGE, a6 as LAVA_BORDER_WIDTH, a7 as Lich, a8 as MATCH_DURATION, a9 as MAX_HEALTH, aa as MISSILE_BASE_CAST, ab as MISSILE_BASE_RADIUS, ac as MISSILE_DAMAGE_POWER, ad as MISSILE_DAMAGE_RADIUS_SCALE, ae as MISSILE_DAMAGE_SCALE, af as MISSILE_HOMING_COEFF, ag as MISSILE_MIN_CAST_TIME, ah as MISSILE_MIN_DAMAGE, ai as MISSILE_MIN_DURATION, aj as MISSILE_MIN_SPEED, ak as MISSILE_RADIUS_PER_DAMAGE, al as MISSILE_SPEED_DURATION_BASELINE, am as MISSILE_SPEED_DURATION_COEFF, an as MISSILE_TURN_DURATION_COEFF, ao as MOVEMENT_SPEED, ap as MOVE_SPEED, aq as ManualMatch, ar as ManualMatchInitOptions, as as ManualMatchOptions, at as ManualMatchStepRequest, au as MatchWinner, av as MissileAction, aw as MissileActions, ax as MissileConfig, ay as MissileContext, az as MissileExpiredEvent, aA as MissileFunction, aB as MissileHitEvent, aC as MissileLaunchEvent, aD as MissileOobEvent, aE as MissileTemplate, aF as Nightblade, aG as ParamDeclaration, aH as Position, aI as ProjectileState, aJ as Pyromancer, aK as RULES, aL as RULESET_RANGES, aM as RefObject, aN as Rookie, aO as SHIELD_CAST_TIME, aP as SHIELD_DECAY_PER_SECOND, aQ as SHIELD_DECAY_RATE, aR as SHIELD_MAX_BLOCK, aS as SHIELD_MAX_STRENGTH, aT as SHIELD_MIN_BLOCK, aU as SHIELD_MIN_STRENGTH, aV as SPAWN_DISTANCE, aW as SeekerParams, aX as Sentinel, aY as Shadowblade, aZ as ShieldBlockEvent, a_ as ShieldStartEvent, a$ as SimEvent, b0 as Spellbinder, b1 as Spellseeker, b2 as Spellshot, b3 as Spellspinner, b4 as Spelltracer, b5 as Spellweaver, b6 as SpiralParams, b7 as StepResult, b8 as Stormcaller, b9 as Stormchaser, ba as Stormforger, bb as StraightParams, bc as TICKS_PER_SECOND, bd as TICK_DURATION_MS, be as TargetDummy, bf as TestBotBuilder, bg as TestFightResult, bh as TestSimulateResult, bi as TraceBotSummary, bj as TraceEvent, bk as TraceEventType, bl as TraceSummary, bm as Turtle, bn as Velocity, bo as Voidblade, bp as WARMUP_DURATION_TOLERANCE, bq as WARMUP_MAX_BONUS, br as WARMUP_MAX_PENALTY, bs as WARMUP_SPEED_TOLERANCE, bt as WARMUP_TURN_TOLERANCE, bu as WIZARD_HEALTH, bv as WIZARD_RADIUS, bw as Warmage, bx as WizardActions, by as WizardContext, bz as WizardDeathEvent, bA as WizardEntry, bB as WizardFunction, bC as WizardGroup, bD as WizardLavaDeathEvent, bE as WizardState, bF as WorkerFactory, bG as WorkerLike, bH as aim, bI as analyzeThreats, bJ as angleDiff, bK as angleInRange, bL as angleTo, bM as applyDamage, bN as applyRulesetOverrides, bO as blink, bP as browserSandboxFight, bQ as browserSandboxSimulate, bR as calculateBlinkCooldown, bS as calculateMissileCastTime, bT as calculateMissileRadius, bU as calculateMissileSimilarity, bV as calculateShieldBlock, bW as calculateWarmupMultiplier, bX as canReplayMatch, bY as cancel, bZ as cancelCast, b_ as clampPositionToArena, b$ as clampToArena, c0 as clampToSafeZone, c1 as clearHooks, c2 as clearParamValues, c3 as completeCast, c4 as createBudgetState, c5 as createEntitySeed, c6 as createFightBudget, c7 as createInitialState, c8 as createRandom, c9 as createWorkerScript, ca as currentRuleset, cb as diagnoseTrace, cc as directionAway, cd as directionTo, ce as distanceTo, cf as effectiveTurnRateCost, cg as extractAction, ch as extractMissileAction, ci as extractStats, cj as extractTraceEvents, ck as fight, cl as findInRange, cm as findNearest, cn as fitMissileForEscapingTarget, co as fitMissileToBudget, cp as flyStraight, cq as formatDiagnosis, cr as formatStats, cs as formatTraceEvents, ct as formatTraceSummary, cu as generateCandidates, cv as generateCombos, cw as getAdaptiveMissileConfig, cx as getEffectiveRange, cy as getLeadPosition, cz as getMissileCastTime, cA as getMissileContext, cB as getPlayerState, cC as getWizardContext, cD as hashCombine, cE as homingMissile, cF as idle, cG as inRange, cH as interceptAngle, cI as isBannedBotName, cJ as isInLava, cK as magnitude, cL as matchesAnyBanRule, cM as mayAct, cN as missile, cO as move, cP as moveInDirection, cQ as moveProjectile, cR as moveTowardSafely, cS as moveWizard, cT as narrowRange, cU as nextRandom, cV as normalize, cW as normalizeAngle, cX as predictPosition, cY as recordSpend, cZ as resetAllHooks, c_ as resetRuleset, c$ as resolveWizardCollision, d0 as runWithHooks, d1 as runWizardWithContext, d2 as scoreFight, d3 as scoreFightAsWizard2, d4 as seekerMissile, d5 as setParamValues, d6 as shield, d7 as simulate, d8 as simulateMinDuration, d9 as sortByDistance, da as spiralMissile, db as startCast, dc as startDiscovery, dd as stopDiscovery, de as straightMissile, df as summarizeTrace, dg as sweptCircleCollision, dh as testBot, di as tick, dj as turnToAngle, dk as turnToward, dl as updateShield, dm as useArenaSize, dn as useBlinkCooldown, dp as useCastProgress, dq as useCastingSpell, dr as useClosestThreat, ds as useDamageDealt, dt as useDamageTaken, du as useEffect, dv as useEnemy, dw as useHealth, dx as useLastHitTick, dy as useLastMissileConfig, dz as useMemo, dA as useMyProjectiles, dB as useMyThreatsToEnemy, dC as useParam, dD as usePosition, dE as useProjectiles, dF as useRandom, dG as useRef, dH as useShieldStrength, dI as useState, dJ as useStatus, dK as useThreats, dL as useTick, dM as useTicksUntilReady, dN as useVelocity, dO as validateHookCall, dP as validateMissileConfig, dQ as withMissileContext, dR as withWizardContext, dS as wrapWithParams } from './index-browser-Dc-Vl1HI.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A compiled bot ready for sandboxed execution.
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -38,10 +38,24 @@ export const MAX_ERRORS_PER_MATCH = 500;
|
|
|
38
38
|
*/
|
|
39
39
|
export function describeThrown(thrown: unknown): string
|
|
40
40
|
{
|
|
41
|
-
|
|
41
|
+
const {text, alreadyBounded} = rawDescription(thrown);
|
|
42
|
+
// `alreadyBounded` exists because truncating twice destroyed the marker. The oversized-
|
|
43
|
+
// Error branch returns a string that is ALREADY capped and ends in
|
|
44
|
+
// "[stack omitted: message too large]"; running truncate() over that re-sliced to the
|
|
45
|
+
// first MAX_MESSAGE_LENGTH characters — all of them original message text — and cut the
|
|
46
|
+
// marker off every single time. The player was told "truncated, N more characters" and
|
|
47
|
+
// never that a stack had been dropped, which was the one thing the branch existed to say.
|
|
48
|
+
return alreadyBounded ? text : truncate(text);
|
|
42
49
|
}
|
|
43
50
|
|
|
44
|
-
|
|
51
|
+
/** A description plus whether it is already within the size cap and must not be re-cut. */
|
|
52
|
+
interface RawDescription
|
|
53
|
+
{
|
|
54
|
+
text: string;
|
|
55
|
+
alreadyBounded: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function rawDescription(thrown: unknown): RawDescription
|
|
45
59
|
{
|
|
46
60
|
if (thrown instanceof Error)
|
|
47
61
|
{
|
|
@@ -62,26 +76,26 @@ function rawDescription(thrown: unknown): string
|
|
|
62
76
|
{
|
|
63
77
|
// Deliberately NOT reading .stack here. The location is worth having, but not at
|
|
64
78
|
// the cost of letting a bot decide how much the engine allocates.
|
|
65
|
-
return truncate(message) + ' [stack omitted: message too large]';
|
|
79
|
+
return {text: truncate(message) + ' [stack omitted: message too large]', alreadyBounded: true};
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
// The stack's first line is "Error: message", so it carries both the message and the
|
|
69
83
|
// location — which is the whole point, since nothing else gives a player a line number.
|
|
70
84
|
const stack = typeof thrown.stack === 'string' ? stripBundleCoordinates(thrown.stack.trim()) : '';
|
|
71
|
-
if (stack) return stack;
|
|
72
|
-
return message || `${thrown.name || 'Error'} (thrown with no message)
|
|
85
|
+
if (stack) return {text: stack, alreadyBounded: false};
|
|
86
|
+
return {text: message || `${thrown.name || 'Error'} (thrown with no message)`, alreadyBounded: false};
|
|
73
87
|
}
|
|
74
88
|
|
|
75
|
-
if (typeof thrown === 'string') return thrown || '(empty string thrown)';
|
|
76
|
-
if (thrown === null) return 'null was thrown';
|
|
77
|
-
if (thrown === undefined) return 'undefined was thrown';
|
|
89
|
+
if (typeof thrown === 'string') return {text: thrown || '(empty string thrown)', alreadyBounded: false};
|
|
90
|
+
if (thrown === null) return {text: 'null was thrown', alreadyBounded: false};
|
|
91
|
+
if (thrown === undefined) return {text: 'undefined was thrown', alreadyBounded: false};
|
|
78
92
|
|
|
79
93
|
// A thrown object is a deliberate choice by the author — a custom error type, usually —
|
|
80
94
|
// so its fields are exactly what they wanted to see. String() would discard all of them.
|
|
81
95
|
try
|
|
82
96
|
{
|
|
83
97
|
const json = JSON.stringify(thrown);
|
|
84
|
-
if (typeof json === 'string' && json !== '{}') return json;
|
|
98
|
+
if (typeof json === 'string' && json !== '{}') return {text: json, alreadyBounded: false};
|
|
85
99
|
}
|
|
86
100
|
catch
|
|
87
101
|
{
|
|
@@ -96,12 +110,12 @@ function rawDescription(thrown: unknown): string
|
|
|
96
110
|
// returning it is a silent failure to describe. It happens when JSON.stringify refuses
|
|
97
111
|
// — a circular reference, or a getter that throws — and a player who sees it learns
|
|
98
112
|
// nothing at all. Say what we DO know instead: the constructor name and the keys.
|
|
99
|
-
if (text === '[object Object]') return describeOpaqueObject(thrown);
|
|
100
|
-
return text;
|
|
113
|
+
if (text === '[object Object]') return {text: describeOpaqueObject(thrown), alreadyBounded: false};
|
|
114
|
+
return {text, alreadyBounded: false};
|
|
101
115
|
}
|
|
102
116
|
catch
|
|
103
117
|
{
|
|
104
|
-
return '(a value was thrown that could not be described)';
|
|
118
|
+
return {text: '(a value was thrown that could not be described)', alreadyBounded: false};
|
|
105
119
|
}
|
|
106
120
|
}
|
|
107
121
|
|
|
@@ -152,7 +166,13 @@ function describeOpaqueObject(thrown: object): string
|
|
|
152
166
|
*/
|
|
153
167
|
function stripBundleCoordinates(stack: string): string
|
|
154
168
|
{
|
|
155
|
-
return stack
|
|
169
|
+
return stack
|
|
170
|
+
// `at helper (match-bundle.js:9108:16)` — the common case, name kept, coordinates gone.
|
|
171
|
+
.replace(/\s*\((?:[^()\s]*match-bundle\.js|<isolated-vm>)[^()]*\)/g, '')
|
|
172
|
+
// `at match-bundle.js:911:62` — V8 emits this for an ANONYMOUS frame, with no
|
|
173
|
+
// parentheses at all, so the version above never matched it and the bogus line number
|
|
174
|
+
// survived in precisely the case with no function name to make up for it.
|
|
175
|
+
.replace(/(\s*at )(?:[^()\s]*match-bundle\.js|<isolated-vm>)[^\s)]*/g, '$1<your bot>');
|
|
156
176
|
}
|
|
157
177
|
|
|
158
178
|
function truncate(message: string): string
|
package/src/engine/simulation.ts
CHANGED
|
@@ -160,6 +160,33 @@ function buildWizardContext(state: GameState, config: GameConfig, random: () =>
|
|
|
160
160
|
/**
|
|
161
161
|
* Process one game tick.
|
|
162
162
|
*/
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Name the first non-finite number a bot handed the engine this tick, or null.
|
|
166
|
+
*
|
|
167
|
+
* Checks the three doors that were each found separately: the move vector, the blink target,
|
|
168
|
+
* and the aim angle. Returns WHICH field so the message can point at the player's own call
|
|
169
|
+
* rather than saying "something went wrong".
|
|
170
|
+
*/
|
|
171
|
+
function nonFiniteField(
|
|
172
|
+
move: {x?: number; y?: number},
|
|
173
|
+
action: {startCast?: {spell?: string; target?: {x?: number; y?: number}}; aimDirection?: number},
|
|
174
|
+
wizard: {blinkTarget?: {x?: number; y?: number}},
|
|
175
|
+
): {what: string; value: string} | null
|
|
176
|
+
{
|
|
177
|
+
const bad = (v: unknown): boolean => typeof v === 'number' && !Number.isFinite(v);
|
|
178
|
+
if (bad(move?.x)) return {what: 'move(x, y): x', value: String(move.x)};
|
|
179
|
+
if (bad(move?.y)) return {what: 'move(x, y): y', value: String(move.y)};
|
|
180
|
+
|
|
181
|
+
const target = action.startCast?.spell === 'blink' ? action.startCast.target : undefined;
|
|
182
|
+
if (bad(target?.x)) return {what: 'blink(x, y): x', value: String(target!.x)};
|
|
183
|
+
if (bad(target?.y)) return {what: 'blink(x, y): y', value: String(target!.y)};
|
|
184
|
+
if (bad(wizard.blinkTarget?.x)) return {what: 'blink(x, y): x', value: String(wizard.blinkTarget!.x)};
|
|
185
|
+
if (bad(wizard.blinkTarget?.y)) return {what: 'blink(x, y): y', value: String(wizard.blinkTarget!.y)};
|
|
186
|
+
|
|
187
|
+
if (bad(action.aimDirection)) return {what: 'aim(degrees)', value: String(action.aimDirection)};
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
163
190
|
export function tick(
|
|
164
191
|
currentTick: number,
|
|
165
192
|
wizard1AI: WizardFunction,
|
|
@@ -430,6 +457,35 @@ export function tick(
|
|
|
430
457
|
// Handle movement (default to no movement if bot didn't provide move)
|
|
431
458
|
const oldPos = wizard.position;
|
|
432
459
|
const move = action.move ?? {x: 0, y: 0};
|
|
460
|
+
|
|
461
|
+
// The non-finite guards work, and were completely silent. A bot that divides by zero
|
|
462
|
+
// somewhere just stops moving; the guard saves the match but tells the author nothing,
|
|
463
|
+
// which is the same "looks passive, is broken" failure as an unreported throw — and
|
|
464
|
+
// reached by accident rather than by anything hostile.
|
|
465
|
+
//
|
|
466
|
+
// Reporting deliberately does NOT change behaviour: the clamp still stands and the
|
|
467
|
+
// match plays out identically. Throwing instead would alter results for bots already
|
|
468
|
+
// on the ladder and could feed crash-deactivation, which is far too large a
|
|
469
|
+
// consequence to attach to a diagnostic.
|
|
470
|
+
// The flag lives on the WIZARD, not in a Set here: tick() runs afresh every frame, so a
|
|
471
|
+
// Set declared inside it deduped nothing and the first version of this reported the
|
|
472
|
+
// same paragraph 500 times, straight into the error cap.
|
|
473
|
+
if (!wizard.advised?.nonFinite)
|
|
474
|
+
{
|
|
475
|
+
const bad = nonFiniteField(move, action, wizard);
|
|
476
|
+
if (bad)
|
|
477
|
+
{
|
|
478
|
+
wizard.advised = {...wizard.advised, nonFinite: true};
|
|
479
|
+
errors.push({
|
|
480
|
+
tick: nextTick,
|
|
481
|
+
entityId: wizard.id,
|
|
482
|
+
message: `${bad.what} is not a finite number (it was ${bad.value}). The engine ignored it `
|
|
483
|
+
+ 'to keep the match running — the value is treated as zero, so your wizard simply does '
|
|
484
|
+
+ 'not do what you asked. This is almost always a divide-by-zero or a subtraction of '
|
|
485
|
+
+ 'two equal positions somewhere in your maths. Reported once per match.',
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
433
489
|
wizard.position = moveWizard(wizard, move, 1);
|
|
434
490
|
|
|
435
491
|
// Process delayed knockback (apply impulse after delay expires)
|
|
@@ -500,6 +556,32 @@ export function tick(
|
|
|
500
556
|
startCast(wizard, castingSpell, castingSpell === 'missile' ? action.startCast.config : undefined);
|
|
501
557
|
if (castingSpell === 'missile')
|
|
502
558
|
{
|
|
559
|
+
// A cast longer than the whole match can never complete, so the wizard
|
|
560
|
+
// stands still until time runs out and NOTHING is reported. Every field is
|
|
561
|
+
// clamped by validateMissileConfig — nothing here is NaN — but turnRate is
|
|
562
|
+
// clamped only to `>= 0`, and turnRate 9999 prices the cast at 130,017
|
|
563
|
+
// ticks against a 30,000-tick match. Measured against TargetDummy, a bot
|
|
564
|
+
// that does nothing at all, the result was ten draws: the bot could not
|
|
565
|
+
// even lose in a way that hinted at the cause.
|
|
566
|
+
//
|
|
567
|
+
// The cast is still allowed — refusing it would silently change what bots
|
|
568
|
+
// do, and the defect was never the cast, it was the silence.
|
|
569
|
+
if ((wizard.castDuration ?? 0) > MATCH_DURATION && !wizard.advised?.unfinishableCast)
|
|
570
|
+
{
|
|
571
|
+
wizard.advised = {...wizard.advised, unfinishableCast: true};
|
|
572
|
+
const config = action.startCast.config;
|
|
573
|
+
errors.push({
|
|
574
|
+
tick: nextTick,
|
|
575
|
+
entityId: wizard.id,
|
|
576
|
+
message: `This missile takes ${wizard.castDuration} ticks to cast, but a match `
|
|
577
|
+
+ `is only ${MATCH_DURATION} ticks long, so the cast can never finish and this `
|
|
578
|
+
+ 'wizard will stand still for the rest of the match. The cost is dominated by '
|
|
579
|
+
+ `turnRate (${config?.turnRate ?? '?'}) — it is the most expensive field by far, and `
|
|
580
|
+
+ 'unlike the others it is not clamped to a sane maximum. Try a turnRate in the '
|
|
581
|
+
+ 'single digits, then use missile-calc or getMissileCastTime() to check the cast '
|
|
582
|
+
+ 'time before committing to a config.',
|
|
583
|
+
});
|
|
584
|
+
}
|
|
503
585
|
wizard.missileConfig = action.startCast.config;
|
|
504
586
|
wizard.missileAI = action.startCast.missileAI;
|
|
505
587
|
if (action.startCast.direction !== undefined)
|
package/src/engine/spells.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
TICKS_PER_SECOND,
|
|
5
5
|
calculateMissileCastTime,
|
|
6
6
|
calculateWarmupMultiplier,
|
|
7
|
+
validateMissileConfig,
|
|
7
8
|
} from '../rules.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -19,8 +20,19 @@ export function startCast(wizard: WizardState, spell: 'missile' | 'shield' | 'bl
|
|
|
19
20
|
// Stryker disable next-line ConditionalExpression: `true && config` equivalent for valid calls (spell is always correct type)
|
|
20
21
|
if (spell === 'missile' && config)
|
|
21
22
|
{
|
|
23
|
+
// VALIDATE FIRST. This used to pass the raw config, and the raw formula returns NaN for
|
|
24
|
+
// a config a beginner can easily type — `{damage: -999, speed: 0, duration: -5}` —
|
|
25
|
+
// because of the powers and divisions inside it. `Math.max(1, Math.round(NaN))` is NaN,
|
|
26
|
+
// not 1, so castDuration became NaN and `castProgress >= castDuration` was never true:
|
|
27
|
+
// the cast could NEVER complete and the wizard stood still for the whole match, silently.
|
|
28
|
+
// Measured against TargetDummy, a bot that does nothing: ten draws, zero errors.
|
|
29
|
+
//
|
|
30
|
+
// getMissileCastTime — the function bots call to PREDICT this exact number — has always
|
|
31
|
+
// validated first, so the engine and its own prediction disagreed for precisely the
|
|
32
|
+
// configs where a player most needed them to agree.
|
|
33
|
+
const validated = validateMissileConfig(config);
|
|
22
34
|
// calculateMissileCastTime includes warmup when lastMissileConfig is passed
|
|
23
|
-
const castTimeSec = calculateMissileCastTime(
|
|
35
|
+
const castTimeSec = calculateMissileCastTime(validated, wizard.lastMissileConfig);
|
|
24
36
|
wizard.castDuration = Math.max(1, Math.round(castTimeSec * TICKS_PER_SECOND));
|
|
25
37
|
// Store warmup multiplier for visualization
|
|
26
38
|
wizard.warmupMultiplier = calculateWarmupMultiplier(wizard.lastMissileConfig, config);
|
package/src/engine-version.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Used to gate spectator replays: a recorded match can only be re-simulated when
|
|
10
10
|
* the runtime engine version matches the version that produced the match.
|
|
11
11
|
*/
|
|
12
|
-
export const ENGINE_VERSION =
|
|
12
|
+
export const ENGINE_VERSION = 4256327861353618;
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Historical versions that denote the SAME engine as `ENGINE_VERSION`.
|
|
@@ -279,12 +279,47 @@ export function flyStraight(): MissileAction
|
|
|
279
279
|
};
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Name what a bot handed back, for an error message it can act on.
|
|
284
|
+
*
|
|
285
|
+
* Named specifically rather than by bare typeof: "a object" is both ungrammatical and
|
|
286
|
+
* unhelpful, and the realistic version of this mistake is `async function MyBot()`, where the
|
|
287
|
+
* value is a Promise and the fix is to remove the `async`.
|
|
288
|
+
*/
|
|
289
|
+
function describeReturned(value: unknown): string
|
|
290
|
+
{
|
|
291
|
+
if (value === null) return 'null';
|
|
292
|
+
if (value === undefined) return 'undefined';
|
|
293
|
+
if (Array.isArray(value)) return 'an array';
|
|
294
|
+
if (typeof value === 'object')
|
|
295
|
+
{
|
|
296
|
+
if ('then' in value && typeof (value as {then?: unknown}).then === 'function')
|
|
297
|
+
{
|
|
298
|
+
return 'a Promise — your function is `async`, and it must not be: remove the async keyword';
|
|
299
|
+
}
|
|
300
|
+
return 'a plain object';
|
|
301
|
+
}
|
|
302
|
+
return `a ${typeof value}`;
|
|
303
|
+
}
|
|
304
|
+
|
|
282
305
|
/**
|
|
283
306
|
* Extract MissileActions from a MissileAction.
|
|
284
307
|
* Used by the engine to get the actual missile action.
|
|
285
308
|
*/
|
|
286
309
|
export function extractMissileAction(action: MissileAction): MissileActions
|
|
287
310
|
{
|
|
311
|
+
// The wizard-level path got a readable message for a bad return; this one did not, and a
|
|
312
|
+
// missile AI is the SECOND function a player writes. Returning `{}` from it produced
|
|
313
|
+
// "TypeError: action._toMissileAction is not a function" over an engine-internal stack —
|
|
314
|
+
// naming a private field that appears nowhere in the API, 84 times in one match.
|
|
315
|
+
if (action === null || action === undefined || typeof action._toMissileAction !== 'function')
|
|
316
|
+
{
|
|
317
|
+
throw new Error(
|
|
318
|
+
`Your missile AI returned ${describeReturned(action)}. Return turnToward(x, y), `
|
|
319
|
+
+ 'turnToAngle(degrees) or flyStraight(). The missile AI runs once per tick for every '
|
|
320
|
+
+ 'missile you have in flight, and it must return one of those every time.',
|
|
321
|
+
);
|
|
322
|
+
}
|
|
288
323
|
return action._toMissileAction();
|
|
289
324
|
}
|
|
290
325
|
|
|
@@ -312,21 +347,6 @@ export function extractAction(finalAction: FinalAction): WizardActions
|
|
|
312
347
|
// Named specifically rather than by bare typeof: "a object" is both ungrammatical and
|
|
313
348
|
// unhelpful, and the realistic version of this mistake is `async function MyBot()`,
|
|
314
349
|
// where the value is a Promise and the fix is to remove the `async`.
|
|
315
|
-
const describeReturned = (value: unknown): string =>
|
|
316
|
-
{
|
|
317
|
-
if (value === null) return 'null';
|
|
318
|
-
if (value === undefined) return 'undefined';
|
|
319
|
-
if (Array.isArray(value)) return 'an array';
|
|
320
|
-
if (typeof value === 'object')
|
|
321
|
-
{
|
|
322
|
-
if ('then' in value && typeof value.then === 'function')
|
|
323
|
-
{
|
|
324
|
-
return 'a Promise — your bot function is `async`, and it must not be: remove the async keyword';
|
|
325
|
-
}
|
|
326
|
-
return 'a plain object';
|
|
327
|
-
}
|
|
328
|
-
return `a ${typeof value}`;
|
|
329
|
-
};
|
|
330
350
|
const got = describeReturned(finalAction);
|
|
331
351
|
throw new Error(
|
|
332
352
|
`Your bot returned ${got}. Return one of: idle(), move(x, y), missile(config, ai, angle), `
|
package/src/hooks/state-hooks.ts
CHANGED
|
@@ -366,8 +366,12 @@ export function useThreats(): AnalyzedThreat[]
|
|
|
366
366
|
ctx.projectiles,
|
|
367
367
|
ctx.myProjectiles,
|
|
368
368
|
ticksUntilReady,
|
|
369
|
+
// Mid-CAST you are one cancel() away from being able to shield, so the analysis
|
|
370
|
+
// must not charge you the whole remainder of the cast. A GCD cannot be cancelled,
|
|
371
|
+
// so it keeps the pessimistic reading.
|
|
372
|
+
{canCancelCurrentCast: ctx.state === 'casting'},
|
|
369
373
|
);
|
|
370
|
-
}, [ctx.projectiles, ctx.myProjectiles, ctx.position.x, ctx.position.y, ticksUntilReady]);
|
|
374
|
+
}, [ctx.projectiles, ctx.myProjectiles, ctx.position.x, ctx.position.y, ticksUntilReady, ctx.state]);
|
|
371
375
|
}
|
|
372
376
|
|
|
373
377
|
/**
|
|
@@ -19,6 +19,14 @@ import {distanceTo} from '../utils/distance.js';
|
|
|
19
19
|
import {angleTo, normalizeAngle, angleDiff} from '../utils/angles.js';
|
|
20
20
|
import type {AnalyzedThreat} from './types.js';
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Ticks lost to cancel() before a new cast can begin.
|
|
24
|
+
*
|
|
25
|
+
* The guide states it: cancel() "returns you to idle immediately and costs NO cooldown, but
|
|
26
|
+
* it takes effect after this tick's cast check — so you can shield on the NEXT tick".
|
|
27
|
+
*/
|
|
28
|
+
const CANCEL_COST_TICKS = 1;
|
|
29
|
+
|
|
22
30
|
// Maximum distance at which a missile is still tracked as a potential threat
|
|
23
31
|
// (even if simulation says it will miss — homing missiles can change course)
|
|
24
32
|
const THREAT_RELEVANCE_DISTANCE = 500;
|
|
@@ -30,6 +38,9 @@ const THREAT_RELEVANCE_DISTANCE = 500;
|
|
|
30
38
|
* @param projectiles - All projectiles in the game
|
|
31
39
|
* @param myProjectiles - Only the bot's own projectiles (used for filtering)
|
|
32
40
|
* @param ticksUntilReady - Ticks until wizard can start a new action
|
|
41
|
+
* @param options - `canCancelCurrentCast` when the wizard is mid-CAST and could cancel()
|
|
42
|
+
* out of it for one tick's cost. Defaults to false, which is the safe
|
|
43
|
+
* reading for a caller that does not know its own state.
|
|
33
44
|
* @returns Array of analyzed threats sorted by ticksToImpact (soonest first)
|
|
34
45
|
*/
|
|
35
46
|
export function analyzeThreats(
|
|
@@ -37,6 +48,7 @@ export function analyzeThreats(
|
|
|
37
48
|
projectiles: ProjectileState[],
|
|
38
49
|
myProjectiles: ProjectileState[],
|
|
39
50
|
ticksUntilReady: number,
|
|
51
|
+
options?: {canCancelCurrentCast?: boolean},
|
|
40
52
|
): AnalyzedThreat[]
|
|
41
53
|
{
|
|
42
54
|
// Filter to enemy projectiles by excluding our own
|
|
@@ -48,7 +60,7 @@ export function analyzeThreats(
|
|
|
48
60
|
|
|
49
61
|
for (const projectile of enemyProjectiles)
|
|
50
62
|
{
|
|
51
|
-
const analysis = analyzeOneThreat(myPos, projectile, ticksUntilReady);
|
|
63
|
+
const analysis = analyzeOneThreat(myPos, projectile, ticksUntilReady, options?.canCancelCurrentCast ?? false);
|
|
52
64
|
// Only include if it will hit or missile is within reasonable distance
|
|
53
65
|
const missileDistance = Math.sqrt(
|
|
54
66
|
(projectile.position.x - myPos.x) ** 2 +
|
|
@@ -74,6 +86,7 @@ function analyzeOneThreat(
|
|
|
74
86
|
targetPos: Position,
|
|
75
87
|
projectile: ProjectileState,
|
|
76
88
|
ticksUntilReady: number,
|
|
89
|
+
canCancelCurrentCast: boolean,
|
|
77
90
|
): AnalyzedThreat
|
|
78
91
|
{
|
|
79
92
|
// Ask the ENGINE for the collision radius rather than re-deriving it.
|
|
@@ -123,13 +136,22 @@ function analyzeOneThreat(
|
|
|
123
136
|
);
|
|
124
137
|
}
|
|
125
138
|
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
//
|
|
132
|
-
|
|
139
|
+
// Shield timing. These two fields MUST be satisfiable together: a bot acts on
|
|
140
|
+
// `canBlockInTime && ticksToStartShield === 0`, so if they can never both hold the bot
|
|
141
|
+
// never shields. They used to disagree — canBlockInTime charged the wizard the whole
|
|
142
|
+
// remainder of its cast while ticksToStartShield ignored the cast entirely — and mid-cast
|
|
143
|
+
// that made them mutually exclusive. Measured cost: zero shields raised in a full match.
|
|
144
|
+
//
|
|
145
|
+
// The pessimism was wrong as well as inconsistent. cancel() leaves a CAST for one tick's
|
|
146
|
+
// cost, so a casting wizard's real delay is 1, not the couple of hundred ticks remaining.
|
|
147
|
+
// A GCD is different: nothing can be started during it and it cannot be cancelled, so
|
|
148
|
+
// there the full ticksUntilReady genuinely does apply.
|
|
149
|
+
const delayBeforeShield = canCancelCurrentCast ? CANCEL_COST_TICKS : ticksUntilReady;
|
|
150
|
+
|
|
151
|
+
// One tick of slack, so the window is not a single exact frame.
|
|
152
|
+
const ticksNeededToBlock = delayBeforeShield + RULES.SHIELD_CAST_TIME + 1;
|
|
153
|
+
const canBlockInTime = ticksToImpact >= ticksNeededToBlock;
|
|
154
|
+
const ticksToStartShield = Math.max(0, ticksToImpact - ticksNeededToBlock);
|
|
133
155
|
|
|
134
156
|
return {
|
|
135
157
|
id: projectile.id,
|
package/src/types.ts
CHANGED
|
@@ -44,6 +44,7 @@ export interface WizardState
|
|
|
44
44
|
lastMissileConfig?: MissileConfig; // last fired missile (for warmup system)
|
|
45
45
|
warmupMultiplier?: number; // warmup multiplier applied to most recent missile cast (0.80 = full bonus, 1.20 = full penalty)
|
|
46
46
|
invincible?: boolean; // manual play mode: damage is ignored when true
|
|
47
|
+
advised?: {nonFinite?: boolean; unfinishableCast?: boolean}; // engine advisories already given, so each is reported once per match
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
/**
|