@world-engines/spatial-authoring 0.1.0-alpha.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/LICENSE +46 -0
- package/dist/SpatialWorldCanvas.d.ts +18 -0
- package/dist/SpatialWorldCanvas.js +24 -0
- package/dist/SpatialWorldEditor.d.ts +38 -0
- package/dist/SpatialWorldEditor.js +61 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/plane-generation.d.ts +48 -0
- package/dist/plane-generation.js +126 -0
- package/dist/plane-generator/package.json +15 -0
- package/dist/plane-generator/plane-generator.d.ts +57 -0
- package/dist/plane-generator/plane-generator.js +310 -0
- package/dist/plane-generator/plane-generator_bg.wasm +0 -0
- package/dist/plane-generator/plane-generator_bg.wasm.d.ts +13 -0
- package/dist/procedural-city.d.ts +135 -0
- package/dist/procedural-city.js +595 -0
- package/dist/protobuf-source-codec.d.ts +57 -0
- package/dist/protobuf-source-codec.js +317 -0
- package/dist/road-generation.d.ts +52 -0
- package/dist/road-generation.js +129 -0
- package/dist/road-parcels.d.ts +83 -0
- package/dist/road-parcels.js +1584 -0
- package/dist/schema.d.ts +47 -0
- package/dist/schema.js +82 -0
- package/dist/source-adapter.d.ts +15 -0
- package/dist/source-adapter.js +23 -0
- package/dist/spatial-runtime.d.ts +210 -0
- package/dist/spatial-runtime.js +1231 -0
- package/dist/spatial-templates.d.ts +117 -0
- package/dist/spatial-templates.js +200 -0
- package/dist/worker.d.ts +13 -0
- package/dist/worker.js +9 -0
- package/package.json +155 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { validatePlaneGeneration } from "./plane-generation.js";
|
|
2
|
+
import { validateProceduralWorld } from "./procedural-city.js";
|
|
3
|
+
import { validateRoadGeneration } from "./road-generation.js";
|
|
4
|
+
import { validateSpatialTemplateConfiguration, validateSpatialTemplateLibrary, } from "./spatial-templates.js";
|
|
5
|
+
const textEncoder = new TextEncoder();
|
|
6
|
+
const textDecoder = new TextDecoder("utf-8", { fatal: true });
|
|
7
|
+
const SPATIAL_KEY = /^[a-z][a-z0-9-]{0,63}$/;
|
|
8
|
+
function fail(message) { throw new TypeError(message); }
|
|
9
|
+
function record(value, label) {
|
|
10
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
11
|
+
fail(`${label} 必须是 object`);
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
function stringValue(value, label) {
|
|
15
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
16
|
+
fail(`${label} 必须是非空 string`);
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
function finiteCoordinate(value, label) {
|
|
20
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
21
|
+
fail(`${label} 必须是有限数`);
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
function controlledActorId(value, label) {
|
|
25
|
+
if (value === undefined || value === null)
|
|
26
|
+
return value;
|
|
27
|
+
if (typeof value !== "string" || !/^[^\0\r\n]{1,512}$/u.test(value))
|
|
28
|
+
fail(`${label} 必须是有效 entity id 或 null`);
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
function spatialKind(value, label) {
|
|
32
|
+
if (value === "location" || value === "portal" || value === "region")
|
|
33
|
+
return value;
|
|
34
|
+
return fail(`${label} 不支持的 spatial kind`);
|
|
35
|
+
}
|
|
36
|
+
function stableJson(value) { return textEncoder.encode(JSON.stringify(value)); }
|
|
37
|
+
function compareCodepoint(left, right) {
|
|
38
|
+
const leftPoints = Array.from(left, (value) => value.codePointAt(0));
|
|
39
|
+
const rightPoints = Array.from(right, (value) => value.codePointAt(0));
|
|
40
|
+
const length = Math.min(leftPoints.length, rightPoints.length);
|
|
41
|
+
for (let index = 0; index < length; index += 1)
|
|
42
|
+
if (leftPoints[index] !== rightPoints[index])
|
|
43
|
+
return leftPoints[index] - rightPoints[index];
|
|
44
|
+
return leftPoints.length - rightPoints.length;
|
|
45
|
+
}
|
|
46
|
+
function stableStructuralJson(value) {
|
|
47
|
+
const normalize = (candidate) => {
|
|
48
|
+
if (Array.isArray(candidate))
|
|
49
|
+
return candidate.map(normalize);
|
|
50
|
+
if (candidate === null || typeof candidate !== "object")
|
|
51
|
+
return candidate;
|
|
52
|
+
return Object.fromEntries(Object.entries(candidate)
|
|
53
|
+
.filter(([, entry]) => entry !== undefined)
|
|
54
|
+
.sort(([left], [right]) => compareCodepoint(left, right))
|
|
55
|
+
.map(([key, entry]) => [key, normalize(entry)]));
|
|
56
|
+
};
|
|
57
|
+
return JSON.stringify(normalize(value));
|
|
58
|
+
}
|
|
59
|
+
function spatialObjectSignature(object) {
|
|
60
|
+
return stableStructuralJson({ id: object.id, key: object.key, kind: object.kind, label: object.label, x: object.x, y: object.y, references: object.references });
|
|
61
|
+
}
|
|
62
|
+
function bytesToBase64(bytes) {
|
|
63
|
+
let binary = "";
|
|
64
|
+
for (let offset = 0; offset < bytes.length; offset += 0x8000)
|
|
65
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));
|
|
66
|
+
return btoa(binary);
|
|
67
|
+
}
|
|
68
|
+
function base64ToBytes(encoded) {
|
|
69
|
+
const binary = atob(encoded);
|
|
70
|
+
const bytes = new Uint8Array(binary.length);
|
|
71
|
+
for (let index = 0; index < binary.length; index += 1)
|
|
72
|
+
bytes[index] = binary.charCodeAt(index);
|
|
73
|
+
return bytes;
|
|
74
|
+
}
|
|
75
|
+
function readVarint(bytes, start) {
|
|
76
|
+
let value = 0;
|
|
77
|
+
let shift = 0;
|
|
78
|
+
for (let offset = start; offset < bytes.length && offset < start + 10; offset += 1) {
|
|
79
|
+
const byte = bytes[offset];
|
|
80
|
+
value += (byte & 0x7f) * 2 ** shift;
|
|
81
|
+
if ((byte & 0x80) === 0)
|
|
82
|
+
return { value, next: offset + 1 };
|
|
83
|
+
shift += 7;
|
|
84
|
+
}
|
|
85
|
+
return fail("Scenario protobuf varint 无效");
|
|
86
|
+
}
|
|
87
|
+
function varint(value) {
|
|
88
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
89
|
+
fail("protobuf varint 必须为非负安全整数");
|
|
90
|
+
const out = [];
|
|
91
|
+
do {
|
|
92
|
+
const byte = value % 128;
|
|
93
|
+
value = Math.floor(value / 128);
|
|
94
|
+
out.push(value === 0 ? byte : byte | 0x80);
|
|
95
|
+
} while (value !== 0);
|
|
96
|
+
return new Uint8Array(out);
|
|
97
|
+
}
|
|
98
|
+
function concat(parts) {
|
|
99
|
+
const out = new Uint8Array(parts.reduce((size, part) => size + part.length, 0));
|
|
100
|
+
let offset = 0;
|
|
101
|
+
for (const part of parts) {
|
|
102
|
+
out.set(part, offset);
|
|
103
|
+
offset += part.length;
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
function fields(bytes) {
|
|
108
|
+
const out = new Map();
|
|
109
|
+
let offset = 0;
|
|
110
|
+
while (offset < bytes.length) {
|
|
111
|
+
const tag = readVarint(bytes, offset);
|
|
112
|
+
offset = tag.next;
|
|
113
|
+
const field = Math.floor(tag.value / 8);
|
|
114
|
+
const wire = tag.value % 8;
|
|
115
|
+
if (field <= 0)
|
|
116
|
+
fail("Scenario protobuf field 无效");
|
|
117
|
+
if (wire === 0) {
|
|
118
|
+
offset = readVarint(bytes, offset).next;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (wire !== 2)
|
|
122
|
+
fail("Scenario protobuf 包含不支持字段");
|
|
123
|
+
const length = readVarint(bytes, offset);
|
|
124
|
+
offset = length.next;
|
|
125
|
+
const end = offset + length.value;
|
|
126
|
+
if (end > bytes.length)
|
|
127
|
+
fail("Scenario protobuf length 被截断");
|
|
128
|
+
const values = out.get(field) ?? [];
|
|
129
|
+
values.push(bytes.slice(offset, end));
|
|
130
|
+
out.set(field, values);
|
|
131
|
+
offset = end;
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
function first(map, field) { return map.get(field)?.[0] ?? new Uint8Array(); }
|
|
136
|
+
function protobufSegments(bytes) {
|
|
137
|
+
const result = [];
|
|
138
|
+
let offset = 0;
|
|
139
|
+
while (offset < bytes.length) {
|
|
140
|
+
const start = offset;
|
|
141
|
+
const tag = readVarint(bytes, offset);
|
|
142
|
+
offset = tag.next;
|
|
143
|
+
const field = Math.floor(tag.value / 8);
|
|
144
|
+
const wire = tag.value % 8;
|
|
145
|
+
if (field <= 0 || (wire !== 0 && wire !== 2))
|
|
146
|
+
fail("Scenario protobuf 包含不支持字段");
|
|
147
|
+
if (wire === 0) {
|
|
148
|
+
const value = readVarint(bytes, offset);
|
|
149
|
+
offset = value.next;
|
|
150
|
+
result.push({ field, wire, encoded: bytes.slice(start, offset), varintValue: value.value });
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const length = readVarint(bytes, offset);
|
|
154
|
+
offset = length.next;
|
|
155
|
+
const end = offset + length.value;
|
|
156
|
+
if (end > bytes.length)
|
|
157
|
+
fail("Scenario protobuf length 被截断");
|
|
158
|
+
result.push({ field, wire, encoded: bytes.slice(start, end), payload: bytes.slice(offset, end) });
|
|
159
|
+
offset = end;
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
function encodedField(field, value) { return concat([varint(field * 8 + 2), varint(value.length), value]); }
|
|
164
|
+
function replaceBytesField(message, fieldNumber, payload) {
|
|
165
|
+
const replacement = encodedField(fieldNumber, payload);
|
|
166
|
+
let replaced = false;
|
|
167
|
+
const output = [];
|
|
168
|
+
for (const segment of protobufSegments(message)) {
|
|
169
|
+
if (segment.field !== fieldNumber)
|
|
170
|
+
output.push(segment.encoded);
|
|
171
|
+
else if (!replaced) {
|
|
172
|
+
output.push(replacement);
|
|
173
|
+
replaced = true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!replaced)
|
|
177
|
+
output.push(replacement);
|
|
178
|
+
return concat(output);
|
|
179
|
+
}
|
|
180
|
+
export function decodeSpatialWorldProtobuf(bytes) {
|
|
181
|
+
if (bytes === undefined)
|
|
182
|
+
return undefined;
|
|
183
|
+
const top = fields(bytes);
|
|
184
|
+
const topologyFields = fields(first(top, 3));
|
|
185
|
+
const topologySettings = first(topologyFields, 2);
|
|
186
|
+
const topology = topologySettings.length === 0 ? { kind: "freeform", width: 100, height: 100 } : record(JSON.parse(textDecoder.decode(topologySettings)), "spatial topology settings");
|
|
187
|
+
const kind = topology.kind === "grid" ? "grid" : "freeform";
|
|
188
|
+
const width = finiteCoordinate(topology.width, "spatial topology.width");
|
|
189
|
+
const height = finiteCoordinate(topology.height, "spatial topology.height");
|
|
190
|
+
if (width <= 0 || height <= 0)
|
|
191
|
+
fail("spatial topology 边界必须为正数");
|
|
192
|
+
const cellSize = topology.cellSize === undefined ? undefined : finiteCoordinate(topology.cellSize, "spatial topology.cellSize");
|
|
193
|
+
let generation;
|
|
194
|
+
try {
|
|
195
|
+
generation = topology.generation === undefined ? undefined : validatePlaneGeneration(topology.generation);
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
return fail(error instanceof Error ? error.message : "PlaneGenerationV1 无效");
|
|
199
|
+
}
|
|
200
|
+
if (generation !== undefined && (generation.widthMeters !== width || generation.heightMeters !== height))
|
|
201
|
+
fail("spatial topology.generation 范围必须与 topology 一致");
|
|
202
|
+
let roadGeneration;
|
|
203
|
+
try {
|
|
204
|
+
roadGeneration = topology.roadGeneration === undefined ? undefined : validateRoadGeneration(topology.roadGeneration);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
return fail(error instanceof Error ? error.message : "RoadGenerationV1 无效");
|
|
208
|
+
}
|
|
209
|
+
let templateLibrary;
|
|
210
|
+
try {
|
|
211
|
+
templateLibrary = topology.templateLibrary === undefined ? undefined : validateSpatialTemplateLibrary(topology.templateLibrary);
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
return fail(error instanceof Error ? error.message : "SpatialTemplateLibraryV1 无效");
|
|
215
|
+
}
|
|
216
|
+
let templates;
|
|
217
|
+
try {
|
|
218
|
+
templates = topology.templates === undefined ? undefined : validateSpatialTemplateConfiguration(topology.templates);
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
return fail(error instanceof Error ? error.message : "SpatialTemplateConfigurationV1 无效");
|
|
222
|
+
}
|
|
223
|
+
let procedural;
|
|
224
|
+
try {
|
|
225
|
+
procedural = topology.procedural === undefined ? undefined : validateProceduralWorld(topology.procedural, { widthMeters: width, heightMeters: height });
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
return fail(error instanceof Error ? error.message : "ProceduralWorldV1 无效");
|
|
229
|
+
}
|
|
230
|
+
if (procedural !== undefined && generation === undefined)
|
|
231
|
+
fail("spatial topology.procedural 必须绑定 generation");
|
|
232
|
+
if (procedural !== undefined && generation !== undefined && procedural.worldSeed !== generation.seed)
|
|
233
|
+
fail("procedural.worldSeed 必须与 generation.seed 一致");
|
|
234
|
+
const actorId = controlledActorId(topology.controlledActorId, "spatial topology.controlledActorId");
|
|
235
|
+
const objects = [];
|
|
236
|
+
for (const field of [7, 8, 9, 10, 11])
|
|
237
|
+
for (const value of top.get(field) ?? []) {
|
|
238
|
+
const object = fields(value);
|
|
239
|
+
const id = stringValue(textDecoder.decode(first(object, 1)), "spatial object id");
|
|
240
|
+
const label = stringValue(textDecoder.decode(first(object, 3)), "spatial object label");
|
|
241
|
+
const settingsBytes = first(object, 4);
|
|
242
|
+
const settings = settingsBytes.length === 0 ? {} : record(JSON.parse(textDecoder.decode(settingsBytes)), "spatial author settings");
|
|
243
|
+
const inferred = field === 7 ? "location" : field === 8 ? "portal" : "region";
|
|
244
|
+
const objectKind = settings.kind === undefined ? inferred : spatialKind(settings.kind, "spatial object kind");
|
|
245
|
+
const key = typeof settings.key === "string" ? settings.key : id.split(":").at(-1) ?? "";
|
|
246
|
+
if (!SPATIAL_KEY.test(key))
|
|
247
|
+
fail("spatial object key 无效");
|
|
248
|
+
const x = settings.x === undefined ? 0 : finiteCoordinate(settings.x, "spatial object x");
|
|
249
|
+
const y = settings.y === undefined ? 0 : finiteCoordinate(settings.y, "spatial object y");
|
|
250
|
+
if (x < 0 || x > width || y < 0 || y > height)
|
|
251
|
+
fail("spatial object 坐标超出 topology");
|
|
252
|
+
const references = Array.isArray(settings.references) ? settings.references.map((entry) => {
|
|
253
|
+
const ref = record(entry, "spatial reference");
|
|
254
|
+
const candidate = ref.ownerKind;
|
|
255
|
+
if (candidate !== "ladybug" && candidate !== "trigger" && candidate !== "scenario")
|
|
256
|
+
fail("spatial reference ownerKind 无效");
|
|
257
|
+
return { ownerId: stringValue(ref.ownerId, "spatial reference ownerId"), ownerKind: candidate, fieldPath: stringValue(ref.fieldPath, "spatial reference fieldPath") };
|
|
258
|
+
}) : [];
|
|
259
|
+
const decoded = { id, key, kind: objectKind, label, x, y, references, _wireField: field, _wireBase64: bytesToBase64(value), _authorSettings: settings };
|
|
260
|
+
decoded._wireSignature = spatialObjectSignature(decoded);
|
|
261
|
+
objects.push(decoded);
|
|
262
|
+
}
|
|
263
|
+
const topologyBytes = first(top, 3);
|
|
264
|
+
const topologyWireKind = protobufSegments(topologyBytes).find((segment) => segment.field === 1 && segment.wire === 0)?.varintValue;
|
|
265
|
+
const { worldId: storedWorldId, spatialRevision: storedSpatialRevision, ...topologySettingsWithoutWorldId } = topology;
|
|
266
|
+
void storedWorldId;
|
|
267
|
+
const spatialRevision = storedSpatialRevision ?? 0;
|
|
268
|
+
if (!Number.isSafeInteger(spatialRevision) || spatialRevision < 0)
|
|
269
|
+
fail("spatialRevision 必须是非负安全整数");
|
|
270
|
+
const normalizedTopology = { ...topologySettingsWithoutWorldId, kind, width, height, ...(cellSize === undefined ? {} : { cellSize }), ...(generation === undefined ? {} : { generation }), ...(roadGeneration === undefined ? {} : { roadGeneration }), ...(templateLibrary === undefined ? {} : { templateLibrary }), ...(templates === undefined ? {} : { templates }), ...(procedural === undefined ? {} : { procedural }), ...(actorId === undefined ? {} : { controlledActorId: actorId }) };
|
|
271
|
+
return { schemaVersion: 1, worldId: typeof topology.worldId === "string" && SPATIAL_KEY.test(topology.worldId) ? topology.worldId : "world", spatialRevision: spatialRevision, topology: normalizedTopology, objects,
|
|
272
|
+
_wireBase64: bytesToBase64(bytes), _topologyWireBase64: bytesToBase64(topologyBytes), ...(topologyWireKind === undefined ? {} : { _topologyWireKind: topologyWireKind }) };
|
|
273
|
+
}
|
|
274
|
+
export function encodeSpatialWorldProtobuf(world) {
|
|
275
|
+
if (world === undefined)
|
|
276
|
+
return undefined;
|
|
277
|
+
const topologySettings = stableJson({ ...world.topology, worldId: world.worldId, spatialRevision: world.spatialRevision });
|
|
278
|
+
const topologyPayload = world._topologyWireBase64 === undefined
|
|
279
|
+
? concat([varint(8), varint(2), encodedField(2, topologySettings)])
|
|
280
|
+
: replaceBytesField(base64ToBytes(world._topologyWireBase64), 2, topologySettings);
|
|
281
|
+
const encodeObject = (object) => {
|
|
282
|
+
const settings = stableJson({ ...(object._authorSettings ?? {}), key: object.key, kind: object.kind, x: object.x, y: object.y, references: object.references });
|
|
283
|
+
const raw = object._wireBase64 === undefined ? new Uint8Array() : base64ToBytes(object._wireBase64);
|
|
284
|
+
const payload = raw.length === 0
|
|
285
|
+
? concat([encodedField(1, textEncoder.encode(object.id)), encodedField(3, textEncoder.encode(object.label)), encodedField(4, settings)])
|
|
286
|
+
: replaceBytesField(replaceBytesField(replaceBytesField(raw, 1, textEncoder.encode(object.id)), 3, textEncoder.encode(object.label)), 4, settings);
|
|
287
|
+
return encodedField(object._wireField ?? 7, payload);
|
|
288
|
+
};
|
|
289
|
+
if (world._wireBase64 === undefined)
|
|
290
|
+
return concat([varint(8), varint(1), encodedField(3, topologyPayload), ...world.objects.map(encodeObject)]);
|
|
291
|
+
const remaining = new Set(world.objects);
|
|
292
|
+
const output = [];
|
|
293
|
+
let topologyWritten = false;
|
|
294
|
+
for (const segment of protobufSegments(base64ToBytes(world._wireBase64))) {
|
|
295
|
+
if (segment.field === 3) {
|
|
296
|
+
if (!topologyWritten) {
|
|
297
|
+
output.push(encodedField(3, topologyPayload));
|
|
298
|
+
topologyWritten = true;
|
|
299
|
+
}
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (segment.field >= 7 && segment.field <= 11 && segment.payload !== undefined) {
|
|
303
|
+
const rawBase64 = bytesToBase64(segment.payload);
|
|
304
|
+
const object = world.objects.find((candidate) => candidate._wireBase64 === rawBase64);
|
|
305
|
+
if (object !== undefined) {
|
|
306
|
+
output.push(object._wireSignature === spatialObjectSignature(object) ? segment.encoded : encodeObject(object));
|
|
307
|
+
remaining.delete(object);
|
|
308
|
+
}
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
output.push(segment.encoded);
|
|
312
|
+
}
|
|
313
|
+
if (!topologyWritten)
|
|
314
|
+
output.push(encodedField(3, topologyPayload));
|
|
315
|
+
output.push(...[...remaining].map(encodeObject));
|
|
316
|
+
return concat(output);
|
|
317
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export declare const ROAD_GENERATION_PROFILE: "terrain-roads-v1";
|
|
2
|
+
/**
|
|
3
|
+
* 绑定路径搜索、桥隧门户、地形采样和地块图语义的 canonical 规则字节。
|
|
4
|
+
* 任一语义变更都必须更新这份 JSON 并发布新 identity。
|
|
5
|
+
*/
|
|
6
|
+
export declare const LEGACY_V6_ROAD_GENERATION_RULES_CANONICAL: "{\"algorithm\":\"direction-state-a-star-v2;32-coprime-neighbors;stable-heap-tie-order;route-edge-cache-v1\",\"anchor\":\"city-guide-anchor-once;whole-candidate-single-connectors;no-grid-exact-grid-pair-foldback-v3\",\"bridge\":\"complete-dry-portal-water-span-v1;linear-deck-3d-length-at-most-maxBridgeLengthMeters;all-water-interior;no-shortcut-or-split-v2\",\"cityAccess\":\"surface-street-node-short-connect-v1;radius-over-block-times-1.5-ceil-4to12;candidate-distance-0.75to3-block;target-1.7-block;stable-city-seed-node-pair-sort;surface-only-existing-nodes;actual-sampler-direct-or-bounded-astar;complete-portal-no-acute-boundary-containment;access-unreachable-domain-v1\",\"cityBoundary\":\"radial-budget-v2;world-seed-city-seed-city-id-fnv1a-xor;24to128-rays;step-at-most10m;phase-smooth-three-harmonics-radius-0.85to1.15;actual-sampler-surface-or-complete-classify-edge-portal;surface-abs-grade-integral-ds-times-1-plus-0.6-normalized-grade-over-one-minus-normalized;normalized-grade-at-least-0.995-reject;bounded-ccw-nonclosed-3to128|unbuildable-empty;city-road-continuous-points-inside-boundary;intercity-unclipped\",\"cityField\":\"seeded-radial-terrain-contour-guide-v1\",\"direction\":\"heading-dot-at-least-zero;interior-angle-at-least90deg;all-assembled-route-final-gate-v2\",\"elevation\":\"relative-height-times-elevationScaleMeters-v1\",\"limits\":\"maxGrade-roadWidthMeters-maxBridgeLengthMeters-maxTunnelLengthMeters-v1\",\"parcelFace\":\"road-parcels-v4-boundary-highway;cityId-exact-arterial-local-street-surface-only;quantized-strict-xy-intersection-split;dcel-left-turn;positive-inside-concave-boundary;oriented-length-weighted-second-moment-principal-axis;canonical-longest-edge-isotropic-fallback;projection-midline-clip;area22500-recursion-depth8;inset-half-widest-road-plus-setback;triangulated-max-square-safe-bounds-v4\",\"parcelFrontage\":\"face-plus-uncovered-frontage-v1;cityId-exact-arterial-local-street-surface-elementary-run;polyline-slice-16to42m;two-sided-offset;depth-clamp-blockSize-times-0.18-10to20;source-and-road-corridor-clear;face-first-frontage-positive-area-nonoverlap-stable-id-greedy-v1\",\"parcelGraph\":\"road-parcels-v4-boundary-highway\",\"parcelIdentity\":\"boundary-token-sorted-hash-worldSeed-citySeed-cityId;oriented-split-normal-token;frontage-side-and-ordinal;weighted-land-use;id-sort-v3\",\"parcelTerrain\":\"polygon-touching-cells-dry-finite;adjacent-touching-cell-slope-at-most-settings-maxSlope;grid-at-most257-v1\",\"portalLookahead\":\"complete-portal-before-partial-surface-v1;exact-dry-grid-portal-preserved-v1;off-grid-origin-requires-validated-surface-connector-v1;connector-budget-cost-required-v1\",\"repair\":\"same-core-segment-splice;10m-grid;at-most8-attempts;min-margin200m-v1\",\"roadClass\":\"required-kind-intercity-arterial-local;required-class-street-motorway;intercity-motorway-else-street;all-intercity-motorway-and-city-nonbuildable-roads-forbidden-corridor;centerline-no-intersection-or-containment;clearance-half-width-plus-setback-v1\",\"shortcut\":\"surface-run-control-edge-boundaries;candidates-i-to-j-j-at-most-i-plus24-or-last;actual-sampler-water-grade-step-at-most10m;reverse-sparse-dag-heading-compatible-dot-epsilon1e-12;no-accumulated-cost;farthest-reachable-stable-descending;retain-original-if-no-complete-chain;whole-road-stable-branch-unreachable-if-final-acute;bridge-tunnel-excluded-v3\",\"terrain\":\"canonical-plane-hydrology-fixed-sampling;global-long-axis257;city-long-axis65to257;actual-finite-sampler-step10m;no-sub10m-obstacle-guarantee-v1\",\"tunnel\":\"covered-dry-portal-bore;actual-cover-at-least1m;3d-length-at-most-maxTunnelLengthMeters;no-shortcut-or-split;no-water-interior;roadWidth-surface-abutment-v2\",\"version\":6}";
|
|
7
|
+
/**
|
|
8
|
+
* 该字符串绑定 profile、算法版本与上方 canonical 规则字节的 SHA-256。
|
|
9
|
+
* 道路语义变化时必须发布新 identity,禁止让旧世界静默重算为不同结果。
|
|
10
|
+
*/
|
|
11
|
+
export declare const LEGACY_V6_IDENTITY: "chat.worldengine.terrain-roads/v1;rules-sha256=9dc32e6e3f5e63fbf378d6b83dfa67c2e9d62dabfebe5b432956f7ff0fdb8443";
|
|
12
|
+
export declare const STRUCTURED_V8_ROAD_GENERATION_RULES_CANONICAL: "{\"version\":8,\"algorithm\":\"direction-state-a-star-v2;32-coprime-neighbors;stable-heap-tie-order;route-edge-cache-v1\",\"cityField\":\"structured-two-family-seeded-skeleton-v8;candidate-only-actual-terrain-router-required\",\"cityCandidateBudget\":\"boundary-polygon-shoelace-abs-area-m2;ceil-sqrt-area-over-block-times-0.42-per-family;clamp6to15;not-nominal-radius-area;not-feasible-terrain-mask\",\"cityCandidateFamilies\":\"six-arterial-rays-from-center-four-collinear-points;arterial-base-seeded-angle-plus-sixth-turn-plus-seeded-jitter;two-transverse-families-base-and-base-plus-pi-over-two-plus-seeded-0.18rad;collector-and-local;four-point-parabolic-sweeps;seeded-bend-block-times-0.32;actual-terrain-and-boundary-gates\",\"cityBoundary\":\"radial-budget-v2;world-seed-city-seed-city-id-fnv1a-xor;24to128-rays;step-at-most10m;phase-smooth-three-harmonics-radius-0.85to1.15;actual-sampler-surface-or-complete-classify-edge-portal;surface-abs-grade-integral-ds-times-1-plus-0.6-normalized-grade-over-one-minus-normalized;normalized-grade-at-least-0.995-reject;bounded-ccw-nonclosed-3to128|unbuildable-empty;city-road-continuous-points-inside-boundary;intercity-unclipped\",\"cityStructuredClip\":\"v8-nonarterial-all-exact-boundary-inside-runs;candidate-segment-boundary-intersections;boundary-inclusive;rooted-run-nearest-original-sweep-midpoint-else-inside-run-nearest-midpoint;arterial-uses-legacy-origin-first-exit-clip;final-route-sample-boundary-verify-at-most10m\",\"cityStructuredRoot\":\"v8-transverse-same-city-surface-strict-internal-xy-intersection;parallel-collinear-endpoint-near-end-reject-epsilon1e-9;nearest-original-sweep-midpoint-then-stable-road-segment-point-pair-tie-order;root-z-actual-terrain-sample;both-old-surface-halves-revalidate-before-noding\",\"cityRootArms\":\"root-left-right-independent-anchor-direct-or-bounded-astar-finish-and-boundary-check;both-concatenate|one-commit-valid-arm-and-record-other-unreachable|none-reject;assembled-root-route-no-acute-final-gate;preserve-root-xy\",\"citySurfaceNoding\":\"v8-same-city-nonintercity-surface-only;strict-internal-xy-crossing-with-nonfinite-reject-and-dimensionless-interior-epsilon1e-9|positive-collinear-xy-overlap-line-distance-at-most1e-7;canonical-overlap-endpoints-xy-round-1e-7m-and-z-actual-terrain-sample;all-four-half-edges-or-local-endpoint-replacement-surface-revalidate;stable-road-segment-original-edge-index-descending-and-t-descending-insert-order;not-cross-city-bridge-tunnel-or-general-grade-separation\",\"cityExactSurfaceReuse\":\"after-same-city-surface-noding-and-common-overlap-endpoint-rewrite;same-city-nonintercity-contiguous-all-surface-normalized-run-only-without-crossing-bridge-or-tunnel;undirected-exact-xyz-bit-elementary-key;arterial-then-local-then-lexical-road-id-owner;partial-overlap-reuse-removes-nonowner-exact-pieces-retains-continuous-chains;single-retained-chain-keeps-parent-id|multiple-retained-chains-child-id-parent-plus-retained-child-first-last-xy-IEEE-bits-and-reuse-purpose-no-ordinal;refresh-sorted-layout-ids;no-reuse-status-output;not-near-or-unvalidated-overlap-reuse\",\"cityAccess\":\"legacy-v6-only:surface-street-node-short-connect-v1;radius-over-block-times-1.5-ceil-4to12;candidate-distance-0.75to3-block;target-1.7-block;stable-city-seed-node-pair-sort;surface-only-existing-nodes;actual-sampler-direct-or-bounded-astar;complete-portal-no-acute-boundary-containment;access-unreachable-domain-v1\",\"parcelFace\":\"structured-v8-convex-closed-face-with-every-simplified-edge-matched-to-real-ordinary-street-elementary-frontage-only;per-edge-inward-half-plane-offset-before-split;clearance-max-matching-edge-half-width-plus-nonnegative-setback;mixed-width-collinear-match-uses-max-conservative-clearance;nonconvex-unmatched-or-invalid-offset-closed-face-reject-no-topology-guess\",\"parcelSplit\":\"structured-v8-projection-midline-clip-area-settings-maxFaceAreaMeters-default22500-recursion-depth8;each-child-needs-longest-continuous-collinear-overlap-with-inset-real-street-frontage-at-least-max-10-min-20-widest-road;not-fragment-sum;on-failure-retain-parent-unsplit;cut-edge-is-not-frontage\",\"parcelFrontage\":\"face-plus-uncovered-frontage-v1;open-area-only-real-surface-ordinary-street-segment-run;polyline-slice-min16-max42-except-unsplittable-first-edge-may-exceed42;two-sided-offset;inner-road-corridor-clearance-plus-0.01;source-run-clearance-recheck-at-least-clearance-minus0.2;depth-clamp-blockSize-times-0.18-10to20;source-and-road-corridor-clear;face-first-frontage-positive-area-nonoverlap-stable-id-greedy-v1;no-entrance-or-navigation-reachability-claim\",\"legacyParcel\":\"legacy-v6-undefined-or-explicit-legacy-uses-prior-split-and-centroid-inset-path;v8-only-convex-offset-and-child-frontage-rule;shared-invalid-algorithm-fail-closed\",\"parcelGraph\":\"road-parcels-v4-boundary-highway\",\"parcelIdentity\":\"boundary-token-sorted-hash-worldSeed-citySeed-cityId;oriented-split-normal-token;frontage-side-and-ordinal;weighted-land-use;id-sort-v3\",\"parcelTerrain\":\"polygon-touching-cells-dry-finite;adjacent-touching-cell-slope-at-most-settings-maxSlope;grid-at-most257-v1\",\"roadClass\":\"required-kind-intercity-arterial-local;required-class-street-motorway;intercity-motorway-else-street;all-intercity-motorway-and-city-nonbuildable-roads-forbidden-corridor;centerline-no-intersection-or-containment;clearance-half-width-plus-setback-v1\",\"direction\":\"heading-dot-at-least-zero;interior-angle-at-least90deg;all-assembled-route-final-gate-v2\",\"limits\":\"maxGrade-roadWidthMeters-maxBridgeLengthMeters-maxTunnelLengthMeters-v1\",\"elevation\":\"relative-height-times-elevationScaleMeters-v1\",\"terrain\":\"canonical-plane-hydrology-fixed-sampling;global-long-axis257;city-long-axis65to257;actual-finite-sampler-step10m;no-sub10m-obstacle-guarantee-v1\",\"portalLookahead\":\"complete-portal-before-partial-surface-v1;exact-dry-grid-portal-preserved-v1;off-grid-origin-requires-validated-surface-connector-v1;connector-budget-cost-required-v1\",\"bridge\":\"complete-dry-portal-water-span-v1;linear-deck-3d-length-at-most-maxBridgeLengthMeters;all-water-interior;no-shortcut-or-split-v2\",\"tunnel\":\"covered-dry-portal-bore;actual-cover-at-least1m;3d-length-at-most-maxTunnelLengthMeters;no-shortcut-or-split;no-water-interior;roadWidth-surface-abutment-v2\",\"shortcut\":\"surface-run-control-edge-boundaries;candidates-i-to-j-j-at-most-i-plus24-or-last;actual-sampler-water-grade-step-at-most10m;reverse-sparse-dag-heading-compatible-dot-epsilon1e-12;no-accumulated-cost;farthest-reachable-stable-descending;retain-original-if-no-complete-chain;whole-road-stable-branch-unreachable-if-final-acute;bridge-tunnel-excluded-v3\",\"repair\":\"same-core-segment-splice;10m-grid;at-most8-attempts;min-margin200m-v1\"}";
|
|
13
|
+
export declare const STRUCTURED_V8_IDENTITY: "chat.worldengine.terrain-roads/v1;rules-sha256=03ab0544e8fdfc741045dccac18a946eb95b76bc10e9880012cba50060b29b4f";
|
|
14
|
+
export declare const FRONTAGE_BALANCED_V9_ROAD_GENERATION_RULES_CANONICAL: "{\"version\":9,\"algorithm\":\"direction-state-a-star-v2;32-coprime-neighbors;stable-heap-tie-order;route-edge-cache-v1\",\"cityField\":\"structured-two-family-seeded-skeleton-v8;candidate-only-actual-terrain-router-required\",\"cityCandidateBudget\":\"boundary-polygon-shoelace-abs-area-m2;ceil-sqrt-area-over-block-times-0.42-per-family;clamp6to15;not-nominal-radius-area;not-feasible-terrain-mask\",\"cityCandidateFamilies\":\"six-arterial-rays-from-center-four-collinear-points;arterial-base-seeded-angle-plus-sixth-turn-plus-seeded-jitter;two-transverse-families-base-and-base-plus-pi-over-two-plus-seeded-0.18rad;collector-and-local;four-point-parabolic-sweeps;seeded-bend-block-times-0.32;actual-terrain-and-boundary-gates\",\"cityBoundary\":\"radial-budget-v2;world-seed-city-seed-city-id-fnv1a-xor;24to128-rays;step-at-most10m;phase-smooth-three-harmonics-radius-0.85to1.15;actual-sampler-surface-or-complete-classify-edge-portal;surface-abs-grade-integral-ds-times-1-plus-0.6-normalized-grade-over-one-minus-normalized;normalized-grade-at-least-0.995-reject;bounded-ccw-nonclosed-3to128|unbuildable-empty;city-road-continuous-points-inside-boundary;intercity-unclipped\",\"cityStructuredClip\":\"v8-nonarterial-all-exact-boundary-inside-runs;candidate-segment-boundary-intersections;boundary-inclusive;rooted-run-nearest-original-sweep-midpoint-else-inside-run-nearest-midpoint;arterial-uses-legacy-origin-first-exit-clip;final-route-sample-boundary-verify-at-most10m\",\"cityStructuredRoot\":\"v8-transverse-same-city-surface-strict-internal-xy-intersection;parallel-collinear-endpoint-near-end-reject-epsilon1e-9;nearest-original-sweep-midpoint-then-stable-road-segment-point-pair-tie-order;root-z-actual-terrain-sample;both-old-surface-halves-revalidate-before-noding\",\"cityRootArms\":\"root-left-right-independent-anchor-direct-or-bounded-astar-finish-and-boundary-check;both-concatenate|one-commit-valid-arm-and-record-other-unreachable|none-reject;assembled-root-route-no-acute-final-gate;preserve-root-xy\",\"citySurfaceNoding\":\"v8-same-city-nonintercity-surface-only;strict-internal-xy-crossing-with-nonfinite-reject-and-dimensionless-interior-epsilon1e-9|positive-collinear-xy-overlap-line-distance-at-most1e-7;canonical-overlap-endpoints-xy-round-1e-7m-and-z-actual-terrain-sample;all-four-half-edges-or-local-endpoint-replacement-surface-revalidate;stable-road-segment-original-edge-index-descending-and-t-descending-insert-order;not-cross-city-bridge-tunnel-or-general-grade-separation\",\"cityExactSurfaceReuse\":\"after-same-city-surface-noding-and-common-overlap-endpoint-rewrite;same-city-nonintercity-contiguous-all-surface-normalized-run-only-without-crossing-bridge-or-tunnel;undirected-exact-xyz-bit-elementary-key;arterial-then-local-then-lexical-road-id-owner;partial-overlap-reuse-removes-nonowner-exact-pieces-retains-continuous-chains;single-retained-chain-keeps-parent-id|multiple-retained-chains-child-id-parent-plus-retained-child-first-last-xy-IEEE-bits-and-reuse-purpose-no-ordinal;refresh-sorted-layout-ids;no-reuse-status-output;not-near-or-unvalidated-overlap-reuse\",\"cityAccess\":\"legacy-v6-only:surface-street-node-short-connect-v1;radius-over-block-times-1.5-ceil-4to12;candidate-distance-0.75to3-block;target-1.7-block;stable-city-seed-node-pair-sort;surface-only-existing-nodes;actual-sampler-direct-or-bounded-astar;complete-portal-no-acute-boundary-containment;access-unreachable-domain-v1\",\"parcelAlgorithm\":\"identity-bound-v1;v6=legacy-v6-road-core-and-parcels;v8=structured-v8-road-core-and-parcels;v9=structured-v8-road-core-plus-frontage-balanced-v9-parcels;v9-vs-v8-selects-parcel-only;no-load-time-upgrade\",\"parcelFace\":\"structured-v8-convex-closed-face-with-every-simplified-edge-matched-to-real-ordinary-street-elementary-frontage-only;per-edge-inward-half-plane-offset-before-split;clearance-max-matching-edge-half-width-plus-nonnegative-setback;mixed-width-collinear-match-uses-max-conservative-clearance;nonconvex-unmatched-or-invalid-offset-closed-face-reject-no-topology-guess\",\"parcelSplit\":\"frontage-balanced-v9-primary-structured-v8-projection-midline-clip-area-settings-maxFaceAreaMeters-default22500;each-child-needs-longest-continuous-collinear-overlap-with-inset-real-street-frontage-at-least-max-10-min-20-widest-road;not-fragment-sum;when-primary-area-frontage-invalid-try-real-inset-frontage-overlap-cuts-length-desc-edge-id-normal-key;canonical-positive-frontage-tangent-normal;cut-overlap-midpoint-projection;dedup-normal-and-cut;first-area-frontage-valid-candidate;recursive-split-depth8;emitted-children-pass-existing-final-terrain-corridor-nonoverlap-gates;no-retry-after-final-rejection;on-all-area-frontage-failure-retain-parent-unsplit;cut-edge-is-not-frontage\",\"parcelFrontage\":\"face-plus-uncovered-frontage-v1;open-area-only-real-surface-ordinary-street-segment-run;polyline-slice-min16-max42-except-unsplittable-first-edge-may-exceed42;two-sided-offset;inner-road-corridor-clearance-plus-0.01;source-run-clearance-recheck-at-least-clearance-minus0.2;depth-clamp-blockSize-times-0.18-10to20;source-and-road-corridor-clear;face-first-frontage-positive-area-nonoverlap-stable-id-greedy-v1;no-entrance-or-navigation-reachability-claim\",\"legacyParcel\":\"legacy-v6-undefined-or-explicit-legacy-uses-prior-split-and-centroid-inset-path;structured-v8-uses-convex-offset-and-primary-child-frontage-rule;frontage-balanced-v9-keeps-v8-road-core-face-inset-and-primary-split-plus-stable-frontage-cut-fallback;shared-invalid-algorithm-fail-closed\",\"parcelGraph\":\"road-parcels-v4-boundary-highway\",\"parcelIdentity\":\"boundary-token-sorted-hash-worldSeed-citySeed-cityId;oriented-split-normal-token;frontage-side-and-ordinal;weighted-land-use;id-sort-v3\",\"parcelTerrain\":\"polygon-touching-cells-dry-finite;adjacent-touching-cell-slope-at-most-settings-maxSlope;grid-at-most257-v1\",\"roadClass\":\"required-kind-intercity-arterial-local;required-class-street-motorway;intercity-motorway-else-street;all-intercity-motorway-and-city-nonbuildable-roads-forbidden-corridor;centerline-no-intersection-or-containment;clearance-half-width-plus-setback-v1\",\"direction\":\"heading-dot-at-least-zero;interior-angle-at-least90deg;all-assembled-route-final-gate-v2\",\"limits\":\"maxGrade-roadWidthMeters-maxBridgeLengthMeters-maxTunnelLengthMeters-v1\",\"elevation\":\"relative-height-times-elevationScaleMeters-v1\",\"terrain\":\"canonical-plane-hydrology-fixed-sampling;global-long-axis257;city-long-axis65to257;actual-finite-sampler-step10m;no-sub10m-obstacle-guarantee-v1\",\"portalLookahead\":\"complete-portal-before-partial-surface-v1;exact-dry-grid-portal-preserved-v1;off-grid-origin-requires-validated-surface-connector-v1;connector-budget-cost-required-v1\",\"bridge\":\"complete-dry-portal-water-span-v1;linear-deck-3d-length-at-most-maxBridgeLengthMeters;all-water-interior;no-shortcut-or-split-v2\",\"tunnel\":\"covered-dry-portal-bore;actual-cover-at-least1m;3d-length-at-most-maxTunnelLengthMeters;no-shortcut-or-split;no-water-interior;roadWidth-surface-abutment-v2\",\"shortcut\":\"surface-run-control-edge-boundaries;candidates-i-to-j-j-at-most-i-plus24-or-last;actual-sampler-water-grade-step-at-most10m;reverse-sparse-dag-heading-compatible-dot-epsilon1e-12;no-accumulated-cost;farthest-reachable-stable-descending;retain-original-if-no-complete-chain;whole-road-stable-branch-unreachable-if-final-acute;bridge-tunnel-excluded-v3\",\"repair\":\"same-core-segment-splice;10m-grid;at-most8-attempts;min-margin200m-v1\"}";
|
|
15
|
+
export declare const FRONTAGE_BALANCED_V9_IDENTITY: "chat.worldengine.terrain-roads/v1;rules-sha256=671b01df50d378c85208f910ba10e63ff5b03d3787619898c8de60453329b38b";
|
|
16
|
+
export declare const ROAD_GENERATION_RULES_CANONICAL: "{\"version\":10,\"algorithm\":\"direction-state-a-star-v2;32-coprime-neighbors;stable-heap-tie-order;route-edge-cache-v1\",\"cityField\":\"structured-two-family-seeded-skeleton-v8;candidate-only-actual-terrain-router-required\",\"cityCandidateBudget\":\"boundary-polygon-shoelace-abs-area-m2;ceil-sqrt-area-over-block-times-0.42-per-family;clamp6to15;not-nominal-radius-area;not-feasible-terrain-mask\",\"cityCandidateFamilies\":\"six-arterial-rays-from-center-four-collinear-points;arterial-base-seeded-angle-plus-sixth-turn-plus-seeded-jitter;two-transverse-families-base-and-base-plus-pi-over-two-plus-seeded-0.18rad;collector-and-local;four-point-parabolic-sweeps;seeded-bend-block-times-0.32;actual-terrain-and-boundary-gates\",\"cityBoundary\":\"radial-budget-v2;world-seed-city-seed-city-id-fnv1a-xor;24to128-rays;step-at-most10m;phase-smooth-three-harmonics-radius-0.85to1.15;actual-sampler-surface-or-complete-classify-edge-portal;surface-abs-grade-integral-ds-times-1-plus-0.6-normalized-grade-over-one-minus-normalized;normalized-grade-at-least-0.995-reject;bounded-ccw-nonclosed-3to128|unbuildable-empty;city-road-continuous-points-inside-boundary;intercity-unclipped\",\"cityStructuredClip\":\"v8-nonarterial-all-exact-boundary-inside-runs;candidate-segment-boundary-intersections;boundary-inclusive;rooted-run-nearest-original-sweep-midpoint-else-inside-run-nearest-midpoint;arterial-uses-legacy-origin-first-exit-clip;final-route-sample-boundary-verify-at-most10m\",\"cityStructuredRoot\":\"v8-transverse-same-city-surface-strict-internal-xy-intersection;parallel-collinear-endpoint-near-end-reject-epsilon1e-9;nearest-original-sweep-midpoint-then-stable-road-segment-point-pair-tie-order;root-z-actual-terrain-sample;both-old-surface-halves-revalidate-before-noding\",\"cityRootArms\":\"root-left-right-independent-anchor-direct-or-bounded-astar-finish-and-boundary-check;both-concatenate|one-commit-valid-arm-and-record-other-unreachable|none-reject;assembled-root-route-no-acute-final-gate;preserve-root-xy\",\"citySurfaceNoding\":\"v8-same-city-nonintercity-surface-only;strict-internal-xy-crossing-with-nonfinite-reject-and-dimensionless-interior-epsilon1e-9|positive-collinear-xy-overlap-line-distance-at-most1e-7;canonical-overlap-endpoints-xy-round-1e-7m-and-z-actual-terrain-sample;all-four-half-edges-or-local-endpoint-replacement-surface-revalidate;stable-road-segment-original-edge-index-descending-and-t-descending-insert-order;not-cross-city-bridge-tunnel-or-general-grade-separation\",\"cityExactSurfaceReuse\":\"after-same-city-surface-noding-and-common-overlap-endpoint-rewrite;same-city-nonintercity-contiguous-all-surface-normalized-run-only-without-crossing-bridge-or-tunnel;undirected-exact-xyz-bit-elementary-key;arterial-then-local-then-lexical-road-id-owner;partial-overlap-reuse-removes-nonowner-exact-pieces-retains-continuous-chains;single-retained-chain-keeps-parent-id|multiple-retained-chains-child-id-parent-plus-retained-child-first-last-xy-IEEE-bits-and-reuse-purpose-no-ordinal;refresh-sorted-layout-ids;no-reuse-status-output;not-near-or-unvalidated-overlap-reuse\",\"cityAccess\":\"legacy-v6-only:surface-street-node-short-connect-v1;radius-over-block-times-1.5-ceil-4to12;candidate-distance-0.75to3-block;target-1.7-block;stable-city-seed-node-pair-sort;surface-only-existing-nodes;actual-sampler-direct-or-bounded-astar;complete-portal-no-acute-boundary-containment;access-unreachable-domain-v1\",\"parcelAlgorithm\":\"identity-bound-v1;v6=legacy-v6-road-core-and-parcels;v8=structured-v8-road-core-and-parcels;v9=structured-v8-road-core-plus-frontage-balanced-v9-parcels;v10=structured-v8-road-core-plus-shape-filled-v10-parcels;v10-vs-v9-selects-parcel-only;no-load-time-upgrade\",\"parcelFace\":\"shape-filled-v10-simple-single-ring-closed-face;real-ordinary-street-elementary-frontage-only;collinear-ring-simplify;per-edge-inward-offset-max-matching-real-road-half-width-plus-nonnegative-setback-plus-4epsilon-quantization-guard;inset-must-stay-within-source-and-strict-road-clearance;self-cross-reject;concave-ear-clip-then-stable-convex-merge;internal-chords-have-no-offset-and-no-frontage;unsupported-complex-or-hole-face-reject-no-topology-guess\",\"parcelSplit\":\"shape-filled-v10-long-axis-oriented-bounds-primary-then-stable-face-axis;projection-midline-clip-targets-settings-maxFaceAreaMeters-default22500-not-terminal-hard-limit;shape-context-cut-children-inset-by-4epsilon-per-side-leaving-8epsilon-artificial-nonroad-gap-to-prevent-quantized-shared-edge-positive-overlap;no-distance-or-overlap-gate-relaxation;both-children-area-at-least-settings-minParcelAreaMeters-default36;each-child-needs-longest-continuous-real-inset-street-frontage-at-least-max-10-min-20-widest-road;shape-node-and-recursive-output-hard-gates;pair-atomic-no-single-child-commit;frontage-balanced-real-inset-frontage-cut-fallback;recursive-depth8;at-depth-or-no-valid-cut-shape-valid-parent-may-remain-even-over-max-area;at-depth-or-no-valid-cut-invalid-shape-reject;cut-edge-is-not-frontage\",\"parcelFrontage\":\"shape-filled-v10-face-first-then-uncovered-frontage-only;closed-valid-inset-faces-fill-before-open-street;real-surface-ordinary-street-run-resampled-at-at-most-min-10-maxSliceLength;two-sided-offset;depth-at-least-blockSize-times-0.18-and-at-least-minShortSide-times-sqrt2-plus1;maxSliceLength-max-requiredStripDimension-plus10-only-when-requiredStripDimension-over42-and-min-42-maxAspectRatio-times-depth-so-minShortSide-override-remains-sliceable;slice-at-least-minShortSide-times-sqrt2-plus1;shape-safe-bounds-and-ratio-hard-gates;source-and-all-road-corridor-boundary-terrain-clear;positive-area-nonoverlap-stable-id-greedy;no-entrance-or-navigation-reachability-claim\",\"legacyParcel\":\"legacy-v6-undefined-or-explicit-legacy-uses-prior-split-and-centroid-inset-path;structured-v8-uses-convex-offset-and-primary-child-frontage-rule;frontage-balanced-v9-keeps-v8-road-core-face-inset-and-primary-split-plus-stable-frontage-cut-fallback;shape-filled-v10-keeps-v8-road-core-and-uses-simple-ring-inset-concave-decomposition-shape-hard-gates-and-uncovered-frontage-only;shared-invalid-algorithm-fail-closed\",\"parcelGraph\":\"road-parcels-v4-boundary-highway\",\"parcelIdentity\":\"boundary-token-sorted-hash-worldSeed-citySeed-cityId;oriented-split-normal-token;frontage-side-and-ordinal;weighted-land-use;id-sort-v3;shape-filled-v10-canonical-ring-ear-clip-first-valid-vertex-order-then-first-pair-convex-merge-loop;decomposed-piece-traversal-order-is-explicit-root-c-pieceIndex-and-affects-stable-id\",\"parcelTerrain\":\"polygon-touching-cells-dry-finite;adjacent-touching-cell-slope-at-most-settings-maxSlope;grid-at-most257-v1\",\"roadClass\":\"required-kind-intercity-arterial-local;required-class-street-motorway;intercity-motorway-else-street;all-intercity-motorway-and-city-nonbuildable-roads-forbidden-corridor;centerline-no-intersection-or-containment;clearance-half-width-plus-setback-v1\",\"direction\":\"heading-dot-at-least-zero;interior-angle-at-least90deg;all-assembled-route-final-gate-v2\",\"limits\":\"maxGrade-roadWidthMeters-maxBridgeLengthMeters-maxTunnelLengthMeters-v1\",\"elevation\":\"relative-height-times-elevationScaleMeters-v1\",\"terrain\":\"canonical-plane-hydrology-fixed-sampling;global-long-axis257;city-long-axis65to257;actual-finite-sampler-step10m;no-sub10m-obstacle-guarantee-v1\",\"portalLookahead\":\"complete-portal-before-partial-surface-v1;exact-dry-grid-portal-preserved-v1;off-grid-origin-requires-validated-surface-connector-v1;connector-budget-cost-required-v1\",\"bridge\":\"complete-dry-portal-water-span-v1;linear-deck-3d-length-at-most-maxBridgeLengthMeters;all-water-interior;no-shortcut-or-split-v2\",\"tunnel\":\"covered-dry-portal-bore;actual-cover-at-least1m;3d-length-at-most-maxTunnelLengthMeters;no-shortcut-or-split;no-water-interior;roadWidth-surface-abutment-v2\",\"shortcut\":\"surface-run-control-edge-boundaries;candidates-i-to-j-j-at-most-i-plus24-or-last;actual-sampler-water-grade-step-at-most10m;reverse-sparse-dag-heading-compatible-dot-epsilon1e-12;no-accumulated-cost;farthest-reachable-stable-descending;retain-original-if-no-complete-chain;whole-road-stable-branch-unreachable-if-final-acute;bridge-tunnel-excluded-v3\",\"repair\":\"same-core-segment-splice;10m-grid;at-most8-attempts;min-margin200m-v1\",\"parcelShape\":\"coordinates-round-decimal6-epsilon1e-6;minimum-oriented-convex-hull-edge-aligned-bounds;settings-maxAspectRatio-finite-at-least1-default4;settings-minShortSideMeters-finite-at-least26-default26;short-side-at-least-minShortSideMeters;long-side-at-most-short-side-times-maxAspectRatio;safe-axis-aligned-square-inside-polygon-width-and-height-at-least-minShortSideMeters;all-partition-nodes-and-emitted-parcels-terrain-boundary-corridor-clear\"}";
|
|
17
|
+
export declare const ROAD_GENERATION_IDENTITY: "chat.worldengine.terrain-roads/v1;rules-sha256=58fc51cfe9986a8da0cc6931c8600cf4c1afbbcda80f58e00038b76f982aec5b";
|
|
18
|
+
/**
|
|
19
|
+
* 已发布 identity 的精确 allowlist。新算法必须先有冻结的 rules digest,才能加入这里;
|
|
20
|
+
* 不得以“当前默认值”替换已保存 source 的 identity。
|
|
21
|
+
*/
|
|
22
|
+
export declare const SUPPORTED_ROAD_GENERATION_IDENTITIES: readonly ["chat.worldengine.terrain-roads/v1;rules-sha256=9dc32e6e3f5e63fbf378d6b83dfa67c2e9d62dabfebe5b432956f7ff0fdb8443", "chat.worldengine.terrain-roads/v1;rules-sha256=03ab0544e8fdfc741045dccac18a946eb95b76bc10e9880012cba50060b29b4f", "chat.worldengine.terrain-roads/v1;rules-sha256=671b01df50d378c85208f910ba10e63ff5b03d3787619898c8de60453329b38b", "chat.worldengine.terrain-roads/v1;rules-sha256=58fc51cfe9986a8da0cc6931c8600cf4c1afbbcda80f58e00038b76f982aec5b"];
|
|
23
|
+
export type RoadGenerationIdentity = (typeof SUPPORTED_ROAD_GENERATION_IDENTITIES)[number];
|
|
24
|
+
/**
|
|
25
|
+
* 已保存 identity 到 core selector 的语义映射。source 只能按它自身的已发布 identity
|
|
26
|
+
* 重放;新默认值不得覆盖旧 source。
|
|
27
|
+
*/
|
|
28
|
+
export type RoadGenerationAlgorithm = "legacy-v6" | "structured-v8";
|
|
29
|
+
export declare function roadGenerationAlgorithm(identity: RoadGenerationIdentity): RoadGenerationAlgorithm;
|
|
30
|
+
export type RoadParcelAlgorithm = "legacy-v6" | "structured-v8" | "frontage-balanced-v9" | "shape-filled-v10";
|
|
31
|
+
/** 已保存 identity 独立选择 parcel 语义;V9 仍复用已发布 V8 道路 core。 */
|
|
32
|
+
export declare function roadParcelAlgorithm(identity: RoadGenerationIdentity): RoadParcelAlgorithm;
|
|
33
|
+
/**
|
|
34
|
+
* 只有已冻结的后继 identity 才能令旧 source 出现显式升级预览入口。
|
|
35
|
+
* 加载或保存绝不能据此静默升级。
|
|
36
|
+
*/
|
|
37
|
+
export declare function roadGenerationUpgradeTarget(identity: RoadGenerationIdentity): RoadGenerationIdentity | undefined;
|
|
38
|
+
export interface RoadGenerationV1 {
|
|
39
|
+
readonly profile: typeof ROAD_GENERATION_PROFILE;
|
|
40
|
+
readonly identity: RoadGenerationIdentity;
|
|
41
|
+
readonly allowBridges: boolean;
|
|
42
|
+
readonly allowTunnels: boolean;
|
|
43
|
+
readonly maxBridgeLengthMeters: number;
|
|
44
|
+
readonly maxTunnelLengthMeters: number;
|
|
45
|
+
readonly maxGrade: number;
|
|
46
|
+
readonly roadWidthMeters: number;
|
|
47
|
+
/** 相对地形高度值换算为米的比例。 */
|
|
48
|
+
readonly elevationScaleMeters: number;
|
|
49
|
+
}
|
|
50
|
+
export declare const DEFAULT_ROAD_GENERATION: Readonly<RoadGenerationV1>;
|
|
51
|
+
/** 严格验证桥隧道路设置,拒绝未知字段并返回不可变 canonical 副本。 */
|
|
52
|
+
export declare function validateRoadGeneration(input: unknown): RoadGenerationV1;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
export const ROAD_GENERATION_PROFILE = "terrain-roads-v1";
|
|
2
|
+
/**
|
|
3
|
+
* 绑定路径搜索、桥隧门户、地形采样和地块图语义的 canonical 规则字节。
|
|
4
|
+
* 任一语义变更都必须更新这份 JSON 并发布新 identity。
|
|
5
|
+
*/
|
|
6
|
+
export const LEGACY_V6_ROAD_GENERATION_RULES_CANONICAL = '{"algorithm":"direction-state-a-star-v2;32-coprime-neighbors;stable-heap-tie-order;route-edge-cache-v1","anchor":"city-guide-anchor-once;whole-candidate-single-connectors;no-grid-exact-grid-pair-foldback-v3","bridge":"complete-dry-portal-water-span-v1;linear-deck-3d-length-at-most-maxBridgeLengthMeters;all-water-interior;no-shortcut-or-split-v2","cityAccess":"surface-street-node-short-connect-v1;radius-over-block-times-1.5-ceil-4to12;candidate-distance-0.75to3-block;target-1.7-block;stable-city-seed-node-pair-sort;surface-only-existing-nodes;actual-sampler-direct-or-bounded-astar;complete-portal-no-acute-boundary-containment;access-unreachable-domain-v1","cityBoundary":"radial-budget-v2;world-seed-city-seed-city-id-fnv1a-xor;24to128-rays;step-at-most10m;phase-smooth-three-harmonics-radius-0.85to1.15;actual-sampler-surface-or-complete-classify-edge-portal;surface-abs-grade-integral-ds-times-1-plus-0.6-normalized-grade-over-one-minus-normalized;normalized-grade-at-least-0.995-reject;bounded-ccw-nonclosed-3to128|unbuildable-empty;city-road-continuous-points-inside-boundary;intercity-unclipped","cityField":"seeded-radial-terrain-contour-guide-v1","direction":"heading-dot-at-least-zero;interior-angle-at-least90deg;all-assembled-route-final-gate-v2","elevation":"relative-height-times-elevationScaleMeters-v1","limits":"maxGrade-roadWidthMeters-maxBridgeLengthMeters-maxTunnelLengthMeters-v1","parcelFace":"road-parcels-v4-boundary-highway;cityId-exact-arterial-local-street-surface-only;quantized-strict-xy-intersection-split;dcel-left-turn;positive-inside-concave-boundary;oriented-length-weighted-second-moment-principal-axis;canonical-longest-edge-isotropic-fallback;projection-midline-clip;area22500-recursion-depth8;inset-half-widest-road-plus-setback;triangulated-max-square-safe-bounds-v4","parcelFrontage":"face-plus-uncovered-frontage-v1;cityId-exact-arterial-local-street-surface-elementary-run;polyline-slice-16to42m;two-sided-offset;depth-clamp-blockSize-times-0.18-10to20;source-and-road-corridor-clear;face-first-frontage-positive-area-nonoverlap-stable-id-greedy-v1","parcelGraph":"road-parcels-v4-boundary-highway","parcelIdentity":"boundary-token-sorted-hash-worldSeed-citySeed-cityId;oriented-split-normal-token;frontage-side-and-ordinal;weighted-land-use;id-sort-v3","parcelTerrain":"polygon-touching-cells-dry-finite;adjacent-touching-cell-slope-at-most-settings-maxSlope;grid-at-most257-v1","portalLookahead":"complete-portal-before-partial-surface-v1;exact-dry-grid-portal-preserved-v1;off-grid-origin-requires-validated-surface-connector-v1;connector-budget-cost-required-v1","repair":"same-core-segment-splice;10m-grid;at-most8-attempts;min-margin200m-v1","roadClass":"required-kind-intercity-arterial-local;required-class-street-motorway;intercity-motorway-else-street;all-intercity-motorway-and-city-nonbuildable-roads-forbidden-corridor;centerline-no-intersection-or-containment;clearance-half-width-plus-setback-v1","shortcut":"surface-run-control-edge-boundaries;candidates-i-to-j-j-at-most-i-plus24-or-last;actual-sampler-water-grade-step-at-most10m;reverse-sparse-dag-heading-compatible-dot-epsilon1e-12;no-accumulated-cost;farthest-reachable-stable-descending;retain-original-if-no-complete-chain;whole-road-stable-branch-unreachable-if-final-acute;bridge-tunnel-excluded-v3","terrain":"canonical-plane-hydrology-fixed-sampling;global-long-axis257;city-long-axis65to257;actual-finite-sampler-step10m;no-sub10m-obstacle-guarantee-v1","tunnel":"covered-dry-portal-bore;actual-cover-at-least1m;3d-length-at-most-maxTunnelLengthMeters;no-shortcut-or-split;no-water-interior;roadWidth-surface-abutment-v2","version":6}';
|
|
7
|
+
/**
|
|
8
|
+
* 该字符串绑定 profile、算法版本与上方 canonical 规则字节的 SHA-256。
|
|
9
|
+
* 道路语义变化时必须发布新 identity,禁止让旧世界静默重算为不同结果。
|
|
10
|
+
*/
|
|
11
|
+
export const LEGACY_V6_IDENTITY = "chat.worldengine.terrain-roads/v1;rules-sha256=9dc32e6e3f5e63fbf378d6b83dfa67c2e9d62dabfebe5b432956f7ff0fdb8443";
|
|
12
|
+
export const STRUCTURED_V8_ROAD_GENERATION_RULES_CANONICAL = '{"version":8,"algorithm":"direction-state-a-star-v2;32-coprime-neighbors;stable-heap-tie-order;route-edge-cache-v1","cityField":"structured-two-family-seeded-skeleton-v8;candidate-only-actual-terrain-router-required","cityCandidateBudget":"boundary-polygon-shoelace-abs-area-m2;ceil-sqrt-area-over-block-times-0.42-per-family;clamp6to15;not-nominal-radius-area;not-feasible-terrain-mask","cityCandidateFamilies":"six-arterial-rays-from-center-four-collinear-points;arterial-base-seeded-angle-plus-sixth-turn-plus-seeded-jitter;two-transverse-families-base-and-base-plus-pi-over-two-plus-seeded-0.18rad;collector-and-local;four-point-parabolic-sweeps;seeded-bend-block-times-0.32;actual-terrain-and-boundary-gates","cityBoundary":"radial-budget-v2;world-seed-city-seed-city-id-fnv1a-xor;24to128-rays;step-at-most10m;phase-smooth-three-harmonics-radius-0.85to1.15;actual-sampler-surface-or-complete-classify-edge-portal;surface-abs-grade-integral-ds-times-1-plus-0.6-normalized-grade-over-one-minus-normalized;normalized-grade-at-least-0.995-reject;bounded-ccw-nonclosed-3to128|unbuildable-empty;city-road-continuous-points-inside-boundary;intercity-unclipped","cityStructuredClip":"v8-nonarterial-all-exact-boundary-inside-runs;candidate-segment-boundary-intersections;boundary-inclusive;rooted-run-nearest-original-sweep-midpoint-else-inside-run-nearest-midpoint;arterial-uses-legacy-origin-first-exit-clip;final-route-sample-boundary-verify-at-most10m","cityStructuredRoot":"v8-transverse-same-city-surface-strict-internal-xy-intersection;parallel-collinear-endpoint-near-end-reject-epsilon1e-9;nearest-original-sweep-midpoint-then-stable-road-segment-point-pair-tie-order;root-z-actual-terrain-sample;both-old-surface-halves-revalidate-before-noding","cityRootArms":"root-left-right-independent-anchor-direct-or-bounded-astar-finish-and-boundary-check;both-concatenate|one-commit-valid-arm-and-record-other-unreachable|none-reject;assembled-root-route-no-acute-final-gate;preserve-root-xy","citySurfaceNoding":"v8-same-city-nonintercity-surface-only;strict-internal-xy-crossing-with-nonfinite-reject-and-dimensionless-interior-epsilon1e-9|positive-collinear-xy-overlap-line-distance-at-most1e-7;canonical-overlap-endpoints-xy-round-1e-7m-and-z-actual-terrain-sample;all-four-half-edges-or-local-endpoint-replacement-surface-revalidate;stable-road-segment-original-edge-index-descending-and-t-descending-insert-order;not-cross-city-bridge-tunnel-or-general-grade-separation","cityExactSurfaceReuse":"after-same-city-surface-noding-and-common-overlap-endpoint-rewrite;same-city-nonintercity-contiguous-all-surface-normalized-run-only-without-crossing-bridge-or-tunnel;undirected-exact-xyz-bit-elementary-key;arterial-then-local-then-lexical-road-id-owner;partial-overlap-reuse-removes-nonowner-exact-pieces-retains-continuous-chains;single-retained-chain-keeps-parent-id|multiple-retained-chains-child-id-parent-plus-retained-child-first-last-xy-IEEE-bits-and-reuse-purpose-no-ordinal;refresh-sorted-layout-ids;no-reuse-status-output;not-near-or-unvalidated-overlap-reuse","cityAccess":"legacy-v6-only:surface-street-node-short-connect-v1;radius-over-block-times-1.5-ceil-4to12;candidate-distance-0.75to3-block;target-1.7-block;stable-city-seed-node-pair-sort;surface-only-existing-nodes;actual-sampler-direct-or-bounded-astar;complete-portal-no-acute-boundary-containment;access-unreachable-domain-v1","parcelFace":"structured-v8-convex-closed-face-with-every-simplified-edge-matched-to-real-ordinary-street-elementary-frontage-only;per-edge-inward-half-plane-offset-before-split;clearance-max-matching-edge-half-width-plus-nonnegative-setback;mixed-width-collinear-match-uses-max-conservative-clearance;nonconvex-unmatched-or-invalid-offset-closed-face-reject-no-topology-guess","parcelSplit":"structured-v8-projection-midline-clip-area-settings-maxFaceAreaMeters-default22500-recursion-depth8;each-child-needs-longest-continuous-collinear-overlap-with-inset-real-street-frontage-at-least-max-10-min-20-widest-road;not-fragment-sum;on-failure-retain-parent-unsplit;cut-edge-is-not-frontage","parcelFrontage":"face-plus-uncovered-frontage-v1;open-area-only-real-surface-ordinary-street-segment-run;polyline-slice-min16-max42-except-unsplittable-first-edge-may-exceed42;two-sided-offset;inner-road-corridor-clearance-plus-0.01;source-run-clearance-recheck-at-least-clearance-minus0.2;depth-clamp-blockSize-times-0.18-10to20;source-and-road-corridor-clear;face-first-frontage-positive-area-nonoverlap-stable-id-greedy-v1;no-entrance-or-navigation-reachability-claim","legacyParcel":"legacy-v6-undefined-or-explicit-legacy-uses-prior-split-and-centroid-inset-path;v8-only-convex-offset-and-child-frontage-rule;shared-invalid-algorithm-fail-closed","parcelGraph":"road-parcels-v4-boundary-highway","parcelIdentity":"boundary-token-sorted-hash-worldSeed-citySeed-cityId;oriented-split-normal-token;frontage-side-and-ordinal;weighted-land-use;id-sort-v3","parcelTerrain":"polygon-touching-cells-dry-finite;adjacent-touching-cell-slope-at-most-settings-maxSlope;grid-at-most257-v1","roadClass":"required-kind-intercity-arterial-local;required-class-street-motorway;intercity-motorway-else-street;all-intercity-motorway-and-city-nonbuildable-roads-forbidden-corridor;centerline-no-intersection-or-containment;clearance-half-width-plus-setback-v1","direction":"heading-dot-at-least-zero;interior-angle-at-least90deg;all-assembled-route-final-gate-v2","limits":"maxGrade-roadWidthMeters-maxBridgeLengthMeters-maxTunnelLengthMeters-v1","elevation":"relative-height-times-elevationScaleMeters-v1","terrain":"canonical-plane-hydrology-fixed-sampling;global-long-axis257;city-long-axis65to257;actual-finite-sampler-step10m;no-sub10m-obstacle-guarantee-v1","portalLookahead":"complete-portal-before-partial-surface-v1;exact-dry-grid-portal-preserved-v1;off-grid-origin-requires-validated-surface-connector-v1;connector-budget-cost-required-v1","bridge":"complete-dry-portal-water-span-v1;linear-deck-3d-length-at-most-maxBridgeLengthMeters;all-water-interior;no-shortcut-or-split-v2","tunnel":"covered-dry-portal-bore;actual-cover-at-least1m;3d-length-at-most-maxTunnelLengthMeters;no-shortcut-or-split;no-water-interior;roadWidth-surface-abutment-v2","shortcut":"surface-run-control-edge-boundaries;candidates-i-to-j-j-at-most-i-plus24-or-last;actual-sampler-water-grade-step-at-most10m;reverse-sparse-dag-heading-compatible-dot-epsilon1e-12;no-accumulated-cost;farthest-reachable-stable-descending;retain-original-if-no-complete-chain;whole-road-stable-branch-unreachable-if-final-acute;bridge-tunnel-excluded-v3","repair":"same-core-segment-splice;10m-grid;at-most8-attempts;min-margin200m-v1"}';
|
|
13
|
+
export const STRUCTURED_V8_IDENTITY = "chat.worldengine.terrain-roads/v1;rules-sha256=03ab0544e8fdfc741045dccac18a946eb95b76bc10e9880012cba50060b29b4f";
|
|
14
|
+
export const FRONTAGE_BALANCED_V9_ROAD_GENERATION_RULES_CANONICAL = '{"version":9,"algorithm":"direction-state-a-star-v2;32-coprime-neighbors;stable-heap-tie-order;route-edge-cache-v1","cityField":"structured-two-family-seeded-skeleton-v8;candidate-only-actual-terrain-router-required","cityCandidateBudget":"boundary-polygon-shoelace-abs-area-m2;ceil-sqrt-area-over-block-times-0.42-per-family;clamp6to15;not-nominal-radius-area;not-feasible-terrain-mask","cityCandidateFamilies":"six-arterial-rays-from-center-four-collinear-points;arterial-base-seeded-angle-plus-sixth-turn-plus-seeded-jitter;two-transverse-families-base-and-base-plus-pi-over-two-plus-seeded-0.18rad;collector-and-local;four-point-parabolic-sweeps;seeded-bend-block-times-0.32;actual-terrain-and-boundary-gates","cityBoundary":"radial-budget-v2;world-seed-city-seed-city-id-fnv1a-xor;24to128-rays;step-at-most10m;phase-smooth-three-harmonics-radius-0.85to1.15;actual-sampler-surface-or-complete-classify-edge-portal;surface-abs-grade-integral-ds-times-1-plus-0.6-normalized-grade-over-one-minus-normalized;normalized-grade-at-least-0.995-reject;bounded-ccw-nonclosed-3to128|unbuildable-empty;city-road-continuous-points-inside-boundary;intercity-unclipped","cityStructuredClip":"v8-nonarterial-all-exact-boundary-inside-runs;candidate-segment-boundary-intersections;boundary-inclusive;rooted-run-nearest-original-sweep-midpoint-else-inside-run-nearest-midpoint;arterial-uses-legacy-origin-first-exit-clip;final-route-sample-boundary-verify-at-most10m","cityStructuredRoot":"v8-transverse-same-city-surface-strict-internal-xy-intersection;parallel-collinear-endpoint-near-end-reject-epsilon1e-9;nearest-original-sweep-midpoint-then-stable-road-segment-point-pair-tie-order;root-z-actual-terrain-sample;both-old-surface-halves-revalidate-before-noding","cityRootArms":"root-left-right-independent-anchor-direct-or-bounded-astar-finish-and-boundary-check;both-concatenate|one-commit-valid-arm-and-record-other-unreachable|none-reject;assembled-root-route-no-acute-final-gate;preserve-root-xy","citySurfaceNoding":"v8-same-city-nonintercity-surface-only;strict-internal-xy-crossing-with-nonfinite-reject-and-dimensionless-interior-epsilon1e-9|positive-collinear-xy-overlap-line-distance-at-most1e-7;canonical-overlap-endpoints-xy-round-1e-7m-and-z-actual-terrain-sample;all-four-half-edges-or-local-endpoint-replacement-surface-revalidate;stable-road-segment-original-edge-index-descending-and-t-descending-insert-order;not-cross-city-bridge-tunnel-or-general-grade-separation","cityExactSurfaceReuse":"after-same-city-surface-noding-and-common-overlap-endpoint-rewrite;same-city-nonintercity-contiguous-all-surface-normalized-run-only-without-crossing-bridge-or-tunnel;undirected-exact-xyz-bit-elementary-key;arterial-then-local-then-lexical-road-id-owner;partial-overlap-reuse-removes-nonowner-exact-pieces-retains-continuous-chains;single-retained-chain-keeps-parent-id|multiple-retained-chains-child-id-parent-plus-retained-child-first-last-xy-IEEE-bits-and-reuse-purpose-no-ordinal;refresh-sorted-layout-ids;no-reuse-status-output;not-near-or-unvalidated-overlap-reuse","cityAccess":"legacy-v6-only:surface-street-node-short-connect-v1;radius-over-block-times-1.5-ceil-4to12;candidate-distance-0.75to3-block;target-1.7-block;stable-city-seed-node-pair-sort;surface-only-existing-nodes;actual-sampler-direct-or-bounded-astar;complete-portal-no-acute-boundary-containment;access-unreachable-domain-v1","parcelAlgorithm":"identity-bound-v1;v6=legacy-v6-road-core-and-parcels;v8=structured-v8-road-core-and-parcels;v9=structured-v8-road-core-plus-frontage-balanced-v9-parcels;v9-vs-v8-selects-parcel-only;no-load-time-upgrade","parcelFace":"structured-v8-convex-closed-face-with-every-simplified-edge-matched-to-real-ordinary-street-elementary-frontage-only;per-edge-inward-half-plane-offset-before-split;clearance-max-matching-edge-half-width-plus-nonnegative-setback;mixed-width-collinear-match-uses-max-conservative-clearance;nonconvex-unmatched-or-invalid-offset-closed-face-reject-no-topology-guess","parcelSplit":"frontage-balanced-v9-primary-structured-v8-projection-midline-clip-area-settings-maxFaceAreaMeters-default22500;each-child-needs-longest-continuous-collinear-overlap-with-inset-real-street-frontage-at-least-max-10-min-20-widest-road;not-fragment-sum;when-primary-area-frontage-invalid-try-real-inset-frontage-overlap-cuts-length-desc-edge-id-normal-key;canonical-positive-frontage-tangent-normal;cut-overlap-midpoint-projection;dedup-normal-and-cut;first-area-frontage-valid-candidate;recursive-split-depth8;emitted-children-pass-existing-final-terrain-corridor-nonoverlap-gates;no-retry-after-final-rejection;on-all-area-frontage-failure-retain-parent-unsplit;cut-edge-is-not-frontage","parcelFrontage":"face-plus-uncovered-frontage-v1;open-area-only-real-surface-ordinary-street-segment-run;polyline-slice-min16-max42-except-unsplittable-first-edge-may-exceed42;two-sided-offset;inner-road-corridor-clearance-plus-0.01;source-run-clearance-recheck-at-least-clearance-minus0.2;depth-clamp-blockSize-times-0.18-10to20;source-and-road-corridor-clear;face-first-frontage-positive-area-nonoverlap-stable-id-greedy-v1;no-entrance-or-navigation-reachability-claim","legacyParcel":"legacy-v6-undefined-or-explicit-legacy-uses-prior-split-and-centroid-inset-path;structured-v8-uses-convex-offset-and-primary-child-frontage-rule;frontage-balanced-v9-keeps-v8-road-core-face-inset-and-primary-split-plus-stable-frontage-cut-fallback;shared-invalid-algorithm-fail-closed","parcelGraph":"road-parcels-v4-boundary-highway","parcelIdentity":"boundary-token-sorted-hash-worldSeed-citySeed-cityId;oriented-split-normal-token;frontage-side-and-ordinal;weighted-land-use;id-sort-v3","parcelTerrain":"polygon-touching-cells-dry-finite;adjacent-touching-cell-slope-at-most-settings-maxSlope;grid-at-most257-v1","roadClass":"required-kind-intercity-arterial-local;required-class-street-motorway;intercity-motorway-else-street;all-intercity-motorway-and-city-nonbuildable-roads-forbidden-corridor;centerline-no-intersection-or-containment;clearance-half-width-plus-setback-v1","direction":"heading-dot-at-least-zero;interior-angle-at-least90deg;all-assembled-route-final-gate-v2","limits":"maxGrade-roadWidthMeters-maxBridgeLengthMeters-maxTunnelLengthMeters-v1","elevation":"relative-height-times-elevationScaleMeters-v1","terrain":"canonical-plane-hydrology-fixed-sampling;global-long-axis257;city-long-axis65to257;actual-finite-sampler-step10m;no-sub10m-obstacle-guarantee-v1","portalLookahead":"complete-portal-before-partial-surface-v1;exact-dry-grid-portal-preserved-v1;off-grid-origin-requires-validated-surface-connector-v1;connector-budget-cost-required-v1","bridge":"complete-dry-portal-water-span-v1;linear-deck-3d-length-at-most-maxBridgeLengthMeters;all-water-interior;no-shortcut-or-split-v2","tunnel":"covered-dry-portal-bore;actual-cover-at-least1m;3d-length-at-most-maxTunnelLengthMeters;no-shortcut-or-split;no-water-interior;roadWidth-surface-abutment-v2","shortcut":"surface-run-control-edge-boundaries;candidates-i-to-j-j-at-most-i-plus24-or-last;actual-sampler-water-grade-step-at-most10m;reverse-sparse-dag-heading-compatible-dot-epsilon1e-12;no-accumulated-cost;farthest-reachable-stable-descending;retain-original-if-no-complete-chain;whole-road-stable-branch-unreachable-if-final-acute;bridge-tunnel-excluded-v3","repair":"same-core-segment-splice;10m-grid;at-most8-attempts;min-margin200m-v1"}';
|
|
15
|
+
export const FRONTAGE_BALANCED_V9_IDENTITY = "chat.worldengine.terrain-roads/v1;rules-sha256=671b01df50d378c85208f910ba10e63ff5b03d3787619898c8de60453329b38b";
|
|
16
|
+
export const ROAD_GENERATION_RULES_CANONICAL = '{"version":10,"algorithm":"direction-state-a-star-v2;32-coprime-neighbors;stable-heap-tie-order;route-edge-cache-v1","cityField":"structured-two-family-seeded-skeleton-v8;candidate-only-actual-terrain-router-required","cityCandidateBudget":"boundary-polygon-shoelace-abs-area-m2;ceil-sqrt-area-over-block-times-0.42-per-family;clamp6to15;not-nominal-radius-area;not-feasible-terrain-mask","cityCandidateFamilies":"six-arterial-rays-from-center-four-collinear-points;arterial-base-seeded-angle-plus-sixth-turn-plus-seeded-jitter;two-transverse-families-base-and-base-plus-pi-over-two-plus-seeded-0.18rad;collector-and-local;four-point-parabolic-sweeps;seeded-bend-block-times-0.32;actual-terrain-and-boundary-gates","cityBoundary":"radial-budget-v2;world-seed-city-seed-city-id-fnv1a-xor;24to128-rays;step-at-most10m;phase-smooth-three-harmonics-radius-0.85to1.15;actual-sampler-surface-or-complete-classify-edge-portal;surface-abs-grade-integral-ds-times-1-plus-0.6-normalized-grade-over-one-minus-normalized;normalized-grade-at-least-0.995-reject;bounded-ccw-nonclosed-3to128|unbuildable-empty;city-road-continuous-points-inside-boundary;intercity-unclipped","cityStructuredClip":"v8-nonarterial-all-exact-boundary-inside-runs;candidate-segment-boundary-intersections;boundary-inclusive;rooted-run-nearest-original-sweep-midpoint-else-inside-run-nearest-midpoint;arterial-uses-legacy-origin-first-exit-clip;final-route-sample-boundary-verify-at-most10m","cityStructuredRoot":"v8-transverse-same-city-surface-strict-internal-xy-intersection;parallel-collinear-endpoint-near-end-reject-epsilon1e-9;nearest-original-sweep-midpoint-then-stable-road-segment-point-pair-tie-order;root-z-actual-terrain-sample;both-old-surface-halves-revalidate-before-noding","cityRootArms":"root-left-right-independent-anchor-direct-or-bounded-astar-finish-and-boundary-check;both-concatenate|one-commit-valid-arm-and-record-other-unreachable|none-reject;assembled-root-route-no-acute-final-gate;preserve-root-xy","citySurfaceNoding":"v8-same-city-nonintercity-surface-only;strict-internal-xy-crossing-with-nonfinite-reject-and-dimensionless-interior-epsilon1e-9|positive-collinear-xy-overlap-line-distance-at-most1e-7;canonical-overlap-endpoints-xy-round-1e-7m-and-z-actual-terrain-sample;all-four-half-edges-or-local-endpoint-replacement-surface-revalidate;stable-road-segment-original-edge-index-descending-and-t-descending-insert-order;not-cross-city-bridge-tunnel-or-general-grade-separation","cityExactSurfaceReuse":"after-same-city-surface-noding-and-common-overlap-endpoint-rewrite;same-city-nonintercity-contiguous-all-surface-normalized-run-only-without-crossing-bridge-or-tunnel;undirected-exact-xyz-bit-elementary-key;arterial-then-local-then-lexical-road-id-owner;partial-overlap-reuse-removes-nonowner-exact-pieces-retains-continuous-chains;single-retained-chain-keeps-parent-id|multiple-retained-chains-child-id-parent-plus-retained-child-first-last-xy-IEEE-bits-and-reuse-purpose-no-ordinal;refresh-sorted-layout-ids;no-reuse-status-output;not-near-or-unvalidated-overlap-reuse","cityAccess":"legacy-v6-only:surface-street-node-short-connect-v1;radius-over-block-times-1.5-ceil-4to12;candidate-distance-0.75to3-block;target-1.7-block;stable-city-seed-node-pair-sort;surface-only-existing-nodes;actual-sampler-direct-or-bounded-astar;complete-portal-no-acute-boundary-containment;access-unreachable-domain-v1","parcelAlgorithm":"identity-bound-v1;v6=legacy-v6-road-core-and-parcels;v8=structured-v8-road-core-and-parcels;v9=structured-v8-road-core-plus-frontage-balanced-v9-parcels;v10=structured-v8-road-core-plus-shape-filled-v10-parcels;v10-vs-v9-selects-parcel-only;no-load-time-upgrade","parcelFace":"shape-filled-v10-simple-single-ring-closed-face;real-ordinary-street-elementary-frontage-only;collinear-ring-simplify;per-edge-inward-offset-max-matching-real-road-half-width-plus-nonnegative-setback-plus-4epsilon-quantization-guard;inset-must-stay-within-source-and-strict-road-clearance;self-cross-reject;concave-ear-clip-then-stable-convex-merge;internal-chords-have-no-offset-and-no-frontage;unsupported-complex-or-hole-face-reject-no-topology-guess","parcelSplit":"shape-filled-v10-long-axis-oriented-bounds-primary-then-stable-face-axis;projection-midline-clip-targets-settings-maxFaceAreaMeters-default22500-not-terminal-hard-limit;shape-context-cut-children-inset-by-4epsilon-per-side-leaving-8epsilon-artificial-nonroad-gap-to-prevent-quantized-shared-edge-positive-overlap;no-distance-or-overlap-gate-relaxation;both-children-area-at-least-settings-minParcelAreaMeters-default36;each-child-needs-longest-continuous-real-inset-street-frontage-at-least-max-10-min-20-widest-road;shape-node-and-recursive-output-hard-gates;pair-atomic-no-single-child-commit;frontage-balanced-real-inset-frontage-cut-fallback;recursive-depth8;at-depth-or-no-valid-cut-shape-valid-parent-may-remain-even-over-max-area;at-depth-or-no-valid-cut-invalid-shape-reject;cut-edge-is-not-frontage","parcelFrontage":"shape-filled-v10-face-first-then-uncovered-frontage-only;closed-valid-inset-faces-fill-before-open-street;real-surface-ordinary-street-run-resampled-at-at-most-min-10-maxSliceLength;two-sided-offset;depth-at-least-blockSize-times-0.18-and-at-least-minShortSide-times-sqrt2-plus1;maxSliceLength-max-requiredStripDimension-plus10-only-when-requiredStripDimension-over42-and-min-42-maxAspectRatio-times-depth-so-minShortSide-override-remains-sliceable;slice-at-least-minShortSide-times-sqrt2-plus1;shape-safe-bounds-and-ratio-hard-gates;source-and-all-road-corridor-boundary-terrain-clear;positive-area-nonoverlap-stable-id-greedy;no-entrance-or-navigation-reachability-claim","legacyParcel":"legacy-v6-undefined-or-explicit-legacy-uses-prior-split-and-centroid-inset-path;structured-v8-uses-convex-offset-and-primary-child-frontage-rule;frontage-balanced-v9-keeps-v8-road-core-face-inset-and-primary-split-plus-stable-frontage-cut-fallback;shape-filled-v10-keeps-v8-road-core-and-uses-simple-ring-inset-concave-decomposition-shape-hard-gates-and-uncovered-frontage-only;shared-invalid-algorithm-fail-closed","parcelGraph":"road-parcels-v4-boundary-highway","parcelIdentity":"boundary-token-sorted-hash-worldSeed-citySeed-cityId;oriented-split-normal-token;frontage-side-and-ordinal;weighted-land-use;id-sort-v3;shape-filled-v10-canonical-ring-ear-clip-first-valid-vertex-order-then-first-pair-convex-merge-loop;decomposed-piece-traversal-order-is-explicit-root-c-pieceIndex-and-affects-stable-id","parcelTerrain":"polygon-touching-cells-dry-finite;adjacent-touching-cell-slope-at-most-settings-maxSlope;grid-at-most257-v1","roadClass":"required-kind-intercity-arterial-local;required-class-street-motorway;intercity-motorway-else-street;all-intercity-motorway-and-city-nonbuildable-roads-forbidden-corridor;centerline-no-intersection-or-containment;clearance-half-width-plus-setback-v1","direction":"heading-dot-at-least-zero;interior-angle-at-least90deg;all-assembled-route-final-gate-v2","limits":"maxGrade-roadWidthMeters-maxBridgeLengthMeters-maxTunnelLengthMeters-v1","elevation":"relative-height-times-elevationScaleMeters-v1","terrain":"canonical-plane-hydrology-fixed-sampling;global-long-axis257;city-long-axis65to257;actual-finite-sampler-step10m;no-sub10m-obstacle-guarantee-v1","portalLookahead":"complete-portal-before-partial-surface-v1;exact-dry-grid-portal-preserved-v1;off-grid-origin-requires-validated-surface-connector-v1;connector-budget-cost-required-v1","bridge":"complete-dry-portal-water-span-v1;linear-deck-3d-length-at-most-maxBridgeLengthMeters;all-water-interior;no-shortcut-or-split-v2","tunnel":"covered-dry-portal-bore;actual-cover-at-least1m;3d-length-at-most-maxTunnelLengthMeters;no-shortcut-or-split;no-water-interior;roadWidth-surface-abutment-v2","shortcut":"surface-run-control-edge-boundaries;candidates-i-to-j-j-at-most-i-plus24-or-last;actual-sampler-water-grade-step-at-most10m;reverse-sparse-dag-heading-compatible-dot-epsilon1e-12;no-accumulated-cost;farthest-reachable-stable-descending;retain-original-if-no-complete-chain;whole-road-stable-branch-unreachable-if-final-acute;bridge-tunnel-excluded-v3","repair":"same-core-segment-splice;10m-grid;at-most8-attempts;min-margin200m-v1","parcelShape":"coordinates-round-decimal6-epsilon1e-6;minimum-oriented-convex-hull-edge-aligned-bounds;settings-maxAspectRatio-finite-at-least1-default4;settings-minShortSideMeters-finite-at-least26-default26;short-side-at-least-minShortSideMeters;long-side-at-most-short-side-times-maxAspectRatio;safe-axis-aligned-square-inside-polygon-width-and-height-at-least-minShortSideMeters;all-partition-nodes-and-emitted-parcels-terrain-boundary-corridor-clear"}';
|
|
17
|
+
export const ROAD_GENERATION_IDENTITY = "chat.worldengine.terrain-roads/v1;rules-sha256=58fc51cfe9986a8da0cc6931c8600cf4c1afbbcda80f58e00038b76f982aec5b";
|
|
18
|
+
/**
|
|
19
|
+
* 已发布 identity 的精确 allowlist。新算法必须先有冻结的 rules digest,才能加入这里;
|
|
20
|
+
* 不得以“当前默认值”替换已保存 source 的 identity。
|
|
21
|
+
*/
|
|
22
|
+
export const SUPPORTED_ROAD_GENERATION_IDENTITIES = Object.freeze([
|
|
23
|
+
LEGACY_V6_IDENTITY,
|
|
24
|
+
STRUCTURED_V8_IDENTITY,
|
|
25
|
+
FRONTAGE_BALANCED_V9_IDENTITY,
|
|
26
|
+
ROAD_GENERATION_IDENTITY,
|
|
27
|
+
]);
|
|
28
|
+
export function roadGenerationAlgorithm(identity) {
|
|
29
|
+
switch (identity) {
|
|
30
|
+
case LEGACY_V6_IDENTITY:
|
|
31
|
+
return "legacy-v6";
|
|
32
|
+
case STRUCTURED_V8_IDENTITY:
|
|
33
|
+
case FRONTAGE_BALANCED_V9_IDENTITY:
|
|
34
|
+
case ROAD_GENERATION_IDENTITY:
|
|
35
|
+
return "structured-v8";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** 已保存 identity 独立选择 parcel 语义;V9 仍复用已发布 V8 道路 core。 */
|
|
39
|
+
export function roadParcelAlgorithm(identity) {
|
|
40
|
+
switch (identity) {
|
|
41
|
+
case LEGACY_V6_IDENTITY:
|
|
42
|
+
return "legacy-v6";
|
|
43
|
+
case STRUCTURED_V8_IDENTITY:
|
|
44
|
+
return "structured-v8";
|
|
45
|
+
case FRONTAGE_BALANCED_V9_IDENTITY:
|
|
46
|
+
return "frontage-balanced-v9";
|
|
47
|
+
case ROAD_GENERATION_IDENTITY:
|
|
48
|
+
return "shape-filled-v10";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 只有已冻结的后继 identity 才能令旧 source 出现显式升级预览入口。
|
|
53
|
+
* 加载或保存绝不能据此静默升级。
|
|
54
|
+
*/
|
|
55
|
+
export function roadGenerationUpgradeTarget(identity) {
|
|
56
|
+
return identity === LEGACY_V6_IDENTITY
|
|
57
|
+
|| identity === STRUCTURED_V8_IDENTITY
|
|
58
|
+
|| identity === FRONTAGE_BALANCED_V9_IDENTITY
|
|
59
|
+
? ROAD_GENERATION_IDENTITY
|
|
60
|
+
: undefined;
|
|
61
|
+
}
|
|
62
|
+
export const DEFAULT_ROAD_GENERATION = Object.freeze({
|
|
63
|
+
profile: ROAD_GENERATION_PROFILE,
|
|
64
|
+
identity: ROAD_GENERATION_IDENTITY,
|
|
65
|
+
allowBridges: true,
|
|
66
|
+
allowTunnels: false,
|
|
67
|
+
maxBridgeLengthMeters: 300,
|
|
68
|
+
maxTunnelLengthMeters: 1_000,
|
|
69
|
+
maxGrade: 0.15,
|
|
70
|
+
roadWidthMeters: 8,
|
|
71
|
+
elevationScaleMeters: 3_000,
|
|
72
|
+
});
|
|
73
|
+
const ROAD_GENERATION_KEYS = new Set([
|
|
74
|
+
"profile",
|
|
75
|
+
"identity",
|
|
76
|
+
"allowBridges",
|
|
77
|
+
"allowTunnels",
|
|
78
|
+
"maxBridgeLengthMeters",
|
|
79
|
+
"maxTunnelLengthMeters",
|
|
80
|
+
"maxGrade",
|
|
81
|
+
"roadWidthMeters",
|
|
82
|
+
"elevationScaleMeters",
|
|
83
|
+
]);
|
|
84
|
+
function isRecord(value) {
|
|
85
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
86
|
+
}
|
|
87
|
+
function boundedNumber(value, field, minimum, maximum) {
|
|
88
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
89
|
+
throw new TypeError(`${field} 必须是有限数`);
|
|
90
|
+
if (value < minimum || value > maximum)
|
|
91
|
+
throw new RangeError(`${field} 必须位于 ${minimum}..${maximum}`);
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
function boolean(value, field) {
|
|
95
|
+
if (typeof value !== "boolean")
|
|
96
|
+
throw new TypeError(`${field} 必须是 boolean`);
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
function supportedIdentity(value) {
|
|
100
|
+
if (typeof value !== "string" || !SUPPORTED_ROAD_GENERATION_IDENTITIES.includes(value)) {
|
|
101
|
+
throw new TypeError(`identity 必须为 ${SUPPORTED_ROAD_GENERATION_IDENTITIES.join(" 或 ")}`);
|
|
102
|
+
}
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
/** 严格验证桥隧道路设置,拒绝未知字段并返回不可变 canonical 副本。 */
|
|
106
|
+
export function validateRoadGeneration(input) {
|
|
107
|
+
if (!isRecord(input))
|
|
108
|
+
throw new TypeError("RoadGenerationV1 必须是对象");
|
|
109
|
+
const unknown = Object.keys(input).filter((key) => !ROAD_GENERATION_KEYS.has(key));
|
|
110
|
+
if (unknown.length > 0)
|
|
111
|
+
throw new TypeError(`RoadGenerationV1 包含未知字段:${unknown.join(", ")}`);
|
|
112
|
+
const missing = [...ROAD_GENERATION_KEYS].filter((key) => !Object.hasOwn(input, key));
|
|
113
|
+
if (missing.length > 0)
|
|
114
|
+
throw new TypeError(`RoadGenerationV1 缺少字段:${missing.join(", ")}`);
|
|
115
|
+
if (input.profile !== ROAD_GENERATION_PROFILE)
|
|
116
|
+
throw new TypeError(`profile 必须为 ${ROAD_GENERATION_PROFILE}`);
|
|
117
|
+
const identity = supportedIdentity(input.identity);
|
|
118
|
+
return Object.freeze({
|
|
119
|
+
profile: ROAD_GENERATION_PROFILE,
|
|
120
|
+
identity,
|
|
121
|
+
allowBridges: boolean(input.allowBridges, "allowBridges"),
|
|
122
|
+
allowTunnels: boolean(input.allowTunnels, "allowTunnels"),
|
|
123
|
+
maxBridgeLengthMeters: boundedNumber(input.maxBridgeLengthMeters, "maxBridgeLengthMeters", 1, 1_000_000),
|
|
124
|
+
maxTunnelLengthMeters: boundedNumber(input.maxTunnelLengthMeters, "maxTunnelLengthMeters", 1, 1_000_000),
|
|
125
|
+
maxGrade: boundedNumber(input.maxGrade, "maxGrade", 0.01, 1),
|
|
126
|
+
roadWidthMeters: boundedNumber(input.roadWidthMeters, "roadWidthMeters", 3, 40),
|
|
127
|
+
elevationScaleMeters: boundedNumber(input.elevationScaleMeters, "elevationScaleMeters", 1, 100_000),
|
|
128
|
+
});
|
|
129
|
+
}
|