@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
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
|
|
8
8
|
import {Position, ProjectileState} from '../types.js';
|
|
9
9
|
import {
|
|
10
|
+
RULES,
|
|
10
11
|
COLLISION_RADIUS,
|
|
11
12
|
MISSILE_BASE_RADIUS,
|
|
12
13
|
MISSILE_RADIUS_PER_DAMAGE,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
ARENA_HEIGHT,
|
|
14
|
+
ARENA_SIZE,
|
|
15
|
+
ARENA_MIN,
|
|
16
|
+
ARENA_MAX,
|
|
17
17
|
ARENA_WATER_BUFFER,
|
|
18
18
|
} from '../rules.js';
|
|
19
19
|
import {distanceTo} from '../utils/distance.js';
|
|
@@ -55,6 +55,7 @@ export function analyzeThreats(
|
|
|
55
55
|
(projectile.position.x - myPos.x) ** 2 +
|
|
56
56
|
(projectile.position.y - myPos.y) ** 2,
|
|
57
57
|
);
|
|
58
|
+
// Stryker disable next-line EqualityOperator: < vs <= equivalent (exact distance never equals constant in practice)
|
|
58
59
|
if (analysis.willHit || missileDistance < THREAT_RELEVANCE_DISTANCE)
|
|
59
60
|
{
|
|
60
61
|
threats.push(analysis);
|
|
@@ -83,6 +84,7 @@ function analyzeOneThreat(
|
|
|
83
84
|
// Dodge simulation uses a larger collision distance to account for swept circle collision.
|
|
84
85
|
// The real game checks the entire missile path each tick (swept), but the simulation only
|
|
85
86
|
// checks endpoints. Adding half the missile speed compensates for this gap.
|
|
87
|
+
// Stryker disable next-line ArithmeticOperator: +speed*0.5 vs -speed*0.5 or /0.5 — only affects tight-margin dodge outcomes which are inherently stochastic
|
|
86
88
|
const dodgeCollisionDist = collisionDist + projectile.speed * 0.5;
|
|
87
89
|
|
|
88
90
|
// Simulate missile trajectory toward stationary target
|
|
@@ -115,10 +117,11 @@ function analyzeOneThreat(
|
|
|
115
117
|
|
|
116
118
|
// Calculate shield timing
|
|
117
119
|
// Can block if: ticksUntilReady + shield cast time < ticksToImpact
|
|
118
|
-
|
|
120
|
+
// Stryker disable next-line EqualityOperator,ArithmeticOperator: ±1 tick boundary is intentional buffer, not precisely testable
|
|
121
|
+
const canBlockInTime = ticksToImpact > ticksUntilReady + RULES.SHIELD_CAST_TIME + 1;
|
|
119
122
|
|
|
120
123
|
// When to start casting shield (leave 1 tick buffer)
|
|
121
|
-
const ticksToStartShield = Math.max(0, ticksToImpact - SHIELD_CAST_TIME - 1);
|
|
124
|
+
const ticksToStartShield = Math.max(0, ticksToImpact - RULES.SHIELD_CAST_TIME - 1);
|
|
122
125
|
|
|
123
126
|
return {
|
|
124
127
|
id: projectile.id,
|
|
@@ -150,19 +153,22 @@ function simulateMissileToTarget(
|
|
|
150
153
|
const speed = projectile.speed;
|
|
151
154
|
const turnRate = projectile.turnRate;
|
|
152
155
|
|
|
156
|
+
// Stryker disable next-line EqualityOperator: <= vs < equivalent (off-by-one at max tick boundary is non-observable)
|
|
153
157
|
for (let tick = 1; tick <= maxTicks; tick++)
|
|
154
158
|
{
|
|
155
|
-
//
|
|
159
|
+
// Stryker disable next-line ConditionalExpression,EqualityOperator: turnRate>=0 equivalent (homing with rate 0 is no-op)
|
|
156
160
|
if (turnRate > 0)
|
|
157
161
|
{
|
|
158
162
|
const desiredAngle = angleTo(pos, targetPos);
|
|
159
163
|
const diff = angleDiff(rotation, desiredAngle);
|
|
160
164
|
const maxTurn = turnRate;
|
|
161
165
|
|
|
166
|
+
// Stryker disable all: homing snap — equivalent mutations (removing snap or changing boundary doesn't affect hit outcome)
|
|
162
167
|
if (Math.abs(diff) <= maxTurn)
|
|
163
168
|
{
|
|
164
169
|
rotation = desiredAngle;
|
|
165
170
|
}
|
|
171
|
+
// Stryker restore all
|
|
166
172
|
else
|
|
167
173
|
{
|
|
168
174
|
rotation = normalizeAngle(rotation + Math.sign(diff) * maxTurn);
|
|
@@ -184,6 +190,7 @@ function simulateMissileToTarget(
|
|
|
184
190
|
|
|
185
191
|
// Check collision
|
|
186
192
|
const dist = distanceTo(pos, targetPos);
|
|
193
|
+
// Stryker disable next-line EqualityOperator: <= vs < equivalent (float distance never exactly equals collisionDist)
|
|
187
194
|
if (dist <= collisionDist)
|
|
188
195
|
{
|
|
189
196
|
return {willHit: true, ticksToImpact: tick};
|
|
@@ -208,11 +215,12 @@ function simulateDodge(
|
|
|
208
215
|
// Calculate dodge direction vector
|
|
209
216
|
const dodgeDir = getDodgeDirection(projectile, startPos, direction);
|
|
210
217
|
|
|
211
|
-
//
|
|
218
|
+
// Stryker disable all: guard is equivalent — zero vector means zero movement, missile still hits, returns false either way
|
|
212
219
|
if (dodgeDir.x === 0 && dodgeDir.y === 0)
|
|
213
220
|
{
|
|
214
221
|
return false;
|
|
215
222
|
}
|
|
223
|
+
// Stryker restore all
|
|
216
224
|
|
|
217
225
|
let targetPos = {...startPos};
|
|
218
226
|
let missilePos = {...projectile.position};
|
|
@@ -221,25 +229,29 @@ function simulateDodge(
|
|
|
221
229
|
const speed = projectile.speed;
|
|
222
230
|
const turnRate = projectile.turnRate;
|
|
223
231
|
|
|
232
|
+
// Stryker disable next-line EqualityOperator,UpdateOperator: <= vs < off-by-one equivalent; tick-- causes timeout not assertion failure
|
|
224
233
|
for (let tick = 1; tick <= maxTicks; tick++)
|
|
225
234
|
{
|
|
226
|
-
//
|
|
227
|
-
const newX = targetPos.x + dodgeDir.x *
|
|
228
|
-
|
|
235
|
+
// Stryker disable next-line ArithmeticOperator: RULES.MOVEMENT_SPEED=1 makes * and / identical; sign flip equivalent when wizard is wall-clamped
|
|
236
|
+
const newX = targetPos.x + dodgeDir.x * RULES.MOVEMENT_SPEED;
|
|
237
|
+
// Stryker disable next-line ArithmeticOperator: same as above
|
|
238
|
+
const newY = targetPos.y + dodgeDir.y * RULES.MOVEMENT_SPEED;
|
|
229
239
|
|
|
230
|
-
//
|
|
240
|
+
// Stryker disable all: arena clamp sign — +/- COLLISION_RADIUS equivalent (wizard rarely reaches exact wall boundary during dodge)
|
|
231
241
|
targetPos = {
|
|
232
|
-
x: Math.max(COLLISION_RADIUS, Math.min(
|
|
233
|
-
y: Math.max(COLLISION_RADIUS, Math.min(
|
|
242
|
+
x: Math.max(ARENA_MIN + COLLISION_RADIUS, Math.min(ARENA_MAX - COLLISION_RADIUS, newX)),
|
|
243
|
+
y: Math.max(ARENA_MIN + COLLISION_RADIUS, Math.min(ARENA_MAX - COLLISION_RADIUS, newY)),
|
|
234
244
|
};
|
|
245
|
+
// Stryker restore all
|
|
235
246
|
|
|
236
|
-
//
|
|
247
|
+
// Stryker disable next-line ConditionalExpression,EqualityOperator: turnRate>=0 equivalent (homing with rate 0 is no-op)
|
|
237
248
|
if (turnRate > 0)
|
|
238
249
|
{
|
|
239
250
|
const desiredAngle = angleTo(missilePos, targetPos);
|
|
240
251
|
const diff = angleDiff(missileRotation, desiredAngle);
|
|
241
252
|
const maxTurn = turnRate;
|
|
242
253
|
|
|
254
|
+
// Stryker disable all: homing snap/incremental — equivalent mutations (removing either branch doesn't change dodge outcome)
|
|
243
255
|
if (Math.abs(diff) <= maxTurn)
|
|
244
256
|
{
|
|
245
257
|
missileRotation = desiredAngle;
|
|
@@ -248,6 +260,7 @@ function simulateDodge(
|
|
|
248
260
|
{
|
|
249
261
|
missileRotation = normalizeAngle(missileRotation + Math.sign(diff) * maxTurn);
|
|
250
262
|
}
|
|
263
|
+
// Stryker restore all
|
|
251
264
|
}
|
|
252
265
|
|
|
253
266
|
// Move missile
|
|
@@ -257,14 +270,16 @@ function simulateDodge(
|
|
|
257
270
|
y: missilePos.y + Math.sin(rad) * speed,
|
|
258
271
|
};
|
|
259
272
|
|
|
260
|
-
//
|
|
273
|
+
// Stryker disable all: OOB early-return equivalent — removing it means missile expires instead, still returns true
|
|
261
274
|
if (isOutOfBounds(missilePos))
|
|
262
275
|
{
|
|
263
|
-
return true;
|
|
276
|
+
return true;
|
|
264
277
|
}
|
|
278
|
+
// Stryker restore all
|
|
265
279
|
|
|
266
280
|
// Check collision
|
|
267
281
|
const dist = distanceTo(missilePos, targetPos);
|
|
282
|
+
// Stryker disable next-line EqualityOperator: <= vs < equivalent (float dist never exactly equals collisionDist)
|
|
268
283
|
if (dist <= collisionDist)
|
|
269
284
|
{
|
|
270
285
|
return false; // Got hit while dodging
|
|
@@ -327,7 +342,7 @@ function calculateBestDodgeDirection(
|
|
|
327
342
|
canOutrun: boolean,
|
|
328
343
|
): Position | null
|
|
329
344
|
{
|
|
330
|
-
//
|
|
345
|
+
// Stryker disable next-line ConditionalExpression: replacing with false just falls through to the `else if (canDodgeLeft)` which returns the same value
|
|
331
346
|
if (canDodgeLeft && canDodgeRight)
|
|
332
347
|
{
|
|
333
348
|
// Both sides work - pick the one closer to where we want to be
|
|
@@ -358,8 +373,17 @@ function isOutOfBounds(pos: Position): boolean
|
|
|
358
373
|
{
|
|
359
374
|
return (
|
|
360
375
|
pos.x < -ARENA_WATER_BUFFER ||
|
|
361
|
-
pos.x >
|
|
376
|
+
pos.x > ARENA_SIZE + ARENA_WATER_BUFFER ||
|
|
362
377
|
pos.y < -ARENA_WATER_BUFFER ||
|
|
363
|
-
pos.y >
|
|
378
|
+
pos.y > ARENA_SIZE + ARENA_WATER_BUFFER
|
|
364
379
|
);
|
|
365
380
|
}
|
|
381
|
+
|
|
382
|
+
/** @internal Exposed for mutation testing only. */
|
|
383
|
+
export const _testing = {
|
|
384
|
+
simulateMissileToTarget,
|
|
385
|
+
simulateDodge,
|
|
386
|
+
getDodgeDirection,
|
|
387
|
+
calculateBestDodgeDirection,
|
|
388
|
+
isOutOfBounds,
|
|
389
|
+
};
|
package/src/hooks/types.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Angles: degrees (0°=right, 90°=down, 180°=left, 270°=up)
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import {Position, Velocity, ProjectileState, MissileConfig,
|
|
14
|
+
import {Position, Velocity, ProjectileState, MissileConfig, WizardActions, GameState, MissileActions} from '../types.js';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Enemy wizard state as seen by your bot.
|
|
@@ -31,6 +31,14 @@ export interface EnemyState
|
|
|
31
31
|
status: 'idle' | 'casting' | 'channeling' | 'gcd_locked';
|
|
32
32
|
/** Which spell enemy is casting, or null. */
|
|
33
33
|
castingSpell: 'missile' | 'shield' | 'blink' | null;
|
|
34
|
+
/** Cast progress in ticks (0 if not casting). */
|
|
35
|
+
castProgress: number;
|
|
36
|
+
/** Total cast duration in ticks (0 if not casting). */
|
|
37
|
+
castDuration: number;
|
|
38
|
+
/** Remaining GCD ticks (0 if not in GCD). */
|
|
39
|
+
gcdRemaining: number;
|
|
40
|
+
/** Duration the enemy has been channeling in ticks (0 if not channeling). */
|
|
41
|
+
channelDuration: number;
|
|
34
42
|
/** Enemy shield block multiplier (0 if not shielding, 0.3-0.9 if shielding). */
|
|
35
43
|
shieldStrength: number;
|
|
36
44
|
}
|
|
@@ -93,15 +101,15 @@ export interface ActionBuilder extends FinalAction
|
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
/**
|
|
96
|
-
*
|
|
104
|
+
* Wizard function type for the hooks API.
|
|
97
105
|
* Called every tick. Read state with hooks, return an action.
|
|
98
106
|
*/
|
|
99
|
-
export type
|
|
107
|
+
export type WizardFunction = () => FinalAction;
|
|
100
108
|
|
|
101
109
|
/**
|
|
102
110
|
* Internal context for game state hooks.
|
|
103
111
|
*/
|
|
104
|
-
export interface
|
|
112
|
+
export interface WizardContext
|
|
105
113
|
{
|
|
106
114
|
entityId: string;
|
|
107
115
|
tick: number;
|
|
@@ -117,6 +125,7 @@ export interface BotContext
|
|
|
117
125
|
channelDuration?: number;
|
|
118
126
|
gcdRemaining?: number;
|
|
119
127
|
blinkCooldown: number;
|
|
128
|
+
lastMissileConfig?: MissileConfig;
|
|
120
129
|
enemies: Array<{
|
|
121
130
|
id: string;
|
|
122
131
|
position: Position;
|
|
@@ -124,6 +133,9 @@ export interface BotContext
|
|
|
124
133
|
health: number;
|
|
125
134
|
state: 'idle' | 'casting' | 'channeling' | 'gcd_locked';
|
|
126
135
|
castingSpell?: 'missile' | 'shield' | 'blink';
|
|
136
|
+
castProgress?: number;
|
|
137
|
+
castDuration?: number;
|
|
138
|
+
gcdRemaining?: number;
|
|
127
139
|
channelingSpell?: 'shield';
|
|
128
140
|
channelDuration?: number;
|
|
129
141
|
}>;
|
|
@@ -134,9 +146,54 @@ export interface BotContext
|
|
|
134
146
|
damageDealt: number;
|
|
135
147
|
damageTaken: number;
|
|
136
148
|
lastHitTick: number;
|
|
149
|
+
random: () => number;
|
|
137
150
|
}
|
|
138
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Context available to missile AI functions via getMissileContext().
|
|
154
|
+
* Provides missile state, the full game state from the owner's perspective,
|
|
155
|
+
* and a seeded PRNG.
|
|
156
|
+
*/
|
|
157
|
+
export interface MissileContext
|
|
158
|
+
{
|
|
159
|
+
/** Missile position in world coordinates. */
|
|
160
|
+
position: Position;
|
|
161
|
+
/** Missile heading in degrees (0=right, 90=down). */
|
|
162
|
+
rotation: number;
|
|
163
|
+
/** Missile speed in units/tick. */
|
|
164
|
+
speed: number;
|
|
165
|
+
/** Missile turn rate in degrees/tick. */
|
|
166
|
+
turnRate: number;
|
|
167
|
+
/** Missile damage on hit. */
|
|
168
|
+
damage: number;
|
|
169
|
+
/** Ticks remaining before the missile expires. */
|
|
170
|
+
remainingTicks: number;
|
|
171
|
+
/** ID of the wizard who owns this missile. */
|
|
172
|
+
ownerId: string;
|
|
173
|
+
/** Full game state from the missile owner's perspective. */
|
|
174
|
+
worldState: GameState;
|
|
175
|
+
/** Seeded PRNG [0, 1). Deterministic per missile per tick. */
|
|
176
|
+
random: () => number;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Action returned by a missile AI function.
|
|
181
|
+
* Call turnToward(x, y) to steer, or flyStraight() to coast.
|
|
182
|
+
*/
|
|
183
|
+
export interface MissileAction
|
|
184
|
+
{
|
|
185
|
+
/** Internal: extract the MissileActions */
|
|
186
|
+
readonly _toMissileAction: () => MissileActions;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Missile AI function type for the hooks API.
|
|
191
|
+
* Called every tick for each in-flight missile.
|
|
192
|
+
* Read state with getMissileContext(), return a MissileAction.
|
|
193
|
+
*/
|
|
194
|
+
export type MissileFunction = () => MissileAction;
|
|
195
|
+
|
|
139
196
|
/**
|
|
140
197
|
* Missile configuration for action builders.
|
|
141
198
|
*/
|
|
142
|
-
export type {MissileConfig
|
|
199
|
+
export type {MissileConfig};
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,8 @@ export * from './engine/params-runtime.js';
|
|
|
14
14
|
export * from './engine/optimizer.js';
|
|
15
15
|
export {BotBundle, compileMatchBundle, MatchSandbox, sandboxFight, sandboxSimulate} from './engine/sandbox.js';
|
|
16
16
|
export type {CompileOptions, SandboxOptions} from './engine/sandbox.js';
|
|
17
|
+
export {buildMatchTemplate, runBundleFight, runBundleSimulate} from './engine/bundle-fight.js';
|
|
18
|
+
export type {RunBundleFightOptions, RunBundleSimulateOptions} from './engine/bundle-fight.js';
|
|
17
19
|
export {BrowserMatchSandbox, BrowserManualMatchSandbox, browserSandboxFight, browserSandboxSimulate, createWorkerScript} from './engine/sandbox-browser.js';
|
|
18
20
|
export type {BrowserSandboxOptions, ManualMatchInitOptions, ManualMatchStepRequest, WorkerFactory, WorkerLike} from './engine/sandbox-browser.js';
|
|
19
21
|
export {testBot} from './testing.js';
|