@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,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, useMyProjectiles, 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, getLeadPosition} from '../../utils/combat.js';
6
7
  import {
7
- getThreats,
8
8
  getMostUrgentThreat,
9
9
  getDodgeDirectionForMovement,
10
10
  getBlinkToCenter,
@@ -13,27 +13,30 @@ import {
13
13
  MAX_CAST_BUDGET,
14
14
  MIN_USEFUL_DAMAGE,
15
15
  getEnemyVulnerabilityTicks,
16
+ keepOffWalls,
17
+ getPreCastReposition,
16
18
  } from '../shared.js';
17
19
  import {useParam} from '../../engine/params-runtime.js';
18
20
 
19
21
  const WALL_BUFFER = 60;
20
22
  const SHIELD_BUFFER = 3;
21
23
 
22
- const HomingAI: MissileAIFunction = ({worldState}) =>
24
+ const HomingAI: MissileFunction = () =>
23
25
  {
24
- const enemy = worldState.enemies[0];
25
- return {turnToward: enemy?.position};
26
+ const ctx = getMissileContext();
27
+ const enemy = ctx.worldState.enemies[0];
28
+ return enemy ? turnToward(enemy.position.x, enemy.position.y) : flyStraight();
26
29
  };
27
30
 
28
- const StraightAI: MissileAIFunction = () => ({});
31
+ const StraightAI: MissileFunction = () => flyStraight();
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
 
@@ -64,8 +67,8 @@ function smartStrafe(
64
67
  {
65
68
  const fCw = {x: position.x + cw.x * 100, y: position.y + cw.y * 100};
66
69
  const fCcw = {x: position.x + ccw.x * 100, y: position.y + ccw.y * 100};
67
- const sCw = Math.min(fCw.x, ARENA_SIZE - fCw.x, fCw.y, ARENA_SIZE - fCw.y);
68
- const sCcw = Math.min(fCcw.x, ARENA_SIZE - fCcw.x, fCcw.y, ARENA_SIZE - fCcw.y);
70
+ const sCw = Math.min(fCw.x - ARENA_MIN, ARENA_MAX - fCw.x, fCw.y - ARENA_MIN, ARENA_MAX - fCw.y);
71
+ const sCcw = Math.min(fCcw.x - ARENA_MIN, ARENA_MAX - fCcw.x, fCcw.y - ARENA_MIN, ARENA_MAX - fCcw.y);
69
72
  dir = sCw > sCcw ? cw : ccw;
70
73
  }
71
74
  else
@@ -74,7 +77,7 @@ function smartStrafe(
74
77
  dir = cycle === 0 ? cw : ccw;
75
78
  }
76
79
 
77
- const intensity = dodgeDirection ? 100 : strafeIntensity;
80
+ const intensity = dodgeDirection ? 1 : strafeIntensity;
78
81
  return {x: dir.x * intensity, y: dir.y * intensity};
79
82
  }
80
83
 
@@ -89,41 +92,81 @@ function smartStrafe(
89
92
  * PROGRESSION LINE: Bonemancer → Lich → Archlich
90
93
  * TIER: 3 (elite Homing line)
91
94
  */
92
- export const Archlich: WizardFunction = ({state}) =>
95
+ export function Archlich(): FinalAction
93
96
  {
97
+ const position = usePosition();
98
+ const enemy = useEnemy();
99
+ const status = useStatus();
100
+ const blinkCooldown = useBlinkCooldown();
101
+ const health = useHealth();
102
+ const tick = useTick();
103
+ const threats = useThreats();
104
+ const projectiles = useProjectiles();
105
+ const myProjectiles = useMyProjectiles();
106
+ const lastMissileConfig = useLastMissileConfig();
107
+ const castingSpell = useCastingSpell();
108
+ const castProgressData = useCastProgress();
109
+
110
+
94
111
  // Same params as Lich — Archlich is Lich + punish mode (disabled by default, optimizer tunes)
95
- const targetDistance = useParam('targetDistance', 737, {range: 175, min: 0});
112
+ const targetDistance = useParam('targetDistance', 1160, {range: 175, min: 0});
96
113
  const minTurnRate = useParam('minTurnRate', 0.4, {range: 1.5, steps: 5});
97
114
  const shieldHealthThreshold = useParam('shieldHealthThreshold', 99, {range: 23, min: 1, steps: 7});
98
- const strafeCyclePeriod = useParam('strafeCyclePeriod', 300, {range: 75, min: 80, max: 300});
99
- const strafeIntensity = useParam('strafeIntensity', 96, {range: 40, min: 20, max: 100, steps: 7});
100
- const approachSpeed = useParam('approachSpeed', 65, {range: 30, min: 10, max: 70, steps: 7});
101
- const distanceDeadZone = useParam('distanceDeadZone', 7, {range: 30, min: 5, max: 80, steps: 7});
115
+ const strafeCyclePeriod = useParam('strafeCyclePeriod', 181, {range: 75, min: 80, max: 300});
116
+ const strafeIntensity = useParam('strafeIntensity', 0.98, {range: 0.4, min: 0.2, max: 1, steps: 7});
117
+ const approachSpeed = useParam('approachSpeed', 0.1, {range: 0.3, min: 0.1, max: 0.7, steps: 7});
118
+ const distanceDeadZone = useParam('distanceDeadZone', 51, {range: 30, min: 5, max: 80, steps: 7});
102
119
  const shieldCancelThreshold = useParam('shieldCancelThreshold', 150, {range: 75, min: 50, max: 300});
103
- const blinkWindowMax = useParam('blinkWindowMax', 38, {range: 30, min: 15, max: 80, steps: 7});
104
- const flightTimeDivisor = useParam('flightTimeDivisor', 5.8, {range: 4, min: 2, max: 12, steps: 5});
120
+ const blinkWindowMax = useParam('blinkWindowMax', 80, {range: 30, min: 15, max: 80, steps: 7});
121
+ const flightTimeDivisor = useParam('flightTimeDivisor', 6.6, {range: 4, min: 2, max: 12, steps: 5});
105
122
  const punishMinVulnerability = useParam('punishMinVulnerability', 500, {range: 80, min: 30, steps: 7});
123
+ const centerBias = useParam('centerBias', 0.3, {
124
+ range: 0.4,
125
+ min: 0,
126
+ max: 0.8,
127
+ steps: 5,
128
+ });
129
+ const maxBlinkDistance = useParam('maxBlinkDistance', 283, {
130
+ range: 100,
131
+ min: 50,
132
+ max: 300,
133
+ steps: 7,
134
+ });
106
135
 
107
- const enemy = state.enemies[0];
108
- if (!enemy) return {move: {x: 0, y: 0}};
136
+ function capBlinkDistance(target: {x: number; y: number}): {x: number; y: number}
137
+ {
138
+ const dx = target.x - position.x;
139
+ const dy = target.y - position.y;
140
+ const dist = Math.sqrt(dx * dx + dy * dy);
141
+ if (dist <= maxBlinkDistance) return target;
142
+ const scale = maxBlinkDistance / dist;
143
+ return {x: position.x + dx * scale, y: position.y + dy * scale};
144
+ }
109
145
 
110
- const distance = distanceTo(state.position, enemy.position);
111
- const threats = getThreats(state);
146
+ const distance = distanceTo(position, enemy.position);
112
147
  const urgentThreat = getMostUrgentThreat(threats);
113
- const dodgeDirection = getDodgeDirectionForMovement(threats, state.position);
114
- const strafe = smartStrafe(state.position, enemy, dodgeDirection, state.tick, strafeCyclePeriod, strafeIntensity);
148
+ const dodgeDirection = getDodgeDirectionForMovement(threats, position);
149
+ const strafe = smartStrafe(position, enemy, dodgeDirection, tick, strafeCyclePeriod, strafeIntensity);
115
150
 
116
151
  let moveX = strafe.x;
117
152
  let moveY = strafe.y;
118
153
  if (!dodgeDirection)
119
154
  {
120
- const dx = enemy.position.x - state.position.x;
121
- const dy = enemy.position.y - state.position.y;
155
+ const dx = enemy.position.x - position.x;
156
+ const dy = enemy.position.y - position.y;
122
157
  const nd = distance > 0 ? distance : 1;
123
158
  if (distance > targetDistance + distanceDeadZone)
124
159
  {
125
- moveX += (dx / nd) * approachSpeed;
126
- moveY += (dy / nd) * approachSpeed;
160
+ // Approach the center-side of the enemy so knockback pushes toward center, not lava.
161
+ const center = (ARENA_MIN + ARENA_MAX) / 2;
162
+ const toCenterX = center - enemy.position.x;
163
+ const toCenterY = center - enemy.position.y;
164
+ const toCenterDist = Math.sqrt(toCenterX * toCenterX + toCenterY * toCenterY) || 1;
165
+ const approachDirX = dx / nd + (toCenterX / toCenterDist) * centerBias;
166
+ const approachDirY = dy / nd + (toCenterY / toCenterDist) * centerBias;
167
+ const approachMag = Math.sqrt(approachDirX * approachDirX + approachDirY * approachDirY) || 1;
168
+ moveX += (approachDirX / approachMag) * approachSpeed;
169
+ moveY += (approachDirY / approachMag) * approachSpeed;
127
170
  }
128
171
  else if (distance < targetDistance - distanceDeadZone)
129
172
  {
@@ -132,47 +175,73 @@ export const Archlich: WizardFunction = ({state}) =>
132
175
  }
133
176
  }
134
177
  const mag = Math.sqrt(moveX * moveX + moveY * moveY);
135
- if (mag > 100)
178
+ if (mag > 1)
136
179
  {
137
- moveX = (moveX / mag) * 100; moveY = (moveY / mag) * 100;
180
+ moveX = moveX / mag; moveY = moveY / mag;
138
181
  }
139
182
  const move = {x: moveX, y: moveY};
183
+ const safeMove = keepOffWalls(position, move);
140
184
 
141
185
  // === DEFENSE: identical to Lich ===
142
- if (state.state === 'channeling')
186
+ if (status === 'channeling')
143
187
  {
144
- if (!urgentThreat || urgentThreat.ticksToImpact > shieldCancelThreshold) return {move, cancel: true};
145
- return {move: {x: 0, y: 0}};
188
+ if (!urgentThreat || urgentThreat.ticksToImpact > shieldCancelThreshold) return cancel().move(safeMove.x, safeMove.y);
189
+ return idle();
146
190
  }
147
191
 
148
- if (state.state === 'casting' && state.castingSpell === 'missile' && urgentThreat && urgentThreat.projectile.damage >= state.health)
192
+ if (status === 'casting' && castingSpell === 'missile' && urgentThreat && urgentThreat.projectile.damage >= health)
149
193
  {
150
- const rem = (state.castDuration ?? 0) - (state.castProgress ?? 0);
151
- if (urgentThreat.ticksToImpact <= rem + GCD_DURATION + SHIELD_CAST_TIME + SHIELD_BUFFER) return {move, cancel: true};
194
+ const rem = ((castProgressData?.total) ?? 0) - ((castProgressData?.current) ?? 0);
195
+ if (urgentThreat.ticksToImpact <= rem + RULES.GCD_DURATION + RULES.SHIELD_CAST_TIME + SHIELD_BUFFER) return cancel().move(safeMove.x, safeMove.y);
152
196
  }
153
197
 
154
- if (state.state === 'idle' && urgentThreat && state.blinkCooldown === 0 && urgentThreat.ticksToImpact >= 10 && urgentThreat.ticksToImpact <= blinkWindowMax)
198
+ if (status === 'idle' && urgentThreat && blinkCooldown === 0 && urgentThreat.ticksToImpact >= 10 && urgentThreat.ticksToImpact <= blinkWindowMax)
155
199
  {
156
- return {move: {x: 0, y: 0}, startCast: {spell: 'blink', target: getBlinkToCenter(state.position)}};
200
+ const blinkTarget = capBlinkDistance(getBlinkToCenter(position));
201
+ return blink(blinkTarget.x, blinkTarget.y);
157
202
  }
158
203
 
159
- if (state.state === 'idle' && urgentThreat)
204
+ if (status === 'idle' && urgentThreat)
160
205
  {
161
206
  const forceShield = shouldForceShield(urgentThreat);
162
207
  const undodgeable = !urgentThreat.bestDodgeDirection;
163
- const healthLow = state.health < shieldHealthThreshold;
164
- const healthCritical = state.health <= urgentThreat.projectile.damage * 2 + 1;
208
+ const healthLow = health < shieldHealthThreshold;
209
+ const healthCritical = health <= urgentThreat.projectile.damage * 2 + 1;
165
210
  if ((forceShield || (undodgeable && healthLow) || healthCritical) && urgentThreat.canBlockInTime && urgentThreat.ticksToStartShield <= SHIELD_BUFFER)
166
211
  {
167
- return {move: {x: 0, y: 0}, startCast: {spell: 'shield'}};
212
+ return shield();
213
+ }
214
+ }
215
+
216
+ // === Preemptive shield — shield when enemy is casting a missile at close range ===
217
+ if (
218
+ status === 'idle' &&
219
+ distance < 300 &&
220
+ enemy.status === 'casting' &&
221
+ enemy.castingSpell === 'missile'
222
+ )
223
+ {
224
+ const remainingCast = (enemy.castDuration ?? 0) - (enemy.castProgress ?? 0);
225
+ const estimatedFlightTicks = distance / 8;
226
+ const ticksUntilHit = remainingCast + estimatedFlightTicks;
227
+
228
+ if (
229
+ ticksUntilHit >= RULES.SHIELD_CAST_TIME &&
230
+ ticksUntilHit < RULES.SHIELD_CAST_TIME + 15
231
+ )
232
+ {
233
+ return shield();
168
234
  }
169
235
  }
170
236
 
171
237
  // === OFFENSE ===
172
- const myIds = new Set(state.myProjectiles.map((p) => p.id));
173
- const hasLethal = state.projectiles.some((p) => !myIds.has(p.id) && p.damage >= state.health && distanceTo(state.position, p.position) / p.speed < MAX_CAST_BUDGET + GCD_DURATION);
174
- if (state.state === 'idle' && !hasLethal)
238
+ const myIds = new Set(myProjectiles.map((p) => p.id));
239
+ const hasLethal = projectiles.some((p) => !myIds.has(p.id) && p.damage >= health && distanceTo(position, p.position) / p.speed < MAX_CAST_BUDGET + RULES.GCD_DURATION);
240
+ if (status === 'idle' && !hasLethal)
175
241
  {
242
+ const reposition = getPreCastReposition(position, enemy.position);
243
+ if (reposition) return moveAction(reposition.x, reposition.y);
244
+
176
245
  const enemyHP = Math.ceil(enemy.health);
177
246
 
178
247
  // T3 PUNISH: fast straight missiles during vulnerability
@@ -182,18 +251,18 @@ export const Archlich: WizardFunction = ({state}) =>
182
251
  const flight = distance / flightTimeDivisor;
183
252
  const pBudget = vulnTicks - flight;
184
253
  const ntt = urgentThreat?.ticksToImpact ?? Infinity;
185
- const sBudget = ntt - GCD_DURATION - SHIELD_CAST_TIME - SHIELD_BUFFER;
254
+ const sBudget = ntt - RULES.GCD_DURATION - RULES.SHIELD_CAST_TIME - SHIELD_BUFFER;
186
255
  const eBudget = Math.min(pBudget, sBudget, MAX_CAST_BUDGET);
187
256
  if (eBudget > 0)
188
257
  {
189
- const cfg = fitMissileToBudget(eBudget, distance, {minTurnRate: 0, maxDamage: enemyHP, lastMissileConfig: state.lastMissileConfig});
258
+ const cfg = fitMissileToBudget(eBudget, distance, {minTurnRate: 0, maxDamage: enemyHP, lastMissileConfig: lastMissileConfig});
190
259
  if (cfg && cfg.damage >= MIN_USEFUL_DAMAGE)
191
260
  {
192
- const ct = getMissileCastTime(cfg, state.lastMissileConfig);
261
+ const ct = getMissileCastTime(cfg, lastMissileConfig);
193
262
  if (ct + distance / cfg.speed < vulnTicks)
194
263
  {
195
- const aim = cfg.turnRate > 0 ? enemy.position : getLeadPosition(enemy.position, enemy.velocity, cfg.speed, state.position);
196
- return {move, startCast: {spell: 'missile', config: cfg, missileAI: cfg.turnRate > 0 ? HomingAI : StraightAI, direction: angleTo(state.position, aim)}};
264
+ const aim = cfg.turnRate > 0 ? enemy.position : getLeadPosition(enemy.position, enemy.velocity, cfg.speed, position);
265
+ return missile(cfg, cfg.turnRate > 0 ? HomingAI : StraightAI, angleTo(position, aim)).move(safeMove.x, safeMove.y);
197
266
  }
198
267
  }
199
268
  }
@@ -201,20 +270,20 @@ export const Archlich: WizardFunction = ({state}) =>
201
270
 
202
271
  // STANDARD MODE: identical to Lich
203
272
  let ntt = urgentThreat?.ticksToImpact ?? Infinity;
204
- if (enemy.state === 'casting' && enemy.castingSpell === 'missile')
273
+ if (enemy.status === 'casting' && enemy.castingSpell === 'missile')
205
274
  {
206
275
  const rem = (enemy.castDuration ?? 0) - (enemy.castProgress ?? 0);
207
276
  ntt = Math.min(ntt, rem + distance / flightTimeDivisor);
208
277
  }
209
- const sBudget = ntt - GCD_DURATION - SHIELD_CAST_TIME - SHIELD_BUFFER;
278
+ const sBudget = ntt - RULES.GCD_DURATION - RULES.SHIELD_CAST_TIME - SHIELD_BUFFER;
210
279
  const budget = Math.min(sBudget, MAX_CAST_BUDGET);
211
280
  const minDmg = Math.min(MIN_USEFUL_DAMAGE, enemyHP);
212
- const cfg = fitMissileToBudget(budget, distance, {minTurnRate, lastMissileConfig: state.lastMissileConfig});
281
+ const cfg = fitMissileToBudget(budget, distance, {minTurnRate, lastMissileConfig: lastMissileConfig});
213
282
  if (cfg && cfg.damage >= minDmg)
214
283
  {
215
- return {move, startCast: {spell: 'missile', config: cfg, missileAI: HomingAI, direction: angleTo(state.position, enemy.position)}};
284
+ return missile(cfg, HomingAI, angleTo(position, enemy.position)).move(safeMove.x, safeMove.y);
216
285
  }
217
286
  }
218
287
 
219
- return {move};
220
- };
288
+ return moveAction(safeMove.x, safeMove.y);
289
+ }