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