@vibemancer/core 0.1.0

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 (78) hide show
  1. package/README.md +28 -0
  2. package/dist/chunk-L7Z7OFXD.js +9140 -0
  3. package/dist/chunk-L7Z7OFXD.js.map +1 -0
  4. package/dist/index-browser.d.ts +2602 -0
  5. package/dist/index-browser.js +407 -0
  6. package/dist/index-browser.js.map +1 -0
  7. package/dist/index.d.ts +150 -0
  8. package/dist/index.js +750 -0
  9. package/dist/index.js.map +1 -0
  10. package/package.json +79 -0
  11. package/src/bots/berserker/01_Stormchaser.ts +457 -0
  12. package/src/bots/berserker/02_Stormcaller.ts +417 -0
  13. package/src/bots/berserker/03_Stormforger.ts +481 -0
  14. package/src/bots/caster/01_Flamecaller.ts +286 -0
  15. package/src/bots/caster/02_Pyromancer.ts +350 -0
  16. package/src/bots/caster/03_Infernalist.ts +492 -0
  17. package/src/bots/defensive/01_Turtle.ts +151 -0
  18. package/src/bots/defensive/02_Sentinel.ts +134 -0
  19. package/src/bots/defensive/03_Golem.ts +357 -0
  20. package/src/bots/duelist/01_Battlemage.ts +433 -0
  21. package/src/bots/duelist/02_Warmage.ts +438 -0
  22. package/src/bots/duelist/03_Archmage.ts +588 -0
  23. package/src/bots/homing/01_Bonemancer.ts +67 -0
  24. package/src/bots/homing/02_Lich.ts +356 -0
  25. package/src/bots/homing/03_Archlich.ts +220 -0
  26. package/src/bots/index.ts +30 -0
  27. package/src/bots/kiter/01_Spellspinner.ts +398 -0
  28. package/src/bots/kiter/02_Spellweaver.ts +378 -0
  29. package/src/bots/kiter/03_Spellbinder.ts +448 -0
  30. package/src/bots/melee/01_Shadowblade.ts +270 -0
  31. package/src/bots/melee/02_Nightblade.ts +437 -0
  32. package/src/bots/melee/03_Voidblade.ts +582 -0
  33. package/src/bots/registry.ts +207 -0
  34. package/src/bots/shared.ts +472 -0
  35. package/src/bots/sniper/01_Spellshot.ts +385 -0
  36. package/src/bots/sniper/02_Spelltracer.ts +441 -0
  37. package/src/bots/sniper/03_Spellseeker.ts +546 -0
  38. package/src/bots/standalone/Critter.ts +89 -0
  39. package/src/bots/standalone/Doombringer.ts +91 -0
  40. package/src/bots/standalone/Hogger.ts +228 -0
  41. package/src/bots/standalone/Rookie.ts +50 -0
  42. package/src/bots/standalone/TargetDummy.ts +21 -0
  43. package/src/bots/test/cheater.ts +405 -0
  44. package/src/bots/test/crasher.ts +81 -0
  45. package/src/engine/hooks-runtime.ts +394 -0
  46. package/src/engine/manual-match.ts +289 -0
  47. package/src/engine/missile-templates.ts +155 -0
  48. package/src/engine/optimizer.ts +220 -0
  49. package/src/engine/params-runtime.ts +189 -0
  50. package/src/engine/physics.ts +143 -0
  51. package/src/engine/sandbox-browser.ts +671 -0
  52. package/src/engine/sandbox-compile.ts +197 -0
  53. package/src/engine/sandbox-harness.ts +367 -0
  54. package/src/engine/sandbox.ts +332 -0
  55. package/src/engine/simulation.ts +828 -0
  56. package/src/engine/spells.ts +128 -0
  57. package/src/engine-version.ts +11 -0
  58. package/src/hooks/action-builders.ts +210 -0
  59. package/src/hooks/bot-wrapper.ts +84 -0
  60. package/src/hooks/index.ts +75 -0
  61. package/src/hooks/state-hooks.ts +354 -0
  62. package/src/hooks/threat-analysis.ts +365 -0
  63. package/src/hooks/types.ts +142 -0
  64. package/src/index-browser.ts +30 -0
  65. package/src/index.ts +24 -0
  66. package/src/rules.ts +254 -0
  67. package/src/stats.ts +262 -0
  68. package/src/testing.ts +207 -0
  69. package/src/trace.ts +430 -0
  70. package/src/types.ts +193 -0
  71. package/src/utils/angles.ts +47 -0
  72. package/src/utils/combat.ts +279 -0
  73. package/src/utils/distance.ts +21 -0
  74. package/src/utils/index.ts +7 -0
  75. package/src/utils/movement.ts +108 -0
  76. package/src/utils/random.ts +65 -0
  77. package/src/utils/spatial.ts +63 -0
  78. package/src/utils/targeting.ts +45 -0
@@ -0,0 +1,332 @@
1
+ /**
2
+ * VIBEMANCER — SANDBOX
3
+ *
4
+ * Provides isolated-vm sandboxing for bot code execution. Both bots + the
5
+ * entire simulation engine run inside a single V8 isolate, so there is ZERO
6
+ * per-tick boundary crossing overhead. The only data crossing the boundary
7
+ * is fight/simulate options going in and results coming out.
8
+ *
9
+ * Architecture:
10
+ * - Host: creates isolate, loads compiled bundle, calls __fight/__simulate
11
+ * - Isolate: contains both bots + full simulation engine, runs fight/simulate
12
+ *
13
+ * Safety:
14
+ * - Memory limit (default 512 MB) catches memory bombs
15
+ * - Timeout (default 30s) catches infinite loops
16
+ * - Prototype freeze prevents cross-bot sabotage
17
+ * - platform: 'neutral' strips Node.js APIs (no fs/net/process)
18
+ */
19
+
20
+ import ivm from 'isolated-vm';
21
+ import type {FightResult, SimulateResult} from './simulation.js';
22
+ import {BotBundle, compileMatchBundle} from './sandbox-compile.js';
23
+ import type {CompileOptions} from './sandbox-compile.js';
24
+
25
+ // Re-export BotBundle so existing imports from sandbox.ts keep working
26
+ export {BotBundle} from './sandbox-compile.js';
27
+ export {compileMatchBundle, compileManualMatchBundle} from './sandbox-compile.js';
28
+ export type {CompileOptions} from './sandbox-compile.js';
29
+
30
+ /**
31
+ * Options for sandbox creation.
32
+ */
33
+ export interface SandboxOptions
34
+ {
35
+ /** Memory limit in MB for the isolate (default: 512). */
36
+ memoryLimitMB?: number;
37
+ /** Timeout in ms for fight/simulate calls (default: 30000). */
38
+ timeoutMs?: number;
39
+ /** Options passed to esbuild compilation (aliases, externals). */
40
+ compileOptions?: CompileOptions;
41
+ }
42
+
43
+ /**
44
+ * A sandboxed match runner. Both bots + the entire simulation engine run
45
+ * inside a single isolated-vm isolate.
46
+ *
47
+ * Usage:
48
+ * ```ts
49
+ * const sandbox = await MatchSandbox.create(botA, botB);
50
+ * const result = sandbox.fight({ seed: 42 });
51
+ * sandbox.dispose();
52
+ * ```
53
+ */
54
+ export class MatchSandbox
55
+ {
56
+ private isolate: ivm.Isolate;
57
+ private context: ivm.Context;
58
+ private fightFn: ivm.Reference;
59
+ private simulateFn: ivm.Reference;
60
+ private timeout: number;
61
+ private disposed = false;
62
+
63
+ private constructor(
64
+ isolate: ivm.Isolate,
65
+ context: ivm.Context,
66
+ fightFn: ivm.Reference,
67
+ simulateFn: ivm.Reference,
68
+ timeout: number,
69
+ )
70
+ {
71
+ this.isolate = isolate;
72
+ this.context = context;
73
+ this.fightFn = fightFn;
74
+ this.simulateFn = simulateFn;
75
+ this.timeout = timeout;
76
+ }
77
+
78
+ /**
79
+ * Create a sandbox with both bots loaded. Compiles the match bundle
80
+ * automatically using esbuild.
81
+ */
82
+ static async create(
83
+ bot1: BotBundle,
84
+ bot2: BotBundle,
85
+ options?: SandboxOptions,
86
+ ): Promise<MatchSandbox>
87
+ {
88
+ const memoryLimitMB = options?.memoryLimitMB ?? 512;
89
+ const timeoutMs = options?.timeoutMs ?? 30000;
90
+
91
+ // 1. Compile the match bundle
92
+ const bundle = await compileMatchBundle(bot1, bot2, options?.compileOptions);
93
+
94
+ // 2. Create isolate with memory limit
95
+ const isolate = new ivm.Isolate({memoryLimit: memoryLimitMB});
96
+
97
+ try
98
+ {
99
+ // 3. Create context and load the bundle
100
+ const context = await isolate.createContext();
101
+ const script = await isolate.compileScript(bundle, {filename: 'match-bundle.js'});
102
+ await script.run(context, {timeout: timeoutMs});
103
+
104
+ // 4. Get references to the exposed functions
105
+ const global = context.global;
106
+ const fightFn = await global.get('__fight', {reference: true});
107
+ const simulateFn = await global.get('__simulate', {reference: true});
108
+
109
+ return new MatchSandbox(isolate, context, fightFn, simulateFn, timeoutMs);
110
+ }
111
+ catch(error)
112
+ {
113
+ // OOM or timeout can auto-dispose the isolate, so guard the cleanup dispose.
114
+ try
115
+ {
116
+ isolate.dispose();
117
+ }
118
+ catch
119
+ {
120
+ // already disposed
121
+ }
122
+ throw error;
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Create a sandbox from a pre-compiled bundle string.
128
+ * Useful for caching compiled bundles across multiple MatchSandbox instances.
129
+ */
130
+ static async fromBundle(
131
+ bundle: string,
132
+ options?: SandboxOptions,
133
+ ): Promise<MatchSandbox>
134
+ {
135
+ const memoryLimitMB = options?.memoryLimitMB ?? 512;
136
+ const timeoutMs = options?.timeoutMs ?? 30000;
137
+
138
+ const isolate = new ivm.Isolate({memoryLimit: memoryLimitMB});
139
+
140
+ try
141
+ {
142
+ const context = await isolate.createContext();
143
+ const script = await isolate.compileScript(bundle, {filename: 'match-bundle.js'});
144
+ await script.run(context, {timeout: timeoutMs});
145
+
146
+ const global = context.global;
147
+ const fightFn = await global.get('__fight', {reference: true});
148
+ const simulateFn = await global.get('__simulate', {reference: true});
149
+
150
+ return new MatchSandbox(isolate, context, fightFn, simulateFn, timeoutMs);
151
+ }
152
+ catch(error)
153
+ {
154
+ try
155
+ {
156
+ isolate.dispose();
157
+ }
158
+ catch
159
+ {
160
+ // already disposed
161
+ }
162
+ throw error;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Compile a match bundle without creating an isolate.
168
+ * Returns the compiled JS string for caching/reuse.
169
+ */
170
+ static async compile(bot1: BotBundle, bot2: BotBundle, compileOptions?: CompileOptions): Promise<string>
171
+ {
172
+ return compileMatchBundle(bot1, bot2, compileOptions);
173
+ }
174
+
175
+ /**
176
+ * Run a full fight (10 matches: 5 spawn distances x 2 sides).
177
+ * Synchronous after isolate creation — runs entirely inside the isolate.
178
+ */
179
+ fight(options?: {seed?: number; maxTicks?: number}): FightResult
180
+ {
181
+ this.ensureNotDisposed();
182
+
183
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- isolated-vm returns unknown
184
+ return this.fightFn.applySync(undefined, [options ?? {}], {
185
+ arguments: {copy: true},
186
+ result: {copy: true},
187
+ timeout: this.timeout,
188
+ }) as FightResult;
189
+ }
190
+
191
+ /**
192
+ * Run a single simulation.
193
+ * Synchronous after isolate creation.
194
+ *
195
+ * @param options.params1 - useParam overrides for bot 1 (wizard-1)
196
+ * @param options.params2 - useParam overrides for bot 2 (wizard-2)
197
+ */
198
+ simulate(options?: {
199
+ seed?: number;
200
+ maxTicks?: number;
201
+ spawnDistance?: number;
202
+ skipHistory?: boolean;
203
+ params1?: Record<string, number>;
204
+ params2?: Record<string, number>;
205
+ }): SimulateResult
206
+ {
207
+ this.ensureNotDisposed();
208
+
209
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- isolated-vm returns unknown
210
+ return this.simulateFn.applySync(undefined, [options ?? {}], {
211
+ arguments: {copy: true},
212
+ result: {copy: true},
213
+ timeout: this.timeout,
214
+ }) as SimulateResult;
215
+ }
216
+
217
+ /**
218
+ * Dispose the isolate and free all memory.
219
+ * The sandbox cannot be used after disposal.
220
+ */
221
+ dispose(): void
222
+ {
223
+ if (!this.disposed)
224
+ {
225
+ this.disposed = true;
226
+ // OOM can auto-dispose the isolate, so guard all cleanup
227
+ try
228
+ {
229
+ this.fightFn.release();
230
+ }
231
+ catch
232
+ {
233
+ // isolate already disposed
234
+ }
235
+ try
236
+ {
237
+ this.simulateFn.release();
238
+ }
239
+ catch
240
+ {
241
+ // isolate already disposed
242
+ }
243
+ try
244
+ {
245
+ this.context.release();
246
+ }
247
+ catch
248
+ {
249
+ // isolate already disposed
250
+ }
251
+ try
252
+ {
253
+ this.isolate.dispose();
254
+ }
255
+ catch
256
+ {
257
+ // isolate already disposed
258
+ }
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Whether this sandbox has been disposed.
264
+ */
265
+ get isDisposed(): boolean
266
+ {
267
+ return this.disposed;
268
+ }
269
+
270
+ private ensureNotDisposed(): void
271
+ {
272
+ if (this.disposed)
273
+ {
274
+ throw new Error('MatchSandbox has been disposed');
275
+ }
276
+ }
277
+ }
278
+
279
+ /**
280
+ * One-shot sandboxed fight. Creates isolate, runs fight, disposes.
281
+ * Convenience wrapper for single-use scenarios.
282
+ */
283
+ export async function sandboxFight(
284
+ bot1: BotBundle,
285
+ bot2: BotBundle,
286
+ options?: {seed?: number; maxTicks?: number} & SandboxOptions,
287
+ ): Promise<FightResult>
288
+ {
289
+ const sandbox = await MatchSandbox.create(bot1, bot2, options);
290
+ try
291
+ {
292
+ return sandbox.fight({seed: options?.seed, maxTicks: options?.maxTicks});
293
+ }
294
+ finally
295
+ {
296
+ sandbox.dispose();
297
+ }
298
+ }
299
+
300
+ /**
301
+ * One-shot sandboxed simulate. Creates isolate, runs simulate, disposes.
302
+ */
303
+ export async function sandboxSimulate(
304
+ bot1: BotBundle,
305
+ bot2: BotBundle,
306
+ options?: {
307
+ seed?: number;
308
+ maxTicks?: number;
309
+ spawnDistance?: number;
310
+ skipHistory?: boolean;
311
+ params1?: Record<string, number>;
312
+ params2?: Record<string, number>;
313
+ } & SandboxOptions,
314
+ ): Promise<SimulateResult>
315
+ {
316
+ const sandbox = await MatchSandbox.create(bot1, bot2, options);
317
+ try
318
+ {
319
+ return sandbox.simulate({
320
+ seed: options?.seed,
321
+ maxTicks: options?.maxTicks,
322
+ spawnDistance: options?.spawnDistance,
323
+ skipHistory: options?.skipHistory,
324
+ params1: options?.params1,
325
+ params2: options?.params2,
326
+ });
327
+ }
328
+ finally
329
+ {
330
+ sandbox.dispose();
331
+ }
332
+ }