@series-inc/rundot-kinetix 0.0.0-bootstrap.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.
- package/README.md +77 -0
- package/authoring.d.ts +30 -0
- package/index.d.ts +221 -0
- package/native/component_runtime.cpp +341 -0
- package/native/component_runtime.hpp +112 -0
- package/native/deterministic_math.cpp +594 -0
- package/native/deterministic_math.hpp +21 -0
- package/native/kinetix-f64-native-profile.cpp +406 -0
- package/native/kinetix-f64-native-profile.hpp +5 -0
- package/native/kinetix-fixed1000-native-profile.cpp +777 -0
- package/native/kinetix-fixed1000-native-profile.hpp +58 -0
- package/native/kinetix-fixed1000-physics.cpp +887 -0
- package/native/kinetix-fixed1000-physics.hpp +28 -0
- package/native/kinetix-installed-f64-renderer.cpp +344 -0
- package/native/kinetix-installed-f64-renderer.hpp +35 -0
- package/native/kinetix-installed-f64-runtime.cpp +1085 -0
- package/native/kinetix-installed-f64-runtime.hpp +141 -0
- package/native/kinetix-installed-fixed1000-data.hpp +77 -0
- package/native/kinetix-native-main.cpp +37 -0
- package/native/kinetix-native-runtime.cpp +20 -0
- package/native/kinetix-native-runtime.hpp +25 -0
- package/native/kinetix-render-projection.cpp +20 -0
- package/native/kinetix-render-projection.hpp +14 -0
- package/package.json +65 -0
- package/runtime.d.ts +76 -0
- package/scripts/build-native.mjs +67 -0
- package/scripts/emit-product-digests.mjs +33 -0
- package/scripts/preflight.mjs +76 -0
- package/src/index.mjs +57 -0
- package/src/kinetix-authoring.mjs +69 -0
- package/src/kinetix-baker.mjs +587 -0
- package/src/kinetix-deterministic-math.mjs +1044 -0
- package/src/kinetix-fixed1000-runtime-adapter.mjs +33 -0
- package/src/kinetix-fixed1000-runtime.mjs +954 -0
- package/src/kinetix-installed-f64-reference.mjs +157 -0
- package/src/kinetix-installed-f64-render-frames.mjs +53 -0
- package/src/kinetix-installed-f64-renderer.mjs +240 -0
- package/src/kinetix-installed-f64-runtime-adapter.mjs +68 -0
- package/src/kinetix-installed-f64-runtime.mjs +607 -0
- package/src/kinetix-installed-mechanics.mjs +377 -0
- package/src/kinetix-installed-systems.mjs +181 -0
- package/src/kinetix-native-product.mjs +72 -0
- package/src/kinetix-product-contract.mjs +121 -0
- package/src/kinetix-project-compiler.mjs +1017 -0
- package/src/kinetix-render-projection.mjs +28 -0
- package/src/kinetix-runtime-adapter-utils.mjs +168 -0
- package/src/kinetix-runtime-contract.mjs +54 -0
- package/src/kinetix-session-config.mjs +170 -0
- package/src/kinetix-source-snapshot.mjs +24 -0
- package/src/kinetix-survival-runtime-adapter.mjs +305 -0
- package/src/kinetix-survival-runtime.generated.mjs +1 -0
- package/src/kinetix-world-kernel.mjs +580 -0
- package/src/kinetix-world-runtime.mjs +171 -0
- package/src/runtime.mjs +14 -0
- package/src/shared/f64-bits.mjs +14 -0
- package/src/shared/kinetix-envelope-v1.mjs +589 -0
- package/src/shared/render-records-v1.mjs +168 -0
- package/src/shared/sha256.mjs +73 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { InstalledSurvivalRuntime } from './kinetix-survival-runtime.generated.mjs';
|
|
2
|
+
import {
|
|
3
|
+
decodeRuntimeCheckpoint,
|
|
4
|
+
encodeRuntimeCheckpoint,
|
|
5
|
+
} from './kinetix-runtime-adapter-utils.mjs';
|
|
6
|
+
import { assertKinetixRuntimeIdentity } from './kinetix-runtime-contract.mjs';
|
|
7
|
+
import {
|
|
8
|
+
decodeKinetixSessionConfig,
|
|
9
|
+
kinetixSessionConfigDigest,
|
|
10
|
+
} from './kinetix-session-config.mjs';
|
|
11
|
+
import { sha256Hex } from './shared/sha256.mjs';
|
|
12
|
+
|
|
13
|
+
const ANCHOR_INTERVAL_FRAMES = 32;
|
|
14
|
+
const RETAINED_ROLLBACK_FRAMES = 512;
|
|
15
|
+
const INPUT_STRIDE = 6;
|
|
16
|
+
|
|
17
|
+
const eventTypes = [
|
|
18
|
+
'enemy.killed',
|
|
19
|
+
'bomb.activated',
|
|
20
|
+
'bomb.pickup',
|
|
21
|
+
'gate.passed',
|
|
22
|
+
'bullet.wallImpact',
|
|
23
|
+
'bullet.blocked',
|
|
24
|
+
'weapon.fireStateChanged',
|
|
25
|
+
'ship.hit',
|
|
26
|
+
'maw.spawned',
|
|
27
|
+
'multiplier.changed',
|
|
28
|
+
'score.changed',
|
|
29
|
+
'run.ended',
|
|
30
|
+
'enemy.spawned',
|
|
31
|
+
'spark.collected',
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const neutralInput = Object.freeze({
|
|
35
|
+
movementVector: Object.freeze({ x: 0, y: 0 }),
|
|
36
|
+
aimVector: Object.freeze({ x: 0, y: 0 }),
|
|
37
|
+
fireDown: false,
|
|
38
|
+
bombPressed: false,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function fail(code) {
|
|
42
|
+
throw new Error(code);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function validAxis(axis) {
|
|
46
|
+
return typeof axis === 'object' && axis !== null && !Array.isArray(axis)
|
|
47
|
+
&& Reflect.ownKeys(axis).length === 2
|
|
48
|
+
&& Number.isFinite(axis.x) && axis.x >= -1 && axis.x <= 1
|
|
49
|
+
&& Number.isFinite(axis.y) && axis.y >= -1 && axis.y <= 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function decodeInput(input) {
|
|
53
|
+
if (input === null || input === undefined) return neutralInput;
|
|
54
|
+
if (typeof input !== 'object' || Array.isArray(input)
|
|
55
|
+
|| Reflect.ownKeys(input).length !== 4
|
|
56
|
+
|| !validAxis(input.movementVector) || !validAxis(input.aimVector)
|
|
57
|
+
|| typeof input.fireDown !== 'boolean' || typeof input.bombPressed !== 'boolean') {
|
|
58
|
+
fail('KINETIX_SURVIVAL_INPUT_INVALID');
|
|
59
|
+
}
|
|
60
|
+
return input;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function packInputs(inputs) {
|
|
64
|
+
const packed = new Array(inputs.length * INPUT_STRIDE);
|
|
65
|
+
for (let slot = 0; slot < inputs.length; slot += 1) {
|
|
66
|
+
const input = inputs[slot];
|
|
67
|
+
const offset = slot * INPUT_STRIDE;
|
|
68
|
+
packed[offset] = input.movementVector.x;
|
|
69
|
+
packed[offset + 1] = input.movementVector.y;
|
|
70
|
+
packed[offset + 2] = input.aimVector.x;
|
|
71
|
+
packed[offset + 3] = input.aimVector.y;
|
|
72
|
+
packed[offset + 4] = input.fireDown ? 1 : 0;
|
|
73
|
+
packed[offset + 5] = input.bombPressed ? 1 : 0;
|
|
74
|
+
}
|
|
75
|
+
return packed;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function unpackInputs(packed) {
|
|
79
|
+
const inputs = new Array(packed.length / INPUT_STRIDE);
|
|
80
|
+
for (let slot = 0; slot < inputs.length; slot += 1) {
|
|
81
|
+
const offset = slot * INPUT_STRIDE;
|
|
82
|
+
inputs[slot] = {
|
|
83
|
+
movementVector: { x: packed[offset], y: packed[offset + 1] },
|
|
84
|
+
aimVector: { x: packed[offset + 2], y: packed[offset + 3] },
|
|
85
|
+
fireDown: packed[offset + 4] === 1,
|
|
86
|
+
bombPressed: packed[offset + 5] === 1,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return inputs;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function sameBytes(left, right) {
|
|
93
|
+
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function createInstalledSurvivalRuntime({
|
|
97
|
+
pack,
|
|
98
|
+
identity,
|
|
99
|
+
sessionConfigSchema,
|
|
100
|
+
sessionConfigBytes,
|
|
101
|
+
}) {
|
|
102
|
+
if (pack?.kind !== 'survival-arena' || pack.version !== 1) {
|
|
103
|
+
fail('KINETIX_SURVIVAL_PACK_INVALID');
|
|
104
|
+
}
|
|
105
|
+
assertKinetixRuntimeIdentity(identity);
|
|
106
|
+
const session = decodeKinetixSessionConfig(sessionConfigSchema, sessionConfigBytes);
|
|
107
|
+
const engine = new InstalledSurvivalRuntime({
|
|
108
|
+
playerCount: session.playerCount,
|
|
109
|
+
transitionEngine: true,
|
|
110
|
+
});
|
|
111
|
+
const checkpoints = new WeakMap();
|
|
112
|
+
const anchors = new Map();
|
|
113
|
+
const inputFrames = new Map();
|
|
114
|
+
let frame = 0;
|
|
115
|
+
let frameEvents = [];
|
|
116
|
+
let currentCheckpoint;
|
|
117
|
+
|
|
118
|
+
for (const type of eventTypes) {
|
|
119
|
+
engine.events.on(type, (payload) => frameEvents.push({ type, payload }));
|
|
120
|
+
}
|
|
121
|
+
engine.configureDeterministic(session.seed, session.playerCount);
|
|
122
|
+
engine.start(session.modeId);
|
|
123
|
+
frameEvents = [];
|
|
124
|
+
|
|
125
|
+
function liveState() {
|
|
126
|
+
return {
|
|
127
|
+
frame,
|
|
128
|
+
state: engine.serialize(),
|
|
129
|
+
events: structuredClone(frameEvents),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function loadState(checkpointState) {
|
|
134
|
+
if (checkpointState?.frame !== frameOf(checkpointState)) fail('KINETIX_RUNTIME_CHECKPOINT_INVALID');
|
|
135
|
+
frame = checkpointState.frame;
|
|
136
|
+
frameEvents = structuredClone(checkpointState.events);
|
|
137
|
+
engine.hydrate(structuredClone(checkpointState.state));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function frameOf(checkpointState) {
|
|
141
|
+
return Number.isSafeInteger(checkpointState?.frame) && checkpointState.frame >= 0
|
|
142
|
+
? checkpointState.frame
|
|
143
|
+
: -1;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function makeCheckpoint(selectedFrame, state = null) {
|
|
147
|
+
if (!Number.isSafeInteger(selectedFrame) || selectedFrame < 0) fail('KINETIX_RUNTIME_FRAME_MISMATCH');
|
|
148
|
+
const checkpoint = Object.freeze({ frame: selectedFrame });
|
|
149
|
+
checkpoints.set(checkpoint, { frame: selectedFrame, state, serialized: null });
|
|
150
|
+
return checkpoint;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function checkpointData(checkpoint) {
|
|
154
|
+
const selected = typeof checkpoint === 'object' && checkpoint !== null
|
|
155
|
+
? checkpoints.get(checkpoint)
|
|
156
|
+
: undefined;
|
|
157
|
+
if (!selected) fail('KINETIX_RUNTIME_CHECKPOINT_INVALID');
|
|
158
|
+
return selected;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function rebuild(targetFrame) {
|
|
162
|
+
let selectedAnchor = -1;
|
|
163
|
+
for (const candidate of anchors.keys()) {
|
|
164
|
+
if (candidate <= targetFrame && candidate > selectedAnchor) selectedAnchor = candidate;
|
|
165
|
+
}
|
|
166
|
+
const anchor = anchors.get(selectedAnchor);
|
|
167
|
+
if (!anchor) fail('KINETIX_RUNTIME_CHECKPOINT_INVALID');
|
|
168
|
+
loadState(anchor);
|
|
169
|
+
for (let next = selectedAnchor + 1; next <= targetFrame; next += 1) {
|
|
170
|
+
const packedInputs = inputFrames.get(next);
|
|
171
|
+
if (!packedInputs) fail('KINETIX_RUNTIME_CHECKPOINT_INVALID');
|
|
172
|
+
frameEvents = [];
|
|
173
|
+
engine.tick(1 / identity.tickRate, unpackInputs(packedInputs));
|
|
174
|
+
frame = next;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function materialize(checkpoint) {
|
|
179
|
+
const selected = checkpointData(checkpoint);
|
|
180
|
+
if (selected.state) return structuredClone(selected.state);
|
|
181
|
+
if (selected.frame === frame) return liveState();
|
|
182
|
+
const saved = liveState();
|
|
183
|
+
rebuild(selected.frame);
|
|
184
|
+
const result = liveState();
|
|
185
|
+
loadState(saved);
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function serialized(checkpoint) {
|
|
190
|
+
const selected = checkpointData(checkpoint);
|
|
191
|
+
selected.serialized ??= encodeRuntimeCheckpoint(materialize(checkpoint));
|
|
192
|
+
return selected.serialized;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function pruneHistory() {
|
|
196
|
+
const expiredFrame = frame - RETAINED_ROLLBACK_FRAMES - ANCHOR_INTERVAL_FRAMES;
|
|
197
|
+
anchors.delete(expiredFrame);
|
|
198
|
+
inputFrames.delete(expiredFrame);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function discardFuture(targetFrame) {
|
|
202
|
+
for (const anchorFrame of anchors.keys()) {
|
|
203
|
+
if (anchorFrame > targetFrame) anchors.delete(anchorFrame);
|
|
204
|
+
}
|
|
205
|
+
for (const inputFrame of inputFrames.keys()) {
|
|
206
|
+
if (inputFrame > targetFrame) inputFrames.delete(inputFrame);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
anchors.set(0, liveState());
|
|
211
|
+
currentCheckpoint = makeCheckpoint(0);
|
|
212
|
+
const digest = kinetixSessionConfigDigest(sessionConfigBytes);
|
|
213
|
+
|
|
214
|
+
const runtime = {
|
|
215
|
+
identity: Object.freeze(structuredClone(identity)),
|
|
216
|
+
sessionConfigDigest: digest,
|
|
217
|
+
get currentFrame() {
|
|
218
|
+
return frame;
|
|
219
|
+
},
|
|
220
|
+
sessionConfigBytes() {
|
|
221
|
+
return sessionConfigBytes.slice();
|
|
222
|
+
},
|
|
223
|
+
capture() {
|
|
224
|
+
return makeCheckpoint(frame);
|
|
225
|
+
},
|
|
226
|
+
advance(requests) {
|
|
227
|
+
if (!Array.isArray(requests)) fail('KINETIX_RUNTIME_BATCH_INVALID');
|
|
228
|
+
let expected = frame + 1;
|
|
229
|
+
const decodedRequests = requests.map((request) => {
|
|
230
|
+
if (typeof request !== 'object' || request === null || request.frame !== expected
|
|
231
|
+
|| !Array.isArray(request.inputs) || !Array.isArray(request.commands)
|
|
232
|
+
|| request.commands.length !== 0 || typeof request.checksum !== 'boolean') {
|
|
233
|
+
fail('KINETIX_RUNTIME_FRAME_MISMATCH');
|
|
234
|
+
}
|
|
235
|
+
expected += 1;
|
|
236
|
+
const inputs = request.inputs.map(decodeInput);
|
|
237
|
+
return {
|
|
238
|
+
request,
|
|
239
|
+
inputs,
|
|
240
|
+
packedInputs: packInputs(inputs),
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
const startingFrame = frame;
|
|
244
|
+
const results = [];
|
|
245
|
+
try {
|
|
246
|
+
for (const { request, inputs, packedInputs } of decodedRequests) {
|
|
247
|
+
frameEvents = [];
|
|
248
|
+
engine.tick(1 / identity.tickRate, inputs);
|
|
249
|
+
frame = request.frame;
|
|
250
|
+
inputFrames.set(frame, packedInputs);
|
|
251
|
+
if (frame % ANCHOR_INTERVAL_FRAMES === 0) anchors.set(frame, liveState());
|
|
252
|
+
currentCheckpoint = makeCheckpoint(frame);
|
|
253
|
+
const result = { frame, checkpoint: currentCheckpoint };
|
|
254
|
+
if (request.checksum) result.checksum = this.checksum(currentCheckpoint);
|
|
255
|
+
results.push(result);
|
|
256
|
+
pruneHistory();
|
|
257
|
+
}
|
|
258
|
+
return results;
|
|
259
|
+
} catch (error) {
|
|
260
|
+
discardFuture(startingFrame);
|
|
261
|
+
rebuild(startingFrame);
|
|
262
|
+
currentCheckpoint = makeCheckpoint(startingFrame);
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
restore(checkpoint) {
|
|
267
|
+
const selected = checkpointData(checkpoint);
|
|
268
|
+
if (selected.state) {
|
|
269
|
+
loadState(selected.state);
|
|
270
|
+
anchors.set(frame, structuredClone(selected.state));
|
|
271
|
+
} else if (selected.frame !== frame) {
|
|
272
|
+
rebuild(selected.frame);
|
|
273
|
+
}
|
|
274
|
+
discardFuture(selected.frame);
|
|
275
|
+
currentCheckpoint = makeCheckpoint(selected.frame);
|
|
276
|
+
},
|
|
277
|
+
inspect(checkpoint) {
|
|
278
|
+
const checkpointState = materialize(checkpoint);
|
|
279
|
+
return structuredClone({
|
|
280
|
+
...checkpointState.state,
|
|
281
|
+
frameEvents: checkpointState.events,
|
|
282
|
+
});
|
|
283
|
+
},
|
|
284
|
+
serializeCheckpoint(checkpoint) {
|
|
285
|
+
return serialized(checkpoint).slice();
|
|
286
|
+
},
|
|
287
|
+
hydrateCheckpoint(selectedFrame, bytes) {
|
|
288
|
+
const state = decodeRuntimeCheckpoint(bytes);
|
|
289
|
+
if (frameOf(state) !== selectedFrame) fail('KINETIX_RUNTIME_FRAME_MISMATCH');
|
|
290
|
+
const canonical = encodeRuntimeCheckpoint(state);
|
|
291
|
+
if (!sameBytes(canonical, Uint8Array.from(bytes))) fail('KINETIX_RUNTIME_CHECKPOINT_INVALID');
|
|
292
|
+
return makeCheckpoint(selectedFrame, state);
|
|
293
|
+
},
|
|
294
|
+
checksum(checkpoint) {
|
|
295
|
+
return sha256Hex(serialized(checkpoint));
|
|
296
|
+
},
|
|
297
|
+
wireChecksum(checkpoint) {
|
|
298
|
+
return sha256Hex(serialized(checkpoint));
|
|
299
|
+
},
|
|
300
|
+
release(checkpoint) {
|
|
301
|
+
if (checkpoint !== currentCheckpoint) checkpoints.delete(checkpoint);
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
return Object.freeze(runtime);
|
|
305
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var xe={intensity:.1,duration:.3,decay:.9};function le(s,e,t){let i=s+e*100+t,n=Math.floor(i),r=i-n,o=m=>{let u=Math.sin(m*127.1+311.7)*43758.5453;return(u-Math.floor(u))*2-1},a=o(n),h=o(n+1),l=(1-Math.cos(r*Math.PI))*.5;return a+(h-a)*l}function nt(s){let e=s+1831565813|0;return e=Math.imul(e^e>>>15,e|1),e^=e+Math.imul(e^e>>>7,e|61),((e^e>>>14)>>>0)/4294967296}function st(s,e){let t=e.permanent??!1;if(!t&&s>=e.duration)return{x:0,y:0,z:0,isActive:!1};let i=t?0:s/e.duration,n=e.frequency??30,r=e.frequencyPerAxis?.x??n,o=e.frequencyPerAxis?.y??(e.frequencyPerAxis?e.frequencyPerAxis.y:n*1.3),a=e.frequencyPerAxis?.z??(e.frequencyPerAxis?e.frequencyPerAxis.z:n*.7),h=e.amplitude??{x:1,y:1,z:0},l;t?l=e.intensity:e.attenuationCurve?l=e.attenuationCurve(i)*e.intensity:l=e.intensity*Math.pow(e.decay,s*60);let m,u,p;if(e.noiseMode){let f=e.noiseSeed??42;m=le(s*r,f,0)*l*h.x,u=le(s*o,f,100)*l*h.y,p=le(s*a,f,200)*l*h.z}else m=Math.sin(s*r*Math.PI*2)*l*h.x,u=Math.cos(s*o*Math.PI*2)*l*h.y,p=Math.sin(s*a*Math.PI*2)*l*h.z;if(e.direction){let f=e.direction,y=e.directionRandomness??0,b=Math.sqrt(m*m+u*u+p*p);if(b>0){let g=Math.sqrt(f.x*f.x+f.y*f.y+f.z*f.z);if(g>0){let v=f.x/g,d=f.y/g,x=f.z/g,E={x:v*b,y:d*b,z:x*b};m=E.x*(1-y)+m*y,u=E.y*(1-y)+u*y,p=E.z*(1-y)+p*y}}}return{x:m||0,y:u||0,z:p||0,isActive:t||i<1}}var z=class{config;elapsed=0;active=!1;clock=0;lastTriggerClock=-1/0;triggerCount=0;constructor(e={}){this.config={...xe,...e}}trigger(e,t){if(e&&(this.config={...this.config,...e}),!(this.config.interruptible===!1&&this.active)&&!(this.config.cooldown&&this.config.cooldown>0&&this.triggerCount>0&&this.clock-this.lastTriggerClock<this.config.cooldown)){if(this.config.randomizeSeed&&(this.config={...this.config,noiseSeed:Math.floor(nt(Date.now()+this.triggerCount)*1e5)}),t!==void 0&&this.config.maxRange!==void 0&&this.config.maxRange>0){if(t>=this.config.maxRange)return;let i=t/this.config.maxRange,n=this.config.rangeFalloff?this.config.rangeFalloff(i):1-i;this.config={...this.config,intensity:this.config.intensity*Math.max(0,n)}}this.elapsed=0,this.active=!0,this.lastTriggerClock=this.clock,this.triggerCount++}}update(e){return this.clock+=e,this.active?(this.elapsed+=e,!(this.config.permanent??!1)&&this.elapsed>=this.config.duration?(this.active=!1,{x:0,y:0,z:0,isActive:!1}):st(this.elapsed,this.config)):{x:0,y:0,z:0,isActive:!1}}stop(){this.active=!1,this.elapsed=0}isActive(){return this.active}};function Se(){let s=1,e=!1,t=0,i=!1,n=0,r=0,o=1,a=1,h=b=>b,l=1,m="shake",u=1;function p(b){s=b,i=!1}function f(b,g,v,d){i=!0,n=0,r=v,o=b,a=g,h=d}function y(b,g,v,d){let x=Math.abs(g-b);if(x===0||v<=0){p(g);return}let E=x/v*1e3;i=!0,n=0,r=E,o=b,a=g,h=d}return{trigger(b){m=b.mode??"shake";let g=b.lerpEasing??(x=>x),v=b.lerpDuration??0,d=b.lerpSpeed;if(m==="reset"){d!==void 0&&d>0?y(s,1,d,g):v>0?f(s,1,v,g):p(1),e=!1,t=0;return}if(m==="unfreeze"){let x=u;d!==void 0&&d>0?y(s,x,d,g):v>0?f(s,x,v,g):p(x),e=!1,t=0;return}if(u=s,m==="change"){e=!0,t=0,d!==void 0&&d>0?y(s,b.scale,d,g):v>0?f(s,b.scale,v,g):p(b.scale);return}l=u,t=b.duration??0,e=!0,d!==void 0&&d>0?y(s,b.scale,d,g):v>0?f(s,b.scale,v,g):p(b.scale)},reset(){p(1),e=!1,t=0,i=!1,u=1},update(b){let g=b*1e3;if(i)if(n+=g,n>=r)s=a,i=!1;else{let v=h(n/r);s=o+(a-o)*v}return m==="shake"&&e&&t>0&&(t-=g,t<=0&&(t=0,s=l,e=!1,i=!1)),{scale:s,active:e||i}},getScale(){return s}}}function M(s){return{id:s.id,kind:s.kind,position:{x:s.position.x,y:s.position.y},velocity:{x:s.velocity.x,y:s.velocity.y},radius:s.radius,visualRadius:s.visualRadius,hue:s.hue,telegraph:s.telegraph,alive:s.alive,dead:s.dead,killedByBomb:s.killedByBomb===!0,hp:s.hp}}var P={wanderer:100,"wanderer-shard":40,grunt:100,green\u0072unner:200,cross\u0065r:50,"snake-head":1e3,"snake-body":0,"spawn-pellet":200},Ee=[{minIngest:25,points:1e5},{minIngest:15,points:3e4},{minIngest:5,points:3e3},{minIngest:0,points:600}],c={ship:{visualRadius:14,collisionRadius:7,maxSpeedCellsPerSec:6,spawnShieldSeconds:1},projectile:{fireRatePerSec:60,fireIntervalSeconds:1/60,speedCellsPerSec:24,collisionRadius:4},multiplier:{cap:150,sparkIncrement:.01,decayWindowSeconds:1.5},bomb:{startCount:3,earnEveryScore:1e5,maxConcurrent:8,cascadeMin:96,cascadeMax:160,cascadeLifetimeMinS:.42,cascadeLifetimeMaxS:.72,screenShakePx:6,screenShakeDurationS:.4},lives:{startCount:3,earnEveryScore:75e3},spark:{magnetRadiusPx:88,collectRadiusPx:8/3,magnetSurgeDurationSeconds:2.4,magnetSurgePerSpark:.08,magnetSurgeMaxMultiplier:2.25,magnetAccelPxPerSec2:1400,maxMagnetSpeedPxPerSec:320,edgeBounceRestitution:.62,lifetimeSeconds:6,deathFadeSeconds:.55,dropsPerKillMin:6,dropsPerKillMax:8,renderCapacity:512},particles:{poolCapacity:2e3,killBurstMin:30,killBurstMax:60},death:{screenShakePx:12,screenShakeDurationS:.6,deathAnimSeconds:.4},maw:{activeRadiusCells:10,pullStrength:50,bulletsToKillMin:5,bulletsToKillMax:8,explosionPelletsMin:12,explosionPelletsMax:18},snake:{segments:8,maxSimultaneous:2},spawn:{wandererFromScore:0,cross\u0065rFromScore:0,gruntFromScore:0,green\u0052unnerFromScore:5e3,mawFromScore:3e4,snakeFromScore:5e4,postDeathSuppressionSeconds:1.1,postDeathPressureScale:.2}};var H=class{_bombs=c.bomb.startCount;_lives=c.lives.startCount;_bombsEarned=0;_livesEarned=0;get bombs(){return this._bombs}get lives(){return this._lives}onScoreChanged(e,t={}){let i=t.earnBombEveryScore??c.bomb.earnEveryScore,n=t.earnLifeEveryScore??c.lives.earnEveryScore,r=i?Math.floor(e/i):0,o=r-this._bombsEarned;o>0&&(this._bombs=Math.min(this._bombs+o,c.bomb.maxConcurrent),this._bombsEarned=r);let a=n?Math.floor(e/n):0,h=a-this._livesEarned;h>0&&(this._lives+=h,this._livesEarned=a)}consumeBomb(){return this._bombs<=0?!1:(this._bombs-=1,!0)}consumeLife(){return this._lives<=0?!0:(this._lives-=1,this._lives<=0)}reset(){this._bombs=c.bomb.startCount,this._lives=c.lives.startCount,this._bombsEarned=0,this._livesEarned=0}resetWithCounts(e){this._bombs=Math.min(Math.max(0,e.bombs),c.bomb.maxConcurrent),this._lives=Math.max(0,e.lives),this._bombsEarned=0,this._livesEarned=0}serialize(){return{bombs:this._bombs,lives:this._lives,bombsEarned:this._bombsEarned,livesEarned:this._livesEarned}}loadState(e){this._bombs=e.bombs,this._lives=e.lives,this._bombsEarned=e.bombsEarned,this._livesEarned=e.livesEarned}};var rt={fast:6,medium:3,slow:1.5};function Me(s){let e=rt[s.decayRate],t=s.durationMs/1e3;return{intensity:s.peak,duration:t,decay:1,attenuationCurve:i=>i<=0?1:i>=1?0:Math.exp(-e*i)}}var he=Me({peak:6,durationMs:400,decayRate:"fast"}),ce=Me({peak:12,durationMs:600,decayRate:"medium"});function W(s,e,t,i){let n=s.x-t.x,r=s.y-t.y,o=e+i;return n*n+r*r<=o*o}var q=2.5*40,ke=1.2,T=class s{entity;rerollTimer=ke;rng;constructor(e,t,i=Math.random){this.rng=i;let n=i()*Math.PI*2;this.entity={id:e,kind:"cross\u0065r",position:{x:t.x,y:t.y},velocity:{x:Math.cos(n)*q,y:Math.sin(n)*q},radius:6,visualRadius:10,hue:45,telegraph:.8,alive:0,dead:!1,hp:1}}update(e,t){if(!this.entity.dead){if(this.entity.telegraph>0){this.entity.telegraph=Math.max(0,this.entity.telegraph-e);return}if(this.entity.alive+=e,this.rerollTimer-=e,this.rerollTimer<=0){this.rerollTimer+=ke;let i=this.rng()*Math.PI*2;this.entity.velocity.x=Math.cos(i)*q,this.entity.velocity.y=Math.sin(i)*q}this.entity.position.x+=this.entity.velocity.x*e,this.entity.position.y+=this.entity.velocity.y*e,this.entity.position.x<this.entity.radius&&(this.entity.position.x=this.entity.radius,this.entity.velocity.x=-this.entity.velocity.x),this.entity.position.y<this.entity.radius&&(this.entity.position.y=this.entity.radius,this.entity.velocity.y=-this.entity.velocity.y),this.entity.position.x>t.width-this.entity.radius&&(this.entity.position.x=t.width-this.entity.radius,this.entity.velocity.x=-this.entity.velocity.x),this.entity.position.y>t.height-this.entity.radius&&(this.entity.position.y=t.height-this.entity.radius,this.entity.velocity.y=-this.entity.velocity.y)}}onBulletHit(e){return this.entity.telegraph>0?!1:(this.entity.dead=!0,!0)}onBombKilled(e){this.entity.dead=!0,this.entity.killedByBomb=!0}basePoints(){return P.cross\u0065r}serialize(){return{entity:M(this.entity),rerollTimer:this.rerollTimer}}static hydrate(e,t){let i=Object.create(s.prototype);return i.entity=M(e.entity),i.rerollTimer=e.rerollTimer,i.rng=t,i}};var _e=120,we=5.5*40,ot=240,at=25/180*Math.PI,lt=.24,ht=.42,I=class s{entity;dodgeTimer=0;dodgeCooldown=0;dodgeDir=1;rng;constructor(e,t,i=Math.random){this.rng=i,this.entity={id:e,kind:"green\u0072unner",position:{x:t.x,y:t.y},velocity:{x:0,y:0},radius:7,visualRadius:11,hue:315,telegraph:.6,alive:0,dead:!1,hp:1}}update(e,t){if(this.entity.dead)return;if(this.entity.telegraph>0){this.entity.telegraph=Math.max(0,this.entity.telegraph-e);return}if(this.entity.alive+=e,this.dodgeCooldown=Math.max(0,this.dodgeCooldown-e),this.dodgeTimer<=0&&this.dodgeCooldown<=0)for(let o of t.bullets){let a=this.entity.position.x-o.position.x,h=this.entity.position.y-o.position.y;if(Math.hypot(a,h)>ot)continue;let m=Math.atan2(o.velocity.y,o.velocity.x),u=Math.atan2(h,a),p=Math.abs(u-m);if(p>Math.PI&&(p=2*Math.PI-p),p<at){this.dodgeTimer=lt,this.dodgeCooldown=ht,this.dodgeDir=this.rng()<.5?-1:1;break}}let i=t.shipPosition.x-this.entity.position.x,n=t.shipPosition.y-this.entity.position.y,r=Math.hypot(i,n);if(this.dodgeTimer>0){if(this.dodgeTimer-=e,r>1e-4){let o=-n/r,a=i/r;this.entity.velocity.x=o*we*this.dodgeDir,this.entity.velocity.y=a*we*this.dodgeDir}}else r>1e-4&&(this.entity.velocity.x=i/r*_e,this.entity.velocity.y=n/r*_e);this.entity.position.x+=this.entity.velocity.x*e,this.entity.position.y+=this.entity.velocity.y*e,this.entity.position.x<this.entity.radius&&(this.entity.position.x=this.entity.radius),this.entity.position.y<this.entity.radius&&(this.entity.position.y=this.entity.radius),this.entity.position.x>t.width-this.entity.radius&&(this.entity.position.x=t.width-this.entity.radius),this.entity.position.y>t.height-this.entity.radius&&(this.entity.position.y=t.height-this.entity.radius)}onBulletHit(e){return this.entity.telegraph>0?!1:(this.entity.dead=!0,!0)}onBombKilled(e){this.entity.dead=!0,this.entity.killedByBomb=!0}basePoints(){return P.green\u0072unner}serialize(){return{entity:M(this.entity),dodgeTimer:this.dodgeTimer,dodgeCooldown:this.dodgeCooldown,dodgeDir:this.dodgeDir}}static hydrate(e,t){let i=Object.create(s.prototype);return i.entity=M(e.entity),i.dodgeTimer=e.dodgeTimer,i.dodgeCooldown=e.dodgeCooldown,i.dodgeDir=e.dodgeDir,i.rng=t,i}};var ue=80,ct=4.5*40,pe=6,ut=10,B=class s{entity;constructor(e,t){this.entity={id:e,kind:"grunt",position:{x:t.x,y:t.y},velocity:{x:0,y:0},radius:7,visualRadius:11,hue:85,telegraph:.6,alive:0,dead:!1,hp:1}}update(e,t){if(this.entity.dead)return;if(this.entity.telegraph>0){this.entity.telegraph=Math.max(0,this.entity.telegraph-e);return}this.entity.alive+=e;let i=this.entity.alive,n=ue;if(i>pe){let h=Math.min(1,(i-pe)/(ut-pe));n=ue+(ct-ue)*h}let r=t.shipPosition.x-this.entity.position.x,o=t.shipPosition.y-this.entity.position.y,a=Math.hypot(r,o);a>1e-4&&(this.entity.velocity.x=r/a*n,this.entity.velocity.y=o/a*n),this.entity.position.x+=this.entity.velocity.x*e,this.entity.position.y+=this.entity.velocity.y*e}onBulletHit(e){return this.entity.telegraph>0?!1:(this.entity.dead=!0,!0)}onBombKilled(e){this.entity.dead=!0,this.entity.killedByBomb=!0}basePoints(){return P.grunt}serialize(){return{entity:M(this.entity)}}static hydrate(e){let t=Object.create(s.prototype);return t.entity=M(e.entity),t}};var X=.8*40,Pe=c.maw.activeRadiusCells*40,Ae=c.maw.pullStrength*40*40,pt=.6*40;var D=class s{entity;phase="telegraph";ingestCount=0;bulletsAbsorbed=0;bulletsToKill;rng;constructor(e,t,i=Math.random){this.rng=i,this.entity={id:e,kind:"maw",position:{x:t.x,y:t.y},velocity:{x:0,y:0},radius:7,visualRadius:10,hue:125,telegraph:1,alive:0,dead:!1,hp:1},this.bulletsToKill=c.maw.bulletsToKillMin+Math.floor(this.rng()*(c.maw.bulletsToKillMax-c.maw.bulletsToKillMin+1))}get pullRadiusPx(){return this.phase==="active"?Pe:0}get pullStrength(){return Ae}update(e,t){if(this.entity.dead)return;if(this.entity.telegraph>0){this.entity.telegraph=Math.max(0,this.entity.telegraph-e),this.entity.telegraph===0&&(this.phase="inactive");return}this.entity.alive+=e;let i=t.shipPosition.x-this.entity.position.x,n=t.shipPosition.y-this.entity.position.y,r=Math.hypot(i,n);if(this.phase==="inactive"&&r>1e-4&&(this.entity.position.x+=i/r*X*e,this.entity.position.y+=n/r*X*e),this.phase==="active"){r>1e-4&&(this.entity.position.x+=i/r*X*.3*e,this.entity.position.y+=n/r*X*.3*e);for(let o of t.enemies){if(o.entity.dead||o===this||o.entity.kind==="maw")continue;let a=this.entity.position.x-o.entity.position.x,h=this.entity.position.y-o.entity.position.y,l=Math.hypot(a,h);if(!(l>Pe)){if(l>0){let m=Ae/Math.max(l*l,1);o.entity.velocity.x+=a/l*m*e,o.entity.velocity.y+=h/l*m*e}l<=pt&&(o.entity.dead=!0,this.ingestCount+=1)}}}this.phase==="active"&&(this.entity.radius=9,this.entity.visualRadius=15)}onBulletHit(e){return this.entity.telegraph>0?!1:this.phase==="inactive"?(this.phase="active",this.entity.hue=125,!1):this.phase==="active"&&(this.bulletsAbsorbed+=1,this.bulletsAbsorbed>=this.bulletsToKill)?(this.entity.dead=!0,this.releaseSpawns(e),this.phase="exploded",!0):!1}onBombKilled(e){this.entity.dead=!0,this.entity.killedByBomb=!0,this.phase="exploded"}basePoints(){if(this.entity.killedByBomb)return 0;for(let e of Ee)if(this.ingestCount>=e.minIngest)return e.points;return P.wanderer}releaseSpawns(e){let t=c.maw.explosionPelletsMin,i=c.maw.explosionPelletsMax,n=t+Math.floor(this.rng()*(i-t+1));for(let r=0;r<n;r++){let o=r/n*Math.PI*2;e.spawnEnemy("spawn-pellet",this.entity.position,{x:Math.cos(o),y:Math.sin(o)})}}serialize(){return{entity:M(this.entity),phase:this.phase,ingestCount:this.ingestCount,bulletsAbsorbed:this.bulletsAbsorbed,bulletsToKill:this.bulletsToKill}}static hydrate(e,t){let i=Object.create(s.prototype);return i.entity=M(e.entity),i.phase=e.phase,i.ingestCount=e.ingestCount,i.bulletsAbsorbed=e.bulletsAbsorbed,i.bulletsToKill=e.bulletsToKill,i.rng=t,i}};var j=c.snake.segments,Ce=.52*40,Y=3.5*40,dt=180/180*Math.PI,mt=.4,A=class s{entity;segments=[];heading;trail=[];constructor(e,t){this.entity={id:e,kind:"snake-head",position:{x:t.x,y:t.y},velocity:{x:Y,y:0},radius:8,visualRadius:12,hue:245,telegraph:1,alive:0,dead:!1,hp:1},this.heading=0;for(let i=0;i<j-1;i++)this.segments.push({id:e*100+i+1,kind:"snake-body",position:{x:t.x-Ce*(i+1),y:t.y},velocity:{x:Y,y:0},radius:7,visualRadius:8,hue:55,telegraph:1,alive:0,dead:!1,hp:1});for(let i=0;i<j*4;i++)this.trail.push({x:t.x,y:t.y})}update(e,t){if(this.entity.dead)return;if(this.entity.telegraph>0){this.entity.telegraph=Math.max(0,this.entity.telegraph-e);for(let m of this.segments)m.telegraph=this.entity.telegraph;return}let i={x:t.shipPosition.x,y:t.shipPosition.y},n=i.x-this.entity.position.x,r=i.y-this.entity.position.y,o=Math.atan2(r,n),a=o-this.heading;for(;a>Math.PI;)a-=2*Math.PI;for(;a<-Math.PI;)a+=2*Math.PI;let h=dt*e;Math.abs(a)<=h?this.heading=o:this.heading+=Math.sign(a)*h,this.entity.velocity.x=Math.cos(this.heading)*Y,this.entity.velocity.y=Math.sin(this.heading)*Y,this.entity.position.x+=this.entity.velocity.x*e,this.entity.position.y+=this.entity.velocity.y*e,this.entity.alive+=e,this.trail.unshift({x:this.entity.position.x,y:this.entity.position.y}),this.trail.length>j*8&&(this.trail.length=j*8);let l=0;for(let m=0;m<this.segments.length;m++){let u=this.segments[m];if(!u||u.dead)continue;let p=u.position.x,f=u.position.y,y=Ce*(l+1);l+=1;let b=0,g=!1;for(let v=0;v<this.trail.length-1;v++){let d=this.trail[v],x=this.trail[v+1],E=Math.hypot(x.x-d.x,x.y-d.y);if(b+E>=y){let C=y-b,ge=E===0?0:C/E;u.position.x=d.x+(x.x-d.x)*ge,u.position.y=d.y+(x.y-d.y)*ge,g=!0;break}b+=E}if(!g){let v=this.trail[this.trail.length-1];v&&(u.position.x=v.x,u.position.y=v.y)}e>0&&(u.velocity.x=(u.position.x-p)/e,u.velocity.y=(u.position.y-f)/e)}}onBulletHit(e){return this.entity.telegraph>0,!1}onBodyBulletHit(e){return this.entity.telegraph>0||this.entity.dead||e.dead?!1:(e.dead=!0,this.segments.some(i=>!i.dead)?!1:(this.entity.dead=!0,!0))}onBombKilled(e){this.entity.dead=!0,this.entity.killedByBomb=!0;for(let t of this.segments)t.dead=!0,t.killedByBomb=!0}basePoints(){return P["snake-head"]}bodySegments(){return this.segments}serialize(){return{entity:M(this.entity),segments:this.segments.map(M),heading:this.heading,trail:this.trail.map(e=>({x:e.x,y:e.y}))}}static hydrate(e){let t=Object.create(s.prototype);return t.entity=M(e.entity),t.segments=e.segments.map(M),t.heading=e.heading,t.trail=e.trail.map(i=>({x:i.x,y:i.y})),t}};var J=200,yt=6,V=class s{entity;arcSign;constructor(e,t,i,n=Math.random){let r=Math.hypot(i.x,i.y)||1;this.entity={id:e,kind:"spawn-pellet",position:{x:t.x,y:t.y},velocity:{x:i.x/r*J,y:i.y/r*J},radius:6,visualRadius:8,hue:165,telegraph:0,alive:0,dead:!1,hp:1},this.arcSign=n()<.5?-1:1}update(e,t){if(this.entity.dead)return;if(this.entity.alive+=e,this.entity.alive>yt){this.entity.dead=!0;return}let i=this.entity.velocity.x,n=this.entity.velocity.y,r=Math.hypot(i,n)||1,o=-n/r,a=i/r,h=60*this.arcSign;this.entity.velocity.x+=o*h*e,this.entity.velocity.y+=a*h*e;let l=Math.hypot(this.entity.velocity.x,this.entity.velocity.y)||1;this.entity.velocity.x=this.entity.velocity.x/l*J,this.entity.velocity.y=this.entity.velocity.y/l*J,this.entity.position.x+=this.entity.velocity.x*e,this.entity.position.y+=this.entity.velocity.y*e,this.entity.position.x<this.entity.radius&&(this.entity.position.x=this.entity.radius,this.entity.velocity.x=-this.entity.velocity.x),this.entity.position.y<this.entity.radius&&(this.entity.position.y=this.entity.radius,this.entity.velocity.y=-this.entity.velocity.y),this.entity.position.x>t.width-this.entity.radius&&(this.entity.position.x=t.width-this.entity.radius,this.entity.velocity.x=-this.entity.velocity.x),this.entity.position.y>t.height-this.entity.radius&&(this.entity.position.y=t.height-this.entity.radius,this.entity.velocity.y=-this.entity.velocity.y)}onBulletHit(e){return this.entity.dead=!0,!0}onBombKilled(e){this.entity.dead=!0,this.entity.killedByBomb=!0}basePoints(){return P["spawn-pellet"]}serialize(){return{entity:M(this.entity),arcSign:this.arcSign}}static hydrate(e){let t=Object.create(s.prototype);return t.entity=M(e.entity),t.arcSign=e.arcSign,t}};var $=120,Re=1,Te=3,de=2.45*40,Ie=5,ft=7.5,L=class s{entity;rerollTimer=Re;rng;constructor(e,t,i=Math.random){this.rng=i;let n=i()*Math.PI*2;this.entity={id:e,kind:"wanderer",position:{x:t.x,y:t.y},velocity:{x:Math.cos(n)*$,y:Math.sin(n)*$},radius:8,visualRadius:12,hue:280,telegraph:.8,alive:0,dead:!1,hp:1}}update(e,t){if(!this.entity.dead){if(this.entity.telegraph>0){this.entity.telegraph=Math.max(0,this.entity.telegraph-e);return}if(this.entity.alive+=e,this.rerollTimer-=e,this.rerollTimer<=0){this.rerollTimer+=Re;let i=Math.atan2(this.entity.velocity.y,this.entity.velocity.x),n=30/180*Math.PI,r=i+bt(this.rng)*n;this.entity.velocity.x=Math.cos(r)*$,this.entity.velocity.y=Math.sin(r)*$}this.entity.position.x+=this.entity.velocity.x*e,this.entity.position.y+=this.entity.velocity.y*e,this.entity.position.x<this.entity.radius&&(this.entity.position.x=this.entity.radius,this.entity.velocity.x=-this.entity.velocity.x),this.entity.position.y<this.entity.radius&&(this.entity.position.y=this.entity.radius,this.entity.velocity.y=-this.entity.velocity.y),this.entity.position.x>t.width-this.entity.radius&&(this.entity.position.x=t.width-this.entity.radius,this.entity.velocity.x=-this.entity.velocity.x),this.entity.position.y>t.height-this.entity.radius&&(this.entity.position.y=t.height-this.entity.radius,this.entity.velocity.y=-this.entity.velocity.y)}}onBulletHit(e){return this.entity.telegraph>0?!1:(this.entity.dead=!0,this.spawnShards(e),!0)}onBombKilled(e){this.entity.dead=!0,this.entity.killedByBomb=!0}basePoints(){return P.wanderer}serialize(){return{entity:M(this.entity),rerollTimer:this.rerollTimer}}static hydrate(e,t){let i=Object.create(s.prototype);return i.entity=M(e.entity),i.rerollTimer=e.rerollTimer,i.rng=t,i}spawnShards(e){let t=Math.hypot(this.entity.velocity.x,this.entity.velocity.y)>1?Math.atan2(this.entity.velocity.y,this.entity.velocity.x):this.rng()*Math.PI*2,i=this.rng()*Math.PI*2,n=this.entity.radius+Ie+3;for(let r=0;r<Te;r+=1){let o=t+i+r/Te*Math.PI*2;e.spawnEnemy("wanderer-shard",{x:this.entity.position.x+Math.cos(o)*n,y:this.entity.position.y+Math.sin(o)*n},{x:Math.cos(o)*de,y:Math.sin(o)*de})}}},O=class s{entity;constructor(e,t,i={x:de,y:0}){this.entity={id:e,kind:"wanderer-shard",position:{x:t.x,y:t.y},velocity:{x:i.x,y:i.y},radius:Ie,visualRadius:ft,hue:286,telegraph:0,alive:0,dead:!1,hp:1}}update(e,t){this.entity.dead||(this.entity.alive+=e,this.entity.position.x+=this.entity.velocity.x*e,this.entity.position.y+=this.entity.velocity.y*e,vt(this.entity,t.width,t.height))}onBulletHit(e){return this.entity.dead=!0,!0}onBombKilled(e){this.entity.dead=!0,this.entity.killedByBomb=!0}basePoints(){return P["wanderer-shard"]}serialize(){return{entity:M(this.entity)}}static hydrate(e){let t=Object.create(s.prototype);return t.entity=M(e.entity),t}};function bt(s){let e=Math.max(s(),1e-9),t=s();return Math.sqrt(-2*Math.log(e))*Math.cos(2*Math.PI*t)}function vt(s,e,t){s.position.x<s.radius&&(s.position.x=s.radius,s.velocity.x=Math.abs(s.velocity.x)),s.position.y<s.radius&&(s.position.y=s.radius,s.velocity.y=Math.abs(s.velocity.y)),s.position.x>e-s.radius&&(s.position.x=e-s.radius,s.velocity.x=-Math.abs(s.velocity.x)),s.position.y>t-s.radius&&(s.position.y=t-s.radius,s.velocity.y=-Math.abs(s.velocity.y))}function Be(s){if(s instanceof L)return{kind:"wanderer",data:s.serialize()};if(s instanceof O)return{kind:"wanderer-shard",data:s.serialize()};if(s instanceof B)return{kind:"grunt",data:s.serialize()};if(s instanceof I)return{kind:"green\u0072unner",data:s.serialize()};if(s instanceof T)return{kind:"cross\u0065r",data:s.serialize()};if(s instanceof A)return{kind:"snake-head",data:s.serialize()};if(s instanceof D)return{kind:"maw",data:s.serialize()};if(s instanceof V)return{kind:"spawn-pellet",data:s.serialize()};throw new Error("serializeEnemy: unknown enemy class")}function De(s,e){switch(s.kind){case"wanderer":return L.hydrate(s.data,e);case"wanderer-shard":return O.hydrate(s.data);case"grunt":return B.hydrate(s.data);case"green\u0072unner":return I.hydrate(s.data,e);case"cross\u0065r":return T.hydrate(s.data,e);case"snake-head":return A.hydrate(s.data);case"maw":return D.hydrate(s.data,e);case"spawn-pellet":return V.hydrate(s.data)}}var Ve=["wanderer","grunt","green\u0072unner","cross\u0065r","snake-head","maw","spawn-pellet"],gt=["grunt","green\u0072unner","wanderer"],U="endless",Z={endless:{id:"endless",displayName:"Endless",startLives:3,startBombs:3,fireRule:"standard",enemyRoster:Ve,spawnProfile:"endless-pressure",timeLimitSeconds:null,scoreMultiplier:1,earnBombEveryScore:1e5,earnLifeEveryScore:75e3,finiteLives:!0,multiplierDeathRule:"decay",leaderboardScope:"endless",debugProofId:"arcade-mode:endless"},"time-attack":{id:"time-attack",displayName:"Time Attack",startLives:3,startBombs:3,fireRule:"standard",enemyRoster:Ve,spawnProfile:"timed-pressure",timeLimitSeconds:180,scoreMultiplier:1,earnBombEveryScore:1e5,earnLifeEveryScore:null,finiteLives:!1,multiplierDeathRule:"reset-to-one",leaderboardScope:"time_attack",debugProofId:"arcade-mode:time-attack"},"gate-run":{id:"gate-run",displayName:"Gate Run",startLives:1,startBombs:0,fireRule:"disabled",enemyRoster:gt,spawnProfile:"pursuer-gates",timeLimitSeconds:null,scoreMultiplier:1,earnBombEveryScore:null,earnLifeEveryScore:null,finiteLives:!0,multiplierDeathRule:"decay",leaderboardScope:"gate_run",debugProofId:"arcade-mode:gate-run"}},vi=Object.keys(Z);function Q(s){return s?Z[s]??Z[U]:Z[U]}var ee=class{_value=1;_peak=1;_decayFrom=1;_decayElapsed=0;_decaying=!1;get value(){return this._value}get peak(){return this._peak}get isDecaying(){return this._decaying}collectSpark(){if(this._decaying||this._value>=c.multiplier.cap)return;let e=this._value+c.multiplier.sparkIncrement;this._value=Math.min(Math.round(e*100)/100,c.multiplier.cap),this._value>this._peak&&(this._peak=this._value)}beginDeathDecay(){this._decaying=!0,this._decayFrom=this._value,this._decayElapsed=0}advance(e){if(!this._decaying)return;this._decayElapsed+=e;let t=c.multiplier.decayWindowSeconds;if(this._decayElapsed>=t){this._value=1,this._decaying=!1;return}let i=this._decayElapsed/t;this._value=this._decayFrom+(1-this._decayFrom)*i}lockOnRespawn(){this._decaying=!1}resetCurrentValue(){this._value=1,this._decayFrom=1,this._decayElapsed=0,this._decaying=!1}reset(){this._value=1,this._peak=1,this._decayFrom=1,this._decayElapsed=0,this._decaying=!1}snapshot(){return{value:this._value,peak:this._peak,decaying:this._decaying}}serialize(){return{value:this._value,peak:this._peak,decayFrom:this._decayFrom,decayElapsed:this._decayElapsed,decaying:this._decaying}}loadState(e){this._value=e.value,this._peak=e.peak,this._decayFrom=e.decayFrom,this._decayElapsed=e.decayElapsed,this._decaying=e.decaying}};function Le(s){let{factory:e,reset:t}=s,i=s.initialSize??10,n=s.maxSize??100,r=s.growBy??5,o=[],a=new Set,h=Math.min(i,n);for(let u=0;u<h;u++)o.push(e());function l(){return o.length+a.size}function m(){let u=n-l(),p=Math.min(r,u);for(let f=0;f<p;f++)o.push(e())}return{acquire(){if(o.length===0){if(l()>=n)return null;m()}if(o.length===0)return null;let u=o.pop();return a.add(u),u},release(u){a.has(u)&&(a.delete(u),t(u),o.push(u))},prewarm(u){let p=n-l(),f=Math.min(u,p);for(let y=0;y<f;y++)o.push(e())},clear(){o.length=0,a.clear()},get activeCount(){return a.size},get availableCount(){return o.length}}}function xt(){return{x:0,y:0,vx:0,vy:0,life:0,maxLife:0,color:"#ffffff",size:4,baseSize:4,visualKind:"streak",alpha:1,scale:1,rotation:0,rotationSpeed:0,additive:!0,trailLength:8,trailWidth:2,birthColor:"#ffffff",fadeToWhiteAtMidlife:!1,priority:"burst"}}function St(s){s.x=0,s.y=0,s.vx=0,s.vy=0,s.life=0,s.maxLife=0,s.color="#ffffff",s.size=4,s.baseSize=4,s.visualKind="streak",s.alpha=1,s.scale=1,s.rotation=0,s.rotationSpeed=0,s.additive=!0,s.trailLength=8,s.trailWidth=2,s.birthColor="#ffffff",s.fadeToWhiteAtMidlife=!1,s.priority="burst"}var te=class{pool;_active=[];priorityScratch=[];activeLimit=c.particles.poolCapacity;constructor(){this.pool=Le({factory:xt,reset:St,initialSize:c.particles.poolCapacity,maxSize:c.particles.poolCapacity,growBy:0})}get particles(){return this._active}getPoolStats(){return{active:this.pool.activeCount,available:this.pool.availableCount,capacity:c.particles.poolCapacity}}setActiveLimit(e){let t=Math.max(0,Math.min(c.particles.poolCapacity,Math.floor(e)));this.activeLimit=t,this.trimToActiveLimit()}reserveForPriorityParticles(e){let t=Math.max(0,this._active.length+e-this.activeLimit);if(t<=0)return;let i=0,n=0;for(let r=0;r<this._active.length;r+=1){let o=this._active[r];if(i<t&&o.priority==="trail"){this.pool.release(o),i+=1;continue}this._active[n]=o,n+=1}this._active.length=n}trimToActiveLimit(){let e=this._active.length-this.activeLimit;if(e<=0)return;let t=0,i=0;for(let n=0;n<this._active.length;n+=1){let r=this._active[n];if(t<e&&r.priority==="trail"){this.pool.release(r),t+=1;continue}this._active[i]=r,i+=1}for(this._active.length=i;this._active.length>this.activeLimit;){let n=this._active.pop();n&&this.pool.release(n)}}prependPriorityParticles(e){if(e.length===0)return;let t=this._active.length;this._active.length=e.length+t;for(let i=t-1;i>=0;i-=1)this._active[i+e.length]=this._active[i];for(let i=0;i<e.length;i+=1)this._active[i]=e[i]}emitKillBurst(e,t,i=Math.random){let n=c.particles.killBurstMin,r=c.particles.killBurstMax,o=Math.floor(n+i()*(r-n));this.emitBurst(e,{count:o,speed:320,spread:Math.PI,lifetime:340,colors:[t,"#ffffff",t,"#fef3c7"],fadeToWhiteAtMidlife:!0,additive:!0,trailLength:8.5,trailWidth:.65,initialOffset:4,priority:"impact"},i)}emitBombCascade(e,t=Math.random){let i=c.bomb.cascadeMin+Math.floor(t()*(c.bomb.cascadeMax-c.bomb.cascadeMin)),n=c.bomb.cascadeLifetimeMinS*1e3+t()*(c.bomb.cascadeLifetimeMaxS-c.bomb.cascadeLifetimeMinS)*1e3;return this.emitBurst(e,{count:i,speed:340,spread:Math.PI,lifetime:n,colors:["#fef3c7","#ffe2a8","#ff9c54","#ffffff"],additive:!0,trailLength:9,trailWidth:.85,initialOffset:18,priority:"impact"},t)}emitShipDeathBurst(e,t=Math.random){return this.emitBurst(e,{count:90,speed:260,spread:Math.PI,lifetime:520,colors:["#ffffff","#7af0ff","#ffffff","#ff4cf8","#bff8ff"],additive:!0,trailLength:10,trailWidth:.7,initialOffset:6,priority:"impact"},t)}emitBulletTrail(e,t="#9cf0ff",i=0,n=Math.random){this.emitTrailResidue(e,{count:4,color:t,colors:[t,"#7af0ff","#bff8ff"],lifetime:150,direction:i,speed:42,spread:.34,backOffset:5,sideJitter:3,trailLength:3.5,trailWidth:.41,size:.7,alpha:.58},n)}emitBulletWallImpact(e,t,i="#fef3c7",n=Math.random){let r=Math.atan2(t.y,t.x);return this.emitBurst(e,{count:18,speed:240,spread:Math.PI*.5,lifetime:260,colors:[i,"#ffffff","#7af0ff","#ffffff"],baseDirection:r,additive:!0,trailLength:6,trailWidth:.65,initialOffset:1,priority:"impact"},n)}emitTrailResidue(e,t,i=Math.random){let n=0,r=Math.cos(t.direction),o=Math.sin(t.direction),a=-o,h=r,l=t.speed??20,m=t.spread??.2,u=t.backOffset??10,p=t.sideJitter??2;for(let f=0;f<t.count&&!(this._active.length>=this.activeLimit);f++){let y=this.pool.acquire();if(!y)break;let b=u*(.35+i()*.9),g=(i()*2-1)*p,v=t.direction+Math.PI+(i()*2-1)*m,d=l*(.35+i()*.8);y.x=e.x-r*b+a*g,y.y=e.y-o*b+h*g,y.vx=Math.cos(v)*d,y.vy=Math.sin(v)*d,y.life=0,y.maxLife=t.lifetime;let x=t.colors??[t.color],E=Math.min(Math.floor(i()*x.length),x.length-1),C=x[E]??t.color;y.color=C,y.birthColor=C,y.fadeToWhiteAtMidlife=!1,y.size=t.size??2,y.baseSize=y.size,y.visualKind="streak",y.alpha=t.alpha??.7,y.scale=1,y.additive=!0,y.trailLength=t.trailLength??10,y.trailWidth=t.trailWidth??1.2,y.priority="trail",y.rotation=t.direction,y.rotationSpeed=(i()*2-1)*.004,this._active.push(y),n+=1}return n}emitBurst(e,t,i=Math.random){this.reserveForPriorityParticles(t.count);let n=this.priorityScratch;n.length=0;let r=0;for(let o=0;o<t.count&&!(this._active.length+n.length>=this.activeLimit);o++){let a=this.pool.acquire();if(!a)break;let l=(t.baseDirection??0)+(i()*2-1)*t.spread,m=t.speed*(.6+i()*.4),u=Math.min(Math.floor(i()*t.colors.length),t.colors.length-1),p=t.colors[u]??t.colors[0]??"#ffffff",f=t.initialOffset??0;a.x=e.x+Math.cos(l)*f,a.y=e.y+Math.sin(l)*f,a.vx=Math.cos(l)*m,a.vy=Math.sin(l)*m,a.life=0,a.maxLife=t.lifetime,a.color=p,a.birthColor=p,a.fadeToWhiteAtMidlife=t.fadeToWhiteAtMidlife??!1,a.size=3,a.baseSize=3,a.visualKind="streak",a.alpha=1,a.scale=1,a.additive=t.additive??!0,a.trailLength=t.trailLength??14,a.trailWidth=t.trailWidth??2,a.priority=t.priority??"burst",a.rotation=l,a.rotationSpeed=(i()*2-1)*.012,n.push(a),r+=1}return this.prependPriorityParticles(n),n.length=0,r}update(e){let t=e*1e3,i=0;for(let n=0;n<this._active.length;n+=1){let r=this._active[n];if(r.life+=t,r.life>=r.maxLife){this.pool.release(r);continue}r.x+=r.vx*e,r.y+=r.vy*e;let o=r.life/r.maxLife;r.alpha=Math.max(0,1-o),r.scale=1+(1-o)*.25,r.rotation=(r.rotation??0)+(r.rotationSpeed??0)*t,r.fadeToWhiteAtMidlife&&o>=.45&&o<=.62?r.color="#ffffff":r.color=r.birthColor??r.color,r.vx*=.94,r.vy*=.94,this._active[i]=r,i+=1}this._active.length=i,this.trimToActiveLimit()}clearAll(){for(let e of this._active)this.pool.release(e);this._active.length=0}};var G=8,Et=96,Oe=384,Mt=16,kt=Math.PI/120;function _t(s,e){let t=e<=0?0:s/e,i=255,n=Math.round(243-t*121),r=Math.round(199-t*175);return`#${i.toString(16).padStart(2,"0")}${n.toString(16).padStart(2,"0")}${r.toString(16).padStart(2,"0")}`}function wt(s,e){let t=e<=0?0:s/e;return(1-t)*(1-t)}var Ne=G-1,ze=Array.from({length:G},(s,e)=>wt(e,Ne)),We=Array.from({length:G},(s,e)=>_t(e,Ne));function Pt(){return Array.from({length:G},(s,e)=>({position:{x:0,y:0},rotation:0,alpha:ze[e]??0,color:We[e]??"#ffffff"}))}function Ge(s,e,t){for(let i=0;i<G;i+=1){let n=s[i];n&&(n.position.x=e.x,n.position.y=e.y,n.rotation=t,n.alpha=ze[i]??0,n.color=We[i]??"#ffffff")}}function At(s,e,t){for(let n=G-1;n>0;n-=1){let r=s[n],o=s[n-1];!r||!o||(r.position.x=o.position.x,r.position.y=o.position.y,r.rotation=o.rotation)}let i=s[0];i&&(i.position.x=e.x,i.position.y=e.y,i.rotation=t)}function me(){return{id:0,position:{x:0,y:0},velocity:{x:0,y:0},age:0,alive:!1,trailSegments:Pt()}}function Fe(){return{position:{x:0,y:0},normal:{x:0,y:0}}}function Ct(s){let e=Math.sin(s*12.9898+78.233)*43758.5453;return((e-Math.floor(e))*2-1)*kt}var ie=class{_bullets=[];_available=Array.from({length:Et},me);_wallImpacts=[];_wallImpactPool=Array.from({length:Mt},Fe);_nextId=1;_fireCooldowns=[];get bullets(){return this._bullets}autoFire(e,t,i,n,r=0){if(!t)return this._fireCooldowns[r]=0,0;let o=Math.hypot(n.x,n.y);if(o<1e-4)return 0;let a=(this._fireCooldowns[r]??0)-e,h=0;for(;a<=0;){a+=c.projectile.fireIntervalSeconds;let l=c.projectile.speedCellsPerSec*40,m=n.x/o,u=n.y/o,p=this.acquireBullet();if(!p)break;p.id=this._nextId++;let f=Ct(p.id),y=Math.cos(f),b=Math.sin(f),g=m*y-u*b,v=m*b+u*y,d=Math.atan2(v,g);if(p.position.x=i.x,p.position.y=i.y,p.velocity.x=g*l,p.velocity.y=v*l,p.age=0,p.alive=!0,Ge(p.trailSegments,i,d),this._bullets.push(p),h+=1,h>100)break}return this._fireCooldowns[r]=a,h}update(e,t=[]){this._wallImpacts.length=0;for(let n of this._wallImpactPool)n.position.x=0,n.position.y=0,n.normal.x=0,n.normal.y=0;let i=0;for(let n of this._bullets){if(!n.alive){this.releaseBullet(n);continue}for(let l of t){if(l.pullRadiusPx<=0)continue;let m=l.position.x-n.position.x,u=l.position.y-n.position.y,p=m*m+u*u,f=Math.sqrt(p);if(f===0||f>l.pullRadiusPx)continue;let y=l.k/Math.max(p,1);n.velocity.x+=m/f*y*e,n.velocity.y+=u/f*y*e}n.position.x+=n.velocity.x*e,n.position.y+=n.velocity.y*e,n.age+=e,At(n.trailSegments,n.position,Math.atan2(n.velocity.y,n.velocity.x));let r=n.position.x<0,o=n.position.x>1280,a=n.position.y<0,h=n.position.y>720;if(r||o||a||h){let l=this._wallImpactPool[this._wallImpacts.length]??Fe();this._wallImpacts.length>=this._wallImpactPool.length&&this._wallImpactPool.push(l),l.position.x=Math.min(1280,Math.max(0,n.position.x)),l.position.y=Math.min(720,Math.max(0,n.position.y)),l.normal.x=r?1:o?-1:0,l.normal.y=a?1:h?-1:0,this._wallImpacts.push(l),n.alive=!1}n.alive?(this._bullets[i]=n,i+=1):this.releaseBullet(n)}return this._bullets.length=i,this._wallImpacts}resetFireCooldowns(e){this._fireCooldowns=Array.from({length:e},()=>0)}killBullet(e){for(let t of this._bullets)t.id===e&&(t.alive=!1)}clearAll(){for(let e of this._bullets)this.releaseBullet(e);this._bullets.length=0}serialize(){return{bullets:this._bullets.map(e=>({id:e.id,position:{x:e.position.x,y:e.position.y},velocity:{x:e.velocity.x,y:e.velocity.y},age:e.age,alive:e.alive})),nextId:this._nextId,fireCooldowns:[...this._fireCooldowns]}}loadState(e){this._available=[],this._wallImpacts.length=0,this._bullets=e.bullets.map(t=>{let i=me();return i.id=t.id,i.position.x=t.position.x,i.position.y=t.position.y,i.velocity.x=t.velocity.x,i.velocity.y=t.velocity.y,i.age=t.age,i.alive=t.alive,Ge(i.trailSegments,i.position,Math.atan2(t.velocity.y,t.velocity.x)),i}),this._nextId=e.nextId,this._fireCooldowns=[...e.fireCooldowns]}acquireBullet(){return this._available.pop()??(this._bullets.length+this._available.length<Oe?me():null)}releaseBullet(e){e.alive=!1,e.age=0,this._available.length<Oe&&this._available.push(e)}};var K=class{position;heading=0;shield;dying=!1;spawnPosition;constructor(e){this.spawnPosition=e??{x:1280/2,y:720/2},this.position={...this.spawnPosition},this.shield=c.ship.spawnShieldSeconds}update(e,t){if(this.dying)return;this.shield>0&&(this.shield=Math.max(0,this.shield-e));let i=c.ship.maxSpeedCellsPerSec*40,n=t.movementVector,r=Math.hypot(n.x,n.y);if(r>1e-4){let l=n.x/Math.max(r,1),m=n.y/Math.max(r,1);this.position.x+=l*i*e,this.position.y+=m*i*e}let o=c.ship.collisionRadius;this.position.x<o&&(this.position.x=o),this.position.y<o&&(this.position.y=o),this.position.x>1280-o&&(this.position.x=1280-o),this.position.y>720-o&&(this.position.y=720-o);let a=t.aimVector;Math.hypot(a.x,a.y)>1e-4?this.heading=Math.atan2(a.y,a.x):r>1e-4&&(this.heading=Math.atan2(n.y,n.x))}respawn(){this.position={...this.spawnPosition},this.shield=c.ship.spawnShieldSeconds,this.dying=!1}beginDeathAnim(){this.dying=!0}get isShielded(){return this.shield>0}serialize(){return{position:{x:this.position.x,y:this.position.y},heading:this.heading,shield:this.shield,dying:this.dying}}loadState(e){this.position={x:e.position.x,y:e.position.y},this.heading=e.heading,this.shield=e.shield,this.dying=e.dying}};var Rt=c.spark.renderCapacity,Ue=512;function Ke(s,e){let t=null,i=1/0;for(let n of s){let r=n.x-e.x,o=n.y-e.y,a=r*r+o*o;a<i&&(i=a,t=n)}return t}function He(s,e){let t=Math.exp(-8.5*e);s.velocity.x*=t,s.velocity.y*=t;let i=Math.hypot(s.velocity.x,s.velocity.y);if(i>c.spark.maxMagnetSpeedPxPerSec){let n=c.spark.maxMagnetSpeedPxPerSec/i;s.velocity.x*=n,s.velocity.y*=n}s.position.x+=s.velocity.x*e,s.position.y+=s.velocity.y*e}function qe(){return{id:0,position:{x:0,y:0},velocity:{x:0,y:0},age:0,lifetime:c.spark.lifetimeSeconds,fading:!1,fadeAge:0,fadeDuration:c.spark.deathFadeSeconds}}var ne=class{_sparks=[];_available=Array.from({length:Rt},qe);_nextId=1;get sparks(){return this._sparks}drop(e,t=Math.random){let i=this.acquireSpark();if(!i)return;let n=t()*Math.PI*2,r=101+t()*134;i.id=this._nextId++,i.position.x=e.x,i.position.y=e.y,i.velocity.x=Math.cos(n)*r,i.velocity.y=Math.sin(n)*r,i.age=0,i.lifetime=c.spark.lifetimeSeconds,i.fading=!1,i.fadeAge=0,i.fadeDuration=c.spark.deathFadeSeconds,this._sparks.push(i)}dropBurst(e,t=Math.random){let i=c.spark.dropsPerKillMin,n=c.spark.dropsPerKillMax,r=i+Math.floor(t()*(n-i+1)),o=this._sparks.length;for(let a=0;a<r;a+=1)this.drop(e,t);return this._sparks.length-o}update(e,t,i=1){let n=0,r=c.spark.magnetRadiusPx*Math.max(1,i),o=r**2,a=c.spark.collectRadiusPx**2,h=0;for(let l of this._sparks){if(l.fading){l.fadeAge+=e,l.velocity.x*=Math.exp(-14*e),l.velocity.y*=Math.exp(-14*e),l.position.x+=l.velocity.x*e,l.position.y+=l.velocity.y*e,this.bounceOffArenaEdges(l),l.age+=e,l.fadeAge>=l.fadeDuration?this.releaseSpark(l):(this._sparks[h]=l,h+=1);continue}let m=Ke(t,l.position);if(!m){He(l,e),this.bounceOffArenaEdges(l),l.age+=e,this._sparks[h]=l,h+=1;continue}let u=m.x-l.position.x,p=m.y-l.position.y,f=u*u+p*p;if(f<=a){n+=1,this.releaseSpark(l);continue}if(e>0&&f<=o&&f>1e-4){let v=Math.sqrt(f),d=1-v/r,x=c.spark.magnetAccelPxPerSec2*(.25+d*d*1.75);l.velocity.x+=u/v*x*e,l.velocity.y+=p/v*x*e}He(l,e),this.bounceOffArenaEdges(l),l.age+=e;let y=Ke(t,l.position)??m,b=y.x-l.position.x,g=y.y-l.position.y;b*b+g*g<=a?(n+=1,this.releaseSpark(l)):(this._sparks[h]=l,h+=1)}return this._sparks.length=h,n}fadeAll(e=c.spark.deathFadeSeconds){let t=Math.max(.05,e);for(let i of this._sparks)i.fading=!0,i.fadeAge=0,i.fadeDuration=t}clearAll(){for(let e of this._sparks)this.releaseSpark(e);this._sparks.length=0}serialize(){return{sparks:this._sparks.map(e=>({id:e.id,position:{x:e.position.x,y:e.position.y},velocity:{x:e.velocity.x,y:e.velocity.y},age:e.age,lifetime:e.lifetime,fading:e.fading,fadeAge:e.fadeAge,fadeDuration:e.fadeDuration})),nextId:this._nextId}}loadState(e){this._available=[],this._sparks=e.sparks.map(t=>({id:t.id,position:{x:t.position.x,y:t.position.y},velocity:{x:t.velocity.x,y:t.velocity.y},age:t.age,lifetime:t.lifetime,fading:t.fading,fadeAge:t.fadeAge,fadeDuration:t.fadeDuration})),this._nextId=e.nextId}bounceOffArenaEdges(e){let t=c.spark.edgeBounceRestitution;e.position.x<0?(e.position.x=0,e.velocity.x<0&&(e.velocity.x=-e.velocity.x*t)):e.position.x>1280&&(e.position.x=1280,e.velocity.x>0&&(e.velocity.x=-e.velocity.x*t)),e.position.y<0?(e.position.y=0,e.velocity.y<0&&(e.velocity.y=-e.velocity.y*t)):e.position.y>720&&(e.position.y=720,e.velocity.y>0&&(e.velocity.y=-e.velocity.y*t))}acquireSpark(){return this._available.pop()??(this._sparks.length+this._available.length<Ue?qe():null)}releaseSpark(e){e.age=0,e.velocity.x=0,e.velocity.y=0,e.fading=!1,e.fadeAge=0,e.fadeDuration=c.spark.deathFadeSeconds,this._available.length<Ue&&this._available.push(e)}};var Tt=.95,It=.13,Bt=7e4,F=class{cooldown=0;rng;allowedScratch=[];constructor(e=Math.random){this.rng=e}allowedKinds(e){let t=[];return this.allowedKindsInto(e,t),t}allowedKindsInto(e,t){t.length=0;let i=c.spawn;e>=i.wandererFromScore&&t.push("wanderer"),e>=i.gruntFromScore&&t.push("grunt"),e>=i.cross\u0065rFromScore&&t.push("cross\u0065r"),e>=i.green\u0052unnerFromScore&&t.push("green\u0072unner"),e>=i.mawFromScore&&t.push("maw"),e>=i.snakeFromScore&&t.push("snake-head")}tick(e,t,i){this.cooldown-=e;let n=[],r=Math.max(It,Tt-t/Bt);for(;this.cooldown<=0;){this.cooldown+=r;let o=this.pickKind(t,i);if(o!==null&&(n.push({kind:o,position:this.spawnPos()}),n.length>12))break}return n}pickKind(e,t){let i=this.allowedScratch;if(this.allowedKindsInto(e,i),i.length===0)return null;let n={wanderer:4,"wanderer-shard":0,grunt:7+Math.min(28,e/16e3),green\u0072unner:3.5+Math.min(4,e/55e3),cross\u0065r:3.5,"snake-head":.7,"snake-body":0,maw:.8,"spawn-pellet":0},r=0;for(let h of i)h==="snake-head"&&t>=c.snake.maxSimultaneous||(r+=n[h]);if(r<=0)return null;let o=this.rng()*r,a=null;for(let h of i)if(!(h==="snake-head"&&t>=c.snake.maxSimultaneous)&&(a=h,o-=n[h],o<=0))return h;return a}serialize(){return{cooldown:this.cooldown}}loadState(e){this.cooldown=e.cooldown}spawnPos(){let e=Math.floor(this.rng()*4),t=60;return e===0?{x:this.rng()*1280,y:t}:e===1?{x:this.rng()*1280,y:720-t}:e===2?{x:t,y:this.rng()*720}:{x:1280-t,y:this.rng()*720}}};function Xe(s,e,t,i){if(s==="idle"||s==="run-end")return{duration:e,status:s,endRun:!1};let n=e+t;return i!==null&&n>=i?{duration:i,status:s,endRun:!0}:{duration:n,status:s==="spawning"?"active":s,endRun:!1}}function je(s,e,t,i){if(s!=="death-decay"&&s!=="death-pending")return{deathAnimTimer:e,completeRespawn:!1};let n=e+t;return{deathAnimTimer:n,completeRespawn:n>=i}}function Ye(s,e,t,i){let n=Array.from({length:i},(o,a)=>t[a]?.bombPressed??!1),r=n.flatMap((o,a)=>o&&!e[a]&&(s==="active"||s==="death-decay")?[a]:[]);return{previous:n,slots:r}}var se=class{constructor(e){this.cellSize=e;if(!Number.isFinite(e)||e<=0)throw new Error("SpatialHash cellSize must be positive")}cellSize;cells=new Map;seen=new Set;clear(){this.cells.clear(),this.seen.clear()}insertCircle(e,t,i){let n=this.cellBounds(t,i);for(let r=n.minY;r<=n.maxY;r+=1)for(let o=n.minX;o<=n.maxX;o+=1){let a=this.key(o,r),h=this.cells.get(a);h?h.push(e):this.cells.set(a,[e])}}queryCircle(e,t,i=[]){i.length=0,this.seen.clear();let n=this.cellBounds(e,t);for(let r=n.minY;r<=n.maxY;r+=1)for(let o=n.minX;o<=n.maxX;o+=1){let a=this.cells.get(this.key(o,r));if(a)for(let h of a)this.seen.has(h)||(this.seen.add(h),i.push(h))}return i}cellBounds(e,t){let i=Math.max(0,t);return{minX:Math.floor((e.x-i)/this.cellSize),maxX:Math.floor((e.x+i)/this.cellSize),minY:Math.floor((e.y-i)/this.cellSize),maxY:Math.floor((e.y+i)/this.cellSize)}}key(e,t){return`${e}:${t}`}};var w={spawnImplosion:{strength:8,radius:90},bulletWake:{strength:.6,radius:20},bombShockwave:{strength:22,radius:340},deathShockwave:{strength:32,radius:380},mawImplosionStrength:3,enemyDeathBurst:{strength:16,radius:130}},Je=w.bombShockwave.radius,ye={bombActivated:{scale:.42,duration:150,lerpDuration:0},shipDeath:{scale:.32,duration:230,lerpDuration:0}},fe={radius:12,spawnRingRadius:190,spawnMargin:120},R={length:100,radius:24,postRadius:6,telegraphSeconds:.9,driftSpeedMinPxPerSec:3.5,driftSpeedMaxPxPerSec:6,angularSpeedMinRadPerSec:.006,angularSpeedMaxRadPerSec:.014},Dt=64,be=class{listeners=new Map;on(e,t){let i=this.listeners.get(e);return i||(i=[],this.listeners.set(e,i)),i.push(t),()=>{let n=this.listeners.get(e);if(!n)return;let r=n.indexOf(t);r>=0&&n.splice(r,1)}}emit(e,t){let i=this.listeners.get(e);if(i)for(let n of i)n(t)}};function $e(s){return{id:s.id,position:{x:s.position.x,y:s.position.y},angle:s.angle,driftVelocity:{x:s.driftVelocity.x,y:s.driftVelocity.y},angularVelocity:s.angularVelocity,length:s.length,radius:s.radius,postRadius:s.postRadius,age:s.age,telegraph:s.telegraph,active:s.active}}function Ze(s){let e=s>>>0,t=(()=>{e|=0,e=e+1831565813|0;let i=Math.imul(e^e>>>15,1|e);return i=i+Math.imul(i^i>>>7,61|i)^i,((i^i>>>14)>>>0)/4294967296});return t.getState=()=>e>>>0,t.setState=i=>{e=i>>>0},t}function Qe(s){return{id:s.id,position:{x:s.position.x,y:s.position.y},radius:s.radius,age:s.age,active:s.active}}function Vt(s,e,t){if(s.rngState===void 0)throw new Error("advanceInstalledSurvivalState requires state-owned rngState");let i=new ve({deterministic:!0,disableSpawnDirector:t.spawnDirectorEnabled===!1,playerCount:s.playerCount,transitionEngine:!0});return i.configureDeterministic(0,s.playerCount),i.hydrate(s),i.tick(t.dt,e),i.serialize()}var Lt={movementVector:{x:0,y:0},aimVector:{x:0,y:0},fireDown:!1,bombPressed:!1},Ot=60;function et(s,e){let t=(s-(e-1)/2)*(2*Ot);return{x:1280/2+t,y:720/2}}var ve=class{events=new be;ships;bullets=new ie;sparks=new ne;particles=new te;bomb=new H;multiplier=new ee;bombShake=new z;deathShake=new z;timeScale=Se();gates=[];bombPickups=[];renderPrevPositions=new WeakMap;renderAlphaValue=1;enemies=[];nextEnemyId=1;director;rng;cosmeticRng;deterministic;springGrid;spawnDirectorEnabled;_score=0;_status="idle";_duration=0;_deathAnimTimer=0;nextGateId=1;nextBombPickupId=1;gateSpawnTimer=0;spawnSuppressionTimer=0;sparkMagnetSurgeTimer=0;sparkMagnetSurgeMultiplier=1;previousShipPositions;prevBombPressed;playerCount;localSlot=0;weaponFireActive=!1;mode=Q(U);bulletTrailFrame=0;enemyCollisionHash=new se(Dt);enemyCollisionTargets=[];enemyCollisionQuery=[];frameProfileSections=[];enemyUpdateCtx;_pendingRespawn=!1;_pendingShipDeathBurstEmitted=!1;transitionEngine;stateAuthorityEnabled=!1;authoritativeState=null;constructor(e={}){this.rng=e.rng??Math.random,this.cosmeticRng=e.cosmeticRng??this.rng,this.deterministic=e.deterministic??!1,this.springGrid=e.springGrid??null,this.spawnDirectorEnabled=!e.disableSpawnDirector,this.transitionEngine=e.transitionEngine??!1,this.playerCount=Math.max(1,e.playerCount??1),this.ships=this.buildShips(),this.previousShipPositions=this.ships.map(t=>({...t.position})),this.prevBombPressed=this.ships.map(()=>!1),this.director=new F(this.rng),this.enemyUpdateCtx={shipPosition:this.ships[0].position,bullets:this.bullets.bullets,enemies:this.enemies,dropSpark:t=>this.sparks.drop(t,this.rng),spawnEnemy:(t,i,n)=>this.spawnEnemy(t,i,n),now:this._duration,width:1280,height:720}}get ship(){return this.ships[this.localSlot]??this.ships[0]}setRenderAlpha(e){this.renderAlphaValue=e<0?0:e>1?1:e}renderPosition(e,t){let i=this.renderPrevPositions.get(e),n=e.position;if(!i)return t.x=n.x,t.y=n.y,t;let r=this.renderAlphaValue;return t.x=i.x+(n.x-i.x)*r,t.y=i.y+(n.y-i.y)*r,t}captureRenderPrevPosition(e){let t=this.renderPrevPositions.get(e);t?(t.x=e.position.x,t.y=e.position.y):this.renderPrevPositions.set(e,{x:e.position.x,y:e.position.y})}captureRenderPrevPositions(){for(let e of this.ships)this.captureRenderPrevPosition(e);for(let e of this.enemies)if(this.captureRenderPrevPosition(e.entity),e instanceof A)for(let t of e.bodySegments())this.captureRenderPrevPosition(t);for(let e of this.bullets.bullets)this.captureRenderPrevPosition(e);for(let e of this.sparks.sparks)this.captureRenderPrevPosition(e)}setLocalSlot(e){this.localSlot=Math.max(0,e)}buildShips(){return Array.from({length:this.playerCount},(e,t)=>new K(et(t,this.playerCount)))}start(e=U){this.mode=Q(e),this._score=0,this._duration=0,this._deathAnimTimer=0,this._pendingRespawn=!1,this._pendingShipDeathBurstEmitted=!1,this.bomb.resetWithCounts({bombs:this.mode.startBombs,lives:this.mode.startLives}),this.multiplier.reset(),this.enemies=[],this.gates.length=0,this.bombPickups.length=0,this.nextGateId=1,this.nextBombPickupId=1,this.gateSpawnTimer=0,this.spawnSuppressionTimer=0,this.sparkMagnetSurgeTimer=0,this.sparkMagnetSurgeMultiplier=1,this.setWeaponFireActive(!1),this.bulletTrailFrame=0,this.bullets.clearAll(),this.bullets.resetFireCooldowns(this.playerCount),this.sparks.clearAll(),this.particles.clearAll(),this.ships=this.buildShips(),this.previousShipPositions=this.ships.map(t=>({...t.position})),this.prevBombPressed=this.ships.map(()=>!1);for(let t=0;t<this.mode.startBombs;t+=1)this.spawnBombPickup();this._status="spawning",this.events.emit("multiplier.changed",{value:1,peak:1}),this.stateAuthorityEnabled&&(this.authoritativeState=this.serializeMutable())}tick(e,t){if(this.stateAuthorityEnabled&&!this.transitionEngine){let i=this.normalizeInputs(t),n=this.serializeMutable();this.tickMutable(e,i);let r=this.serializeMutable(),o=Vt(n,i,{dt:e,deathAnimationSeconds:c.death.deathAnimSeconds,timeLimitSeconds:this.mode.timeLimitSeconds,spawnDirectorEnabled:this.spawnDirectorEnabled});if(JSON.stringify(r)!==JSON.stringify(o))throw new Error("InstalledSurvivalRuntime state-authority divergence");this.authoritativeState=o;return}this.tickMutable(e,t)}tickMutable(e,t){let i=this.normalizeInputs(t);this.frameProfileSections.length=0;let n=!this.deterministic,r=n?performance.now():0,o=n?u=>{let p=performance.now();this.frameProfileSections.push({name:`arena.${u}`,ms:p-r}),r=p}:u=>{};if(this._status==="idle"){o("idle");return}this.captureRenderPrevPositions();let a=this.deterministic?e:e*this.timeScale.update(e).scale;if(o("timeScale"),this._status==="run-end"){this.particles.update(a),this.bombShake.update(e),this.deathShake.update(e),o("runEndEffects");return}let h=Xe(this._status,this._duration,e,this.mode.timeLimitSeconds);if(this._duration=h.duration,this._status=h.status,h.endRun){this.endRun();return}if(o("status"),this.multiplier.isDecaying){let u=this.multiplier.value;this.multiplier.advance(a),this.multiplier.value!==u&&this.emitMultiplierChanged()}o("multiplier"),this.advanceSparkMagnetSurge(a),o("sparkMagnetSurge");let l=je(this._status,this._deathAnimTimer,e,c.death.deathAnimSeconds);this._deathAnimTimer=l.deathAnimTimer,l.completeRespawn&&this.completeRespawn(),o("deathState");let m=Ye(this._status,this.prevBombPressed,i,this.ships.length);this.prevBombPressed=m.previous;for(let u of m.slots)this.consumeBombAndDetonateAt(this.ships[u].position);if(o("bombInput"),this._status==="active"){let u=!1;for(let d=0;d<this.ships.length;d+=1){let x=this.ships[d],E=i[d],C=this.mode.fireRule==="disabled"?{...E,fireDown:!1}:E;C.fireDown&&Math.hypot(C.aimVector.x,C.aimVector.y)>1e-4&&(u=!0),this.previousShipPositions[d]={...x.position},x.update(a,C),this.emitShipTrailResidue(x,C.movementVector)}this.setWeaponFireActive(u),o("ship");for(let d=0;d<this.ships.length;d+=1){let x=this.mode.fireRule==="disabled"?!1:i[d].fireDown;this.bullets.autoFire(a,x,this.ships[d].position,i[d].aimVector,d)}o("bulletsAutoFire");let p=[];for(let d of this.enemies)if(d.entity.kind==="maw"){let x=d;if(x.pullRadiusPx>0){let E={position:x.entity.position,pullRadiusPx:x.pullRadiusPx,k:x.pullStrength};p.push(E),this.dispatchActiveMawEffects(E)}}o("mawSources");let f=this.bullets.update(a,p);for(let d of f)this.events.emit("bullet.wallImpact",{position:{...d.position}}),this.particles.emitBulletWallImpact(d.position,d.normal,"#fef3c7",this.cosmeticRng),this.springGrid&&this.springGrid.enqueueForce({kind:"explosive",origin:{...d.position},strength:w.bulletWake.strength*1.35,radius:w.bulletWake.radius*1.4});o("bulletsUpdate"),this.emitBulletTrailResidue(),o("bulletTrails"),this.advanceSparkMotion(a),o("sparks");let y=this.makeCtx();o("makePreCtx"),this.processBulletCollisions(y),o("collisions.preBulletEnemy");let b=this.makeCtx();for(let d of this.enemies)b.shipPosition=this.nearestLivingShipPosition(d.entity.position),d.update(a,b);o("enemies"),this.emitEnemyTrailResidue(),o("enemyTrails");let v=this.spawnSuppressionTimer>0?a*c.spawn.postDeathPressureScale:a;if(this.spawnSuppressionTimer=Math.max(0,this.spawnSuppressionTimer-a),this.spawnDirectorEnabled){let d=0;for(let E of this.enemies)E.entity.kind==="snake-head"&&!E.entity.dead&&(d+=1);let x=this.director.tick(v,this.spawnPressureScore(),d);for(let E of x)this.mode.enemyRoster.includes(E.kind)&&this.spawnEnemy(E.kind,E.position)}o("spawnDirector"),this.updateGateOpportunities(a),this.processGateCollisions(),this.processBombPickupCollisions(a),o("gatesPickups"),this.processBulletCollisions(b),o("collisions.postBulletEnemy"),this.processShipCollisions(!1),o("collisions.shipEnemy"),this.collectDead(),o("collectDead")}this.particles.update(a),this.bombShake.update(e),this.deathShake.update(e),o("particlesShake")}getFrameProfileSections(){return this.frameProfileSections}applyInput(e){}bombActivate(){return this._status!=="active"&&this._status!=="death-decay"&&this._status!=="spawning"?!1:this.consumeBombAndDetonateAt(this.ship.position)}consumeBombAndDetonateAt(e){if(!this.bomb.consumeBomb())return!1;let t=this.bombPickups.find(i=>i.active);return t&&(t.active=!1,this.bombPickups.splice(this.bombPickups.indexOf(t),1)),this.activateBombAt(e),!0}endRun(){if(this._status!=="run-end"){if(this._status="run-end",this.setWeaponFireActive(!1),!this._pendingShipDeathBurstEmitted)for(let e of this.ships)this.particles.emitShipDeathBurst(e.position,this.cosmeticRng),this.dispatchDeathShockwave(e.position);this.deathShake.trigger(ce),this.events.emit("run.ended",{finalScore:this._score,peakMultiplier:this.multiplier.peak,durationSeconds:this._duration})}}snapshot(){return{score:this._score,multiplier:Math.round(this.multiplier.value*100)/100,lives:this.bomb.lives,bombs:this.bomb.bombs,peakMultiplier:this.multiplier.peak,durationSeconds:this._duration,status:this._status,modeId:this.mode.id,modeLabel:this.mode.displayName,leaderboardScope:this.mode.leaderboardScope,fireDisabled:this.mode.fireRule==="disabled",timeRemainingSeconds:this.mode.timeLimitSeconds===null?null:Math.max(0,this.mode.timeLimitSeconds-this._duration),gates:this.gates.map(e=>({...e,position:{...e.position}})),bombPickups:this.bombPickups.map(e=>({...e,position:{...e.position}})),ships:this.ships.map(e=>({position:{...e.position},heading:e.heading,shield:e.shield,dying:e.dying})),localSlot:this.localSlot}}getLiveEnemyPositions(){let e=[];for(let t of this.enemies)t.entity.dead||t.entity.telegraph>0||e.push({x:t.entity.position.x,y:t.entity.position.y});return e}configureDeterministic(e,t=1){this.deterministic=!0,this.playerCount=Math.max(1,t),this.rng=Ze((e^439041101)>>>0),this.cosmeticRng=Ze((e^1374510829)>>>0),this.director=new F(this.rng),this.transitionEngine||(this.stateAuthorityEnabled=!0,this.authoritativeState=null)}serialize(){return this.authoritativeState===null?this.serializeMutable():structuredClone(this.authoritativeState)}serializeMutable(){let e=typeof this.rng.getState=="function"?this.rng.getState():void 0;return{version:1,score:this._score,status:this._status,duration:this._duration,deathAnimTimer:this._deathAnimTimer,nextEnemyId:this.nextEnemyId,nextGateId:this.nextGateId,nextBombPickupId:this.nextBombPickupId,gateSpawnTimer:this.gateSpawnTimer,spawnSuppressionTimer:this.spawnSuppressionTimer,sparkMagnetSurgeTimer:this.sparkMagnetSurgeTimer,sparkMagnetSurgeMultiplier:this.sparkMagnetSurgeMultiplier,previousShipPositions:this.previousShipPositions.map(t=>({x:t.x,y:t.y})),weaponFireActive:this.weaponFireActive,bulletTrailFrame:this.bulletTrailFrame,pendingRespawn:this._pendingRespawn,pendingShipDeathBurstEmitted:this._pendingShipDeathBurstEmitted,modeId:this.mode.id,playerCount:this.playerCount,ships:this.ships.map(t=>t.serialize()),prevBombPressed:[...this.prevBombPressed],bullets:this.bullets.serialize(),sparks:this.sparks.serialize(),bomb:this.bomb.serialize(),multiplier:this.multiplier.serialize(),director:this.director.serialize(),gates:this.gates.map($e),bombPickups:this.bombPickups.map(Qe),enemies:this.enemies.map(Be),...e!==void 0?{rngState:e}:{}}}hydrate(e){let t=typeof this.rng.setState=="function";if(e.rngState!==void 0&&!t)throw new Error("InstalledSurvivalRuntime.hydrate: snapshot carries rngState but the injected rng is stateless \u2014 inject mulberry32 or drop the injection");if(e.rngState===void 0&&t)throw new Error("InstalledSurvivalRuntime.hydrate: state-owning runtime refuses a snapshot without rngState (donor serialized with a stateless rng?)");e.rngState!==void 0&&t&&(this.rng.setState(e.rngState),this.director=new F(this.rng)),this._score=e.score,this._status=e.status,this._duration=e.duration,this._deathAnimTimer=e.deathAnimTimer,this.nextEnemyId=e.nextEnemyId,this.nextGateId=e.nextGateId,this.nextBombPickupId=e.nextBombPickupId,this.gateSpawnTimer=e.gateSpawnTimer,this.spawnSuppressionTimer=e.spawnSuppressionTimer,this.sparkMagnetSurgeTimer=e.sparkMagnetSurgeTimer,this.sparkMagnetSurgeMultiplier=e.sparkMagnetSurgeMultiplier,this.previousShipPositions=e.previousShipPositions.map(i=>({x:i.x,y:i.y})),this.weaponFireActive=e.weaponFireActive,this.bulletTrailFrame=e.bulletTrailFrame,this._pendingRespawn=e.pendingRespawn,this._pendingShipDeathBurstEmitted=e.pendingShipDeathBurstEmitted,this.mode=Q(e.modeId),this.playerCount=e.playerCount,this.ships=e.ships.map((i,n)=>{let r=new K(et(n,e.playerCount));return r.loadState(i),r}),this.prevBombPressed=[...e.prevBombPressed],this.bullets.loadState(e.bullets),this.sparks.loadState(e.sparks),this.bomb.loadState(e.bomb),this.multiplier.loadState(e.multiplier),this.director.loadState(e.director),this.gates.length=0;for(let i of e.gates)this.gates.push($e(i));this.bombPickups.length=0;for(let i of e.bombPickups)this.bombPickups.push(Qe(i));this.enemies=e.enemies.map(i=>De(i,this.rng)),this.particles.clearAll(),this.stateAuthorityEnabled&&(this.authoritativeState=structuredClone(e))}normalizeInputs(e){let t=Array.isArray(e)?e:[e],i=[];for(let n=0;n<this.ships.length;n+=1)i.push(t[n]??Lt);return i}nearestLivingShipPosition(e){let t=null,i=1/0;for(let n of this.ships){if(n.dying)continue;let r=n.position.x-e.x,o=n.position.y-e.y,a=r*r+o*o;a<i&&(i=a,t=n.position)}return t??this.ships[0].position}livingShipPositions(){let e=[];for(let t of this.ships)t.dying||e.push(t.position);return e.length===0&&e.push(this.ships[0].position),e}advanceSparkMotion(e){this.applySparkPickups(this.sparks.update(e,this.livingShipPositions(),this.sparkMagnetSurgeMultiplier))}applySparkPickups(e){e>0&&this.triggerSparkMagnetSurge(e);for(let t=0;t<e;t++){let i=this.multiplier.value;this.multiplier.collectSpark(),this.multiplier.value!==i&&(this.emitMultiplierChanged(),this.events.emit("spark.collected",{multiplier:this.multiplier.value}))}}triggerSparkMagnetSurge(e){let t=c.spark;this.sparkMagnetSurgeTimer=t.magnetSurgeDurationSeconds,this.sparkMagnetSurgeMultiplier=Math.min(t.magnetSurgeMaxMultiplier,this.sparkMagnetSurgeMultiplier+t.magnetSurgePerSpark*e)}advanceSparkMagnetSurge(e){if(this.sparkMagnetSurgeTimer<=0){this.sparkMagnetSurgeMultiplier=1;return}if(this.sparkMagnetSurgeTimer=Math.max(0,this.sparkMagnetSurgeTimer-e),this.sparkMagnetSurgeTimer===0){this.sparkMagnetSurgeMultiplier=1;return}let t=Math.exp(-e*.65);this.sparkMagnetSurgeMultiplier=1+(this.sparkMagnetSurgeMultiplier-1)*t}makeCtx(){return this.enemyUpdateCtx.shipPosition=this.livingShipPositions()[0],this.enemyUpdateCtx.bullets=this.bullets.bullets,this.enemyUpdateCtx.enemies=this.enemies,this.enemyUpdateCtx.now=this._duration,this.enemyUpdateCtx}spawnEnemy(e,t,i){if(!this.mode.enemyRoster.includes(e)&&e!=="snake-body"&&e!=="wanderer-shard")return null;let n=this.nextEnemyId++,r;switch(e){case"wanderer":r=new L(n,t,this.rng);break;case"wanderer-shard":r=new O(n,t,i);break;case"grunt":r=new B(n,t);break;case"green\u0072unner":r=new I(n,t,this.rng);break;case"cross\u0065r":r=new T(n,t,this.rng);break;case"snake-head":r=new A(n,t);break;case"maw":r=new D(n,t,this.rng),this.events.emit("maw.spawned",{position:t});break;case"spawn-pellet":r=new V(n,t,i??{x:1,y:0},this.rng);break;default:return null}return this.enemies.push(r),this.events.emit("enemy.spawned",{kind:e,position:t}),this.springGrid&&this.springGrid.enqueueForce({kind:"implosive",origin:t,strength:w.spawnImplosion.strength,radius:w.spawnImplosion.radius}),r}emitShipTrailResidue(e,t){if(e.dying||Math.hypot(t.x,t.y)<=.05)return;let n=Math.atan2(t.y,t.x);this.particles.emitTrailResidue(e.position,{count:2,color:"#7af0ff",colors:["#7af0ff","#38dff5","#bff8ff"],lifetime:300,direction:n,speed:24,spread:.22,backOffset:c.ship.visualRadius*.78,sideJitter:3,trailLength:6,trailWidth:.45,size:1.1,alpha:.38},this.cosmeticRng)}emitBulletTrailResidue(){this.bulletTrailFrame=(this.bulletTrailFrame+1)%4;let e=0;for(let t of this.bullets.bullets){if(!t.alive||!(t.age<=c.projectile.fireIntervalSeconds*1.5)&&(t.id+this.bulletTrailFrame)%4!==0)continue;let n=Math.atan2(t.velocity.y,t.velocity.x);if(this.particles.emitBulletTrail(t.position,"#fef3c7",n,this.cosmeticRng),e+=1,e>=10)break}}emitEnemyTrailResidue(){for(let e of this.enemies){if(e.entity.dead||e.entity.telegraph>0)continue;let t=Math.hypot(e.entity.velocity.x,e.entity.velocity.y);if(t<=24)continue;let i=Math.atan2(e.entity.velocity.y,e.entity.velocity.x),n=re(e.entity.hue);this.particles.emitTrailResidue(e.entity.position,{count:t>150?2:1,color:n,colors:[n,"#ffffff"],lifetime:380,direction:i,speed:30,spread:.42,backOffset:e.entity.visualRadius*.8,sideJitter:Math.max(2,e.entity.visualRadius*.18),trailLength:8,trailWidth:.72,alpha:.5},this.cosmeticRng)}}dispatchBombShockwave(e){this.springGrid&&this.springGrid.enqueueForce({kind:"explosive",origin:{...e},strength:w.bombShockwave.strength,radius:w.bombShockwave.radius})}activateBombAt(e){this.particles.emitBombCascade(e,this.cosmeticRng),this.dispatchBombShockwave(e),this.bombShake.trigger(he),this.timeScale.trigger({mode:"shake",...ye.bombActivated});for(let t of this.enemies){if(t.entity.dead||!tt(t.entity,e,Je))continue;let i={...t.entity.position},n=this.makeCtx();n.shipPosition=this.nearestLivingShipPosition(i),t.onBombKilled(n),this.sparks.dropBurst(i,this.rng),this.particles.emitKillBurst(i,re(t.entity.hue),this.cosmeticRng),this.springGrid&&this.springGrid.enqueueForce({kind:"explosive",origin:i,strength:w.enemyDeathBurst.strength,radius:w.enemyDeathBurst.radius}),this.events.emit("enemy.killed",{kind:t.entity.kind,position:i,points:0,killedByBomb:!0})}this.events.emit("bomb.activated",{position:{...e}})}dispatchDeathShockwave(e){this.springGrid&&this.springGrid.enqueueForce({kind:"explosive",origin:{...e},strength:w.deathShockwave.strength,radius:w.deathShockwave.radius})}dispatchActiveMawEffects(e){this.springGrid&&this.springGrid.enqueueForce({kind:"implosive",origin:{...e.position},strength:w.mawImplosionStrength,radius:e.pullRadiusPx})}processBulletCollisions(e){this.rebuildEnemyCollisionHash();for(let t of this.bullets.bullets){if(!t.alive)continue;let i=this.enemyCollisionHash.queryCircle(t.position,c.projectile.collisionRadius,this.enemyCollisionQuery);i.sort((n,r)=>n.order-r.order);for(let n of i)if(this.enemyCollisionTargetActive(n)){if(n.kind==="snake-segment"){if(W(t.position,c.projectile.collisionRadius,n.position,n.radius)){let r=n.enemy.onBodyBulletHit(n.segment);t.alive=!1,this.particles.emitKillBurst(n.position,re(n.segment.hue),this.cosmeticRng),this.springGrid&&this.springGrid.enqueueForce({kind:"explosive",origin:{...n.position},strength:w.enemyDeathBurst.strength*.7,radius:w.enemyDeathBurst.radius*.72});break}continue}if(W(t.position,c.projectile.collisionRadius,n.position,n.radius)){e.shipPosition=this.nearestLivingShipPosition(n.position);let r=n.enemy.onBulletHit(e);t.alive=!1,!r&&n.enemy instanceof A&&this.emitBulletBlockedImpact(t.position,t.velocity,n.position);break}}}}processShipCollisions(e=!0){let t=c.ship.collisionRadius;e&&this.rebuildEnemyCollisionHash();for(let i of this.ships){if(i.dying||i.isShielded)continue;let n=this.enemyCollisionHash.queryCircle(i.position,t,this.enemyCollisionQuery);n.sort((r,o)=>r.order-o.order);for(let r of n)if(this.enemyCollisionTargetActive(r)&&W(i.position,t,r.position,r.radius)){this.shipDied(i);return}}}rebuildEnemyCollisionHash(){this.enemyCollisionHash.clear(),this.enemyCollisionTargets.length=0;let e=0;for(let t of this.enemies){if(t.entity.dead||t.entity.telegraph>0)continue;if(t instanceof A)for(let n of t.bodySegments()){if(n.dead||n.telegraph>0)continue;let r={kind:"snake-segment",enemy:t,segment:n,position:n.position,radius:n.radius,order:e};e+=1,this.enemyCollisionTargets.push(r),this.enemyCollisionHash.insertCircle(r,r.position,r.radius)}let i={kind:"enemy",enemy:t,position:t.entity.position,radius:t.entity.radius,order:e};e+=1,this.enemyCollisionTargets.push(i),this.enemyCollisionHash.insertCircle(i,i.position,i.radius)}}enemyCollisionTargetActive(e){return e.kind==="snake-segment"?!e.enemy.entity.dead&&!e.segment.dead&&e.segment.telegraph<=0:!e.enemy.entity.dead&&e.enemy.entity.telegraph<=0}emitBulletBlockedImpact(e,t,i){let n={x:e.x-i.x,y:e.y-i.y},r=Math.hypot(n.x,n.y),o=n;r<.001&&(o={x:-t.x,y:-t.y},r=Math.hypot(o.x,o.y));let a=r>.001?{x:o.x/r,y:o.y/r}:{x:1,y:0};this.events.emit("bullet.blocked",{position:{...e}}),this.particles.emitBulletWallImpact(e,a,"#fef3c7",this.cosmeticRng),this.springGrid&&this.springGrid.enqueueForce({kind:"explosive",origin:{...e},strength:w.bulletWake.strength*1.35,radius:w.bulletWake.radius*1.4})}updateGateOpportunities(e){if(this.mode.spawnProfile!=="pursuer-gates")return;for(let r of this.gates)r.age+=e,r.telegraph=Math.max(0,r.telegraph-e),r.active=r.telegraph<=0&&r.age<12,r.angle+=r.angularVelocity*e,r.position.x+=r.driftVelocity.x*e,r.position.y+=r.driftVelocity.y*e,it(r);for(let r=this.gates.length-1;r>=0;r--){let o=this.gates[r];o&&o.age>=14&&this.gates.splice(r,1)}if(!this.spawnDirectorEnabled||(this.gateSpawnTimer-=e,this.gates.length>=2||this.gateSpawnTimer>0))return;this.gateSpawnTimer=4.5;let t=1280*(.28+this.rng()*.44),i=720*(.28+this.rng()*.44),n=this.rng()*Math.PI;this.spawnGate({x:t,y:i},n)}spawnGate(e,t){let i=this.rng()*Math.PI*2,n=R.driftSpeedMinPxPerSec+this.rng()*(R.driftSpeedMaxPxPerSec-R.driftSpeedMinPxPerSec),r=this.rng()<.5?-1:1,o=R.angularSpeedMinRadPerSec+this.rng()*(R.angularSpeedMaxRadPerSec-R.angularSpeedMinRadPerSec),a={id:this.nextGateId++,position:{...e},angle:t,driftVelocity:{x:Math.cos(i)*n,y:Math.sin(i)*n},angularVelocity:r*o,length:R.length,radius:R.radius,postRadius:R.postRadius,telegraph:R.telegraphSeconds,age:0,active:!1};return it(a),this.gates.push(a),a}spawnBombPickup(e=this.chooseBombPickupPosition()){let t={id:this.nextBombPickupId++,position:{...e},radius:fe.radius,age:0,active:!0};return this.bombPickups.push(t),t}chooseBombPickupPosition(){let e=fe.spawnMargin,t=this.nextBombPickupId*2.399963229728653+this.rng()*.42,i=fe.spawnRingRadius+(this.rng()-.5)*54,n=this.livingShipPositions()[0];return{x:Math.max(e,Math.min(1280-e,n.x+Math.cos(t)*i)),y:Math.max(e,Math.min(720-e,n.y+Math.sin(t)*i))}}processGateCollisions(){if(this.gates.length===0)return;let e=c.ship.collisionRadius;for(let t=0;t<this.ships.length;t+=1){let i=this.ships[t];if(i.dying)continue;let n=this.previousShipPositions[t]??i.position,r=i.position;for(let o=this.gates.length-1;o>=0;o-=1){let a=this.gates[o];if(!a||!a.active)continue;let h=Gt(a);if(N(h.start,n,r)<=a.postRadius+e||N(h.end,n,r)<=a.postRadius+e){this.shipDied(i);return}if(Math.hypot(r.x-n.x,r.y-n.y)>.5&&Ft(n,r,h.start,h.end)<=a.radius+e){this.gates.splice(o,1),this.gateSpawnTimer=Math.max(this.gateSpawnTimer,2.75),this.events.emit("gate.passed",{position:{...a.position}}),this.activateGateAt(a.position);return}}}}activateGateAt(e){this.particles.emitBombCascade(e,this.cosmeticRng),this.dispatchBombShockwave(e),this.bombShake.trigger(he),this.timeScale.trigger({mode:"shake",...ye.bombActivated});for(let t of this.enemies)t.entity.dead||t.entity.telegraph>0||tt(t.entity,e,Je)&&(t.entity.dead=!0);this.collectDead(),this.events.emit("bomb.activated",{position:{...e}})}processBombPickupCollisions(e){if(this.bombPickups.length===0)return;let t=c.ship.collisionRadius;e:for(let i of this.bombPickups)if(i.active){i.age+=e;for(let n of this.ships)if(!n.dying&&W(n.position,t,i.position,i.radius)){i.active=!1,this.events.emit("bomb.pickup",{position:{...i.position}}),this.bomb.consumeBomb()&&this.activateBombAt(i.position);break e}}for(let i=this.bombPickups.length-1;i>=0;i-=1)this.bombPickups[i]?.active||this.bombPickups.splice(i,1)}shipDied(e){if(this._pendingRespawn||this._status==="run-end")return;this._pendingRespawn=!0,this.setWeaponFireActive(!1),e.beginDeathAnim(),this.timeScale.trigger({mode:"shake",...ye.shipDeath}),this.multiplier.resetCurrentValue(),this.emitMultiplierChanged(),this.sparks.fadeAll(),this.spawnSuppressionTimer=c.spawn.postDeathSuppressionSeconds,this.particles.emitShipDeathBurst(e.position,this.cosmeticRng),this.dispatchDeathShockwave(e.position),this._pendingShipDeathBurstEmitted=!0;let t=this.mode.finiteLives?this.bomb.consumeLife():!1;this.events.emit("ship.hit",{position:{...e.position},finalDeath:t,livesRemaining:this.bomb.lives}),t?(this._status="death-pending",this._deathAnimTimer=0):(this._status="death-decay",this._deathAnimTimer=0)}completeRespawn(){if(this._status==="death-pending"){this.endRun();return}this.multiplier.lockOnRespawn(),this.emitMultiplierChanged();for(let e of this.ships)e.dying&&e.respawn();this._pendingRespawn=!1,this._pendingShipDeathBurstEmitted=!1,this._status="active",this._deathAnimTimer=0;for(let e of this.enemies)e.entity.dead||(e.entity.dead=!0);this.collectDead()}collectDead(){let e=[];for(let t of this.enemies)if(t.entity.dead){if(!t.entity.killedByBomb&&t.entity.kind!=="snake-body"){let i=Math.floor(t.basePoints()*this.multiplier.value);i>0&&(this.addScore(i),this.events.emit("enemy.killed",{kind:t.entity.kind,position:{...t.entity.position},points:i,killedByBomb:!1}),this.sparks.dropBurst(t.entity.position,this.rng),this.particles.emitKillBurst(t.entity.position,re(t.entity.hue),this.cosmeticRng),this.springGrid&&this.springGrid.enqueueForce({kind:"explosive",origin:{...t.entity.position},strength:w.enemyDeathBurst.strength,radius:w.enemyDeathBurst.radius}))}}else e.push(t);this.enemies=e,this.applySparkPickups(this.sparks.update(0,this.livingShipPositions()))}emitMultiplierChanged(){this.events.emit("multiplier.changed",{value:this.multiplier.value,peak:this.multiplier.peak})}setWeaponFireActive(e){this.weaponFireActive!==e&&(this.weaponFireActive=e,this.events.emit("weapon.fireStateChanged",{active:e}))}addScore(e){this._score+=e;let t=this.bomb.bombs;this.bomb.onScoreChanged(this._score,{earnBombEveryScore:this.mode.earnBombEveryScore,earnLifeEveryScore:this.mode.earnLifeEveryScore});let i=this.bomb.bombs-t;for(let n=0;n<i;n+=1)this.spawnBombPickup();this.events.emit("score.changed",{value:this._score,delta:e})}spawnPressureScore(){let e=this._duration*1800;return this.mode.spawnProfile==="timed-pressure"?Math.max(this._score,this._duration*2400):Math.max(this._score,e)}testHelpers={spawn:(e,t)=>{this.spawnEnemy(e,t)},spawnBombPickup:e=>{this.spawnBombPickup(e)},killAllEnemiesByBullet:()=>{let e=this.makeCtx();for(let t of[...this.enemies])!t.entity.dead&&t.entity.telegraph===0&&(e.shipPosition=this.nearestLivingShipPosition(t.entity.position),t.onBulletHit(e));this.collectDead()},clearTelegraphs:()=>{for(let e of this.enemies)if(!e.entity.dead&&(e.entity.telegraph=0,e instanceof A))for(let t of e.bodySegments())t.telegraph=0},activeEnemyCount:()=>this.enemies.filter(e=>!e.entity.dead).length,snakeBodyAliveCount:()=>this.enemies.reduce((e,t)=>!(t instanceof A)||t.entity.dead?e:e+t.bodySegments().filter(i=>!i.dead).length,0),activeBulletCount:()=>this.bullets.bullets.filter(e=>e.alive).length,spawnGate:(e,t=0)=>{this.spawnGate(e,t)},armGates:()=>{for(let e of this.gates)e.telegraph=0,e.active=!0},setShipPosition:e=>{let t=Math.max(0,this.ships.indexOf(this.ship));this.ship.position={...e},this.previousShipPositions[t]={...e}},setScore:e=>{let t=e-this._score;t>0&&this.addScore(t)},collectSpark:()=>{this.triggerSparkMagnetSurge(1),this.multiplier.collectSpark(),this.emitMultiplierChanged(),this.events.emit("spark.collected",{multiplier:this.multiplier.value})},sparkMagnetMultiplier:()=>this.sparkMagnetSurgeMultiplier,spawnSuppressionSeconds:()=>this.spawnSuppressionTimer,killShipNTimes:e=>{for(let t=0;t<e;t++)if(this.shipDied(this.ship),this._deathAnimTimer=c.death.deathAnimSeconds,this._status==="death-pending"){this.endRun();return}else this._status==="death-decay"&&this.completeRespawn()},beginFinalShipDeath:()=>{for(;this.bomb.lives>1;)this.bomb.consumeLife();this.shipDied(this.ship)},setLives:e=>{this.bomb.resetWithCounts({bombs:this.bomb.bombs,lives:Math.max(0,e)})},emitDeathSpectacleProof:()=>{this.particles.emitShipDeathBurst(this.ship.position,this.cosmeticRng),this.dispatchDeathShockwave(this.ship.position),this.deathShake.trigger(ce)}}};function re(s){let e=(s%360+360)%360,t=.85,i=.6,n=(1-Math.abs(2*i-1))*t,r=e/60,o=n*(1-Math.abs(r%2-1)),a=0,h=0,l=0;r<1?(a=n,h=o):r<2?(a=o,h=n):r<3?(h=n,l=o):r<4?(h=o,l=n):r<5?(a=o,l=n):(a=n,l=o);let m=i-n/2,u=p=>Math.round((p+m)*255).toString(16).padStart(2,"0");return`#${u(a)}${u(h)}${u(l)}`}function tt(s,e,t){return Math.hypot(s.position.x-e.x,s.position.y-e.y)<=t+s.radius}function Gt(s){let e=s.length/2,t=Math.cos(s.angle),i=Math.sin(s.angle);return{start:{x:s.position.x-t*e,y:s.position.y-i*e},end:{x:s.position.x+t*e,y:s.position.y+i*e}}}function it(s){let e=s.length/2,t=Math.cos(s.angle),i=Math.sin(s.angle),n=Math.abs(t)*e+s.postRadius,r=Math.abs(i)*e+s.postRadius;s.position.x=Math.max(n,Math.min(1280-n,s.position.x)),s.position.y=Math.max(r,Math.min(720-r,s.position.y))}function N(s,e,t){let i=e.x,n=e.y,r=t.x,o=t.y,a=r-i,h=o-n,l=s.x-i,m=s.y-n,u=a*a+h*h,p=u<=0?0:Math.max(0,Math.min(1,(l*a+m*h)/u)),f=i+a*p,y=n+h*p;return Math.hypot(s.x-f,s.y-y)}function Ft(s,e,t,i){return Nt(s,e,t,i)?0:Math.min(N(s,t,i),N(e,t,i),N(t,s,e),N(i,s,e))}function Nt(s,e,t,i){let n=oe(s,e,t),r=oe(s,e,i),o=oe(t,i,s),a=oe(t,i,e);return n===0&&ae(t,s,e)||r===0&&ae(i,s,e)||o===0&&ae(s,t,i)||a===0&&ae(e,t,i)?!0:n!==r&&o!==a}function oe(s,e,t){let i=(e.x-s.x)*(t.y-s.y)-(e.y-s.y)*(t.x-s.x);return Math.abs(i)<1e-6?0:i>0?1:-1}function ae(s,e,t){return s.x>=Math.min(e.x,t.x)-1e-6&&s.x<=Math.max(e.x,t.x)+1e-6&&s.y>=Math.min(e.y,t.y)-1e-6&&s.y<=Math.max(e.y,t.y)+1e-6}export{ve as InstalledSurvivalRuntime,be as TypedEventBus,Vt as advanceInstalledSurvivalState,Ze as mulberry32,et as shipSpawnPositionForSlot};
|