@vibemancer/core 0.1.4 → 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.
- package/README.md +2 -2
- package/dist/chunk-W7HWJFQ4.js +10599 -0
- package/dist/chunk-W7HWJFQ4.js.map +1 -0
- package/dist/index-browser.d.ts +1300 -1051
- package/dist/index-browser.js +55 -17
- package/dist/index.d.ts +53 -3
- package/dist/index.js +200 -54
- package/dist/index.js.map +1 -1
- package/package.json +21 -15
- package/src/bots/Hero.ts +867 -0
- package/src/bots/berserker/01_Stormchaser.ts +162 -120
- package/src/bots/berserker/02_Stormcaller.ts +160 -116
- package/src/bots/berserker/03_Stormforger.ts +162 -129
- package/src/bots/caster/01_Flamecaller.ts +126 -84
- package/src/bots/caster/02_Pyromancer.ts +148 -101
- package/src/bots/caster/03_Infernalist.ts +180 -160
- package/src/bots/defensive/01_Turtle.ts +48 -44
- package/src/bots/defensive/02_Sentinel.ts +57 -53
- package/src/bots/defensive/03_Golem.ts +85 -97
- package/src/bots/duelist/01_Battlemage.ts +157 -122
- package/src/bots/duelist/02_Warmage.ts +166 -121
- package/src/bots/duelist/03_Archmage.ts +198 -178
- package/src/bots/homing/01_Bonemancer.ts +20 -32
- package/src/bots/homing/02_Lich.ts +145 -93
- package/src/bots/homing/03_Archlich.ts +129 -60
- package/src/bots/kiter/01_Spellspinner.ts +145 -107
- package/src/bots/kiter/02_Spellweaver.ts +153 -105
- package/src/bots/kiter/03_Spellbinder.ts +168 -123
- package/src/bots/melee/01_Shadowblade.ts +131 -75
- package/src/bots/melee/02_Nightblade.ts +174 -130
- package/src/bots/melee/03_Voidblade.ts +266 -168
- package/src/bots/registry.ts +44 -37
- package/src/bots/shared.ts +185 -25
- package/src/bots/sniper/01_Spellshot.ts +151 -98
- package/src/bots/sniper/02_Spelltracer.ts +173 -128
- package/src/bots/sniper/03_Spellseeker.ts +177 -152
- package/src/bots/standalone/Critter.ts +46 -52
- package/src/bots/standalone/Doombringer.ts +36 -40
- package/src/bots/standalone/Hogger.ts +120 -89
- package/src/bots/standalone/Rookie.ts +21 -23
- package/src/bots/standalone/TargetDummy.ts +5 -6
- package/src/bots/test/cheater.ts +91 -85
- package/src/bots/test/crasher.ts +26 -28
- package/src/engine/bundle-fight.ts +242 -0
- package/src/engine/hooks-runtime.ts +58 -18
- package/src/engine/manual-match.ts +33 -25
- package/src/engine/missile-templates.ts +25 -43
- package/src/engine/optimizer.ts +7 -7
- package/src/engine/params-runtime.ts +3 -3
- package/src/engine/physics.ts +33 -23
- package/src/engine/sandbox-browser.ts +8 -7
- package/src/engine/sandbox-compile.ts +1 -1
- package/src/engine/sandbox-harness.ts +299 -367
- package/src/engine/sandbox.ts +3 -3
- package/src/engine/simulation.ts +291 -76
- package/src/engine/spells.ts +9 -12
- package/src/engine-version.ts +1 -1
- package/src/hooks/action-builders.ts +76 -21
- package/src/hooks/index.ts +17 -9
- package/src/hooks/state-hooks.ts +61 -25
- package/src/hooks/threat-analysis.ts +44 -20
- package/src/hooks/types.ts +62 -5
- package/src/index.ts +2 -0
- package/src/rules.ts +209 -63
- package/src/stats.ts +2 -2
- package/src/testing.ts +6 -30
- package/src/trace.ts +63 -3
- package/src/types.ts +127 -14
- package/src/utils/angles.ts +1 -0
- package/src/utils/combat.ts +98 -6
- package/src/utils/spatial.ts +3 -3
- package/dist/chunk-74FFJCFF.js +0 -9140
- package/dist/chunk-74FFJCFF.js.map +0 -1
- package/src/hooks/bot-wrapper.ts +0 -84
package/src/types.ts
CHANGED
|
@@ -104,6 +104,9 @@ export interface GameState
|
|
|
104
104
|
damageDealt: number; // total damage dealt this match
|
|
105
105
|
damageTaken: number; // total damage taken this match
|
|
106
106
|
lastHitTick: number; // tick when last took damage (0 if never)
|
|
107
|
+
|
|
108
|
+
/** Events emitted during the most recent tick (empty for the initial state). */
|
|
109
|
+
events: SimEvent[];
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
/**
|
|
@@ -133,7 +136,7 @@ export interface WizardActions
|
|
|
133
136
|
startCast?: {
|
|
134
137
|
spell: 'missile';
|
|
135
138
|
config: MissileConfig;
|
|
136
|
-
missileAI:
|
|
139
|
+
missileAI: MissileFunction;
|
|
137
140
|
direction?: number; // initial rotation override (auto-aims at enemy by default)
|
|
138
141
|
} | {
|
|
139
142
|
spell: 'shield';
|
|
@@ -153,6 +156,20 @@ export interface WizardActions
|
|
|
153
156
|
* Only applies while state === 'casting' and castingSpell === 'missile'.
|
|
154
157
|
*/
|
|
155
158
|
aimDirection?: number;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Missile guide — manual play mode only. Takes direct control of a
|
|
162
|
+
* specific missile using WASD/joystick input. While active, the wizard
|
|
163
|
+
* is invulnerable and frozen (move ignored), and the missile steers
|
|
164
|
+
* using its turnRate toward the given direction.
|
|
165
|
+
*
|
|
166
|
+
* direction: {x, y} → converted to target angle via atan2(y, x)
|
|
167
|
+
* direction: null → missile flies straight (no steering input)
|
|
168
|
+
*/
|
|
169
|
+
missileGuide?: {
|
|
170
|
+
missileId: string;
|
|
171
|
+
direction: {x: number; y: number} | null;
|
|
172
|
+
} | null;
|
|
156
173
|
}
|
|
157
174
|
|
|
158
175
|
/**
|
|
@@ -167,27 +184,123 @@ export interface MissileConfig
|
|
|
167
184
|
}
|
|
168
185
|
|
|
169
186
|
/**
|
|
170
|
-
* Missile AI function type.
|
|
187
|
+
* Missile AI function type — re-exported from hooks/types.ts.
|
|
171
188
|
*/
|
|
172
|
-
|
|
173
|
-
missileState: ProjectileState;
|
|
174
|
-
worldState: GameState;
|
|
175
|
-
random: () => number; // Seeded PRNG, isolated per entity, 0-1 range
|
|
176
|
-
}) => MissileActions;
|
|
189
|
+
type MissileFunction = import('./hooks/types.js').MissileFunction;
|
|
177
190
|
|
|
178
191
|
/**
|
|
179
192
|
* Actions returned by missile each tick.
|
|
180
193
|
*/
|
|
181
194
|
export interface MissileActions
|
|
182
195
|
{
|
|
183
|
-
turnToward?: Position;
|
|
196
|
+
turnToward?: Position;
|
|
197
|
+
turnToAngle?: number;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ============================================================
|
|
201
|
+
// GAME EVENTS
|
|
202
|
+
// ============================================================
|
|
203
|
+
|
|
204
|
+
export interface MissileHitEvent
|
|
205
|
+
{
|
|
206
|
+
type: 'missile-hit';
|
|
207
|
+
position: Position;
|
|
208
|
+
damage: number;
|
|
209
|
+
actualDamage: number;
|
|
210
|
+
speed: number;
|
|
211
|
+
ownerId: string;
|
|
212
|
+
targetId: string;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface MissileExpiredEvent
|
|
216
|
+
{
|
|
217
|
+
type: 'missile-expired';
|
|
218
|
+
position: Position;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface MissileOobEvent
|
|
222
|
+
{
|
|
223
|
+
type: 'missile-oob';
|
|
224
|
+
position: Position;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export interface BlinkEvent
|
|
228
|
+
{
|
|
229
|
+
type: 'blink';
|
|
230
|
+
wizardId: string;
|
|
231
|
+
from: Position;
|
|
232
|
+
to: Position;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export interface ShieldStartEvent
|
|
236
|
+
{
|
|
237
|
+
type: 'shield-start';
|
|
238
|
+
wizardId: string;
|
|
239
|
+
position: Position;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export interface CastStartEvent
|
|
243
|
+
{
|
|
244
|
+
type: 'cast-start';
|
|
245
|
+
wizardId: string;
|
|
246
|
+
position: Position;
|
|
247
|
+
spell: 'missile' | 'shield' | 'blink';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export interface WizardDeathEvent
|
|
251
|
+
{
|
|
252
|
+
type: 'wizard-death';
|
|
253
|
+
wizardId: string;
|
|
254
|
+
position: Position;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export interface WizardLavaDeathEvent
|
|
258
|
+
{
|
|
259
|
+
type: 'wizard-lava-death';
|
|
260
|
+
wizardId: string;
|
|
261
|
+
position: Position;
|
|
184
262
|
}
|
|
185
263
|
|
|
264
|
+
export interface CastCancelEvent
|
|
265
|
+
{
|
|
266
|
+
type: 'cast-cancel';
|
|
267
|
+
wizardId: string;
|
|
268
|
+
position: Position;
|
|
269
|
+
spell: 'missile' | 'shield' | 'blink';
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export interface MissileLaunchEvent
|
|
273
|
+
{
|
|
274
|
+
type: 'missile-launch';
|
|
275
|
+
position: Position;
|
|
276
|
+
damage: number;
|
|
277
|
+
speed: number;
|
|
278
|
+
ownerId: string;
|
|
279
|
+
rotation: number;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export interface ShieldBlockEvent
|
|
283
|
+
{
|
|
284
|
+
type: 'shield-block';
|
|
285
|
+
wizardId: string;
|
|
286
|
+
position: Position;
|
|
287
|
+
damageBlocked: number;
|
|
288
|
+
damageThrough: number;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export type SimEvent =
|
|
292
|
+
| MissileHitEvent
|
|
293
|
+
| MissileExpiredEvent
|
|
294
|
+
| MissileOobEvent
|
|
295
|
+
| MissileLaunchEvent
|
|
296
|
+
| BlinkEvent
|
|
297
|
+
| ShieldStartEvent
|
|
298
|
+
| ShieldBlockEvent
|
|
299
|
+
| CastStartEvent
|
|
300
|
+
| CastCancelEvent
|
|
301
|
+
| WizardDeathEvent
|
|
302
|
+
| WizardLavaDeathEvent;
|
|
303
|
+
|
|
186
304
|
/**
|
|
187
|
-
*
|
|
305
|
+
* @deprecated Use WizardFunction from hooks/types.ts instead.
|
|
188
306
|
*/
|
|
189
|
-
export type WizardFunction = (props: {
|
|
190
|
-
state: GameState;
|
|
191
|
-
config: GameConfig;
|
|
192
|
-
random: () => number; // Seeded PRNG, isolated per entity, 0-1 range
|
|
193
|
-
}) => WizardActions;
|
package/src/utils/angles.ts
CHANGED
|
@@ -30,6 +30,7 @@ export function normalizeAngle(angle: number): number
|
|
|
30
30
|
*/
|
|
31
31
|
export function angleDiff(angleA: number, angleB: number): number
|
|
32
32
|
{
|
|
33
|
+
// Stryker disable next-line ArithmeticOperator: +180 vs -180 in modular arithmetic produces identical results for all inputs
|
|
33
34
|
let diff = (angleB - angleA + 180) % 360 - 180;
|
|
34
35
|
if (diff < -180)
|
|
35
36
|
{
|
package/src/utils/combat.ts
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
calculateWarmupMultiplier,
|
|
11
11
|
validateMissileConfig,
|
|
12
12
|
TICKS_PER_SECOND,
|
|
13
|
+
MOVEMENT_SPEED,
|
|
14
|
+
CASTING_MOVEMENT_MULT,
|
|
13
15
|
WIZARD_HEALTH,
|
|
14
16
|
MISSILE_BASE_CAST,
|
|
15
17
|
MISSILE_DAMAGE_SCALE,
|
|
@@ -21,6 +23,7 @@ import {
|
|
|
21
23
|
MISSILE_MIN_CAST_TIME,
|
|
22
24
|
MISSILE_MIN_DURATION,
|
|
23
25
|
MISSILE_MIN_DAMAGE,
|
|
26
|
+
WIZARD_RADIUS,
|
|
24
27
|
effectiveTurnRateCost,
|
|
25
28
|
} from '../rules.js';
|
|
26
29
|
|
|
@@ -172,12 +175,61 @@ export function getAdaptiveMissileConfig(
|
|
|
172
175
|
* If `lastMissileConfig` is provided, accounts for warmup bonus: similar
|
|
173
176
|
* missiles cast faster, so more damage can fit in the same budget.
|
|
174
177
|
*/
|
|
178
|
+
/**
|
|
179
|
+
* Simulate a missile trajectory to find the minimum duration (ticks) needed
|
|
180
|
+
* to reach a target at the given distance. Works for all turnRate values:
|
|
181
|
+
* positive (homing) and zero (straight).
|
|
182
|
+
*
|
|
183
|
+
* The simulation starts the missile aimed directly at the target and steps
|
|
184
|
+
* through the trajectory tick by tick. For homing, the missile tracks the
|
|
185
|
+
* target each tick matching the engine's steering physics.
|
|
186
|
+
*/
|
|
187
|
+
/**
|
|
188
|
+
* Simulate a missile trajectory to find the minimum duration (ticks) needed
|
|
189
|
+
* to reach a target at the given distance. Works for straight (turnRate=0)
|
|
190
|
+
* and homing (turnRate>0) missiles.
|
|
191
|
+
*
|
|
192
|
+
* The missile starts aimed directly at the target at (dist, 0) and steps
|
|
193
|
+
* through the trajectory tick by tick. For homing, the missile tracks the
|
|
194
|
+
* target each tick matching the engine's steering physics.
|
|
195
|
+
*
|
|
196
|
+
* Returns 500 if the missile cannot reach the target within 500 ticks.
|
|
197
|
+
*/
|
|
198
|
+
export function simulateMinDuration(speed: number, turnRateDeg: number, dist: number, collisionRadius?: number): number
|
|
199
|
+
{
|
|
200
|
+
if (dist <= 0) return 1;
|
|
201
|
+
const hitR = collisionRadius ?? (WIZARD_RADIUS + 8);
|
|
202
|
+
if (turnRateDeg === 0)
|
|
203
|
+
{
|
|
204
|
+
return Math.max(1, Math.ceil((dist - hitR) / speed));
|
|
205
|
+
}
|
|
206
|
+
const maxTurn = Math.abs(turnRateDeg) * Math.PI / 180;
|
|
207
|
+
let angle = 0;
|
|
208
|
+
let x = 0;
|
|
209
|
+
let y = 0;
|
|
210
|
+
for (let t = 1; t <= 500; t++)
|
|
211
|
+
{
|
|
212
|
+
const targetAngle = Math.atan2(-y, dist - x);
|
|
213
|
+
let diff = targetAngle - angle;
|
|
214
|
+
while (diff > Math.PI) diff -= 2 * Math.PI;
|
|
215
|
+
while (diff < -Math.PI) diff += 2 * Math.PI;
|
|
216
|
+
const turn = Math.max(-maxTurn, Math.min(maxTurn, diff));
|
|
217
|
+
angle += turn;
|
|
218
|
+
x += Math.cos(angle) * speed;
|
|
219
|
+
y += Math.sin(angle) * speed;
|
|
220
|
+
const d = Math.sqrt((x - dist) * (x - dist) + y * y);
|
|
221
|
+
if (d <= hitR) return t;
|
|
222
|
+
}
|
|
223
|
+
return 500;
|
|
224
|
+
}
|
|
225
|
+
|
|
175
226
|
export function fitMissileToBudget(
|
|
176
227
|
budgetTicks: number,
|
|
177
228
|
distance: number,
|
|
178
229
|
options?: {minTurnRate?: number; maxDamage?: number; lastMissileConfig?: MissileConfig},
|
|
179
230
|
): MissileConfig | null
|
|
180
231
|
{
|
|
232
|
+
|
|
181
233
|
const budgetSec = budgetTicks / TICKS_PER_SECOND;
|
|
182
234
|
const minTurnRate = options?.minTurnRate ?? 0;
|
|
183
235
|
const maxDamage = options?.maxDamage ?? WIZARD_HEALTH;
|
|
@@ -195,10 +247,7 @@ export function fitMissileToBudget(
|
|
|
195
247
|
|
|
196
248
|
// Templates: [speed, turnRate] combos to try, ordered by preference.
|
|
197
249
|
// Lower turnRate = cheaper cast. Higher speed = shorter duration needed.
|
|
198
|
-
// Negative turnRate = anti-homing (turns away from target) but cheaper cast time.
|
|
199
250
|
const templates: [number, number][] = [
|
|
200
|
-
[7, -1], // Fast anti-homing: cheapest cast, curves away from target
|
|
201
|
-
[7, -0.5],// Fast light anti-homing
|
|
202
251
|
[7, 0], // Fast straight: cheapest cast, only hits stationary
|
|
203
252
|
[5, 0], // Medium straight
|
|
204
253
|
[6, 0.5], // Fast with light tracking
|
|
@@ -214,11 +263,9 @@ export function fitMissileToBudget(
|
|
|
214
263
|
for (const [speed, turnRate] of templates)
|
|
215
264
|
{
|
|
216
265
|
if (turnRate < minTurnRate) continue;
|
|
217
|
-
// Minimum duration to reach target (with 20% margin for homing curve)
|
|
218
|
-
const margin = turnRate > 0 ? 1.2 : 1.05;
|
|
219
266
|
const minDurationTicks = Math.max(
|
|
220
267
|
MISSILE_MIN_DURATION,
|
|
221
|
-
|
|
268
|
+
simulateMinDuration(speed, turnRate, distance),
|
|
222
269
|
);
|
|
223
270
|
const durationSec = minDurationTicks / TICKS_PER_SECOND;
|
|
224
271
|
|
|
@@ -277,3 +324,48 @@ export function fitMissileToBudget(
|
|
|
277
324
|
|
|
278
325
|
return best;
|
|
279
326
|
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Fit a missile config that accounts for the enemy escaping during cast time.
|
|
330
|
+
*
|
|
331
|
+
* During casting, the caster moves at CASTING_MOVEMENT_MULT speed while
|
|
332
|
+
* the enemy moves at full MOVEMENT_SPEED. This means the effective distance
|
|
333
|
+
* at launch is larger than the current distance. This function iteratively
|
|
334
|
+
* converges on a missile config whose range covers the escape distance.
|
|
335
|
+
*
|
|
336
|
+
* @param currentDistance - Current distance to enemy
|
|
337
|
+
* @param budgetTicks - Maximum cast time budget in ticks
|
|
338
|
+
* @param options - Same options as fitMissileToBudget, plus:
|
|
339
|
+
* - enemyApproaching: if true, enemy is moving toward caster (reduces escape)
|
|
340
|
+
* - distanceBuffer: flat units added to target distance for safety margin (default 20)
|
|
341
|
+
* - maxIterations: convergence iterations (default 5)
|
|
342
|
+
*/
|
|
343
|
+
export function fitMissileForEscapingTarget(
|
|
344
|
+
currentDistance: number,
|
|
345
|
+
budgetTicks: number,
|
|
346
|
+
options?: {minTurnRate?: number; maxDamage?: number; lastMissileConfig?: MissileConfig; enemyApproaching?: boolean; distanceBuffer?: number; maxIterations?: number},
|
|
347
|
+
): MissileConfig | null
|
|
348
|
+
{
|
|
349
|
+
const maxIter = options?.maxIterations ?? 5;
|
|
350
|
+
const buffer = options?.distanceBuffer ?? 20;
|
|
351
|
+
const escapeRate = options?.enemyApproaching
|
|
352
|
+
? 0
|
|
353
|
+
: MOVEMENT_SPEED - MOVEMENT_SPEED * CASTING_MOVEMENT_MULT;
|
|
354
|
+
|
|
355
|
+
let targetDistance = currentDistance + buffer;
|
|
356
|
+
let lastCastTicks = 0;
|
|
357
|
+
|
|
358
|
+
for (let i = 0; i < maxIter; i++)
|
|
359
|
+
{
|
|
360
|
+
const config = fitMissileToBudget(budgetTicks, targetDistance, options);
|
|
361
|
+
if (!config) return null;
|
|
362
|
+
|
|
363
|
+
const castTicks = getMissileCastTime(config, options?.lastMissileConfig);
|
|
364
|
+
if (castTicks === lastCastTicks) return config;
|
|
365
|
+
lastCastTicks = castTicks;
|
|
366
|
+
|
|
367
|
+
targetDistance = currentDistance + castTicks * escapeRate + buffer;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return fitMissileToBudget(budgetTicks, targetDistance, options);
|
|
371
|
+
}
|
package/src/utils/spatial.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {Position} from '../types.js';
|
|
8
|
-
import {
|
|
8
|
+
import {ARENA_SIZE} from '../rules.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Normalize a vector to unit length.
|
|
@@ -49,8 +49,8 @@ export function directionAway(from: Position, to: Position): Position
|
|
|
49
49
|
export function clampPositionToArena(position: Position): Position
|
|
50
50
|
{
|
|
51
51
|
return {
|
|
52
|
-
x: Math.max(0, Math.min(
|
|
53
|
-
y: Math.max(0, Math.min(
|
|
52
|
+
x: Math.max(0, Math.min(ARENA_SIZE, position.x)),
|
|
53
|
+
y: Math.max(0, Math.min(ARENA_SIZE, position.y)),
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
|