@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.
Files changed (58) hide show
  1. package/README.md +77 -0
  2. package/authoring.d.ts +30 -0
  3. package/index.d.ts +221 -0
  4. package/native/component_runtime.cpp +341 -0
  5. package/native/component_runtime.hpp +112 -0
  6. package/native/deterministic_math.cpp +594 -0
  7. package/native/deterministic_math.hpp +21 -0
  8. package/native/kinetix-f64-native-profile.cpp +406 -0
  9. package/native/kinetix-f64-native-profile.hpp +5 -0
  10. package/native/kinetix-fixed1000-native-profile.cpp +777 -0
  11. package/native/kinetix-fixed1000-native-profile.hpp +58 -0
  12. package/native/kinetix-fixed1000-physics.cpp +887 -0
  13. package/native/kinetix-fixed1000-physics.hpp +28 -0
  14. package/native/kinetix-installed-f64-renderer.cpp +344 -0
  15. package/native/kinetix-installed-f64-renderer.hpp +35 -0
  16. package/native/kinetix-installed-f64-runtime.cpp +1085 -0
  17. package/native/kinetix-installed-f64-runtime.hpp +141 -0
  18. package/native/kinetix-installed-fixed1000-data.hpp +77 -0
  19. package/native/kinetix-native-main.cpp +37 -0
  20. package/native/kinetix-native-runtime.cpp +20 -0
  21. package/native/kinetix-native-runtime.hpp +25 -0
  22. package/native/kinetix-render-projection.cpp +20 -0
  23. package/native/kinetix-render-projection.hpp +14 -0
  24. package/package.json +65 -0
  25. package/runtime.d.ts +76 -0
  26. package/scripts/build-native.mjs +67 -0
  27. package/scripts/emit-product-digests.mjs +33 -0
  28. package/scripts/preflight.mjs +76 -0
  29. package/src/index.mjs +57 -0
  30. package/src/kinetix-authoring.mjs +69 -0
  31. package/src/kinetix-baker.mjs +587 -0
  32. package/src/kinetix-deterministic-math.mjs +1044 -0
  33. package/src/kinetix-fixed1000-runtime-adapter.mjs +33 -0
  34. package/src/kinetix-fixed1000-runtime.mjs +954 -0
  35. package/src/kinetix-installed-f64-reference.mjs +157 -0
  36. package/src/kinetix-installed-f64-render-frames.mjs +53 -0
  37. package/src/kinetix-installed-f64-renderer.mjs +240 -0
  38. package/src/kinetix-installed-f64-runtime-adapter.mjs +68 -0
  39. package/src/kinetix-installed-f64-runtime.mjs +607 -0
  40. package/src/kinetix-installed-mechanics.mjs +377 -0
  41. package/src/kinetix-installed-systems.mjs +181 -0
  42. package/src/kinetix-native-product.mjs +72 -0
  43. package/src/kinetix-product-contract.mjs +121 -0
  44. package/src/kinetix-project-compiler.mjs +1017 -0
  45. package/src/kinetix-render-projection.mjs +28 -0
  46. package/src/kinetix-runtime-adapter-utils.mjs +168 -0
  47. package/src/kinetix-runtime-contract.mjs +54 -0
  48. package/src/kinetix-session-config.mjs +170 -0
  49. package/src/kinetix-source-snapshot.mjs +24 -0
  50. package/src/kinetix-survival-runtime-adapter.mjs +305 -0
  51. package/src/kinetix-survival-runtime.generated.mjs +1 -0
  52. package/src/kinetix-world-kernel.mjs +580 -0
  53. package/src/kinetix-world-runtime.mjs +171 -0
  54. package/src/runtime.mjs +14 -0
  55. package/src/shared/f64-bits.mjs +14 -0
  56. package/src/shared/kinetix-envelope-v1.mjs +589 -0
  57. package/src/shared/render-records-v1.mjs +168 -0
  58. package/src/shared/sha256.mjs +73 -0
@@ -0,0 +1,377 @@
1
+ export const kinetixMechanicEventBits = Object.freeze({
2
+ phase: 1,
3
+ spawn: 2,
4
+ score: 4,
5
+ bomb: 8,
6
+ multiplier: 16,
7
+ boundary: 32,
8
+ stagedAxis: 64,
9
+ targetDrift: 128,
10
+ gravityWell: 256,
11
+ followChain: 512,
12
+ growth: 1024,
13
+ randomSteering: 2048,
14
+ projectile: 4096,
15
+ driveFire: 8192,
16
+ pickup: 16384,
17
+ death: 32768,
18
+ respawn: 65536,
19
+ });
20
+
21
+ export const kinetixRequiredMechanicEventMask = Object.values(kinetixMechanicEventBits)
22
+ .reduce((mask, bit) => mask | bit, 0);
23
+
24
+ export const kinetixInstalledMechanicFamilyOrder = Object.freeze([
25
+ 'phase-spawn-score-policy',
26
+ 'consumable-charge-economy',
27
+ 'score-multiplier-decay',
28
+ 'boundary-crossing-oscillator',
29
+ 'staged-axis-runner',
30
+ 'target-seeking-drift',
31
+ 'gravity-well-state-machine',
32
+ 'segmented-follow-chain',
33
+ 'spawn-pellet-growth',
34
+ 'bounded-random-steering',
35
+ 'projectile-lifecycle-collision',
36
+ 'axis-drive-fire-control',
37
+ 'pickup-drift-lifecycle',
38
+ ]);
39
+
40
+ export function decodeInstalledMechanicConfig(config, decodeF64) {
41
+ const decode = (value) => {
42
+ if (typeof value === 'string') return decodeF64(value);
43
+ if (Array.isArray(value)) return value.map(decode);
44
+ if (typeof value === 'object' && value !== null) {
45
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, decode(entry)]));
46
+ }
47
+ return value;
48
+ };
49
+ return decode(config);
50
+ }
51
+
52
+ export function createInstalledMechanicState(config) {
53
+ return {
54
+ tick: 0,
55
+ phase: 0,
56
+ phaseElapsed: 0,
57
+ deathElapsed: 0,
58
+ deathOccurred: false,
59
+ spawnCooldown: 0,
60
+ score: 0,
61
+ charges: config.charge.startCharges,
62
+ lives: config.charge.startLives,
63
+ chargesEarned: 0,
64
+ livesEarned: 0,
65
+ previousBomb: false,
66
+ multiplier: 1,
67
+ multiplierPeak: 1,
68
+ multiplierDecayFrom: 1,
69
+ multiplierDecayElapsed: 0,
70
+ multiplierDecaying: false,
71
+ oscillatorX: config.boundary.min,
72
+ oscillatorY: 0,
73
+ oscillatorDirection: 1,
74
+ stagedX: 0,
75
+ stagedY: 0,
76
+ stagedMode: 0,
77
+ targetX: config.target.startX,
78
+ targetY: config.target.startY,
79
+ targetAge: 0,
80
+ gravityRadius: 0,
81
+ gravityHp: config.gravity.hitPoints,
82
+ gravityIngested: 0,
83
+ gravityStage: 0,
84
+ chainHeadX: 0,
85
+ chainHeadY: 0,
86
+ chainTailX: -config.follow.spacing * config.follow.segments,
87
+ chainTailY: 0,
88
+ growthAge: 0,
89
+ growthRadius: config.growth.startRadius,
90
+ growthCycles: 0,
91
+ randomX: config.random.startX,
92
+ randomY: config.random.startY,
93
+ randomVx: config.random.speed,
94
+ randomVy: 0,
95
+ randomTimer: 0,
96
+ randomState: config.random.seed >>> 0,
97
+ projectileX: 0,
98
+ projectileY: 0,
99
+ projectileVx: 0,
100
+ projectileVy: 0,
101
+ projectileAge: 0,
102
+ projectileActive: false,
103
+ shipX: config.drive.startX,
104
+ shipY: config.drive.startY,
105
+ fireCooldown: 0,
106
+ shotsFired: 0,
107
+ pickupX: config.pickup.startX,
108
+ pickupY: config.pickup.startY,
109
+ pickupAge: 0,
110
+ pickupActive: true,
111
+ eventMask: 0,
112
+ };
113
+ }
114
+
115
+ function clamp(value, min, max) {
116
+ return Math.max(min, Math.min(max, value));
117
+ }
118
+
119
+ function normalized(vector) {
120
+ const magnitude = Math.hypot(vector.x, vector.y);
121
+ if (magnitude <= 0.0001) return { x: 0, y: 0 };
122
+ return { x: vector.x / magnitude, y: vector.y / magnitude };
123
+ }
124
+
125
+ function nextRandom(state) {
126
+ return (Math.imul(state, 1664525) + 1013904223) >>> 0;
127
+ }
128
+
129
+ export function stepInstalledMechanics(current, input, config, dt) {
130
+ const state = structuredClone(current);
131
+ state.tick += 1;
132
+ state.phaseElapsed += dt;
133
+
134
+ let scored = false;
135
+ if (state.phase === 0 && state.phaseElapsed >= config.phase.spawnDelay) {
136
+ state.phase = 1;
137
+ state.spawnCooldown = 0;
138
+ state.eventMask |= kinetixMechanicEventBits.phase;
139
+ }
140
+ if (state.phase === 1) {
141
+ state.spawnCooldown -= dt;
142
+ while (state.spawnCooldown <= 0) {
143
+ state.spawnCooldown += config.phase.spawnInterval;
144
+ state.score += config.phase.scorePerSpawn;
145
+ scored = true;
146
+ state.eventMask |= kinetixMechanicEventBits.spawn | kinetixMechanicEventBits.score;
147
+ }
148
+ if (!state.deathOccurred && state.phaseElapsed >= config.phase.deathAt) {
149
+ state.phase = 2;
150
+ state.deathElapsed = 0;
151
+ state.deathOccurred = true;
152
+ state.lives = Math.max(0, state.lives - 1);
153
+ state.multiplierDecaying = true;
154
+ state.multiplierDecayFrom = state.multiplier;
155
+ state.multiplierDecayElapsed = 0;
156
+ state.eventMask |= kinetixMechanicEventBits.death;
157
+ }
158
+ } else if (state.phase === 2) {
159
+ state.deathElapsed += dt;
160
+ if (state.deathElapsed >= config.phase.respawnDelay) {
161
+ state.phase = 1;
162
+ state.multiplierDecaying = false;
163
+ state.eventMask |= kinetixMechanicEventBits.respawn;
164
+ }
165
+ }
166
+
167
+ const chargeMilestones = Math.floor(state.score / config.charge.chargeEveryScore);
168
+ if (chargeMilestones > state.chargesEarned) {
169
+ state.charges = Math.min(
170
+ config.charge.maxCharges,
171
+ state.charges + chargeMilestones - state.chargesEarned,
172
+ );
173
+ state.chargesEarned = chargeMilestones;
174
+ }
175
+ const lifeMilestones = Math.floor(state.score / config.charge.lifeEveryScore);
176
+ if (lifeMilestones > state.livesEarned) {
177
+ state.lives += lifeMilestones - state.livesEarned;
178
+ state.livesEarned = lifeMilestones;
179
+ }
180
+ if (input.bomb && !state.previousBomb && state.charges > 0) {
181
+ state.charges -= 1;
182
+ state.eventMask |= kinetixMechanicEventBits.bomb;
183
+ }
184
+ state.previousBomb = input.bomb;
185
+
186
+ if (scored && !state.multiplierDecaying) {
187
+ state.multiplier = Math.min(config.multiplier.cap, state.multiplier + config.multiplier.increment);
188
+ state.multiplierPeak = Math.max(state.multiplierPeak, state.multiplier);
189
+ state.eventMask |= kinetixMechanicEventBits.multiplier;
190
+ }
191
+ if (state.multiplierDecaying) {
192
+ state.multiplierDecayElapsed += dt;
193
+ if (state.multiplierDecayElapsed >= config.multiplier.decayWindow) {
194
+ state.multiplier = 1;
195
+ state.multiplierDecaying = false;
196
+ } else {
197
+ const progress = state.multiplierDecayElapsed / config.multiplier.decayWindow;
198
+ state.multiplier = state.multiplierDecayFrom + (1 - state.multiplierDecayFrom) * progress;
199
+ }
200
+ }
201
+
202
+ state.oscillatorX += state.oscillatorDirection * config.boundary.speed * dt;
203
+ state.oscillatorY += state.oscillatorDirection * config.boundary.lateralSpeed * dt;
204
+ if (state.oscillatorX <= config.boundary.min || state.oscillatorX >= config.boundary.max) {
205
+ state.oscillatorX = clamp(state.oscillatorX, config.boundary.min, config.boundary.max);
206
+ state.oscillatorDirection *= -1;
207
+ }
208
+ state.eventMask |= kinetixMechanicEventBits.boundary;
209
+
210
+ if (state.stagedMode === 0) {
211
+ state.stagedX += config.staged.approachSpeed * dt;
212
+ if (state.stagedX >= config.staged.switchX) state.stagedMode = 1;
213
+ } else {
214
+ state.stagedY += (input.fire ? -1 : 1) * config.staged.dodgeSpeed * dt;
215
+ }
216
+ state.eventMask |= kinetixMechanicEventBits.stagedAxis;
217
+
218
+ state.targetAge += dt;
219
+ const targetProgress = clamp(
220
+ (state.targetAge - config.target.accelStart) / (config.target.accelEnd - config.target.accelStart),
221
+ 0,
222
+ 1,
223
+ );
224
+ const targetSpeed = config.target.baseSpeed
225
+ + (config.target.topSpeed - config.target.baseSpeed) * targetProgress;
226
+ const targetDirection = normalized({ x: state.shipX - state.targetX, y: state.shipY - state.targetY });
227
+ state.targetX += targetDirection.x * targetSpeed * dt;
228
+ state.targetY += targetDirection.y * targetSpeed * dt;
229
+ state.eventMask |= kinetixMechanicEventBits.targetDrift;
230
+
231
+ if (state.gravityStage === 0) {
232
+ state.gravityRadius = Math.min(
233
+ config.gravity.activeRadius,
234
+ state.gravityRadius + (config.gravity.activeRadius / config.gravity.growSeconds) * dt,
235
+ );
236
+ if (state.gravityRadius >= config.gravity.activeRadius) state.gravityStage = 1;
237
+ } else if (state.gravityStage === 1) {
238
+ if (scored) state.gravityIngested += 1;
239
+ if (input.fire && state.tick % 30 === 0) state.gravityHp -= 1;
240
+ if (state.gravityHp <= 0) state.gravityStage = 2;
241
+ }
242
+ state.eventMask |= kinetixMechanicEventBits.gravityWell;
243
+
244
+ const chainDirection = normalized({ x: state.shipX - state.chainHeadX, y: state.shipY - state.chainHeadY });
245
+ state.chainHeadX += chainDirection.x * config.follow.speed * dt;
246
+ state.chainHeadY += chainDirection.y * config.follow.speed * dt;
247
+ const followAlpha = Math.min(1, config.follow.followRate * dt);
248
+ const desiredTailX = state.chainHeadX - chainDirection.x * config.follow.spacing * config.follow.segments;
249
+ const desiredTailY = state.chainHeadY - chainDirection.y * config.follow.spacing * config.follow.segments;
250
+ state.chainTailX += (desiredTailX - state.chainTailX) * followAlpha;
251
+ state.chainTailY += (desiredTailY - state.chainTailY) * followAlpha;
252
+ state.eventMask |= kinetixMechanicEventBits.followChain;
253
+
254
+ state.growthAge += dt;
255
+ state.growthRadius = config.growth.startRadius + (
256
+ config.growth.endRadius - config.growth.startRadius
257
+ ) * Math.min(1, state.growthAge / config.growth.growSeconds);
258
+ if (state.growthAge >= config.growth.lifetime) {
259
+ state.growthAge -= config.growth.lifetime;
260
+ state.growthRadius = config.growth.startRadius;
261
+ state.growthCycles += 1;
262
+ }
263
+ state.eventMask |= kinetixMechanicEventBits.growth;
264
+
265
+ state.randomTimer -= dt;
266
+ if (state.randomTimer <= 0) {
267
+ state.randomTimer += config.random.rerollSeconds;
268
+ state.randomState = nextRandom(state.randomState);
269
+ state.randomVx = ((state.randomState / 4294967296) * 2 - 1) * config.random.speed;
270
+ state.randomState = nextRandom(state.randomState);
271
+ state.randomVy = ((state.randomState / 4294967296) * 2 - 1) * config.random.speed;
272
+ }
273
+ state.randomX += state.randomVx * dt;
274
+ state.randomY += state.randomVy * dt;
275
+ if (state.randomX <= config.random.minX || state.randomX >= config.random.maxX) {
276
+ state.randomX = clamp(state.randomX, config.random.minX, config.random.maxX);
277
+ state.randomVx *= -1;
278
+ }
279
+ if (state.randomY <= config.random.minY || state.randomY >= config.random.maxY) {
280
+ state.randomY = clamp(state.randomY, config.random.minY, config.random.maxY);
281
+ state.randomVy *= -1;
282
+ }
283
+ state.eventMask |= kinetixMechanicEventBits.randomSteering;
284
+
285
+ const movement = normalized(input.movement);
286
+ state.shipX = clamp(
287
+ state.shipX + movement.x * config.drive.maxSpeed * dt,
288
+ config.drive.minX,
289
+ config.drive.maxX,
290
+ );
291
+ state.shipY = clamp(
292
+ state.shipY + movement.y * config.drive.maxSpeed * dt,
293
+ config.drive.minY,
294
+ config.drive.maxY,
295
+ );
296
+ state.fireCooldown -= dt;
297
+ const aim = normalized(input.aim);
298
+ if (input.fire && (aim.x !== 0 || aim.y !== 0) && state.fireCooldown <= 0) {
299
+ state.fireCooldown += config.drive.fireInterval;
300
+ state.shotsFired += 1;
301
+ if (!state.projectileActive) {
302
+ state.projectileActive = true;
303
+ state.projectileX = state.shipX;
304
+ state.projectileY = state.shipY;
305
+ state.projectileVx = aim.x * config.projectile.speed;
306
+ state.projectileVy = aim.y * config.projectile.speed;
307
+ state.projectileAge = 0;
308
+ }
309
+ }
310
+ state.eventMask |= kinetixMechanicEventBits.driveFire;
311
+
312
+ if (state.projectileActive) {
313
+ const gravityDx = state.targetX - state.projectileX;
314
+ const gravityDy = state.targetY - state.projectileY;
315
+ const gravityDistanceSq = gravityDx * gravityDx + gravityDy * gravityDy;
316
+ const gravityDistance = Math.sqrt(gravityDistanceSq);
317
+ if (gravityDistance > 0 && gravityDistance <= state.gravityRadius) {
318
+ const force = config.gravity.pullStrength / Math.max(1, gravityDistanceSq);
319
+ state.projectileVx += (gravityDx / gravityDistance) * force * config.projectile.gravityScale * dt;
320
+ state.projectileVy += (gravityDy / gravityDistance) * force * config.projectile.gravityScale * dt;
321
+ }
322
+ state.projectileX += state.projectileVx * dt;
323
+ state.projectileY += state.projectileVy * dt;
324
+ state.projectileAge += dt;
325
+ if (state.projectileAge >= config.projectile.maxAge
326
+ || state.projectileX < config.projectile.minX
327
+ || state.projectileX > config.projectile.maxX
328
+ || state.projectileY < config.projectile.minY
329
+ || state.projectileY > config.projectile.maxY) {
330
+ state.projectileActive = false;
331
+ }
332
+ }
333
+ state.eventMask |= kinetixMechanicEventBits.projectile;
334
+
335
+ if (state.pickupActive) {
336
+ state.pickupAge += dt;
337
+ state.pickupX += config.pickup.driftSpeed * dt;
338
+ const pickupDistance = Math.hypot(state.pickupX - state.shipX, state.pickupY - state.shipY);
339
+ if (pickupDistance <= config.pickup.collectRadius || state.pickupAge >= config.pickup.lifetime) {
340
+ state.pickupActive = false;
341
+ state.eventMask |= kinetixMechanicEventBits.pickup;
342
+ }
343
+ }
344
+
345
+ return state;
346
+ }
347
+
348
+ export function canonicalInstalledMechanicState(state) {
349
+ return {
350
+ tick: state.tick,
351
+ eventMask: state.eventMask,
352
+ score: state.score,
353
+ charges: state.charges,
354
+ lives: state.lives,
355
+ shotsFired: state.shotsFired,
356
+ growthCycles: state.growthCycles,
357
+ };
358
+ }
359
+
360
+ export function installedMechanicReplayInput(tick) {
361
+ return {
362
+ movement: tick < 180 ? { x: 1, y: 0 } : { x: -1, y: 0.25 },
363
+ aim: { x: 1, y: tick % 120 < 60 ? 0.25 : -0.25 },
364
+ fire: tick % 3 !== 0,
365
+ bomb: tick === 45 || tick === 300,
366
+ };
367
+ }
368
+
369
+ export function runInstalledMechanicReplay(config, ticks = 600) {
370
+ let state = createInstalledMechanicState(config);
371
+ const states = [];
372
+ for (let tick = 0; tick < ticks; tick += 1) {
373
+ state = stepInstalledMechanics(state, installedMechanicReplayInput(tick), config, 1 / 60);
374
+ states.push(canonicalInstalledMechanicState(state));
375
+ }
376
+ return { state, bytes: Buffer.from(JSON.stringify(states)) };
377
+ }
@@ -0,0 +1,181 @@
1
+ import {
2
+ createFixedRuntime,
3
+ executeFixedRuntimeSystem,
4
+ } from './kinetix-fixed1000-runtime.mjs';
5
+
6
+ function family({ id, phase, order, reads, writes, numericProfile, structuralCommands = [] }) {
7
+ return Object.freeze({
8
+ id,
9
+ phase,
10
+ order,
11
+ reads: Object.freeze(reads),
12
+ writes: Object.freeze(writes),
13
+ numericProfiles: Object.freeze([numericProfile]),
14
+ structuralCommands: Object.freeze(structuralCommands),
15
+ });
16
+ }
17
+
18
+ const fixed = 'fixed-1000-3d';
19
+ const f64 = 'deterministic-f64';
20
+
21
+ export const kinetixInstalledSystemFamilies = Object.freeze([
22
+ family({ id: 'slot-input-resolution', phase: 'fixed-simulation', order: 100, reads: ['InputFrame', 'SlotState'], writes: ['VehicleInput', 'SlotState'], numericProfile: fixed }),
23
+ family({ id: 'vehicle-drive-body-build', phase: 'fixed-simulation', order: 200, reads: ['VehicleInput', 'FixedTransform', 'ColliderRecipe'], writes: ['RigidBody3D'], numericProfile: fixed }),
24
+ family({ id: 'vehicle-physics-step', phase: 'fixed-simulation', order: 300, reads: ['RigidBody3D', 'ColliderRecipe'], writes: ['RigidBody3D', 'VehicleContactState', 'FixedTransform'], numericProfile: fixed }),
25
+ family({ id: 'vehicle-lap-finish', phase: 'fixed-simulation', order: 400, reads: ['FixedTransform', 'TrackCells', 'LapState'], writes: ['LapState', 'FinishState'], numericProfile: fixed }),
26
+ family({ id: 'match-phase-transition', phase: 'post-simulation', order: 500, reads: ['SlotState', 'FinishState', 'MatchPhase'], writes: ['MatchPhase'], numericProfile: fixed }),
27
+ family({ id: 'arena-input-mapping', phase: 'fixed-simulation', order: 100, reads: ['InputFrame'], writes: ['DriveIntent', 'FireIntent'], numericProfile: f64 }),
28
+ family({ id: 'arena-countdowns', phase: 'fixed-simulation', order: 200, reads: ['Countdown'], writes: ['Countdown'], numericProfile: f64 }),
29
+ family({ id: 'arena-axis-drive', phase: 'fixed-simulation', order: 300, reads: ['DriveIntent', 'Transform2D'], writes: ['Velocity2D'], numericProfile: f64 }),
30
+ family({ id: 'arena-autofire', phase: 'fixed-simulation', order: 400, reads: ['FireIntent', 'Transform2D', 'Countdown'], writes: ['Countdown'], numericProfile: f64, structuralCommands: ['spawnPrefab'] }),
31
+ family({ id: 'arena-linear-integration-bounds', phase: 'fixed-simulation', order: 500, reads: ['Transform2D', 'Velocity2D', 'ArenaBounds'], writes: ['Transform2D', 'Velocity2D'], numericProfile: f64 }),
32
+ family({ id: 'arena-collision-pre-ai', phase: 'fixed-simulation', order: 600, reads: ['Transform2D', 'CircleCollider', 'CollisionLayer'], writes: ['Health'], numericProfile: f64, structuralCommands: ['despawnPrefab'] }),
33
+ family({ id: 'arena-bounded-enemy-steering', phase: 'fixed-simulation', order: 700, reads: ['Transform2D', 'EnemySteering', 'ArenaBounds'], writes: ['Velocity2D'], numericProfile: f64 }),
34
+ family({ id: 'arena-collision-post-ai', phase: 'fixed-simulation', order: 800, reads: ['Transform2D', 'CircleCollider', 'CollisionLayer'], writes: ['Health'], numericProfile: f64, structuralCommands: ['despawnPrefab'] }),
35
+ family({ id: 'arena-lifetimes', phase: 'post-simulation', order: 900, reads: ['Lifetime'], writes: ['Lifetime'], numericProfile: f64, structuralCommands: ['despawnPrefab'] }),
36
+ family({ id: 'phase-spawn-score-policy', phase: 'post-simulation', order: 1000, reads: ['RunPhase', 'SpawnPolicy', 'ScoreState'], writes: ['RunPhase', 'SpawnPolicy', 'ScoreState'], numericProfile: f64, structuralCommands: ['spawnPrefab', 'despawnPrefab'] }),
37
+ family({ id: 'consumable-charge-economy', phase: 'post-simulation', order: 1100, reads: ['ScoreState', 'ChargeEconomy'], writes: ['ChargeEconomy'], numericProfile: f64 }),
38
+ family({ id: 'score-multiplier-decay', phase: 'post-simulation', order: 1200, reads: ['ScoreState', 'MultiplierState'], writes: ['MultiplierState'], numericProfile: f64 }),
39
+ family({ id: 'boundary-crossing-oscillator', phase: 'fixed-simulation', order: 710, reads: ['Transform2D', 'OscillatorState', 'ArenaBounds'], writes: ['Transform2D', 'OscillatorState'], numericProfile: f64 }),
40
+ family({ id: 'staged-axis-runner', phase: 'fixed-simulation', order: 720, reads: ['Transform2D', 'StageState', 'ProjectileField'], writes: ['Transform2D', 'StageState'], numericProfile: f64 }),
41
+ family({ id: 'target-seeking-drift', phase: 'fixed-simulation', order: 730, reads: ['Transform2D', 'TargetState'], writes: ['Transform2D'], numericProfile: f64 }),
42
+ family({ id: 'gravity-well-state-machine', phase: 'fixed-simulation', order: 740, reads: ['Transform2D', 'GravityWellState'], writes: ['Transform2D', 'GravityWellState'], numericProfile: f64, structuralCommands: ['spawnPrefab', 'despawnPrefab'] }),
43
+ family({ id: 'segmented-follow-chain', phase: 'fixed-simulation', order: 750, reads: ['Transform2D', 'FollowChainState'], writes: ['Transform2D', 'FollowChainState'], numericProfile: f64 }),
44
+ family({ id: 'spawn-pellet-growth', phase: 'fixed-simulation', order: 760, reads: ['Transform2D', 'GrowthState', 'Lifetime'], writes: ['Transform2D', 'GrowthState', 'Lifetime'], numericProfile: f64, structuralCommands: ['despawnPrefab'] }),
45
+ family({ id: 'bounded-random-steering', phase: 'fixed-simulation', order: 770, reads: ['Transform2D', 'RandomSteeringState', 'ArenaBounds'], writes: ['Transform2D', 'RandomSteeringState'], numericProfile: f64 }),
46
+ family({ id: 'projectile-lifecycle-collision', phase: 'fixed-simulation', order: 780, reads: ['Transform2D', 'ProjectileState', 'CollisionLayer'], writes: ['Transform2D', 'ProjectileState'], numericProfile: f64, structuralCommands: ['spawnPrefab', 'despawnPrefab'] }),
47
+ family({ id: 'axis-drive-fire-control', phase: 'fixed-simulation', order: 790, reads: ['DriveIntent', 'FireIntent', 'Transform2D'], writes: ['Transform2D', 'FireIntent'], numericProfile: f64 }),
48
+ family({ id: 'pickup-drift-lifecycle', phase: 'fixed-simulation', order: 795, reads: ['Transform2D', 'PickupState', 'Lifetime'], writes: ['Transform2D', 'PickupState', 'Lifetime'], numericProfile: f64, structuralCommands: ['despawnPrefab'] }),
49
+ ]);
50
+
51
+ export const kinetixInstalledSourceSignatures = Object.freeze({
52
+ 'slot-input-resolution': Object.freeze([{ call: 'resolveInputs' }, { call: 'push', receiver: 'cmds' }]),
53
+ 'vehicle-drive-body-build': Object.freeze([{ call: 'driveVehicle' }, { call: 'stepVehicle' }]),
54
+ 'vehicle-physics-step': Object.freeze([{ call: 'stepPhysicsWorld' }, { call: 'stepDeterministicPhysicsWorld3D' }]),
55
+ 'vehicle-lap-finish': Object.freeze([{ call: 'stepLap' }]),
56
+ 'match-phase-transition': Object.freeze([{ call: 'stepPhase' }, { call: 'snapToGrid' }]),
57
+ 'arena-input-mapping': Object.freeze([{ call: 'readInputFrame' }, { call: 'normalizeInputs' }]),
58
+ 'arena-countdowns': Object.freeze([{ call: 'advanceCountdown' }, { call: 'advanceSparkMagnetSurge' }]),
59
+ 'arena-axis-drive': Object.freeze([{ call: 'applyAxisDrive' }, { call: 'update', receiver: 'ship' }]),
60
+ 'arena-autofire': Object.freeze([{ call: 'autoFire' }]),
61
+ 'arena-linear-integration-bounds': Object.freeze([{ call: 'integrateBounds' }, { call: 'update', receiver: 'bullets' }]),
62
+ 'arena-collision-pre-ai': Object.freeze([{ call: 'collisionPass', argument: 'pre-ai' }, { call: 'processBulletCollisions', occurrence: 0 }]),
63
+ 'arena-bounded-enemy-steering': Object.freeze([{ call: 'steerEnemies' }, { call: 'nearestLivingShipPosition' }]),
64
+ 'arena-collision-post-ai': Object.freeze([{ call: 'collisionPass', argument: 'post-ai' }, { call: 'processBulletCollisions', occurrence: 1 }]),
65
+ 'arena-lifetimes': Object.freeze([{ call: 'advanceLifetimes' }, { call: 'collectDead' }]),
66
+ });
67
+
68
+ export function installedSystemFamiliesForProfile(numericProfile) {
69
+ return kinetixInstalledSystemFamilies.filter((family) => family.numericProfiles.includes(numericProfile));
70
+ }
71
+
72
+ export function createInstalledKinetixProfile(numericProfile, options = {}) {
73
+ const families = installedSystemFamiliesForProfile(numericProfile);
74
+ if (families.length === 0) throw new Error('KINETIX_NUMERIC_PROFILE_UNSUPPORTED');
75
+ const abi = numericProfile === fixed ? 'fixed-1000-3d.v1' : 'deterministic-f64-2d.v1';
76
+ const runtimeContract = kinetixRuntimeContractForProfile(numericProfile);
77
+ let fixedRuntime = null;
78
+ return {
79
+ abi,
80
+ runtimeContract,
81
+ componentSchemas: new Map([
82
+ ['KinetixRuntimeState', { currentSchemaVersion: 1, domains: ['simulation'], validate: () => [] }],
83
+ ['KinetixRenderRecord', { currentSchemaVersion: 1, domains: ['presentation'], validate: () => [] }],
84
+ ['KinetixRenderBinding', { currentSchemaVersion: 1, domains: ['binding'], validate: () => [] }],
85
+ ]),
86
+ systems: families.map((installedFamily) => ({
87
+ id: installedFamily.id,
88
+ version: 1,
89
+ phase: installedFamily.phase,
90
+ order: installedFamily.order,
91
+ query: { all: [], none: [] },
92
+ reads: [],
93
+ writes: [],
94
+ structuralCommands: [...installedFamily.structuralCommands],
95
+ })),
96
+ bakeEntity: () => ({ simulation: [], presentation: [], binding: [] }),
97
+ bakePrefab: () => ({ simulation: [], presentation: [], binding: [] }),
98
+ executeSystem: numericProfile === fixed
99
+ ? ({ systemId, inputFrame, simulation, initialization }) => {
100
+ const properties = simulation.read('runtime-root', 'KinetixRuntimeState');
101
+ const pack = properties.installedProfileData;
102
+ if (fixedRuntime === null) {
103
+ const configuredPack = {
104
+ ...pack,
105
+ slotCount: initialization.slotCount ?? pack.slotCount,
106
+ humanCount: initialization.humanCount ?? pack.humanCount,
107
+ initialPhase: initialization.initialPhase ?? pack.initialPhase,
108
+ };
109
+ fixedRuntime = createFixedRuntime(configuredPack, options.fixedPhysics);
110
+ } else if (properties.authoritativeState !== undefined
111
+ && fixedRuntime.pendingInstalledTick === undefined) {
112
+ fixedRuntime.state = structuredClone(properties.authoritativeState);
113
+ }
114
+ executeFixedRuntimeSystem(fixedRuntime, systemId, inputFrame.slots);
115
+ if (fixedRuntime.pendingInstalledTick === undefined) {
116
+ simulation.write('runtime-root', 'KinetixRuntimeState', {
117
+ ...properties,
118
+ authoritativeState: structuredClone(fixedRuntime.state),
119
+ });
120
+ }
121
+ }
122
+ : undefined,
123
+ };
124
+ }
125
+
126
+ export function kinetixRuntimeContractForProfile(numericProfile) {
127
+ if (numericProfile === fixed) return fixedRuntimeContract('fixed-1000-3d.v1');
128
+ if (numericProfile === f64) return f64RuntimeContract('deterministic-f64-2d.v1');
129
+ throw new Error('KINETIX_NUMERIC_PROFILE_UNSUPPORTED');
130
+ }
131
+
132
+ function fixedRuntimeContract(abi) {
133
+ return Object.freeze({
134
+ tickRate: 30,
135
+ targetProfile: fixed,
136
+ numericProfile: 'fixed-1000',
137
+ deterministicAbi: abi,
138
+ deterministicVersion: 'rundot.kinetix.fixed-1000-runtime.v1',
139
+ inputSchema: Object.freeze({
140
+ version: 'rundot.kinetix-input-schema.v1',
141
+ slots: 'bounded-array',
142
+ fields: Object.freeze([
143
+ Object.freeze({ name: 'steerX', type: 'fixed-1000' }),
144
+ Object.freeze({ name: 'throttle', type: 'fixed-1000' }),
145
+ Object.freeze({ name: 'handbrake', type: 'boolean' }),
146
+ ]),
147
+ }),
148
+ stateSchema: Object.freeze({
149
+ version: 'rundot.kinetix-state-schema.v1',
150
+ checkpoint: 'opaque-canonical-bytes',
151
+ numericProfile: 'fixed-1000',
152
+ componentRoot: 'KinetixRuntimeState',
153
+ }),
154
+ });
155
+ }
156
+
157
+ function f64RuntimeContract(abi) {
158
+ return Object.freeze({
159
+ tickRate: 30,
160
+ targetProfile: f64,
161
+ numericProfile: f64,
162
+ deterministicAbi: abi,
163
+ deterministicVersion: 'rundot.kinetix.deterministic-f64-runtime.v1',
164
+ inputSchema: Object.freeze({
165
+ version: 'rundot.kinetix-input-schema.v1',
166
+ slots: 'bounded-array',
167
+ fields: Object.freeze([
168
+ Object.freeze({ name: 'movementVector', type: 'f64x2' }),
169
+ Object.freeze({ name: 'aimVector', type: 'f64x2' }),
170
+ Object.freeze({ name: 'fireDown', type: 'boolean' }),
171
+ Object.freeze({ name: 'bombPressed', type: 'boolean' }),
172
+ ]),
173
+ }),
174
+ stateSchema: Object.freeze({
175
+ version: 'rundot.kinetix-state-schema.v1',
176
+ checkpoint: 'opaque-canonical-bytes',
177
+ numericProfile: f64,
178
+ componentRoot: 'KinetixRuntimeState',
179
+ }),
180
+ });
181
+ }
@@ -0,0 +1,72 @@
1
+ import {
2
+ bakeKinetixEnvelope,
3
+ scanKinetixPayloadTree,
4
+ stableKinetixProductBytes,
5
+ } from './kinetix-baker.mjs';
6
+ import { createInstalledKinetixProfile } from './kinetix-installed-systems.mjs';
7
+ import { compileKinetixProject, compileTrustedKinetixSource } from './kinetix-project-compiler.mjs';
8
+
9
+ function compileProducts(input, compile) {
10
+ const compiled = compile(input);
11
+ if (compiled.diagnostics.length > 0 || compiled.envelope === null) {
12
+ throw new Error(`KINETIX_PROJECT_INELIGIBLE ${JSON.stringify(compiled.diagnostics)}`);
13
+ }
14
+ const profileId = compiled.envelope.profiles[0].id;
15
+ const installedProfiles = new Map([[profileId, createInstalledKinetixProfile(input.targetProfile)]]);
16
+ const baked = bakeKinetixEnvelope(compiled.envelope, installedProfiles);
17
+ if (!baked.valid) throw new Error(`KINETIX_BAKE_FAILED ${JSON.stringify(baked.diagnostics)}`);
18
+ return { compiled, products: baked.products, runtimeIdentity: baked.runtimeIdentity };
19
+ }
20
+
21
+ export function compileStableKinetixProducts(input) {
22
+ return compileStableProducts(input, compileKinetixProject);
23
+ }
24
+
25
+ export function compileStableKinetixProductsFromTrustedSource(input) {
26
+ return compileStableProducts(input, compileTrustedKinetixSource);
27
+ }
28
+
29
+ function compileStableProducts(input, compile) {
30
+ const first = compileProducts(input, compile);
31
+ const second = compileProducts(input, compile);
32
+ if (!stableKinetixProductBytes(first.products).equals(stableKinetixProductBytes(second.products))) {
33
+ throw new Error('KINETIX_PRODUCT_NONDETERMINISTIC');
34
+ }
35
+ scanKinetixPayloadTree(first.products);
36
+ return first;
37
+ }
38
+
39
+ export function nativeFixed1000ProductBytes(products, pack) {
40
+ const chunks = [Buffer.from('KNATIVE1')];
41
+ const appendU32 = (value) => {
42
+ const bytes = Buffer.alloc(4);
43
+ bytes.writeUInt32LE(value);
44
+ chunks.push(bytes);
45
+ };
46
+ const appendI64 = (value) => {
47
+ const bytes = Buffer.alloc(8);
48
+ bytes.writeBigInt64LE(BigInt(value));
49
+ chunks.push(bytes);
50
+ };
51
+ const appendString = (value) => {
52
+ const bytes = Buffer.from(value);
53
+ appendU32(bytes.length);
54
+ chunks.push(bytes);
55
+ };
56
+ appendString(products.sourceDigest);
57
+ const simulation = products.manifest.payloads.find((payload) => payload.name === 'simulation');
58
+ if (!simulation) throw new Error('KINETIX_SIMULATION_PAYLOAD_MISSING');
59
+ appendString(simulation.digest);
60
+ appendU32(products.manifest.systems.length);
61
+ for (const system of products.manifest.systems) {
62
+ appendString(system.id);
63
+ appendString(system.phase);
64
+ appendI64(system.phaseIndex);
65
+ appendI64(system.order);
66
+ }
67
+ const profile = Buffer.from(pack.installedProfileBinary, 'base64');
68
+ appendString(pack.installedProfileBinarySha256);
69
+ appendU32(profile.length);
70
+ chunks.push(profile);
71
+ return Buffer.concat(chunks);
72
+ }