@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,21 @@
|
|
|
1
|
+
// Deterministic-f64 math for the component runtime: C++ transliteration of the
|
|
2
|
+
// fdlibm-style JS shim (kinetix-deterministic-math.mjs). Every function must be
|
|
3
|
+
// bit-identical to the JS implementation for all finite inputs — verified by the
|
|
4
|
+
// parity battery (deterministic-f64-battery.mjs vs
|
|
5
|
+
// deterministic-math-battery-main.cpp).
|
|
6
|
+
//
|
|
7
|
+
// Compile with FMA contraction disabled (-ffp-contract=off): the algorithms
|
|
8
|
+
// depend on exact IEEE double rounding of each written operation.
|
|
9
|
+
#pragma once
|
|
10
|
+
|
|
11
|
+
namespace rundot::detmath {
|
|
12
|
+
|
|
13
|
+
double det_sin(double x);
|
|
14
|
+
double det_cos(double x);
|
|
15
|
+
double det_atan(double x);
|
|
16
|
+
double det_atan2(double y, double x);
|
|
17
|
+
double det_exp(double x);
|
|
18
|
+
double det_log(double x);
|
|
19
|
+
double det_hypot2(double x, double y);
|
|
20
|
+
|
|
21
|
+
} // namespace rundot::detmath
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
// Headless native proof executable for the deterministic-f64 component
|
|
2
|
+
// runtime (pack v2). Loads a component pack plus a replay, runs the fixed-tick
|
|
3
|
+
// simulation, and writes per-tick canonical hashes and metrics. Usage:
|
|
4
|
+
// component-runtime-f64-main <component-pack.json> <replay.json> <outDir> [hashEveryTick 0|1] [--render-frames]
|
|
5
|
+
//
|
|
6
|
+
// With --render-frames it additionally renders after each tick with
|
|
7
|
+
// tick % 2 == 1 (matching render-frames-node.mjs), writing
|
|
8
|
+
// outDir/frame-%03d.rgb raw RGB24 frames plus outDir/frame-hashes.json.
|
|
9
|
+
|
|
10
|
+
#include <bit>
|
|
11
|
+
#include <chrono>
|
|
12
|
+
#include <cmath>
|
|
13
|
+
#include <cstdint>
|
|
14
|
+
#include <cstdlib>
|
|
15
|
+
#include <fstream>
|
|
16
|
+
#include <iostream>
|
|
17
|
+
#include <map>
|
|
18
|
+
#include <memory>
|
|
19
|
+
#include <sstream>
|
|
20
|
+
#include <string>
|
|
21
|
+
#include <vector>
|
|
22
|
+
|
|
23
|
+
#include "kinetix-installed-f64-renderer.hpp"
|
|
24
|
+
#include "kinetix-installed-f64-runtime.hpp"
|
|
25
|
+
#include "kinetix-f64-native-profile.hpp"
|
|
26
|
+
|
|
27
|
+
namespace {
|
|
28
|
+
|
|
29
|
+
// Minimal recursive-descent JSON parser: exactly what the fixed pack/replay
|
|
30
|
+
// schemas need (objects, arrays, strings, numbers, bools, null).
|
|
31
|
+
struct JsonValue {
|
|
32
|
+
enum class Kind { Object, Array, String, Number, Bool, Null } kind = Kind::Null;
|
|
33
|
+
std::map<std::string, std::shared_ptr<JsonValue>> object;
|
|
34
|
+
std::vector<std::shared_ptr<JsonValue>> array;
|
|
35
|
+
std::string string;
|
|
36
|
+
double number = 0;
|
|
37
|
+
bool boolean = false;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
using JsonPtr = std::shared_ptr<JsonValue>;
|
|
41
|
+
|
|
42
|
+
struct JsonParser {
|
|
43
|
+
const std::string& text;
|
|
44
|
+
std::size_t cursor = 0;
|
|
45
|
+
|
|
46
|
+
explicit JsonParser(const std::string& source) : text(source) {}
|
|
47
|
+
|
|
48
|
+
[[noreturn]] void fail(const std::string& message) {
|
|
49
|
+
std::cerr << "JSON parse error at offset " << cursor << ": " << message << "\n";
|
|
50
|
+
std::exit(2);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
void skipWhitespace() {
|
|
54
|
+
while (cursor < text.size() && (text[cursor] == ' ' || text[cursor] == '\n' || text[cursor] == '\t' || text[cursor] == '\r')) {
|
|
55
|
+
++cursor;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
char peek() {
|
|
60
|
+
if (cursor >= text.size()) fail("unexpected end of input");
|
|
61
|
+
return text[cursor];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void expect(char expected) {
|
|
65
|
+
if (peek() != expected) fail(std::string("expected '") + expected + "'");
|
|
66
|
+
++cursor;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
JsonPtr parseValue() {
|
|
70
|
+
skipWhitespace();
|
|
71
|
+
const char head = peek();
|
|
72
|
+
if (head == '{') return parseObject();
|
|
73
|
+
if (head == '[') return parseArray();
|
|
74
|
+
if (head == '"') return parseString();
|
|
75
|
+
if (head == 't' || head == 'f') return parseBool();
|
|
76
|
+
if (head == 'n') return parseNull();
|
|
77
|
+
return parseNumber();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
JsonPtr parseObject() {
|
|
81
|
+
auto value = std::make_shared<JsonValue>();
|
|
82
|
+
value->kind = JsonValue::Kind::Object;
|
|
83
|
+
expect('{');
|
|
84
|
+
skipWhitespace();
|
|
85
|
+
if (peek() == '}') { ++cursor; return value; }
|
|
86
|
+
while (true) {
|
|
87
|
+
skipWhitespace();
|
|
88
|
+
JsonPtr key = parseString();
|
|
89
|
+
skipWhitespace();
|
|
90
|
+
expect(':');
|
|
91
|
+
value->object[key->string] = parseValue();
|
|
92
|
+
skipWhitespace();
|
|
93
|
+
if (peek() == ',') { ++cursor; continue; }
|
|
94
|
+
expect('}');
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
JsonPtr parseArray() {
|
|
100
|
+
auto value = std::make_shared<JsonValue>();
|
|
101
|
+
value->kind = JsonValue::Kind::Array;
|
|
102
|
+
expect('[');
|
|
103
|
+
skipWhitespace();
|
|
104
|
+
if (peek() == ']') { ++cursor; return value; }
|
|
105
|
+
while (true) {
|
|
106
|
+
value->array.push_back(parseValue());
|
|
107
|
+
skipWhitespace();
|
|
108
|
+
if (peek() == ',') { ++cursor; continue; }
|
|
109
|
+
expect(']');
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
JsonPtr parseString() {
|
|
115
|
+
auto value = std::make_shared<JsonValue>();
|
|
116
|
+
value->kind = JsonValue::Kind::String;
|
|
117
|
+
expect('"');
|
|
118
|
+
std::string out;
|
|
119
|
+
while (true) {
|
|
120
|
+
if (cursor >= text.size()) fail("unterminated string");
|
|
121
|
+
const char ch = text[cursor++];
|
|
122
|
+
if (ch == '"') break;
|
|
123
|
+
if (ch == '\\') {
|
|
124
|
+
if (cursor >= text.size()) fail("unterminated escape");
|
|
125
|
+
const char escape = text[cursor++];
|
|
126
|
+
switch (escape) {
|
|
127
|
+
case '"': out.push_back('"'); break;
|
|
128
|
+
case '\\': out.push_back('\\'); break;
|
|
129
|
+
case '/': out.push_back('/'); break;
|
|
130
|
+
case 'b': out.push_back('\b'); break;
|
|
131
|
+
case 'f': out.push_back('\f'); break;
|
|
132
|
+
case 'n': out.push_back('\n'); break;
|
|
133
|
+
case 'r': out.push_back('\r'); break;
|
|
134
|
+
case 't': out.push_back('\t'); break;
|
|
135
|
+
case 'u': {
|
|
136
|
+
if (cursor + 4 > text.size()) fail("bad unicode escape");
|
|
137
|
+
const std::string hex = text.substr(cursor, 4);
|
|
138
|
+
cursor += 4;
|
|
139
|
+
const unsigned code = static_cast<unsigned>(std::strtoul(hex.c_str(), nullptr, 16));
|
|
140
|
+
if (code < 0x80) {
|
|
141
|
+
out.push_back(static_cast<char>(code));
|
|
142
|
+
} else if (code < 0x800) {
|
|
143
|
+
out.push_back(static_cast<char>(0xc0 | (code >> 6)));
|
|
144
|
+
out.push_back(static_cast<char>(0x80 | (code & 0x3f)));
|
|
145
|
+
} else {
|
|
146
|
+
out.push_back(static_cast<char>(0xe0 | (code >> 12)));
|
|
147
|
+
out.push_back(static_cast<char>(0x80 | ((code >> 6) & 0x3f)));
|
|
148
|
+
out.push_back(static_cast<char>(0x80 | (code & 0x3f)));
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
default: fail("unsupported escape");
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
out.push_back(ch);
|
|
157
|
+
}
|
|
158
|
+
value->string = out;
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
JsonPtr parseBool() {
|
|
163
|
+
auto value = std::make_shared<JsonValue>();
|
|
164
|
+
value->kind = JsonValue::Kind::Bool;
|
|
165
|
+
if (text.compare(cursor, 4, "true") == 0) { value->boolean = true; cursor += 4; return value; }
|
|
166
|
+
if (text.compare(cursor, 5, "false") == 0) { value->boolean = false; cursor += 5; return value; }
|
|
167
|
+
fail("invalid literal");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
JsonPtr parseNull() {
|
|
171
|
+
auto value = std::make_shared<JsonValue>();
|
|
172
|
+
if (text.compare(cursor, 4, "null") == 0) { cursor += 4; return value; }
|
|
173
|
+
fail("invalid literal");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
JsonPtr parseNumber() {
|
|
177
|
+
auto value = std::make_shared<JsonValue>();
|
|
178
|
+
value->kind = JsonValue::Kind::Number;
|
|
179
|
+
const std::size_t start = cursor;
|
|
180
|
+
if (peek() == '-') ++cursor;
|
|
181
|
+
while (cursor < text.size() && ((text[cursor] >= '0' && text[cursor] <= '9') || text[cursor] == '.' || text[cursor] == 'e' || text[cursor] == 'E' || text[cursor] == '+' || text[cursor] == '-')) {
|
|
182
|
+
++cursor;
|
|
183
|
+
}
|
|
184
|
+
value->number = std::strtod(text.substr(start, cursor - start).c_str(), nullptr);
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
[[noreturn]] void fatal(const std::string& message) {
|
|
190
|
+
std::cerr << message << "\n";
|
|
191
|
+
std::exit(2);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
std::string readFile(const std::string& path) {
|
|
195
|
+
std::ifstream stream(path, std::ios::binary);
|
|
196
|
+
if (!stream) fatal("cannot open " + path);
|
|
197
|
+
std::ostringstream out;
|
|
198
|
+
out << stream.rdbuf();
|
|
199
|
+
return out.str();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const JsonPtr& objectField(const JsonPtr& value, const std::string& key) {
|
|
203
|
+
const auto found = value->object.find(key);
|
|
204
|
+
if (found == value->object.end()) fatal("missing field " + key);
|
|
205
|
+
return found->second;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
bool isF64Hex(const std::string& value) {
|
|
209
|
+
if (value.size() != 18 || value[0] != '0' || value[1] != 'x') return false;
|
|
210
|
+
for (std::size_t index = 2; index < value.size(); ++index) {
|
|
211
|
+
const char ch = value[index];
|
|
212
|
+
if (!((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f'))) return false;
|
|
213
|
+
}
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
double f64FromHex(const std::string& hex) {
|
|
218
|
+
if (!isF64Hex(hex)) fatal("invalid f64 hex scalar " + hex);
|
|
219
|
+
const std::uint64_t bits = std::strtoull(hex.c_str() + 2, nullptr, 16);
|
|
220
|
+
return std::bit_cast<double>(bits);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
rundot::f64rt::Vec2 parseVec2Hex(const JsonPtr& value) {
|
|
224
|
+
if (value->kind != JsonValue::Kind::Array || value->array.size() != 2) fatal("expected [x, y] f64 hex pair");
|
|
225
|
+
return {f64FromHex(value->array[0]->string), f64FromHex(value->array[1]->string)};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
rundot::f64rt::PropertyValue toProperty(const JsonPtr& value) {
|
|
229
|
+
rundot::f64rt::PropertyValue property;
|
|
230
|
+
switch (value->kind) {
|
|
231
|
+
case JsonValue::Kind::String:
|
|
232
|
+
if (isF64Hex(value->string)) {
|
|
233
|
+
property.kind = rundot::f64rt::PropertyValue::Kind::F64;
|
|
234
|
+
property.f64 = f64FromHex(value->string);
|
|
235
|
+
} else {
|
|
236
|
+
property.kind = rundot::f64rt::PropertyValue::Kind::String;
|
|
237
|
+
property.str = value->string;
|
|
238
|
+
}
|
|
239
|
+
return property;
|
|
240
|
+
case JsonValue::Kind::Array:
|
|
241
|
+
property.kind = rundot::f64rt::PropertyValue::Kind::Vec2;
|
|
242
|
+
property.vec2 = parseVec2Hex(value);
|
|
243
|
+
return property;
|
|
244
|
+
case JsonValue::Kind::Number:
|
|
245
|
+
property.kind = rundot::f64rt::PropertyValue::Kind::Int;
|
|
246
|
+
property.i64 = static_cast<std::int64_t>(value->number);
|
|
247
|
+
return property;
|
|
248
|
+
case JsonValue::Kind::Bool:
|
|
249
|
+
property.kind = rundot::f64rt::PropertyValue::Kind::Bool;
|
|
250
|
+
property.boolean = value->boolean;
|
|
251
|
+
return property;
|
|
252
|
+
default:
|
|
253
|
+
fatal("unsupported property value kind");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
std::vector<rundot::f64rt::ComponentDef> parseComponents(const JsonPtr& componentsJson) {
|
|
258
|
+
std::vector<rundot::f64rt::ComponentDef> components;
|
|
259
|
+
for (const JsonPtr& componentJson : componentsJson->array) {
|
|
260
|
+
rundot::f64rt::ComponentDef component;
|
|
261
|
+
component.type = objectField(componentJson, "type")->string;
|
|
262
|
+
for (const auto& [name, propertyJson] : objectField(componentJson, "properties")->object) {
|
|
263
|
+
component.properties[name] = toProperty(propertyJson);
|
|
264
|
+
}
|
|
265
|
+
components.push_back(std::move(component));
|
|
266
|
+
}
|
|
267
|
+
return components;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
rundot::f64rt::PackDef parsePack(const JsonPtr& packJson) {
|
|
271
|
+
rundot::f64rt::PackDef pack;
|
|
272
|
+
pack.inputSlots = static_cast<std::int64_t>(objectField(packJson, "inputSlots")->number);
|
|
273
|
+
pack.maxSpawnsPerTick = static_cast<std::int64_t>(objectField(objectField(packJson, "budgets"), "maxSpawnsPerTick")->number);
|
|
274
|
+
for (const JsonPtr& streamJson : objectField(packJson, "rngStreams")->array) {
|
|
275
|
+
rundot::f64rt::RngStreamDef stream;
|
|
276
|
+
stream.id = objectField(streamJson, "id")->string;
|
|
277
|
+
stream.seed = static_cast<std::uint32_t>(objectField(streamJson, "seed")->number);
|
|
278
|
+
pack.rngStreams.push_back(std::move(stream));
|
|
279
|
+
}
|
|
280
|
+
for (const JsonPtr& ruleJson : objectField(packJson, "collisionRules")->array) {
|
|
281
|
+
rundot::f64rt::CollisionRuleDef rule;
|
|
282
|
+
rule.layerA = objectField(ruleJson, "layerA")->string;
|
|
283
|
+
rule.layerB = objectField(ruleJson, "layerB")->string;
|
|
284
|
+
rule.action = objectField(ruleJson, "action")->string;
|
|
285
|
+
pack.collisionRules.push_back(std::move(rule));
|
|
286
|
+
}
|
|
287
|
+
for (const JsonPtr& templateJson : objectField(packJson, "templates")->array) {
|
|
288
|
+
rundot::f64rt::TemplateDef templateDef;
|
|
289
|
+
templateDef.id = objectField(templateJson, "id")->string;
|
|
290
|
+
templateDef.pool = static_cast<std::int64_t>(objectField(templateJson, "pool")->number);
|
|
291
|
+
templateDef.components = parseComponents(objectField(templateJson, "components"));
|
|
292
|
+
pack.templates.push_back(std::move(templateDef));
|
|
293
|
+
}
|
|
294
|
+
for (const JsonPtr& entityJson : objectField(packJson, "entities")->array) {
|
|
295
|
+
rundot::f64rt::EntityDef entity;
|
|
296
|
+
entity.id = objectField(entityJson, "id")->string;
|
|
297
|
+
entity.components = parseComponents(objectField(entityJson, "components"));
|
|
298
|
+
pack.entities.push_back(std::move(entity));
|
|
299
|
+
}
|
|
300
|
+
return pack;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
rundot::f64rt::ReplayDef parseReplay(const JsonPtr& replayJson) {
|
|
304
|
+
rundot::f64rt::ReplayDef replay;
|
|
305
|
+
replay.tickCount = static_cast<std::int64_t>(objectField(replayJson, "tickCount")->number);
|
|
306
|
+
for (const JsonPtr& frameJson : objectField(replayJson, "inputFrames")->array) {
|
|
307
|
+
rundot::f64rt::InputFrameDef frame;
|
|
308
|
+
frame.tick = static_cast<std::int64_t>(objectField(frameJson, "tick")->number);
|
|
309
|
+
for (const JsonPtr& slotJson : objectField(frameJson, "slots")->array) {
|
|
310
|
+
rundot::f64rt::SlotInputDef slot;
|
|
311
|
+
slot.movement = parseVec2Hex(objectField(slotJson, "movement"));
|
|
312
|
+
slot.aim = parseVec2Hex(objectField(slotJson, "aim"));
|
|
313
|
+
slot.fire = objectField(slotJson, "fire")->kind == JsonValue::Kind::Bool && objectField(slotJson, "fire")->boolean;
|
|
314
|
+
frame.slots.push_back(slot);
|
|
315
|
+
}
|
|
316
|
+
replay.inputFrames.push_back(std::move(frame));
|
|
317
|
+
}
|
|
318
|
+
return replay;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
} // namespace
|
|
322
|
+
|
|
323
|
+
int rundot::kinetix::RunF64Profile(int argc, char** argv) {
|
|
324
|
+
if (argc < 4) {
|
|
325
|
+
std::cerr << "usage: component-runtime-f64-main <component-pack.json> <replay.json> <outDir> [hashEveryTick 0|1] [--render-frames]\n";
|
|
326
|
+
return 2;
|
|
327
|
+
}
|
|
328
|
+
const std::string packPath = argv[1];
|
|
329
|
+
const std::string replayPath = argv[2];
|
|
330
|
+
const std::string outDir = argv[3];
|
|
331
|
+
const bool hashEveryTick = argc < 5 || std::string(argv[4]) != "0";
|
|
332
|
+
const bool renderFrames = argc >= 6 && std::string(argv[5]) == "--render-frames";
|
|
333
|
+
|
|
334
|
+
const std::string packText = readFile(packPath);
|
|
335
|
+
const std::string replayText = readFile(replayPath);
|
|
336
|
+
JsonParser packParser(packText);
|
|
337
|
+
JsonParser replayParser(replayText);
|
|
338
|
+
const JsonPtr packJson = packParser.parseValue();
|
|
339
|
+
const JsonPtr replayJson = replayParser.parseValue();
|
|
340
|
+
|
|
341
|
+
const rundot::f64rt::PackDef pack = parsePack(packJson);
|
|
342
|
+
const rundot::f64rt::ReplayDef replay = parseReplay(replayJson);
|
|
343
|
+
|
|
344
|
+
rundot::f64rt::F64RunResult result;
|
|
345
|
+
std::vector<std::string> frameHashes;
|
|
346
|
+
const auto startedAt = std::chrono::steady_clock::now();
|
|
347
|
+
try {
|
|
348
|
+
if (renderFrames) {
|
|
349
|
+
rundot::f64rt::F64FrameDumpResult dump = rundot::f64rt::runReplayF64WithFrames(pack, replay, hashEveryTick, outDir);
|
|
350
|
+
result = std::move(dump.run);
|
|
351
|
+
frameHashes = std::move(dump.frameHashes);
|
|
352
|
+
} else {
|
|
353
|
+
result = rundot::f64rt::runReplayF64(pack, replay, hashEveryTick);
|
|
354
|
+
}
|
|
355
|
+
} catch (const std::exception& error) {
|
|
356
|
+
fatal(error.what());
|
|
357
|
+
}
|
|
358
|
+
const auto finishedAt = std::chrono::steady_clock::now();
|
|
359
|
+
const double elapsedMs = std::chrono::duration<double, std::milli>(finishedAt - startedAt).count();
|
|
360
|
+
|
|
361
|
+
std::ofstream hashStream(outDir + "/tick-hashes.txt", std::ios::binary);
|
|
362
|
+
if (!hashStream) fatal("cannot write tick hashes in " + outDir);
|
|
363
|
+
for (const std::string& tickHash : result.tickHashes) {
|
|
364
|
+
hashStream << tickHash << "\n";
|
|
365
|
+
}
|
|
366
|
+
hashStream.close();
|
|
367
|
+
|
|
368
|
+
const std::int64_t ticksPerSecond = elapsedMs > 0
|
|
369
|
+
? static_cast<std::int64_t>(std::llround(static_cast<double>(result.completedTicks) / elapsedMs * 1000.0))
|
|
370
|
+
: 0;
|
|
371
|
+
std::ostringstream metrics;
|
|
372
|
+
metrics << "{";
|
|
373
|
+
metrics << "\"runtime\":\"portable-cpp-f64\",";
|
|
374
|
+
metrics << "\"tickCount\":" << result.completedTicks << ",";
|
|
375
|
+
metrics << "\"elapsedMs\":" << elapsedMs << ",";
|
|
376
|
+
metrics << "\"ticksPerSecond\":" << ticksPerSecond << ",";
|
|
377
|
+
metrics << "\"liveEntitiesAtEnd\":" << result.liveEntitiesAtEnd << ",";
|
|
378
|
+
metrics << "\"finalScore\":\"" << result.finalScore << "\",";
|
|
379
|
+
metrics << "\"shipDeaths\":\"" << result.shipDeaths << "\",";
|
|
380
|
+
metrics << "\"finalDigest\":\"" << result.finalDigest << "\"";
|
|
381
|
+
metrics << "}\n";
|
|
382
|
+
std::ofstream metricsStream(outDir + "/metrics.json", std::ios::binary);
|
|
383
|
+
if (!metricsStream) fatal("cannot write metrics in " + outDir);
|
|
384
|
+
metricsStream << metrics.str();
|
|
385
|
+
metricsStream.close();
|
|
386
|
+
|
|
387
|
+
if (renderFrames) {
|
|
388
|
+
std::ostringstream framesJson;
|
|
389
|
+
framesJson << "{\"width\":" << rundot::f64rt::kFrameWidth;
|
|
390
|
+
framesJson << ",\"height\":" << rundot::f64rt::kFrameHeight;
|
|
391
|
+
framesJson << ",\"frames\":" << frameHashes.size();
|
|
392
|
+
framesJson << ",\"hashes\":[";
|
|
393
|
+
for (std::size_t index = 0; index < frameHashes.size(); ++index) {
|
|
394
|
+
if (index > 0) framesJson << ",";
|
|
395
|
+
framesJson << "\"" << frameHashes[index] << "\"";
|
|
396
|
+
}
|
|
397
|
+
framesJson << "]}\n";
|
|
398
|
+
std::ofstream framesStream(outDir + "/frame-hashes.json", std::ios::binary);
|
|
399
|
+
if (!framesStream) fatal("cannot write frame hashes in " + outDir);
|
|
400
|
+
framesStream << framesJson.str();
|
|
401
|
+
framesStream.close();
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
std::cout << metrics.str();
|
|
405
|
+
return 0;
|
|
406
|
+
}
|