@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
|
@@ -1,367 +1,299 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VIBEMANCER — SANDBOX HARNESS
|
|
3
|
-
*
|
|
4
|
-
* Generates the entry point code that runs inside an isolated-vm isolate.
|
|
5
|
-
* The harness bundles both bots + the simulation engine into a single IIFE
|
|
6
|
-
* via esbuild, then exposes __fight and __simulate on globalThis.
|
|
7
|
-
*
|
|
8
|
-
* The prototype freeze banner runs before any module code, preventing
|
|
9
|
-
* prototype pollution attacks between bots sharing the same isolate.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* JavaScript code injected as esbuild banner — runs before the IIFE bundle.
|
|
14
|
-
*
|
|
15
|
-
* 1. Freezes all built-in prototypes to prevent cross-bot sabotage via prototype
|
|
16
|
-
* pollution. This does NOT prevent calling existing methods (e.g. Array.push
|
|
17
|
-
* still works), it only prevents reassigning them.
|
|
18
|
-
*
|
|
19
|
-
* 2. Blocks all IO/network capabilities. Web Workers have fetch, XMLHttpRequest,
|
|
20
|
-
* WebSocket, importScripts, etc. Bot code must not be able to make network
|
|
21
|
-
* calls or load external scripts. Uses Object.defineProperty to make the block
|
|
22
|
-
* irrecoverable (non-writable, non-configurable). In isolated-vm, these globals
|
|
23
|
-
* don't exist — the try-catch makes the deletes a harmless no-op.
|
|
24
|
-
*/
|
|
25
|
-
export const PROTOTYPE_FREEZE_BANNER = `
|
|
26
|
-
Object.freeze(Object.prototype);
|
|
27
|
-
Object.freeze(Array.prototype);
|
|
28
|
-
Object.freeze(Function.prototype);
|
|
29
|
-
Object.freeze(String.prototype);
|
|
30
|
-
Object.freeze(Number.prototype);
|
|
31
|
-
Object.freeze(Boolean.prototype);
|
|
32
|
-
Object.freeze(RegExp.prototype);
|
|
33
|
-
Object.freeze(Date.prototype);
|
|
34
|
-
Object.freeze(Error.prototype);
|
|
35
|
-
Object.freeze(Map.prototype);
|
|
36
|
-
Object.freeze(Set.prototype);
|
|
37
|
-
Object.freeze(Math);
|
|
38
|
-
Object.freeze(JSON);
|
|
39
|
-
(function() {
|
|
40
|
-
var g = typeof globalThis !== 'undefined' ? globalThis : typeof self !== 'undefined' ? self : {};
|
|
41
|
-
var blocked = [
|
|
42
|
-
'fetch', 'XMLHttpRequest', 'WebSocket', 'EventSource',
|
|
43
|
-
'importScripts', 'Worker', 'SharedWorker',
|
|
44
|
-
'Request', 'Response', 'Headers',
|
|
45
|
-
'navigator', 'BroadcastChannel',
|
|
46
|
-
'indexedDB', 'caches'
|
|
47
|
-
];
|
|
48
|
-
for (var i = 0; i < blocked.length; i++) {
|
|
49
|
-
try { Object.defineProperty(g, blocked[i], {value: undefined, writable: false, configurable: false}); }
|
|
50
|
-
catch(e) {}
|
|
51
|
-
}
|
|
52
|
-
})();
|
|
53
|
-
`;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Generate the TypeScript entry point for a match sandbox.
|
|
57
|
-
*
|
|
58
|
-
* This entry point imports both bots and the simulation engine, then
|
|
59
|
-
* exposes __fight and __simulate on globalThis. esbuild bundles this
|
|
60
|
-
* + all transitive imports into a single self-contained IIFE.
|
|
61
|
-
*
|
|
62
|
-
* @param bot1SourcePath - Absolute path to bot 1's TypeScript source file
|
|
63
|
-
* @param bot1ExportName - Named export of bot 1's WizardFunction
|
|
64
|
-
* @param bot2SourcePath - Absolute path to bot 2's TypeScript source file
|
|
65
|
-
* @param bot2ExportName - Named export of bot 2's WizardFunction
|
|
66
|
-
* @param engineDir - Absolute path to the engine directory (src/engine/)
|
|
67
|
-
*/
|
|
68
|
-
export function generateMatchEntryPoint(
|
|
69
|
-
bot1SourcePath: string,
|
|
70
|
-
bot1ExportName: string,
|
|
71
|
-
bot2SourcePath: string,
|
|
72
|
-
bot2ExportName: string,
|
|
73
|
-
engineDir: string,
|
|
74
|
-
): string
|
|
75
|
-
{
|
|
76
|
-
// Use forward slashes for esbuild compatibility (works on all platforms)
|
|
77
|
-
const enginePath = engineDir.replace(/\\/g, '/');
|
|
78
|
-
const bot1Path = bot1SourcePath.replace(/\\/g, '/');
|
|
79
|
-
const bot2Path = bot2SourcePath.replace(/\\/g, '/');
|
|
80
|
-
|
|
81
|
-
// All imports use the engineDir's parent (= packages/core/src/) as root.
|
|
82
|
-
// When user bots alias @vibemancer/core → src/index-browser.ts, esbuild
|
|
83
|
-
// deduplicates these with the bot's imports since they resolve to the same files.
|
|
84
|
-
// This ensures the hooks runtime global state is shared between harness and bot.
|
|
85
|
-
const srcPath = enginePath.replace(/\/engine\/?$/, '');
|
|
86
|
-
|
|
87
|
-
return `
|
|
88
|
-
import {${bot1ExportName} as
|
|
89
|
-
import {${bot2ExportName} as
|
|
90
|
-
import {fight, simulate} from '${srcPath}/engine/simulation.ts';
|
|
91
|
-
import {wrapWithParams} from '${srcPath}/engine/params-runtime.ts';
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
*
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
return
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
`;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Generate an alternate entry point where __fight/__simulate accept and return
|
|
305
|
-
* JSON strings instead of structured objects. Used by the benchmark to compare
|
|
306
|
-
* JSON serialization vs V8 structured clone performance.
|
|
307
|
-
*/
|
|
308
|
-
export function generateMatchEntryPointJSON(
|
|
309
|
-
bot1SourcePath: string,
|
|
310
|
-
bot1ExportName: string,
|
|
311
|
-
bot2SourcePath: string,
|
|
312
|
-
bot2ExportName: string,
|
|
313
|
-
engineDir: string,
|
|
314
|
-
): string
|
|
315
|
-
{
|
|
316
|
-
const enginePath = engineDir.replace(/\\/g, '/');
|
|
317
|
-
const bot1Path = bot1SourcePath.replace(/\\/g, '/');
|
|
318
|
-
const bot2Path = bot2SourcePath.replace(/\\/g, '/');
|
|
319
|
-
const srcPath = enginePath.replace(/\/engine\/?$/, '');
|
|
320
|
-
|
|
321
|
-
return `
|
|
322
|
-
import {${bot1ExportName} as __RawBot1} from '${bot1Path}';
|
|
323
|
-
import {${bot2ExportName} as __RawBot2} from '${bot2Path}';
|
|
324
|
-
import {fight, simulate} from '${srcPath}/engine/simulation.ts';
|
|
325
|
-
import {wrapWithParams} from '${srcPath}/engine/params-runtime.ts';
|
|
326
|
-
import {wrapNewBot} from '${srcPath}/hooks/bot-wrapper.ts';
|
|
327
|
-
|
|
328
|
-
function __wrap(bot) {
|
|
329
|
-
if (bot.length > 0) return bot;
|
|
330
|
-
try {
|
|
331
|
-
var result = bot();
|
|
332
|
-
if (result && typeof result._toAction === 'function') {
|
|
333
|
-
return wrapNewBot(bot);
|
|
334
|
-
}
|
|
335
|
-
} catch(e) {
|
|
336
|
-
return wrapNewBot(bot);
|
|
337
|
-
}
|
|
338
|
-
return bot;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Detection is deferred until the first fight/simulate call so that malicious
|
|
342
|
-
// bots time out during fight(), not during sandbox creation.
|
|
343
|
-
var __Bot1 = null;
|
|
344
|
-
var __Bot2 = null;
|
|
345
|
-
function __resolveBots() {
|
|
346
|
-
if (__Bot1 === null) __Bot1 = __wrap(__RawBot1);
|
|
347
|
-
if (__Bot2 === null) __Bot2 = __wrap(__RawBot2);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
globalThis.__fightJSON = function __fightJSON(optionsJSON) {
|
|
351
|
-
__resolveBots();
|
|
352
|
-
const options = JSON.parse(optionsJSON);
|
|
353
|
-
const result = fight(__Bot1, __Bot2, options);
|
|
354
|
-
return JSON.stringify(result);
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
globalThis.__simulateJSON = function __simulateJSON(optionsJSON) {
|
|
358
|
-
__resolveBots();
|
|
359
|
-
const options = JSON.parse(optionsJSON);
|
|
360
|
-
const {params1, params2, ...simOptions} = options;
|
|
361
|
-
const bot1 = params1 ? wrapWithParams(__Bot1, params1) : __Bot1;
|
|
362
|
-
const bot2 = params2 ? wrapWithParams(__Bot2, params2) : __Bot2;
|
|
363
|
-
const result = simulate(bot1, bot2, simOptions);
|
|
364
|
-
return JSON.stringify(result);
|
|
365
|
-
};
|
|
366
|
-
`;
|
|
367
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* VIBEMANCER — SANDBOX HARNESS
|
|
3
|
+
*
|
|
4
|
+
* Generates the entry point code that runs inside an isolated-vm isolate.
|
|
5
|
+
* The harness bundles both bots + the simulation engine into a single IIFE
|
|
6
|
+
* via esbuild, then exposes __fight and __simulate on globalThis.
|
|
7
|
+
*
|
|
8
|
+
* The prototype freeze banner runs before any module code, preventing
|
|
9
|
+
* prototype pollution attacks between bots sharing the same isolate.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* JavaScript code injected as esbuild banner — runs before the IIFE bundle.
|
|
14
|
+
*
|
|
15
|
+
* 1. Freezes all built-in prototypes to prevent cross-bot sabotage via prototype
|
|
16
|
+
* pollution. This does NOT prevent calling existing methods (e.g. Array.push
|
|
17
|
+
* still works), it only prevents reassigning them.
|
|
18
|
+
*
|
|
19
|
+
* 2. Blocks all IO/network capabilities. Web Workers have fetch, XMLHttpRequest,
|
|
20
|
+
* WebSocket, importScripts, etc. Bot code must not be able to make network
|
|
21
|
+
* calls or load external scripts. Uses Object.defineProperty to make the block
|
|
22
|
+
* irrecoverable (non-writable, non-configurable). In isolated-vm, these globals
|
|
23
|
+
* don't exist — the try-catch makes the deletes a harmless no-op.
|
|
24
|
+
*/
|
|
25
|
+
export const PROTOTYPE_FREEZE_BANNER = `
|
|
26
|
+
Object.freeze(Object.prototype);
|
|
27
|
+
Object.freeze(Array.prototype);
|
|
28
|
+
Object.freeze(Function.prototype);
|
|
29
|
+
Object.freeze(String.prototype);
|
|
30
|
+
Object.freeze(Number.prototype);
|
|
31
|
+
Object.freeze(Boolean.prototype);
|
|
32
|
+
Object.freeze(RegExp.prototype);
|
|
33
|
+
Object.freeze(Date.prototype);
|
|
34
|
+
Object.freeze(Error.prototype);
|
|
35
|
+
Object.freeze(Map.prototype);
|
|
36
|
+
Object.freeze(Set.prototype);
|
|
37
|
+
Object.freeze(Math);
|
|
38
|
+
Object.freeze(JSON);
|
|
39
|
+
(function() {
|
|
40
|
+
var g = typeof globalThis !== 'undefined' ? globalThis : typeof self !== 'undefined' ? self : {};
|
|
41
|
+
var blocked = [
|
|
42
|
+
'fetch', 'XMLHttpRequest', 'WebSocket', 'EventSource',
|
|
43
|
+
'importScripts', 'Worker', 'SharedWorker',
|
|
44
|
+
'Request', 'Response', 'Headers',
|
|
45
|
+
'navigator', 'BroadcastChannel',
|
|
46
|
+
'indexedDB', 'caches'
|
|
47
|
+
];
|
|
48
|
+
for (var i = 0; i < blocked.length; i++) {
|
|
49
|
+
try { Object.defineProperty(g, blocked[i], {value: undefined, writable: false, configurable: false}); }
|
|
50
|
+
catch(e) {}
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generate the TypeScript entry point for a match sandbox.
|
|
57
|
+
*
|
|
58
|
+
* This entry point imports both bots and the simulation engine, then
|
|
59
|
+
* exposes __fight and __simulate on globalThis. esbuild bundles this
|
|
60
|
+
* + all transitive imports into a single self-contained IIFE.
|
|
61
|
+
*
|
|
62
|
+
* @param bot1SourcePath - Absolute path to bot 1's TypeScript source file
|
|
63
|
+
* @param bot1ExportName - Named export of bot 1's WizardFunction
|
|
64
|
+
* @param bot2SourcePath - Absolute path to bot 2's TypeScript source file
|
|
65
|
+
* @param bot2ExportName - Named export of bot 2's WizardFunction
|
|
66
|
+
* @param engineDir - Absolute path to the engine directory (src/engine/)
|
|
67
|
+
*/
|
|
68
|
+
export function generateMatchEntryPoint(
|
|
69
|
+
bot1SourcePath: string,
|
|
70
|
+
bot1ExportName: string,
|
|
71
|
+
bot2SourcePath: string,
|
|
72
|
+
bot2ExportName: string,
|
|
73
|
+
engineDir: string,
|
|
74
|
+
): string
|
|
75
|
+
{
|
|
76
|
+
// Use forward slashes for esbuild compatibility (works on all platforms)
|
|
77
|
+
const enginePath = engineDir.replace(/\\/g, '/');
|
|
78
|
+
const bot1Path = bot1SourcePath.replace(/\\/g, '/');
|
|
79
|
+
const bot2Path = bot2SourcePath.replace(/\\/g, '/');
|
|
80
|
+
|
|
81
|
+
// All imports use the engineDir's parent (= packages/core/src/) as root.
|
|
82
|
+
// When user bots alias @vibemancer/core → src/index-browser.ts, esbuild
|
|
83
|
+
// deduplicates these with the bot's imports since they resolve to the same files.
|
|
84
|
+
// This ensures the hooks runtime global state is shared between harness and bot.
|
|
85
|
+
const srcPath = enginePath.replace(/\/engine\/?$/, '');
|
|
86
|
+
|
|
87
|
+
return `
|
|
88
|
+
import {${bot1ExportName} as __Bot1} from '${bot1Path}';
|
|
89
|
+
import {${bot2ExportName} as __Bot2} from '${bot2Path}';
|
|
90
|
+
import {fight, simulate} from '${srcPath}/engine/simulation.ts';
|
|
91
|
+
import {wrapWithParams} from '${srcPath}/engine/params-runtime.ts';
|
|
92
|
+
|
|
93
|
+
globalThis.__fight = function __fight(options) {
|
|
94
|
+
const result = fight(__Bot1, __Bot2, options);
|
|
95
|
+
return result;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
globalThis.__simulate = function __simulate(options) {
|
|
99
|
+
const {params1, params2, ...simOptions} = options;
|
|
100
|
+
const bot1 = params1 ? wrapWithParams(__Bot1, params1) : __Bot1;
|
|
101
|
+
const bot2 = params2 ? wrapWithParams(__Bot2, params2) : __Bot2;
|
|
102
|
+
const result = simulate(bot1, bot2, simOptions);
|
|
103
|
+
return result;
|
|
104
|
+
};
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Generate the entry point for a Manual Play sandbox bundle.
|
|
110
|
+
*
|
|
111
|
+
* Unlike the fight/simulate entry point (which exposes one-shot batch APIs),
|
|
112
|
+
* the manual-match entry point holds a single long-lived ManualMatch instance
|
|
113
|
+
* inside the worker and exposes per-tick step/rewind/guide APIs.
|
|
114
|
+
*
|
|
115
|
+
* The "player" wizard is a worker-local stub that returns whatever
|
|
116
|
+
* `__latestHumanActions` is set to — this avoids the can't-postMessage-functions
|
|
117
|
+
* problem (the function lives entirely worker-side, only data crosses the
|
|
118
|
+
* boundary). Guided missiles use a similar pattern via
|
|
119
|
+
* `__latestHumanMissileTargets[projectileId]`.
|
|
120
|
+
*
|
|
121
|
+
* @param opponentSourcePath - Absolute path to opponent bot's TypeScript source
|
|
122
|
+
* @param opponentExportName - Named export of the opponent's WizardFunction
|
|
123
|
+
* @param engineDir - Absolute path to src/engine/
|
|
124
|
+
*/
|
|
125
|
+
export function generateManualMatchEntryPoint(
|
|
126
|
+
opponentSourcePath: string,
|
|
127
|
+
opponentExportName: string,
|
|
128
|
+
engineDir: string,
|
|
129
|
+
): string
|
|
130
|
+
{
|
|
131
|
+
const opponentPath = opponentSourcePath.replace(/\\/g, '/');
|
|
132
|
+
return generateManualMatchEntryPointInner(
|
|
133
|
+
`import {${opponentExportName} as __RawOpponent} from '${opponentPath}';`,
|
|
134
|
+
engineDir,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Browser-friendly variant — the opponent is injected at runtime via
|
|
140
|
+
* globalThis.__injectedBot1 (set by prepending the player's bot bundle to
|
|
141
|
+
* the compiled output of this template). Used by the web client for manual
|
|
142
|
+
* play against uploaded wizards.
|
|
143
|
+
*/
|
|
144
|
+
export function generateBrowserManualMatchEntryPoint(engineDir: string): string
|
|
145
|
+
{
|
|
146
|
+
const opponentImport = 'var __RawOpponent = globalThis.__injectedBot1;\n'
|
|
147
|
+
+ 'if (!__RawOpponent) throw new Error("Opponent not injected (set globalThis.__injectedBot1)");';
|
|
148
|
+
return generateManualMatchEntryPointInner(opponentImport, engineDir);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function generateManualMatchEntryPointInner(opponentImport: string, engineDir: string): string
|
|
152
|
+
{
|
|
153
|
+
const enginePath = engineDir.replace(/\\/g, '/');
|
|
154
|
+
const srcPath = enginePath.replace(/\/engine\/?$/, '');
|
|
155
|
+
|
|
156
|
+
return `
|
|
157
|
+
${opponentImport}
|
|
158
|
+
import {ManualMatch} from '${srcPath}/engine/manual-match.ts';
|
|
159
|
+
import {idle, turnToward, flyStraight} from '${srcPath}/hooks/action-builders.ts';
|
|
160
|
+
import {getMissileContext} from '${srcPath}/engine/hooks-runtime.ts';
|
|
161
|
+
|
|
162
|
+
// Worker-local state — the player AI and guided-missile AIs read from these.
|
|
163
|
+
var __latestHumanActions = null;
|
|
164
|
+
var __latestHumanMissileTargets = {};
|
|
165
|
+
var __manualMatch = null;
|
|
166
|
+
|
|
167
|
+
function __playerStub() {
|
|
168
|
+
if (__latestHumanActions) {
|
|
169
|
+
return {_toAction: function() { return __latestHumanActions; }};
|
|
170
|
+
}
|
|
171
|
+
return idle();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function __makeGuideStub(projectileId) {
|
|
175
|
+
return function() {
|
|
176
|
+
var target = __latestHumanMissileTargets[projectileId];
|
|
177
|
+
if (!target) return flyStraight();
|
|
178
|
+
return turnToward(target.x, target.y);
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function __ensureMatch() {
|
|
183
|
+
if (!__manualMatch) throw new Error('ManualMatch not initialized — call manualMatchInit first');
|
|
184
|
+
return __manualMatch;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
globalThis.__manualMatchInit = function(options) {
|
|
188
|
+
if (__manualMatch) {
|
|
189
|
+
__manualMatch.dispose();
|
|
190
|
+
__manualMatch = null;
|
|
191
|
+
}
|
|
192
|
+
__latestHumanActions = (options && options.initialHumanActions) || null;
|
|
193
|
+
__latestHumanMissileTargets = {};
|
|
194
|
+
__manualMatch = new ManualMatch(__playerStub, __RawOpponent, options || {});
|
|
195
|
+
return __manualMatch.getGameState();
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
globalThis.__manualMatchStep = function(options) {
|
|
199
|
+
var match = __ensureMatch();
|
|
200
|
+
if (options) {
|
|
201
|
+
if (options.humanActions) __latestHumanActions = options.humanActions;
|
|
202
|
+
if (options.humanMissileTargets) {
|
|
203
|
+
// Merge — the host may only update specific projectiles per call
|
|
204
|
+
for (var k in options.humanMissileTargets) {
|
|
205
|
+
__latestHumanMissileTargets[k] = options.humanMissileTargets[k];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
var count = (options && options.count) || 1;
|
|
210
|
+
return match.step(count);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
globalThis.__manualMatchGuide = function(options) {
|
|
214
|
+
var match = __ensureMatch();
|
|
215
|
+
var id = options && options.projectileId;
|
|
216
|
+
if (!id) return {ok: false, reason: 'missing projectileId'};
|
|
217
|
+
match.replaceMissileAI(id, __makeGuideStub(id));
|
|
218
|
+
return {ok: true};
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
globalThis.__manualMatchRelease = function(options) {
|
|
222
|
+
var match = __ensureMatch();
|
|
223
|
+
var id = options && options.projectileId;
|
|
224
|
+
if (!id) return {ok: false, reason: 'missing projectileId'};
|
|
225
|
+
match.restoreMissileAI(id);
|
|
226
|
+
delete __latestHumanMissileTargets[id];
|
|
227
|
+
return {ok: true};
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
globalThis.__manualMatchSetInvincible = function(options) {
|
|
231
|
+
var match = __ensureMatch();
|
|
232
|
+
var idx = (options && options.wizardIndex) || 0;
|
|
233
|
+
var on = !!(options && options.on);
|
|
234
|
+
match.setInvincible(idx, on);
|
|
235
|
+
return {ok: true};
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
globalThis.__manualMatchGetState = function() {
|
|
239
|
+
var match = __ensureMatch();
|
|
240
|
+
return match.getGameState();
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
globalThis.__manualMatchGetResult = function() {
|
|
244
|
+
var match = __ensureMatch();
|
|
245
|
+
return match.getResult();
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
globalThis.__manualMatchDispose = function() {
|
|
249
|
+
if (__manualMatch) {
|
|
250
|
+
__manualMatch.dispose();
|
|
251
|
+
__manualMatch = null;
|
|
252
|
+
}
|
|
253
|
+
__latestHumanActions = {move: {x: 0, y: 0}};
|
|
254
|
+
__latestHumanMissileTargets = {};
|
|
255
|
+
return {ok: true};
|
|
256
|
+
};
|
|
257
|
+
`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Generate an alternate entry point where __fight/__simulate accept and return
|
|
262
|
+
* JSON strings instead of structured objects. Used by the benchmark to compare
|
|
263
|
+
* JSON serialization vs V8 structured clone performance.
|
|
264
|
+
*/
|
|
265
|
+
export function generateMatchEntryPointJSON(
|
|
266
|
+
bot1SourcePath: string,
|
|
267
|
+
bot1ExportName: string,
|
|
268
|
+
bot2SourcePath: string,
|
|
269
|
+
bot2ExportName: string,
|
|
270
|
+
engineDir: string,
|
|
271
|
+
): string
|
|
272
|
+
{
|
|
273
|
+
const enginePath = engineDir.replace(/\\/g, '/');
|
|
274
|
+
const bot1Path = bot1SourcePath.replace(/\\/g, '/');
|
|
275
|
+
const bot2Path = bot2SourcePath.replace(/\\/g, '/');
|
|
276
|
+
const srcPath = enginePath.replace(/\/engine\/?$/, '');
|
|
277
|
+
|
|
278
|
+
return `
|
|
279
|
+
import {${bot1ExportName} as __Bot1} from '${bot1Path}';
|
|
280
|
+
import {${bot2ExportName} as __Bot2} from '${bot2Path}';
|
|
281
|
+
import {fight, simulate} from '${srcPath}/engine/simulation.ts';
|
|
282
|
+
import {wrapWithParams} from '${srcPath}/engine/params-runtime.ts';
|
|
283
|
+
|
|
284
|
+
globalThis.__fightJSON = function __fightJSON(optionsJSON) {
|
|
285
|
+
const options = JSON.parse(optionsJSON);
|
|
286
|
+
const result = fight(__Bot1, __Bot2, options);
|
|
287
|
+
return JSON.stringify(result);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
globalThis.__simulateJSON = function __simulateJSON(optionsJSON) {
|
|
291
|
+
const options = JSON.parse(optionsJSON);
|
|
292
|
+
const {params1, params2, ...simOptions} = options;
|
|
293
|
+
const bot1 = params1 ? wrapWithParams(__Bot1, params1) : __Bot1;
|
|
294
|
+
const bot2 = params2 ? wrapWithParams(__Bot2, params2) : __Bot2;
|
|
295
|
+
const result = simulate(bot1, bot2, simOptions);
|
|
296
|
+
return JSON.stringify(result);
|
|
297
|
+
};
|
|
298
|
+
`;
|
|
299
|
+
}
|