@vibemancer/core 0.1.0 → 0.1.2

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 (50) hide show
  1. package/README.md +28 -28
  2. package/dist/{chunk-L7Z7OFXD.js → chunk-OL7ETV6V.js} +3 -3
  3. package/dist/chunk-OL7ETV6V.js.map +1 -0
  4. package/dist/index-browser.d.ts +1 -1
  5. package/dist/index-browser.js +1 -1
  6. package/dist/index.js +1 -1
  7. package/package.json +79 -78
  8. package/src/bots/berserker/01_Stormchaser.ts +457 -457
  9. package/src/bots/berserker/02_Stormcaller.ts +417 -417
  10. package/src/bots/berserker/03_Stormforger.ts +481 -481
  11. package/src/bots/caster/01_Flamecaller.ts +286 -286
  12. package/src/bots/caster/02_Pyromancer.ts +350 -350
  13. package/src/bots/caster/03_Infernalist.ts +492 -492
  14. package/src/bots/defensive/01_Turtle.ts +151 -151
  15. package/src/bots/defensive/02_Sentinel.ts +134 -134
  16. package/src/bots/defensive/03_Golem.ts +357 -357
  17. package/src/bots/duelist/01_Battlemage.ts +433 -433
  18. package/src/bots/duelist/02_Warmage.ts +438 -438
  19. package/src/bots/duelist/03_Archmage.ts +588 -588
  20. package/src/bots/homing/01_Bonemancer.ts +67 -67
  21. package/src/bots/homing/02_Lich.ts +356 -356
  22. package/src/bots/homing/03_Archlich.ts +220 -220
  23. package/src/bots/kiter/01_Spellspinner.ts +398 -398
  24. package/src/bots/kiter/02_Spellweaver.ts +378 -378
  25. package/src/bots/kiter/03_Spellbinder.ts +448 -448
  26. package/src/bots/melee/01_Shadowblade.ts +270 -270
  27. package/src/bots/melee/02_Nightblade.ts +437 -437
  28. package/src/bots/melee/03_Voidblade.ts +582 -582
  29. package/src/bots/sniper/01_Spellshot.ts +385 -385
  30. package/src/bots/sniper/02_Spelltracer.ts +441 -441
  31. package/src/bots/sniper/03_Spellseeker.ts +546 -546
  32. package/src/bots/standalone/Critter.ts +89 -89
  33. package/src/bots/standalone/Doombringer.ts +91 -91
  34. package/src/bots/standalone/Hogger.ts +228 -228
  35. package/src/bots/standalone/Rookie.ts +50 -50
  36. package/src/bots/standalone/TargetDummy.ts +21 -21
  37. package/src/bots/test/cheater.ts +405 -405
  38. package/src/bots/test/crasher.ts +81 -81
  39. package/src/engine/hooks-runtime.ts +394 -394
  40. package/src/engine/manual-match.ts +289 -289
  41. package/src/engine/missile-templates.ts +155 -155
  42. package/src/engine/physics.ts +143 -143
  43. package/src/engine/simulation.ts +828 -828
  44. package/src/engine/spells.ts +128 -128
  45. package/src/engine-version.ts +1 -1
  46. package/src/index.ts +23 -23
  47. package/src/rules.ts +254 -254
  48. package/src/types.ts +193 -193
  49. package/src/utils/index.ts +6 -6
  50. package/dist/chunk-L7Z7OFXD.js.map +0 -1
@@ -1,270 +1,270 @@
1
- import {WizardFunction} from '../../types.js';
2
- import {distanceTo} from '../../utils/distance.js';
3
- import {ARENA_SIZE} from '../../rules.js';
4
- import {
5
- getThreats,
6
- getMostUrgentThreat,
7
- getDodgeDirectionForMovement,
8
- getBlinkToDecreaseDistance,
9
- shouldForceShield,
10
- } from '../shared.js';
11
- import {useParam} from '../../engine/params-runtime.js';
12
- const WALL_BUFFER = 50;
13
-
14
- // Melee stab: 20 damage, speed 8, 3-hit kill.
15
- const STAB_CONFIG = {damage: 20, speed: 8, turnRate: 0, duration: 10};
16
-
17
- /**
18
- * Aggressive movement: dodge missiles using threat analysis, close distance, light strafe.
19
- */
20
- function aggressiveMove(
21
- position: {x: number; y: number},
22
- enemy: {position: {x: number; y: number}},
23
- currentDistance: number,
24
- dodgeDirection: {x: number; y: number} | null,
25
- tick: number,
26
- strafeCyclePeriod: number,
27
- strafeIntensity: number,
28
- approachSpeed: number,
29
- ): {x: number; y: number}
30
- {
31
- const deltaX = enemy.position.x - position.x;
32
- const deltaY = enemy.position.y - position.y;
33
- const normalizedDistance = currentDistance > 0 ? currentDistance : 1;
34
-
35
- const strafeClockwise = {
36
- x: -deltaY / normalizedDistance,
37
- y: deltaX / normalizedDistance,
38
- };
39
- const strafeCounterClockwise = {
40
- x: deltaY / normalizedDistance,
41
- y: -deltaX / normalizedDistance,
42
- };
43
-
44
- let strafeDirection: {x: number; y: number};
45
-
46
- if (dodgeDirection)
47
- {
48
- strafeDirection = dodgeDirection;
49
- }
50
- 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
55
- )
56
- {
57
- const futureClockwise = {
58
- x: position.x + strafeClockwise.x * 100,
59
- y: position.y + strafeClockwise.y * 100,
60
- };
61
- const futureCounterClockwise = {
62
- x: position.x + strafeCounterClockwise.x * 100,
63
- y: position.y + strafeCounterClockwise.y * 100,
64
- };
65
- const scoreClockwise = Math.min(
66
- futureClockwise.x,
67
- ARENA_SIZE - futureClockwise.x,
68
- futureClockwise.y,
69
- ARENA_SIZE - futureClockwise.y,
70
- );
71
- const scoreCounterClockwise = Math.min(
72
- futureCounterClockwise.x,
73
- ARENA_SIZE - futureCounterClockwise.x,
74
- futureCounterClockwise.y,
75
- ARENA_SIZE - futureCounterClockwise.y,
76
- );
77
- strafeDirection =
78
- scoreClockwise > scoreCounterClockwise
79
- ? strafeClockwise
80
- : strafeCounterClockwise;
81
- }
82
- else
83
- {
84
- const cycle = Math.floor(tick / strafeCyclePeriod) % 2;
85
- strafeDirection = cycle === 0 ? strafeClockwise : strafeCounterClockwise;
86
- }
87
-
88
- // When actively dodging, commit fully
89
- if (dodgeDirection)
90
- {
91
- return {x: strafeDirection.x * 100, y: strafeDirection.y * 100};
92
- }
93
-
94
- // Light strafe + aggressive closing
95
- let moveX = strafeDirection.x * strafeIntensity;
96
- let moveY = strafeDirection.y * strafeIntensity;
97
-
98
- // Always close distance aggressively (melee wants to be in the enemy's face)
99
- moveX += (deltaX / normalizedDistance) * approachSpeed;
100
- moveY += (deltaY / normalizedDistance) * approachSpeed;
101
-
102
- const magnitude = Math.sqrt(moveX * moveX + moveY * moveY);
103
- if (magnitude > 100)
104
- {
105
- moveX = (moveX / magnitude) * 100;
106
- moveY = (moveY / magnitude) * 100;
107
- }
108
-
109
- return {x: moveX, y: moveY};
110
- }
111
-
112
- /**
113
- * Bot: Shadowblade
114
- *
115
- * BEHAVIOR: Melee assassin. Blinks to the enemy, then lands devastating point-blank
116
- * stab attacks (15 damage, 30u range, ~60 tick cast = 2-hit kill). Runs directly
117
- * at the enemy with minimal strafe, shields undodgeable threats. The entire
118
- * strategy is: get close, stab, kill. Simple and brutal.
119
- *
120
- * PROGRESSION LINE: Shadowblade → Nightblade → Voidblade
121
- * - Shadowblade (tier 1): Offensive blink, melee stabs, basic shield
122
- * - Nightblade (tier 2): + missile-aware blinks, adaptive stabs, timed defense
123
- * - Voidblade (tier 3): Future — perfect assassination timing, inescapable engages
124
- *
125
- * TIER: 1 (base)
126
- */
127
- export const Shadowblade: WizardFunction = ({state}) =>
128
- {
129
- const stabRange = useParam('stabRange', 76, {
130
- range: 30,
131
- min: 20,
132
- max: 80,
133
- steps: 5,
134
- });
135
- const strafeCyclePeriod = useParam('strafeCyclePeriod', 252, {
136
- range: 75,
137
- min: 80,
138
- max: 300,
139
- });
140
- const strafeIntensity = useParam('strafeIntensity', 55, {
141
- range: 30,
142
- min: 10,
143
- max: 80,
144
- steps: 7,
145
- });
146
- const approachSpeed = useParam('approachSpeed', 61, {
147
- range: 20,
148
- min: 50,
149
- max: 100,
150
- steps: 7,
151
- });
152
- const shieldCancelThreshold = useParam('shieldCancelThreshold', 150, {
153
- range: 75,
154
- min: 50,
155
- max: 300,
156
- });
157
- const blinkWindowMax = useParam('blinkWindowMax', 23, {
158
- range: 30,
159
- min: 15,
160
- max: 80,
161
- steps: 7,
162
- });
163
-
164
- const enemy = state.enemies[0];
165
- if (!enemy)
166
- {
167
- return {move: {x: 0, y: 0}};
168
- }
169
-
170
- const distance = distanceTo(state.position, enemy.position);
171
-
172
- const threats = getThreats(state);
173
- const urgentThreat = getMostUrgentThreat(threats);
174
-
175
- // Cancel shield if no longer needed
176
- if (state.state === 'channeling')
177
- {
178
- if (!urgentThreat || urgentThreat.ticksToImpact > shieldCancelThreshold)
179
- {
180
- return {move: {x: 0, y: 0}, cancel: true};
181
- }
182
- return {move: {x: 0, y: 0}};
183
- }
184
-
185
- if (state.state === 'idle')
186
- {
187
- // === BLINK: Dodge missile + close gap simultaneously (first priority) ===
188
- // Wait until the missile is close (<=40 ticks) so the blink actually dodges it.
189
- // Blinking too early means the missile can still re-track. Too late (<10) and the
190
- // blink cast won't complete in time.
191
- if (
192
- urgentThreat &&
193
- state.blinkCooldown === 0 &&
194
- distance > stabRange &&
195
- urgentThreat.ticksToImpact >= 10 &&
196
- urgentThreat.ticksToImpact <= blinkWindowMax
197
- )
198
- {
199
- const blinkTarget =
200
- getBlinkToDecreaseDistance(
201
- state.position,
202
- enemy.position,
203
- state.projectiles,
204
- ) ?? enemy.position;
205
- return {
206
- move: {x: 0, y: 0},
207
- startCast: {spell: 'blink', target: blinkTarget},
208
- };
209
- }
210
-
211
- // Pre-stab shield: already at melee range, survival beats landing another stab.
212
- // Gated to stab range so out-of-range threats wait for the blink window.
213
- if (
214
- urgentThreat &&
215
- distance < stabRange &&
216
- (!urgentThreat.bestDodgeDirection || shouldForceShield(urgentThreat)) &&
217
- urgentThreat.canBlockInTime &&
218
- urgentThreat.ticksToStartShield <= 3
219
- )
220
- {
221
- return {
222
- move: {x: 0, y: 0},
223
- startCast: {spell: 'shield'},
224
- };
225
- }
226
-
227
- // Melee stab when close enough (offense over defense at melee range)
228
- if (distance < stabRange)
229
- {
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
- };
238
- }
239
-
240
- // === DEFENSE: Shield undodgeable threats (only when blink can't handle it) ===
241
- // Don't shield if blink is available — wait for the blink window instead.
242
- if (
243
- urgentThreat &&
244
- (!urgentThreat.bestDodgeDirection || shouldForceShield(urgentThreat)) &&
245
- urgentThreat.canBlockInTime &&
246
- urgentThreat.ticksToStartShield <= 3 &&
247
- (state.blinkCooldown > 0 || urgentThreat.ticksToImpact < 10)
248
- )
249
- {
250
- return {
251
- move: {x: 0, y: 0},
252
- startCast: {spell: 'shield'},
253
- };
254
- }
255
- }
256
-
257
- // Aggressive movement — always close distance
258
- const dodgeDirection = getDodgeDirectionForMovement(threats, state.position);
259
- const move = aggressiveMove(
260
- state.position,
261
- enemy,
262
- distance,
263
- dodgeDirection,
264
- state.tick,
265
- strafeCyclePeriod,
266
- strafeIntensity,
267
- approachSpeed,
268
- );
269
- return {move};
270
- };
1
+ import {WizardFunction} from '../../types.js';
2
+ import {distanceTo} from '../../utils/distance.js';
3
+ import {ARENA_SIZE} from '../../rules.js';
4
+ import {
5
+ getThreats,
6
+ getMostUrgentThreat,
7
+ getDodgeDirectionForMovement,
8
+ getBlinkToDecreaseDistance,
9
+ shouldForceShield,
10
+ } from '../shared.js';
11
+ import {useParam} from '../../engine/params-runtime.js';
12
+ const WALL_BUFFER = 50;
13
+
14
+ // Melee stab: 20 damage, speed 8, 3-hit kill.
15
+ const STAB_CONFIG = {damage: 20, speed: 8, turnRate: 0, duration: 10};
16
+
17
+ /**
18
+ * Aggressive movement: dodge missiles using threat analysis, close distance, light strafe.
19
+ */
20
+ function aggressiveMove(
21
+ position: {x: number; y: number},
22
+ enemy: {position: {x: number; y: number}},
23
+ currentDistance: number,
24
+ dodgeDirection: {x: number; y: number} | null,
25
+ tick: number,
26
+ strafeCyclePeriod: number,
27
+ strafeIntensity: number,
28
+ approachSpeed: number,
29
+ ): {x: number; y: number}
30
+ {
31
+ const deltaX = enemy.position.x - position.x;
32
+ const deltaY = enemy.position.y - position.y;
33
+ const normalizedDistance = currentDistance > 0 ? currentDistance : 1;
34
+
35
+ const strafeClockwise = {
36
+ x: -deltaY / normalizedDistance,
37
+ y: deltaX / normalizedDistance,
38
+ };
39
+ const strafeCounterClockwise = {
40
+ x: deltaY / normalizedDistance,
41
+ y: -deltaX / normalizedDistance,
42
+ };
43
+
44
+ let strafeDirection: {x: number; y: number};
45
+
46
+ if (dodgeDirection)
47
+ {
48
+ strafeDirection = dodgeDirection;
49
+ }
50
+ 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
55
+ )
56
+ {
57
+ const futureClockwise = {
58
+ x: position.x + strafeClockwise.x * 100,
59
+ y: position.y + strafeClockwise.y * 100,
60
+ };
61
+ const futureCounterClockwise = {
62
+ x: position.x + strafeCounterClockwise.x * 100,
63
+ y: position.y + strafeCounterClockwise.y * 100,
64
+ };
65
+ const scoreClockwise = Math.min(
66
+ futureClockwise.x,
67
+ ARENA_SIZE - futureClockwise.x,
68
+ futureClockwise.y,
69
+ ARENA_SIZE - futureClockwise.y,
70
+ );
71
+ const scoreCounterClockwise = Math.min(
72
+ futureCounterClockwise.x,
73
+ ARENA_SIZE - futureCounterClockwise.x,
74
+ futureCounterClockwise.y,
75
+ ARENA_SIZE - futureCounterClockwise.y,
76
+ );
77
+ strafeDirection =
78
+ scoreClockwise > scoreCounterClockwise
79
+ ? strafeClockwise
80
+ : strafeCounterClockwise;
81
+ }
82
+ else
83
+ {
84
+ const cycle = Math.floor(tick / strafeCyclePeriod) % 2;
85
+ strafeDirection = cycle === 0 ? strafeClockwise : strafeCounterClockwise;
86
+ }
87
+
88
+ // When actively dodging, commit fully
89
+ if (dodgeDirection)
90
+ {
91
+ return {x: strafeDirection.x * 100, y: strafeDirection.y * 100};
92
+ }
93
+
94
+ // Light strafe + aggressive closing
95
+ let moveX = strafeDirection.x * strafeIntensity;
96
+ let moveY = strafeDirection.y * strafeIntensity;
97
+
98
+ // Always close distance aggressively (melee wants to be in the enemy's face)
99
+ moveX += (deltaX / normalizedDistance) * approachSpeed;
100
+ moveY += (deltaY / normalizedDistance) * approachSpeed;
101
+
102
+ const magnitude = Math.sqrt(moveX * moveX + moveY * moveY);
103
+ if (magnitude > 100)
104
+ {
105
+ moveX = (moveX / magnitude) * 100;
106
+ moveY = (moveY / magnitude) * 100;
107
+ }
108
+
109
+ return {x: moveX, y: moveY};
110
+ }
111
+
112
+ /**
113
+ * Bot: Shadowblade
114
+ *
115
+ * BEHAVIOR: Melee assassin. Blinks to the enemy, then lands devastating point-blank
116
+ * stab attacks (15 damage, 30u range, ~60 tick cast = 2-hit kill). Runs directly
117
+ * at the enemy with minimal strafe, shields undodgeable threats. The entire
118
+ * strategy is: get close, stab, kill. Simple and brutal.
119
+ *
120
+ * PROGRESSION LINE: Shadowblade → Nightblade → Voidblade
121
+ * - Shadowblade (tier 1): Offensive blink, melee stabs, basic shield
122
+ * - Nightblade (tier 2): + missile-aware blinks, adaptive stabs, timed defense
123
+ * - Voidblade (tier 3): Future — perfect assassination timing, inescapable engages
124
+ *
125
+ * TIER: 1 (base)
126
+ */
127
+ export const Shadowblade: WizardFunction = ({state}) =>
128
+ {
129
+ const stabRange = useParam('stabRange', 76, {
130
+ range: 30,
131
+ min: 20,
132
+ max: 80,
133
+ steps: 5,
134
+ });
135
+ const strafeCyclePeriod = useParam('strafeCyclePeriod', 252, {
136
+ range: 75,
137
+ min: 80,
138
+ max: 300,
139
+ });
140
+ const strafeIntensity = useParam('strafeIntensity', 55, {
141
+ range: 30,
142
+ min: 10,
143
+ max: 80,
144
+ steps: 7,
145
+ });
146
+ const approachSpeed = useParam('approachSpeed', 61, {
147
+ range: 20,
148
+ min: 50,
149
+ max: 100,
150
+ steps: 7,
151
+ });
152
+ const shieldCancelThreshold = useParam('shieldCancelThreshold', 150, {
153
+ range: 75,
154
+ min: 50,
155
+ max: 300,
156
+ });
157
+ const blinkWindowMax = useParam('blinkWindowMax', 23, {
158
+ range: 30,
159
+ min: 15,
160
+ max: 80,
161
+ steps: 7,
162
+ });
163
+
164
+ const enemy = state.enemies[0];
165
+ if (!enemy)
166
+ {
167
+ return {move: {x: 0, y: 0}};
168
+ }
169
+
170
+ const distance = distanceTo(state.position, enemy.position);
171
+
172
+ const threats = getThreats(state);
173
+ const urgentThreat = getMostUrgentThreat(threats);
174
+
175
+ // Cancel shield if no longer needed
176
+ if (state.state === 'channeling')
177
+ {
178
+ if (!urgentThreat || urgentThreat.ticksToImpact > shieldCancelThreshold)
179
+ {
180
+ return {move: {x: 0, y: 0}, cancel: true};
181
+ }
182
+ return {move: {x: 0, y: 0}};
183
+ }
184
+
185
+ if (state.state === 'idle')
186
+ {
187
+ // === BLINK: Dodge missile + close gap simultaneously (first priority) ===
188
+ // Wait until the missile is close (<=40 ticks) so the blink actually dodges it.
189
+ // Blinking too early means the missile can still re-track. Too late (<10) and the
190
+ // blink cast won't complete in time.
191
+ if (
192
+ urgentThreat &&
193
+ state.blinkCooldown === 0 &&
194
+ distance > stabRange &&
195
+ urgentThreat.ticksToImpact >= 10 &&
196
+ urgentThreat.ticksToImpact <= blinkWindowMax
197
+ )
198
+ {
199
+ const blinkTarget =
200
+ getBlinkToDecreaseDistance(
201
+ state.position,
202
+ enemy.position,
203
+ state.projectiles,
204
+ ) ?? enemy.position;
205
+ return {
206
+ move: {x: 0, y: 0},
207
+ startCast: {spell: 'blink', target: blinkTarget},
208
+ };
209
+ }
210
+
211
+ // Pre-stab shield: already at melee range, survival beats landing another stab.
212
+ // Gated to stab range so out-of-range threats wait for the blink window.
213
+ if (
214
+ urgentThreat &&
215
+ distance < stabRange &&
216
+ (!urgentThreat.bestDodgeDirection || shouldForceShield(urgentThreat)) &&
217
+ urgentThreat.canBlockInTime &&
218
+ urgentThreat.ticksToStartShield <= 3
219
+ )
220
+ {
221
+ return {
222
+ move: {x: 0, y: 0},
223
+ startCast: {spell: 'shield'},
224
+ };
225
+ }
226
+
227
+ // Melee stab when close enough (offense over defense at melee range)
228
+ if (distance < stabRange)
229
+ {
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
+ };
238
+ }
239
+
240
+ // === DEFENSE: Shield undodgeable threats (only when blink can't handle it) ===
241
+ // Don't shield if blink is available — wait for the blink window instead.
242
+ if (
243
+ urgentThreat &&
244
+ (!urgentThreat.bestDodgeDirection || shouldForceShield(urgentThreat)) &&
245
+ urgentThreat.canBlockInTime &&
246
+ urgentThreat.ticksToStartShield <= 3 &&
247
+ (state.blinkCooldown > 0 || urgentThreat.ticksToImpact < 10)
248
+ )
249
+ {
250
+ return {
251
+ move: {x: 0, y: 0},
252
+ startCast: {spell: 'shield'},
253
+ };
254
+ }
255
+ }
256
+
257
+ // Aggressive movement — always close distance
258
+ const dodgeDirection = getDodgeDirectionForMovement(threats, state.position);
259
+ const move = aggressiveMove(
260
+ state.position,
261
+ enemy,
262
+ distance,
263
+ dodgeDirection,
264
+ state.tick,
265
+ strafeCyclePeriod,
266
+ strafeIntensity,
267
+ approachSpeed,
268
+ );
269
+ return {move};
270
+ };