@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,18 +1,21 @@
1
- import {WizardFunction} from '../../types.js';
1
+ import type {FinalAction} from '../../hooks/index.js';
2
+ import {usePosition, useEnemy, useStatus, useBlinkCooldown, useTick, useThreats, useProjectiles, idle, move as moveAction, missile, shield, blink, cancel, flyStraight} from '../../hooks/index.js';
2
3
  import {distanceTo} from '../../utils/distance.js';
3
- import {ARENA_SIZE} from '../../rules.js';
4
+ import {RULES, ARENA_MIN, ARENA_MAX} from '../../rules.js';
4
5
  import {
5
- getThreats,
6
6
  getMostUrgentThreat,
7
7
  getDodgeDirectionForMovement,
8
8
  getBlinkToDecreaseDistance,
9
9
  shouldForceShield,
10
+ keepOffWalls,
11
+ getPreCastReposition,
10
12
  } from '../shared.js';
11
13
  import {useParam} from '../../engine/params-runtime.js';
12
14
  const WALL_BUFFER = 50;
13
15
 
14
16
  // Melee stab: 20 damage, speed 8, 3-hit kill.
15
- const STAB_CONFIG = {damage: 20, speed: 8, turnRate: 0, duration: 10};
17
+ const STAB_SPEED = 8;
18
+ const STAB_DURATION = 25;
16
19
 
17
20
  /**
18
21
  * Aggressive movement: dodge missiles using threat analysis, close distance, light strafe.
@@ -26,12 +29,22 @@ function aggressiveMove(
26
29
  strafeCyclePeriod: number,
27
30
  strafeIntensity: number,
28
31
  approachSpeed: number,
29
- ): {x: number; y: number}
32
+ centerBias: number,
33
+ ): {x: number; y: number}
30
34
  {
31
35
  const deltaX = enemy.position.x - position.x;
32
36
  const deltaY = enemy.position.y - position.y;
33
37
  const normalizedDistance = currentDistance > 0 ? currentDistance : 1;
34
38
 
39
+ // Approach the center-side of the enemy so knockback pushes toward center, not lava.
40
+ const center = (ARENA_MIN + ARENA_MAX) / 2;
41
+ const toCenterX = center - enemy.position.x;
42
+ const toCenterY = center - enemy.position.y;
43
+ const toCenterDist = Math.sqrt(toCenterX * toCenterX + toCenterY * toCenterY) || 1;
44
+ const approachDirX = deltaX / normalizedDistance + (toCenterX / toCenterDist) * centerBias;
45
+ const approachDirY = deltaY / normalizedDistance + (toCenterY / toCenterDist) * centerBias;
46
+ const approachMag = Math.sqrt(approachDirX * approachDirX + approachDirY * approachDirY) || 1;
47
+
35
48
  const strafeClockwise = {
36
49
  x: -deltaY / normalizedDistance,
37
50
  y: deltaX / normalizedDistance,
@@ -48,10 +61,10 @@ function aggressiveMove(
48
61
  strafeDirection = dodgeDirection;
49
62
  }
50
63
  else if (
51
- position.x < WALL_BUFFER ||
52
- position.x > ARENA_SIZE - WALL_BUFFER ||
53
- position.y < WALL_BUFFER ||
54
- position.y > ARENA_SIZE - WALL_BUFFER
64
+ position.x < ARENA_MIN + WALL_BUFFER ||
65
+ position.x > ARENA_MAX - WALL_BUFFER ||
66
+ position.y < ARENA_MIN + WALL_BUFFER ||
67
+ position.y > ARENA_MAX - WALL_BUFFER
55
68
  )
56
69
  {
57
70
  const futureClockwise = {
@@ -63,16 +76,16 @@ function aggressiveMove(
63
76
  y: position.y + strafeCounterClockwise.y * 100,
64
77
  };
65
78
  const scoreClockwise = Math.min(
66
- futureClockwise.x,
67
- ARENA_SIZE - futureClockwise.x,
68
- futureClockwise.y,
69
- ARENA_SIZE - futureClockwise.y,
79
+ futureClockwise.x - ARENA_MIN,
80
+ ARENA_MAX - futureClockwise.x,
81
+ futureClockwise.y - ARENA_MIN,
82
+ ARENA_MAX - futureClockwise.y,
70
83
  );
71
84
  const scoreCounterClockwise = Math.min(
72
- futureCounterClockwise.x,
73
- ARENA_SIZE - futureCounterClockwise.x,
74
- futureCounterClockwise.y,
75
- ARENA_SIZE - futureCounterClockwise.y,
85
+ futureCounterClockwise.x - ARENA_MIN,
86
+ ARENA_MAX - futureCounterClockwise.x,
87
+ futureCounterClockwise.y - ARENA_MIN,
88
+ ARENA_MAX - futureCounterClockwise.y,
76
89
  );
77
90
  strafeDirection =
78
91
  scoreClockwise > scoreCounterClockwise
@@ -88,7 +101,7 @@ function aggressiveMove(
88
101
  // When actively dodging, commit fully
89
102
  if (dodgeDirection)
90
103
  {
91
- return {x: strafeDirection.x * 100, y: strafeDirection.y * 100};
104
+ return {x: strafeDirection.x, y: strafeDirection.y};
92
105
  }
93
106
 
94
107
  // Light strafe + aggressive closing
@@ -96,14 +109,14 @@ function aggressiveMove(
96
109
  let moveY = strafeDirection.y * strafeIntensity;
97
110
 
98
111
  // Always close distance aggressively (melee wants to be in the enemy's face)
99
- moveX += (deltaX / normalizedDistance) * approachSpeed;
100
- moveY += (deltaY / normalizedDistance) * approachSpeed;
112
+ moveX += (approachDirX / approachMag) * approachSpeed;
113
+ moveY += (approachDirY / approachMag) * approachSpeed;
101
114
 
102
115
  const magnitude = Math.sqrt(moveX * moveX + moveY * moveY);
103
- if (magnitude > 100)
116
+ if (magnitude > 1)
104
117
  {
105
- moveX = (moveX / magnitude) * 100;
106
- moveY = (moveY / magnitude) * 100;
118
+ moveX = moveX / magnitude;
119
+ moveY = moveY / magnitude;
107
120
  }
108
121
 
109
122
  return {x: moveX, y: moveY};
@@ -124,29 +137,38 @@ function aggressiveMove(
124
137
  *
125
138
  * TIER: 1 (base)
126
139
  */
127
- export const Shadowblade: WizardFunction = ({state}) =>
140
+ export function Shadowblade(): FinalAction
128
141
  {
129
- const stabRange = useParam('stabRange', 76, {
142
+ const position = usePosition();
143
+ const enemy = useEnemy();
144
+ const status = useStatus();
145
+ const blinkCooldown = useBlinkCooldown();
146
+ const tick = useTick();
147
+ const threats = useThreats();
148
+ const projectiles = useProjectiles();
149
+
150
+
151
+ const stabRange = useParam('stabRange', 80, {
130
152
  range: 30,
131
153
  min: 20,
132
154
  max: 80,
133
155
  steps: 5,
134
156
  });
135
- const strafeCyclePeriod = useParam('strafeCyclePeriod', 252, {
157
+ const strafeCyclePeriod = useParam('strafeCyclePeriod', 282, {
136
158
  range: 75,
137
159
  min: 80,
138
160
  max: 300,
139
161
  });
140
- const strafeIntensity = useParam('strafeIntensity', 55, {
141
- range: 30,
142
- min: 10,
143
- max: 80,
162
+ const strafeIntensity = useParam('strafeIntensity', 0.15, {
163
+ range: 0.3,
164
+ min: 0.1,
165
+ max: 0.8,
144
166
  steps: 7,
145
167
  });
146
- const approachSpeed = useParam('approachSpeed', 61, {
147
- range: 20,
148
- min: 50,
149
- max: 100,
168
+ const approachSpeed = useParam('approachSpeed', 0.87, {
169
+ range: 0.2,
170
+ min: 0.5,
171
+ max: 1,
150
172
  steps: 7,
151
173
  });
152
174
  const shieldCancelThreshold = useParam('shieldCancelThreshold', 150, {
@@ -154,35 +176,56 @@ export const Shadowblade: WizardFunction = ({state}) =>
154
176
  min: 50,
155
177
  max: 300,
156
178
  });
157
- const blinkWindowMax = useParam('blinkWindowMax', 23, {
179
+ const blinkWindowMax = useParam('blinkWindowMax', 51, {
158
180
  range: 30,
159
181
  min: 15,
160
182
  max: 80,
161
183
  steps: 7,
162
184
  });
185
+ const stabDamage = Math.round(useParam('stabDamage', 25, {
186
+ range: 10,
187
+ min: 5,
188
+ max: 25,
189
+ steps: 7,
190
+ }));
191
+ const centerBias = useParam('centerBias', 0.8, {
192
+ range: 0.4,
193
+ min: 0,
194
+ max: 0.8,
195
+ steps: 5,
196
+ });
197
+ const maxBlinkDistance = useParam('maxBlinkDistance', 269, {
198
+ range: 100,
199
+ min: 50,
200
+ max: 300,
201
+ steps: 7,
202
+ });
163
203
 
164
- const enemy = state.enemies[0];
165
- if (!enemy)
204
+ const distance = distanceTo(position, enemy.position);
205
+
206
+ function capBlinkDistance(target: {x: number; y: number}): {x: number; y: number}
166
207
  {
167
- return {move: {x: 0, y: 0}};
208
+ const dx = target.x - position.x;
209
+ const dy = target.y - position.y;
210
+ const dist = Math.sqrt(dx * dx + dy * dy);
211
+ if (dist <= maxBlinkDistance) return target;
212
+ const scale = maxBlinkDistance / dist;
213
+ return {x: position.x + dx * scale, y: position.y + dy * scale};
168
214
  }
169
215
 
170
- const distance = distanceTo(state.position, enemy.position);
171
-
172
- const threats = getThreats(state);
173
216
  const urgentThreat = getMostUrgentThreat(threats);
174
217
 
175
218
  // Cancel shield if no longer needed
176
- if (state.state === 'channeling')
219
+ if (status === 'channeling')
177
220
  {
178
221
  if (!urgentThreat || urgentThreat.ticksToImpact > shieldCancelThreshold)
179
222
  {
180
- return {move: {x: 0, y: 0}, cancel: true};
223
+ return cancel();
181
224
  }
182
- return {move: {x: 0, y: 0}};
225
+ return idle();
183
226
  }
184
227
 
185
- if (state.state === 'idle')
228
+ if (status === 'idle')
186
229
  {
187
230
  // === BLINK: Dodge missile + close gap simultaneously (first priority) ===
188
231
  // Wait until the missile is close (<=40 ticks) so the blink actually dodges it.
@@ -190,22 +233,20 @@ export const Shadowblade: WizardFunction = ({state}) =>
190
233
  // blink cast won't complete in time.
191
234
  if (
192
235
  urgentThreat &&
193
- state.blinkCooldown === 0 &&
236
+ blinkCooldown === 0 &&
194
237
  distance > stabRange &&
195
238
  urgentThreat.ticksToImpact >= 10 &&
196
239
  urgentThreat.ticksToImpact <= blinkWindowMax
197
240
  )
198
241
  {
199
- const blinkTarget =
242
+ const rawBlinkTarget =
200
243
  getBlinkToDecreaseDistance(
201
- state.position,
244
+ position,
202
245
  enemy.position,
203
- state.projectiles,
246
+ projectiles,
204
247
  ) ?? enemy.position;
205
- return {
206
- move: {x: 0, y: 0},
207
- startCast: {spell: 'blink', target: blinkTarget},
208
- };
248
+ const blinkTarget = capBlinkDistance(rawBlinkTarget);
249
+ return blink(blinkTarget.x, blinkTarget.y);
209
250
  }
210
251
 
211
252
  // Pre-stab shield: already at melee range, survival beats landing another stab.
@@ -218,23 +259,18 @@ export const Shadowblade: WizardFunction = ({state}) =>
218
259
  urgentThreat.ticksToStartShield <= 3
219
260
  )
220
261
  {
221
- return {
222
- move: {x: 0, y: 0},
223
- startCast: {spell: 'shield'},
224
- };
262
+ return shield();
225
263
  }
226
264
 
265
+ // Pre-cast wall discipline: don't commit to a stab we can't react out of while
266
+ // pinned near a wall — step toward center first so knockback can't reach lava.
267
+ const reposition = getPreCastReposition(position, enemy.position);
268
+ if (reposition) return moveAction(reposition.x, reposition.y);
269
+
227
270
  // Melee stab when close enough (offense over defense at melee range)
228
271
  if (distance < stabRange)
229
272
  {
230
- return {
231
- move: {x: 0, y: 0},
232
- startCast: {
233
- spell: 'missile',
234
- config: STAB_CONFIG,
235
- missileAI: () => ({}), // Non-homing, point blank
236
- },
237
- };
273
+ return missile({damage: stabDamage, speed: STAB_SPEED, turnRate: 0, duration: STAB_DURATION}, () => flyStraight(), 0).move(0, 0);
238
274
  }
239
275
 
240
276
  // === DEFENSE: Shield undodgeable threats (only when blink can't handle it) ===
@@ -244,27 +280,47 @@ export const Shadowblade: WizardFunction = ({state}) =>
244
280
  (!urgentThreat.bestDodgeDirection || shouldForceShield(urgentThreat)) &&
245
281
  urgentThreat.canBlockInTime &&
246
282
  urgentThreat.ticksToStartShield <= 3 &&
247
- (state.blinkCooldown > 0 || urgentThreat.ticksToImpact < 10)
283
+ (blinkCooldown > 0 || urgentThreat.ticksToImpact < 10)
284
+ )
285
+ {
286
+ return shield();
287
+ }
288
+ }
289
+
290
+ // === Preemptive shield — shield when enemy is casting a missile at close range ===
291
+ if (
292
+ status === 'idle' &&
293
+ distance < 300 &&
294
+ enemy.status === 'casting' &&
295
+ enemy.castingSpell === 'missile'
296
+ )
297
+ {
298
+ const remainingCast = (enemy.castDuration ?? 0) - (enemy.castProgress ?? 0);
299
+ const estimatedFlightTicks = distance / 8;
300
+ const ticksUntilHit = remainingCast + estimatedFlightTicks;
301
+
302
+ if (
303
+ ticksUntilHit >= RULES.SHIELD_CAST_TIME &&
304
+ ticksUntilHit < RULES.SHIELD_CAST_TIME + 15
248
305
  )
249
306
  {
250
- return {
251
- move: {x: 0, y: 0},
252
- startCast: {spell: 'shield'},
253
- };
307
+ return shield();
254
308
  }
255
309
  }
256
310
 
257
311
  // Aggressive movement — always close distance
258
- const dodgeDirection = getDodgeDirectionForMovement(threats, state.position);
312
+ const dodgeDirection = getDodgeDirectionForMovement(threats, position);
259
313
  const move = aggressiveMove(
260
- state.position,
314
+ position,
261
315
  enemy,
262
316
  distance,
263
317
  dodgeDirection,
264
- state.tick,
318
+ tick,
265
319
  strafeCyclePeriod,
266
320
  strafeIntensity,
267
321
  approachSpeed,
322
+ centerBias,
268
323
  );
269
- return {move};
270
- };
324
+ const safeMove = keepOffWalls(position, move);
325
+ return moveAction(safeMove.x, safeMove.y);
326
+ }