@vibemancer/core 0.1.4 → 0.1.6

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 (76) hide show
  1. package/README.md +2 -2
  2. package/dist/chunk-G5T65F3W.js +10617 -0
  3. package/dist/chunk-G5T65F3W.js.map +1 -0
  4. package/dist/index-browser.d.ts +1328 -1051
  5. package/dist/index-browser.js +59 -17
  6. package/dist/index.d.ts +53 -3
  7. package/dist/index.js +204 -54
  8. package/dist/index.js.map +1 -1
  9. package/package.json +21 -15
  10. package/src/banned-bot-names.ts +53 -0
  11. package/src/bots/Hero.ts +867 -0
  12. package/src/bots/berserker/01_Stormchaser.ts +162 -120
  13. package/src/bots/berserker/02_Stormcaller.ts +160 -116
  14. package/src/bots/berserker/03_Stormforger.ts +162 -129
  15. package/src/bots/caster/01_Flamecaller.ts +126 -84
  16. package/src/bots/caster/02_Pyromancer.ts +148 -101
  17. package/src/bots/caster/03_Infernalist.ts +180 -160
  18. package/src/bots/defensive/01_Turtle.ts +48 -44
  19. package/src/bots/defensive/02_Sentinel.ts +57 -53
  20. package/src/bots/defensive/03_Golem.ts +85 -97
  21. package/src/bots/duelist/01_Battlemage.ts +157 -122
  22. package/src/bots/duelist/02_Warmage.ts +166 -121
  23. package/src/bots/duelist/03_Archmage.ts +198 -178
  24. package/src/bots/homing/01_Bonemancer.ts +20 -32
  25. package/src/bots/homing/02_Lich.ts +145 -93
  26. package/src/bots/homing/03_Archlich.ts +129 -60
  27. package/src/bots/kiter/01_Spellspinner.ts +145 -107
  28. package/src/bots/kiter/02_Spellweaver.ts +153 -105
  29. package/src/bots/kiter/03_Spellbinder.ts +168 -123
  30. package/src/bots/melee/01_Shadowblade.ts +131 -75
  31. package/src/bots/melee/02_Nightblade.ts +174 -130
  32. package/src/bots/melee/03_Voidblade.ts +266 -168
  33. package/src/bots/registry.ts +44 -37
  34. package/src/bots/shared.ts +185 -25
  35. package/src/bots/sniper/01_Spellshot.ts +151 -98
  36. package/src/bots/sniper/02_Spelltracer.ts +173 -128
  37. package/src/bots/sniper/03_Spellseeker.ts +177 -152
  38. package/src/bots/standalone/Critter.ts +46 -52
  39. package/src/bots/standalone/Doombringer.ts +36 -40
  40. package/src/bots/standalone/Hogger.ts +120 -89
  41. package/src/bots/standalone/Rookie.ts +21 -23
  42. package/src/bots/standalone/TargetDummy.ts +5 -6
  43. package/src/bots/test/cheater.ts +91 -85
  44. package/src/bots/test/crasher.ts +26 -28
  45. package/src/engine/bundle-fight.ts +242 -0
  46. package/src/engine/hooks-runtime.ts +58 -18
  47. package/src/engine/manual-match.ts +33 -25
  48. package/src/engine/missile-templates.ts +25 -43
  49. package/src/engine/optimizer.ts +7 -7
  50. package/src/engine/params-runtime.ts +3 -3
  51. package/src/engine/physics.ts +33 -23
  52. package/src/engine/sandbox-browser.ts +8 -7
  53. package/src/engine/sandbox-compile.ts +1 -1
  54. package/src/engine/sandbox-harness.ts +299 -367
  55. package/src/engine/sandbox.ts +3 -3
  56. package/src/engine/simulation.ts +291 -76
  57. package/src/engine/spells.ts +9 -12
  58. package/src/engine-version.ts +1 -1
  59. package/src/hooks/action-builders.ts +76 -21
  60. package/src/hooks/index.ts +17 -9
  61. package/src/hooks/state-hooks.ts +61 -25
  62. package/src/hooks/threat-analysis.ts +44 -20
  63. package/src/hooks/types.ts +62 -5
  64. package/src/index-browser.ts +1 -0
  65. package/src/index.ts +3 -0
  66. package/src/rules.ts +209 -63
  67. package/src/stats.ts +2 -2
  68. package/src/testing.ts +6 -30
  69. package/src/trace.ts +63 -3
  70. package/src/types.ts +127 -14
  71. package/src/utils/angles.ts +1 -0
  72. package/src/utils/combat.ts +98 -6
  73. package/src/utils/spatial.ts +3 -3
  74. package/dist/chunk-74FFJCFF.js +0 -9140
  75. package/dist/chunk-74FFJCFF.js.map +0 -1
  76. package/src/hooks/bot-wrapper.ts +0 -84
@@ -1,15 +1,17 @@
1
- import {WizardFunction, MissileAIFunction} from '../../types.js';
1
+ import type {FinalAction, MissileFunction} from '../../hooks/index.js';
2
+ import {usePosition, useEnemy, useStatus, useBlinkCooldown, useTick, useThreats, useLastMissileConfig, useCastingSpell, 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,
11
11
  getBlinkToCenter,
12
12
  shouldForceShield,
13
+ keepOffWalls,
14
+ getPreCastReposition,
13
15
  } from '../shared.js';
14
16
  import {useParam} from '../../engine/params-runtime.js';
15
17
 
@@ -21,19 +23,20 @@ const SHIELD_BUFFER = 3;
21
23
  // Range: 5 × 180 = 900 units (covers arena)
22
24
  const MISSILE_CONFIG = {damage: 12, speed: 5, turnRate: 1, duration: 180};
23
25
 
24
- const HomingAI: MissileAIFunction = ({worldState}) =>
26
+ const HomingAI: MissileFunction = () =>
25
27
  {
26
- const enemy = worldState.enemies[0];
27
- return {turnToward: enemy?.position};
28
+ const ctx = getMissileContext();
29
+ const enemy = ctx.worldState.enemies[0];
30
+ return enemy ? turnToward(enemy.position.x, enemy.position.y) : flyStraight();
28
31
  };
29
32
 
30
33
  function isNearWall(position: {x: number; y: number}): boolean
31
34
  {
32
35
  return (
33
- position.x < WALL_BUFFER ||
34
- position.x > ARENA_SIZE - WALL_BUFFER ||
35
- position.y < WALL_BUFFER ||
36
- position.y > ARENA_SIZE - WALL_BUFFER
36
+ position.x < ARENA_MIN + WALL_BUFFER ||
37
+ position.x > ARENA_MAX - WALL_BUFFER ||
38
+ position.y < ARENA_MIN + WALL_BUFFER ||
39
+ position.y > ARENA_MAX - WALL_BUFFER
37
40
  );
38
41
  }
39
42
 
@@ -77,16 +80,16 @@ function smartStrafe(
77
80
  y: position.y + strafeCounterClockwise.y * 100,
78
81
  };
79
82
  const scoreClockwise = Math.min(
80
- futureClockwise.x,
81
- ARENA_SIZE - futureClockwise.x,
82
- futureClockwise.y,
83
- ARENA_SIZE - futureClockwise.y,
83
+ futureClockwise.x - ARENA_MIN,
84
+ ARENA_MAX - futureClockwise.x,
85
+ futureClockwise.y - ARENA_MIN,
86
+ ARENA_MAX - futureClockwise.y,
84
87
  );
85
88
  const scoreCounterClockwise = Math.min(
86
- futureCounterClockwise.x,
87
- ARENA_SIZE - futureCounterClockwise.x,
88
- futureCounterClockwise.y,
89
- ARENA_SIZE - futureCounterClockwise.y,
89
+ futureCounterClockwise.x - ARENA_MIN,
90
+ ARENA_MAX - futureCounterClockwise.x,
91
+ futureCounterClockwise.y - ARENA_MIN,
92
+ ARENA_MAX - futureCounterClockwise.y,
90
93
  );
91
94
  strafeDir =
92
95
  scoreClockwise > scoreCounterClockwise
@@ -99,7 +102,7 @@ function smartStrafe(
99
102
  strafeDir = cycle === 0 ? strafeClockwise : strafeCounterClockwise;
100
103
  }
101
104
 
102
- const intensity = dodgeDirection ? 100 : strafeIntensity;
105
+ const intensity = dodgeDirection ? 1 : strafeIntensity;
103
106
  return {x: strafeDir.x * intensity, y: strafeDir.y * intensity};
104
107
  }
105
108
 
@@ -119,30 +122,40 @@ function smartStrafe(
119
122
  *
120
123
  * TIER: 1 (base)
121
124
  */
122
- export const Flamecaller: WizardFunction = ({state}) =>
125
+ export function Flamecaller(): FinalAction
123
126
  {
124
- const targetDistance = useParam('targetDistance', 753, {
127
+ const position = usePosition();
128
+ const enemy = useEnemy();
129
+ const status = useStatus();
130
+ const blinkCooldown = useBlinkCooldown();
131
+ const tick = useTick();
132
+ const threats = useThreats();
133
+ const lastMissileConfig = useLastMissileConfig();
134
+ const castingSpell = useCastingSpell();
135
+
136
+
137
+ const targetDistance = useParam('targetDistance', 498, {
125
138
  range: 150,
126
139
  min: 0,
127
140
  });
128
- const strafeCyclePeriod = useParam('strafeCyclePeriod', 273, {
141
+ const strafeCyclePeriod = useParam('strafeCyclePeriod', 211, {
129
142
  range: 75,
130
143
  min: 80,
131
144
  max: 300,
132
145
  });
133
- const strafeIntensity = useParam('strafeIntensity', 20, {
134
- range: 40,
135
- min: 20,
136
- max: 100,
146
+ const strafeIntensity = useParam('strafeIntensity', 0.87, {
147
+ range: 0.4,
148
+ min: 0.2,
149
+ max: 1,
137
150
  steps: 7,
138
151
  });
139
- const approachSpeed = useParam('approachSpeed', 40, {
140
- range: 30,
141
- min: 10,
142
- max: 70,
152
+ const approachSpeed = useParam('approachSpeed', 0.1, {
153
+ range: 0.3,
154
+ min: 0.1,
155
+ max: 0.7,
143
156
  steps: 7,
144
157
  });
145
- const distanceDeadZone = useParam('distanceDeadZone', 56, {
158
+ const distanceDeadZone = useParam('distanceDeadZone', 73, {
146
159
  range: 30,
147
160
  min: 5,
148
161
  max: 80,
@@ -153,30 +166,39 @@ export const Flamecaller: WizardFunction = ({state}) =>
153
166
  min: 50,
154
167
  max: 300,
155
168
  });
156
- const safeAttackCastEstimate = useParam('safeAttackCastEstimate', 35, {
157
- range: 25,
158
- min: 10,
159
- max: 80,
169
+ const centerBias = useParam('centerBias', 0.05, {
170
+ range: 0.4,
171
+ min: 0,
172
+ max: 0.8,
160
173
  steps: 5,
161
174
  });
175
+ const maxBlinkDistance = useParam('maxBlinkDistance', 204, {
176
+ range: 100,
177
+ min: 50,
178
+ max: 300,
179
+ steps: 7,
180
+ });
162
181
 
163
- const enemy = state.enemies[0];
164
- if (!enemy)
182
+ const distance = distanceTo(position, enemy.position);
183
+
184
+ function capBlinkDistance(target: {x: number; y: number}): {x: number; y: number}
165
185
  {
166
- return {move: {x: 0, y: 0}};
186
+ const dx = target.x - position.x;
187
+ const dy = target.y - position.y;
188
+ const dist = Math.sqrt(dx * dx + dy * dy);
189
+ if (dist <= maxBlinkDistance) return target;
190
+ const scale = maxBlinkDistance / dist;
191
+ return {x: position.x + dx * scale, y: position.y + dy * scale};
167
192
  }
168
193
 
169
- const distance = distanceTo(state.position, enemy.position);
170
-
171
- const threats = getThreats(state);
172
194
  const urgentThreat = getMostUrgentThreat(threats);
173
195
 
174
- const dodgeDirection = getDodgeDirectionForMovement(threats, state.position);
196
+ const dodgeDirection = getDodgeDirectionForMovement(threats, position);
175
197
  const strafe = smartStrafe(
176
- state.position,
198
+ position,
177
199
  enemy,
178
200
  dodgeDirection,
179
- state.tick,
201
+ tick,
180
202
  strafeCyclePeriod,
181
203
  strafeIntensity,
182
204
  );
@@ -185,18 +207,26 @@ export const Flamecaller: WizardFunction = ({state}) =>
185
207
  let moveX = strafe.x;
186
208
  let moveY = strafe.y;
187
209
 
188
- if (!dodgeDirection)
210
+ if (!dodgeDirection)
189
211
  {
190
- const deltaX = enemy.position.x - state.position.x;
191
- const deltaY = enemy.position.y - state.position.y;
212
+ const deltaX = enemy.position.x - position.x;
213
+ const deltaY = enemy.position.y - position.y;
192
214
  const normalizedDistance = distance > 0 ? distance : 1;
193
215
 
194
- if (distance > targetDistance + distanceDeadZone)
216
+ if (distance > targetDistance + distanceDeadZone)
195
217
  {
196
- moveX += (deltaX / normalizedDistance) * approachSpeed;
197
- moveY += (deltaY / normalizedDistance) * approachSpeed;
218
+ // Approach the center-side of the enemy so knockback pushes toward center, not lava.
219
+ const center = (ARENA_MIN + ARENA_MAX) / 2;
220
+ const toCenterX = center - enemy.position.x;
221
+ const toCenterY = center - enemy.position.y;
222
+ const toCenterDist = Math.sqrt(toCenterX * toCenterX + toCenterY * toCenterY) || 1;
223
+ const approachDirX = deltaX / normalizedDistance + (toCenterX / toCenterDist) * centerBias;
224
+ const approachDirY = deltaY / normalizedDistance + (toCenterY / toCenterDist) * centerBias;
225
+ const approachMag = Math.sqrt(approachDirX * approachDirX + approachDirY * approachDirY) || 1;
226
+ moveX += (approachDirX / approachMag) * approachSpeed;
227
+ moveY += (approachDirY / approachMag) * approachSpeed;
198
228
  }
199
- else if (distance < targetDistance - distanceDeadZone)
229
+ else if (distance < targetDistance - distanceDeadZone)
200
230
  {
201
231
  moveX -= (deltaX / normalizedDistance) * approachSpeed;
202
232
  moveY -= (deltaY / normalizedDistance) * approachSpeed;
@@ -204,33 +234,34 @@ export const Flamecaller: WizardFunction = ({state}) =>
204
234
  }
205
235
 
206
236
  const magnitude = Math.sqrt(moveX * moveX + moveY * moveY);
207
- if (magnitude > 100)
237
+ if (magnitude > 1)
208
238
  {
209
- moveX = (moveX / magnitude) * 100;
210
- moveY = (moveY / magnitude) * 100;
239
+ moveX = moveX / magnitude;
240
+ moveY = moveY / magnitude;
211
241
  }
212
242
 
213
243
  const move = {x: moveX, y: moveY};
244
+ const safeMove = keepOffWalls(position, move);
214
245
 
215
246
  // === DEFENSE: Handle channeling ===
216
- if (state.state === 'channeling')
247
+ if (status === 'channeling')
217
248
  {
218
249
  if (!urgentThreat || urgentThreat.ticksToImpact > shieldCancelThreshold)
219
250
  {
220
- return {move, cancel: true};
251
+ return cancel().move(safeMove.x, safeMove.y);
221
252
  }
222
- return {move: {x: 0, y: 0}};
253
+ return idle();
223
254
  }
224
255
 
225
256
  // === DEFENSE: Let shield cast finish ===
226
- if (state.state === 'casting' && state.castingSpell === 'shield')
257
+ if (status === 'casting' && castingSpell === 'shield')
227
258
  {
228
- return {move: {x: 0, y: 0}};
259
+ return idle();
229
260
  }
230
261
 
231
262
  // === DEFENSE: Shield undodgeable or high-damage homing threats ===
232
263
  if (
233
- state.state === 'idle' &&
264
+ status === 'idle' &&
234
265
  urgentThreat &&
235
266
  (!urgentThreat.bestDodgeDirection || shouldForceShield(urgentThreat))
236
267
  )
@@ -239,48 +270,59 @@ export const Flamecaller: WizardFunction = ({state}) =>
239
270
  {
240
271
  if (urgentThreat.ticksToStartShield <= SHIELD_BUFFER)
241
272
  {
242
- return {
243
- move: {x: 0, y: 0},
244
- startCast: {spell: 'shield'},
245
- };
273
+ return shield();
246
274
  }
247
275
  }
248
- else if (state.blinkCooldown === 0)
276
+ else if (blinkCooldown === 0)
249
277
  {
250
- return {
251
- move: {x: 0, y: 0},
252
- startCast: {spell: 'blink', target: getBlinkToCenter(state.position)},
253
- };
278
+ const blinkTarget = capBlinkDistance(getBlinkToCenter(position));
279
+ return blink(blinkTarget.x, blinkTarget.y);
280
+ }
281
+ }
282
+
283
+ // === Preemptive shield — shield when enemy is casting a missile at close range ===
284
+ if (
285
+ status === 'idle' &&
286
+ distance < 300 &&
287
+ enemy.status === 'casting' &&
288
+ enemy.castingSpell === 'missile'
289
+ )
290
+ {
291
+ const remainingCast = (enemy.castDuration ?? 0) - (enemy.castProgress ?? 0);
292
+ const estimatedFlightTicks = distance / 8;
293
+ const ticksUntilHit = remainingCast + estimatedFlightTicks;
294
+
295
+ if (
296
+ ticksUntilHit >= RULES.SHIELD_CAST_TIME &&
297
+ ticksUntilHit < RULES.SHIELD_CAST_TIME + 15
298
+ )
299
+ {
300
+ return shield();
254
301
  }
255
302
  }
256
303
 
257
304
  // === OFFENSE: Fire fixed homing missile when safe ===
258
- if (state.state === 'idle')
305
+ if (status === 'idle')
259
306
  {
307
+ const reposition = getPreCastReposition(position, enemy.position);
308
+ if (reposition) return moveAction(reposition.x, reposition.y);
309
+
260
310
  const nextThreatTime = urgentThreat?.ticksToImpact ?? Infinity;
261
311
  const castTime = getMissileCastTime(
262
312
  MISSILE_CONFIG,
263
- state.lastMissileConfig,
313
+ lastMissileConfig,
264
314
  );
265
315
  const safeWindow =
266
- castTime + GCD_DURATION + SHIELD_CAST_TIME + SHIELD_BUFFER;
316
+ castTime + RULES.GCD_DURATION + RULES.SHIELD_CAST_TIME + SHIELD_BUFFER;
267
317
 
268
318
  if (
269
319
  nextThreatTime > safeWindow &&
270
- isSafeToAttack(threats, safeAttackCastEstimate)
320
+ isSafeToAttack(threats, castTime)
271
321
  )
272
322
  {
273
- return {
274
- move,
275
- startCast: {
276
- spell: 'missile',
277
- config: MISSILE_CONFIG,
278
- missileAI: HomingAI,
279
- direction: angleTo(state.position, enemy.position),
280
- },
281
- };
323
+ return missile(MISSILE_CONFIG, HomingAI, angleTo(position, enemy.position)).move(safeMove.x, safeMove.y);
282
324
  }
283
325
  }
284
326
 
285
- return {move};
286
- };
327
+ return moveAction(safeMove.x, safeMove.y);
328
+ }