@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,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
+ }