@vibemancer/core 0.1.4 → 0.1.5

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.
Files changed (74) hide show
  1. package/README.md +2 -2
  2. package/dist/chunk-W7HWJFQ4.js +10599 -0
  3. package/dist/chunk-W7HWJFQ4.js.map +1 -0
  4. package/dist/index-browser.d.ts +1300 -1051
  5. package/dist/index-browser.js +55 -17
  6. package/dist/index.d.ts +53 -3
  7. package/dist/index.js +200 -54
  8. package/dist/index.js.map +1 -1
  9. package/package.json +21 -15
  10. package/src/bots/Hero.ts +867 -0
  11. package/src/bots/berserker/01_Stormchaser.ts +162 -120
  12. package/src/bots/berserker/02_Stormcaller.ts +160 -116
  13. package/src/bots/berserker/03_Stormforger.ts +162 -129
  14. package/src/bots/caster/01_Flamecaller.ts +126 -84
  15. package/src/bots/caster/02_Pyromancer.ts +148 -101
  16. package/src/bots/caster/03_Infernalist.ts +180 -160
  17. package/src/bots/defensive/01_Turtle.ts +48 -44
  18. package/src/bots/defensive/02_Sentinel.ts +57 -53
  19. package/src/bots/defensive/03_Golem.ts +85 -97
  20. package/src/bots/duelist/01_Battlemage.ts +157 -122
  21. package/src/bots/duelist/02_Warmage.ts +166 -121
  22. package/src/bots/duelist/03_Archmage.ts +198 -178
  23. package/src/bots/homing/01_Bonemancer.ts +20 -32
  24. package/src/bots/homing/02_Lich.ts +145 -93
  25. package/src/bots/homing/03_Archlich.ts +129 -60
  26. package/src/bots/kiter/01_Spellspinner.ts +145 -107
  27. package/src/bots/kiter/02_Spellweaver.ts +153 -105
  28. package/src/bots/kiter/03_Spellbinder.ts +168 -123
  29. package/src/bots/melee/01_Shadowblade.ts +131 -75
  30. package/src/bots/melee/02_Nightblade.ts +174 -130
  31. package/src/bots/melee/03_Voidblade.ts +266 -168
  32. package/src/bots/registry.ts +44 -37
  33. package/src/bots/shared.ts +185 -25
  34. package/src/bots/sniper/01_Spellshot.ts +151 -98
  35. package/src/bots/sniper/02_Spelltracer.ts +173 -128
  36. package/src/bots/sniper/03_Spellseeker.ts +177 -152
  37. package/src/bots/standalone/Critter.ts +46 -52
  38. package/src/bots/standalone/Doombringer.ts +36 -40
  39. package/src/bots/standalone/Hogger.ts +120 -89
  40. package/src/bots/standalone/Rookie.ts +21 -23
  41. package/src/bots/standalone/TargetDummy.ts +5 -6
  42. package/src/bots/test/cheater.ts +91 -85
  43. package/src/bots/test/crasher.ts +26 -28
  44. package/src/engine/bundle-fight.ts +242 -0
  45. package/src/engine/hooks-runtime.ts +58 -18
  46. package/src/engine/manual-match.ts +33 -25
  47. package/src/engine/missile-templates.ts +25 -43
  48. package/src/engine/optimizer.ts +7 -7
  49. package/src/engine/params-runtime.ts +3 -3
  50. package/src/engine/physics.ts +33 -23
  51. package/src/engine/sandbox-browser.ts +8 -7
  52. package/src/engine/sandbox-compile.ts +1 -1
  53. package/src/engine/sandbox-harness.ts +299 -367
  54. package/src/engine/sandbox.ts +3 -3
  55. package/src/engine/simulation.ts +291 -76
  56. package/src/engine/spells.ts +9 -12
  57. package/src/engine-version.ts +1 -1
  58. package/src/hooks/action-builders.ts +76 -21
  59. package/src/hooks/index.ts +17 -9
  60. package/src/hooks/state-hooks.ts +61 -25
  61. package/src/hooks/threat-analysis.ts +44 -20
  62. package/src/hooks/types.ts +62 -5
  63. package/src/index.ts +2 -0
  64. package/src/rules.ts +209 -63
  65. package/src/stats.ts +2 -2
  66. package/src/testing.ts +6 -30
  67. package/src/trace.ts +63 -3
  68. package/src/types.ts +127 -14
  69. package/src/utils/angles.ts +1 -0
  70. package/src/utils/combat.ts +98 -6
  71. package/src/utils/spatial.ts +3 -3
  72. package/dist/chunk-74FFJCFF.js +0 -9140
  73. package/dist/chunk-74FFJCFF.js.map +0 -1
  74. package/src/hooks/bot-wrapper.ts +0 -84
@@ -1,3 +1,178 @@
1
+ /**
2
+ * VIBEMANCER - HOOKS API TYPES
3
+ *
4
+ * Types for the hooks-based bot API.
5
+ *
6
+ * UNITS REFERENCE (100 ticks = 1 second):
7
+ * Position: absolute world coordinates, 0-800 on each axis (800×800 arena)
8
+ * Velocity: units per tick on each axis (player max speed = 1 u/t)
9
+ * Health: hit points (max 60)
10
+ * Ticks: game ticks (10ms each, 100/sec). Divide by 100 for seconds.
11
+ * Angles: degrees (0°=right, 90°=down, 180°=left, 270°=up)
12
+ */
13
+
14
+ /**
15
+ * Enemy wizard state as seen by your bot.
16
+ *
17
+ * Note: you cannot see the enemy's missile configs, cooldown timers, or
18
+ * damage history — only what's visible on the battlefield.
19
+ */
20
+ interface EnemyState {
21
+ /** Enemy position in world coordinates (0-800). */
22
+ position: Position;
23
+ /** Enemy velocity in units/tick. */
24
+ velocity: Velocity;
25
+ /** Enemy current HP (0-60). */
26
+ health: number;
27
+ /** Enemy status: 'idle', 'casting', 'channeling' (shield), or 'gcd_locked'. */
28
+ status: 'idle' | 'casting' | 'channeling' | 'gcd_locked';
29
+ /** Which spell enemy is casting, or null. */
30
+ castingSpell: 'missile' | 'shield' | 'blink' | null;
31
+ /** Cast progress in ticks (0 if not casting). */
32
+ castProgress: number;
33
+ /** Total cast duration in ticks (0 if not casting). */
34
+ castDuration: number;
35
+ /** Remaining GCD ticks (0 if not in GCD). */
36
+ gcdRemaining: number;
37
+ /** Duration the enemy has been channeling in ticks (0 if not channeling). */
38
+ channelDuration: number;
39
+ /** Enemy shield block multiplier (0 if not shielding, 0.3-0.9 if shielding). */
40
+ shieldStrength: number;
41
+ }
42
+ /**
43
+ * Pre-computed analysis of an incoming enemy projectile.
44
+ *
45
+ * All timing values are in ticks (100 ticks = 1 second).
46
+ * Dodge directions are relative to missile heading, not world axes.
47
+ */
48
+ interface AnalyzedThreat {
49
+ /** Unique projectile ID. */
50
+ id: string;
51
+ /** Raw projectile state (position, rotation in degrees, speed in u/t, turnRate, remainingTicks). */
52
+ projectile: ProjectileState;
53
+ /** Ticks until missile hits your current position. Infinity if predicted to miss. */
54
+ ticksToImpact: number;
55
+ /** Whether the missile will hit if you stand still. */
56
+ willHit: boolean;
57
+ /** Whether strafing left (perpendicular to missile heading) avoids it. */
58
+ canDodgeLeft: boolean;
59
+ /** Whether strafing right (perpendicular to missile heading) avoids it. */
60
+ canDodgeRight: boolean;
61
+ /** Whether moving directly away from the missile avoids it. */
62
+ canOutrun: boolean;
63
+ /** Optimal dodge direction as a unit vector {x, y}, or null if undodgeable. */
64
+ bestDodgeDirection: Position | null;
65
+ /** Whether you can channel shield before the missile arrives. */
66
+ canBlockInTime: boolean;
67
+ /** Ticks from now when you should START channeling shield to block in time. */
68
+ ticksToStartShield: number;
69
+ }
70
+ /**
71
+ * Final action that can be returned from a bot.
72
+ * Cannot be further chained.
73
+ */
74
+ interface FinalAction {
75
+ /** Internal: extract the WizardActions */
76
+ readonly _toAction: () => WizardActions;
77
+ }
78
+ /**
79
+ * Action builder that allows chaining .move() for simultaneous movement.
80
+ * Returned by shield(), missile(), and cancel().
81
+ */
82
+ interface ActionBuilder extends FinalAction {
83
+ /**
84
+ * Add movement to this action (e.g., move while casting).
85
+ * Direction vector, not absolute position. Auto-normalized.
86
+ * Positive X = right, positive Y = down.
87
+ */
88
+ move(x: number, y: number): FinalAction;
89
+ }
90
+ /**
91
+ * Wizard function type for the hooks API.
92
+ * Called every tick. Read state with hooks, return an action.
93
+ */
94
+ type WizardFunction = () => FinalAction;
95
+ /**
96
+ * Internal context for game state hooks.
97
+ */
98
+ interface WizardContext {
99
+ entityId: string;
100
+ tick: number;
101
+ position: Position;
102
+ velocity: Velocity;
103
+ health: number;
104
+ maxHealth: number;
105
+ state: 'idle' | 'casting' | 'channeling' | 'gcd_locked';
106
+ castingSpell?: 'missile' | 'shield' | 'blink';
107
+ castProgress?: number;
108
+ castDuration?: number;
109
+ channelingSpell?: 'shield';
110
+ channelDuration?: number;
111
+ gcdRemaining?: number;
112
+ blinkCooldown: number;
113
+ lastMissileConfig?: MissileConfig;
114
+ enemies: Array<{
115
+ id: string;
116
+ position: Position;
117
+ velocity: Velocity;
118
+ health: number;
119
+ state: 'idle' | 'casting' | 'channeling' | 'gcd_locked';
120
+ castingSpell?: 'missile' | 'shield' | 'blink';
121
+ castProgress?: number;
122
+ castDuration?: number;
123
+ gcdRemaining?: number;
124
+ channelingSpell?: 'shield';
125
+ channelDuration?: number;
126
+ }>;
127
+ projectiles: ProjectileState[];
128
+ myProjectiles: ProjectileState[];
129
+ arenaWidth: number;
130
+ arenaHeight: number;
131
+ damageDealt: number;
132
+ damageTaken: number;
133
+ lastHitTick: number;
134
+ random: () => number;
135
+ }
136
+ /**
137
+ * Context available to missile AI functions via getMissileContext().
138
+ * Provides missile state, the full game state from the owner's perspective,
139
+ * and a seeded PRNG.
140
+ */
141
+ interface MissileContext {
142
+ /** Missile position in world coordinates. */
143
+ position: Position;
144
+ /** Missile heading in degrees (0=right, 90=down). */
145
+ rotation: number;
146
+ /** Missile speed in units/tick. */
147
+ speed: number;
148
+ /** Missile turn rate in degrees/tick. */
149
+ turnRate: number;
150
+ /** Missile damage on hit. */
151
+ damage: number;
152
+ /** Ticks remaining before the missile expires. */
153
+ remainingTicks: number;
154
+ /** ID of the wizard who owns this missile. */
155
+ ownerId: string;
156
+ /** Full game state from the missile owner's perspective. */
157
+ worldState: GameState;
158
+ /** Seeded PRNG [0, 1). Deterministic per missile per tick. */
159
+ random: () => number;
160
+ }
161
+ /**
162
+ * Action returned by a missile AI function.
163
+ * Call turnToward(x, y) to steer, or flyStraight() to coast.
164
+ */
165
+ interface MissileAction {
166
+ /** Internal: extract the MissileActions */
167
+ readonly _toMissileAction: () => MissileActions;
168
+ }
169
+ /**
170
+ * Missile AI function type for the hooks API.
171
+ * Called every tick for each in-flight missile.
172
+ * Read state with getMissileContext(), return a MissileAction.
173
+ */
174
+ type MissileFunction$1 = () => MissileAction;
175
+
1
176
  /**
2
177
  * VIBEMANCER - TYPES
3
178
  *
@@ -90,6 +265,8 @@ interface GameState {
90
265
  damageDealt: number;
91
266
  damageTaken: number;
92
267
  lastHitTick: number;
268
+ /** Events emitted during the most recent tick (empty for the initial state). */
269
+ events: SimEvent[];
93
270
  }
94
271
  /**
95
272
  * Actions returned by wizard each tick.
@@ -116,7 +293,7 @@ interface WizardActions {
116
293
  startCast?: {
117
294
  spell: 'missile';
118
295
  config: MissileConfig;
119
- missileAI: MissileAIFunction;
296
+ missileAI: MissileFunction;
120
297
  direction?: number;
121
298
  } | {
122
299
  spell: 'shield';
@@ -134,6 +311,22 @@ interface WizardActions {
134
311
  * Only applies while state === 'casting' and castingSpell === 'missile'.
135
312
  */
136
313
  aimDirection?: number;
314
+ /**
315
+ * Missile guide — manual play mode only. Takes direct control of a
316
+ * specific missile using WASD/joystick input. While active, the wizard
317
+ * is invulnerable and frozen (move ignored), and the missile steers
318
+ * using its turnRate toward the given direction.
319
+ *
320
+ * direction: {x, y} → converted to target angle via atan2(y, x)
321
+ * direction: null → missile flies straight (no steering input)
322
+ */
323
+ missileGuide?: {
324
+ missileId: string;
325
+ direction: {
326
+ x: number;
327
+ y: number;
328
+ } | null;
329
+ } | null;
137
330
  }
138
331
  /**
139
332
  * Missile configuration.
@@ -145,80 +338,184 @@ interface MissileConfig {
145
338
  duration: number;
146
339
  }
147
340
  /**
148
- * Missile AI function type.
341
+ * Missile AI function type — re-exported from hooks/types.ts.
149
342
  */
150
- type MissileAIFunction = (props: {
151
- missileState: ProjectileState;
152
- worldState: GameState;
153
- random: () => number;
154
- }) => MissileActions;
343
+ type MissileFunction = MissileFunction$1;
155
344
  /**
156
345
  * Actions returned by missile each tick.
157
346
  */
158
347
  interface MissileActions {
159
348
  turnToward?: Position;
349
+ turnToAngle?: number;
160
350
  }
161
- /**
162
- * Main wizard function type.
163
- */
164
- type WizardFunction = (props: {
165
- state: GameState;
166
- config: GameConfig;
167
- random: () => number;
168
- }) => WizardActions;
351
+ interface MissileHitEvent {
352
+ type: 'missile-hit';
353
+ position: Position;
354
+ damage: number;
355
+ actualDamage: number;
356
+ speed: number;
357
+ ownerId: string;
358
+ targetId: string;
359
+ }
360
+ interface MissileExpiredEvent {
361
+ type: 'missile-expired';
362
+ position: Position;
363
+ }
364
+ interface MissileOobEvent {
365
+ type: 'missile-oob';
366
+ position: Position;
367
+ }
368
+ interface BlinkEvent {
369
+ type: 'blink';
370
+ wizardId: string;
371
+ from: Position;
372
+ to: Position;
373
+ }
374
+ interface ShieldStartEvent {
375
+ type: 'shield-start';
376
+ wizardId: string;
377
+ position: Position;
378
+ }
379
+ interface CastStartEvent {
380
+ type: 'cast-start';
381
+ wizardId: string;
382
+ position: Position;
383
+ spell: 'missile' | 'shield' | 'blink';
384
+ }
385
+ interface WizardDeathEvent {
386
+ type: 'wizard-death';
387
+ wizardId: string;
388
+ position: Position;
389
+ }
390
+ interface WizardLavaDeathEvent {
391
+ type: 'wizard-lava-death';
392
+ wizardId: string;
393
+ position: Position;
394
+ }
395
+ interface CastCancelEvent {
396
+ type: 'cast-cancel';
397
+ wizardId: string;
398
+ position: Position;
399
+ spell: 'missile' | 'shield' | 'blink';
400
+ }
401
+ interface MissileLaunchEvent {
402
+ type: 'missile-launch';
403
+ position: Position;
404
+ damage: number;
405
+ speed: number;
406
+ ownerId: string;
407
+ rotation: number;
408
+ }
409
+ interface ShieldBlockEvent {
410
+ type: 'shield-block';
411
+ wizardId: string;
412
+ position: Position;
413
+ damageBlocked: number;
414
+ damageThrough: number;
415
+ }
416
+ type SimEvent = MissileHitEvent | MissileExpiredEvent | MissileOobEvent | MissileLaunchEvent | BlinkEvent | ShieldStartEvent | ShieldBlockEvent | CastStartEvent | CastCancelEvent | WizardDeathEvent | WizardLavaDeathEvent;
169
417
 
170
418
  /**
171
419
  * VIBEMANCER - GAME RULES
172
420
  *
173
421
  * This is the single source of truth for all game constants.
174
422
  * All game logic imports from here. Read this to understand the game.
423
+ *
424
+ * SWEEPABLE CONSTANTS — the balance-search ruleset:
425
+ * Every combat-balance constant lives on the mutable `RULES` object. The engine
426
+ * and bots read `RULES.X` so the balance-search optimizer can override any of
427
+ * them at runtime via applyRulesetOverrides() — object property reads are live
428
+ * across every module and survive bundling (unlike a reassigned `let`, which
429
+ * esbuild/Vite snapshot at the import site). Structural constants (arena size,
430
+ * tick rate, hitbox radius, missile floors) stay plain `const`.
431
+ *
432
+ * The UPPER_CASE named exports below (WIZARD_HEALTH, SHIELD_CAST_TIME, …) are
433
+ * default SNAPSHOTS for external/UI/MCP consumers that only need the factory
434
+ * value. They do NOT track overrides — anything that must respond to a sweep
435
+ * reads `RULES.X`.
175
436
  */
176
437
 
177
438
  declare const TICKS_PER_SECOND = 100;
178
439
  declare const TICK_DURATION_MS = 10;
179
- declare const WIZARD_HEALTH = 60;
440
+ /**
441
+ * The mutable ruleset: the single source of truth for every sweepable combat
442
+ * constant. Engine + bots read these via `RULES.X`. Override with
443
+ * applyRulesetOverrides(); restore with resetRuleset().
444
+ */
445
+ declare const RULES: {
446
+ WIZARD_HEALTH: number;
447
+ MOVEMENT_SPEED: number;
448
+ CASTING_MOVEMENT_MULT: number;
449
+ GCD_DURATION: number;
450
+ SHIELD_CAST_TIME: number;
451
+ SHIELD_MAX_BLOCK: number;
452
+ SHIELD_DECAY_PER_SECOND: number;
453
+ SHIELD_MIN_BLOCK: number;
454
+ BLINK_CAST_TIME: number;
455
+ BLINK_RANGE: number;
456
+ BLINK_MAX_COOLDOWN: number;
457
+ BLINK_MIN_COOLDOWN: number;
458
+ KNOCKBACK_DAMAGE_THRESHOLD: number;
459
+ KNOCKBACK_SPEED_PER_DAMAGE: number;
460
+ KNOCKBACK_DELAY: number;
461
+ KNOCKBACK_DECAY: number;
462
+ MISSILE_MIN_CAST_TIME: number;
463
+ MISSILE_BASE_RADIUS: number;
464
+ MISSILE_DAMAGE_RADIUS_SCALE: number;
465
+ MISSILE_BASE_CAST: number;
466
+ MISSILE_DAMAGE_SCALE: number;
467
+ MISSILE_DAMAGE_POWER: number;
468
+ MISSILE_HOMING_COEFF: number;
469
+ MISSILE_TURN_DURATION_COEFF: number;
470
+ MISSILE_SPEED_DURATION_BASELINE: number;
471
+ MISSILE_SPEED_DURATION_COEFF: number;
472
+ WARMUP_MAX_BONUS: number;
473
+ WARMUP_MAX_PENALTY: number;
474
+ };
475
+ declare const WIZARD_HEALTH: number;
180
476
  declare const WIZARD_RADIUS = 5;
181
- declare const MOVEMENT_SPEED = 1;
182
- declare const CASTING_MOVEMENT_MULT = 0.5;
183
- declare const GCD_DURATION = 100;
184
- declare const SHIELD_CAST_TIME = 20;
185
- declare const SHIELD_MAX_BLOCK = 0.9;
186
- declare const SHIELD_DECAY_PER_SECOND = 0.2;
187
- declare const SHIELD_MIN_BLOCK = 0.3;
188
- declare const BLINK_CAST_TIME = 10;
189
- declare const BLINK_RANGE = 300;
190
- declare const BLINK_MAX_COOLDOWN = 2000;
191
- declare const BLINK_MIN_COOLDOWN = 100;
477
+ declare const MOVEMENT_SPEED: number;
478
+ declare const CASTING_MOVEMENT_MULT: number;
479
+ declare const GCD_DURATION: number;
480
+ declare const SHIELD_CAST_TIME: number;
481
+ declare const SHIELD_MAX_BLOCK: number;
482
+ declare const SHIELD_DECAY_PER_SECOND: number;
483
+ declare const SHIELD_MIN_BLOCK: number;
484
+ declare const BLINK_CAST_TIME: number;
485
+ declare const BLINK_RANGE: number;
486
+ declare const BLINK_MAX_COOLDOWN: number;
487
+ declare const BLINK_MIN_COOLDOWN: number;
192
488
  /** @deprecated Use BLINK_MAX_COOLDOWN */
193
- declare const BLINK_COOLDOWN = 2000;
194
- declare const ARENA_WIDTH = 800;
195
- declare const ARENA_HEIGHT = 800;
196
- declare const ARENA_SIZE = 800;
489
+ declare const BLINK_COOLDOWN: number;
490
+ declare const KNOCKBACK_DAMAGE_THRESHOLD: number;
491
+ declare const KNOCKBACK_SPEED_PER_DAMAGE: number;
492
+ declare const KNOCKBACK_DELAY: number;
493
+ declare const KNOCKBACK_DECAY: number;
494
+ declare const ARENA_SIZE = 860;
495
+ declare const LAVA_BORDER_WIDTH = 30;
496
+ declare const ARENA_MIN = 30;
497
+ declare const ARENA_MAX: number;
197
498
  declare const SPAWN_DISTANCE = 600;
198
499
  declare const MISSILE_MIN_DAMAGE = 1;
199
500
  declare const MISSILE_MIN_SPEED = 1.5;
200
501
  declare const MISSILE_MIN_DURATION = 10;
201
- declare const MISSILE_MIN_CAST_TIME = 0.1;
202
- declare const MISSILE_BASE_RADIUS = 2;
203
- declare const MISSILE_DAMAGE_RADIUS_SCALE = 0.1;
502
+ declare const MISSILE_MIN_CAST_TIME: number;
503
+ declare const MISSILE_BASE_RADIUS: number;
504
+ declare const MISSILE_DAMAGE_RADIUS_SCALE: number;
204
505
  /**
205
506
  * Calculate missile hitbox radius based on damage.
206
507
  */
207
508
  declare function calculateMissileRadius(damage: number): number;
208
- declare const MISSILE_BASE_CAST = 0.1;
209
- declare const MISSILE_DAMAGE_SCALE = 0.226;
509
+ declare const MISSILE_BASE_CAST: number;
510
+ declare const MISSILE_DAMAGE_SCALE: number;
210
511
  declare const MISSILE_DAMAGE_POWER: number;
211
- declare const MISSILE_HOMING_COEFF = 0.12;
212
- declare const MISSILE_TURN_DURATION_COEFF = 0.1;
213
- declare const MISSILE_SPEED_DURATION_BASELINE = 1.5;
214
- declare const MISSILE_SPEED_DURATION_COEFF = 0.025;
512
+ declare const MISSILE_HOMING_COEFF: number;
513
+ declare const MISSILE_TURN_DURATION_COEFF: number;
514
+ declare const MISSILE_SPEED_DURATION_BASELINE: number;
515
+ declare const MISSILE_SPEED_DURATION_COEFF: number;
215
516
  /**
216
517
  * Maps turnRate to effective cost for the cast time formula.
217
- * - Positive: linear (unchanged behavior)
218
- * - Negative: diminishing returns via -|t|/(1+|t|), saturating at -1.
219
- * turnRate -0.5 → -0.33, -1 → -0.5, -5 → -0.83, -10 → -0.91
220
- * Big cast speed gains from 0 to -1, worthwhile to -5, negligible after.
221
- * No hard floor — the curve naturally caps the benefit.
518
+ * Higher turn rate = more expensive cast. Negative values clamped to 0.
222
519
  */
223
520
  declare function effectiveTurnRateCost(turnRate: number): number;
224
521
  /**
@@ -236,15 +533,15 @@ declare function validateMissileConfig(config: MissileConfig): MissileConfig;
236
533
  * + 0.10 × (effectiveTurnRateCost(turnRate) × durationSeconds)
237
534
  * + 0.025 × (speed × durationSeconds - 1.5)
238
535
  *
239
- * effectiveTurnRateCost: linear for positive, -ln(1+|t|) for negative (diminishing returns).
536
+ * turnRate is clamped to >= 0. Cost is linear.
240
537
  *
241
538
  * If lastMissileConfig is a MissileConfig, applies warmup multiplier:
242
539
  * - Similar to previous: up to 20% faster
243
540
  * - Very different: up to 20% slower (switching penalty)
244
541
  */
245
542
  declare function calculateMissileCastTime(config: MissileConfig, lastMissileConfig?: MissileConfig | undefined | null): number;
246
- declare const WARMUP_MAX_BONUS = 0.2;
247
- declare const WARMUP_MAX_PENALTY = 0.2;
543
+ declare const WARMUP_MAX_BONUS: number;
544
+ declare const WARMUP_MAX_PENALTY: number;
248
545
  declare const WARMUP_SPEED_TOLERANCE = 3;
249
546
  declare const WARMUP_TURN_TOLERANCE = 1;
250
547
  declare const WARMUP_DURATION_TOLERANCE = 50;
@@ -268,20 +565,35 @@ declare function calculateMissileSimilarity(prev: MissileConfig | undefined, cur
268
565
  */
269
566
  declare function calculateWarmupMultiplier(prev: MissileConfig | undefined, current: MissileConfig): number;
270
567
  declare const MATCH_DURATION = 30000;
271
- declare const MAX_HEALTH = 60;
568
+ declare const MAX_HEALTH: number;
272
569
  declare const COLLISION_RADIUS = 5;
273
- declare const MOVE_SPEED = 1;
274
- declare const MISSILE_RADIUS_PER_DAMAGE = 0.1;
275
- declare const SHIELD_MAX_STRENGTH = 0.9;
276
- declare const SHIELD_MIN_STRENGTH = 0.3;
277
- declare const SHIELD_DECAY_RATE = 0.2;
278
- declare const BLINK_MAX_RANGE = 300;
570
+ declare const MOVE_SPEED: number;
571
+ declare const MISSILE_RADIUS_PER_DAMAGE: number;
572
+ declare const SHIELD_MAX_STRENGTH: number;
573
+ declare const SHIELD_MIN_STRENGTH: number;
574
+ declare const SHIELD_DECAY_RATE: number;
575
+ declare const BLINK_MAX_RANGE: number;
279
576
  /**
280
577
  * Calculate blink cooldown based on distance traveled.
281
578
  * Short blinks get short cooldowns, full-range blinks get the maximum.
282
579
  */
283
580
  declare function calculateBlinkCooldown(distance: number): number;
284
581
  declare const ARENA_WATER_BUFFER = 200;
582
+ /**
583
+ * Search bounds for each sweepable constant. The current value is the start.
584
+ * Every key here is read by the engine/bots through `RULES.X`, so overrides
585
+ * take effect across the whole simulation.
586
+ */
587
+ declare const RULESET_RANGES: Record<string, {
588
+ min: number;
589
+ max: number;
590
+ }>;
591
+ /** Merge overrides onto the RULES object and apply them globally. */
592
+ declare function applyRulesetOverrides(overrides: Record<string, number>): void;
593
+ /** Restore all swept constants to their factory defaults. */
594
+ declare function resetRuleset(): void;
595
+ /** Current value of every sweepable constant (the optimizer's starting point). */
596
+ declare function currentRuleset(): Record<string, number>;
285
597
 
286
598
  /**
287
599
  * Engine version — milliseconds since the Unix epoch.
@@ -293,11 +605,11 @@ declare const ARENA_WATER_BUFFER = 200;
293
605
  * Used to gate spectator replays: a recorded match can only be re-simulated when
294
606
  * the runtime engine version matches the version that produced the match.
295
607
  */
296
- declare const ENGINE_VERSION = 1777687052301;
608
+ declare const ENGINE_VERSION = 1781975653307;
297
609
 
298
610
  interface InternalWizardState extends WizardState {
299
611
  missileConfig?: MissileConfig;
300
- missileAI?: MissileAIFunction;
612
+ missileAI?: MissileFunction$1;
301
613
  blinkTarget?: {
302
614
  x: number;
303
615
  y: number;
@@ -305,6 +617,11 @@ interface InternalWizardState extends WizardState {
305
617
  damageDealt: number;
306
618
  damageTaken: number;
307
619
  lastHitTick: number;
620
+ knockbackVx?: number;
621
+ knockbackVy?: number;
622
+ knockbackDelay?: number;
623
+ knockbackPendingVx?: number;
624
+ knockbackPendingVy?: number;
308
625
  }
309
626
  /**
310
627
  * Initialize a new match state.
@@ -313,10 +630,11 @@ declare function createInitialState(_seed: number, spawnDist?: number): GameStat
313
630
  /**
314
631
  * Process one game tick.
315
632
  */
316
- declare function tick(currentTick: number, wizard1AI: WizardFunction, wizard2AI: WizardFunction, config: GameConfig, wizards: InternalWizardState[], projectiles: ProjectileState[], missileAIs: Map<string, MissileAIFunction>, matchSeed: number): {
633
+ declare function tick(currentTick: number, wizard1AI: WizardFunction, wizard2AI: WizardFunction, config: GameConfig, wizards: InternalWizardState[], projectiles: ProjectileState[], missileAIs: Map<string, MissileFunction$1>, matchSeed: number): {
317
634
  nextTick: number;
318
635
  wizards: InternalWizardState[];
319
636
  projectiles: ProjectileState[];
637
+ events: SimEvent[];
320
638
  errors: BotError[];
321
639
  };
322
640
  /**
@@ -324,7 +642,7 @@ declare function tick(currentTick: number, wizard1AI: WizardFunction, wizard2AI:
324
642
  * Returns a deep clone to prevent mutation of history entries.
325
643
  * Used for history recording where independent snapshots are needed.
326
644
  */
327
- declare function getPlayerState(playerIndex: number, wizards: InternalWizardState[], projectiles: ProjectileState[], tick: number): GameState;
645
+ declare function getPlayerState(playerIndex: number, wizards: InternalWizardState[], projectiles: ProjectileState[], tick: number, events?: SimEvent[]): GameState;
328
646
  /** Winner of a single match: a wizard ID, 'draw' (simultaneous kill), or null (timeout). */
329
647
  type MatchWinner = 'wizard-1' | 'wizard-2' | 'draw' | null;
330
648
  /** Winner of a fight (aggregate): a wizard ID or 'draw'. Never null. */
@@ -395,190 +713,76 @@ declare function simulate(wizard1AI: WizardFunction, wizard2AI: WizardFunction,
395
713
  }): SimulateResult;
396
714
 
397
715
  /**
398
- * VIBEMANCER - HOOKS API TYPES
716
+ * VIBEMANCER - HOOKS RUNTIME
399
717
  *
400
- * Types for the hooks-based bot API.
718
+ * This file implements a minimal React-like hooks runtime for AI programming.
719
+ * It supports useState, useEffect, useMemo, useRef, and useParam with entity isolation.
401
720
  *
402
- * UNITS REFERENCE (100 ticks = 1 second):
403
- * Position: absolute world coordinates, 0-800 on each axis (800×800 arena)
404
- * Velocity: units per tick on each axis (player max speed = 1 u/t)
405
- * Health: hit points (max 60)
406
- * Ticks: game ticks (10ms each, 100/sec). Divide by 100 for seconds.
407
- * Angles: degrees (0°=right, 90°=down, 180°=left, 270°=up)
721
+ * RULES OF HOOKS (same as React):
722
+ * - Hooks must be called at the top level of the bot function
723
+ * - Hooks must be called in the same order every tick
724
+ * - Hooks must NOT be called conditionally
725
+ *
726
+ * Violating these rules throws an error (detected via hook index validation).
408
727
  */
409
728
 
729
+ interface HookState {
730
+ values: unknown[];
731
+ effects: {
732
+ callback: () => void | (() => void);
733
+ deps?: unknown[];
734
+ cleanup?: () => void;
735
+ }[];
736
+ memos: {
737
+ value: unknown;
738
+ deps?: unknown[];
739
+ }[];
740
+ /** Type of each hook call in order (for validation). */
741
+ hookTypes: string[];
742
+ /** Total hooks called on first successful tick. */
743
+ hookCount: number;
744
+ /** Whether the first tick has completed successfully (hook pattern established). */
745
+ initialized: boolean;
746
+ }
410
747
  /**
411
- * Enemy wizard state as seen by your bot.
748
+ * Validate a hook call and return its sequential index.
749
+ * Ensures hooks are called in the same order every tick.
412
750
  *
413
- * Note: you cannot see the enemy's missile configs, cooldown timers, or
414
- * damage history only what's visible on the battlefield.
751
+ * On the first tick: records the hook type at this index.
752
+ * On subsequent ticks: validates the hook type matches.
753
+ *
754
+ * @param type - The hook type name (e.g., 'useState', 'useEffect', 'useParam')
755
+ * @returns The sequential hook index
756
+ * @throws If called outside runWithHooks or if hook order changed
415
757
  */
416
- interface EnemyState {
417
- /** Enemy position in world coordinates (0-800). */
418
- position: Position;
419
- /** Enemy velocity in units/tick. */
420
- velocity: Velocity;
421
- /** Enemy current HP (0-60). */
422
- health: number;
423
- /** Enemy status: 'idle', 'casting', 'channeling' (shield), or 'gcd_locked'. */
424
- status: 'idle' | 'casting' | 'channeling' | 'gcd_locked';
425
- /** Which spell enemy is casting, or null. */
426
- castingSpell: 'missile' | 'shield' | 'blink' | null;
427
- /** Enemy shield block multiplier (0 if not shielding, 0.3-0.9 if shielding). */
428
- shieldStrength: number;
429
- }
758
+ declare function validateHookCall(type: string): number;
430
759
  /**
431
- * Pre-computed analysis of an incoming enemy projectile.
432
- *
433
- * All timing values are in ticks (100 ticks = 1 second).
434
- * Dodge directions are relative to missile heading, not world axes.
435
- */
436
- interface AnalyzedThreat {
437
- /** Unique projectile ID. */
438
- id: string;
439
- /** Raw projectile state (position, rotation in degrees, speed in u/t, turnRate, remainingTicks). */
440
- projectile: ProjectileState;
441
- /** Ticks until missile hits your current position. Infinity if predicted to miss. */
442
- ticksToImpact: number;
443
- /** Whether the missile will hit if you stand still. */
444
- willHit: boolean;
445
- /** Whether strafing left (perpendicular to missile heading) avoids it. */
446
- canDodgeLeft: boolean;
447
- /** Whether strafing right (perpendicular to missile heading) avoids it. */
448
- canDodgeRight: boolean;
449
- /** Whether moving directly away from the missile avoids it. */
450
- canOutrun: boolean;
451
- /** Optimal dodge direction as a unit vector {x, y}, or null if undodgeable. */
452
- bestDodgeDirection: Position | null;
453
- /** Whether you can channel shield before the missile arrives. */
454
- canBlockInTime: boolean;
455
- /** Ticks from now when you should START channeling shield to block in time. */
456
- ticksToStartShield: number;
457
- }
458
- /**
459
- * Final action that can be returned from a bot.
460
- * Cannot be further chained.
461
- */
462
- interface FinalAction {
463
- /** Internal: extract the WizardActions */
464
- readonly _toAction: () => WizardActions;
465
- }
466
- /**
467
- * Action builder that allows chaining .move() for simultaneous movement.
468
- * Returned by shield(), missile(), and cancel().
469
- */
470
- interface ActionBuilder extends FinalAction {
471
- /**
472
- * Add movement to this action (e.g., move while casting).
473
- * Direction vector, not absolute position. Auto-normalized.
474
- * Positive X = right, positive Y = down.
475
- */
476
- move(x: number, y: number): FinalAction;
477
- }
478
- /**
479
- * Bot function type for the hooks API.
480
- * Called every tick. Read state with hooks, return an action.
481
- */
482
- type BotFunction = () => FinalAction;
483
- /**
484
- * Internal context for game state hooks.
485
- */
486
- interface BotContext {
487
- entityId: string;
488
- tick: number;
489
- position: Position;
490
- velocity: Velocity;
491
- health: number;
492
- maxHealth: number;
493
- state: 'idle' | 'casting' | 'channeling' | 'gcd_locked';
494
- castingSpell?: 'missile' | 'shield' | 'blink';
495
- castProgress?: number;
496
- castDuration?: number;
497
- channelingSpell?: 'shield';
498
- channelDuration?: number;
499
- gcdRemaining?: number;
500
- blinkCooldown: number;
501
- enemies: Array<{
502
- id: string;
503
- position: Position;
504
- velocity: Velocity;
505
- health: number;
506
- state: 'idle' | 'casting' | 'channeling' | 'gcd_locked';
507
- castingSpell?: 'missile' | 'shield' | 'blink';
508
- channelingSpell?: 'shield';
509
- channelDuration?: number;
510
- }>;
511
- projectiles: ProjectileState[];
512
- myProjectiles: ProjectileState[];
513
- arenaWidth: number;
514
- arenaHeight: number;
515
- damageDealt: number;
516
- damageTaken: number;
517
- lastHitTick: number;
518
- }
519
-
520
- /**
521
- * VIBEMANCER - HOOKS RUNTIME
522
- *
523
- * This file implements a minimal React-like hooks runtime for AI programming.
524
- * It supports useState, useEffect, useMemo, useRef, and useParam with entity isolation.
525
- *
526
- * RULES OF HOOKS (same as React):
527
- * - Hooks must be called at the top level of the bot function
528
- * - Hooks must be called in the same order every tick
529
- * - Hooks must NOT be called conditionally
530
- *
531
- * Violating these rules throws an error (detected via hook index validation).
532
- */
533
-
534
- interface HookState {
535
- values: unknown[];
536
- effects: {
537
- callback: () => void | (() => void);
538
- deps?: unknown[];
539
- cleanup?: () => void;
540
- }[];
541
- memos: {
542
- value: unknown;
543
- deps?: unknown[];
544
- }[];
545
- /** Type of each hook call in order (for validation). */
546
- hookTypes: string[];
547
- /** Total hooks called on first successful tick. */
548
- hookCount: number;
549
- /** Whether the first tick has completed successfully (hook pattern established). */
550
- initialized: boolean;
551
- }
552
- /**
553
- * Validate a hook call and return its sequential index.
554
- * Ensures hooks are called in the same order every tick.
555
- *
556
- * On the first tick: records the hook type at this index.
557
- * On subsequent ticks: validates the hook type matches.
558
- *
559
- * @param type - The hook type name (e.g., 'useState', 'useEffect', 'useParam')
560
- * @returns The sequential hook index
561
- * @throws If called outside runWithHooks or if hook order changed
562
- */
563
- declare function validateHookCall(type: string): number;
564
- /**
565
- * Run a function with a specific entity's hook context.
760
+ * Run a function with a specific entity's hook context.
566
761
  */
567
762
  declare function runWithHooks<T>(entityId: string, fn: () => T): T;
568
763
  /**
569
764
  * Run a bot function with full context (game state + persistence hooks).
570
765
  * Use this when you need to set BOTH the entity ID and the game context.
571
766
  */
572
- declare function runBotWithContext<T>(entityId: string, context: BotContext, fn: () => T): T;
767
+ declare function runWizardWithContext<T>(entityId: string, context: WizardContext, fn: () => T): T;
573
768
  /**
574
769
  * Run a function with game state context only, preserving the current entity ID.
575
- * Use this inside wrapNewBot where the entity ID is already set by the outer runWithHooks.
770
+ * Used by the simulation to set up WizardContext before calling hooks-style bots.
576
771
  */
577
- declare function withBotContext<T>(context: BotContext, fn: () => T): T;
772
+ declare function withWizardContext<T>(context: WizardContext, fn: () => T): T;
578
773
  /**
579
774
  * Get the current bot context. Throws if called outside bot execution.
580
775
  */
581
- declare function getBotContext(): BotContext;
776
+ declare function getWizardContext(): WizardContext;
777
+ /**
778
+ * Run a function with missile context set, preserving the current entity ID.
779
+ * Used by the simulation to set up MissileContext before calling hooks-style missile AIs.
780
+ */
781
+ declare function withMissileContext<T>(context: MissileContext, fn: () => T): T;
782
+ /**
783
+ * Get the current missile context. Throws if called outside missile AI execution.
784
+ */
785
+ declare function getMissileContext(): MissileContext;
582
786
  /**
583
787
  * Persist state between ticks.
584
788
  */
@@ -611,12 +815,8 @@ declare function resetAllHooks(): void;
611
815
  /**
612
816
  * Move a wizard based on world-space input direction.
613
817
  *
614
- * Movement model (from design doc):
615
- * - x: +100 = right, -100 = left
616
- * - y: +100 = down, -100 = up
617
- * - Values are clamped to [-100, 100]
618
- * - Diagonal movement is normalized (magnitude capped at 100)
619
- * - No rotation tracking - just output (x, y) direction
818
+ * Any vector works: direction is preserved, speed is clamped to [0, 1].
819
+ * (0.5, 0) = half speed right. (300, 200) = full speed at 33.7°.
620
820
  */
621
821
  declare function moveWizard(wizard: WizardState, move: {
622
822
  x: number;
@@ -627,9 +827,15 @@ declare function moveWizard(wizard: WizardState, move: {
627
827
  */
628
828
  declare function moveProjectile(projectile: ProjectileState, deltaTicks: number): Position;
629
829
  /**
630
- * Clamp a position to the arena boundaries.
830
+ * Clamp a position to the full arena bounds (0-860).
831
+ * No playfield clamping — wizards CAN walk/blink into lava.
631
832
  */
632
833
  declare function clampToArena(position: Position, radius: number): Position;
834
+ /**
835
+ * Check if a position is in the lava zone (outside the playfield).
836
+ * Lava zones: [0, ARENA_MIN] and [ARENA_MAX, ARENA_SIZE] on each axis.
837
+ */
838
+ declare function isInLava(position: Position, radius: number): boolean;
633
839
  /**
634
840
  * Resolve body collision between two wizards.
635
841
  * Pushes both apart equally so they don't overlap. Neither is blocked — they just can't stack.
@@ -686,7 +892,7 @@ declare function applyDamage(wizard: WizardState, damage: number, _projectile?:
686
892
  * Extra capabilities over `simulate()`:
687
893
  * - step(N) advances N ticks at a time (default 1) — caller controls pacing
688
894
  * - replaceMissileAI(id, ai) hot-swaps a missile's AI mid-flight (used by
689
- * the missile-hijack feature in manual play)
895
+ * the missile-guide feature in manual play)
690
896
  * - setInvincible(wizardIndex, on) toggles damage immunity per wizard
691
897
  */
692
898
 
@@ -709,12 +915,14 @@ declare class ManualMatch {
709
915
  private wizards;
710
916
  private projectiles;
711
917
  private missileAIs;
712
- /** First-replacement originals for hijacked missiles. Used by restoreMissileAI. */
918
+ /** First-replacement originals for guided missiles. Used by restoreMissileAI. */
713
919
  private originalMissileAIs;
714
920
  private currentTick;
715
921
  private done;
922
+ private deathTick;
716
923
  private allErrors;
717
924
  private history;
925
+ private lastTickEvents;
718
926
  constructor(wizard1AI: WizardFunction, wizard2AI: WizardFunction, options?: ManualMatchOptions);
719
927
  /**
720
928
  * Advance the match by `count` ticks (default 1). Stops early if the
@@ -722,17 +930,17 @@ declare class ManualMatch {
722
930
  */
723
931
  step(count?: number): StepResult;
724
932
  /**
725
- * Hot-swap a missile's AI function. Used by the missile-hijack feature
933
+ * Hot-swap a missile's AI function. Used by the missile-guide feature
726
934
  * in manual play. The first replacement remembers the original AI so
727
935
  * `restoreMissileAI` can put it back. Subsequent replacements update
728
936
  * the active AI but leave the remembered original alone.
729
937
  *
730
938
  * No-op if the projectile id doesn't exist.
731
939
  */
732
- replaceMissileAI(projectileId: string, ai: MissileAIFunction): void;
940
+ replaceMissileAI(projectileId: string, ai: MissileFunction$1): void;
733
941
  /**
734
- * Restore a previously hijacked missile's original AI function.
735
- * No-op if the projectile id doesn't exist or was never hijacked.
942
+ * Restore a previously guided missile's original AI function.
943
+ * No-op if the projectile id doesn't exist or was never guided.
736
944
  */
737
945
  restoreMissileAI(projectileId: string): void;
738
946
  /**
@@ -773,7 +981,6 @@ interface TurningParams extends BaseParams {
773
981
  }
774
982
  type StraightParams = BaseParams;
775
983
  type HomingParams = TurningParams;
776
- type AntiHomingParams = TurningParams;
777
984
  interface SpiralParams extends BaseParams {
778
985
  spiralRadius: number;
779
986
  spiralFreq: number;
@@ -783,7 +990,7 @@ interface SeekerParams extends TurningParams {
783
990
  }
784
991
  interface MissileTemplate {
785
992
  config: MissileConfig;
786
- ai: MissileAIFunction;
993
+ ai: MissileFunction$1;
787
994
  }
788
995
  /**
789
996
  * Fire-and-forget missile. No steering — flies in a straight line.
@@ -793,11 +1000,6 @@ declare function straightMissile(p: StraightParams): MissileTemplate;
793
1000
  * Homing missile. Steers toward the enemy each tick.
794
1001
  */
795
1002
  declare function homingMissile(p: HomingParams): MissileTemplate;
796
- /**
797
- * Anti-homing missile. Steers AWAY from the enemy — useful as a feint or
798
- * area-denial pattern.
799
- */
800
- declare function antiHomingMissile(p: AntiHomingParams): MissileTemplate;
801
1003
  /**
802
1004
  * Spiral missile. Continuously orbits its current heading while advancing.
803
1005
  * Uses missileState.remainingTicks as a deterministic phase counter so the
@@ -979,1041 +1181,1087 @@ declare function getAdaptiveMissileConfig(targetVelocity: Position, distance: nu
979
1181
  * If `lastMissileConfig` is provided, accounts for warmup bonus: similar
980
1182
  * missiles cast faster, so more damage can fit in the same budget.
981
1183
  */
1184
+ /**
1185
+ * Simulate a missile trajectory to find the minimum duration (ticks) needed
1186
+ * to reach a target at the given distance. Works for all turnRate values:
1187
+ * positive (homing) and zero (straight).
1188
+ *
1189
+ * The simulation starts the missile aimed directly at the target and steps
1190
+ * through the trajectory tick by tick. For homing, the missile tracks the
1191
+ * target each tick matching the engine's steering physics.
1192
+ */
1193
+ /**
1194
+ * Simulate a missile trajectory to find the minimum duration (ticks) needed
1195
+ * to reach a target at the given distance. Works for straight (turnRate=0)
1196
+ * and homing (turnRate>0) missiles.
1197
+ *
1198
+ * The missile starts aimed directly at the target at (dist, 0) and steps
1199
+ * through the trajectory tick by tick. For homing, the missile tracks the
1200
+ * target each tick matching the engine's steering physics.
1201
+ *
1202
+ * Returns 500 if the missile cannot reach the target within 500 ticks.
1203
+ */
1204
+ declare function simulateMinDuration(speed: number, turnRateDeg: number, dist: number, collisionRadius?: number): number;
982
1205
  declare function fitMissileToBudget(budgetTicks: number, distance: number, options?: {
983
1206
  minTurnRate?: number;
984
1207
  maxDamage?: number;
985
1208
  lastMissileConfig?: MissileConfig;
986
1209
  }): MissileConfig | null;
987
-
988
1210
  /**
989
- * Bot: TargetDummy
990
- *
991
- * BEHAVIOR: Does absolutely nothing. No movement, no spells, no AI.
1211
+ * Fit a missile config that accounts for the enemy escaping during cast time.
992
1212
  *
993
- * NAMING RATIONALE: "Target Dummy" is universal MMO player vocabulary for the
994
- * practice objects found in capital cities. Every WoW/FFXIV player has beaten
995
- * on a target dummy to test DPS rotations. That's exactly what this bot is —
996
- * a punching bag for testing missile mechanics and baseline damage output.
997
- * Nobody calls them "training dummies"; the player term is always "target dummy."
1213
+ * During casting, the caster moves at CASTING_MOVEMENT_MULT speed while
1214
+ * the enemy moves at full MOVEMENT_SPEED. This means the effective distance
1215
+ * at launch is larger than the current distance. This function iteratively
1216
+ * converges on a missile config whose range covers the escape distance.
998
1217
  *
999
- * STANDALONE no tier progression. It's a test fixture, not a combatant.
1218
+ * @param currentDistance - Current distance to enemy
1219
+ * @param budgetTicks - Maximum cast time budget in ticks
1220
+ * @param options - Same options as fitMissileToBudget, plus:
1221
+ * - enemyApproaching: if true, enemy is moving toward caster (reduces escape)
1222
+ * - distanceBuffer: flat units added to target distance for safety margin (default 20)
1223
+ * - maxIterations: convergence iterations (default 5)
1000
1224
  */
1001
- declare const TargetDummy: WizardFunction;
1225
+ declare function fitMissileForEscapingTarget(currentDistance: number, budgetTicks: number, options?: {
1226
+ minTurnRate?: number;
1227
+ maxDamage?: number;
1228
+ lastMissileConfig?: MissileConfig;
1229
+ enemyApproaching?: boolean;
1230
+ distanceBuffer?: number;
1231
+ maxIterations?: number;
1232
+ }): MissileConfig | null;
1002
1233
 
1003
1234
  /**
1004
- * Bot: Rookie
1005
- *
1006
- * BEHAVIOR: Stands perfectly still and fires straight (non-homing) missiles at
1007
- * the enemy. No movement, no dodging, no shielding. Knows one spell and uses
1008
- * it on cooldown. The wizard equivalent of an FPS player who stands in the open
1009
- * and holds left-click.
1010
- *
1011
- * NAMING RATIONALE: "Rookie" is the universal term for a first-timer who barely
1012
- * knows what they're doing. This bot is a day-one player who learned how to cast
1013
- * missile and nothing else. No movement, no defense, just raw "I press the button."
1014
- * We considered "Noob" (more accurate) but Rookie is less abrasive while conveying
1015
- * the same thing — a beginner who doesn't know any better.
1016
- *
1017
- * STANDALONE — no tier progression. Rookies either learn to play a real class
1018
- * or quit. This bot represents the rock-bottom of "at least it shoots."
1235
+ * Get a seeded random number generator. Returns a function that produces
1236
+ * deterministic values in [0, 1) — same seed + same tick = same sequence.
1237
+ * Use this instead of Math.random() so replays are deterministic.
1019
1238
  */
1020
- declare const Rookie: WizardFunction;
1021
-
1239
+ declare function useRandom(): () => number;
1022
1240
  /**
1023
- * Bot: Critter
1024
- *
1025
- * BEHAVIOR: Picks random valid actions each tick — random movement, random spells,
1026
- * random missile configs, random directions. Occasionally cancels its own casts.
1027
- * Uses engine-provided seeded random for deterministic behavior. Useful for
1028
- * finding edge cases in the engine, but completely useless in combat.
1029
- *
1030
- * NAMING RATIONALE: In WoW, critters are the 1-HP ambient mobs (rabbits, squirrels,
1031
- * prairie dogs) that wander around doing nothing useful and die to literally anything.
1032
- * This bot is the wizard equivalent — it flails around randomly and gets destroyed by
1033
- * anyone with a plan. The word "Critter" immediately tells any gamer "this thing is
1034
- * helpless and exists only to fill space."
1035
- *
1036
- * STANDALONE — no tier progression. Critters don't level up. However, a future
1037
- * "Hogger" bot could be an elite critter: same chaotic spirit but actually dangerous
1038
- * (like the famous WoW elite that wipes unprepared lowbies).
1241
+ * Get your current health (0-60). Wizard dies at 0.
1039
1242
  */
1040
- declare const Critter: WizardFunction;
1041
-
1243
+ declare function useHealth(): number;
1042
1244
  /**
1043
- * Bot: Hogger
1044
- *
1045
- * BEHAVIOR: The elite critter. Chaotic and unpredictable but genuinely dangerous.
1046
- * Randomly varies missile configs each cast (damage 7-15, speed 3-8, random homing),
1047
- * moves erratically but still somewhat toward/away from the enemy, shields when
1048
- * in real danger, and blinks unpredictably. The randomness makes Hogger hard to
1049
- * predict — you never know if the next missile will be a slow tracker or a fast
1050
- * snipe. Unlike Critter's pure randomness, Hogger has enough combat awareness
1051
- * to actually win fights.
1052
- *
1053
- * NAMING RATIONALE: In WoW, Hogger is the iconic level 11 elite gnoll in Elwynn
1054
- * Forest who infamously kills unprepared lowbies. He's technically a basic mob
1055
- * but hits way harder than expected. This bot is the Critter that learned to
1056
- * fight — still chaotic, still a bit dumb, but capable of ending you if you
1057
- * underestimate it. "Hogger" is one of WoW's most recognizable references and
1058
- * perfectly captures "deceptively dangerous chaos."
1059
- *
1060
- * STANDALONE — no tier progression. There's only one Hogger.
1245
+ * Get your current position as {x, y} in world coordinates (0-800).
1246
+ * Position is clamped to [5, 795] (arena bounds minus wizard radius).
1061
1247
  */
1062
- declare const Hogger: WizardFunction;
1063
-
1248
+ declare function usePosition(): Position;
1064
1249
  /**
1065
- * Bot: Doombringer
1066
- *
1067
- * BEHAVIOR: Fires a single maximum-damage homing missile with infinite budget.
1068
- * No damage cap — goes for the biggest possible hit. Exists as a benchmark to
1069
- * demonstrate why lower-damage + shield play is superior. Has basic shield
1070
- * defense but no sophisticated tactics. One fat cast, one fat hit.
1071
- *
1072
- * STANDALONE — no tier progression. Benchmark/test bot.
1250
+ * Get your current velocity as {x, y} in units/tick.
1251
+ * Max magnitude is 1 u/t when idle, 0.5 u/t when casting, 0 when channeling shield.
1073
1252
  */
1074
- declare const Doombringer: WizardFunction;
1075
-
1076
- declare const Turtle: WizardFunction;
1077
-
1253
+ declare function useVelocity(): Velocity;
1078
1254
  /**
1079
- * Bot: Sentinel
1080
- *
1081
- * BEHAVIOR: Stationary tank with last-moment shielding AND two-tier offense.
1082
- * Like Turtle, never moves and shields at the last moment. Unlike Turtle,
1083
- * fires bigger missiles (damage 20) when the safe window is large enough,
1084
- * falling back to Turtle's fast missile (damage 12) when pressed.
1255
+ * Get your current status:
1256
+ * - 'idle': free to act
1257
+ * - 'casting': casting a spell (missile or blink). Can move at 50% speed.
1258
+ * - 'channeling': channeling shield. Cannot move. Cancel anytime with cancel().
1259
+ * - 'gcd_locked': global cooldown after spell. Can move at full speed, but cannot cast.
1260
+ */
1261
+ declare function useStatus(): 'idle' | 'casting' | 'channeling' | 'gcd_locked';
1262
+ /**
1263
+ * Get ticks until you can start a new spell.
1085
1264
  *
1086
- * PROGRESSION LINE: Turtle Sentinel Golem
1087
- * - Turtle (tier 1): Stationary, fixed missiles, reactive shield timing
1088
- * - Sentinel (tier 2): Stationary, two-tier offense (big + fast missiles)
1089
- * - Golem (tier 3): Immovable fortress, perfect shield timing
1265
+ * Returns 0 when idle or channeling (shield can be canceled immediately).
1266
+ * During casting: remaining cast ticks. During GCD: remaining GCD ticks.
1090
1267
  *
1091
- * TIER: 2 (enhanced Turtle)
1268
+ * Note: 100 ticks = 1 second.
1092
1269
  */
1093
- declare const Sentinel: WizardFunction;
1094
-
1270
+ declare function useTicksUntilReady(): number;
1095
1271
  /**
1096
- * Bot: Golem
1272
+ * Get current shield block multiplier.
1097
1273
  *
1098
- * BEHAVIOR: Stationary fortress with perfect shield timing and devastating
1099
- * counterattacks during enemy vulnerability windows. Reads enemy cast/GCD
1100
- * state to time punish missiles that land when the enemy can't shield.
1101
- * Handles multi-missile volleys by holding shield through consecutive impacts.
1102
- * Uses progressive cast-cancel thresholds for optimal damage trading.
1103
- *
1104
- * KEY IMPROVEMENTS OVER SENTINEL:
1105
- * - Counterattack punish: fires during enemy cast/GCD recovery
1106
- * - Multi-threat volley awareness: holds shield through consecutive hits
1107
- * - Progressive cast-cancel: graduated damage thresholds
1108
- * - Perfect shield timing: uses ticksToStartShield precisely
1109
- *
1110
- * PROGRESSION LINE: Turtle → Sentinel → Golem
1111
- * TIER: 3 (elite Defensive line)
1274
+ * Returns 0 if not channeling shield.
1275
+ * Returns 0.3-0.9 if channeling: starts at 0.9 (blocks 90%), decays by 0.2/sec,
1276
+ * minimum 0.3 (blocks 30%). The remaining damage gets through:
1277
+ * actualDamage = incomingDamage × (1 - shieldStrength).
1112
1278
  */
1113
- declare const Golem: WizardFunction;
1114
-
1279
+ declare function useShieldStrength(): number;
1115
1280
  /**
1116
- * Bot: Shadowblade
1117
- *
1118
- * BEHAVIOR: Melee assassin. Blinks to the enemy, then lands devastating point-blank
1119
- * stab attacks (15 damage, 30u range, ~60 tick cast = 2-hit kill). Runs directly
1120
- * at the enemy with minimal strafe, shields undodgeable threats. The entire
1121
- * strategy is: get close, stab, kill. Simple and brutal.
1122
- *
1123
- * PROGRESSION LINE: Shadowblade → Nightblade → Voidblade
1124
- * - Shadowblade (tier 1): Offensive blink, melee stabs, basic shield
1125
- * - Nightblade (tier 2): + missile-aware blinks, adaptive stabs, timed defense
1126
- * - Voidblade (tier 3): Future — perfect assassination timing, inescapable engages
1127
- *
1128
- * TIER: 1 (base)
1281
+ * Get all projectiles currently in flight (yours and enemy's).
1282
+ * Used for blink safety calculations and threat analysis.
1129
1283
  */
1130
- declare const Shadowblade: WizardFunction;
1131
-
1284
+ declare function useProjectiles(): ProjectileState[];
1132
1285
  /**
1133
- * Bot: Nightblade
1134
- *
1135
- * BEHAVIOR: Enhanced melee assassin. Same aggressive engagement as Shadowblade —
1136
- * blinks directly to the enemy and stabs for 15 damage (2-hit kill). The tier 2
1137
- * upgrade is PREEMPTIVE DEFENSE: Nightblade watches the enemy's cast bar and
1138
- * shields before a point-blank missile is even launched. At melee range, missiles
1139
- * arrive almost instantly after launch — too fast to react. Nightblade anticipates
1140
- * the threat. Also has emergency blink and proper channeling management.
1141
- *
1142
- * PROGRESSION LINE: Shadowblade → Nightblade → Voidblade
1143
- * - Shadowblade (tier 1): Offensive blink, melee stabs, basic shield (reactive only)
1144
- * - Nightblade (tier 2): + preemptive shield vs enemy casts, emergency blink
1145
- * - Voidblade (tier 3): Future — perfect assassination timing, inescapable engages
1146
- *
1147
- * TIER: 2 (enhanced Shadowblade)
1286
+ * Get the config of the last missile you fired, or undefined if none fired yet.
1287
+ * Used for the warmup system: consecutive similar missiles cast faster.
1148
1288
  */
1149
- declare const Nightblade: WizardFunction;
1150
-
1289
+ declare function useLastMissileConfig(): MissileConfig | undefined;
1151
1290
  /**
1152
- * Bot: Voidblade
1153
- *
1154
- * BEHAVIOR: Reactive counter-puncher. Shields everything, then fires quick stabs
1155
- * during enemy vulnerability windows (GCD/casting) when they can't shield back.
1156
- * At melee range, the shield blocks ~90% of incoming damage while Voidblade's
1157
- * counter-stabs land at full damage — winning through attrition.
1158
- *
1159
- * CORE LOOP (melee range):
1160
- * 1. Enemy casts missile → Voidblade blink-dodges (100% avoid) or shields (90% block)
1161
- * 2. Enemy enters GCD → Voidblade fires quick stab (lands unblocked)
1162
- * 3. Voidblade enters GCD → enemy recovers → repeat
1291
+ * Get blink cooldown remaining in ticks. Returns 0 if ready to blink.
1163
1292
  *
1164
- * KEY IMPROVEMENTS OVER NIGHTBLADE:
1165
- * - Blink-dodge priority: avoids 100% of damage when blink available, shields as fallback
1166
- * - Reads enemy vulnerability to time counter-stabs perfectly
1167
- * - Cancel-into-defense: aborts own cast if enemy missile incoming
1168
- * - Punish budget: sizes stabs to fit exactly in the vulnerability window
1293
+ * Cooldown scales with distance used:
1294
+ * - 100 units ~100 ticks (1s)
1295
+ * - 300 units (max range) 2000 ticks (20s)
1169
1296
  *
1170
- * PROGRESSION LINE: Shadowblade Nightblade Voidblade
1171
- * TIER: 3 (elite Melee line)
1297
+ * Note: 100 ticks = 1 second.
1172
1298
  */
1173
- declare const Voidblade: WizardFunction;
1174
-
1299
+ declare function useBlinkCooldown(): number;
1175
1300
  /**
1176
- * Bot: Bonemancer
1177
- *
1178
- * BEHAVIOR: Stands still and fires slow, homing missiles constantly. Every missile
1179
- * tracks the enemy with turnRate 2 — they curve relentlessly toward the target.
1180
- * No movement, no shields, just an unending stream of seeking projectiles. The
1181
- * missiles are slow (speed 3) but long-lived (300 ticks) and will chase you across
1182
- * the entire arena.
1183
- *
1184
- * NAMING RATIONALE: Named after Diablo 2's Bone Necromancer ("Bonemancer"), whose
1185
- * signature spell Bone Spirit is a slow-moving, auto-tracking projectile that hunts
1186
- * enemies relentlessly. That's exactly what this bot does — it stands in place and
1187
- * spams seeking missiles. The homing behavior is the key identity: these aren't
1188
- * aimed shots, they're heat-seeking spirits that chase you down. Every D2 player
1189
- * knows the Bonemancer — it's one of the most iconic builds.
1190
- *
1191
- * PROGRESSION LINE: Bonemancer → Lich → Archlich
1192
- * - Bonemancer (tier 1): Stationary, spams slow homing missiles
1193
- * - Lich (tier 2): Future — enhanced homing with adaptive missiles and defense
1194
- * - Archlich (tier 3): Future — master of tracking magic, undodgeable death swarm
1195
- * The progression follows the D2 necromancer power fantasy: from bone apprentice
1196
- * to undead overlord, each tier's missiles become harder to escape.
1197
- *
1198
- * TIER: 1 (base)
1301
+ * Get currently casting spell, or null if not casting.
1302
+ * Returns 'missile', 'shield', or 'blink'.
1199
1303
  */
1200
- declare const Bonemancer: WizardFunction;
1201
-
1304
+ declare function useCastingSpell(): 'missile' | 'shield' | 'blink' | null;
1202
1305
  /**
1203
- * Bot: Lich
1204
- *
1205
- * BEHAVIOR: Homing missile specialist with strong-tracking adaptive missiles.
1206
- * Uses fitMissileToBudget with minTurnRate 1.0 — higher than other bots (0.5) —
1207
- * producing missiles with superior tracking at the cost of some damage/speed.
1208
- * Strafing launches missiles from different angles, creating multi-angle pressure.
1209
- *
1210
- * Shields undodgeable/critical threats, emergency blinks. Cancels missile cast
1211
- * only for lethal incoming damage.
1306
+ * Get cast progress as {current, total} in ticks, or null if not casting.
1212
1307
  *
1213
- * KEY DIFFERENCES FROM BONEMANCER:
1214
- * - Bonemancer: stationary, no defense, fixed d=7/s=3/t=2/dur=300
1215
- * - Lich: mobile, full defense, adaptive strong-tracking homing missiles
1308
+ * current = ticks elapsed, total = ticks needed. When current >= total, spell fires.
1309
+ * Note: 100 ticks = 1 second.
1310
+ */
1311
+ declare function useCastProgress(): {
1312
+ current: number;
1313
+ total: number;
1314
+ } | null;
1315
+ /**
1316
+ * Get enemy wizard state.
1216
1317
  *
1217
- * PROGRESSION LINE: Bonemancer Lich Archlich
1218
- * - Bonemancer (tier 1): Stationary, spams fixed slow homing missiles, no defense
1219
- * - Lich (tier 2): Mobile + defense, adaptive strong-tracking missiles (minTurnRate 1.0)
1220
- * - Archlich (tier 3): Future — converging web patterns, impossible to escape
1318
+ * Returns position, velocity, health, status, casting spell, and shield strength.
1319
+ * Note: you cannot see the enemy's missile configs or exact cooldown timers —
1320
+ * only their status and what's visible on the field.
1321
+ */
1322
+ declare function useEnemy(): EnemyState;
1323
+ /**
1324
+ * Get all your active (in-flight) projectiles.
1325
+ * Each has position, rotation (degrees), speed (u/t), turnRate, remainingTicks.
1326
+ */
1327
+ declare function useMyProjectiles(): ProjectileState[];
1328
+ /**
1329
+ * Get total damage you've dealt this match.
1330
+ */
1331
+ declare function useDamageDealt(): number;
1332
+ /**
1333
+ * Get total damage you've taken this match.
1334
+ */
1335
+ declare function useDamageTaken(): number;
1336
+ /**
1337
+ * Get the tick number when you last took damage. Returns 0 if never hit.
1338
+ * Compare with useTick() to get ticks since last hit.
1339
+ */
1340
+ declare function useLastHitTick(): number;
1341
+ /**
1342
+ * Get arena dimensions. Default: {width: 800, height: 800}.
1343
+ * Wizards are clamped to [5, 795] on each axis (radius = 5).
1344
+ */
1345
+ declare function useArenaSize(): {
1346
+ width: number;
1347
+ height: number;
1348
+ };
1349
+ /**
1350
+ * Get current game tick (starts at 0, increments each tick).
1351
+ * 100 ticks = 1 second. Match ends at 30,000 ticks (5 minutes).
1352
+ */
1353
+ declare function useTick(): number;
1354
+ /**
1355
+ * Get analyzed threats from all incoming enemy projectiles.
1356
+ * Sorted by ticksToImpact (soonest first). Only includes missiles within 500 units
1357
+ * or that are predicted to hit.
1221
1358
  *
1222
- * TIER: 2 (enhanced Bonemancer)
1359
+ * Each threat includes:
1360
+ * - ticksToImpact: ticks until hit (Infinity if will miss)
1361
+ * - willHit: true if missile hits your current position
1362
+ * - canDodgeLeft/Right: whether strafing perpendicular to missile heading works
1363
+ * - canOutrun: whether moving away from missile escapes it
1364
+ * - bestDodgeDirection: {x, y} unit vector to dodge optimally, or null if undodgeable
1365
+ * - canBlockInTime: whether you can raise shield before impact
1366
+ * - ticksToStartShield: when to START channeling shield to block in time
1367
+ */
1368
+ declare function useThreats(): AnalyzedThreat[];
1369
+ /**
1370
+ * Get the most imminent threat, or null if no threats.
1371
+ * Shorthand for useThreats()[0].
1372
+ */
1373
+ declare function useClosestThreat(): AnalyzedThreat | null;
1374
+ /**
1375
+ * Get your missiles analyzed from the enemy's perspective.
1376
+ * Useful to predict when enemy will shield/dodge your attacks.
1223
1377
  */
1224
- declare const Lich: WizardFunction;
1378
+ declare function useMyThreatsToEnemy(): AnalyzedThreat[];
1225
1379
 
1226
1380
  /**
1227
- * Bot: Archlich
1228
- *
1229
- * BEHAVIOR: Lich's proven core (mobile homing specialist) plus vulnerability
1230
- * exploitation. Defense, movement, and standard offense are identical to Lich.
1231
- * The T3 advantage: when the enemy is locked in GCD/cast, fires fast straight
1232
- * punish missiles that land during the vulnerability window.
1381
+ * VIBEMANCER - THREAT ANALYSIS
1233
1382
  *
1234
- * PROGRESSION LINE: Bonemancer Lich → Archlich
1235
- * TIER: 3 (elite Homing line)
1383
+ * Pre-computes threat information for incoming projectiles.
1384
+ * This handles the "subconscious" perception of missile trajectories.
1236
1385
  */
1237
- declare const Archlich: WizardFunction;
1238
1386
 
1239
1387
  /**
1240
- * Bot: Flamecaller
1241
- *
1242
- * BEHAVIOR: Long-range homing missile caster with fixed missile config.
1243
- * Maintains 350u distance, strafes to dodge, and fires standard homing
1244
- * missiles (d=10, s=5, t=1, dur=180). Shields undodgeable threats,
1245
- * emergency blinks. A straightforward ranged caster that trades
1246
- * consistency for adaptability.
1247
- *
1248
- * PROGRESSION LINE: Flamecaller → Pyromancer → Infernalist
1249
- * - Flamecaller (tier 1): Fixed homing missiles, basic strafe and defense
1250
- * - Pyromancer (tier 2): + adaptive fitting, cast canceling, smart fallbacks
1251
- * - Infernalist (tier 3): Future — overwhelming adaptive fire
1388
+ * Analyze all threats from enemy projectiles.
1252
1389
  *
1253
- * TIER: 1 (base)
1390
+ * @param myPos - Current position of the wizard
1391
+ * @param projectiles - All projectiles in the game
1392
+ * @param myProjectiles - Only the bot's own projectiles (used for filtering)
1393
+ * @param ticksUntilReady - Ticks until wizard can start a new action
1394
+ * @returns Array of analyzed threats sorted by ticksToImpact (soonest first)
1254
1395
  */
1255
- declare const Flamecaller: WizardFunction;
1396
+ declare function analyzeThreats(myPos: Position, projectiles: ProjectileState[], myProjectiles: ProjectileState[], ticksUntilReady: number): AnalyzedThreat[];
1256
1397
 
1257
1398
  /**
1258
- * Bot: Pyromancer
1259
- *
1260
- * BEHAVIOR: Adaptive homing missile specialist at long range. Maintains 400u
1261
- * distance, strafes to dodge, and uses fitMissileToBudget with minTurnRate 0.5
1262
- * to fire the highest-damage homing missile that fits in the safe window.
1263
- * Shields undodgeable threats, emergency blinks. A versatile ranged caster
1264
- * that adapts its missiles to the situation.
1399
+ * VIBEMANCER - ACTION BUILDERS
1265
1400
  *
1266
- * PROGRESSION LINE: Flamecaller Pyromancer Infernalist
1267
- * - Flamecaller (tier 1): Fixed homing missiles, basic strafe and defense
1268
- * - Pyromancer (tier 2): + adaptive fitting, cast canceling, smart fallbacks
1269
- * - Infernalist (tier 3): Future — overwhelming adaptive fire
1401
+ * Fluent API for constructing bot actions with type-safe chaining.
1270
1402
  *
1271
- * TIER: 2 (enhanced Flamecaller)
1403
+ * UNITS REFERENCE (100 ticks = 1 second):
1404
+ * Position: absolute world coordinates, 0-800 on each axis (800×800 arena)
1405
+ * Movement: direction vector, magnitude clamped to max 1 (speed in [0, 1])
1406
+ * Speed: units per tick (player moves at 1 unit/tick = 100 units/sec)
1407
+ * Duration: ticks (divide by 100 for seconds)
1408
+ * Angles: degrees (0°=right, 90°=down, 180°=left, 270°=up)
1409
+ * Damage: raw HP removed on hit (wizard has 60 HP)
1410
+ * Turn rate: degrees per tick the missile can rotate
1272
1411
  */
1273
- declare const Pyromancer: WizardFunction;
1274
1412
 
1275
1413
  /**
1276
- * Bot: Infernalist
1414
+ * Channel a shield that blocks incoming damage.
1277
1415
  *
1278
- * BEHAVIOR: Rapid-fire caster that exploits warmup bonus for accelerating DPS.
1279
- * Fires consistent homing missiles to build warmup, punishes vulnerability windows
1280
- * with warmup-boosted fast casts. Proactive blink kiting when enemy closes.
1416
+ * Starts at 90% block, decays by 20% per second, minimum 30%.
1417
+ * Takes 20 ticks (0.2s) to activate. Movement is disabled while channeling.
1418
+ * Cancel anytime with cancel(). Triggers 100-tick (1s) GCD after cancel.
1281
1419
  *
1282
- * KEY IMPROVEMENTS OVER PYROMANCER:
1283
- * - Warmup exploitation: always passes lastMissileConfig for bonus
1284
- * - Punish mode: straight missiles during enemy vulnerability
1285
- * - Proactive blink kiting: monitors closing rate
1286
- * - Progressive cast-cancel: graduated thresholds
1420
+ * Can chain .move() — movement applies during the 20-tick cast, NOT during channel.
1287
1421
  *
1288
- * PROGRESSION LINE: Flamecaller → Pyromancer → Infernalist
1289
- * TIER: 3 (elite Caster line)
1422
+ * @example
1423
+ * return shield(); // shield and stay still
1424
+ * return shield().move(1, 0); // move right while cast starts
1290
1425
  */
1291
- declare const Infernalist: WizardFunction;
1292
-
1426
+ declare function shield(): ActionBuilder;
1293
1427
  /**
1294
- * Bot: Spellshot
1428
+ * Cast a missile spell.
1295
1429
  *
1296
- * BEHAVIOR: Uses interceptAngle to calculate where the enemy will be and fires
1297
- * fast, non-homing missiles (speed 8, turnRate 0) along the predicted path.
1298
- * Strafes at medium range (300-400), shields undodgeable threats, emergency
1299
- * blinks when shield isn't available. The key mechanic is PREDICTION — these
1300
- * missiles don't track, they go exactly where you calculated the enemy would be.
1430
+ * Cast time scales with damage, speed, duration, and turn rate bigger missiles
1431
+ * take longer to cast. While casting you move at 50% speed. After firing, 100-tick
1432
+ * (1s) GCD before next spell.
1301
1433
  *
1302
- * NAMING RATIONALE: "Spellshot" a spell that is a single, precisely aimed shot.
1303
- * Like a sniper's "called shot" but magical. The defining feature is the intercept
1304
- * calculation: this bot doesn't fire tracking missiles, it calculates the exact
1305
- * angle needed to hit a moving target. "Shot" implies precision, singular impact,
1306
- * and skill-based aiming — everything this bot is about.
1434
+ * Repeated similar missiles cast 20% faster (warmup bonus). Switching styles
1435
+ * incurs a 20% penalty.
1307
1436
  *
1308
- * PROGRESSION LINE: Spellshot Spelltracer Spellseeker
1309
- * - Spellshot (tier 1): Basic intercept prediction, non-homing missiles
1310
- * - Spelltracer (tier 2): Future — predictive homing (missiles that lead AND track)
1311
- * - Spellseeker (tier 3): Future — perfect prediction, multi-angle attacks
1312
- * The naming progression: shot (single bullet) → tracer (bullet that tracks a path)
1313
- * → seeker (actively hunts). Each tier adds more intelligence to the projectile,
1314
- * evolving from "I calculate where you'll be" to "my missile calculates where you'll be."
1437
+ * Can chain .move() for simultaneous movement while casting.
1315
1438
  *
1316
- * NOTE: A separate future archetype "Spellslinger" (volume-of-fire) is reserved
1317
- * for a rapid-fire bot that prioritizes quantity over prediction.
1439
+ * @param config - Missile stats:
1440
+ * - damage: HP removed on hit (1-60 typical). Also sets hitbox: radius = 2 + 0.1×damage.
1441
+ * - speed: units/tick (min 1.5). Player moves at 1 u/t, so 5 = 5× player speed.
1442
+ * - duration: ticks the missile lives (min 10). Range ≈ speed × duration.
1443
+ * - turnRate: degrees/tick of homing (0 = straight line, 3 = moderate homing, 5+ = strong).
1444
+ * Negative = no homing + minor speed cost reduction.
1445
+ * @param ai - Called every tick to control missile steering. Use getMissileContext()
1446
+ * to read the missile's state (position, rotation, speed, etc.), the full game
1447
+ * state (worldState), and a seeded PRNG (random). Return turnToward(x, y) to home
1448
+ * toward a position, or flyStraight() to fly straight.
1449
+ * @param direction - Launch angle in degrees (0°=right, 90°=down, 180°=left, 270°=up).
1450
+ * Tip: use Math.atan2(dy, dx) * (180 / Math.PI) to aim at a target.
1318
1451
  *
1319
- * TIER: 1 (base)
1452
+ * @example
1453
+ * // Straight missile aimed at enemy
1454
+ * const angle = Math.atan2(dy, dx) * (180 / Math.PI);
1455
+ * return missile({damage: 15, speed: 6, duration: 200, turnRate: 0}, () => flyStraight(), angle);
1456
+ *
1457
+ * // Homing missile that tracks enemy
1458
+ * return missile(
1459
+ * {damage: 10, speed: 5, duration: 300, turnRate: 3},
1460
+ * () => {
1461
+ * const ctx = getMissileContext();
1462
+ * const enemy = ctx.worldState.enemies[0];
1463
+ * return enemy ? turnToward(enemy.position.x, enemy.position.y) : flyStraight();
1464
+ * },
1465
+ * angle,
1466
+ * );
1320
1467
  */
1321
- declare const Spellshot: WizardFunction;
1322
-
1468
+ declare function missile(config: MissileConfig, ai: MissileFunction$1, direction: number): ActionBuilder;
1323
1469
  /**
1324
- * Bot: Spelltracer
1470
+ * Teleport to an absolute position on the arena.
1325
1471
  *
1326
- * BEHAVIOR: Enhanced ranged sniper with adaptive missile fitting and intercept
1327
- * prediction. Uses fitMissileToBudget to find the highest-damage fast missile
1328
- * that fits the safe window, then fires it along the predicted intercept angle.
1329
- * Maintains medium-long range (300-450), shields undodgeable threats with proper
1330
- * timing, emergency blinks, and distance blinks when cornered. The key mechanic
1331
- * is still PREDICTION — but now with adaptive damage optimization.
1472
+ * Max range: 300 units from current position (clamped by engine if further).
1473
+ * Cast time: 10 ticks (0.1s). Cooldown scales with distance:
1474
+ * - 100 units 100 ticks (1s)
1475
+ * - 300 units 2000 ticks (20s)
1332
1476
  *
1333
- * PROGRESSION LINE: Spellshot Spelltracer Spellseeker
1334
- * - Spellshot (tier 1): Fixed config intercept prediction, non-homing missiles
1335
- * - Spelltracer (tier 2): + adaptive fitting, timed defense, distance management
1336
- * - Spellseeker (tier 3): Future — perfect prediction, multi-angle attacks
1477
+ * Cannot chain .move() blink IS the movement.
1337
1478
  *
1338
- * TIER: 2 (enhanced Spellshot)
1479
+ * @param x - Target X position (0-800, absolute world coordinate)
1480
+ * @param y - Target Y position (0-800, absolute world coordinate)
1481
+ *
1482
+ * @example
1483
+ * return blink(400, 400); // blink to center
1484
+ * return blink(enemy.position.x, enemy.position.y); // blink to enemy
1339
1485
  */
1340
- declare const Spelltracer: WizardFunction;
1341
-
1486
+ declare function blink(x: number, y: number): FinalAction;
1342
1487
  /**
1343
- * Bot: Spellseeker
1344
- *
1345
- * BEHAVIOR: Elite sniper that uses intercept-aimed straight missiles during vulnerability
1346
- * windows. Combines Spelltracer's adaptive fitting with precise lead-position aiming
1347
- * and vulnerability exploitation. Straight punish missiles at sniper range are nearly
1348
- * unavoidable. Proactive distance control via closing rate detection.
1488
+ * Cancel current cast or channel (e.g. stop shielding to attack).
1349
1489
  *
1350
- * KEY IMPROVEMENTS OVER SPELLTRACER:
1351
- * - Intercept-aimed punish: getLeadPosition + straight missiles during vulnerability
1352
- * - Proactive distance blink: monitors closing rate, blinks before danger zone
1353
- * - Progressive cast-cancel: graduated damage thresholds
1354
- * - Warmup exploitation: always passes lastMissileConfig
1490
+ * Canceling a cast/channel triggers 100-tick (1s) GCD.
1491
+ * Can chain .move() for simultaneous movement.
1355
1492
  *
1356
- * PROGRESSION LINE: Spellshot → Spelltracer → Spellseeker
1357
- * TIER: 3 (elite Sniper line)
1493
+ * @example
1494
+ * return cancel().move(-1, 0); // cancel and dodge left
1358
1495
  */
1359
- declare const Spellseeker: WizardFunction;
1360
-
1496
+ declare function cancel(): ActionBuilder;
1361
1497
  /**
1362
- * Bot: Battlemage
1498
+ * Move in a direction without casting any spell.
1363
1499
  *
1364
- * BEHAVIOR: Balanced mid-range duelist. Shields undodgeable threats, interrupts
1365
- * enemy casts with quick missiles, saves blink for emergencies OR gap-closing.
1366
- * Switches between quick (10 dmg, fast) and heavy (15 dmg, slow) missile configs
1367
- * based on safety window and range. Will aggressively trade hits when health allows.
1368
- * The unique trait is cast-interruption: fires quick missiles specifically when the
1369
- * enemy is casting, punishing long cast times.
1500
+ * This is a **direction vector**, not a target position. Values are in the
1501
+ * range [-1, 1] where 1 = full speed. Larger values (like raw position
1502
+ * deltas) are clamped to full speed automatically.
1370
1503
  *
1371
- * PROGRESSION LINE: Battlemage Warmage Archmage
1372
- * - Battlemage (tier 1): Quick/heavy fixed configs, cast interruption, basic defense
1373
- * - Warmage (tier 2): Adaptive missile fitting (fitMissileToBudget), smarter attacks
1374
- * - Archmage (tier 3): Future — supreme duelist, perfect tactical mastery
1504
+ * @param x - Horizontal direction (positive = right, negative = left)
1505
+ * @param y - Vertical direction (positive = down, negative = up)
1375
1506
  *
1376
- * TIER: 1 (base)
1507
+ * @example
1508
+ * return move(1, 0); // move right at full speed
1509
+ * return move(dx / dist, dy / dist); // normalized unit vector = full speed
1510
+ * return move(enemy.position.x - myPos.x, enemy.position.y - myPos.y); // raw delta = clamped to full speed
1377
1511
  */
1378
- declare const Battlemage: WizardFunction;
1379
-
1512
+ declare function move(x: number, y: number): FinalAction;
1380
1513
  /**
1381
- * Bot: Warmage
1382
- *
1383
- * BEHAVIOR: Enhanced Battlemage with adaptive missile fitting. Uses fitMissileToBudget
1384
- * to maximize damage within safe attack windows instead of fixed quick/heavy configs.
1385
- * Same close-range playstyle: shields undodgeable threats, blinks to close distance
1386
- * or escape, aggressive hit-trading when health allows. The adaptive fitting means
1387
- * every attack is optimized for the current situation — no wasted cast time.
1514
+ * Do nothing — no action, no movement.
1515
+ */
1516
+ declare function idle(): FinalAction;
1517
+ /**
1518
+ * Steer the missile toward a world position.
1519
+ * The engine clamps the actual turn to the missile's turnRate.
1388
1520
  *
1389
- * PROGRESSION LINE: Battlemage Warmage Archmage
1390
- * - Battlemage (tier 1): Quick/heavy fixed configs, cast interruption, basic defense
1391
- * - Warmage (tier 2): Adaptive missile fitting, optimized damage windows
1392
- * - Archmage (tier 3): Future — supreme duelist, perfect tactical mastery
1521
+ * @param x - Target X position (world coordinates)
1522
+ * @param y - Target Y position (world coordinates)
1523
+ */
1524
+ declare function turnToward(x: number, y: number): MissileAction;
1525
+ /**
1526
+ * Steer the missile toward a specific angle (degrees).
1527
+ * The engine clamps the actual turn to the missile's turnRate.
1393
1528
  *
1394
- * TIER: 2 (enhanced Battlemage)
1529
+ * @param degrees - Target rotation in degrees (0=right, 90=down, 180=left, 270=up)
1395
1530
  */
1396
- declare const Warmage: WizardFunction;
1531
+ declare function turnToAngle(degrees: number): MissileAction;
1532
+ /**
1533
+ * Fly straight — no steering this tick.
1534
+ */
1535
+ declare function flyStraight(): MissileAction;
1536
+ /**
1537
+ * Extract MissileActions from a MissileAction.
1538
+ * Used by the engine to get the actual missile action.
1539
+ */
1540
+ declare function extractMissileAction(action: MissileAction): MissileActions;
1541
+ /**
1542
+ * Extract WizardActions from a FinalAction.
1543
+ * Used by the engine to get the actual action.
1544
+ */
1545
+ declare function extractAction(finalAction: FinalAction): WizardActions;
1397
1546
 
1398
1547
  /**
1399
- * Bot: Archmage
1548
+ * VIBEMANCER - PARAMETER RUNTIME
1400
1549
  *
1401
- * BEHAVIOR: Versatile duelist that adapts missile choice based on distance and HP.
1402
- * Close range straight missiles (no turn cost = more damage). Mid/far range → homing.
1403
- * Uses dual-blink aggressively (gap-close during vulnerability, escape when trade is bad).
1404
- * HP-aware: ahead → aggressive close range; behind → defensive ranged kiting.
1550
+ * Provides the useParam() hook for bots to declare tunable parameters,
1551
+ * and the infrastructure for the optimizer to inject/discover parameter values.
1405
1552
  *
1406
- * KEY IMPROVEMENTS OVER WARMAGE:
1407
- * - Range-adaptive missiles: straight close, homing far
1408
- * - Vulnerability-timed blinks: gap-close during enemy cast/GCD
1409
- * - HP-aware aggression: adjusts distance + risk tolerance based on HP differential
1410
- * - Progressive cast-cancel: graduated thresholds
1553
+ * Design: Module-level state (JS is single-threaded, no race conditions).
1554
+ * The optimizer sets param values before running a bot, and clears them after.
1555
+ * During discovery, all useParam calls are recorded.
1411
1556
  *
1412
- * PROGRESSION LINE: Battlemage → Warmage → Archmage
1413
- * TIER: 3 (elite Duelist line)
1414
- */
1415
- declare const Archmage: WizardFunction;
1416
-
1417
- /**
1418
- * Bot: Stormchaser
1557
+ * ## useParam API
1419
1558
  *
1420
- * BEHAVIOR: Fights aggressively while managing defense intelligently. Uses
1421
- * two fixed missile configs (standard homing + quick attack) with predictive
1422
- * homing missile AI (interceptAngle on the missile itself). Blink-dodges
1423
- * incoming threats, shields when blink is on cooldown. Tight distance
1424
- * management (350 units, ±30 band).
1559
+ * ```typescript
1560
+ * // Basic: just a value, no optimizer config
1561
+ * const damage = useParam('damage', 15);
1425
1562
  *
1426
- * PROGRESSION LINE: Stormchaser Stormcaller Stormforger
1427
- * - Stormchaser (tier 1): Fixed missiles, predictive homing AI, blink-dodge
1428
- * - Stormcaller (tier 2): + fitMissileToBudget, predictive homing, optimized damage
1429
- * - Stormforger (tier 3): Future — supreme berserker, perfect aggression
1563
+ * // With range: optimizer searches value ± range (sliding window)
1564
+ * const distance = useParam('distance', 350, {range: 150, min: 0});
1430
1565
  *
1431
- * TIER: 1 (base)
1566
+ * // With fixed min/max: optimizer searches [min, max] (fixed bounds)
1567
+ * const damage = useParam('damage', 15, {min: 5, max: 25});
1568
+ *
1569
+ * // With all: range defines search radius, min/max clamp it
1570
+ * const fraction = useParam('fraction', 0.25, {range: 0.2, min: 0, max: 1});
1571
+ *
1572
+ * // With custom step count: optimizer tests 20 values instead of default 10
1573
+ * const distance = useParam('distance', 500, {range: 200, min: 0, steps: 20});
1574
+ * ```
1575
+ *
1576
+ * - **Arg 1** `name` — unique parameter name (must be consistent across ticks)
1577
+ * - **Arg 2** `value` — the actual value used in gameplay. This is what your bot
1578
+ * uses during matches. The optimizer script automatically updates this value.
1579
+ * - **Arg 3** `config` (optional) — optimizer search configuration:
1580
+ * - `range` — search radius: optimizer checks `value ± range`. The auto-optimizer
1581
+ * rewrites `value` after each run, so the search window slides automatically.
1582
+ * - `min` / `max` — hard constraints (e.g., distance ≥ 0, fraction ≤ 1).
1583
+ * When `range` is omitted, these define fixed search bounds (old-style).
1584
+ * - `steps` — how many evenly-spaced values the optimizer tests per pass (default: 10).
1585
+ * - `substeps` — steps to use in refinement passes (passes 2+). Set to 0 to freeze
1586
+ * after pass 1 (ideal for boolean params). Defaults to `steps` if not specified.
1587
+ * - At least `range` or both `min` + `max` must be provided.
1588
+ *
1589
+ * Without optimizer config, useParam simply returns `value` every tick.
1590
+ * With optimizer config, the offline optimizer script can override the value during search.
1591
+ *
1592
+ * ## Rules of Hooks
1593
+ * useParam follows the same rules as useState/useEffect/etc:
1594
+ * - Must be called at the top level of your bot function (not inside conditionals)
1595
+ * - Must be called in the same order every tick
1596
+ * - Violations are detected and throw errors
1432
1597
  */
1433
- declare const Stormchaser: WizardFunction;
1434
1598
 
1435
1599
  /**
1436
- * Bot: Stormcaller
1600
+ * Declaration of a tunable parameter, as discovered by the optimizer.
1601
+ */
1602
+ interface ParamDeclaration {
1603
+ name: string;
1604
+ value: number;
1605
+ range?: number;
1606
+ min?: number;
1607
+ max?: number;
1608
+ steps: number;
1609
+ /** Steps to use in refinement passes (passes 2+). 0 = freeze after pass 1. Defaults to `steps`. */
1610
+ substeps?: number;
1611
+ }
1612
+ /**
1613
+ * Declare a tunable parameter. Returns the current value (optimizer-injected or the provided value).
1437
1614
  *
1438
- * BEHAVIOR: Enhanced Stormchaser with adaptive missile fitting (fitMissileToBudget)
1439
- * AND predictive homing missiles. Combines aggressive fighting philosophy with
1440
- * optimized damage output. Uses budget-based missile fitting to maximize damage
1441
- * within safe windows. Falls back to quick missiles under pressure. Same smart
1442
- * trade/shield decisions as Stormchaser but with better resource usage.
1615
+ * @param name - Unique parameter name (consistent across ticks)
1616
+ * @param value - The gameplay value. The auto-optimizer rewrites this in source code.
1617
+ * @param config - Optional optimizer search configuration
1618
+ * @returns The optimizer-injected value during optimization, or `value` during normal play
1443
1619
  *
1444
- * PROGRESSION LINE: Stormchaser → Stormcaller → Stormforger
1445
- * - Stormchaser (tier 1): Adaptive missiles, smart trading, aggressive defense
1446
- * - Stormcaller (tier 2): + fitMissileToBudget, predictive homing, optimized damage
1447
- * - Stormforger (tier 3): Future — supreme berserker, perfect aggression
1620
+ * @example
1621
+ * // Simple: no optimizer config
1622
+ * const damage = useParam('damage', 15);
1448
1623
  *
1449
- * TIER: 2 (enhanced Stormchaser)
1624
+ * // With range: optimizer searches value ± range (sliding window)
1625
+ * const distance = useParam('distance', 350, {range: 150, min: 0});
1626
+ *
1627
+ * // With fixed min/max: optimizer searches [min, max]
1628
+ * const damage = useParam('damage', 15, {min: 5, max: 25});
1629
+ */
1630
+ declare function useParam(name: string, value: number, config?: {
1631
+ range?: number;
1632
+ min?: number;
1633
+ max?: number;
1634
+ steps?: number;
1635
+ substeps?: number;
1636
+ }): number;
1637
+ /**
1638
+ * Inject parameter values for the next bot execution.
1639
+ * The wrapped bot will read these values via useParam().
1640
+ */
1641
+ declare function setParamValues(values: Record<string, number>): void;
1642
+ /**
1643
+ * Clear injected parameter values. useParam() will return its provided value.
1644
+ */
1645
+ declare function clearParamValues(): void;
1646
+ /**
1647
+ * Start discovery mode. All subsequent useParam() calls with optimizer config
1648
+ * will register their declarations.
1649
+ */
1650
+ declare function startDiscovery(): void;
1651
+ /**
1652
+ * Stop discovery mode and return all discovered parameter declarations.
1653
+ */
1654
+ declare function stopDiscovery(): ParamDeclaration[];
1655
+ /**
1656
+ * Wrap a bot function to inject specific parameter values.
1657
+ * The returned function sets params before calling the bot and clears them after.
1450
1658
  */
1451
- declare const Stormcaller: WizardFunction;
1659
+ declare function wrapWithParams(bot: WizardFunction, params: Record<string, number>): WizardFunction;
1452
1660
 
1453
1661
  /**
1454
- * Bot: Stormforger
1662
+ * Bot: TargetDummy
1455
1663
  *
1456
- * BEHAVIOR: Enhanced Stormcaller with vulnerability exploitation. Takes the exact
1457
- * Stormcaller foundation (adaptive missile fitting + predictive homing) and adds
1458
- * a punish mode that fires fast straight missiles when the enemy is locked in
1459
- * GCD or cast animation. During vulnerability windows, uses getLeadPosition for
1460
- * accurate straight shots that arrive before the enemy can react.
1664
+ * BEHAVIOR: Does absolutely nothing. No movement, no spells, no AI.
1461
1665
  *
1462
- * PROGRESSION LINE: Stormchaser Stormcaller Stormforger
1463
- * - Stormchaser (tier 1): Adaptive missiles, smart trading, aggressive defense
1464
- * - Stormcaller (tier 2): + fitMissileToBudget, predictive homing, optimized damage
1465
- * - Stormforger (tier 3): + vulnerability exploitation, punish missiles during enemy GCD
1666
+ * NAMING RATIONALE: "Target Dummy" is universal MMO player vocabulary for the
1667
+ * practice objects found in capital cities. Every WoW/FFXIV player has beaten
1668
+ * on a target dummy to test DPS rotations. That's exactly what this bot is —
1669
+ * a punching bag for testing missile mechanics and baseline damage output.
1670
+ * Nobody calls them "training dummies"; the player term is always "target dummy."
1466
1671
  *
1467
- * TIER: 3 (elite Berserker line)
1672
+ * STANDALONE no tier progression. It's a test fixture, not a combatant.
1468
1673
  */
1469
- declare const Stormforger: WizardFunction;
1674
+ declare function TargetDummy(): FinalAction;
1470
1675
 
1471
1676
  /**
1472
- * Bot: Spellspinner
1473
- *
1474
- * BEHAVIOR: Maintains medium range (350 units), strafes constantly to dodge
1475
- * missiles, and fires homing missiles (damage 10, speed 4, turnRate 2). Heavy
1476
- * emphasis on movement — 80% strafe intensity when not dodging, 100% when dodging.
1477
- * Shields only undodgeable threats, emergency blinks when shield isn't available.
1478
- * The constant circular strafing motion traces patterns like thread being spun.
1677
+ * Bot: Rookie
1479
1678
  *
1480
- * NAMING RATIONALE: Like a spider spinning a web of projectiles while circling its
1481
- * prey. The constant strafing movement pattern traces circles spinning thread
1482
- * around the arena. "Spell" + "spinner" = a wizard who spins spells around the
1483
- * battlefield. The kiting behavior (maintaining distance while attacking) creates
1484
- * a web-like pattern of missiles and movement that traps opponents.
1679
+ * BEHAVIOR: Stands perfectly still and fires straight (non-homing) missiles at
1680
+ * the enemy. No movement, no dodging, no shielding. Knows one spell and uses
1681
+ * it on cooldown. The wizard equivalent of an FPS player who stands in the open
1682
+ * and holds left-click.
1485
1683
  *
1486
- * PROGRESSION LINE: Spellspinner Spellweaver Spellbinder
1487
- * - Spellspinner (tier 1): Fixed homing missiles, constant strafe, basic defense
1488
- * - Spellweaver (tier 2): + adaptive missile fitting, more sophisticated patterns
1489
- * - Spellbinder (tier 3): Future inescapable web of magic, perfect distance control
1490
- * The progression: spinner (raw thread) weaver (creates patterns) binder
1491
- * (constrains and traps). Each tier's projectile web becomes harder to escape.
1684
+ * NAMING RATIONALE: "Rookie" is the universal term for a first-timer who barely
1685
+ * knows what they're doing. This bot is a day-one player who learned how to cast
1686
+ * missile and nothing else. No movement, no defense, just raw "I press the button."
1687
+ * We considered "Noob" (more accurate) but Rookie is less abrasive while conveying
1688
+ * the same thing a beginner who doesn't know any better.
1492
1689
  *
1493
- * TIER: 1 (base)
1690
+ * STANDALONE no tier progression. Rookies either learn to play a real class
1691
+ * or quit. This bot represents the rock-bottom of "at least it shoots."
1494
1692
  */
1495
- declare const Spellspinner: WizardFunction;
1693
+ declare function Rookie(): FinalAction;
1496
1694
 
1497
1695
  /**
1498
- * Bot: Spellweaver
1499
- *
1500
- * BEHAVIOR: Enhanced Spellspinner with adaptive missile fitting. Same medium-range
1501
- * kiting playstyle — maintains distance, strafes heavily — but uses fitMissileToBudget
1502
- * to maximize damage within safe attack windows. Always uses homing missiles since
1503
- * kiting means enemies are always moving. More sophisticated than Spellspinner's
1504
- * fixed damage/speed/turnRate configuration.
1696
+ * Bot: Critter
1505
1697
  *
1506
- * NAMING RATIONALE: A weaver creates intricate patterns from raw thread. Where the
1507
- * Spellspinner produces raw threads of magic (fixed missiles), the Spellweaver
1508
- * combines them into optimized patterns (adaptive fitting). The name suggests
1509
- * craftsmanship and sophistication the same kiting web, but deliberately woven
1510
- * rather than chaotically spun.
1698
+ * BEHAVIOR: Picks random valid actions each tick random movement, random spells,
1699
+ * random missile configs, random directions. Occasionally cancels its own casts.
1700
+ * Uses engine-provided seeded random for deterministic behavior. Useful for
1701
+ * finding edge cases in the engine, but completely useless in combat.
1511
1702
  *
1512
- * PROGRESSION LINE: Spellspinner Spellweaver Spellbinder
1513
- * - Spellspinner (tier 1): Fixed homing missiles, constant strafe, basic defense
1514
- * - Spellweaver (tier 2): + adaptive missile fitting, more sophisticated patterns
1515
- * - Spellbinder (tier 3): Future inescapable web, perfect distance control
1703
+ * NAMING RATIONALE: In WoW, critters are the 1-HP ambient mobs (rabbits, squirrels,
1704
+ * prairie dogs) that wander around doing nothing useful and die to literally anything.
1705
+ * This bot is the wizard equivalent it flails around randomly and gets destroyed by
1706
+ * anyone with a plan. The word "Critter" immediately tells any gamer "this thing is
1707
+ * helpless and exists only to fill space."
1516
1708
  *
1517
- * TIER: 2 (enhanced Spellspinner)
1709
+ * STANDALONE no tier progression. Critters don't level up. However, a future
1710
+ * "Hogger" bot could be an elite critter: same chaotic spirit but actually dangerous
1711
+ * (like the famous WoW elite that wipes unprepared lowbies).
1518
1712
  */
1519
- declare const Spellweaver: WizardFunction;
1713
+ declare function Critter(): FinalAction;
1520
1714
 
1521
1715
  /**
1522
- * Bot: Spellbinder
1523
- *
1524
- * BEHAVIOR: Enhanced Spellweaver with vulnerability exploitation. Same medium-range
1525
- * kiting playstyle — maintains distance, strafes heavily, uses fitMissileToBudget
1526
- * for adaptive homing missiles. The T3 upgrade adds a punish mode that fires fast
1527
- * straight missiles timed to land while the enemy is locked in a cast or GCD,
1528
- * when they cannot shield. Defense, movement, and standard offense are identical
1529
- * to Spellweaver.
1716
+ * Bot: Hogger
1530
1717
  *
1531
- * NAMING RATIONALE: A binder constrains and locks down opponents. Where the
1532
- * Spellweaver optimizes missile patterns (adaptive fitting), the Spellbinder
1533
- * reads the enemy's state and punishes vulnerability windows binding them
1534
- * to their commitments with unavoidable damage.
1718
+ * BEHAVIOR: The elite critter. Chaotic and unpredictable but genuinely dangerous.
1719
+ * Randomly varies missile configs each cast (damage 7-15, speed 3-8, random homing),
1720
+ * moves erratically but still somewhat toward/away from the enemy, shields when
1721
+ * in real danger, and blinks unpredictably. The randomness makes Hogger hard to
1722
+ * predict — you never know if the next missile will be a slow tracker or a fast
1723
+ * snipe. Unlike Critter's pure randomness, Hogger has enough combat awareness
1724
+ * to actually win fights.
1535
1725
  *
1536
- * PROGRESSION LINE: Spellspinner Spellweaver Spellbinder
1537
- * - Spellspinner (tier 1): Fixed homing missiles, constant strafe, basic defense
1538
- * - Spellweaver (tier 2): + adaptive missile fitting, more sophisticated patterns
1539
- * - Spellbinder (tier 3): + vulnerability punish mode with fast straight missiles
1726
+ * NAMING RATIONALE: In WoW, Hogger is the iconic level 11 elite gnoll in Elwynn
1727
+ * Forest who infamously kills unprepared lowbies. He's technically a basic mob
1728
+ * but hits way harder than expected. This bot is the Critter that learned to
1729
+ * fight still chaotic, still a bit dumb, but capable of ending you if you
1730
+ * underestimate it. "Hogger" is one of WoW's most recognizable references and
1731
+ * perfectly captures "deceptively dangerous chaos."
1540
1732
  *
1541
- * TIER: 3 (elite Spellspinner line)
1733
+ * STANDALONE no tier progression. There's only one Hogger.
1542
1734
  */
1543
- declare const Spellbinder: WizardFunction;
1735
+ declare function Hogger(): FinalAction;
1544
1736
 
1545
1737
  /**
1546
- * VIBEMANCER - BOT REGISTRY
1547
- *
1548
- * Single source of truth for all bots, ordered from weakest to strongest.
1549
- * Run the tournament test to determine the correct ordering.
1738
+ * Bot: Doombringer
1550
1739
  *
1551
- * To reorder: run `npx vitest run tests/bots/tournament.test.ts`
1552
- * and update the list below based on the results.
1740
+ * BEHAVIOR: Fires a single maximum-damage homing missile with infinite budget.
1741
+ * No damage cap — goes for the biggest possible hit. Exists as a benchmark to
1742
+ * demonstrate why lower-damage + shield play is superior. Has basic shield
1743
+ * defense but no sophisticated tactics. One fat cast, one fat hit.
1553
1744
  *
1554
- * BOT NAMING SCHEME (3-tier progression):
1745
+ * STANDALONE no tier progression. Benchmark/test bot.
1746
+ */
1747
+ declare function Doombringer(): FinalAction;
1748
+
1749
+ declare function Turtle(): FinalAction;
1750
+
1751
+ /**
1752
+ * Bot: Sentinel
1555
1753
  *
1556
- * | Group | Tier 1 (base) | Tier 2 (enhanced) | Tier 3 (elite) |
1557
- * |------------|----------------|-------------------|-----------------|
1558
- * | Defensive | Turtle | Sentinel | Golem |
1559
- * | Duelist | Battlemage | Warmage | Archmage |
1560
- * | Homing | Bonemancer | Lich | Archlich |
1561
- * | Caster | Flamecaller | Pyromancer | Infernalist |
1562
- * | Melee | Shadowblade | Nightblade | Voidblade |
1563
- * | Sniper | Spellshot | Spelltracer | Spellseeker |
1564
- * | Berserker | Stormchaser | Stormcaller | Stormforger |
1565
- * | Kiter | Spellspinner | Spellweaver | Spellbinder |
1754
+ * BEHAVIOR: Stationary tank with last-moment shielding AND two-tier offense.
1755
+ * Like Turtle, never moves and shields at the last moment. Unlike Turtle,
1756
+ * fires bigger missiles (damage 20) when the safe window is large enough,
1757
+ * falling back to Turtle's fast missile (damage 12) when pressed.
1566
1758
  *
1567
- * Standalone: TargetDummy, Critter, Hogger, Rookie, Doombringer
1568
- * Reserved: Spellslinger (future volume-of-fire archetype)
1759
+ * PROGRESSION LINE: Turtle Sentinel Golem
1760
+ * - Turtle (tier 1): Stationary, fixed missiles, reactive shield timing
1761
+ * - Sentinel (tier 2): Stationary, two-tier offense (big + fast missiles)
1762
+ * - Golem (tier 3): Immovable fortress, perfect shield timing
1569
1763
  *
1764
+ * TIER: 2 (enhanced Turtle)
1570
1765
  */
1766
+ declare function Sentinel(): FinalAction;
1571
1767
 
1572
- interface BotEntry {
1573
- name: string;
1574
- ai: WizardFunction;
1575
- description: string;
1576
- tier?: number;
1577
- group: string;
1578
- }
1579
- interface BotGroup {
1580
- label: string;
1581
- bots: BotEntry[];
1582
- }
1583
1768
  /**
1584
- * All bots organized by progression line.
1585
- * Each group contains bots from the same archetype, ordered by tier.
1769
+ * Bot: Golem
1770
+ *
1771
+ * BEHAVIOR: Stationary fortress with perfect shield timing and devastating
1772
+ * counterattacks during enemy vulnerability windows. Reads enemy cast/GCD
1773
+ * state to time punish missiles that land when the enemy can't shield.
1774
+ * Handles multi-missile volleys by holding shield through consecutive impacts.
1775
+ * Uses progressive cast-cancel thresholds for optimal damage trading.
1776
+ *
1777
+ * KEY IMPROVEMENTS OVER SENTINEL:
1778
+ * - Counterattack punish: fires during enemy cast/GCD recovery
1779
+ * - Multi-threat volley awareness: holds shield through consecutive hits
1780
+ * - Progressive cast-cancel: graduated damage thresholds
1781
+ * - Perfect shield timing: uses ticksToStartShield precisely
1782
+ *
1783
+ * PROGRESSION LINE: Turtle → Sentinel → Golem
1784
+ * TIER: 3 (elite Defensive line)
1586
1785
  */
1587
- declare const BOT_GROUPS: BotGroup[];
1588
- declare const ALL_BOTS: BotEntry[];
1786
+ declare function Golem(): FinalAction;
1589
1787
 
1590
1788
  /**
1591
- * VIBEMANCER - STATE HOOKS
1789
+ * Bot: Shadowblade
1592
1790
  *
1593
- * React-style hooks for reading game state.
1594
- * These handle the "subconscious" perception that humans do instinctively.
1791
+ * BEHAVIOR: Melee assassin. Blinks to the enemy, then lands devastating point-blank
1792
+ * stab attacks (15 damage, 30u range, ~60 tick cast = 2-hit kill). Runs directly
1793
+ * at the enemy with minimal strafe, shields undodgeable threats. The entire
1794
+ * strategy is: get close, stab, kill. Simple and brutal.
1595
1795
  *
1596
- * UNITS REFERENCE (100 ticks = 1 second):
1597
- * Position: absolute world coordinates, 0-800 on each axis (800×800 arena)
1598
- * Velocity: units per tick on each axis (player max speed = 1 u/t)
1599
- * Health: hit points (max 60)
1600
- * Ticks: game ticks (10ms each, 100/sec). Divide by 100 for seconds.
1601
- * Shield: block multiplier 0.0-0.9 (0.9 = blocks 90% damage)
1796
+ * PROGRESSION LINE: Shadowblade Nightblade Voidblade
1797
+ * - Shadowblade (tier 1): Offensive blink, melee stabs, basic shield
1798
+ * - Nightblade (tier 2): + missile-aware blinks, adaptive stabs, timed defense
1799
+ * - Voidblade (tier 3): Future — perfect assassination timing, inescapable engages
1800
+ *
1801
+ * TIER: 1 (base)
1602
1802
  */
1803
+ declare function Shadowblade(): FinalAction;
1603
1804
 
1604
1805
  /**
1605
- * Get your current health (0-60). Wizard dies at 0.
1606
- */
1607
- declare function useHealth(): number;
1608
- /**
1609
- * Get your current position as {x, y} in world coordinates (0-800).
1610
- * Position is clamped to [5, 795] (arena bounds minus wizard radius).
1611
- */
1612
- declare function usePosition(): Position;
1613
- /**
1614
- * Get your current velocity as {x, y} in units/tick.
1615
- * Max magnitude is 1 u/t when idle, 0.5 u/t when casting, 0 when channeling shield.
1616
- */
1617
- declare function useVelocity(): Velocity;
1618
- /**
1619
- * Get your current status:
1620
- * - 'idle': free to act
1621
- * - 'casting': casting a spell (missile or blink). Can move at 50% speed.
1622
- * - 'channeling': channeling shield. Cannot move. Cancel anytime with cancel().
1623
- * - 'gcd_locked': global cooldown after spell. Can move at full speed, but cannot cast.
1624
- */
1625
- declare function useStatus(): 'idle' | 'casting' | 'channeling' | 'gcd_locked';
1626
- /**
1627
- * Get ticks until you can start a new spell.
1806
+ * Bot: Nightblade
1628
1807
  *
1629
- * Returns 0 when idle or channeling (shield can be canceled immediately).
1630
- * During casting: remaining cast ticks. During GCD: remaining GCD ticks.
1808
+ * BEHAVIOR: Enhanced melee assassin. Same aggressive engagement as Shadowblade
1809
+ * blinks directly to the enemy and stabs for 15 damage (2-hit kill). The tier 2
1810
+ * upgrade is PREEMPTIVE DEFENSE: Nightblade watches the enemy's cast bar and
1811
+ * shields before a point-blank missile is even launched. At melee range, missiles
1812
+ * arrive almost instantly after launch — too fast to react. Nightblade anticipates
1813
+ * the threat. Also has emergency blink and proper channeling management.
1631
1814
  *
1632
- * Note: 100 ticks = 1 second.
1633
- */
1634
- declare function useTicksUntilReady(): number;
1635
- /**
1636
- * Get current shield block multiplier.
1815
+ * PROGRESSION LINE: Shadowblade Nightblade Voidblade
1816
+ * - Shadowblade (tier 1): Offensive blink, melee stabs, basic shield (reactive only)
1817
+ * - Nightblade (tier 2): + preemptive shield vs enemy casts, emergency blink
1818
+ * - Voidblade (tier 3): Future — perfect assassination timing, inescapable engages
1637
1819
  *
1638
- * Returns 0 if not channeling shield.
1639
- * Returns 0.3-0.9 if channeling: starts at 0.9 (blocks 90%), decays by 0.2/sec,
1640
- * minimum 0.3 (blocks 30%). The remaining damage gets through:
1641
- * actualDamage = incomingDamage × (1 - shieldStrength).
1820
+ * TIER: 2 (enhanced Shadowblade)
1642
1821
  */
1643
- declare function useShieldStrength(): number;
1822
+ declare function Nightblade(): FinalAction;
1823
+
1644
1824
  /**
1645
- * Get blink cooldown remaining in ticks. Returns 0 if ready to blink.
1825
+ * Bot: Voidblade
1646
1826
  *
1647
- * Cooldown scales with distance used:
1648
- * - 100 units ~100 ticks (1s)
1649
- * - 300 units (max range) 2000 ticks (20s)
1827
+ * BEHAVIOR: Reactive counter-puncher. Shields everything, then fires quick stabs
1828
+ * during enemy vulnerability windows (GCD/casting) when they can't shield back.
1829
+ * At melee range, the shield blocks ~90% of incoming damage while Voidblade's
1830
+ * counter-stabs land at full damage — winning through attrition.
1650
1831
  *
1651
- * Note: 100 ticks = 1 second.
1652
- */
1653
- declare function useBlinkCooldown(): number;
1654
- /**
1655
- * Get currently casting spell, or null if not casting.
1656
- * Returns 'missile', 'shield', or 'blink'.
1657
- */
1658
- declare function useCastingSpell(): 'missile' | 'shield' | 'blink' | null;
1659
- /**
1660
- * Get cast progress as {current, total} in ticks, or null if not casting.
1832
+ * CORE LOOP (melee range):
1833
+ * 1. Enemy casts missile → Voidblade blink-dodges (100% avoid) or shields (90% block)
1834
+ * 2. Enemy enters GCD → Voidblade fires quick stab (lands unblocked)
1835
+ * 3. Voidblade enters GCD → enemy recovers → repeat
1661
1836
  *
1662
- * current = ticks elapsed, total = ticks needed. When current >= total, spell fires.
1663
- * Note: 100 ticks = 1 second.
1664
- */
1665
- declare function useCastProgress(): {
1666
- current: number;
1667
- total: number;
1668
- } | null;
1669
- /**
1670
- * Get enemy wizard state.
1837
+ * KEY IMPROVEMENTS OVER NIGHTBLADE:
1838
+ * - Blink-dodge priority: avoids 100% of damage when blink available, shields as fallback
1839
+ * - Reads enemy vulnerability to time counter-stabs perfectly
1840
+ * - Cancel-into-defense: aborts own cast if enemy missile incoming
1841
+ * - Punish budget: sizes stabs to fit exactly in the vulnerability window
1671
1842
  *
1672
- * Returns position, velocity, health, status, casting spell, and shield strength.
1673
- * Note: you cannot see the enemy's missile configs or exact cooldown timers —
1674
- * only their status and what's visible on the field.
1675
- */
1676
- declare function useEnemy(): EnemyState;
1677
- /**
1678
- * Get all your active (in-flight) projectiles.
1679
- * Each has position, rotation (degrees), speed (u/t), turnRate, remainingTicks.
1680
- */
1681
- declare function useMyProjectiles(): ProjectileState[];
1682
- /**
1683
- * Get total damage you've dealt this match.
1684
- */
1685
- declare function useDamageDealt(): number;
1686
- /**
1687
- * Get total damage you've taken this match.
1688
- */
1689
- declare function useDamageTaken(): number;
1690
- /**
1691
- * Get the tick number when you last took damage. Returns 0 if never hit.
1692
- * Compare with useTick() to get ticks since last hit.
1693
- */
1694
- declare function useLastHitTick(): number;
1695
- /**
1696
- * Get arena dimensions. Default: {width: 800, height: 800}.
1697
- * Wizards are clamped to [5, 795] on each axis (radius = 5).
1843
+ * PROGRESSION LINE: Shadowblade Nightblade Voidblade
1844
+ * TIER: 3 (elite Melee line)
1698
1845
  */
1699
- declare function useArenaSize(): {
1700
- width: number;
1701
- height: number;
1702
- };
1846
+ declare function Voidblade(): FinalAction;
1847
+
1703
1848
  /**
1704
- * Get current game tick (starts at 0, increments each tick).
1705
- * 100 ticks = 1 second. Match ends at 30,000 ticks (5 minutes).
1849
+ * Bot: Bonemancer
1850
+ *
1851
+ * BEHAVIOR: Stands still and fires slow, homing missiles constantly. Every missile
1852
+ * tracks the enemy with turnRate 2 — they curve relentlessly toward the target.
1853
+ * No movement, no shields, just an unending stream of seeking projectiles. The
1854
+ * missiles are slow (speed 3) but long-lived (300 ticks) and will chase you across
1855
+ * the entire arena.
1856
+ *
1857
+ * NAMING RATIONALE: Named after Diablo 2's Bone Necromancer ("Bonemancer"), whose
1858
+ * signature spell Bone Spirit is a slow-moving, auto-tracking projectile that hunts
1859
+ * enemies relentlessly. That's exactly what this bot does — it stands in place and
1860
+ * spams seeking missiles. The homing behavior is the key identity: these aren't
1861
+ * aimed shots, they're heat-seeking spirits that chase you down. Every D2 player
1862
+ * knows the Bonemancer — it's one of the most iconic builds.
1863
+ *
1864
+ * PROGRESSION LINE: Bonemancer → Lich → Archlich
1865
+ * - Bonemancer (tier 1): Stationary, spams slow homing missiles
1866
+ * - Lich (tier 2): Future — enhanced homing with adaptive missiles and defense
1867
+ * - Archlich (tier 3): Future — master of tracking magic, undodgeable death swarm
1868
+ * The progression follows the D2 necromancer power fantasy: from bone apprentice
1869
+ * to undead overlord, each tier's missiles become harder to escape.
1870
+ *
1871
+ * TIER: 1 (base)
1706
1872
  */
1707
- declare function useTick(): number;
1873
+ declare function Bonemancer(): FinalAction;
1874
+
1708
1875
  /**
1709
- * Get analyzed threats from all incoming enemy projectiles.
1710
- * Sorted by ticksToImpact (soonest first). Only includes missiles within 500 units
1711
- * or that are predicted to hit.
1876
+ * Bot: Lich
1712
1877
  *
1713
- * Each threat includes:
1714
- * - ticksToImpact: ticks until hit (Infinity if will miss)
1715
- * - willHit: true if missile hits your current position
1716
- * - canDodgeLeft/Right: whether strafing perpendicular to missile heading works
1717
- * - canOutrun: whether moving away from missile escapes it
1718
- * - bestDodgeDirection: {x, y} unit vector to dodge optimally, or null if undodgeable
1719
- * - canBlockInTime: whether you can raise shield before impact
1720
- * - ticksToStartShield: when to START channeling shield to block in time
1878
+ * BEHAVIOR: Homing missile specialist with strong-tracking adaptive missiles.
1879
+ * Uses fitMissileToBudget with minTurnRate 1.0 higher than other bots (0.5)
1880
+ * producing missiles with superior tracking at the cost of some damage/speed.
1881
+ * Strafing launches missiles from different angles, creating multi-angle pressure.
1882
+ *
1883
+ * Shields undodgeable/critical threats, emergency blinks. Cancels missile cast
1884
+ * only for lethal incoming damage.
1885
+ *
1886
+ * KEY DIFFERENCES FROM BONEMANCER:
1887
+ * - Bonemancer: stationary, no defense, fixed d=7/s=3/t=2/dur=300
1888
+ * - Lich: mobile, full defense, adaptive strong-tracking homing missiles
1889
+ *
1890
+ * PROGRESSION LINE: Bonemancer → Lich → Archlich
1891
+ * - Bonemancer (tier 1): Stationary, spams fixed slow homing missiles, no defense
1892
+ * - Lich (tier 2): Mobile + defense, adaptive strong-tracking missiles (minTurnRate 1.0)
1893
+ * - Archlich (tier 3): Future — converging web patterns, impossible to escape
1894
+ *
1895
+ * TIER: 2 (enhanced Bonemancer)
1721
1896
  */
1722
- declare function useThreats(): AnalyzedThreat[];
1897
+ declare function Lich(): FinalAction;
1898
+
1723
1899
  /**
1724
- * Get the most imminent threat, or null if no threats.
1725
- * Shorthand for useThreats()[0].
1900
+ * Bot: Archlich
1901
+ *
1902
+ * BEHAVIOR: Lich's proven core (mobile homing specialist) plus vulnerability
1903
+ * exploitation. Defense, movement, and standard offense are identical to Lich.
1904
+ * The T3 advantage: when the enemy is locked in GCD/cast, fires fast straight
1905
+ * punish missiles that land during the vulnerability window.
1906
+ *
1907
+ * PROGRESSION LINE: Bonemancer → Lich → Archlich
1908
+ * TIER: 3 (elite Homing line)
1726
1909
  */
1727
- declare function useClosestThreat(): AnalyzedThreat | null;
1910
+ declare function Archlich(): FinalAction;
1911
+
1728
1912
  /**
1729
- * Get your missiles analyzed from the enemy's perspective.
1730
- * Useful to predict when enemy will shield/dodge your attacks.
1913
+ * Bot: Flamecaller
1914
+ *
1915
+ * BEHAVIOR: Long-range homing missile caster with fixed missile config.
1916
+ * Maintains 350u distance, strafes to dodge, and fires standard homing
1917
+ * missiles (d=10, s=5, t=1, dur=180). Shields undodgeable threats,
1918
+ * emergency blinks. A straightforward ranged caster that trades
1919
+ * consistency for adaptability.
1920
+ *
1921
+ * PROGRESSION LINE: Flamecaller → Pyromancer → Infernalist
1922
+ * - Flamecaller (tier 1): Fixed homing missiles, basic strafe and defense
1923
+ * - Pyromancer (tier 2): + adaptive fitting, cast canceling, smart fallbacks
1924
+ * - Infernalist (tier 3): Future — overwhelming adaptive fire
1925
+ *
1926
+ * TIER: 1 (base)
1731
1927
  */
1732
- declare function useMyThreatsToEnemy(): AnalyzedThreat[];
1928
+ declare function Flamecaller(): FinalAction;
1733
1929
 
1734
1930
  /**
1735
- * VIBEMANCER - THREAT ANALYSIS
1931
+ * Bot: Pyromancer
1736
1932
  *
1737
- * Pre-computes threat information for incoming projectiles.
1738
- * This handles the "subconscious" perception of missile trajectories.
1933
+ * BEHAVIOR: Adaptive homing missile specialist at long range. Maintains 400u
1934
+ * distance, strafes to dodge, and uses fitMissileToBudget with minTurnRate 0.5
1935
+ * to fire the highest-damage homing missile that fits in the safe window.
1936
+ * Shields undodgeable threats, emergency blinks. A versatile ranged caster
1937
+ * that adapts its missiles to the situation.
1938
+ *
1939
+ * PROGRESSION LINE: Flamecaller → Pyromancer → Infernalist
1940
+ * - Flamecaller (tier 1): Fixed homing missiles, basic strafe and defense
1941
+ * - Pyromancer (tier 2): + adaptive fitting, cast canceling, smart fallbacks
1942
+ * - Infernalist (tier 3): Future — overwhelming adaptive fire
1943
+ *
1944
+ * TIER: 2 (enhanced Flamecaller)
1739
1945
  */
1946
+ declare function Pyromancer(): FinalAction;
1740
1947
 
1741
1948
  /**
1742
- * Analyze all threats from enemy projectiles.
1949
+ * Bot: Infernalist
1743
1950
  *
1744
- * @param myPos - Current position of the wizard
1745
- * @param projectiles - All projectiles in the game
1746
- * @param myProjectiles - Only the bot's own projectiles (used for filtering)
1747
- * @param ticksUntilReady - Ticks until wizard can start a new action
1748
- * @returns Array of analyzed threats sorted by ticksToImpact (soonest first)
1951
+ * BEHAVIOR: Rapid-fire caster that exploits warmup bonus for accelerating DPS.
1952
+ * Fires consistent homing missiles to build warmup, punishes vulnerability windows
1953
+ * with warmup-boosted fast casts. Proactive blink kiting when enemy closes.
1954
+ *
1955
+ * KEY IMPROVEMENTS OVER PYROMANCER:
1956
+ * - Warmup exploitation: always passes lastMissileConfig for bonus
1957
+ * - Punish mode: straight missiles during enemy vulnerability
1958
+ * - Proactive blink kiting: monitors closing rate
1959
+ * - Progressive cast-cancel: graduated thresholds
1960
+ *
1961
+ * PROGRESSION LINE: Flamecaller → Pyromancer → Infernalist
1962
+ * TIER: 3 (elite Caster line)
1749
1963
  */
1750
- declare function analyzeThreats(myPos: Position, projectiles: ProjectileState[], myProjectiles: ProjectileState[], ticksUntilReady: number): AnalyzedThreat[];
1964
+ declare function Infernalist(): FinalAction;
1751
1965
 
1752
1966
  /**
1753
- * VIBEMANCER - ACTION BUILDERS
1967
+ * Bot: Spellshot
1754
1968
  *
1755
- * Fluent API for constructing bot actions with type-safe chaining.
1969
+ * BEHAVIOR: Uses interceptAngle to calculate where the enemy will be and fires
1970
+ * fast, non-homing missiles (speed 8, turnRate 0) along the predicted path.
1971
+ * Strafes at medium range (300-400), shields undodgeable threats, emergency
1972
+ * blinks when shield isn't available. The key mechanic is PREDICTION — these
1973
+ * missiles don't track, they go exactly where you calculated the enemy would be.
1756
1974
  *
1757
- * UNITS REFERENCE (100 ticks = 1 second):
1758
- * Position: absolute world coordinates, 0-800 on each axis (800×800 arena)
1759
- * Movement: direction vector, magnitude auto-normalized to max 100
1760
- * Speed: units per tick (player moves at 1 unit/tick = 100 units/sec)
1761
- * Duration: ticks (divide by 100 for seconds)
1762
- * Angles: degrees (0°=right, 90°=down, 180°=left, 270°=up)
1763
- * Damage: raw HP removed on hit (wizard has 60 HP)
1764
- * Turn rate: degrees per tick the missile can rotate
1975
+ * NAMING RATIONALE: "Spellshot" a spell that is a single, precisely aimed shot.
1976
+ * Like a sniper's "called shot" but magical. The defining feature is the intercept
1977
+ * calculation: this bot doesn't fire tracking missiles, it calculates the exact
1978
+ * angle needed to hit a moving target. "Shot" implies precision, singular impact,
1979
+ * and skill-based aiming everything this bot is about.
1980
+ *
1981
+ * PROGRESSION LINE: Spellshot Spelltracer Spellseeker
1982
+ * - Spellshot (tier 1): Basic intercept prediction, non-homing missiles
1983
+ * - Spelltracer (tier 2): Future — predictive homing (missiles that lead AND track)
1984
+ * - Spellseeker (tier 3): Future — perfect prediction, multi-angle attacks
1985
+ * The naming progression: shot (single bullet) → tracer (bullet that tracks a path)
1986
+ * → seeker (actively hunts). Each tier adds more intelligence to the projectile,
1987
+ * evolving from "I calculate where you'll be" to "my missile calculates where you'll be."
1988
+ *
1989
+ * NOTE: A separate future archetype "Spellslinger" (volume-of-fire) is reserved
1990
+ * for a rapid-fire bot that prioritizes quantity over prediction.
1991
+ *
1992
+ * TIER: 1 (base)
1765
1993
  */
1994
+ declare function Spellshot(): FinalAction;
1766
1995
 
1767
1996
  /**
1768
- * Channel a shield that blocks incoming damage.
1997
+ * Bot: Spelltracer
1769
1998
  *
1770
- * Starts at 90% block, decays by 20% per second, minimum 30%.
1771
- * Takes 20 ticks (0.2s) to activate. Movement is disabled while channeling.
1772
- * Cancel anytime with cancel(). Triggers 100-tick (1s) GCD after cancel.
1999
+ * BEHAVIOR: Enhanced ranged sniper with adaptive missile fitting and intercept
2000
+ * prediction. Uses fitMissileToBudget to find the highest-damage fast missile
2001
+ * that fits the safe window, then fires it along the predicted intercept angle.
2002
+ * Maintains medium-long range (300-450), shields undodgeable threats with proper
2003
+ * timing, emergency blinks, and distance blinks when cornered. The key mechanic
2004
+ * is still PREDICTION — but now with adaptive damage optimization.
1773
2005
  *
1774
- * Can chain .move() movement applies during the 20-tick cast, NOT during channel.
2006
+ * PROGRESSION LINE: Spellshot Spelltracer Spellseeker
2007
+ * - Spellshot (tier 1): Fixed config intercept prediction, non-homing missiles
2008
+ * - Spelltracer (tier 2): + adaptive fitting, timed defense, distance management
2009
+ * - Spellseeker (tier 3): Future — perfect prediction, multi-angle attacks
1775
2010
  *
1776
- * @example
1777
- * return shield(); // shield and stay still
1778
- * return shield().move(100, 0); // move right while cast starts
2011
+ * TIER: 2 (enhanced Spellshot)
1779
2012
  */
1780
- declare function shield(): ActionBuilder;
2013
+ declare function Spelltracer(): FinalAction;
2014
+
1781
2015
  /**
1782
- * Cast a missile spell.
2016
+ * Bot: Spellseeker
1783
2017
  *
1784
- * Cast time scales with damage, speed, duration, and turn rate — bigger missiles
1785
- * take longer to cast. While casting you move at 50% speed. After firing, 100-tick
1786
- * (1s) GCD before next spell.
2018
+ * BEHAVIOR: Elite sniper that uses intercept-aimed straight missiles during vulnerability
2019
+ * windows. Combines Spelltracer's adaptive fitting with precise lead-position aiming
2020
+ * and vulnerability exploitation. Straight punish missiles at sniper range are nearly
2021
+ * unavoidable. Proactive distance control via closing rate detection.
1787
2022
  *
1788
- * Repeated similar missiles cast 20% faster (warmup bonus). Switching styles
1789
- * incurs a 20% penalty.
2023
+ * KEY IMPROVEMENTS OVER SPELLTRACER:
2024
+ * - Intercept-aimed punish: getLeadPosition + straight missiles during vulnerability
2025
+ * - Proactive distance blink: monitors closing rate, blinks before danger zone
2026
+ * - Progressive cast-cancel: graduated damage thresholds
2027
+ * - Warmup exploitation: always passes lastMissileConfig
1790
2028
  *
1791
- * Can chain .move() for simultaneous movement while casting.
2029
+ * PROGRESSION LINE: Spellshot Spelltracer Spellseeker
2030
+ * TIER: 3 (elite Sniper line)
2031
+ */
2032
+ declare function Spellseeker(): FinalAction;
2033
+
2034
+ /**
2035
+ * Bot: Battlemage
1792
2036
  *
1793
- * @param config - Missile stats:
1794
- * - damage: HP removed on hit (1-60 typical). Also sets hitbox: radius = 2 + 0.1×damage.
1795
- * - speed: units/tick (min 1.5). Player moves at 1 u/t, so 5 = 5× player speed.
1796
- * - duration: ticks the missile lives (min 10). Range speed × duration.
1797
- * - turnRate: degrees/tick of homing (0 = straight line, 3 = moderate homing, 5+ = strong).
1798
- * Negative = no homing + minor speed cost reduction.
1799
- * @param ai - Called every tick to control missile steering. Receives:
1800
- * - missileState: the missile's position, rotation (degrees), speed, remainingTicks
1801
- * - worldState: full game state (all wizards, projectiles)
1802
- * - random(): seeded PRNG [0, 1)
1803
- * Return { turnToward: {x, y} } to home toward a position, or {} to fly straight.
1804
- * @param direction - Launch angle in degrees (0°=right, 90°=down, 180°=left, 270°=up).
1805
- * Tip: use Math.atan2(dy, dx) * (180 / Math.PI) to aim at a target.
2037
+ * BEHAVIOR: Balanced mid-range duelist. Shields undodgeable threats, interrupts
2038
+ * enemy casts with quick missiles, saves blink for emergencies OR gap-closing.
2039
+ * Switches between quick (10 dmg, fast) and heavy (15 dmg, slow) missile configs
2040
+ * based on safety window and range. Will aggressively trade hits when health allows.
2041
+ * The unique trait is cast-interruption: fires quick missiles specifically when the
2042
+ * enemy is casting, punishing long cast times.
1806
2043
  *
1807
- * @example
1808
- * // Straight missile aimed at enemy
1809
- * const angle = Math.atan2(dy, dx) * (180 / Math.PI);
1810
- * return missile({damage: 15, speed: 6, duration: 200, turnRate: 0}, () => ({}), angle);
2044
+ * PROGRESSION LINE: Battlemage → Warmage → Archmage
2045
+ * - Battlemage (tier 1): Quick/heavy fixed configs, cast interruption, basic defense
2046
+ * - Warmage (tier 2): Adaptive missile fitting (fitMissileToBudget), smarter attacks
2047
+ * - Archmage (tier 3): Future supreme duelist, perfect tactical mastery
1811
2048
  *
1812
- * // Homing missile that tracks enemy
1813
- * return missile(
1814
- * {damage: 10, speed: 5, duration: 300, turnRate: 3},
1815
- * ({worldState}) => ({turnToward: worldState.enemies[0]?.position}),
1816
- * angle,
1817
- * );
2049
+ * TIER: 1 (base)
1818
2050
  */
1819
- declare function missile(config: MissileConfig, ai: MissileAIFunction, direction: number): ActionBuilder;
2051
+ declare function Battlemage(): FinalAction;
2052
+
1820
2053
  /**
1821
- * Teleport to an absolute position on the arena.
1822
- *
1823
- * Max range: 300 units from current position (clamped by engine if further).
1824
- * Cast time: 10 ticks (0.1s). Cooldown scales with distance:
1825
- * - 100 units → 100 ticks (1s)
1826
- * - 300 units → 2000 ticks (20s)
2054
+ * Bot: Warmage
1827
2055
  *
1828
- * Cannot chain .move() blink IS the movement.
2056
+ * BEHAVIOR: Enhanced Battlemage with adaptive missile fitting. Uses fitMissileToBudget
2057
+ * to maximize damage within safe attack windows instead of fixed quick/heavy configs.
2058
+ * Same close-range playstyle: shields undodgeable threats, blinks to close distance
2059
+ * or escape, aggressive hit-trading when health allows. The adaptive fitting means
2060
+ * every attack is optimized for the current situation — no wasted cast time.
1829
2061
  *
1830
- * @param x - Target X position (0-800, absolute world coordinate)
1831
- * @param y - Target Y position (0-800, absolute world coordinate)
2062
+ * PROGRESSION LINE: Battlemage Warmage Archmage
2063
+ * - Battlemage (tier 1): Quick/heavy fixed configs, cast interruption, basic defense
2064
+ * - Warmage (tier 2): Adaptive missile fitting, optimized damage windows
2065
+ * - Archmage (tier 3): Future — supreme duelist, perfect tactical mastery
1832
2066
  *
1833
- * @example
1834
- * return blink(400, 400); // blink to center
1835
- * return blink(enemy.position.x, enemy.position.y); // blink to enemy
2067
+ * TIER: 2 (enhanced Battlemage)
1836
2068
  */
1837
- declare function blink(x: number, y: number): FinalAction;
2069
+ declare function Warmage(): FinalAction;
2070
+
1838
2071
  /**
1839
- * Cancel current cast or channel (e.g. stop shielding to attack).
2072
+ * Bot: Archmage
1840
2073
  *
1841
- * Canceling a cast/channel triggers 100-tick (1s) GCD.
1842
- * Can chain .move() for simultaneous movement.
2074
+ * BEHAVIOR: Versatile duelist that adapts missile choice based on distance and HP.
2075
+ * Close range → straight missiles (no turn cost = more damage). Mid/far range → homing.
2076
+ * Uses dual-blink aggressively (gap-close during vulnerability, escape when trade is bad).
2077
+ * HP-aware: ahead → aggressive close range; behind → defensive ranged kiting.
1843
2078
  *
1844
- * @example
1845
- * return cancel().move(-100, 0); // cancel and dodge left
2079
+ * KEY IMPROVEMENTS OVER WARMAGE:
2080
+ * - Range-adaptive missiles: straight close, homing far
2081
+ * - Vulnerability-timed blinks: gap-close during enemy cast/GCD
2082
+ * - HP-aware aggression: adjusts distance + risk tolerance based on HP differential
2083
+ * - Progressive cast-cancel: graduated thresholds
2084
+ *
2085
+ * PROGRESSION LINE: Battlemage → Warmage → Archmage
2086
+ * TIER: 3 (elite Duelist line)
1846
2087
  */
1847
- declare function cancel(): ActionBuilder;
2088
+ declare function Archmage(): FinalAction;
2089
+
1848
2090
  /**
1849
- * Move in a direction without casting any spell.
1850
- *
1851
- * This is a **direction vector**, not a target position. The engine normalizes
1852
- * the magnitude to max 100, then moves at 1 unit/tick (100 units/sec).
1853
- * Positive X = right, positive Y = down.
2091
+ * Bot: Stormchaser
1854
2092
  *
1855
- * To move toward a target position, subtract your position:
1856
- * move(target.x - myPos.x, target.y - myPos.y)
2093
+ * BEHAVIOR: Fights aggressively while managing defense intelligently. Uses
2094
+ * two fixed missile configs (standard homing + quick attack) with predictive
2095
+ * homing missile AI (interceptAngle on the missile itself). Blink-dodges
2096
+ * incoming threats, shields when blink is on cooldown. Tight distance
2097
+ * management (350 units, ±30 band).
1857
2098
  *
1858
- * @param x - Horizontal direction (positive = right, negative = left)
1859
- * @param y - Vertical direction (positive = down, negative = up)
2099
+ * PROGRESSION LINE: Stormchaser Stormcaller Stormforger
2100
+ * - Stormchaser (tier 1): Fixed missiles, predictive homing AI, blink-dodge
2101
+ * - Stormcaller (tier 2): + fitMissileToBudget, predictive homing, optimized damage
2102
+ * - Stormforger (tier 3): Future — supreme berserker, perfect aggression
1860
2103
  *
1861
- * @example
1862
- * return move(100, 0); // move right
1863
- * return move(enemy.position.x - myPos.x, enemy.position.y - myPos.y); // move toward enemy
1864
- */
1865
- declare function move(x: number, y: number): FinalAction;
1866
- /**
1867
- * Do nothing — no action, no movement.
1868
- */
1869
- declare function idle(): FinalAction;
1870
- /**
1871
- * Extract WizardActions from a FinalAction.
1872
- * Used by the engine to get the actual action.
2104
+ * TIER: 1 (base)
1873
2105
  */
1874
- declare function extractAction(finalAction: FinalAction): WizardActions;
2106
+ declare function Stormchaser(): FinalAction;
1875
2107
 
1876
2108
  /**
1877
- * VIBEMANCER - BOT WRAPPER
2109
+ * Bot: Stormcaller
2110
+ *
2111
+ * BEHAVIOR: Enhanced Stormchaser with adaptive missile fitting (fitMissileToBudget)
2112
+ * AND predictive homing missiles. Combines aggressive fighting philosophy with
2113
+ * optimized damage output. Uses budget-based missile fitting to maximize damage
2114
+ * within safe windows. Falls back to quick missiles under pressure. Same smart
2115
+ * trade/shield decisions as Stormchaser but with better resource usage.
2116
+ *
2117
+ * PROGRESSION LINE: Stormchaser → Stormcaller → Stormforger
2118
+ * - Stormchaser (tier 1): Adaptive missiles, smart trading, aggressive defense
2119
+ * - Stormcaller (tier 2): + fitMissileToBudget, predictive homing, optimized damage
2120
+ * - Stormforger (tier 3): Future — supreme berserker, perfect aggression
1878
2121
  *
1879
- * Utilities to convert between old-style (WizardFunction) and new-style (BotFunction) bots.
2122
+ * TIER: 2 (enhanced Stormchaser)
1880
2123
  */
2124
+ declare function Stormcaller(): FinalAction;
1881
2125
 
1882
2126
  /**
1883
- * Convert a new-style bot (using hooks) to an old-style bot (WizardFunction).
2127
+ * Bot: Stormforger
1884
2128
  *
1885
- * This allows new bots to work with the existing simulation.
2129
+ * BEHAVIOR: Enhanced Stormcaller with vulnerability exploitation. Takes the exact
2130
+ * Stormcaller foundation (adaptive missile fitting + predictive homing) and adds
2131
+ * a punish mode that fires fast straight missiles when the enemy is locked in
2132
+ * GCD or cast animation. During vulnerability windows, uses getLeadPosition for
2133
+ * accurate straight shots that arrive before the enemy can react.
1886
2134
  *
1887
- * @example
1888
- * const NewBot: BotFunction = () => {
1889
- * const health = useHealth();
1890
- * return health < 10 ? shield() : idle();
1891
- * };
2135
+ * PROGRESSION LINE: Stormchaser → Stormcaller → Stormforger
2136
+ * - Stormchaser (tier 1): Adaptive missiles, smart trading, aggressive defense
2137
+ * - Stormcaller (tier 2): + fitMissileToBudget, predictive homing, optimized damage
2138
+ * - Stormforger (tier 3): + vulnerability exploitation, punish missiles during enemy GCD
1892
2139
  *
1893
- * // Convert to work with simulate()
1894
- * const oldStyleBot = wrapNewBot(NewBot);
1895
- * simulate(oldStyleBot, opponent);
1896
- */
1897
- declare function wrapNewBot(newBot: BotFunction): WizardFunction;
1898
- /**
1899
- * Type guard to check if a bot is a new-style BotFunction.
1900
- * New-style bots have 0 parameters, old-style bots have at least 1.
2140
+ * TIER: 3 (elite Berserker line)
1901
2141
  */
1902
- declare function isNewStyleBot(bot: WizardFunction | BotFunction): bot is BotFunction;
2142
+ declare function Stormforger(): FinalAction;
1903
2143
 
1904
2144
  /**
1905
- * VIBEMANCER - PARAMETER RUNTIME
2145
+ * Bot: Spellspinner
1906
2146
  *
1907
- * Provides the useParam() hook for bots to declare tunable parameters,
1908
- * and the infrastructure for the optimizer to inject/discover parameter values.
2147
+ * BEHAVIOR: Maintains medium range (350 units), strafes constantly to dodge
2148
+ * missiles, and fires homing missiles (damage 10, speed 4, turnRate 2). Heavy
2149
+ * emphasis on movement — 80% strafe intensity when not dodging, 100% when dodging.
2150
+ * Shields only undodgeable threats, emergency blinks when shield isn't available.
2151
+ * The constant circular strafing motion traces patterns like thread being spun.
1909
2152
  *
1910
- * Design: Module-level state (JS is single-threaded, no race conditions).
1911
- * The optimizer sets param values before running a bot, and clears them after.
1912
- * During discovery, all useParam calls are recorded.
2153
+ * NAMING RATIONALE: Like a spider spinning a web of projectiles while circling its
2154
+ * prey. The constant strafing movement pattern traces circles spinning thread
2155
+ * around the arena. "Spell" + "spinner" = a wizard who spins spells around the
2156
+ * battlefield. The kiting behavior (maintaining distance while attacking) creates
2157
+ * a web-like pattern of missiles and movement that traps opponents.
1913
2158
  *
1914
- * ## useParam API
2159
+ * PROGRESSION LINE: Spellspinner → Spellweaver → Spellbinder
2160
+ * - Spellspinner (tier 1): Fixed homing missiles, constant strafe, basic defense
2161
+ * - Spellweaver (tier 2): + adaptive missile fitting, more sophisticated patterns
2162
+ * - Spellbinder (tier 3): Future — inescapable web of magic, perfect distance control
2163
+ * The progression: spinner (raw thread) → weaver (creates patterns) → binder
2164
+ * (constrains and traps). Each tier's projectile web becomes harder to escape.
1915
2165
  *
1916
- * ```typescript
1917
- * // Basic: just a value, no optimizer config
1918
- * const damage = useParam('damage', 15);
2166
+ * TIER: 1 (base)
2167
+ */
2168
+ declare function Spellspinner(): FinalAction;
2169
+
2170
+ /**
2171
+ * Bot: Spellweaver
1919
2172
  *
1920
- * // With range: optimizer searches value ± range (sliding window)
1921
- * const distance = useParam('distance', 350, {range: 150, min: 0});
2173
+ * BEHAVIOR: Enhanced Spellspinner with adaptive missile fitting. Same medium-range
2174
+ * kiting playstyle maintains distance, strafes heavily but uses fitMissileToBudget
2175
+ * to maximize damage within safe attack windows. Always uses homing missiles since
2176
+ * kiting means enemies are always moving. More sophisticated than Spellspinner's
2177
+ * fixed damage/speed/turnRate configuration.
1922
2178
  *
1923
- * // With fixed min/max: optimizer searches [min, max] (fixed bounds)
1924
- * const damage = useParam('damage', 15, {min: 5, max: 25});
2179
+ * NAMING RATIONALE: A weaver creates intricate patterns from raw thread. Where the
2180
+ * Spellspinner produces raw threads of magic (fixed missiles), the Spellweaver
2181
+ * combines them into optimized patterns (adaptive fitting). The name suggests
2182
+ * craftsmanship and sophistication — the same kiting web, but deliberately woven
2183
+ * rather than chaotically spun.
1925
2184
  *
1926
- * // With all: range defines search radius, min/max clamp it
1927
- * const fraction = useParam('fraction', 0.25, {range: 0.2, min: 0, max: 1});
2185
+ * PROGRESSION LINE: Spellspinner Spellweaver Spellbinder
2186
+ * - Spellspinner (tier 1): Fixed homing missiles, constant strafe, basic defense
2187
+ * - Spellweaver (tier 2): + adaptive missile fitting, more sophisticated patterns
2188
+ * - Spellbinder (tier 3): Future — inescapable web, perfect distance control
1928
2189
  *
1929
- * // With custom step count: optimizer tests 20 values instead of default 10
1930
- * const distance = useParam('distance', 500, {range: 200, min: 0, steps: 20});
1931
- * ```
2190
+ * TIER: 2 (enhanced Spellspinner)
2191
+ */
2192
+ declare function Spellweaver(): FinalAction;
2193
+
2194
+ /**
2195
+ * Bot: Spellbinder
1932
2196
  *
1933
- * - **Arg 1** `name` unique parameter name (must be consistent across ticks)
1934
- * - **Arg 2** `value` the actual value used in gameplay. This is what your bot
1935
- * uses during matches. The optimizer script automatically updates this value.
1936
- * - **Arg 3** `config` (optional) optimizer search configuration:
1937
- * - `range` search radius: optimizer checks `value ± range`. The auto-optimizer
1938
- * rewrites `value` after each run, so the search window slides automatically.
1939
- * - `min` / `max` — hard constraints (e.g., distance ≥ 0, fraction ≤ 1).
1940
- * When `range` is omitted, these define fixed search bounds (old-style).
1941
- * - `steps` — how many evenly-spaced values the optimizer tests per pass (default: 10).
1942
- * - `substeps` — steps to use in refinement passes (passes 2+). Set to 0 to freeze
1943
- * after pass 1 (ideal for boolean params). Defaults to `steps` if not specified.
1944
- * - At least `range` or both `min` + `max` must be provided.
2197
+ * BEHAVIOR: Enhanced Spellweaver with vulnerability exploitation. Same medium-range
2198
+ * kiting playstylemaintains distance, strafes heavily, uses fitMissileToBudget
2199
+ * for adaptive homing missiles. The T3 upgrade adds a punish mode that fires fast
2200
+ * straight missiles timed to land while the enemy is locked in a cast or GCD,
2201
+ * when they cannot shield. Defense, movement, and standard offense are identical
2202
+ * to Spellweaver.
1945
2203
  *
1946
- * Without optimizer config, useParam simply returns `value` every tick.
1947
- * With optimizer config, the offline optimizer script can override the value during search.
2204
+ * NAMING RATIONALE: A binder constrains and locks down opponents. Where the
2205
+ * Spellweaver optimizes missile patterns (adaptive fitting), the Spellbinder
2206
+ * reads the enemy's state and punishes vulnerability windows — binding them
2207
+ * to their commitments with unavoidable damage.
1948
2208
  *
1949
- * ## Rules of Hooks
1950
- * useParam follows the same rules as useState/useEffect/etc:
1951
- * - Must be called at the top level of your bot function (not inside conditionals)
1952
- * - Must be called in the same order every tick
1953
- * - Violations are detected and throw errors
2209
+ * PROGRESSION LINE: Spellspinner → Spellweaver → Spellbinder
2210
+ * - Spellspinner (tier 1): Fixed homing missiles, constant strafe, basic defense
2211
+ * - Spellweaver (tier 2): + adaptive missile fitting, more sophisticated patterns
2212
+ * - Spellbinder (tier 3): + vulnerability punish mode with fast straight missiles
2213
+ *
2214
+ * TIER: 3 (elite Spellspinner line)
1954
2215
  */
2216
+ declare function Spellbinder(): FinalAction;
1955
2217
 
1956
2218
  /**
1957
- * Declaration of a tunable parameter, as discovered by the optimizer.
1958
- */
1959
- interface ParamDeclaration {
1960
- name: string;
1961
- value: number;
1962
- range?: number;
1963
- min?: number;
1964
- max?: number;
1965
- steps: number;
1966
- /** Steps to use in refinement passes (passes 2+). 0 = freeze after pass 1. Defaults to `steps`. */
1967
- substeps?: number;
1968
- }
1969
- /**
1970
- * Declare a tunable parameter. Returns the current value (optimizer-injected or the provided value).
2219
+ * VIBEMANCER - BOT REGISTRY
1971
2220
  *
1972
- * @param name - Unique parameter name (consistent across ticks)
1973
- * @param value - The gameplay value. The auto-optimizer rewrites this in source code.
1974
- * @param config - Optional optimizer search configuration
1975
- * @returns The optimizer-injected value during optimization, or `value` during normal play
2221
+ * Single source of truth for all bots, ordered from weakest to strongest.
2222
+ * Run the tournament test to determine the correct ordering.
1976
2223
  *
1977
- * @example
1978
- * // Simple: no optimizer config
1979
- * const damage = useParam('damage', 15);
2224
+ * To reorder: run `npx vitest run tests/bots/tournament.test.ts`
2225
+ * and update the list below based on the results.
1980
2226
  *
1981
- * // With range: optimizer searches value ± range (sliding window)
1982
- * const distance = useParam('distance', 350, {range: 150, min: 0});
2227
+ * BOT NAMING SCHEME (3-tier progression):
2228
+ *
2229
+ * | Group | Tier 1 (base) | Tier 2 (enhanced) | Tier 3 (elite) |
2230
+ * |------------|----------------|-------------------|-----------------|
2231
+ * | Defensive | Turtle | Sentinel | Golem |
2232
+ * | Duelist | Battlemage | Warmage | Archmage |
2233
+ * | Homing | Bonemancer | Lich | Archlich |
2234
+ * | Caster | Flamecaller | Pyromancer | Infernalist |
2235
+ * | Melee | Shadowblade | Nightblade | Voidblade |
2236
+ * | Sniper | Spellshot | Spelltracer | Spellseeker |
2237
+ * | Berserker | Stormchaser | Stormcaller | Stormforger |
2238
+ * | Kiter | Spellspinner | Spellweaver | Spellbinder |
2239
+ *
2240
+ * Standalone: TargetDummy, Critter, Hogger, Rookie, Doombringer, Hero
2241
+ * (Hero is the adaptive SHOWCASE — the #1 reference bot users build their own to beat.
2242
+ * Deliberately kept as a single isolated bot, NOT a tier line: writing adaptive bots is
2243
+ * the game, so the roster ships one exemplar and players make the rest.)
2244
+ * Reserved: Spellslinger (future volume-of-fire archetype)
1983
2245
  *
1984
- * // With fixed min/max: optimizer searches [min, max]
1985
- * const damage = useParam('damage', 15, {min: 5, max: 25});
1986
- */
1987
- declare function useParam(name: string, value: number, config?: {
1988
- range?: number;
1989
- min?: number;
1990
- max?: number;
1991
- steps?: number;
1992
- substeps?: number;
1993
- }): number;
1994
- /**
1995
- * Inject parameter values for the next bot execution.
1996
- * The wrapped bot will read these values via useParam().
1997
- */
1998
- declare function setParamValues(values: Record<string, number>): void;
1999
- /**
2000
- * Clear injected parameter values. useParam() will return its provided value.
2001
- */
2002
- declare function clearParamValues(): void;
2003
- /**
2004
- * Start discovery mode. All subsequent useParam() calls with optimizer config
2005
- * will register their declarations.
2006
- */
2007
- declare function startDiscovery(): void;
2008
- /**
2009
- * Stop discovery mode and return all discovered parameter declarations.
2010
2246
  */
2011
- declare function stopDiscovery(): ParamDeclaration[];
2247
+
2248
+ interface WizardEntry {
2249
+ name: string;
2250
+ ai: WizardFunction;
2251
+ description: string;
2252
+ tier?: number;
2253
+ group: string;
2254
+ }
2255
+ interface WizardGroup {
2256
+ label: string;
2257
+ bots: WizardEntry[];
2258
+ }
2012
2259
  /**
2013
- * Wrap a bot function to inject specific parameter values.
2014
- * The returned function sets params before calling the bot and clears them after.
2260
+ * All bots organized by progression line.
2261
+ * Each group contains bots from the same archetype, ordered by tier.
2015
2262
  */
2016
- declare function wrapWithParams(bot: WizardFunction, params: Record<string, number>): WizardFunction;
2263
+ declare const BOT_GROUPS: WizardGroup[];
2264
+ declare const ALL_BOTS: WizardEntry[];
2017
2265
 
2018
2266
  /**
2019
2267
  * VIBEMANCER - OPTIMIZER UTILITIES
@@ -2317,18 +2565,18 @@ declare class BrowserManualMatchSandbox {
2317
2565
  init(options?: ManualMatchInitOptions): Promise<GameState>;
2318
2566
  /**
2319
2567
  * Advance the match by `request.count` ticks (default 1), updating
2320
- * the player's human actions and any hijacked missile targets first.
2568
+ * the player's human actions and any guided missile targets first.
2321
2569
  */
2322
2570
  step(request?: ManualMatchStepRequest): Promise<StepResult>;
2323
2571
  /**
2324
- * Replace a missile's AI with a worker-local hijack stub that reads
2572
+ * Replace a missile's AI with a worker-local guide stub that reads
2325
2573
  * from `latestHumanMissileTargets[projectileId]`. Subsequent step()
2326
2574
  * calls with `humanMissileTargets` populated for this id steer the
2327
2575
  * missile.
2328
2576
  */
2329
- hijackMissile(projectileId: string): Promise<void>;
2577
+ guideMissile(projectileId: string): Promise<void>;
2330
2578
  /**
2331
- * Restore a hijacked missile's original AI.
2579
+ * Restore a guided missile's original AI.
2332
2580
  */
2333
2581
  releaseMissile(projectileId: string): Promise<void>;
2334
2582
  /**
@@ -2528,7 +2776,7 @@ interface TestBotBuilder {
2528
2776
  * });
2529
2777
  * ```
2530
2778
  */
2531
- declare function testBot(bot: WizardFunction | BotFunction): TestBotBuilder;
2779
+ declare function testBot(bot: WizardFunction): TestBotBuilder;
2532
2780
 
2533
2781
  /**
2534
2782
  * VIBEMANCER - FIGHT TRACE
@@ -2550,7 +2798,7 @@ interface TraceEvent {
2550
2798
  type: TraceEventType;
2551
2799
  detail: string;
2552
2800
  }
2553
- type TraceEventType = 'STATE' | 'FIRE' | 'HIT' | 'HURT' | 'DODGE_START' | 'DODGE_CLOSE' | 'MOVE' | 'ERROR' | 'WARNING';
2801
+ type TraceEventType = 'STATE' | 'FIRE' | 'HIT' | 'HURT' | 'DEATH' | 'LAVA_DEATH' | 'SHIELD_BLOCK' | 'KNOCKBACK' | 'BLINK' | 'DODGE_START' | 'DODGE_CLOSE' | 'MOVE' | 'ERROR' | 'WARNING';
2554
2802
  interface TraceSummary {
2555
2803
  winner: string;
2556
2804
  ticks: number;
@@ -2569,6 +2817,7 @@ interface TraceBotSummary {
2569
2817
  shields: number;
2570
2818
  blinks: number;
2571
2819
  dodgeEncounters: number;
2820
+ causeOfDeath: 'missile' | 'lava' | 'alive' | 'timeout';
2572
2821
  movement: {
2573
2822
  strafe: number;
2574
2823
  approach: number;
@@ -2599,4 +2848,4 @@ declare function diagnoseTrace(events: TraceEvent[], summary: TraceSummary): str
2599
2848
  /** Format diagnostic tips as a human-readable string. */
2600
2849
  declare function formatDiagnosis(tips: string[]): string;
2601
2850
 
2602
- export { ALL_BOTS, ARENA_HEIGHT, ARENA_SIZE, ARENA_WATER_BUFFER, ARENA_WIDTH, type ActionBuilder, type AnalyzedThreat, type AntiHomingParams, Archlich, Archmage, BLINK_CAST_TIME, BLINK_COOLDOWN, BLINK_MAX_COOLDOWN, BLINK_MAX_RANGE, BLINK_MIN_COOLDOWN, BLINK_RANGE, BOT_GROUPS, Battlemage, Bonemancer, type BotContext, type BotEntry, type BotError, type BotFunction, type BotGroup, BrowserManualMatchSandbox, BrowserMatchSandbox, type BrowserSandboxOptions, CASTING_MOVEMENT_MULT, COLLISION_RADIUS, Critter, Doombringer, ENGINE_VERSION, type EnemyState, FIGHT_SPAWN_DISTANCES, type FightResult, type FightStats, type FightWinner, type FinalAction, Flamecaller, GCD_DURATION, type GameConfig, type GameState, Golem, Hogger, type HomingParams, type HookState, Infernalist, type InternalWizardState, Lich, MATCH_DURATION, MAX_HEALTH, MISSILE_BASE_CAST, MISSILE_BASE_RADIUS, MISSILE_DAMAGE_POWER, MISSILE_DAMAGE_RADIUS_SCALE, MISSILE_DAMAGE_SCALE, MISSILE_HOMING_COEFF, MISSILE_MIN_CAST_TIME, MISSILE_MIN_DAMAGE, MISSILE_MIN_DURATION, MISSILE_MIN_SPEED, MISSILE_RADIUS_PER_DAMAGE, MISSILE_SPEED_DURATION_BASELINE, MISSILE_SPEED_DURATION_COEFF, MISSILE_TURN_DURATION_COEFF, MOVEMENT_SPEED, MOVE_SPEED, ManualMatch, type ManualMatchInitOptions, type ManualMatchOptions, type ManualMatchStepRequest, type MatchWinner, type MissileAIFunction, type MissileActions, type MissileConfig, type MissileTemplate, Nightblade, type ParamDeclaration, type Position, type ProjectileState, Pyromancer, type RefObject, Rookie, SHIELD_CAST_TIME, SHIELD_DECAY_PER_SECOND, SHIELD_DECAY_RATE, SHIELD_MAX_BLOCK, SHIELD_MAX_STRENGTH, SHIELD_MIN_BLOCK, SHIELD_MIN_STRENGTH, SPAWN_DISTANCE, type SeekerParams, Sentinel, Shadowblade, type SimulateResult, Spellbinder, Spellseeker, Spellshot, Spellspinner, Spelltracer, Spellweaver, type SpiralParams, type StepResult, Stormcaller, Stormchaser, Stormforger, type StraightParams, TICKS_PER_SECOND, TICK_DURATION_MS, TargetDummy, type TestBotBuilder, type TestFightResult, type TestSimulateResult, type TraceBotSummary, type TraceEvent, type TraceEventType, type TraceSummary, Turtle, type Velocity, Voidblade, WARMUP_DURATION_TOLERANCE, WARMUP_MAX_BONUS, WARMUP_MAX_PENALTY, WARMUP_SPEED_TOLERANCE, WARMUP_TURN_TOLERANCE, WIZARD_HEALTH, WIZARD_RADIUS, Warmage, type WizardActions, type WizardFunction, type WizardState, type WorkerFactory, type WorkerLike, analyzeThreats, angleDiff, angleInRange, angleTo, antiHomingMissile, applyDamage, blink, browserSandboxFight, browserSandboxSimulate, calculateBlinkCooldown, calculateMissileCastTime, calculateMissileRadius, calculateMissileSimilarity, calculateShieldBlock, calculateWarmupMultiplier, cancel, cancelCast, clampPositionToArena, clampToArena, clearHooks, clearParamValues, completeCast, createEntitySeed, createInitialState, createRandom, createWorkerScript, diagnoseTrace, directionAway, directionTo, distanceTo, effectiveTurnRateCost, extractAction, extractStats, extractTraceEvents, fight, findInRange, findNearest, fitMissileToBudget, formatDiagnosis, formatStats, formatTraceEvents, formatTraceSummary, generateCandidates, generateCombos, getAdaptiveMissileConfig, getBotContext, getEffectiveRange, getLeadPosition, getMissileCastTime, getPlayerState, hashCombine, homingMissile, idle, inRange, interceptAngle, isNewStyleBot, magnitude, missile, move, moveInDirection, moveProjectile, moveWizard, narrowRange, nextRandom, normalize, normalizeAngle, predictPosition, resetAllHooks, resolveWizardCollision, runBotWithContext, runWithHooks, scoreFight, scoreFightAsWizard2, seekerMissile, setParamValues, shield, simulate, sortByDistance, spiralMissile, startCast, startDiscovery, stopDiscovery, straightMissile, summarizeTrace, sweptCircleCollision, testBot, tick, updateShield, useArenaSize, useBlinkCooldown, useCastProgress, useCastingSpell, useClosestThreat, useDamageDealt, useDamageTaken, useEffect, useEnemy, useHealth, useLastHitTick, useMemo, useMyProjectiles, useMyThreatsToEnemy, useParam, usePosition, useRef, useShieldStrength, useState, useStatus, useThreats, useTick, useTicksUntilReady, useVelocity, validateHookCall, validateMissileConfig, withBotContext, wrapNewBot, wrapWithParams };
2851
+ export { ALL_BOTS, ARENA_MAX, ARENA_MIN, ARENA_SIZE, ARENA_WATER_BUFFER, type ActionBuilder, type AnalyzedThreat, Archlich, Archmage, BLINK_CAST_TIME, BLINK_COOLDOWN, BLINK_MAX_COOLDOWN, BLINK_MAX_RANGE, BLINK_MIN_COOLDOWN, BLINK_RANGE, BOT_GROUPS, Battlemage, type BlinkEvent, Bonemancer, type BotError, BrowserManualMatchSandbox, BrowserMatchSandbox, type BrowserSandboxOptions, CASTING_MOVEMENT_MULT, COLLISION_RADIUS, type CastCancelEvent, type CastStartEvent, Critter, Doombringer, ENGINE_VERSION, type EnemyState, FIGHT_SPAWN_DISTANCES, type FightResult, type FightStats, type FightWinner, type FinalAction, Flamecaller, GCD_DURATION, type GameConfig, type GameState, Golem, Hogger, type HomingParams, type HookState, Infernalist, type InternalWizardState, KNOCKBACK_DAMAGE_THRESHOLD, KNOCKBACK_DECAY, KNOCKBACK_DELAY, KNOCKBACK_SPEED_PER_DAMAGE, LAVA_BORDER_WIDTH, Lich, MATCH_DURATION, MAX_HEALTH, MISSILE_BASE_CAST, MISSILE_BASE_RADIUS, MISSILE_DAMAGE_POWER, MISSILE_DAMAGE_RADIUS_SCALE, MISSILE_DAMAGE_SCALE, MISSILE_HOMING_COEFF, MISSILE_MIN_CAST_TIME, MISSILE_MIN_DAMAGE, MISSILE_MIN_DURATION, MISSILE_MIN_SPEED, MISSILE_RADIUS_PER_DAMAGE, MISSILE_SPEED_DURATION_BASELINE, MISSILE_SPEED_DURATION_COEFF, MISSILE_TURN_DURATION_COEFF, MOVEMENT_SPEED, MOVE_SPEED, ManualMatch, type ManualMatchInitOptions, type ManualMatchOptions, type ManualMatchStepRequest, type MatchWinner, type MissileAction, type MissileActions, type MissileConfig, type MissileContext, type MissileExpiredEvent, type MissileFunction$1 as MissileFunction, type MissileHitEvent, type MissileLaunchEvent, type MissileOobEvent, type MissileTemplate, Nightblade, type ParamDeclaration, type Position, type ProjectileState, Pyromancer, RULES, RULESET_RANGES, type RefObject, Rookie, SHIELD_CAST_TIME, SHIELD_DECAY_PER_SECOND, SHIELD_DECAY_RATE, SHIELD_MAX_BLOCK, SHIELD_MAX_STRENGTH, SHIELD_MIN_BLOCK, SHIELD_MIN_STRENGTH, SPAWN_DISTANCE, type SeekerParams, Sentinel, Shadowblade, type ShieldBlockEvent, type ShieldStartEvent, type SimEvent, type SimulateResult, Spellbinder, Spellseeker, Spellshot, Spellspinner, Spelltracer, Spellweaver, type SpiralParams, type StepResult, Stormcaller, Stormchaser, Stormforger, type StraightParams, TICKS_PER_SECOND, TICK_DURATION_MS, TargetDummy, type TestBotBuilder, type TestFightResult, type TestSimulateResult, type TraceBotSummary, type TraceEvent, type TraceEventType, type TraceSummary, Turtle, type Velocity, Voidblade, WARMUP_DURATION_TOLERANCE, WARMUP_MAX_BONUS, WARMUP_MAX_PENALTY, WARMUP_SPEED_TOLERANCE, WARMUP_TURN_TOLERANCE, WIZARD_HEALTH, WIZARD_RADIUS, Warmage, type WizardActions, type WizardContext, type WizardDeathEvent, type WizardEntry, type WizardFunction, type WizardGroup, type WizardLavaDeathEvent, type WizardState, type WorkerFactory, type WorkerLike, analyzeThreats, angleDiff, angleInRange, angleTo, applyDamage, applyRulesetOverrides, blink, browserSandboxFight, browserSandboxSimulate, calculateBlinkCooldown, calculateMissileCastTime, calculateMissileRadius, calculateMissileSimilarity, calculateShieldBlock, calculateWarmupMultiplier, cancel, cancelCast, clampPositionToArena, clampToArena, clearHooks, clearParamValues, completeCast, createEntitySeed, createInitialState, createRandom, createWorkerScript, currentRuleset, diagnoseTrace, directionAway, directionTo, distanceTo, effectiveTurnRateCost, extractAction, extractMissileAction, extractStats, extractTraceEvents, fight, findInRange, findNearest, fitMissileForEscapingTarget, fitMissileToBudget, flyStraight, formatDiagnosis, formatStats, formatTraceEvents, formatTraceSummary, generateCandidates, generateCombos, getAdaptiveMissileConfig, getEffectiveRange, getLeadPosition, getMissileCastTime, getMissileContext, getPlayerState, getWizardContext, hashCombine, homingMissile, idle, inRange, interceptAngle, isInLava, magnitude, missile, move, moveInDirection, moveProjectile, moveWizard, narrowRange, nextRandom, normalize, normalizeAngle, predictPosition, resetAllHooks, resetRuleset, resolveWizardCollision, runWithHooks, runWizardWithContext, scoreFight, scoreFightAsWizard2, seekerMissile, setParamValues, shield, simulate, simulateMinDuration, sortByDistance, spiralMissile, startCast, startDiscovery, stopDiscovery, straightMissile, summarizeTrace, sweptCircleCollision, testBot, tick, turnToAngle, turnToward, updateShield, useArenaSize, useBlinkCooldown, useCastProgress, useCastingSpell, useClosestThreat, useDamageDealt, useDamageTaken, useEffect, useEnemy, useHealth, useLastHitTick, useLastMissileConfig, useMemo, useMyProjectiles, useMyThreatsToEnemy, useParam, usePosition, useProjectiles, useRandom, useRef, useShieldStrength, useState, useStatus, useThreats, useTick, useTicksUntilReady, useVelocity, validateHookCall, validateMissileConfig, withMissileContext, withWizardContext, wrapWithParams };