@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,14 +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, useThreats, useLastMissileConfig, useCastingSpell, useCastProgress, idle, missile, move, shield, blink, cancel, getMissileContext, turnToward, flyStraight} from '../../hooks/index.js';
2
3
  import {distanceTo} from '../../utils/distance.js';
3
4
  import {angleTo} from '../../utils/angles.js';
4
- import {
5
- GCD_DURATION,
6
- SHIELD_CAST_TIME,
7
- BLINK_CAST_TIME,
8
- } from '../../rules.js';
5
+ import {RULES} from '../../rules.js';
9
6
  import {getMissileCastTime, getLeadPosition} from '../../utils/combat.js';
10
7
  import {
11
- getThreats,
12
8
  getMostUrgentThreat,
13
9
  getBlinkToCenter,
14
10
  fitMissileToBudget,
@@ -16,6 +12,8 @@ import {
16
12
  MAX_CAST_BUDGET,
17
13
  MIN_USEFUL_DAMAGE,
18
14
  getEnemyVulnerabilityTicks,
15
+ keepOffWalls,
16
+ getLavaEvade,
19
17
  } from '../shared.js';
20
18
  import {useParam} from '../../engine/params-runtime.js';
21
19
 
@@ -25,16 +23,17 @@ const SHIELD_HOLD_TICKS = 5;
25
23
  /**
26
24
  * Standard homing missile AI — tracks enemy position.
27
25
  */
28
- const HomingAI: MissileAIFunction = ({worldState}) =>
26
+ const HomingAI: MissileFunction = () =>
29
27
  {
30
- const enemy = worldState.enemies[0];
31
- 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();
32
31
  };
33
32
 
34
33
  /**
35
34
  * Straight missile AI — no homing, flies in initial direction.
36
35
  */
37
- const StraightAI: MissileAIFunction = () => ({});
36
+ const StraightAI: MissileFunction = () => flyStraight();
38
37
 
39
38
  /**
40
39
  * Bot: Golem
@@ -54,12 +53,23 @@ const StraightAI: MissileAIFunction = () => ({});
54
53
  * PROGRESSION LINE: Turtle → Sentinel → Golem
55
54
  * TIER: 3 (elite Defensive line)
56
55
  */
57
- export const Golem: WizardFunction = ({state}) =>
56
+ export function Golem(): FinalAction
58
57
  {
58
+ const position = usePosition();
59
+ const enemy = useEnemy();
60
+ const status = useStatus();
61
+ const blinkCooldown = useBlinkCooldown();
62
+ const health = useHealth();
63
+ const threats = useThreats();
64
+ const lastMissileConfig = useLastMissileConfig();
65
+ const castingSpell = useCastingSpell();
66
+ const castProgressData = useCastProgress();
67
+
68
+
59
69
  // Positive turn rate ensures homing missiles that hit moving targets.
60
70
  // Hardcoded — optimizer incorrectly pushes this negative for stationary matchups.
61
71
  const minTurnRate = 0.5;
62
- const preemptiveShieldRange = useParam('preemptiveShieldRange', 150, {
72
+ const preemptiveShieldRange = useParam('preemptiveShieldRange', 65, {
63
73
  range: 85,
64
74
  min: 0,
65
75
  steps: 7,
@@ -68,44 +78,44 @@ export const Golem: WizardFunction = ({state}) =>
68
78
  range: 75,
69
79
  min: 10,
70
80
  });
71
- const punishMinVulnerability = useParam('punishMinVulnerability', 310, {
81
+ const punishMinVulnerability = useParam('punishMinVulnerability', 293, {
72
82
  range: 30,
73
83
  min: 10,
74
84
  steps: 5,
75
85
  });
76
86
  const preferBlinkDodge =
77
87
  useParam('preferBlinkDodge', 1, {min: 0, max: 1, steps: 2}) >= 0.5;
78
- const cancelHeavyDmg = useParam('cancelHeavyDmg', 14, {
88
+ const cancelHeavyDmg = useParam('cancelHeavyDmg', 26, {
79
89
  range: 15,
80
90
  min: 5,
81
91
  max: 40,
82
92
  steps: 5,
83
93
  });
84
- const cancelMediumDmg = useParam('cancelMediumDmg', 12, {
94
+ const cancelMediumDmg = useParam('cancelMediumDmg', 21, {
85
95
  range: 10,
86
96
  min: 3,
87
97
  max: 25,
88
98
  steps: 5,
89
99
  });
90
- const cancelCastRatio = useParam('cancelCastRatio', 0.8, {
100
+ const cancelCastRatio = useParam('cancelCastRatio', 1, {
91
101
  range: 0.4,
92
102
  min: 0.3,
93
103
  max: 1.0,
94
104
  steps: 5,
95
105
  });
96
- const cancelLightDmg = useParam('cancelLightDmg', 8, {
106
+ const cancelLightDmg = useParam('cancelLightDmg', 1, {
97
107
  range: 5,
98
108
  min: 1,
99
109
  max: 15,
100
110
  steps: 5,
101
111
  });
102
- const cancelEarlyCastRatio = useParam('cancelEarlyCastRatio', 0.7, {
112
+ const cancelEarlyCastRatio = useParam('cancelEarlyCastRatio', 0.66, {
103
113
  range: 0.3,
104
114
  min: 0.0,
105
115
  max: 0.7,
106
116
  steps: 5,
107
117
  });
108
- const blinkWindowMax = useParam('blinkWindowMax', 50, {
118
+ const blinkWindowMax = useParam('blinkWindowMax', 52, {
109
119
  range: 40,
110
120
  min: 20,
111
121
  max: 120,
@@ -117,82 +127,85 @@ export const Golem: WizardFunction = ({state}) =>
117
127
  max: 15,
118
128
  steps: 5,
119
129
  });
120
- const flightTimeDivisor = useParam('flightTimeDivisor', 9.5, {
130
+ const flightTimeDivisor = useParam('flightTimeDivisor', 3.1, {
121
131
  range: 4,
122
132
  min: 2,
123
133
  max: 12,
124
134
  steps: 5,
125
135
  });
126
136
 
127
- const enemy = state.enemies[0];
128
- const threats = getThreats(state);
129
137
  const urgentThreat = getMostUrgentThreat(threats);
130
138
  const hittingThreats = threats.filter((t) => t.willHit);
131
139
 
140
+ // Lava-evade: a knockback-lethal hit near a wall must be dodged/blinked, not
141
+ // shielded — knockback ignores shields. Highest priority.
142
+ const lavaEvade = getLavaEvade(position, enemy.position, threats, blinkCooldown);
143
+ if (lavaEvade) return lavaEvade.action === 'blink' ? blink(lavaEvade.x, lavaEvade.y) : move(lavaEvade.x, lavaEvade.y);
144
+
132
145
  // === STEP 1: CHANNELING — hold through volleys, cancel when safe ===
133
- if (state.state === 'channeling' && state.channelingSpell === 'shield')
146
+ if (status === 'channeling' && 'shield' === 'shield')
134
147
  {
135
148
  // Hold if any missile is about to hit
136
149
  const imminentThreat = hittingThreats.find(
137
150
  (t) => t.ticksToImpact <= SHIELD_HOLD_TICKS,
138
151
  );
139
- if (imminentThreat)
152
+ if (imminentThreat)
140
153
  {
141
- return {move: {x: 0, y: 0}};
154
+ { const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
142
155
  }
143
156
 
144
157
  // Hold if next threat will arrive before we can reshield
145
158
  const nextHitting = hittingThreats[0];
146
- const ticksToReshield = SHIELD_CAST_TIME + SHIELD_BUFFER;
147
- if (nextHitting && nextHitting.ticksToImpact <= ticksToReshield)
159
+ const ticksToReshield = RULES.SHIELD_CAST_TIME + SHIELD_BUFFER;
160
+ if (nextHitting && nextHitting.ticksToImpact <= ticksToReshield)
148
161
  {
149
- return {move: {x: 0, y: 0}};
162
+ { const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
150
163
  }
151
164
 
152
165
  // Cancel shield if no threat or threat is far enough
153
- if (!nextHitting || nextHitting.ticksToImpact > shieldCancelThreshold)
166
+ if (!nextHitting || nextHitting.ticksToImpact > shieldCancelThreshold)
154
167
  {
155
- return {move: {x: 0, y: 0}, cancel: true};
168
+ return cancel();
156
169
  }
157
- return {move: {x: 0, y: 0}};
170
+ { const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
158
171
  }
159
172
 
160
173
  // If casting shield, let it finish
161
- if (state.state === 'casting' && state.castingSpell === 'shield')
174
+ if (status === 'casting' && castingSpell === 'shield')
162
175
  {
163
- return {move: {x: 0, y: 0}};
176
+ { const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
164
177
  }
165
178
 
166
179
  // === STEP 2: PROGRESSIVE CAST-CANCEL — evaluate damage tradeoff ===
167
180
  if (
168
- state.state === 'casting' &&
169
- state.castingSpell === 'missile' &&
181
+ status === 'casting' &&
182
+ castingSpell === 'missile' &&
170
183
  urgentThreat
171
184
  )
172
185
  {
173
186
  // Sentinel is stationary — can't dodge. Must always consider canceling.
174
- const remainingCast = (state.castDuration ?? 0) - (state.castProgress ?? 0);
187
+ const remainingCast = ((castProgressData?.total) ?? 0) - ((castProgressData?.current) ?? 0);
175
188
  const defenseTime =
176
- state.blinkCooldown === 0
177
- ? BLINK_CAST_TIME
178
- : SHIELD_CAST_TIME + SHIELD_BUFFER;
189
+ blinkCooldown === 0
190
+ ? RULES.BLINK_CAST_TIME
191
+ : RULES.SHIELD_CAST_TIME + SHIELD_BUFFER;
179
192
  const willArriveInWindow =
180
- urgentThreat.ticksToImpact <= remainingCast + GCD_DURATION + defenseTime;
193
+ urgentThreat.ticksToImpact <= remainingCast + RULES.GCD_DURATION + defenseTime;
181
194
 
182
195
  if (willArriveInWindow)
183
196
  {
184
197
  const incomingDamage = urgentThreat.projectile.damage;
185
- const castRatio = (state.castProgress ?? 0) / (state.castDuration ?? 1);
198
+ const castRatio = ((castProgressData?.current) ?? 0) / ((castProgressData?.total) ?? 1);
186
199
 
187
200
  // Always cancel for lethal or heavy damage
188
- if (incomingDamage >= state.health || incomingDamage >= cancelHeavyDmg)
201
+ if (incomingDamage >= health || incomingDamage >= cancelHeavyDmg)
189
202
  {
190
- return {move: {x: 0, y: 0}, cancel: true};
203
+ return cancel();
191
204
  }
192
205
  // Significant damage + early/mid cast: cancel
193
206
  if (incomingDamage >= cancelMediumDmg && castRatio < cancelCastRatio)
194
207
  {
195
- return {move: {x: 0, y: 0}, cancel: true};
208
+ return cancel();
196
209
  }
197
210
  // Medium damage + very early cast: cancel
198
211
  if (
@@ -200,7 +213,7 @@ export const Golem: WizardFunction = ({state}) =>
200
213
  castRatio < cancelEarlyCastRatio
201
214
  )
202
215
  {
203
- return {move: {x: 0, y: 0}, cancel: true};
216
+ return cancel();
204
217
  }
205
218
  }
206
219
  }
@@ -208,42 +221,36 @@ export const Golem: WizardFunction = ({state}) =>
208
221
  // === STEP 3: BLINK-DODGE — 100% damage avoidance (optimizer-controlled) ===
209
222
  if (
210
223
  preferBlinkDodge &&
211
- state.state === 'idle' &&
224
+ status === 'idle' &&
212
225
  urgentThreat &&
213
- state.blinkCooldown === 0 &&
214
- urgentThreat.ticksToImpact >= BLINK_CAST_TIME &&
226
+ blinkCooldown === 0 &&
227
+ urgentThreat.ticksToImpact >= RULES.BLINK_CAST_TIME &&
215
228
  urgentThreat.ticksToImpact <= blinkWindowMax
216
229
  )
217
230
  {
218
- return {
219
- move: {x: 0, y: 0},
220
- startCast: {spell: 'blink', target: getBlinkToCenter(state.position)},
221
- };
231
+ return blink(getBlinkToCenter(position).x, getBlinkToCenter(position).y);
222
232
  }
223
233
 
224
234
  // === STEP 3b: SHIELD — fallback when blink unavailable (90% block) ===
225
235
  if (
226
- state.state === 'idle' &&
236
+ status === 'idle' &&
227
237
  urgentThreat &&
228
238
  urgentThreat.canBlockInTime &&
229
239
  urgentThreat.ticksToStartShield <= SHIELD_BUFFER
230
240
  )
231
241
  {
232
- return {
233
- move: {x: 0, y: 0},
234
- startCast: {spell: 'shield'},
235
- };
242
+ return shield();
236
243
  }
237
244
 
238
245
  // === STEP 4: PREEMPTIVE SHIELD — enemy casting at close range ===
239
246
  if (
240
- state.state === 'idle' &&
247
+ status === 'idle' &&
241
248
  enemy &&
242
- enemy.state === 'casting' &&
249
+ enemy.status === 'casting' &&
243
250
  enemy.castingSpell === 'missile'
244
251
  )
245
252
  {
246
- const distance = distanceTo(state.position, enemy.position);
253
+ const distance = distanceTo(position, enemy.position);
247
254
  if (distance < preemptiveShieldRange)
248
255
  {
249
256
  const remainingCast =
@@ -252,25 +259,22 @@ export const Golem: WizardFunction = ({state}) =>
252
259
  const ticksUntilHit = remainingCast + estimatedFlightTicks;
253
260
  if (
254
261
  ticksUntilHit <
255
- SHIELD_CAST_TIME + SHIELD_BUFFER + preemptiveShieldBuffer
262
+ RULES.SHIELD_CAST_TIME + SHIELD_BUFFER + preemptiveShieldBuffer
256
263
  )
257
264
  {
258
- return {
259
- move: {x: 0, y: 0},
260
- startCast: {spell: 'shield'},
261
- };
265
+ return shield();
262
266
  }
263
267
  }
264
268
  }
265
269
 
266
270
  // === STEP 5: OFFENSE — punish mode + standard counterattack ===
267
- if (state.state === 'idle' && enemy)
271
+ if (status === 'idle' && enemy)
268
272
  {
269
273
  const offenseDefenseTime =
270
- state.blinkCooldown === 0
271
- ? BLINK_CAST_TIME
272
- : SHIELD_CAST_TIME + SHIELD_BUFFER;
273
- const distance = distanceTo(state.position, enemy.position);
274
+ blinkCooldown === 0
275
+ ? RULES.BLINK_CAST_TIME
276
+ : RULES.SHIELD_CAST_TIME + SHIELD_BUFFER;
277
+ const distance = distanceTo(position, enemy.position);
274
278
  const enemyHP = Math.ceil(enemy.health);
275
279
  const vulnerabilityTicks = getEnemyVulnerabilityTicks(enemy);
276
280
 
@@ -280,7 +284,7 @@ export const Golem: WizardFunction = ({state}) =>
280
284
  const flightTimeEstimate = distance / flightTimeDivisor;
281
285
  const punishBudget = vulnerabilityTicks - flightTimeEstimate;
282
286
  const nextThreatTime = urgentThreat?.ticksToImpact ?? Infinity;
283
- const safeBudget = nextThreatTime - GCD_DURATION - offenseDefenseTime;
287
+ const safeBudget = nextThreatTime - RULES.GCD_DURATION - offenseDefenseTime;
284
288
  const effectiveBudget = Math.min(
285
289
  punishBudget,
286
290
  safeBudget,
@@ -292,12 +296,12 @@ export const Golem: WizardFunction = ({state}) =>
292
296
  const config = fitMissileToBudget(effectiveBudget, distance, {
293
297
  minTurnRate: 0,
294
298
  maxDamage: enemyHP,
295
- lastMissileConfig: state.lastMissileConfig,
299
+ lastMissileConfig: lastMissileConfig,
296
300
  });
297
301
 
298
302
  if (config && config.damage >= MIN_USEFUL_DAMAGE)
299
303
  {
300
- const castTime = getMissileCastTime(config, state.lastMissileConfig);
304
+ const castTime = getMissileCastTime(config, lastMissileConfig);
301
305
  const actualFlight = distance / config.speed;
302
306
 
303
307
  if (
@@ -312,17 +316,9 @@ export const Golem: WizardFunction = ({state}) =>
312
316
  enemy.position,
313
317
  enemy.velocity,
314
318
  config.speed,
315
- state.position,
319
+ position,
316
320
  );
317
- return {
318
- move: {x: 0, y: 0},
319
- startCast: {
320
- spell: 'missile',
321
- config,
322
- missileAI: config.turnRate > 0 ? HomingAI : StraightAI,
323
- direction: angleTo(state.position, aimTarget),
324
- },
325
- };
321
+ return missile(config, config.turnRate > 0 ? HomingAI : StraightAI, angleTo(position, aimTarget)).move(keepOffWalls(position).x, keepOffWalls(position).y);
326
322
  }
327
323
  }
328
324
  }
@@ -330,28 +326,20 @@ export const Golem: WizardFunction = ({state}) =>
330
326
 
331
327
  // --- STANDARD MODE: adaptive homing in safe windows ---
332
328
  const nextThreatTime = urgentThreat?.ticksToImpact ?? Infinity;
333
- const safeBudget = nextThreatTime - GCD_DURATION - offenseDefenseTime;
329
+ const safeBudget = nextThreatTime - RULES.GCD_DURATION - offenseDefenseTime;
334
330
  const budgetTicks = Math.min(safeBudget, MAX_CAST_BUDGET);
335
331
 
336
332
  const config = fitMissileToBudget(budgetTicks, distance, {
337
333
  minTurnRate,
338
334
  maxDamage: enemyHP,
339
- lastMissileConfig: state.lastMissileConfig,
335
+ lastMissileConfig: lastMissileConfig,
340
336
  });
341
337
 
342
338
  if (config && config.damage >= Math.min(MIN_USEFUL_DAMAGE, enemyHP))
343
339
  {
344
- return {
345
- move: {x: 0, y: 0},
346
- startCast: {
347
- spell: 'missile',
348
- config,
349
- missileAI: config.turnRate > 0 ? HomingAI : StraightAI,
350
- direction: angleTo(state.position, enemy.position),
351
- },
352
- };
340
+ return missile(config, config.turnRate > 0 ? HomingAI : StraightAI, angleTo(position, enemy.position)).move(keepOffWalls(position).x, keepOffWalls(position).y);
353
341
  }
354
342
  }
355
343
 
356
- return {move: {x: 0, y: 0}};
357
- };
344
+ { const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
345
+ }