@vibemancer/core 0.1.3 → 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.
Files changed (74) hide show
  1. package/README.md +2 -2
  2. package/dist/chunk-W7HWJFQ4.js +10599 -0
  3. package/dist/chunk-W7HWJFQ4.js.map +1 -0
  4. package/dist/index-browser.d.ts +1300 -1051
  5. package/dist/index-browser.js +55 -17
  6. package/dist/index.d.ts +53 -3
  7. package/dist/index.js +200 -54
  8. package/dist/index.js.map +1 -1
  9. package/package.json +21 -15
  10. package/src/bots/Hero.ts +867 -0
  11. package/src/bots/berserker/01_Stormchaser.ts +162 -120
  12. package/src/bots/berserker/02_Stormcaller.ts +160 -116
  13. package/src/bots/berserker/03_Stormforger.ts +162 -129
  14. package/src/bots/caster/01_Flamecaller.ts +126 -84
  15. package/src/bots/caster/02_Pyromancer.ts +148 -101
  16. package/src/bots/caster/03_Infernalist.ts +180 -160
  17. package/src/bots/defensive/01_Turtle.ts +48 -44
  18. package/src/bots/defensive/02_Sentinel.ts +57 -53
  19. package/src/bots/defensive/03_Golem.ts +85 -97
  20. package/src/bots/duelist/01_Battlemage.ts +157 -122
  21. package/src/bots/duelist/02_Warmage.ts +166 -121
  22. package/src/bots/duelist/03_Archmage.ts +198 -178
  23. package/src/bots/homing/01_Bonemancer.ts +20 -32
  24. package/src/bots/homing/02_Lich.ts +145 -93
  25. package/src/bots/homing/03_Archlich.ts +129 -60
  26. package/src/bots/kiter/01_Spellspinner.ts +145 -107
  27. package/src/bots/kiter/02_Spellweaver.ts +153 -105
  28. package/src/bots/kiter/03_Spellbinder.ts +168 -123
  29. package/src/bots/melee/01_Shadowblade.ts +131 -75
  30. package/src/bots/melee/02_Nightblade.ts +174 -130
  31. package/src/bots/melee/03_Voidblade.ts +266 -168
  32. package/src/bots/registry.ts +44 -37
  33. package/src/bots/shared.ts +185 -25
  34. package/src/bots/sniper/01_Spellshot.ts +151 -98
  35. package/src/bots/sniper/02_Spelltracer.ts +173 -128
  36. package/src/bots/sniper/03_Spellseeker.ts +177 -152
  37. package/src/bots/standalone/Critter.ts +46 -52
  38. package/src/bots/standalone/Doombringer.ts +36 -40
  39. package/src/bots/standalone/Hogger.ts +120 -89
  40. package/src/bots/standalone/Rookie.ts +21 -23
  41. package/src/bots/standalone/TargetDummy.ts +5 -6
  42. package/src/bots/test/cheater.ts +91 -85
  43. package/src/bots/test/crasher.ts +26 -28
  44. package/src/engine/bundle-fight.ts +242 -0
  45. package/src/engine/hooks-runtime.ts +58 -18
  46. package/src/engine/manual-match.ts +33 -25
  47. package/src/engine/missile-templates.ts +25 -43
  48. package/src/engine/optimizer.ts +7 -7
  49. package/src/engine/params-runtime.ts +3 -3
  50. package/src/engine/physics.ts +33 -23
  51. package/src/engine/sandbox-browser.ts +8 -7
  52. package/src/engine/sandbox-compile.ts +1 -1
  53. package/src/engine/sandbox-harness.ts +299 -367
  54. package/src/engine/sandbox.ts +3 -3
  55. package/src/engine/simulation.ts +291 -76
  56. package/src/engine/spells.ts +9 -12
  57. package/src/engine-version.ts +1 -1
  58. package/src/hooks/action-builders.ts +76 -21
  59. package/src/hooks/index.ts +17 -9
  60. package/src/hooks/state-hooks.ts +61 -25
  61. package/src/hooks/threat-analysis.ts +44 -20
  62. package/src/hooks/types.ts +62 -5
  63. package/src/index.ts +2 -0
  64. package/src/rules.ts +209 -63
  65. package/src/stats.ts +2 -2
  66. package/src/testing.ts +6 -30
  67. package/src/trace.ts +63 -3
  68. package/src/types.ts +127 -14
  69. package/src/utils/angles.ts +1 -0
  70. package/src/utils/combat.ts +98 -6
  71. package/src/utils/spatial.ts +3 -3
  72. package/dist/chunk-B7YYYBEC.js +0 -9140
  73. package/dist/chunk-B7YYYBEC.js.map +0 -1
  74. package/src/hooks/bot-wrapper.ts +0 -84
@@ -17,7 +17,7 @@
17
17
 
18
18
  import type {ParamDeclaration} from './params-runtime.js';
19
19
  import type {FightResult} from './simulation.js';
20
- import {WIZARD_HEALTH} from '../rules.js';
20
+ import {RULES} from '../rules.js';
21
21
 
22
22
  /**
23
23
  * Compute the effective min/max search window for a parameter.
@@ -169,7 +169,7 @@ export function scoreFight(result: FightResult): number
169
169
  if (match.winner === 'wizard-1')
170
170
  {
171
171
  // Won: 3.0 base + HP bonus
172
- score += 3.0 + (match.finalState.health / WIZARD_HEALTH) * 0.5;
172
+ score += 3.0 + (match.finalState.health / RULES.WIZARD_HEALTH) * 0.5;
173
173
  }
174
174
  else if (match.winner === 'draw' || match.winner === null)
175
175
  {
@@ -178,8 +178,8 @@ export function scoreFight(result: FightResult): number
178
178
  else
179
179
  {
180
180
  // Lost: small bonus for low enemy HP
181
- const enemyHP = match.finalState.enemies[0]?.health ?? WIZARD_HEALTH;
182
- score += (1 - enemyHP / WIZARD_HEALTH) * 0.5;
181
+ const enemyHP = match.finalState.enemies[0]?.health ?? RULES.WIZARD_HEALTH;
182
+ score += (1 - enemyHP / RULES.WIZARD_HEALTH) * 0.5;
183
183
  }
184
184
  }
185
185
 
@@ -201,8 +201,8 @@ export function scoreFightAsWizard2(result: FightResult): number
201
201
  if (match.winner === 'wizard-2')
202
202
  {
203
203
  // Won: 3.0 base + HP bonus (wizard-2's remaining HP)
204
- const hp = match.finalState.enemies[0]?.health ?? WIZARD_HEALTH;
205
- score += 3.0 + (hp / WIZARD_HEALTH) * 0.5;
204
+ const hp = match.finalState.enemies[0]?.health ?? RULES.WIZARD_HEALTH;
205
+ score += 3.0 + (hp / RULES.WIZARD_HEALTH) * 0.5;
206
206
  }
207
207
  else if (match.winner === 'draw' || match.winner === null)
208
208
  {
@@ -212,7 +212,7 @@ export function scoreFightAsWizard2(result: FightResult): number
212
212
  {
213
213
  // Lost: small bonus for low wizard-1 HP
214
214
  const enemyHP = match.finalState.health;
215
- score += (1 - enemyHP / WIZARD_HEALTH) * 0.5;
215
+ score += (1 - enemyHP / RULES.WIZARD_HEALTH) * 0.5;
216
216
  }
217
217
  }
218
218
 
@@ -50,7 +50,7 @@
50
50
  * - Violations are detected and throw errors
51
51
  */
52
52
 
53
- import type {WizardFunction} from '../types.js';
53
+ import type {WizardFunction} from '../hooks/types.js';
54
54
  import {validateHookCall} from './hooks-runtime.js';
55
55
 
56
56
  /**
@@ -174,12 +174,12 @@ export function stopDiscovery(): ParamDeclaration[]
174
174
  */
175
175
  export function wrapWithParams(bot: WizardFunction, params: Record<string, number>): WizardFunction
176
176
  {
177
- return (props) =>
177
+ return () =>
178
178
  {
179
179
  setParamValues(params);
180
180
  try
181
181
  {
182
- return bot(props);
182
+ return bot();
183
183
  }
184
184
  finally
185
185
  {
@@ -1,38 +1,29 @@
1
1
  import {Position, WizardState, ProjectileState} from '../types.js';
2
- import {ARENA_WIDTH, ARENA_HEIGHT, MOVEMENT_SPEED, CASTING_MOVEMENT_MULT, WIZARD_RADIUS} from '../rules.js';
2
+ import {RULES, ARENA_SIZE, WIZARD_RADIUS, ARENA_MIN, ARENA_MAX} from '../rules.js';
3
3
 
4
4
  /**
5
5
  * Move a wizard based on world-space input direction.
6
6
  *
7
- * Movement model (from design doc):
8
- * - x: +100 = right, -100 = left
9
- * - y: +100 = down, -100 = up
10
- * - Values are clamped to [-100, 100]
11
- * - Diagonal movement is normalized (magnitude capped at 100)
12
- * - No rotation tracking - just output (x, y) direction
7
+ * Any vector works: direction is preserved, speed is clamped to [0, 1].
8
+ * (0.5, 0) = half speed right. (300, 200) = full speed at 33.7°.
13
9
  */
14
10
  export function moveWizard(wizard: WizardState, move: {x: number; y: number}, deltaTicks: number): Position
15
11
  {
16
- // Clamp input values to [-100, 100]
17
- let dx = Math.max(-100, Math.min(100, move.x || 0));
18
- let dy = Math.max(-100, Math.min(100, move.y || 0));
12
+ let dx = move.x || 0;
13
+ let dy = move.y || 0;
19
14
 
20
- // Normalize diagonal movement (cap magnitude at 100)
21
15
  const magnitude = Math.sqrt(dx * dx + dy * dy);
22
- if (magnitude > 100)
16
+ // Stryker disable next-line EqualityOperator: > vs >= equivalent (dividing by exactly 1 is identity)
17
+ if (magnitude > 1)
23
18
  {
24
- dx = dx * 100 / magnitude;
25
- dy = dy * 100 / magnitude;
19
+ dx = dx / magnitude;
20
+ dy = dy / magnitude;
26
21
  }
27
22
 
28
- // Convert from [-100, 100] to [-1, 1] direction
29
- dx = dx / 100;
30
- dy = dy / 100;
31
-
32
- let speed = MOVEMENT_SPEED;
23
+ let speed = RULES.MOVEMENT_SPEED;
33
24
  if (wizard.state === 'casting')
34
25
  {
35
- speed *= CASTING_MOVEMENT_MULT;
26
+ speed *= RULES.CASTING_MOVEMENT_MULT;
36
27
  }
37
28
  else if (wizard.state === 'channeling')
38
29
  {
@@ -61,16 +52,31 @@ export function moveProjectile(projectile: ProjectileState, deltaTicks: number):
61
52
  }
62
53
 
63
54
  /**
64
- * Clamp a position to the arena boundaries.
55
+ * Clamp a position to the full arena bounds (0-860).
56
+ * No playfield clamping — wizards CAN walk/blink into lava.
65
57
  */
66
58
  export function clampToArena(position: Position, radius: number): Position
67
59
  {
68
60
  return {
69
- x: Math.max(radius, Math.min(ARENA_WIDTH - radius, position.x)),
70
- y: Math.max(radius, Math.min(ARENA_HEIGHT - radius, position.y)),
61
+ x: Math.max(radius, Math.min(ARENA_SIZE - radius, position.x)),
62
+ y: Math.max(radius, Math.min(ARENA_SIZE - radius, position.y)),
71
63
  };
72
64
  }
73
65
 
66
+ /**
67
+ * Check if a position is in the lava zone (outside the playfield).
68
+ * Lava zones: [0, ARENA_MIN] and [ARENA_MAX, ARENA_SIZE] on each axis.
69
+ */
70
+ export function isInLava(position: Position, radius: number): boolean
71
+ {
72
+ return (
73
+ position.x - radius < ARENA_MIN ||
74
+ position.x + radius > ARENA_MAX ||
75
+ position.y - radius < ARENA_MIN ||
76
+ position.y + radius > ARENA_MAX
77
+ );
78
+ }
79
+
74
80
  /**
75
81
  * Resolve body collision between two wizards.
76
82
  * Pushes both apart equally so they don't overlap. Neither is blocked — they just can't stack.
@@ -81,12 +87,14 @@ export function resolveWizardCollision(wizard1: WizardState, wizard2: WizardStat
81
87
  const minDist = WIZARD_RADIUS * 2;
82
88
  const maxIterations = 5; // Prevents infinite loops in edge cases
83
89
 
90
+ // Stryker disable next-line EqualityOperator: < vs <= equivalent (one extra iteration produces same result)
84
91
  for (let i = 0; i < maxIterations; i++)
85
92
  {
86
93
  const dx = wizard2.position.x - wizard1.position.x;
87
94
  const dy = wizard2.position.y - wizard1.position.y;
88
95
  const dist = Math.sqrt(dx * dx + dy * dy);
89
96
 
97
+ // Stryker disable next-line EqualityOperator: >= vs > equivalent (at exactly minDist, no push needed either way)
90
98
  if (dist >= minDist) return; // No overlap — done
91
99
 
92
100
  if (dist === 0)
@@ -128,6 +136,7 @@ export function sweptCircleCollision(oldPos: Position, newPos: Position, radius:
128
136
  if (d2 === 0)
129
137
  {
130
138
  const dist2 = (oldPos.x - targetPos.x) ** 2 + (oldPos.y - targetPos.y) ** 2;
139
+ // Stryker disable next-line EqualityOperator: <= vs < equivalent (exact equality of float dist² and integer radius² is vanishingly rare)
131
140
  return dist2 <= totalRadius * totalRadius;
132
141
  }
133
142
 
@@ -139,5 +148,6 @@ export function sweptCircleCollision(oldPos: Position, newPos: Position, radius:
139
148
  const closestY = oldPos.y + t * dy;
140
149
 
141
150
  const dist2 = (targetPos.x - closestX) ** 2 + (targetPos.y - closestY) ** 2;
151
+ // Stryker disable next-line EqualityOperator: <= vs < equivalent (float dist² rarely equals exact integer radius²)
142
152
  return dist2 <= totalRadius * totalRadius;
143
153
  }
@@ -19,7 +19,8 @@
19
19
  * NOTE: This file has ZERO Node.js dependencies. It works in any JS environment.
20
20
  */
21
21
 
22
- import type {GameState, MissileAIFunction, ProjectileState, WizardActions} from '../types.js';
22
+ import type {GameState, ProjectileState, WizardActions} from '../types.js';
23
+ import type {MissileFunction} from '../hooks/types.js';
23
24
  import type {BotError, FightResult, MatchWinner, SimulateResult} from './simulation.js';
24
25
  import type {StepResult} from './manual-match.js';
25
26
 
@@ -580,7 +581,7 @@ export class BrowserManualMatchSandbox
580
581
 
581
582
  /**
582
583
  * Advance the match by `request.count` ticks (default 1), updating
583
- * the player's human actions and any hijacked missile targets first.
584
+ * the player's human actions and any guided missile targets first.
584
585
  */
585
586
  async step(request: ManualMatchStepRequest = {}): Promise<StepResult>
586
587
  {
@@ -589,18 +590,18 @@ export class BrowserManualMatchSandbox
589
590
  }
590
591
 
591
592
  /**
592
- * Replace a missile's AI with a worker-local hijack stub that reads
593
+ * Replace a missile's AI with a worker-local guide stub that reads
593
594
  * from `latestHumanMissileTargets[projectileId]`. Subsequent step()
594
595
  * calls with `humanMissileTargets` populated for this id steer the
595
596
  * missile.
596
597
  */
597
- async hijackMissile(projectileId: string): Promise<void>
598
+ async guideMissile(projectileId: string): Promise<void>
598
599
  {
599
- await this.inner.callRaw('manualMatchHijack', {projectileId});
600
+ await this.inner.callRaw('manualMatchGuide', {projectileId});
600
601
  }
601
602
 
602
603
  /**
603
- * Restore a hijacked missile's original AI.
604
+ * Restore a guided missile's original AI.
604
605
  */
605
606
  async releaseMissile(projectileId: string): Promise<void>
606
607
  {
@@ -664,7 +665,7 @@ export type {
664
665
  BotError,
665
666
  GameState,
666
667
  MatchWinner,
667
- MissileAIFunction,
668
+ MissileFunction,
668
669
  ProjectileState,
669
670
  StepResult,
670
671
  WizardActions,
@@ -148,7 +148,7 @@ export async function compileMatchBundle(
148
148
  * the host).
149
149
  *
150
150
  * Returns a plain JS string that, when loaded into a Web Worker, exposes the
151
- * `__manualMatchInit`, `__manualMatchStep`, `__manualMatchHijack`,
151
+ * `__manualMatchInit`, `__manualMatchStep`, `__manualMatchGuide`,
152
152
  * `__manualMatchRelease`, `__manualMatchSetInvincible`,
153
153
  * `__manualMatchGetState`, `__manualMatchGetResult`, and `__manualMatchDispose`
154
154
  * globals on the worker's globalThis.