@vibemancer/core 0.1.3 → 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-B7YYYBEC.js +0 -9140
  73. package/dist/chunk-B7YYYBEC.js.map +0 -1
  74. package/src/hooks/bot-wrapper.ts +0 -84
@@ -1,10 +1,10 @@
1
- import {WizardFunction, MissileAIFunction} from '../../types.js';
1
+ import type {FinalAction, MissileFunction} from '../../hooks/index.js';
2
+ import {usePosition, useEnemy, useStatus, useBlinkCooldown, useHealth, useTick, useThreats, useProjectiles, useLastMissileConfig, useCastingSpell, useCastProgress, idle, move as moveAction, missile, shield, blink, cancel, getMissileContext, turnToward, flyStraight} from '../../hooks/index.js';
2
3
  import {angleTo} from '../../utils/angles.js';
3
4
  import {distanceTo} from '../../utils/distance.js';
4
- import {ARENA_SIZE, GCD_DURATION, SHIELD_CAST_TIME} from '../../rules.js';
5
+ import {RULES, ARENA_MIN, ARENA_MAX} from '../../rules.js';
5
6
  import {getMissileCastTime} from '../../utils/combat.js';
6
7
  import {
7
- getThreats,
8
8
  getMostUrgentThreat,
9
9
  getDodgeDirectionForMovement,
10
10
  isSafeToAttack,
@@ -12,13 +12,16 @@ import {
12
12
  getBlinkToIncreaseDistance,
13
13
  MIN_BLINK_ESCAPE_DISTANCE,
14
14
  shouldForceShield,
15
+ keepOffWalls,
16
+ getPreCastReposition,
15
17
  } from '../shared.js';
16
18
  import {useParam} from '../../engine/params-runtime.js';
17
19
 
18
- const HomingAI: MissileAIFunction = ({worldState}) =>
20
+ const HomingAI: MissileFunction = () =>
19
21
  {
20
- const enemy = worldState.enemies[0];
21
- return {turnToward: enemy?.position};
22
+ const ctx = getMissileContext();
23
+ const enemy = ctx.worldState.enemies[0];
24
+ return enemy ? turnToward(enemy.position.x, enemy.position.y) : flyStraight();
22
25
  };
23
26
  const WALL_BUFFER = 50;
24
27
 
@@ -36,12 +39,22 @@ function combatMove(
36
39
  strafeCyclePeriod: number,
37
40
  strafeIntensity: number,
38
41
  approachSpeed: number,
39
- ): {x: number; y: number}
42
+ centerBias: number,
43
+ ): {x: number; y: number}
40
44
  {
41
45
  const deltaX = enemy.position.x - position.x;
42
46
  const deltaY = enemy.position.y - position.y;
43
47
  const normalizedDistance = currentDistance > 0 ? currentDistance : 1;
44
48
 
49
+ // Approach the center-side of the enemy so knockback pushes toward center, not lava.
50
+ const center = (ARENA_MIN + ARENA_MAX) / 2;
51
+ const toCenterX = center - enemy.position.x;
52
+ const toCenterY = center - enemy.position.y;
53
+ const toCenterDist = Math.sqrt(toCenterX * toCenterX + toCenterY * toCenterY) || 1;
54
+ const approachDirX = deltaX / normalizedDistance + (toCenterX / toCenterDist) * centerBias;
55
+ const approachDirY = deltaY / normalizedDistance + (toCenterY / toCenterDist) * centerBias;
56
+ const approachMag = Math.sqrt(approachDirX * approachDirX + approachDirY * approachDirY) || 1;
57
+
45
58
  const strafeClockwise = {
46
59
  x: -deltaY / normalizedDistance,
47
60
  y: deltaX / normalizedDistance,
@@ -60,10 +73,10 @@ function combatMove(
60
73
  }
61
74
  // Priority 2: Avoid walls
62
75
  else if (
63
- position.x < WALL_BUFFER ||
64
- position.x > ARENA_SIZE - WALL_BUFFER ||
65
- position.y < WALL_BUFFER ||
66
- position.y > ARENA_SIZE - WALL_BUFFER
76
+ position.x < ARENA_MIN + WALL_BUFFER ||
77
+ position.x > ARENA_MAX - WALL_BUFFER ||
78
+ position.y < ARENA_MIN + WALL_BUFFER ||
79
+ position.y > ARENA_MAX - WALL_BUFFER
67
80
  )
68
81
  {
69
82
  const futureClockwise = {
@@ -75,16 +88,16 @@ function combatMove(
75
88
  y: position.y + strafeCounterClockwise.y * 100,
76
89
  };
77
90
  const scoreClockwise = Math.min(
78
- futureClockwise.x,
79
- ARENA_SIZE - futureClockwise.x,
80
- futureClockwise.y,
81
- ARENA_SIZE - futureClockwise.y,
91
+ futureClockwise.x - ARENA_MIN,
92
+ ARENA_MAX - futureClockwise.x,
93
+ futureClockwise.y - ARENA_MIN,
94
+ ARENA_MAX - futureClockwise.y,
82
95
  );
83
96
  const scoreCounterClockwise = Math.min(
84
- futureCounterClockwise.x,
85
- ARENA_SIZE - futureCounterClockwise.x,
86
- futureCounterClockwise.y,
87
- ARENA_SIZE - futureCounterClockwise.y,
97
+ futureCounterClockwise.x - ARENA_MIN,
98
+ ARENA_MAX - futureCounterClockwise.x,
99
+ futureCounterClockwise.y - ARENA_MIN,
100
+ ARENA_MAX - futureCounterClockwise.y,
88
101
  );
89
102
  strafeDirection =
90
103
  scoreClockwise > scoreCounterClockwise
@@ -101,35 +114,35 @@ function combatMove(
101
114
  // When actively dodging, commit fully — no approach/retreat dilution
102
115
  if (dodgeDirection)
103
116
  {
104
- return {x: strafeDirection.x * 100, y: strafeDirection.y * 100};
117
+ return {x: strafeDirection.x, y: strafeDirection.y};
105
118
  }
106
119
 
107
120
  let moveX = strafeDirection.x * strafeIntensity;
108
121
  let moveY = strafeDirection.y * strafeIntensity;
109
122
 
110
123
  // Distance management (only when not dodging)
111
- if (currentDistance > maxDistance)
124
+ if (currentDistance > maxDistance)
112
125
  {
113
- moveX += (deltaX / normalizedDistance) * approachSpeed;
114
- moveY += (deltaY / normalizedDistance) * approachSpeed;
126
+ moveX += (approachDirX / approachMag) * approachSpeed;
127
+ moveY += (approachDirY / approachMag) * approachSpeed;
115
128
  }
116
129
  else if (currentDistance < minDistance)
117
130
  {
118
131
  let retreatX = -(deltaX / normalizedDistance) * approachSpeed;
119
132
  let retreatY = -(deltaY / normalizedDistance) * approachSpeed;
120
- if (position.x < WALL_BUFFER && retreatX < 0) retreatX = 0;
121
- if (position.x > ARENA_SIZE - WALL_BUFFER && retreatX > 0) retreatX = 0;
122
- if (position.y < WALL_BUFFER && retreatY < 0) retreatY = 0;
123
- if (position.y > ARENA_SIZE - WALL_BUFFER && retreatY > 0) retreatY = 0;
133
+ if (position.x < ARENA_MIN + WALL_BUFFER && retreatX < 0) retreatX = 0;
134
+ if (position.x > ARENA_MAX - WALL_BUFFER && retreatX > 0) retreatX = 0;
135
+ if (position.y < ARENA_MIN + WALL_BUFFER && retreatY < 0) retreatY = 0;
136
+ if (position.y > ARENA_MAX - WALL_BUFFER && retreatY > 0) retreatY = 0;
124
137
  moveX += retreatX;
125
138
  moveY += retreatY;
126
139
  }
127
140
 
128
141
  const magnitude = Math.sqrt(moveX * moveX + moveY * moveY);
129
- if (magnitude > 100)
142
+ if (magnitude > 1)
130
143
  {
131
- moveX = (moveX / magnitude) * 100;
132
- moveY = (moveY / magnitude) * 100;
144
+ moveX = moveX / magnitude;
145
+ moveY = moveY / magnitude;
133
146
  }
134
147
 
135
148
  return {x: moveX, y: moveY};
@@ -163,34 +176,47 @@ function combatMove(
163
176
  *
164
177
  * TIER: 1 (base)
165
178
  */
166
- export const Spellshot: WizardFunction = ({state}) =>
179
+ export function Spellshot(): FinalAction
167
180
  {
168
- const dangerDistance = useParam('dangerDistance', 45, {range: 100, min: 0});
169
- const targetDistance = useParam('targetDistance', 339, {
181
+ const position = usePosition();
182
+ const enemy = useEnemy();
183
+ const status = useStatus();
184
+ const blinkCooldown = useBlinkCooldown();
185
+ const health = useHealth();
186
+ const tick = useTick();
187
+ const threats = useThreats();
188
+ const projectiles = useProjectiles();
189
+ const lastMissileConfig = useLastMissileConfig();
190
+ const castingSpell = useCastingSpell();
191
+ const castProgressData = useCastProgress();
192
+
193
+
194
+ const dangerDistance = useParam('dangerDistance', 99, {range: 100, min: 0});
195
+ const targetDistance = useParam('targetDistance', 310, {
170
196
  range: 150,
171
197
  min: 0,
172
198
  });
173
- const distanceDeadZone = useParam('distanceDeadZone', 22, {
199
+ const distanceDeadZone = useParam('distanceDeadZone', 81, {
174
200
  range: 50,
175
201
  min: 0,
176
202
  });
177
203
  const minCombatDistance = targetDistance - distanceDeadZone;
178
204
  const maxCombatDistance = targetDistance + distanceDeadZone;
179
- const strafeCyclePeriod = useParam('strafeCyclePeriod', 230, {
205
+ const strafeCyclePeriod = useParam('strafeCyclePeriod', 240, {
180
206
  range: 75,
181
207
  min: 80,
182
208
  max: 300,
183
209
  });
184
- const strafeIntensity = useParam('strafeIntensity', 36, {
185
- range: 40,
186
- min: 20,
187
- max: 100,
210
+ const strafeIntensity = useParam('strafeIntensity', 0.63, {
211
+ range: 0.4,
212
+ min: 0.2,
213
+ max: 1,
188
214
  steps: 7,
189
215
  });
190
- const approachSpeed = useParam('approachSpeed', 78, {
191
- range: 30,
192
- min: 10,
193
- max: 80,
216
+ const approachSpeed = useParam('approachSpeed', 0.73, {
217
+ range: 0.3,
218
+ min: 0.1,
219
+ max: 0.8,
194
220
  steps: 7,
195
221
  });
196
222
  const shieldCancelThreshold = useParam('shieldCancelThreshold', 150, {
@@ -214,7 +240,7 @@ export const Spellshot: WizardFunction = ({state}) =>
214
240
  range: 1.5,
215
241
  steps: 5,
216
242
  });
217
- const missileMinDuration = useParam('missileMinDuration', 55, {
243
+ const missileMinDuration = useParam('missileMinDuration', 49, {
218
244
  range: 30,
219
245
  min: 20,
220
246
  max: 100,
@@ -226,71 +252,88 @@ export const Spellshot: WizardFunction = ({state}) =>
226
252
  max: 2.0,
227
253
  steps: 5,
228
254
  });
255
+ const centerBias = useParam('centerBias', 0.3, {
256
+ range: 0.4,
257
+ min: 0,
258
+ max: 0.8,
259
+ steps: 5,
260
+ });
261
+ const maxBlinkDistance = useParam('maxBlinkDistance', 300, {
262
+ range: 100,
263
+ min: 50,
264
+ max: 300,
265
+ steps: 7,
266
+ });
229
267
 
230
- const enemy = state.enemies[0];
231
- if (!enemy)
268
+ const distance = distanceTo(position, enemy.position);
269
+
270
+ function capBlinkDistance(target: {x: number; y: number}): {x: number; y: number}
232
271
  {
233
- return {move: {x: 0, y: 0}};
272
+ const dx = target.x - position.x;
273
+ const dy = target.y - position.y;
274
+ const dist = Math.sqrt(dx * dx + dy * dy);
275
+ if (dist <= maxBlinkDistance) return target;
276
+ const scale = maxBlinkDistance / dist;
277
+ return {x: position.x + dx * scale, y: position.y + dy * scale};
234
278
  }
235
279
 
236
- const distance = distanceTo(state.position, enemy.position);
237
-
238
280
  // Analyze threats using simulation
239
- const threats = getThreats(state);
240
281
  const urgentThreat = getMostUrgentThreat(threats);
241
282
 
242
283
  // === DEFENSE: Handle channeling ===
243
- if (state.state === 'channeling')
284
+ if (status === 'channeling')
244
285
  {
245
286
  if (!urgentThreat || urgentThreat.ticksToImpact > shieldCancelThreshold)
246
287
  {
247
- return {move: {x: 0, y: 0}, cancel: true};
288
+ return cancel();
248
289
  }
249
- return {move: {x: 0, y: 0}};
290
+ return idle();
250
291
  }
251
292
 
252
293
  // === DEFENSE: Cancel missile cast if dangerous threat will arrive before we can shield ===
253
294
  // Only cancel for: high-damage homing (always) or undodgeable when health is at risk
254
295
  if (
255
- state.state === 'casting' &&
256
- state.castingSpell === 'missile' &&
296
+ status === 'casting' &&
297
+ castingSpell === 'missile' &&
257
298
  urgentThreat &&
258
299
  (!urgentThreat.bestDodgeDirection || shouldForceShield(urgentThreat))
259
300
  )
260
301
  {
261
302
  const mustCancel =
262
303
  shouldForceShield(urgentThreat) ||
263
- state.health <= urgentThreat.projectile.damage * 2 + 1;
304
+ health <= urgentThreat.projectile.damage * 2 + 1;
264
305
  if (mustCancel)
265
306
  {
266
307
  const remainingCast =
267
- (state.castDuration ?? 0) - (state.castProgress ?? 0);
308
+ ((castProgressData?.total) ?? 0) - ((castProgressData?.current) ?? 0);
268
309
  if (
269
310
  urgentThreat.ticksToImpact <=
270
- remainingCast + GCD_DURATION + SHIELD_CAST_TIME + 3
311
+ remainingCast + RULES.GCD_DURATION + RULES.SHIELD_CAST_TIME + 3
271
312
  )
272
313
  {
273
- const dodgeDir = getDodgeDirectionForMovement(threats, state.position);
314
+ const dodgeDir = getDodgeDirectionForMovement(threats, position);
274
315
  const cancelMove = combatMove(
275
- state.position,
316
+ position,
276
317
  enemy,
277
318
  distance,
278
319
  minCombatDistance,
279
320
  maxCombatDistance,
280
321
  dodgeDir,
281
- state.tick,
322
+ tick,
282
323
  strafeCyclePeriod,
283
324
  strafeIntensity,
284
325
  approachSpeed,
326
+ centerBias,
285
327
  );
286
- return {move: cancelMove, cancel: true};
328
+ const safeCancelMove = keepOffWalls(position, cancelMove);
329
+ return cancel().move(safeCancelMove.x, safeCancelMove.y);
287
330
  }
288
331
  }
289
332
  }
290
333
 
291
334
  // === DEFENSE: Shield undodgeable or high-damage homing threats ===
292
335
  if (
293
- state.state === 'idle' &&
336
+ status === 'idle' &&
294
337
  urgentThreat &&
295
338
  (!urgentThreat.bestDodgeDirection || shouldForceShield(urgentThreat))
296
339
  )
@@ -300,63 +343,81 @@ export const Spellshot: WizardFunction = ({state}) =>
300
343
  // Start shield at the right time (small buffer for safety)
301
344
  if (urgentThreat.ticksToStartShield <= 3)
302
345
  {
303
- return {
304
- move: {x: 0, y: 0},
305
- startCast: {spell: 'shield'},
306
- };
346
+ return shield();
307
347
  }
308
348
  }
309
349
  // Can't dodge, can't shield → blink away (emergency)
310
- else if (state.blinkCooldown === 0)
350
+ else if (blinkCooldown === 0)
311
351
  {
312
- return {
313
- move: {x: 0, y: 0},
314
- startCast: {spell: 'blink', target: getBlinkToCenter(state.position)},
315
- };
352
+ const blinkTarget = capBlinkDistance(getBlinkToCenter(position));
353
+ return blink(blinkTarget.x, blinkTarget.y);
354
+ }
355
+ }
356
+
357
+ // === Preemptive shield — shield when enemy is casting a missile at close range ===
358
+ if (
359
+ status === 'idle' &&
360
+ distance < 300 &&
361
+ enemy.status === 'casting' &&
362
+ enemy.castingSpell === 'missile'
363
+ )
364
+ {
365
+ const remainingCast = (enemy.castDuration ?? 0) - (enemy.castProgress ?? 0);
366
+ const estimatedFlightTicks = distance / 8;
367
+ const ticksUntilHit = remainingCast + estimatedFlightTicks;
368
+
369
+ if (
370
+ ticksUntilHit >= RULES.SHIELD_CAST_TIME &&
371
+ ticksUntilHit < RULES.SHIELD_CAST_TIME + 15
372
+ )
373
+ {
374
+ return shield();
316
375
  }
317
376
  }
318
377
 
319
378
  // === DISTANCE BLINK: enemy too close, blink to reestablish kiting range ===
320
379
  if (
321
- state.state === 'idle' &&
380
+ status === 'idle' &&
322
381
  distance < dangerDistance &&
323
- state.blinkCooldown === 0 &&
382
+ blinkCooldown === 0 &&
324
383
  (!urgentThreat || urgentThreat.bestDodgeDirection !== null)
325
384
  )
326
385
  {
327
386
  const blinkTarget = getBlinkToIncreaseDistance(
328
- state.position,
387
+ position,
329
388
  enemy.position,
330
- state.projectiles,
389
+ projectiles,
331
390
  MIN_BLINK_ESCAPE_DISTANCE,
332
391
  );
333
- if (blinkTarget)
392
+ if (blinkTarget)
334
393
  {
335
- return {
336
- move: {x: 0, y: 0},
337
- startCast: {spell: 'blink', target: blinkTarget},
338
- };
394
+ const capped = capBlinkDistance(blinkTarget);
395
+ return blink(capped.x, capped.y);
339
396
  }
340
397
  }
341
398
 
342
399
  // Smart combat movement
343
- const dodgeDirection = getDodgeDirectionForMovement(threats, state.position);
400
+ const dodgeDirection = getDodgeDirectionForMovement(threats, position);
344
401
  const move = combatMove(
345
- state.position,
402
+ position,
346
403
  enemy,
347
404
  distance,
348
405
  minCombatDistance,
349
406
  maxCombatDistance,
350
407
  dodgeDirection,
351
- state.tick,
408
+ tick,
352
409
  strafeCyclePeriod,
353
410
  strafeIntensity,
354
411
  approachSpeed,
412
+ centerBias,
355
413
  );
356
-
414
+ const safeMove = keepOffWalls(position, move);
357
415
  // === OFFENSE: Fast homing missiles ===
358
- if (state.state === 'idle' && distance < 600)
416
+ if (status === 'idle' && distance < 600)
359
417
  {
418
+ const reposition = getPreCastReposition(position, enemy.position);
419
+ if (reposition) return moveAction(reposition.x, reposition.y);
420
+
360
421
  const missileConfig = {
361
422
  damage: missileDamage,
362
423
  speed: missileSpeed,
@@ -366,20 +427,12 @@ export const Spellshot: WizardFunction = ({state}) =>
366
427
  Math.ceil((distance * durationRangeFactor) / missileSpeed),
367
428
  ),
368
429
  };
369
- const castTime = getMissileCastTime(missileConfig, state.lastMissileConfig);
430
+ const castTime = getMissileCastTime(missileConfig, lastMissileConfig);
370
431
  if (isSafeToAttack(threats, castTime))
371
432
  {
372
- return {
373
- move,
374
- startCast: {
375
- spell: 'missile',
376
- config: missileConfig,
377
- missileAI: HomingAI,
378
- direction: angleTo(state.position, enemy.position),
379
- },
380
- };
433
+ return missile(missileConfig, HomingAI, angleTo(position, enemy.position)).move(safeMove.x, safeMove.y);
381
434
  }
382
435
  }
383
436
 
384
- return {move};
385
- };
437
+ return moveAction(safeMove.x, safeMove.y);
438
+ }