@vibemancer/core 0.1.0 → 0.1.1

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-7VDJHO22.js} +3 -3
  3. package/dist/chunk-7VDJHO22.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,286 +1,286 @@
1
- import {WizardFunction, MissileAIFunction} from '../../types.js';
2
- import {angleTo} from '../../utils/angles.js';
3
- import {distanceTo} from '../../utils/distance.js';
4
- import {ARENA_SIZE, GCD_DURATION, SHIELD_CAST_TIME} from '../../rules.js';
5
- import {getMissileCastTime} from '../../utils/combat.js';
6
- import {
7
- getThreats,
8
- getMostUrgentThreat,
9
- getDodgeDirectionForMovement,
10
- isSafeToAttack,
11
- getBlinkToCenter,
12
- shouldForceShield,
13
- } from '../shared.js';
14
- import {useParam} from '../../engine/params-runtime.js';
15
-
16
- const WALL_BUFFER = 60;
17
-
18
- const SHIELD_BUFFER = 3;
19
-
20
- // Fixed homing missile: damage 12, speed 5, turnRate 1, duration 180
21
- // Range: 5 × 180 = 900 units (covers arena)
22
- const MISSILE_CONFIG = {damage: 12, speed: 5, turnRate: 1, duration: 180};
23
-
24
- const HomingAI: MissileAIFunction = ({worldState}) =>
25
- {
26
- const enemy = worldState.enemies[0];
27
- return {turnToward: enemy?.position};
28
- };
29
-
30
- function isNearWall(position: {x: number; y: number}): boolean
31
- {
32
- 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
37
- );
38
- }
39
-
40
- function smartStrafe(
41
- position: {x: number; y: number},
42
- enemy: {position: {x: number; y: number}},
43
- dodgeDirection: {x: number; y: number} | null,
44
- tick: number,
45
- strafeCyclePeriod: number,
46
- strafeIntensity: number,
47
- ): {x: number; y: number}
48
- {
49
- const deltaX = enemy.position.x - position.x;
50
- const deltaY = enemy.position.y - position.y;
51
- const normalizedDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
52
- if (normalizedDistance === 0) return {x: 0, y: 0};
53
-
54
- const strafeClockwise = {
55
- x: -deltaY / normalizedDistance,
56
- y: deltaX / normalizedDistance,
57
- };
58
- const strafeCounterClockwise = {
59
- x: deltaY / normalizedDistance,
60
- y: -deltaX / normalizedDistance,
61
- };
62
-
63
- let strafeDir: {x: number; y: number};
64
-
65
- if (dodgeDirection)
66
- {
67
- strafeDir = dodgeDirection;
68
- }
69
- else if (isNearWall(position))
70
- {
71
- const futureClockwise = {
72
- x: position.x + strafeClockwise.x * 100,
73
- y: position.y + strafeClockwise.y * 100,
74
- };
75
- const futureCounterClockwise = {
76
- x: position.x + strafeCounterClockwise.x * 100,
77
- y: position.y + strafeCounterClockwise.y * 100,
78
- };
79
- const scoreClockwise = Math.min(
80
- futureClockwise.x,
81
- ARENA_SIZE - futureClockwise.x,
82
- futureClockwise.y,
83
- ARENA_SIZE - futureClockwise.y,
84
- );
85
- const scoreCounterClockwise = Math.min(
86
- futureCounterClockwise.x,
87
- ARENA_SIZE - futureCounterClockwise.x,
88
- futureCounterClockwise.y,
89
- ARENA_SIZE - futureCounterClockwise.y,
90
- );
91
- strafeDir =
92
- scoreClockwise > scoreCounterClockwise
93
- ? strafeClockwise
94
- : strafeCounterClockwise;
95
- }
96
- else
97
- {
98
- const cycle = Math.floor(tick / strafeCyclePeriod) % 2;
99
- strafeDir = cycle === 0 ? strafeClockwise : strafeCounterClockwise;
100
- }
101
-
102
- const intensity = dodgeDirection ? 100 : strafeIntensity;
103
- return {x: strafeDir.x * intensity, y: strafeDir.y * intensity};
104
- }
105
-
106
- /**
107
- * Bot: Flamecaller
108
- *
109
- * BEHAVIOR: Long-range homing missile caster with fixed missile config.
110
- * Maintains 350u distance, strafes to dodge, and fires standard homing
111
- * missiles (d=10, s=5, t=1, dur=180). Shields undodgeable threats,
112
- * emergency blinks. A straightforward ranged caster that trades
113
- * consistency for adaptability.
114
- *
115
- * PROGRESSION LINE: Flamecaller → Pyromancer → Infernalist
116
- * - Flamecaller (tier 1): Fixed homing missiles, basic strafe and defense
117
- * - Pyromancer (tier 2): + adaptive fitting, cast canceling, smart fallbacks
118
- * - Infernalist (tier 3): Future — overwhelming adaptive fire
119
- *
120
- * TIER: 1 (base)
121
- */
122
- export const Flamecaller: WizardFunction = ({state}) =>
123
- {
124
- const targetDistance = useParam('targetDistance', 753, {
125
- range: 150,
126
- min: 0,
127
- });
128
- const strafeCyclePeriod = useParam('strafeCyclePeriod', 273, {
129
- range: 75,
130
- min: 80,
131
- max: 300,
132
- });
133
- const strafeIntensity = useParam('strafeIntensity', 20, {
134
- range: 40,
135
- min: 20,
136
- max: 100,
137
- steps: 7,
138
- });
139
- const approachSpeed = useParam('approachSpeed', 40, {
140
- range: 30,
141
- min: 10,
142
- max: 70,
143
- steps: 7,
144
- });
145
- const distanceDeadZone = useParam('distanceDeadZone', 56, {
146
- range: 30,
147
- min: 5,
148
- max: 80,
149
- steps: 7,
150
- });
151
- const shieldCancelThreshold = useParam('shieldCancelThreshold', 150, {
152
- range: 75,
153
- min: 50,
154
- max: 300,
155
- });
156
- const safeAttackCastEstimate = useParam('safeAttackCastEstimate', 35, {
157
- range: 25,
158
- min: 10,
159
- max: 80,
160
- steps: 5,
161
- });
162
-
163
- const enemy = state.enemies[0];
164
- if (!enemy)
165
- {
166
- return {move: {x: 0, y: 0}};
167
- }
168
-
169
- const distance = distanceTo(state.position, enemy.position);
170
-
171
- const threats = getThreats(state);
172
- const urgentThreat = getMostUrgentThreat(threats);
173
-
174
- const dodgeDirection = getDodgeDirectionForMovement(threats, state.position);
175
- const strafe = smartStrafe(
176
- state.position,
177
- enemy,
178
- dodgeDirection,
179
- state.tick,
180
- strafeCyclePeriod,
181
- strafeIntensity,
182
- );
183
-
184
- // Distance management (retreat if too close, approach if too far)
185
- let moveX = strafe.x;
186
- let moveY = strafe.y;
187
-
188
- if (!dodgeDirection)
189
- {
190
- const deltaX = enemy.position.x - state.position.x;
191
- const deltaY = enemy.position.y - state.position.y;
192
- const normalizedDistance = distance > 0 ? distance : 1;
193
-
194
- if (distance > targetDistance + distanceDeadZone)
195
- {
196
- moveX += (deltaX / normalizedDistance) * approachSpeed;
197
- moveY += (deltaY / normalizedDistance) * approachSpeed;
198
- }
199
- else if (distance < targetDistance - distanceDeadZone)
200
- {
201
- moveX -= (deltaX / normalizedDistance) * approachSpeed;
202
- moveY -= (deltaY / normalizedDistance) * approachSpeed;
203
- }
204
- }
205
-
206
- const magnitude = Math.sqrt(moveX * moveX + moveY * moveY);
207
- if (magnitude > 100)
208
- {
209
- moveX = (moveX / magnitude) * 100;
210
- moveY = (moveY / magnitude) * 100;
211
- }
212
-
213
- const move = {x: moveX, y: moveY};
214
-
215
- // === DEFENSE: Handle channeling ===
216
- if (state.state === 'channeling')
217
- {
218
- if (!urgentThreat || urgentThreat.ticksToImpact > shieldCancelThreshold)
219
- {
220
- return {move, cancel: true};
221
- }
222
- return {move: {x: 0, y: 0}};
223
- }
224
-
225
- // === DEFENSE: Let shield cast finish ===
226
- if (state.state === 'casting' && state.castingSpell === 'shield')
227
- {
228
- return {move: {x: 0, y: 0}};
229
- }
230
-
231
- // === DEFENSE: Shield undodgeable or high-damage homing threats ===
232
- if (
233
- state.state === 'idle' &&
234
- urgentThreat &&
235
- (!urgentThreat.bestDodgeDirection || shouldForceShield(urgentThreat))
236
- )
237
- {
238
- if (urgentThreat.canBlockInTime)
239
- {
240
- if (urgentThreat.ticksToStartShield <= SHIELD_BUFFER)
241
- {
242
- return {
243
- move: {x: 0, y: 0},
244
- startCast: {spell: 'shield'},
245
- };
246
- }
247
- }
248
- else if (state.blinkCooldown === 0)
249
- {
250
- return {
251
- move: {x: 0, y: 0},
252
- startCast: {spell: 'blink', target: getBlinkToCenter(state.position)},
253
- };
254
- }
255
- }
256
-
257
- // === OFFENSE: Fire fixed homing missile when safe ===
258
- if (state.state === 'idle')
259
- {
260
- const nextThreatTime = urgentThreat?.ticksToImpact ?? Infinity;
261
- const castTime = getMissileCastTime(
262
- MISSILE_CONFIG,
263
- state.lastMissileConfig,
264
- );
265
- const safeWindow =
266
- castTime + GCD_DURATION + SHIELD_CAST_TIME + SHIELD_BUFFER;
267
-
268
- if (
269
- nextThreatTime > safeWindow &&
270
- isSafeToAttack(threats, safeAttackCastEstimate)
271
- )
272
- {
273
- return {
274
- move,
275
- startCast: {
276
- spell: 'missile',
277
- config: MISSILE_CONFIG,
278
- missileAI: HomingAI,
279
- direction: angleTo(state.position, enemy.position),
280
- },
281
- };
282
- }
283
- }
284
-
285
- return {move};
286
- };
1
+ import {WizardFunction, MissileAIFunction} from '../../types.js';
2
+ import {angleTo} from '../../utils/angles.js';
3
+ import {distanceTo} from '../../utils/distance.js';
4
+ import {ARENA_SIZE, GCD_DURATION, SHIELD_CAST_TIME} from '../../rules.js';
5
+ import {getMissileCastTime} from '../../utils/combat.js';
6
+ import {
7
+ getThreats,
8
+ getMostUrgentThreat,
9
+ getDodgeDirectionForMovement,
10
+ isSafeToAttack,
11
+ getBlinkToCenter,
12
+ shouldForceShield,
13
+ } from '../shared.js';
14
+ import {useParam} from '../../engine/params-runtime.js';
15
+
16
+ const WALL_BUFFER = 60;
17
+
18
+ const SHIELD_BUFFER = 3;
19
+
20
+ // Fixed homing missile: damage 12, speed 5, turnRate 1, duration 180
21
+ // Range: 5 × 180 = 900 units (covers arena)
22
+ const MISSILE_CONFIG = {damage: 12, speed: 5, turnRate: 1, duration: 180};
23
+
24
+ const HomingAI: MissileAIFunction = ({worldState}) =>
25
+ {
26
+ const enemy = worldState.enemies[0];
27
+ return {turnToward: enemy?.position};
28
+ };
29
+
30
+ function isNearWall(position: {x: number; y: number}): boolean
31
+ {
32
+ 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
37
+ );
38
+ }
39
+
40
+ function smartStrafe(
41
+ position: {x: number; y: number},
42
+ enemy: {position: {x: number; y: number}},
43
+ dodgeDirection: {x: number; y: number} | null,
44
+ tick: number,
45
+ strafeCyclePeriod: number,
46
+ strafeIntensity: number,
47
+ ): {x: number; y: number}
48
+ {
49
+ const deltaX = enemy.position.x - position.x;
50
+ const deltaY = enemy.position.y - position.y;
51
+ const normalizedDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
52
+ if (normalizedDistance === 0) return {x: 0, y: 0};
53
+
54
+ const strafeClockwise = {
55
+ x: -deltaY / normalizedDistance,
56
+ y: deltaX / normalizedDistance,
57
+ };
58
+ const strafeCounterClockwise = {
59
+ x: deltaY / normalizedDistance,
60
+ y: -deltaX / normalizedDistance,
61
+ };
62
+
63
+ let strafeDir: {x: number; y: number};
64
+
65
+ if (dodgeDirection)
66
+ {
67
+ strafeDir = dodgeDirection;
68
+ }
69
+ else if (isNearWall(position))
70
+ {
71
+ const futureClockwise = {
72
+ x: position.x + strafeClockwise.x * 100,
73
+ y: position.y + strafeClockwise.y * 100,
74
+ };
75
+ const futureCounterClockwise = {
76
+ x: position.x + strafeCounterClockwise.x * 100,
77
+ y: position.y + strafeCounterClockwise.y * 100,
78
+ };
79
+ const scoreClockwise = Math.min(
80
+ futureClockwise.x,
81
+ ARENA_SIZE - futureClockwise.x,
82
+ futureClockwise.y,
83
+ ARENA_SIZE - futureClockwise.y,
84
+ );
85
+ const scoreCounterClockwise = Math.min(
86
+ futureCounterClockwise.x,
87
+ ARENA_SIZE - futureCounterClockwise.x,
88
+ futureCounterClockwise.y,
89
+ ARENA_SIZE - futureCounterClockwise.y,
90
+ );
91
+ strafeDir =
92
+ scoreClockwise > scoreCounterClockwise
93
+ ? strafeClockwise
94
+ : strafeCounterClockwise;
95
+ }
96
+ else
97
+ {
98
+ const cycle = Math.floor(tick / strafeCyclePeriod) % 2;
99
+ strafeDir = cycle === 0 ? strafeClockwise : strafeCounterClockwise;
100
+ }
101
+
102
+ const intensity = dodgeDirection ? 100 : strafeIntensity;
103
+ return {x: strafeDir.x * intensity, y: strafeDir.y * intensity};
104
+ }
105
+
106
+ /**
107
+ * Bot: Flamecaller
108
+ *
109
+ * BEHAVIOR: Long-range homing missile caster with fixed missile config.
110
+ * Maintains 350u distance, strafes to dodge, and fires standard homing
111
+ * missiles (d=10, s=5, t=1, dur=180). Shields undodgeable threats,
112
+ * emergency blinks. A straightforward ranged caster that trades
113
+ * consistency for adaptability.
114
+ *
115
+ * PROGRESSION LINE: Flamecaller → Pyromancer → Infernalist
116
+ * - Flamecaller (tier 1): Fixed homing missiles, basic strafe and defense
117
+ * - Pyromancer (tier 2): + adaptive fitting, cast canceling, smart fallbacks
118
+ * - Infernalist (tier 3): Future — overwhelming adaptive fire
119
+ *
120
+ * TIER: 1 (base)
121
+ */
122
+ export const Flamecaller: WizardFunction = ({state}) =>
123
+ {
124
+ const targetDistance = useParam('targetDistance', 753, {
125
+ range: 150,
126
+ min: 0,
127
+ });
128
+ const strafeCyclePeriod = useParam('strafeCyclePeriod', 273, {
129
+ range: 75,
130
+ min: 80,
131
+ max: 300,
132
+ });
133
+ const strafeIntensity = useParam('strafeIntensity', 20, {
134
+ range: 40,
135
+ min: 20,
136
+ max: 100,
137
+ steps: 7,
138
+ });
139
+ const approachSpeed = useParam('approachSpeed', 40, {
140
+ range: 30,
141
+ min: 10,
142
+ max: 70,
143
+ steps: 7,
144
+ });
145
+ const distanceDeadZone = useParam('distanceDeadZone', 56, {
146
+ range: 30,
147
+ min: 5,
148
+ max: 80,
149
+ steps: 7,
150
+ });
151
+ const shieldCancelThreshold = useParam('shieldCancelThreshold', 150, {
152
+ range: 75,
153
+ min: 50,
154
+ max: 300,
155
+ });
156
+ const safeAttackCastEstimate = useParam('safeAttackCastEstimate', 35, {
157
+ range: 25,
158
+ min: 10,
159
+ max: 80,
160
+ steps: 5,
161
+ });
162
+
163
+ const enemy = state.enemies[0];
164
+ if (!enemy)
165
+ {
166
+ return {move: {x: 0, y: 0}};
167
+ }
168
+
169
+ const distance = distanceTo(state.position, enemy.position);
170
+
171
+ const threats = getThreats(state);
172
+ const urgentThreat = getMostUrgentThreat(threats);
173
+
174
+ const dodgeDirection = getDodgeDirectionForMovement(threats, state.position);
175
+ const strafe = smartStrafe(
176
+ state.position,
177
+ enemy,
178
+ dodgeDirection,
179
+ state.tick,
180
+ strafeCyclePeriod,
181
+ strafeIntensity,
182
+ );
183
+
184
+ // Distance management (retreat if too close, approach if too far)
185
+ let moveX = strafe.x;
186
+ let moveY = strafe.y;
187
+
188
+ if (!dodgeDirection)
189
+ {
190
+ const deltaX = enemy.position.x - state.position.x;
191
+ const deltaY = enemy.position.y - state.position.y;
192
+ const normalizedDistance = distance > 0 ? distance : 1;
193
+
194
+ if (distance > targetDistance + distanceDeadZone)
195
+ {
196
+ moveX += (deltaX / normalizedDistance) * approachSpeed;
197
+ moveY += (deltaY / normalizedDistance) * approachSpeed;
198
+ }
199
+ else if (distance < targetDistance - distanceDeadZone)
200
+ {
201
+ moveX -= (deltaX / normalizedDistance) * approachSpeed;
202
+ moveY -= (deltaY / normalizedDistance) * approachSpeed;
203
+ }
204
+ }
205
+
206
+ const magnitude = Math.sqrt(moveX * moveX + moveY * moveY);
207
+ if (magnitude > 100)
208
+ {
209
+ moveX = (moveX / magnitude) * 100;
210
+ moveY = (moveY / magnitude) * 100;
211
+ }
212
+
213
+ const move = {x: moveX, y: moveY};
214
+
215
+ // === DEFENSE: Handle channeling ===
216
+ if (state.state === 'channeling')
217
+ {
218
+ if (!urgentThreat || urgentThreat.ticksToImpact > shieldCancelThreshold)
219
+ {
220
+ return {move, cancel: true};
221
+ }
222
+ return {move: {x: 0, y: 0}};
223
+ }
224
+
225
+ // === DEFENSE: Let shield cast finish ===
226
+ if (state.state === 'casting' && state.castingSpell === 'shield')
227
+ {
228
+ return {move: {x: 0, y: 0}};
229
+ }
230
+
231
+ // === DEFENSE: Shield undodgeable or high-damage homing threats ===
232
+ if (
233
+ state.state === 'idle' &&
234
+ urgentThreat &&
235
+ (!urgentThreat.bestDodgeDirection || shouldForceShield(urgentThreat))
236
+ )
237
+ {
238
+ if (urgentThreat.canBlockInTime)
239
+ {
240
+ if (urgentThreat.ticksToStartShield <= SHIELD_BUFFER)
241
+ {
242
+ return {
243
+ move: {x: 0, y: 0},
244
+ startCast: {spell: 'shield'},
245
+ };
246
+ }
247
+ }
248
+ else if (state.blinkCooldown === 0)
249
+ {
250
+ return {
251
+ move: {x: 0, y: 0},
252
+ startCast: {spell: 'blink', target: getBlinkToCenter(state.position)},
253
+ };
254
+ }
255
+ }
256
+
257
+ // === OFFENSE: Fire fixed homing missile when safe ===
258
+ if (state.state === 'idle')
259
+ {
260
+ const nextThreatTime = urgentThreat?.ticksToImpact ?? Infinity;
261
+ const castTime = getMissileCastTime(
262
+ MISSILE_CONFIG,
263
+ state.lastMissileConfig,
264
+ );
265
+ const safeWindow =
266
+ castTime + GCD_DURATION + SHIELD_CAST_TIME + SHIELD_BUFFER;
267
+
268
+ if (
269
+ nextThreatTime > safeWindow &&
270
+ isSafeToAttack(threats, safeAttackCastEstimate)
271
+ )
272
+ {
273
+ return {
274
+ move,
275
+ startCast: {
276
+ spell: 'missile',
277
+ config: MISSILE_CONFIG,
278
+ missileAI: HomingAI,
279
+ direction: angleTo(state.position, enemy.position),
280
+ },
281
+ };
282
+ }
283
+ }
284
+
285
+ return {move};
286
+ };