@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,11 +1,13 @@
1
- import {WizardFunction} from '../../types.js';
1
+ import type {FinalAction} from '../../hooks/index.js';
2
+ import {usePosition, useEnemy, useStatus, useBlinkCooldown, useThreats, useLastMissileConfig, useCastingSpell, idle, missile, move, shield, blink, cancel, getMissileContext, turnToward, flyStraight} from '../../hooks/index.js';
2
3
  import {angleTo} from '../../utils/angles.js';
3
- import {GCD_DURATION, SHIELD_CAST_TIME} from '../../rules.js';
4
+ import {RULES} from '../../rules.js';
4
5
  import {getMissileCastTime} from '../../utils/combat.js';
5
6
  import {
6
- getThreats,
7
7
  getMostUrgentThreat,
8
8
  getBlinkToCenter,
9
+ keepOffWalls,
10
+ getLavaEvade,
9
11
  } from '../shared.js';
10
12
  import {useParam} from '../../engine/params-runtime.js';
11
13
 
@@ -44,9 +46,18 @@ import {useParam} from '../../engine/params-runtime.js';
44
46
  // Range = 5 × 180 = 900 units.
45
47
  const MISSILE_CONFIG = {damage: 12, speed: 5, turnRate: 1, duration: 180};
46
48
 
47
- export const Turtle: WizardFunction = ({state}) =>
49
+ export function Turtle(): FinalAction
48
50
  {
49
- const shieldBuffer = useParam('shieldBuffer', 3.1, {
51
+ const position = usePosition();
52
+ const enemy = useEnemy();
53
+ const status = useStatus();
54
+ const blinkCooldown = useBlinkCooldown();
55
+ const threats = useThreats();
56
+ const lastMissileConfig = useLastMissileConfig();
57
+ const castingSpell = useCastingSpell();
58
+
59
+
60
+ const shieldBuffer = useParam('shieldBuffer', 4.3, {
50
61
  range: 5,
51
62
  min: 0,
52
63
  steps: 5,
@@ -56,21 +67,22 @@ export const Turtle: WizardFunction = ({state}) =>
56
67
  min: 0,
57
68
  steps: 5,
58
69
  });
59
- const ticksToReshield = SHIELD_CAST_TIME + shieldBuffer;
60
-
61
- const enemy = state.enemies[0];
62
- if (!enemy) return {move: {x: 0, y: 0}};
70
+ const ticksToReshield = RULES.SHIELD_CAST_TIME + shieldBuffer;
63
71
 
64
- const threats = getThreats(state);
65
72
  const urgentThreat = getMostUrgentThreat(threats);
66
73
 
74
+ // Lava-evade: a knockback-lethal hit near a wall must be dodged/blinked, not
75
+ // shielded — knockback ignores shields. Highest priority.
76
+ const lavaEvade = getLavaEvade(position, enemy.position, threats, blinkCooldown);
77
+ if (lavaEvade) return lavaEvade.action === 'blink' ? blink(lavaEvade.x, lavaEvade.y) : move(lavaEvade.x, lavaEvade.y);
78
+
67
79
  const shouldStartShield =
68
80
  urgentThreat !== null &&
69
- urgentThreat.ticksToImpact <= SHIELD_CAST_TIME + shieldBuffer;
81
+ urgentThreat.ticksToImpact <= RULES.SHIELD_CAST_TIME + shieldBuffer;
70
82
 
71
83
  // === CHANNELING SHIELD ===
72
84
  // Hold shield until the imminent missile(s) have actually impacted.
73
- if (state.state === 'channeling' && state.channelingSpell === 'shield')
85
+ if (status === 'channeling' && 'shield' === 'shield')
74
86
  {
75
87
  // Don't cancel while a missile is about to hit
76
88
  const imminentThreat = threats.find(
@@ -78,74 +90,66 @@ export const Turtle: WizardFunction = ({state}) =>
78
90
  );
79
91
  if (imminentThreat)
80
92
  {
81
- return {move: {x: 0, y: 0}};
93
+ { const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
82
94
  }
83
95
 
84
96
  // Safe to cancel if no next threat, or next threat is far enough to reshield
85
97
  const nextThreat = threats.find((t) => t.willHit);
86
98
  if (!nextThreat || nextThreat.ticksToImpact > ticksToReshield)
87
99
  {
88
- return {move: {x: 0, y: 0}, cancel: true};
100
+ return cancel();
89
101
  }
90
102
  // Next threat too soon — keep channeling
91
- return {move: {x: 0, y: 0}};
103
+ { const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
92
104
  }
93
105
 
94
106
  // === CASTING SHIELD: let it finish ===
95
- if (state.state === 'casting' && state.castingSpell === 'shield')
107
+ if (status === 'casting' && castingSpell === 'shield')
96
108
  {
97
- return {move: {x: 0, y: 0}};
109
+ { const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
98
110
  }
99
111
 
100
112
  // === NEED TO SHIELD NOW: cancel anything else ===
101
113
  if (shouldStartShield)
102
114
  {
103
- if (state.state === 'casting')
115
+ if (status === 'casting')
104
116
  {
105
- return {move: {x: 0, y: 0}, cancel: true};
117
+ return cancel();
106
118
  }
107
- if (state.state === 'idle')
119
+ if (status === 'idle')
108
120
  {
109
- return {move: {x: 0, y: 0}, startCast: {spell: 'shield'}};
121
+ return shield();
110
122
  }
111
123
  }
112
124
 
113
125
  // === EMERGENCY BLINK: missile too close for shield, blink available ===
114
126
  if (
115
- state.state === 'idle' &&
127
+ status === 'idle' &&
116
128
  urgentThreat &&
117
- urgentThreat.ticksToImpact < SHIELD_CAST_TIME + shieldBuffer &&
129
+ urgentThreat.ticksToImpact < RULES.SHIELD_CAST_TIME + shieldBuffer &&
118
130
  urgentThreat.ticksToImpact >= 10 &&
119
- state.blinkCooldown === 0
131
+ blinkCooldown === 0
120
132
  )
121
133
  {
122
- return {
123
- move: {x: 0, y: 0},
124
- startCast: {spell: 'blink', target: getBlinkToCenter(state.position)},
125
- };
134
+ return blink(getBlinkToCenter(position).x, getBlinkToCenter(position).y);
126
135
  }
127
136
 
128
137
  // === ATTACK: one missile if safe window exists ===
129
138
  // Dynamic safe window: actual cast time (with warmup) + GCD + shield + buffer
130
- const castTime = getMissileCastTime(MISSILE_CONFIG, state.lastMissileConfig);
131
- const safeWindow = castTime + GCD_DURATION + SHIELD_CAST_TIME + shieldBuffer;
139
+ const castTime = getMissileCastTime(MISSILE_CONFIG, lastMissileConfig);
140
+ const safeWindow = castTime + RULES.GCD_DURATION + RULES.SHIELD_CAST_TIME + shieldBuffer;
132
141
  if (
133
- state.state === 'idle' &&
142
+ status === 'idle' &&
134
143
  (!urgentThreat || urgentThreat.ticksToImpact > safeWindow)
135
144
  )
136
145
  {
137
- return {
138
- move: {x: 0, y: 0},
139
- startCast: {
140
- spell: 'missile',
141
- config: MISSILE_CONFIG,
142
- missileAI: ({worldState}) => ({
143
- turnToward: worldState.enemies[0]?.position,
144
- }),
145
- direction: angleTo(state.position, enemy.position),
146
- },
147
- };
146
+ return missile(MISSILE_CONFIG, () =>
147
+ {
148
+ const ctx = getMissileContext();
149
+ const enemy = ctx.worldState.enemies[0];
150
+ return enemy ? turnToward(enemy.position.x, enemy.position.y) : flyStraight();
151
+ }, angleTo(position, enemy.position)).move(keepOffWalls(position).x, keepOffWalls(position).y);
148
152
  }
149
153
 
150
- return {move: {x: 0, y: 0}};
151
- };
154
+ { const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
155
+ }
@@ -1,11 +1,13 @@
1
- import {WizardFunction, MissileActions} from '../../types.js';
1
+ import type {FinalAction} from '../../hooks/index.js';
2
+ import {usePosition, useEnemy, useStatus, useBlinkCooldown, useThreats, useLastMissileConfig, useCastingSpell, idle, missile, move, shield, blink, cancel, getMissileContext, turnToward, flyStraight} from '../../hooks/index.js';
2
3
  import {angleTo} from '../../utils/angles.js';
3
- import {GCD_DURATION, SHIELD_CAST_TIME} from '../../rules.js';
4
+ import {RULES} from '../../rules.js';
4
5
  import {getMissileCastTime} from '../../utils/combat.js';
5
6
  import {
6
- getThreats,
7
7
  getMostUrgentThreat,
8
8
  getBlinkToCenter,
9
+ keepOffWalls,
10
+ getLavaEvade,
9
11
  } from '../shared.js';
10
12
  import {useParam} from '../../engine/params-runtime.js';
11
13
 
@@ -24,8 +26,17 @@ import {useParam} from '../../engine/params-runtime.js';
24
26
  *
25
27
  * TIER: 2 (enhanced Turtle)
26
28
  */
27
- export const Sentinel: WizardFunction = ({state}) =>
29
+ export function Sentinel(): FinalAction
28
30
  {
31
+ const position = usePosition();
32
+ const enemy = useEnemy();
33
+ const status = useStatus();
34
+ const blinkCooldown = useBlinkCooldown();
35
+ const threats = useThreats();
36
+ const lastMissileConfig = useLastMissileConfig();
37
+ const castingSpell = useCastingSpell();
38
+
39
+
29
40
  const shieldHoldTicks = useParam('shieldHoldTicks', 2, {
30
41
  range: 5,
31
42
  min: 0,
@@ -33,102 +44,95 @@ export const Sentinel: WizardFunction = ({state}) =>
33
44
  steps: 5,
34
45
  });
35
46
 
36
- const enemy = state.enemies[0];
37
47
 
38
- const threats = getThreats(state);
39
48
  const urgentThreat = getMostUrgentThreat(threats);
40
49
 
41
50
  const SHIELD_BUFFER = 3;
42
51
  const shouldStartShield =
43
52
  urgentThreat !== null &&
44
- urgentThreat.ticksToImpact <= SHIELD_CAST_TIME + SHIELD_BUFFER;
53
+ urgentThreat.ticksToImpact <= RULES.SHIELD_CAST_TIME + SHIELD_BUFFER;
45
54
 
46
- const TICKS_TO_RESHIELD = SHIELD_CAST_TIME + SHIELD_BUFFER;
55
+ const TICKS_TO_RESHIELD = RULES.SHIELD_CAST_TIME + SHIELD_BUFFER;
47
56
  const nextThreat = threats.filter((t) => t.willHit)[1] ?? null;
48
57
  const safeToCancel =
49
58
  !nextThreat || nextThreat.ticksToImpact > TICKS_TO_RESHIELD;
50
59
 
51
- if (state.state === 'channeling' && state.channelingSpell === 'shield')
60
+ // Lava-evade: a knockback-lethal hit near a wall must be dodged/blinked, not
61
+ // shielded — knockback ignores shields. Highest priority.
62
+ const lavaEvade = getLavaEvade(position, enemy.position, threats, blinkCooldown);
63
+ if (lavaEvade) return lavaEvade.action === 'blink' ? blink(lavaEvade.x, lavaEvade.y) : move(lavaEvade.x, lavaEvade.y);
64
+
65
+ if (status === 'channeling' && 'shield' === 'shield')
52
66
  {
53
67
  const imminentThreat = threats.find(
54
68
  (t) => t.willHit && t.ticksToImpact <= shieldHoldTicks,
55
69
  );
56
- if (imminentThreat) return {move: {x: 0, y: 0}};
57
- if (safeToCancel) return {move: {x: 0, y: 0}, cancel: true};
58
- return {move: {x: 0, y: 0}};
70
+ if (imminentThreat)
71
+ {
72
+ const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle();
73
+ }
74
+ if (safeToCancel) return cancel();
75
+ { const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
59
76
  }
60
77
 
61
- if (state.state === 'casting' && state.castingSpell === 'shield')
78
+ if (status === 'casting' && castingSpell === 'shield')
62
79
  {
63
- return {move: {x: 0, y: 0}};
80
+ { const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
64
81
  }
65
82
 
66
83
  if (shouldStartShield)
67
84
  {
68
- if (state.state === 'casting') return {move: {x: 0, y: 0}, cancel: true};
69
- if (state.state === 'idle')
85
+ if (status === 'casting') return cancel();
86
+ if (status === 'idle')
70
87
  {
71
- return {move: {x: 0, y: 0}, startCast: {spell: 'shield'}};
88
+ return shield();
72
89
  }
73
90
  }
74
91
 
75
92
  if (
76
- state.state === 'idle' &&
93
+ status === 'idle' &&
77
94
  urgentThreat &&
78
- urgentThreat.ticksToImpact < SHIELD_CAST_TIME + SHIELD_BUFFER &&
95
+ urgentThreat.ticksToImpact < RULES.SHIELD_CAST_TIME + SHIELD_BUFFER &&
79
96
  urgentThreat.ticksToImpact >= 10 &&
80
- state.blinkCooldown === 0
97
+ blinkCooldown === 0
81
98
  )
82
99
  {
83
- return {
84
- move: {x: 0, y: 0},
85
- startCast: {spell: 'blink', target: getBlinkToCenter(state.position)},
86
- };
100
+ return blink(getBlinkToCenter(position).x, getBlinkToCenter(position).y);
87
101
  }
88
102
 
89
103
  // Two-tier offense: big missile when safe, fast missile otherwise.
90
104
  // Same speed/turnRate/duration → no warmup switching penalty.
91
- if (state.state === 'idle' && enemy)
105
+ if (status === 'idle' && enemy)
92
106
  {
93
107
  const FAST_MISSILE = {damage: 12, speed: 5, turnRate: 1, duration: 180};
94
108
  const BIG_MISSILE = {damage: 20, speed: 5, turnRate: 1, duration: 180};
95
109
 
96
110
  const nextThreatTime = urgentThreat?.ticksToImpact ?? Infinity;
97
111
 
98
- const bigCast = getMissileCastTime(BIG_MISSILE, state.lastMissileConfig);
99
- const bigWindow = bigCast + GCD_DURATION + SHIELD_CAST_TIME + SHIELD_BUFFER;
112
+ const bigCast = getMissileCastTime(BIG_MISSILE, lastMissileConfig);
113
+ const bigWindow = bigCast + RULES.GCD_DURATION + RULES.SHIELD_CAST_TIME + SHIELD_BUFFER;
100
114
  if (!urgentThreat || nextThreatTime > bigWindow)
101
115
  {
102
- return {
103
- move: {x: 0, y: 0},
104
- startCast: {
105
- spell: 'missile',
106
- config: BIG_MISSILE,
107
- missileAI: ({worldState}): MissileActions => ({
108
- turnToward: worldState.enemies[0]?.position,
109
- }),
110
- direction: angleTo(state.position, enemy.position),
111
- },
112
- };
116
+ return missile(BIG_MISSILE, () =>
117
+ {
118
+ const ctx = getMissileContext();
119
+ const e = ctx.worldState.enemies[0];
120
+ return e ? turnToward(e.position.x, e.position.y) : flyStraight();
121
+ }, angleTo(position, enemy.position)).move(keepOffWalls(position).x, keepOffWalls(position).y);
113
122
  }
114
123
 
115
- const fastCast = getMissileCastTime(FAST_MISSILE, state.lastMissileConfig);
116
- const fastWindow = fastCast + GCD_DURATION + SHIELD_CAST_TIME + SHIELD_BUFFER;
124
+ const fastCast = getMissileCastTime(FAST_MISSILE, lastMissileConfig);
125
+ const fastWindow = fastCast + RULES.GCD_DURATION + RULES.SHIELD_CAST_TIME + SHIELD_BUFFER;
117
126
  if (!urgentThreat || nextThreatTime > fastWindow)
118
127
  {
119
- return {
120
- move: {x: 0, y: 0},
121
- startCast: {
122
- spell: 'missile',
123
- config: FAST_MISSILE,
124
- missileAI: ({worldState}): MissileActions => ({
125
- turnToward: worldState.enemies[0]?.position,
126
- }),
127
- direction: angleTo(state.position, enemy.position),
128
- },
129
- };
128
+ return missile(FAST_MISSILE, () =>
129
+ {
130
+ const ctx = getMissileContext();
131
+ const e = ctx.worldState.enemies[0];
132
+ return e ? turnToward(e.position.x, e.position.y) : flyStraight();
133
+ }, angleTo(position, enemy.position)).move(keepOffWalls(position).x, keepOffWalls(position).y);
130
134
  }
131
135
  }
132
136
 
133
- return {move: {x: 0, y: 0}};
134
- };
137
+ { const h = keepOffWalls(position); return (h.x !== 0 || h.y !== 0) ? move(h.x, h.y) : idle(); }
138
+ }