@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,141 @@
|
|
|
1
|
+
// Portable C++20 deterministic-f64 component runtime (pack v2). Transliterates
|
|
2
|
+
// component-reference-runtime-f64.mjs exactly: same phase order, same entity
|
|
3
|
+
// iteration order, same floating-point expression shapes, same RNG consumption
|
|
4
|
+
// order. Per-tick canonical hashes must be bit-identical to the TS reference.
|
|
5
|
+
#pragma once
|
|
6
|
+
|
|
7
|
+
#include <cstdint>
|
|
8
|
+
#include <map>
|
|
9
|
+
#include <memory>
|
|
10
|
+
#include <string>
|
|
11
|
+
#include <vector>
|
|
12
|
+
|
|
13
|
+
namespace rundot::f64rt {
|
|
14
|
+
|
|
15
|
+
struct Vec2 {
|
|
16
|
+
double x = 0.0;
|
|
17
|
+
double y = 0.0;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Decoded pack property value. Main classifies JSON shapes: f64 bit-pattern
|
|
21
|
+
// hex strings decode to F64, [hex, hex] pairs to Vec2, plain strings stay
|
|
22
|
+
// String, JSON numbers become Int, booleans Bool.
|
|
23
|
+
struct PropertyValue {
|
|
24
|
+
enum class Kind { F64, Vec2, String, Int, Bool };
|
|
25
|
+
Kind kind = Kind::Int;
|
|
26
|
+
double f64 = 0.0;
|
|
27
|
+
Vec2 vec2{};
|
|
28
|
+
std::string str;
|
|
29
|
+
std::int64_t i64 = 0;
|
|
30
|
+
bool boolean = false;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
struct ComponentDef {
|
|
34
|
+
std::string type;
|
|
35
|
+
std::map<std::string, PropertyValue> properties;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
struct EntityDef {
|
|
39
|
+
std::string id;
|
|
40
|
+
std::vector<ComponentDef> components;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
struct TemplateDef {
|
|
44
|
+
std::string id;
|
|
45
|
+
std::int64_t pool = 0;
|
|
46
|
+
std::vector<ComponentDef> components;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
struct RngStreamDef {
|
|
50
|
+
std::string id;
|
|
51
|
+
std::uint32_t seed = 0;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
struct CollisionRuleDef {
|
|
55
|
+
std::string layerA;
|
|
56
|
+
std::string layerB;
|
|
57
|
+
std::string action;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
struct PackDef {
|
|
61
|
+
std::int64_t inputSlots = 0;
|
|
62
|
+
std::int64_t maxSpawnsPerTick = 0;
|
|
63
|
+
std::vector<RngStreamDef> rngStreams;
|
|
64
|
+
std::vector<CollisionRuleDef> collisionRules;
|
|
65
|
+
std::vector<TemplateDef> templates;
|
|
66
|
+
std::vector<EntityDef> entities;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
struct SlotInputDef {
|
|
70
|
+
Vec2 movement{};
|
|
71
|
+
Vec2 aim{};
|
|
72
|
+
bool fire = false;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
struct InputFrameDef {
|
|
76
|
+
std::int64_t tick = 0;
|
|
77
|
+
std::vector<SlotInputDef> slots;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
struct ReplayDef {
|
|
81
|
+
std::int64_t tickCount = 0;
|
|
82
|
+
std::vector<InputFrameDef> inputFrames;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
struct F64RunResult {
|
|
86
|
+
std::vector<std::string> tickHashes;
|
|
87
|
+
std::string finalDigest;
|
|
88
|
+
std::int64_t completedTicks = 0;
|
|
89
|
+
std::int64_t liveEntitiesAtEnd = 0;
|
|
90
|
+
std::int64_t finalScore = 0;
|
|
91
|
+
std::int64_t shipDeaths = 0;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
F64RunResult runReplayF64(const PackDef& pack, const ReplayDef& replay, bool hashEveryTick);
|
|
95
|
+
|
|
96
|
+
// Render-facing event log entry for the current tick (mirrors the JS
|
|
97
|
+
// runtime.frameEvents: 'spawned' pushed in spawnFromTemplate after setup,
|
|
98
|
+
// 'enemy-killed'/'ship-death' pushed at the top of handleDeathEvents).
|
|
99
|
+
// Not part of canonical state or hashing.
|
|
100
|
+
struct FrameEventDef {
|
|
101
|
+
std::string type;
|
|
102
|
+
std::string ref;
|
|
103
|
+
double x = 0.0;
|
|
104
|
+
double y = 0.0;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Snapshot of one live entity for render view building (JS liveEntities
|
|
108
|
+
// order: all static entities in pack order, then alive pooled entities in
|
|
109
|
+
// ascending spawnId).
|
|
110
|
+
struct SimEntityView {
|
|
111
|
+
std::string ref;
|
|
112
|
+
std::uint64_t spawnId = 0;
|
|
113
|
+
bool hasTransform = false;
|
|
114
|
+
double x = 0.0;
|
|
115
|
+
double y = 0.0;
|
|
116
|
+
double vx = 0.0;
|
|
117
|
+
double vy = 0.0;
|
|
118
|
+
double heading = 0.0;
|
|
119
|
+
bool hasTelegraph = false;
|
|
120
|
+
double telegraphRemaining = 0.0;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Tick-at-a-time driver over the same internal runtime that runReplayF64
|
|
124
|
+
// uses. The pack must outlive the stepper.
|
|
125
|
+
class F64SimStepper {
|
|
126
|
+
public:
|
|
127
|
+
explicit F64SimStepper(const PackDef& pack);
|
|
128
|
+
~F64SimStepper();
|
|
129
|
+
F64SimStepper(const F64SimStepper&) = delete;
|
|
130
|
+
F64SimStepper& operator=(const F64SimStepper&) = delete;
|
|
131
|
+
|
|
132
|
+
void tick(const std::vector<SlotInputDef>& inputs);
|
|
133
|
+
void liveView(std::vector<SimEntityView>& out);
|
|
134
|
+
const std::vector<FrameEventDef>& frameEvents() const;
|
|
135
|
+
|
|
136
|
+
private:
|
|
137
|
+
struct Impl;
|
|
138
|
+
std::unique_ptr<Impl> impl_;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
} // namespace rundot::f64rt
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <array>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <string>
|
|
6
|
+
#include <vector>
|
|
7
|
+
|
|
8
|
+
namespace rundot::kinetix::fixed1000_data {
|
|
9
|
+
using I = std::int64_t;
|
|
10
|
+
inline constexpr std::size_t kMaxSlots = 8;
|
|
11
|
+
struct Cell { I gridX, gridZ, typeId, orientation; };
|
|
12
|
+
|
|
13
|
+
inline std::string kTrack;
|
|
14
|
+
inline I kScale = 0;
|
|
15
|
+
inline I kSlotCount = 0;
|
|
16
|
+
inline I kHumanCount = 0;
|
|
17
|
+
inline I kInitialPhase = 0;
|
|
18
|
+
inline std::vector<Cell> kCells;
|
|
19
|
+
inline I kCellRaw = 0;
|
|
20
|
+
inline I kGridScale = 0;
|
|
21
|
+
inline I kFinishTypeId = 0;
|
|
22
|
+
inline I kMaxSpeed = 0;
|
|
23
|
+
inline I kAccelRate = 0;
|
|
24
|
+
inline I kBrakeRate = 0;
|
|
25
|
+
inline I kMaxReverse = 0;
|
|
26
|
+
inline I kTurnPerTick = 0;
|
|
27
|
+
inline I kGripMin = 0;
|
|
28
|
+
inline I kSlipSpeed = 0;
|
|
29
|
+
inline I kTractionSlow = 0;
|
|
30
|
+
inline I kTractionFast = 0;
|
|
31
|
+
inline I kDriftSteer = 0;
|
|
32
|
+
inline I kStopDeadband = 0;
|
|
33
|
+
inline I kWallHalfThick = 0;
|
|
34
|
+
inline I kWallLateral = 0;
|
|
35
|
+
inline I kWallY = 0;
|
|
36
|
+
inline I kWallHalfHeightRaw = 0;
|
|
37
|
+
inline I kHalfPiFixed = 0;
|
|
38
|
+
inline I kTwoPiFixed = 0;
|
|
39
|
+
inline I kOuterArcSegments = 0;
|
|
40
|
+
inline I kInnerArcSegments = 0;
|
|
41
|
+
inline I kColliderLayer = 0;
|
|
42
|
+
inline I kColliderMask = 0;
|
|
43
|
+
inline I kArenaHalfUnits = 0;
|
|
44
|
+
inline I kArenaWallThick = 0;
|
|
45
|
+
inline I kArenaWallHalfHeight = 0;
|
|
46
|
+
inline I kArenaWallY = 0;
|
|
47
|
+
inline I kArenaCenterX = 0;
|
|
48
|
+
inline I kArenaCenterZ = 0;
|
|
49
|
+
inline I kScatterCols = 0;
|
|
50
|
+
inline I kScatterSpacing = 0;
|
|
51
|
+
inline I kGridSpawnLateral = 0;
|
|
52
|
+
inline I kGridSpawnRowBack = 0;
|
|
53
|
+
inline I kGridSpawnMinSlots = 0;
|
|
54
|
+
inline I kGridSpawnMaxSlots = 0;
|
|
55
|
+
inline I kWaypointLookahead = 0;
|
|
56
|
+
inline I kWaypointSteerGain = 0;
|
|
57
|
+
inline I kWaypointSharpTurnThrottle = 0;
|
|
58
|
+
inline I kTotalLaps = 0;
|
|
59
|
+
inline I kMaxRaceFrames = 0;
|
|
60
|
+
inline I kNoTeleport = 0;
|
|
61
|
+
inline I kMaxRequiredBits = 0;
|
|
62
|
+
inline I kLobbyInitialFrames = 0;
|
|
63
|
+
inline I kLobbyExtendFrames = 0;
|
|
64
|
+
inline I kLobbyMaxFrames = 0;
|
|
65
|
+
inline I kCountdownFrames = 0;
|
|
66
|
+
inline I kResultsHoldFrames = 0;
|
|
67
|
+
inline I kMaxSlotsAutoStart = 0;
|
|
68
|
+
inline I kBodyRadius = 0;
|
|
69
|
+
inline I kBodyY = 0;
|
|
70
|
+
inline I kBodyLayer = 0;
|
|
71
|
+
inline I kBodyMask = 0;
|
|
72
|
+
inline std::vector<I> kCornerTypeIds;
|
|
73
|
+
inline std::vector<I> kPassThroughTypeIds;
|
|
74
|
+
inline std::string kWallBodyPrefix;
|
|
75
|
+
inline std::string kCarBodyPrefix;
|
|
76
|
+
inline std::array<std::string, 4> kArenaBodyIds;
|
|
77
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#include <iostream>
|
|
2
|
+
#include <vector>
|
|
3
|
+
|
|
4
|
+
#include "kinetix-render-projection.hpp"
|
|
5
|
+
#if defined(KINETIX_ALL_PROFILES) || defined(KINETIX_FIXED1000_PROFILE)
|
|
6
|
+
#include "kinetix-fixed1000-native-profile.hpp"
|
|
7
|
+
#endif
|
|
8
|
+
#if defined(KINETIX_ALL_PROFILES) || defined(KINETIX_F64_PROFILE)
|
|
9
|
+
#include "kinetix-f64-native-profile.hpp"
|
|
10
|
+
#endif
|
|
11
|
+
#include "kinetix-native-runtime.hpp"
|
|
12
|
+
|
|
13
|
+
int main(int argc, char** argv) {
|
|
14
|
+
#if defined(KINETIX_ALL_PROFILES) || defined(KINETIX_FIXED1000_PROFILE)
|
|
15
|
+
if (argc > 1 && std::string(argv[1]) == "--fixed1000") {
|
|
16
|
+
return rundot::kinetix::RunFixed1000Profile(argc - 1, argv + 1);
|
|
17
|
+
}
|
|
18
|
+
#endif
|
|
19
|
+
#if defined(KINETIX_ALL_PROFILES) || defined(KINETIX_F64_PROFILE)
|
|
20
|
+
if (argc > 1 && std::string(argv[1]) == "--f64") {
|
|
21
|
+
return rundot::kinetix::RunF64Profile(argc - 1, argv + 1);
|
|
22
|
+
}
|
|
23
|
+
#endif
|
|
24
|
+
if (argc == 4 && std::string(argv[1]) == "--project") {
|
|
25
|
+
std::vector<std::string> payloads;
|
|
26
|
+
std::string payload;
|
|
27
|
+
while (std::getline(std::cin, payload)) payloads.push_back(payload);
|
|
28
|
+
std::cout << rundot::kinetix::ProjectRenderRecords(
|
|
29
|
+
std::stoull(argv[2]), argv[3], payloads) << '\n';
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
rundot::kinetix::World world;
|
|
33
|
+
std::string input;
|
|
34
|
+
while (std::getline(std::cin, input)) world.Step(input);
|
|
35
|
+
std::cout << world.CanonicalState() << '\n';
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#include "kinetix-native-runtime.hpp"
|
|
2
|
+
|
|
3
|
+
namespace rundot::kinetix {
|
|
4
|
+
|
|
5
|
+
void World::Step(const std::string& inputFrame) {
|
|
6
|
+
state_.tick += 1;
|
|
7
|
+
state_.lastInputFrame = inputFrame;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
Snapshot World::TakeSnapshot() const { return state_; }
|
|
11
|
+
|
|
12
|
+
void World::Restore(const Snapshot& snapshot) { state_ = snapshot; }
|
|
13
|
+
|
|
14
|
+
std::string World::CanonicalState() const {
|
|
15
|
+
return "{\"lastInputFrame\":" + state_.lastInputFrame +
|
|
16
|
+
",\"monotonicSpawnId\":" + std::to_string(state_.monotonicSpawnId) +
|
|
17
|
+
",\"tick\":" + std::to_string(state_.tick) + "}";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
} // namespace rundot::kinetix
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
#include <string>
|
|
5
|
+
|
|
6
|
+
namespace rundot::kinetix {
|
|
7
|
+
|
|
8
|
+
struct Snapshot {
|
|
9
|
+
std::uint64_t tick;
|
|
10
|
+
std::uint64_t monotonicSpawnId;
|
|
11
|
+
std::string lastInputFrame;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
class World {
|
|
15
|
+
public:
|
|
16
|
+
void Step(const std::string& inputFrame);
|
|
17
|
+
Snapshot TakeSnapshot() const;
|
|
18
|
+
void Restore(const Snapshot& snapshot);
|
|
19
|
+
std::string CanonicalState() const;
|
|
20
|
+
|
|
21
|
+
private:
|
|
22
|
+
Snapshot state_{0, 1, "null"};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
} // namespace rundot::kinetix
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#include "kinetix-render-projection.hpp"
|
|
2
|
+
|
|
3
|
+
namespace rundot::kinetix {
|
|
4
|
+
|
|
5
|
+
std::string ProjectRenderRecords(
|
|
6
|
+
std::uint64_t worldTick,
|
|
7
|
+
const std::string& bindingDigest,
|
|
8
|
+
const std::vector<std::string>& payloads) {
|
|
9
|
+
std::string output = "[";
|
|
10
|
+
for (std::size_t index = 0; index < payloads.size(); ++index) {
|
|
11
|
+
if (index > 0) output += ',';
|
|
12
|
+
output += "{\"bindingDigest\":\"" + bindingDigest +
|
|
13
|
+
"\",\"payload\":" + payloads[index] +
|
|
14
|
+
",\"worldTick\":" + std::to_string(worldTick) + "}";
|
|
15
|
+
}
|
|
16
|
+
output += ']';
|
|
17
|
+
return output;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <vector>
|
|
6
|
+
|
|
7
|
+
namespace rundot::kinetix {
|
|
8
|
+
|
|
9
|
+
std::string ProjectRenderRecords(
|
|
10
|
+
std::uint64_t worldTick,
|
|
11
|
+
const std::string& bindingDigest,
|
|
12
|
+
const std::vector<std::string>& payloads);
|
|
13
|
+
|
|
14
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@series-inc/rundot-kinetix",
|
|
3
|
+
"version": "0.0.0-bootstrap.0",
|
|
4
|
+
"description": "Data-only game runtime core: compiler, baker, world kernel, installed systems, deterministic math, render records, and the portable C++ runtime",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/series-ai/venus.git",
|
|
8
|
+
"directory": "packages/kinetix"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./src/index.mjs",
|
|
12
|
+
"types": "./index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./index.d.ts",
|
|
16
|
+
"default": "./src/index.mjs"
|
|
17
|
+
},
|
|
18
|
+
"./runtime": {
|
|
19
|
+
"types": "./runtime.d.ts",
|
|
20
|
+
"default": "./src/runtime.mjs"
|
|
21
|
+
},
|
|
22
|
+
"./authoring": {
|
|
23
|
+
"types": "./authoring.d.ts",
|
|
24
|
+
"default": "./src/kinetix-authoring.mjs"
|
|
25
|
+
},
|
|
26
|
+
"./compiler": "./src/kinetix-project-compiler.mjs",
|
|
27
|
+
"./baker": "./src/kinetix-baker.mjs",
|
|
28
|
+
"./world-kernel": "./src/kinetix-world-kernel.mjs",
|
|
29
|
+
"./installed-systems": "./src/kinetix-installed-systems.mjs",
|
|
30
|
+
"./installed-mechanics": "./src/kinetix-installed-mechanics.mjs",
|
|
31
|
+
"./installed-f64-reference": "./src/kinetix-installed-f64-reference.mjs",
|
|
32
|
+
"./installed-f64-renderer": "./src/kinetix-installed-f64-renderer.mjs",
|
|
33
|
+
"./installed-f64-runtime": "./src/kinetix-installed-f64-runtime.mjs",
|
|
34
|
+
"./installed-f64-render-frames": "./src/kinetix-installed-f64-render-frames.mjs",
|
|
35
|
+
"./fixed1000-runtime": "./src/kinetix-fixed1000-runtime.mjs",
|
|
36
|
+
"./deterministic-math": "./src/kinetix-deterministic-math.mjs",
|
|
37
|
+
"./native-product": "./src/kinetix-native-product.mjs",
|
|
38
|
+
"./render-projection": "./src/kinetix-render-projection.mjs",
|
|
39
|
+
"./envelope": "./src/shared/kinetix-envelope-v1.mjs",
|
|
40
|
+
"./render-records": "./src/shared/render-records-v1.mjs",
|
|
41
|
+
"./f64-bits": "./src/shared/f64-bits.mjs",
|
|
42
|
+
"./build-native": "./scripts/build-native.mjs",
|
|
43
|
+
"./preflight": "./scripts/preflight.mjs",
|
|
44
|
+
"./package.json": "./package.json"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"src",
|
|
48
|
+
"native",
|
|
49
|
+
"scripts",
|
|
50
|
+
"index.d.ts",
|
|
51
|
+
"runtime.d.ts",
|
|
52
|
+
"authoring.d.ts"
|
|
53
|
+
],
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build:native": "node scripts/build-native.mjs",
|
|
56
|
+
"test": "node --test tests/*.test.mjs"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"typescript": "^6.0.3"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=22"
|
|
63
|
+
},
|
|
64
|
+
"license": "MIT"
|
|
65
|
+
}
|
package/runtime.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const kinetixRuntimeAbiVersion: 1;
|
|
2
|
+
|
|
3
|
+
export interface KinetixRuntimeIdentity {
|
|
4
|
+
readonly abiVersion: typeof kinetixRuntimeAbiVersion;
|
|
5
|
+
readonly tickRate: 10 | 20 | 30 | 60;
|
|
6
|
+
readonly inputSchemaId: string;
|
|
7
|
+
readonly stateSchemaId: string;
|
|
8
|
+
readonly deterministicVersion: string;
|
|
9
|
+
readonly engineIdentityHash: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface KinetixAdvanceRequest<Input> {
|
|
13
|
+
readonly frame: number;
|
|
14
|
+
readonly inputs: readonly Readonly<Input>[];
|
|
15
|
+
readonly commands: readonly unknown[];
|
|
16
|
+
readonly checksum: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface KinetixAdvanceResult<Checkpoint> {
|
|
20
|
+
readonly frame: number;
|
|
21
|
+
readonly checkpoint: Checkpoint;
|
|
22
|
+
readonly checksum?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface KinetixRuntime<State, Input, Checkpoint = unknown> {
|
|
26
|
+
readonly identity: KinetixRuntimeIdentity;
|
|
27
|
+
readonly sessionConfigDigest: string;
|
|
28
|
+
readonly currentFrame: number;
|
|
29
|
+
sessionConfigBytes(): Uint8Array;
|
|
30
|
+
capture(): Checkpoint;
|
|
31
|
+
advance(
|
|
32
|
+
requests: readonly KinetixAdvanceRequest<Input>[],
|
|
33
|
+
): readonly KinetixAdvanceResult<Checkpoint>[];
|
|
34
|
+
restore(checkpoint: Checkpoint): void;
|
|
35
|
+
inspect(checkpoint: Checkpoint): State;
|
|
36
|
+
serializeCheckpoint(checkpoint: Checkpoint): Uint8Array;
|
|
37
|
+
hydrateCheckpoint(frame: number, bytes: Uint8Array): Checkpoint;
|
|
38
|
+
checksum(checkpoint: Checkpoint): string;
|
|
39
|
+
wireChecksum(checkpoint: Checkpoint): string;
|
|
40
|
+
release(checkpoint: Checkpoint): void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function assertKinetixRuntime<
|
|
44
|
+
State,
|
|
45
|
+
Input,
|
|
46
|
+
Checkpoint,
|
|
47
|
+
Runtime extends KinetixRuntime<State, Input, Checkpoint>,
|
|
48
|
+
>(runtime: Runtime): Runtime;
|
|
49
|
+
|
|
50
|
+
export function assertKinetixRuntimeIdentity(
|
|
51
|
+
identity: unknown,
|
|
52
|
+
): asserts identity is KinetixRuntimeIdentity;
|
|
53
|
+
|
|
54
|
+
export function createKinetixWorldRuntime<State, Input>(options: {
|
|
55
|
+
world: {
|
|
56
|
+
step(input: unknown): unknown;
|
|
57
|
+
snapshot(): State;
|
|
58
|
+
restore(snapshot: State): void;
|
|
59
|
+
canonicalStateBytes(): Uint8Array;
|
|
60
|
+
};
|
|
61
|
+
identity: KinetixRuntimeIdentity;
|
|
62
|
+
sessionConfigBytes?: Uint8Array;
|
|
63
|
+
}): KinetixRuntime<State, Input>;
|
|
64
|
+
|
|
65
|
+
export function encodeKinetixSessionConfig(
|
|
66
|
+
schema: import('./authoring').KinetixSessionConfigSchema,
|
|
67
|
+
value: Readonly<Record<string, unknown>>,
|
|
68
|
+
): Uint8Array;
|
|
69
|
+
export function decodeKinetixSessionConfig(
|
|
70
|
+
schema: import('./authoring').KinetixSessionConfigSchema,
|
|
71
|
+
bytes: Uint8Array,
|
|
72
|
+
): Record<string, unknown>;
|
|
73
|
+
export function kinetixSessionConfigDigest(bytes: Uint8Array): string;
|
|
74
|
+
export function createInstalledF64Runtime(options: Record<string, unknown>): KinetixRuntime<unknown, unknown>;
|
|
75
|
+
export function createInstalledSurvivalRuntime(options: Record<string, unknown>): KinetixRuntime<unknown, unknown>;
|
|
76
|
+
export function createFixed1000Runtime(options: Record<string, unknown>): KinetixRuntime<unknown, unknown>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Builds the portable C++ core. Probes for a compiler rather than hardcoding one,
|
|
2
|
+
// so the same script serves macOS (clang++) and Linux CI (g++).
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { mkdirSync, realpathSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const root = join(import.meta.dirname, '..');
|
|
9
|
+
const outDir = join(root, 'build');
|
|
10
|
+
|
|
11
|
+
const SOURCES = [
|
|
12
|
+
'kinetix-native-runtime.cpp',
|
|
13
|
+
'kinetix-render-projection.cpp',
|
|
14
|
+
'kinetix-fixed1000-native-profile.cpp',
|
|
15
|
+
'kinetix-fixed1000-physics.cpp',
|
|
16
|
+
'component_runtime.cpp',
|
|
17
|
+
'deterministic_math.cpp',
|
|
18
|
+
'kinetix-installed-f64-runtime.cpp',
|
|
19
|
+
'kinetix-installed-f64-renderer.cpp',
|
|
20
|
+
'kinetix-f64-native-profile.cpp',
|
|
21
|
+
'kinetix-native-main.cpp',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
// -ffp-contract=off is load-bearing, not stylistic: it forbids the compiler from
|
|
25
|
+
// fusing a*b+c into an FMA, which would change f64 results and break JS<->C++
|
|
26
|
+
// determinism. Do not drop it when adding a compiler.
|
|
27
|
+
const FLAGS = [
|
|
28
|
+
'-std=c++20',
|
|
29
|
+
'-O2',
|
|
30
|
+
'-ffp-contract=off',
|
|
31
|
+
'-DKINETIX_ALL_PROFILES',
|
|
32
|
+
'-DKINETIX_FIXED1000_SIM_ONLY',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const CANDIDATES = ['clang++', 'g++', 'c++'];
|
|
36
|
+
|
|
37
|
+
function probeCompiler() {
|
|
38
|
+
for (const candidate of CANDIDATES) {
|
|
39
|
+
const probe = spawnSync(candidate, ['--version'], { encoding: 'utf8' });
|
|
40
|
+
if (probe.status === 0) return candidate;
|
|
41
|
+
}
|
|
42
|
+
throw new Error(`KINETIX_NATIVE_NO_COMPILER tried=${CANDIDATES.join(',')}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function buildNative() {
|
|
46
|
+
mkdirSync(outDir, { recursive: true });
|
|
47
|
+
const compiler = probeCompiler();
|
|
48
|
+
const binary = join(outDir, 'kinetix-native-main');
|
|
49
|
+
const built = spawnSync(compiler, [
|
|
50
|
+
...FLAGS,
|
|
51
|
+
...SOURCES.map((source) => join(root, 'native', source)),
|
|
52
|
+
'-o',
|
|
53
|
+
binary,
|
|
54
|
+
], { encoding: 'utf8' });
|
|
55
|
+
if (built.status !== 0) {
|
|
56
|
+
throw new Error(`KINETIX_NATIVE_BUILD_FAILED compiler=${compiler} ${built.stderr}`);
|
|
57
|
+
}
|
|
58
|
+
return binary;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Build when invoked directly (`npm run build:native`). realpath both sides:
|
|
62
|
+
// import.meta.url is symlink-resolved but process.argv[1] is not, so a checkout
|
|
63
|
+
// under a symlinked path (macOS /tmp, some CI runners) would otherwise turn this
|
|
64
|
+
// entry check — and thus the CI "build the core" step — into a silent no-op.
|
|
65
|
+
if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
|
|
66
|
+
console.log(`KINETIX_NATIVE_BUILD_OK ${buildNative()}`);
|
|
67
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Emits the product-digest oracle: sha256 over the stable (double-compiled,
|
|
3
|
+
// byte-compared) product bytes for each validation game.
|
|
4
|
+
//
|
|
5
|
+
// This is the acceptance oracle for the promotion of the core out of the lab.
|
|
6
|
+
// The digests are content-addressed over the compiler's output, so any change to
|
|
7
|
+
// compiler, baker, envelope or installed-system behavior moves them. A move that
|
|
8
|
+
// is genuinely inert leaves them untouched.
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
import { writeFileSync } from 'node:fs';
|
|
11
|
+
|
|
12
|
+
import { stableKinetixProductBytes } from '../src/kinetix-baker.mjs';
|
|
13
|
+
import { compileStableKinetixProducts } from '../src/kinetix-native-product.mjs';
|
|
14
|
+
|
|
15
|
+
function option(name) {
|
|
16
|
+
const index = process.argv.indexOf(`--${name}`);
|
|
17
|
+
if (index < 0 || index + 1 >= process.argv.length) throw new Error(`KINETIX_OPTION_REQUIRED --${name}`);
|
|
18
|
+
return process.argv[index + 1];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const games = [
|
|
22
|
+
{ id: 'game-f64', projectRoot: option('f64-root'), expectedCommit: option('f64-commit'), targetProfile: 'deterministic-f64' },
|
|
23
|
+
{ id: 'game-fixed1000', projectRoot: option('fixed1000-root'), expectedCommit: option('fixed1000-commit'), targetProfile: 'fixed-1000-3d' },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const digests = {};
|
|
27
|
+
for (const game of games) {
|
|
28
|
+
const result = compileStableKinetixProducts(game);
|
|
29
|
+
digests[game.id] = createHash('sha256').update(stableKinetixProductBytes(result.products)).digest('hex');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
writeFileSync(option('out'), `${JSON.stringify(digests, null, 2)}\n`);
|
|
33
|
+
console.log(`KINETIX_PRODUCT_DIGESTS ${JSON.stringify(digests)}`);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Local pack preflight: compile a game at a commit and report unmatched
|
|
3
|
+
// authoritative branches WITHOUT touching any server. This is the bridge
|
|
4
|
+
// `rundot pack preflight` shells out to (the compiler is JS; the CLI is .NET).
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// node preflight.mjs --project-root <dir> --commit <sha> --profile <deterministic-f64|fixed-1000-3d>
|
|
8
|
+
//
|
|
9
|
+
// Prints a JSON report to stdout and exits non-zero when the game does not
|
|
10
|
+
// compile, so the CLI can format it and set the exit code without re-parsing.
|
|
11
|
+
import { compileKinetixProject } from '../src/kinetix-project-compiler.mjs';
|
|
12
|
+
|
|
13
|
+
function parseArgs(argv) {
|
|
14
|
+
const args = {};
|
|
15
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
16
|
+
const key = argv[i];
|
|
17
|
+
if (key.startsWith('--')) {
|
|
18
|
+
args[key.slice(2)] = argv[i + 1];
|
|
19
|
+
i += 1;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return args;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const PROFILES = new Set(['deterministic-f64', 'fixed-1000-3d']);
|
|
26
|
+
|
|
27
|
+
function main() {
|
|
28
|
+
const args = parseArgs(process.argv.slice(2));
|
|
29
|
+
const projectRoot = args['project-root'];
|
|
30
|
+
const commit = args.commit;
|
|
31
|
+
const profile = args.profile;
|
|
32
|
+
if (!projectRoot || !commit || !PROFILES.has(profile)) {
|
|
33
|
+
process.stderr.write(
|
|
34
|
+
'KINETIX_PREFLIGHT_USAGE --project-root <dir> --commit <sha> --profile <deterministic-f64|fixed-1000-3d>\n',
|
|
35
|
+
);
|
|
36
|
+
process.exit(2);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let result;
|
|
40
|
+
try {
|
|
41
|
+
result = compileKinetixProject({ projectRoot, expectedCommit: commit, targetProfile: profile });
|
|
42
|
+
} catch (err) {
|
|
43
|
+
process.stdout.write(
|
|
44
|
+
JSON.stringify({
|
|
45
|
+
format: 'kinetix-preflight-v1',
|
|
46
|
+
ok: false,
|
|
47
|
+
commitSha: commit,
|
|
48
|
+
numericProfile: profile,
|
|
49
|
+
error: err instanceof Error ? err.message : String(err),
|
|
50
|
+
diagnostics: [],
|
|
51
|
+
unmatchedCount: 0,
|
|
52
|
+
}) + '\n',
|
|
53
|
+
);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const diagnostics = (result.diagnostics ?? []).map((entry) => ({
|
|
58
|
+
code: entry.code,
|
|
59
|
+
file: entry.file,
|
|
60
|
+
line: entry.line,
|
|
61
|
+
enclosingMember: entry.enclosingMember,
|
|
62
|
+
message: entry.message,
|
|
63
|
+
}));
|
|
64
|
+
const ok = result.envelope !== null && diagnostics.length === 0;
|
|
65
|
+
process.stdout.write(JSON.stringify({
|
|
66
|
+
format: 'kinetix-preflight-v1',
|
|
67
|
+
ok,
|
|
68
|
+
commitSha: commit,
|
|
69
|
+
numericProfile: profile,
|
|
70
|
+
diagnostics,
|
|
71
|
+
unmatchedCount: diagnostics.length,
|
|
72
|
+
}) + '\n');
|
|
73
|
+
process.exit(ok ? 0 : 1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
main();
|