@gemwright/ai 0.0.0 → 0.8.1
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/Dockerfile +12 -0
- package/README.md +82 -1
- package/catalogs/aggressive-bodies.json +1 -0
- package/catalogs/crafts/alchemy.json +22 -0
- package/catalogs/crafts/blacksmith.json +110 -0
- package/catalogs/crafts/bowcraft.json +13 -0
- package/catalogs/crafts/carpentry.json +105 -0
- package/catalogs/crafts/inscription.json +67 -0
- package/catalogs/crafts/tailoring.json +72 -0
- package/catalogs/crafts/tinkering.json +61 -0
- package/catalogs/dungeons/covetous.json +1 -0
- package/catalogs/dungeons/deceit.json +1 -0
- package/catalogs/dungeons/despise.json +1 -0
- package/catalogs/dungeons/destard.json +1 -0
- package/catalogs/dungeons/fire.json +1 -0
- package/catalogs/dungeons/hythloth.json +1 -0
- package/catalogs/dungeons/ice.json +1 -0
- package/catalogs/dungeons/khaldun.json +1 -0
- package/catalogs/dungeons/misc-dungeons.json +1 -0
- package/catalogs/dungeons/orc-cave.json +1 -0
- package/catalogs/dungeons/shame.json +1 -0
- package/catalogs/dungeons/terathan-keep.json +1 -0
- package/catalogs/dungeons/wrong.json +1 -0
- package/catalogs/house-lots.json +86 -0
- package/catalogs/hunting-grounds.json +10071 -0
- package/catalogs/magery-spells.json +697 -0
- package/catalogs/monsters.json +1 -0
- package/catalogs/prices.json +466 -0
- package/catalogs/tamables.json +864 -0
- package/catalogs/taming-grounds.json +2198 -0
- package/dist/index.js +55289 -0
- package/dist/life-journal-worker.js +469 -0
- package/dist/load-test.js +55098 -0
- package/dist/path-worker.js +1384 -0
- package/dist/persistence-worker.js +16503 -0
- package/dist/progress-report.js +182 -0
- package/dist/report.js +163 -0
- package/dist/serve.js +62078 -0
- package/locations/ankhs.json +91 -0
- package/locations/areas.json +760 -0
- package/locations/cobblers.json +148 -0
- package/locations/escorts.json +115 -0
- package/locations/forges.json +18 -0
- package/locations/locations.json +1501 -0
- package/locations/new-haven.json +522 -0
- package/locations/route-corridors.json +1 -0
- package/locations/services-guide.json +1329 -0
- package/locations/services.json +144 -0
- package/locations/teleporters.json +38 -0
- package/locations/vendors.json +1503 -0
- package/package.json +39 -3
- package/railway.json +6 -0
- package/start.sh +36 -0
- package/templates/archetypes-new-haven.json +38 -0
- package/templates/archetypes.json +274 -0
- package/templates/population.example.json +123 -0
- package/tuning.json +17 -0
- package/world/names.json +4055 -0
- package/world/world.json +743 -0
|
@@ -0,0 +1,1384 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
12
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
20
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
|
+
const to = isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
+
if (mod && typeof mod === "object" || typeof mod === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(mod))
|
|
24
|
+
if (!__hasOwnProp.call(to, key))
|
|
25
|
+
__defProp(to, key, {
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
27
|
+
enumerable: true
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (canCache)
|
|
31
|
+
cache.set(mod, to);
|
|
32
|
+
return to;
|
|
33
|
+
};
|
|
34
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
35
|
+
var __returnValue = (v) => v;
|
|
36
|
+
function __exportSetter(name, newValue) {
|
|
37
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
38
|
+
}
|
|
39
|
+
var __export = (target, all) => {
|
|
40
|
+
for (var name in all)
|
|
41
|
+
__defProp(target, name, {
|
|
42
|
+
get: all[name],
|
|
43
|
+
enumerable: true,
|
|
44
|
+
configurable: true,
|
|
45
|
+
set: __exportSetter.bind(all, name)
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
var __esm = (fn, res, err) => () => {
|
|
49
|
+
if (fn)
|
|
50
|
+
try {
|
|
51
|
+
res = fn(fn = 0);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
err = [e];
|
|
54
|
+
}
|
|
55
|
+
if (err)
|
|
56
|
+
throw err[0];
|
|
57
|
+
return res;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// src/links.ts
|
|
61
|
+
import { readFileSync } from "fs";
|
|
62
|
+
function linkToward(here, goal, tried, start) {
|
|
63
|
+
return links.filter((link) => !tried.has(link) && distance(here, link.from) <= 150 && distance(link.to, goal) < distance(here, goal) - 20 && !(start && distance(here, start) > 30 && distance(link.to, start) <= 30)).sort((a, b) => distance(here, a.from) + distance(a.to, goal) - (distance(here, b.from) + distance(b.to, goal)))[0];
|
|
64
|
+
}
|
|
65
|
+
var distance = (a, b) => Math.max(Math.abs(a.x - b.x), Math.abs(a.y - b.y)), links, teleporterTiles;
|
|
66
|
+
var init_links = __esm(() => {
|
|
67
|
+
links = JSON.parse(readFileSync(new URL("../locations/teleporters.json", import.meta.url), "utf8")).links;
|
|
68
|
+
teleporterTiles = links.map((link) => `${link.from.x},${link.from.y}`);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// src/direct-route.ts
|
|
72
|
+
function directRoute(terrain, start, goal, range, blocked, targetZ, limit = 32) {
|
|
73
|
+
const path = [];
|
|
74
|
+
let point = start;
|
|
75
|
+
for (let step = 0;step < limit; step++) {
|
|
76
|
+
const distance = Math.max(Math.abs(point.x - goal.x), Math.abs(point.y - goal.y));
|
|
77
|
+
if (distance <= range)
|
|
78
|
+
return targetZ === undefined || Math.abs(point.z - targetZ) <= 8 ? path : null;
|
|
79
|
+
const dx = Math.sign(goal.x - point.x);
|
|
80
|
+
const dy = Math.sign(goal.y - point.y);
|
|
81
|
+
const x = point.x + dx;
|
|
82
|
+
const y = point.y + dy;
|
|
83
|
+
if (blocked.has(`${x},${y}`) || teleporters.has(`${x},${y}`) && (x !== goal.x || y !== goal.y))
|
|
84
|
+
break;
|
|
85
|
+
const next = terrain.stand(x, y, point.z, point);
|
|
86
|
+
if (!next || dx && dy && (!terrain.stand(x, point.y, point.z, point) || !terrain.stand(point.x, y, point.z, point)))
|
|
87
|
+
break;
|
|
88
|
+
path.push(next);
|
|
89
|
+
point = next;
|
|
90
|
+
}
|
|
91
|
+
return path.length ? path : null;
|
|
92
|
+
}
|
|
93
|
+
var teleporters;
|
|
94
|
+
var init_direct_route = __esm(() => {
|
|
95
|
+
init_links();
|
|
96
|
+
teleporters = new Set(teleporterTiles);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// src/path-search.ts
|
|
100
|
+
class RouteSearch {
|
|
101
|
+
capacity;
|
|
102
|
+
budgetMs;
|
|
103
|
+
expansionBudget;
|
|
104
|
+
offRoad;
|
|
105
|
+
keys;
|
|
106
|
+
x;
|
|
107
|
+
y;
|
|
108
|
+
z;
|
|
109
|
+
costs;
|
|
110
|
+
ranks;
|
|
111
|
+
parents;
|
|
112
|
+
positions;
|
|
113
|
+
hash;
|
|
114
|
+
heap;
|
|
115
|
+
hashSize;
|
|
116
|
+
count = 0;
|
|
117
|
+
size = 0;
|
|
118
|
+
constructor(capacity = 65536, budgetMs = 500, expansionBudget, offRoad = 1.12) {
|
|
119
|
+
this.capacity = capacity;
|
|
120
|
+
this.budgetMs = budgetMs;
|
|
121
|
+
this.expansionBudget = expansionBudget;
|
|
122
|
+
this.offRoad = offRoad;
|
|
123
|
+
if (!Number.isInteger(capacity) || capacity < 1024 || capacity > 1048576 || capacity & capacity - 1)
|
|
124
|
+
throw new Error("Search capacity must be a power of two from 1024 to 1048576");
|
|
125
|
+
this.hashSize = capacity * 4;
|
|
126
|
+
this.keys = new Float64Array(capacity);
|
|
127
|
+
this.x = new Uint16Array(capacity);
|
|
128
|
+
this.y = new Uint16Array(capacity);
|
|
129
|
+
this.z = new Int16Array(capacity);
|
|
130
|
+
this.costs = new Float64Array(capacity);
|
|
131
|
+
this.ranks = new Float64Array(capacity);
|
|
132
|
+
this.parents = new Int32Array(capacity);
|
|
133
|
+
this.positions = new Int32Array(capacity);
|
|
134
|
+
this.hash = new Uint32Array(capacity * 4);
|
|
135
|
+
this.heap = new Uint32Array(capacity);
|
|
136
|
+
}
|
|
137
|
+
slot(key) {
|
|
138
|
+
let hash = (key | 0) ^ Math.floor(key / 4294967296);
|
|
139
|
+
hash = Math.imul(hash ^ hash >>> 16, 73244475);
|
|
140
|
+
hash = Math.imul(hash ^ hash >>> 16, 73244475);
|
|
141
|
+
let slot = (hash ^ hash >>> 16) >>> 0;
|
|
142
|
+
slot &= this.hashSize - 1;
|
|
143
|
+
while (this.hash[slot] && this.keys[(this.hash[slot] ?? 1) - 1] !== key)
|
|
144
|
+
slot = slot + 1 & this.hashSize - 1;
|
|
145
|
+
return slot;
|
|
146
|
+
}
|
|
147
|
+
point(index) {
|
|
148
|
+
return { x: this.x[index], y: this.y[index], z: this.z[index] };
|
|
149
|
+
}
|
|
150
|
+
push(index) {
|
|
151
|
+
let at = this.positions[index];
|
|
152
|
+
if (at < 0)
|
|
153
|
+
at = this.size++;
|
|
154
|
+
while (at > 0) {
|
|
155
|
+
const parent = at - 1 >> 1;
|
|
156
|
+
const other = this.heap[parent];
|
|
157
|
+
if (this.ranks[other] <= this.ranks[index])
|
|
158
|
+
break;
|
|
159
|
+
this.heap[at] = other;
|
|
160
|
+
this.positions[other] = at;
|
|
161
|
+
at = parent;
|
|
162
|
+
}
|
|
163
|
+
this.heap[at] = index;
|
|
164
|
+
this.positions[index] = at;
|
|
165
|
+
}
|
|
166
|
+
pop() {
|
|
167
|
+
const first = this.heap[0];
|
|
168
|
+
const last = this.heap[--this.size];
|
|
169
|
+
if (this.size) {
|
|
170
|
+
let at = 0;
|
|
171
|
+
while (at * 2 + 1 < this.size) {
|
|
172
|
+
let child = at * 2 + 1;
|
|
173
|
+
if (child + 1 < this.size && this.ranks[this.heap[child + 1]] < this.ranks[this.heap[child]])
|
|
174
|
+
child++;
|
|
175
|
+
const other = this.heap[child];
|
|
176
|
+
if (this.ranks[other] >= this.ranks[last])
|
|
177
|
+
break;
|
|
178
|
+
this.heap[at] = other;
|
|
179
|
+
this.positions[other] = at;
|
|
180
|
+
at = child;
|
|
181
|
+
}
|
|
182
|
+
this.heap[at] = last;
|
|
183
|
+
this.positions[last] = at;
|
|
184
|
+
}
|
|
185
|
+
this.positions[first] = -1;
|
|
186
|
+
return first;
|
|
187
|
+
}
|
|
188
|
+
run(terrain, request, cancelled = () => false) {
|
|
189
|
+
const began = performance.now();
|
|
190
|
+
this.hash.fill(0);
|
|
191
|
+
this.positions.fill(-1);
|
|
192
|
+
this.count = 1;
|
|
193
|
+
this.size = 0;
|
|
194
|
+
const { start, goal, range, targetZ } = request;
|
|
195
|
+
const MAX_STEP_Z = 8;
|
|
196
|
+
const blocked = new Set(request.blocked);
|
|
197
|
+
const penalties = new Set(request.penalties ?? []);
|
|
198
|
+
const estimate = (x, y) => {
|
|
199
|
+
const dx = Math.abs(x - goal.x), dy = Math.abs(y - goal.y);
|
|
200
|
+
return (Math.max(dx, dy) + 0.4 * Math.min(dx, dy)) * 1.1;
|
|
201
|
+
};
|
|
202
|
+
const distance = (x, y) => Math.max(Math.abs(x - goal.x), Math.abs(y - goal.y));
|
|
203
|
+
const add = (index, point, key, slot) => {
|
|
204
|
+
this.keys[index] = key;
|
|
205
|
+
this.x[index] = point.x;
|
|
206
|
+
this.y[index] = point.y;
|
|
207
|
+
this.z[index] = point.z;
|
|
208
|
+
this.hash[slot] = index + 1;
|
|
209
|
+
};
|
|
210
|
+
const key = coordinate(start.x, start.y, start.z);
|
|
211
|
+
add(0, start, key, this.slot(key));
|
|
212
|
+
this.parents[0] = -1;
|
|
213
|
+
this.costs[0] = 0;
|
|
214
|
+
this.ranks[0] = estimate(start.x, start.y);
|
|
215
|
+
this.push(0);
|
|
216
|
+
let expanded = 0;
|
|
217
|
+
let best = 0;
|
|
218
|
+
let found = -1;
|
|
219
|
+
let exhausted = false;
|
|
220
|
+
const expansionLimit = this.expansionBudget ?? (distance(start.x, start.y) <= 38 && !request.wide ? 8192 : 200000);
|
|
221
|
+
const current = { ...start };
|
|
222
|
+
const height = (x, y) => terrain.standingZ ? terrain.standingZ(x, y, current.z, current) : terrain.stand(x, y, current.z, current)?.z;
|
|
223
|
+
outer:
|
|
224
|
+
while (this.size && expanded < expansionLimit) {
|
|
225
|
+
if ((expanded & 63) === 0 && (cancelled() || performance.now() - began > this.budgetMs)) {
|
|
226
|
+
exhausted = true;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
const index = this.pop();
|
|
230
|
+
current.x = this.x[index];
|
|
231
|
+
current.y = this.y[index];
|
|
232
|
+
current.z = this.z[index];
|
|
233
|
+
expanded++;
|
|
234
|
+
const d = distance(current.x, current.y);
|
|
235
|
+
if (d < distance(this.x[best], this.y[best]))
|
|
236
|
+
best = index;
|
|
237
|
+
if (d <= range && (targetZ === undefined || Math.abs(current.z - targetZ) <= 8)) {
|
|
238
|
+
found = index;
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
for (const [dx, dy] of directions) {
|
|
242
|
+
const x = current.x + dx;
|
|
243
|
+
const y = current.y + dy;
|
|
244
|
+
if (x < 1 || y < 1 || x > 7165 || y > 4093 || blocked.has(`${x},${y}`))
|
|
245
|
+
continue;
|
|
246
|
+
const nextZ = height(x, y);
|
|
247
|
+
if (nextZ === undefined || Math.abs(nextZ - current.z) > MAX_STEP_Z || dx && dy && (height(x, current.y) === undefined || height(current.x, y) === undefined))
|
|
248
|
+
continue;
|
|
249
|
+
const key = coordinate(x, y, nextZ);
|
|
250
|
+
const slot = this.slot(key);
|
|
251
|
+
let other = this.hash[slot] - 1;
|
|
252
|
+
const road = isRoad(terrain, terrain.cell(x, y).land.id);
|
|
253
|
+
const parent = this.parents[index];
|
|
254
|
+
const turn = parent >= 0 && (Math.sign(current.x - this.x[parent]) !== dx || Math.sign(current.y - this.y[parent]) !== dy);
|
|
255
|
+
const trouble = penalties.size && penalties.has((x >> 3) * 1024 + (y >> 3)) ? request.penaltyCost ?? 4 : 0;
|
|
256
|
+
const vary = request.seed ? (((x >> 2) * 73856093 ^ (y >> 2) * 19349663 ^ request.seed) & 1023) / 3000 : 0;
|
|
257
|
+
const cost = this.costs[index] + (dx && dy ? 1.4 : 1) * (road ? 1 : this.offRoad) + (turn ? 0.08 : 0) + trouble + vary;
|
|
258
|
+
if (other >= 0 && this.costs[other] <= cost)
|
|
259
|
+
continue;
|
|
260
|
+
if (other < 0) {
|
|
261
|
+
if (this.count === this.capacity) {
|
|
262
|
+
exhausted = true;
|
|
263
|
+
break outer;
|
|
264
|
+
}
|
|
265
|
+
other = this.count++;
|
|
266
|
+
add(other, { x, y, z: nextZ }, key, slot);
|
|
267
|
+
}
|
|
268
|
+
this.costs[other] = cost;
|
|
269
|
+
this.ranks[other] = cost + estimate(x, y);
|
|
270
|
+
this.parents[other] = index;
|
|
271
|
+
this.push(other);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (expanded >= expansionLimit)
|
|
275
|
+
exhausted = true;
|
|
276
|
+
if (cancelled())
|
|
277
|
+
return { path: null, expanded, exhausted, elapsedMs: performance.now() - began };
|
|
278
|
+
if (found < 0 && request.partial && distance(this.x[best], this.y[best]) < distance(start.x, start.y) - 8)
|
|
279
|
+
found = best;
|
|
280
|
+
let path = null;
|
|
281
|
+
if (found >= 0) {
|
|
282
|
+
path = [];
|
|
283
|
+
for (let index = found, steps = 0;this.parents[index] >= 0 && steps < this.capacity; steps++, index = this.parents[index])
|
|
284
|
+
path.push(this.point(index));
|
|
285
|
+
path.reverse();
|
|
286
|
+
}
|
|
287
|
+
return { path, expanded, exhausted, elapsedMs: performance.now() - began };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
var directions, coordinate = (x, y, z) => (x * 4096 + y) * 512 + z + 256, roads, isRoad = (terrain, id) => {
|
|
291
|
+
let road = roads.get(id);
|
|
292
|
+
if (road === undefined) {
|
|
293
|
+
road = /cobble|flagstone|pavement|paver|road|dirt/i.test(terrain.tile(id).name);
|
|
294
|
+
roads.set(id, road);
|
|
295
|
+
}
|
|
296
|
+
return road;
|
|
297
|
+
}, routeSeed = (goal, now = Date.now()) => goal.x * 73856093 ^ goal.y * 19349663 ^ Math.imul(Math.floor(now / 600000), 83492791) | 1;
|
|
298
|
+
var init_path_search = __esm(() => {
|
|
299
|
+
directions = [
|
|
300
|
+
[0, -1],
|
|
301
|
+
[1, -1],
|
|
302
|
+
[1, 0],
|
|
303
|
+
[1, 1],
|
|
304
|
+
[0, 1],
|
|
305
|
+
[-1, 1],
|
|
306
|
+
[-1, 0],
|
|
307
|
+
[-1, -1]
|
|
308
|
+
];
|
|
309
|
+
roads = new Map;
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// ../common/src/assets/tiledata.ts
|
|
313
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
314
|
+
import { join } from "path";
|
|
315
|
+
|
|
316
|
+
class Tiledata {
|
|
317
|
+
bytes;
|
|
318
|
+
constructor(directory) {
|
|
319
|
+
this.bytes = readFileSync3(join(directory, "tiledata.mul"));
|
|
320
|
+
}
|
|
321
|
+
item(id) {
|
|
322
|
+
const at = ITEMS + (id >> 5) * (4 + 32 * 41) + 4 + (id & 31) * 41;
|
|
323
|
+
if (at + 41 > this.bytes.length)
|
|
324
|
+
return { name: "", weight: 0, layer: 0, animation: 0, height: 0, partialHue: false };
|
|
325
|
+
return {
|
|
326
|
+
name: this.bytes.subarray(at + 21, at + 41).toString("latin1").split("\x00")[0] ?? "",
|
|
327
|
+
weight: this.bytes.readUInt8(at + 8),
|
|
328
|
+
layer: this.bytes.readUInt8(at + 9),
|
|
329
|
+
animation: this.bytes.readUInt16LE(at + 14),
|
|
330
|
+
height: this.bytes.readUInt8(at + 20),
|
|
331
|
+
partialHue: !!(this.bytes.readUInt32LE(at) & PARTIAL_HUE)
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
var ITEMS, PARTIAL_HUE = 262144, TileFlag;
|
|
336
|
+
var init_tiledata = __esm(() => {
|
|
337
|
+
ITEMS = 512 * (4 + 32 * 30);
|
|
338
|
+
TileFlag = {
|
|
339
|
+
Background: 1,
|
|
340
|
+
Weapon: 2,
|
|
341
|
+
Transparent: 4,
|
|
342
|
+
Translucent: 8,
|
|
343
|
+
Wall: 16,
|
|
344
|
+
Damaging: 32,
|
|
345
|
+
Impassable: 64,
|
|
346
|
+
Wet: 128,
|
|
347
|
+
Surface: 512,
|
|
348
|
+
Bridge: 1024,
|
|
349
|
+
Generic: 2048,
|
|
350
|
+
Window: 4096,
|
|
351
|
+
NoShoot: 8192,
|
|
352
|
+
Foliage: 131072,
|
|
353
|
+
PartialHue: 262144,
|
|
354
|
+
Map: 1048576,
|
|
355
|
+
Container: 2097152,
|
|
356
|
+
Wearable: 4194304,
|
|
357
|
+
LightSource: 8388608,
|
|
358
|
+
Animation: 16777216,
|
|
359
|
+
Armor: 134217728,
|
|
360
|
+
Door: 536870912,
|
|
361
|
+
StairBack: 1073741824
|
|
362
|
+
};
|
|
363
|
+
});
|
|
364
|
+
// ../common/src/assets/uop.ts
|
|
365
|
+
function nameHash(name) {
|
|
366
|
+
const data = Buffer.from(name);
|
|
367
|
+
let a = data.length + 3735928559 >>> 0;
|
|
368
|
+
let b = a;
|
|
369
|
+
let c = a;
|
|
370
|
+
const rot = (v, n) => v << n | v >>> 32 - n;
|
|
371
|
+
let offset = 0;
|
|
372
|
+
while (data.length - offset > 12) {
|
|
373
|
+
a = a + data.readUInt32LE(offset) >>> 0;
|
|
374
|
+
b = b + data.readUInt32LE(offset + 4) >>> 0;
|
|
375
|
+
c = c + data.readUInt32LE(offset + 8) >>> 0;
|
|
376
|
+
a = (a - c ^ rot(c, 4)) >>> 0;
|
|
377
|
+
c = c + b >>> 0;
|
|
378
|
+
b = (b - a ^ rot(a, 6)) >>> 0;
|
|
379
|
+
a = a + c >>> 0;
|
|
380
|
+
c = (c - b ^ rot(b, 8)) >>> 0;
|
|
381
|
+
b = b + a >>> 0;
|
|
382
|
+
a = (a - c ^ rot(c, 16)) >>> 0;
|
|
383
|
+
c = c + b >>> 0;
|
|
384
|
+
b = (b - a ^ rot(a, 19)) >>> 0;
|
|
385
|
+
a = a + c >>> 0;
|
|
386
|
+
c = (c - b ^ rot(b, 4)) >>> 0;
|
|
387
|
+
b = b + a >>> 0;
|
|
388
|
+
offset += 12;
|
|
389
|
+
}
|
|
390
|
+
for (let i = 0;offset + i < data.length; i++) {
|
|
391
|
+
const value = (data[offset + i] ?? 0) << i % 4 * 8;
|
|
392
|
+
if (i < 4)
|
|
393
|
+
a = a + value >>> 0;
|
|
394
|
+
else if (i < 8)
|
|
395
|
+
b = b + value >>> 0;
|
|
396
|
+
else
|
|
397
|
+
c = c + value >>> 0;
|
|
398
|
+
}
|
|
399
|
+
c = (c ^ b) - rot(b, 14) >>> 0;
|
|
400
|
+
a = (a ^ c) - rot(c, 11) >>> 0;
|
|
401
|
+
b = (b ^ a) - rot(a, 25) >>> 0;
|
|
402
|
+
c = (c ^ b) - rot(b, 16) >>> 0;
|
|
403
|
+
a = (a ^ c) - rot(c, 4) >>> 0;
|
|
404
|
+
b = (b ^ a) - rot(a, 14) >>> 0;
|
|
405
|
+
c = (c ^ b) - rot(b, 24) >>> 0;
|
|
406
|
+
return BigInt(b) << 32n | BigInt(c);
|
|
407
|
+
}
|
|
408
|
+
var init_uop = () => {};
|
|
409
|
+
|
|
410
|
+
// ../network/src/diagnostics.ts
|
|
411
|
+
function onNetworkTiming(observer) {
|
|
412
|
+
observers.add(observer);
|
|
413
|
+
return () => {
|
|
414
|
+
observers.delete(observer);
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
function timed(label, run) {
|
|
418
|
+
if (!observers.size)
|
|
419
|
+
return run();
|
|
420
|
+
const start = performance.now();
|
|
421
|
+
try {
|
|
422
|
+
return run();
|
|
423
|
+
} finally {
|
|
424
|
+
const sample = { label, ms: performance.now() - start };
|
|
425
|
+
for (const observer of observers) {
|
|
426
|
+
try {
|
|
427
|
+
observer(sample);
|
|
428
|
+
} catch {}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
var observers;
|
|
433
|
+
var init_diagnostics = __esm(() => {
|
|
434
|
+
observers = new Set;
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// src/stall-probe.ts
|
|
438
|
+
function timed2(label, run) {
|
|
439
|
+
const start = performance.now();
|
|
440
|
+
try {
|
|
441
|
+
return run();
|
|
442
|
+
} finally {
|
|
443
|
+
const ms = performance.now() - start;
|
|
444
|
+
recordSlice({ label, ms });
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function recordSlice({ label, ms }) {
|
|
448
|
+
const slice = slices.get(label);
|
|
449
|
+
if (slice) {
|
|
450
|
+
slice.ms += ms;
|
|
451
|
+
slice.count++;
|
|
452
|
+
if (ms > slice.max)
|
|
453
|
+
slice.max = ms;
|
|
454
|
+
} else
|
|
455
|
+
slices.set(label, { ms, count: 1, max: ms });
|
|
456
|
+
}
|
|
457
|
+
function drainSlices() {
|
|
458
|
+
const out = Object.fromEntries([...slices].map(([label, s]) => [label, { ms: +s.ms.toFixed(2), count: s.count, max: +s.max.toFixed(2) }]));
|
|
459
|
+
slices.clear();
|
|
460
|
+
return out;
|
|
461
|
+
}
|
|
462
|
+
var slices;
|
|
463
|
+
var init_stall_probe = __esm(() => {
|
|
464
|
+
init_diagnostics();
|
|
465
|
+
slices = new Map;
|
|
466
|
+
onNetworkTiming(recordSlice);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// src/path-pool.ts
|
|
470
|
+
import { Worker } from "worker_threads";
|
|
471
|
+
var PathPool;
|
|
472
|
+
var init_path_pool = __esm(() => {
|
|
473
|
+
init_stall_probe();
|
|
474
|
+
PathPool = class PathPool {
|
|
475
|
+
static pools = new Map;
|
|
476
|
+
static get(directory, facet, buffers) {
|
|
477
|
+
const key = `${directory}:${facet}`;
|
|
478
|
+
let pool = PathPool.pools.get(key);
|
|
479
|
+
if (!pool) {
|
|
480
|
+
pool = new PathPool(directory, facet, 2, buffers);
|
|
481
|
+
PathPool.pools.set(key, pool);
|
|
482
|
+
}
|
|
483
|
+
return pool;
|
|
484
|
+
}
|
|
485
|
+
static closeAll() {
|
|
486
|
+
for (const pool of PathPool.pools.values())
|
|
487
|
+
pool.close();
|
|
488
|
+
PathPool.pools.clear();
|
|
489
|
+
}
|
|
490
|
+
static statistics() {
|
|
491
|
+
return [...PathPool.pools.values()].map((p) => ({
|
|
492
|
+
...p.stats,
|
|
493
|
+
queued: p.queue.length,
|
|
494
|
+
active: p.slots.filter((s) => s.job).length
|
|
495
|
+
}));
|
|
496
|
+
}
|
|
497
|
+
queue = [];
|
|
498
|
+
slots = [];
|
|
499
|
+
next = 0;
|
|
500
|
+
closed = false;
|
|
501
|
+
stats = {
|
|
502
|
+
submitted: 0,
|
|
503
|
+
completed: 0,
|
|
504
|
+
cancelled: 0,
|
|
505
|
+
exhausted: 0,
|
|
506
|
+
maxQueue: 0,
|
|
507
|
+
maxComputeMs: 0,
|
|
508
|
+
maxWaitMs: 0
|
|
509
|
+
};
|
|
510
|
+
constructor(directory, facet, size = 2, buffers) {
|
|
511
|
+
for (let index = 0;index < size; index++) {
|
|
512
|
+
const worker = new Worker(new URL(`./path-worker${import.meta.url.endsWith(".ts") ? ".ts" : ".js"}`, import.meta.url), {
|
|
513
|
+
workerData: { directory, facet, buffers }
|
|
514
|
+
});
|
|
515
|
+
const slot = { worker };
|
|
516
|
+
this.slots.push(slot);
|
|
517
|
+
worker.on("message", (message) => timed2("path-result", () => {
|
|
518
|
+
const job = slot.job;
|
|
519
|
+
if (!job || message.id !== job.id)
|
|
520
|
+
return;
|
|
521
|
+
slot.job = undefined;
|
|
522
|
+
job.cleanup();
|
|
523
|
+
this.stats.completed++;
|
|
524
|
+
if (message.result) {
|
|
525
|
+
this.stats.exhausted += Number(message.result.exhausted);
|
|
526
|
+
this.stats.maxComputeMs = Math.max(this.stats.maxComputeMs, message.result.elapsedMs);
|
|
527
|
+
job.resolve(message.result);
|
|
528
|
+
} else
|
|
529
|
+
job.reject(new Error(message.error ?? "Path worker failed"));
|
|
530
|
+
this.dispatch();
|
|
531
|
+
}));
|
|
532
|
+
worker.on("error", (error) => this.fail(error instanceof Error ? error : new Error(String(error))));
|
|
533
|
+
worker.on("exit", (code) => {
|
|
534
|
+
if (!this.closed)
|
|
535
|
+
this.fail(new Error(`Path worker exited (${code})`));
|
|
536
|
+
});
|
|
537
|
+
worker.unref();
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
run(request, signal) {
|
|
541
|
+
if (this.closed)
|
|
542
|
+
return Promise.reject(new Error("Pathfinding pool is closed"));
|
|
543
|
+
if (signal?.aborted)
|
|
544
|
+
return Promise.reject(new Error("Pathfinding cancelled"));
|
|
545
|
+
if (this.queue.length >= 512)
|
|
546
|
+
return Promise.reject(new Error("Pathfinding queue is full"));
|
|
547
|
+
return new Promise((resolve, reject) => {
|
|
548
|
+
const cancellation = new Int32Array(new SharedArrayBuffer(4));
|
|
549
|
+
const abort = () => {
|
|
550
|
+
Atomics.store(cancellation, 0, 1);
|
|
551
|
+
this.stats.cancelled++;
|
|
552
|
+
const index = this.queue.indexOf(job);
|
|
553
|
+
if (index >= 0)
|
|
554
|
+
this.queue.splice(index, 1);
|
|
555
|
+
job.cleanup();
|
|
556
|
+
reject(new Error("Pathfinding cancelled"));
|
|
557
|
+
};
|
|
558
|
+
const job = {
|
|
559
|
+
id: ++this.next,
|
|
560
|
+
request,
|
|
561
|
+
cancellation,
|
|
562
|
+
resolve,
|
|
563
|
+
reject,
|
|
564
|
+
started: performance.now(),
|
|
565
|
+
cleanup: () => signal?.removeEventListener("abort", abort)
|
|
566
|
+
};
|
|
567
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
568
|
+
this.queue.push(job);
|
|
569
|
+
this.stats.submitted++;
|
|
570
|
+
this.stats.maxQueue = Math.max(this.stats.maxQueue, this.queue.length);
|
|
571
|
+
this.dispatch();
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
dispatch() {
|
|
575
|
+
for (const slot of this.slots) {
|
|
576
|
+
if (slot.job)
|
|
577
|
+
continue;
|
|
578
|
+
const job = this.queue.shift();
|
|
579
|
+
if (!job) {
|
|
580
|
+
slot.worker.unref();
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
slot.job = job;
|
|
584
|
+
slot.worker.ref();
|
|
585
|
+
this.stats.maxWaitMs = Math.max(this.stats.maxWaitMs, performance.now() - job.started);
|
|
586
|
+
slot.worker.postMessage({ id: job.id, request: job.request, cancellation: job.cancellation.buffer });
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
fail(error) {
|
|
590
|
+
for (const job of [...this.queue, ...this.slots.flatMap((s) => s.job ? [s.job] : [])]) {
|
|
591
|
+
job.cleanup();
|
|
592
|
+
job.reject(error);
|
|
593
|
+
}
|
|
594
|
+
this.close();
|
|
595
|
+
}
|
|
596
|
+
close() {
|
|
597
|
+
this.closed = true;
|
|
598
|
+
for (const job of [...this.queue, ...this.slots.flatMap((s) => s.job ? [s.job] : [])]) {
|
|
599
|
+
Atomics.store(job.cancellation, 0, 1);
|
|
600
|
+
job.cleanup();
|
|
601
|
+
job.reject(new Error("Pathfinding pool closed"));
|
|
602
|
+
}
|
|
603
|
+
this.queue.length = 0;
|
|
604
|
+
for (const slot of this.slots) {
|
|
605
|
+
slot.job = undefined;
|
|
606
|
+
slot.worker.terminate();
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// src/lasting.ts
|
|
613
|
+
function lasting(name, scope) {
|
|
614
|
+
const entries = table(name);
|
|
615
|
+
const prefix = scope === undefined ? "" : `${scope}|`;
|
|
616
|
+
return {
|
|
617
|
+
get: (key) => entries.get(prefix + key),
|
|
618
|
+
active: (key) => (entries.get(prefix + key) ?? 0) > Date.now(),
|
|
619
|
+
set: (key, until) => {
|
|
620
|
+
if (entries.size > LIMIT)
|
|
621
|
+
prune(entries);
|
|
622
|
+
entries.set(prefix + key, until);
|
|
623
|
+
},
|
|
624
|
+
entries: () => entries.entries(),
|
|
625
|
+
count: () => {
|
|
626
|
+
let n = 0;
|
|
627
|
+
for (const [key, until] of entries)
|
|
628
|
+
if (key.startsWith(prefix) && until > Date.now())
|
|
629
|
+
n++;
|
|
630
|
+
return n;
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
function prune(entries) {
|
|
635
|
+
const now = Date.now();
|
|
636
|
+
for (const [key, until] of entries)
|
|
637
|
+
if (until <= now)
|
|
638
|
+
entries.delete(key);
|
|
639
|
+
}
|
|
640
|
+
function loadLasting(json) {
|
|
641
|
+
if (!json)
|
|
642
|
+
return;
|
|
643
|
+
try {
|
|
644
|
+
const now = Date.now();
|
|
645
|
+
for (const [name, rows] of Object.entries(JSON.parse(json)))
|
|
646
|
+
for (const [key, until] of rows)
|
|
647
|
+
if (until > now)
|
|
648
|
+
table(name).set(key, until);
|
|
649
|
+
} catch {}
|
|
650
|
+
}
|
|
651
|
+
function saveLasting() {
|
|
652
|
+
const out = {};
|
|
653
|
+
for (const [name, entries] of tables) {
|
|
654
|
+
prune(entries);
|
|
655
|
+
out[name] = [...entries];
|
|
656
|
+
}
|
|
657
|
+
return JSON.stringify(out);
|
|
658
|
+
}
|
|
659
|
+
var tables, LIMIT = 20000, table = (name) => {
|
|
660
|
+
const found = tables.get(name) ?? new Map;
|
|
661
|
+
tables.set(name, found);
|
|
662
|
+
return found;
|
|
663
|
+
};
|
|
664
|
+
var init_lasting = __esm(() => {
|
|
665
|
+
tables = new Map;
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
// src/telemetry-metrics.ts
|
|
669
|
+
class RuntimeMetrics {
|
|
670
|
+
counters = new Map;
|
|
671
|
+
histograms = new Map;
|
|
672
|
+
starts = new Map;
|
|
673
|
+
increment(name, amount = 1) {
|
|
674
|
+
const key = this.counters.has(name) || this.counters.size < 512 ? name : "other";
|
|
675
|
+
this.counters.set(key, (this.counters.get(key) ?? 0) + amount);
|
|
676
|
+
}
|
|
677
|
+
duration(name, ms) {
|
|
678
|
+
if (!Number.isFinite(ms) || ms < 0)
|
|
679
|
+
return;
|
|
680
|
+
const key = this.histograms.has(name) || this.histograms.size < 64 ? name : "other";
|
|
681
|
+
const histogram = this.histograms.get(key) ?? { count: 0, sum: 0, max: 0, buckets: bounds.map(() => 0) };
|
|
682
|
+
histogram.count++;
|
|
683
|
+
histogram.sum += ms;
|
|
684
|
+
histogram.max = Math.max(histogram.max, ms);
|
|
685
|
+
const index = bounds.findIndex((bound) => ms <= bound);
|
|
686
|
+
histogram.buckets[index] = (histogram.buckets[index] ?? 0) + 1;
|
|
687
|
+
this.histograms.set(key, histogram);
|
|
688
|
+
}
|
|
689
|
+
observe(event) {
|
|
690
|
+
this.increment(`${event.component}.${event.type}.${event.result}`);
|
|
691
|
+
if (event.type !== "action_lifecycle" || !event.actionId)
|
|
692
|
+
return;
|
|
693
|
+
if (["proposed", "authorized", "dispatched", "started"].includes(event.result)) {
|
|
694
|
+
if (!this.starts.has(event.actionId))
|
|
695
|
+
this.starts.set(event.actionId, Date.parse(event.at));
|
|
696
|
+
if (this.starts.size > 4096)
|
|
697
|
+
this.starts.delete(this.starts.keys().next().value ?? "");
|
|
698
|
+
} else if (["completed", "failed", "interrupted", "timed-out", "unconfirmed"].includes(event.result)) {
|
|
699
|
+
const start = this.starts.get(event.actionId);
|
|
700
|
+
if (start !== undefined)
|
|
701
|
+
this.duration("action_ms", Date.parse(event.at) - start);
|
|
702
|
+
this.starts.delete(event.actionId);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
snapshot() {
|
|
706
|
+
return {
|
|
707
|
+
counters: Object.fromEntries(this.counters),
|
|
708
|
+
histograms: Object.fromEntries([...this.histograms].map(([name, h]) => {
|
|
709
|
+
const percentile = (fraction) => {
|
|
710
|
+
let count = 0;
|
|
711
|
+
for (let i = 0;i < bounds.length; i++) {
|
|
712
|
+
count += h.buckets[i] ?? 0;
|
|
713
|
+
if (count >= h.count * fraction)
|
|
714
|
+
return bounds[i] === Infinity ? h.max : bounds[i];
|
|
715
|
+
}
|
|
716
|
+
return h.max;
|
|
717
|
+
};
|
|
718
|
+
return [
|
|
719
|
+
name,
|
|
720
|
+
{
|
|
721
|
+
count: h.count,
|
|
722
|
+
mean: h.sum / Math.max(1, h.count),
|
|
723
|
+
max: h.max,
|
|
724
|
+
p50: percentile(0.5),
|
|
725
|
+
p95: percentile(0.95),
|
|
726
|
+
p99: percentile(0.99)
|
|
727
|
+
}
|
|
728
|
+
];
|
|
729
|
+
}))
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
function markThreat(session, active, at) {
|
|
734
|
+
if (!active) {
|
|
735
|
+
threats.delete(session);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (!threats.has(session))
|
|
739
|
+
threats.set(session, { at, recorded: new Set });
|
|
740
|
+
}
|
|
741
|
+
function observeInterruption(session, event) {
|
|
742
|
+
const threat = threats.get(session);
|
|
743
|
+
const name = event.stage === "interrupted" ? "work_interrupt_ms" : event.kind === "escape" && event.stage === "authorized" && !event.command ? "safety_authorize_ms" : undefined;
|
|
744
|
+
if (!threat || !name || threat.recorded.has(name))
|
|
745
|
+
return;
|
|
746
|
+
runtimeMetrics.duration(name, event.at - threat.at);
|
|
747
|
+
threat.recorded.add(name);
|
|
748
|
+
}
|
|
749
|
+
var bounds, runtimeMetrics, threats;
|
|
750
|
+
var init_telemetry_metrics = __esm(() => {
|
|
751
|
+
bounds = [1, 5, 10, 25, 50, 100, 250, 500, 1000, 5000, 30000, Infinity];
|
|
752
|
+
runtimeMetrics = new RuntimeMetrics;
|
|
753
|
+
threats = new WeakMap;
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
// src/telemetry-schema.ts
|
|
757
|
+
function redact(value, depth = 0, seen = new WeakSet) {
|
|
758
|
+
if (depth > 12)
|
|
759
|
+
return "[depth-limit]";
|
|
760
|
+
if (value instanceof Error)
|
|
761
|
+
return { name: value.name, message: String(redact(value.message, depth + 1, seen)) };
|
|
762
|
+
if (typeof value === "string")
|
|
763
|
+
return value.replace(/\b(Bearer\s+)\S+/gi, "$1[redacted]").replace(/\b(password|api[_-]?key|secret|token)=([^\s&]+)/gi, "$1=[redacted]");
|
|
764
|
+
if (typeof value === "bigint")
|
|
765
|
+
return value.toString();
|
|
766
|
+
if (value === null || typeof value !== "object")
|
|
767
|
+
return value;
|
|
768
|
+
if (seen.has(value))
|
|
769
|
+
return "[circular]";
|
|
770
|
+
seen.add(value);
|
|
771
|
+
try {
|
|
772
|
+
if (Array.isArray(value))
|
|
773
|
+
return value.map((item) => redact(item, depth + 1, seen));
|
|
774
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
775
|
+
key,
|
|
776
|
+
sensitive.test(key) ? "[redacted]" : redact(item, depth + 1, seen)
|
|
777
|
+
]));
|
|
778
|
+
} finally {
|
|
779
|
+
seen.delete(value);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
var sensitive;
|
|
783
|
+
var init_telemetry_schema = __esm(() => {
|
|
784
|
+
sensitive = /^(?:password|passwd|secret|authorization|cookie|token|api[_-]?key|credentials|accountRef|username)$/i;
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
// src/telemetry.ts
|
|
788
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
789
|
+
function updateTrace(fields) {
|
|
790
|
+
const current = context.getStore();
|
|
791
|
+
if (current)
|
|
792
|
+
Object.assign(current, fields);
|
|
793
|
+
}
|
|
794
|
+
function registerActor(session, residentId, name) {
|
|
795
|
+
const actor = { residentId, name, sessionId: crypto.randomUUID() };
|
|
796
|
+
actors.set(session, actor);
|
|
797
|
+
return actor;
|
|
798
|
+
}
|
|
799
|
+
function actorFor(session) {
|
|
800
|
+
return {
|
|
801
|
+
...actors.get(session) ?? { residentId: null, name: null, sessionId: null },
|
|
802
|
+
position: session.world.position ? { map: session.world.map, ...session.world.position } : null
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
function configureTelemetry(options) {
|
|
806
|
+
const previous = { sink, output };
|
|
807
|
+
sink = options.sink ?? sink;
|
|
808
|
+
output = options.output ?? output;
|
|
809
|
+
return () => {
|
|
810
|
+
sink = previous.sink;
|
|
811
|
+
output = previous.output;
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
function makeEvent(component, type, result, details, fields = {}) {
|
|
815
|
+
const current = context.getStore();
|
|
816
|
+
const actor = current?.actor?.();
|
|
817
|
+
return {
|
|
818
|
+
schemaVersion: 2,
|
|
819
|
+
id: crypto.randomUUID(),
|
|
820
|
+
at: new Date().toISOString(),
|
|
821
|
+
runId,
|
|
822
|
+
deploymentId: process.env.RAILWAY_DEPLOYMENT_ID ?? null,
|
|
823
|
+
sequence: ++sequence,
|
|
824
|
+
component,
|
|
825
|
+
message: String(redact(typeof details.message === "string" ? details.message : `${component}: ${type} ${result}`)),
|
|
826
|
+
level: result === "failed" ? "error" : "info",
|
|
827
|
+
type,
|
|
828
|
+
result,
|
|
829
|
+
residentId: actor?.residentId ?? null,
|
|
830
|
+
name: actor?.name ?? null,
|
|
831
|
+
sessionId: actor?.sessionId ?? null,
|
|
832
|
+
traceId: current?.traceId ?? current?.intentId ?? current?.actionId ?? actor?.sessionId ?? runId,
|
|
833
|
+
intentId: current?.intentId,
|
|
834
|
+
decisionId: current?.decisionId,
|
|
835
|
+
actionId: current?.actionId,
|
|
836
|
+
parentId: current?.parentId,
|
|
837
|
+
position: actor?.position ?? null,
|
|
838
|
+
...Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined)),
|
|
839
|
+
details: redact(details)
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
function emitEvent(event, options = {}) {
|
|
843
|
+
runtimeMetrics.observe(event);
|
|
844
|
+
if (options.journal !== false)
|
|
845
|
+
sink?.(event);
|
|
846
|
+
if (options.stdout)
|
|
847
|
+
output?.(JSON.stringify(event), event.level);
|
|
848
|
+
}
|
|
849
|
+
function operational(component, level, message, details = {}) {
|
|
850
|
+
const event = makeEvent(component, "operational_log", level === "error" ? "failed" : "observed", { message, ...details }, { level });
|
|
851
|
+
emitEvent(event, { stdout: true });
|
|
852
|
+
return event;
|
|
853
|
+
}
|
|
854
|
+
function checkpointEvent(checkpoint, component, type, result, details, fields = {}) {
|
|
855
|
+
const session = checkpointSession(checkpoint);
|
|
856
|
+
emitEvent(makeEvent(component, type, result, details, { ...session ? actorFor(session) : {}, ...fields }));
|
|
857
|
+
}
|
|
858
|
+
var context, runId, sequence = 0, sink, output, actors, checkpoints, traceContext = () => context.getStore(), inTrace = (fields, run) => context.run({ ...context.getStore(), ...fields }, run), inSession = (session, run) => inTrace({ actor: () => actorFor(session) }, run), bindCheckpoint = (checkpoint, session) => checkpoints.set(checkpoint, session), checkpointSession = (checkpoint) => checkpoints.get(checkpoint);
|
|
859
|
+
var init_telemetry = __esm(() => {
|
|
860
|
+
init_telemetry_metrics();
|
|
861
|
+
init_telemetry_schema();
|
|
862
|
+
context = new AsyncLocalStorage;
|
|
863
|
+
runId = crypto.randomUUID();
|
|
864
|
+
actors = new WeakMap;
|
|
865
|
+
checkpoints = new WeakMap;
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
// src/log.ts
|
|
869
|
+
function logToStdout() {
|
|
870
|
+
if (structured)
|
|
871
|
+
return;
|
|
872
|
+
structured = true;
|
|
873
|
+
configureTelemetry({ output: (line, level) => level === "error" ? original.error(line) : original.log(line) });
|
|
874
|
+
for (const [method, level] of [
|
|
875
|
+
["log", "info"],
|
|
876
|
+
["warn", "warn"],
|
|
877
|
+
["error", "error"]
|
|
878
|
+
])
|
|
879
|
+
console[method] = (...values) => {
|
|
880
|
+
operational("runtime", level, values.length === 1 ? values[0] : values);
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
var structured = false, original, info = (line, component = "resident", details = {}) => structured ? operational(component, "info", line, details) : original.error(line), error = (component, message) => structured ? operational(component, "error", message) : original.error(message), logger = (component) => ({
|
|
884
|
+
info: (message, details = {}) => info(message, component, details),
|
|
885
|
+
error: (message) => error(component, message)
|
|
886
|
+
});
|
|
887
|
+
var init_log = __esm(() => {
|
|
888
|
+
init_telemetry();
|
|
889
|
+
original = {
|
|
890
|
+
log: console.log.bind(console),
|
|
891
|
+
error: console.error.bind(console),
|
|
892
|
+
warn: console.warn.bind(console)
|
|
893
|
+
};
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
// src/tuning.ts
|
|
897
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
898
|
+
var WALKING_LIMIT = 300, tuning;
|
|
899
|
+
var init_tuning = __esm(() => {
|
|
900
|
+
tuning = JSON.parse(readFileSync4(new URL("../tuning.json", import.meta.url), "utf8"));
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
// src/trouble.ts
|
|
904
|
+
function noteTrouble(at, kind) {
|
|
905
|
+
events.set(`${at.x >> CELL},${at.y >> CELL}|${kind}|${Date.now()}`, Date.now() + HOLD_MS);
|
|
906
|
+
cached.at = 0;
|
|
907
|
+
}
|
|
908
|
+
function tally() {
|
|
909
|
+
const cells = new Map;
|
|
910
|
+
for (const [key, until] of events.entries()) {
|
|
911
|
+
if (until <= Date.now())
|
|
912
|
+
continue;
|
|
913
|
+
const [cell = "", kind = ""] = key.split("|");
|
|
914
|
+
const entry = cells.get(cell) ?? { total: 0, kinds: new Map };
|
|
915
|
+
cells.set(cell, entry);
|
|
916
|
+
entry.total++;
|
|
917
|
+
entry.kinds.set(kind, (entry.kinds.get(kind) ?? 0) + 1);
|
|
918
|
+
}
|
|
919
|
+
return cells;
|
|
920
|
+
}
|
|
921
|
+
function troubleCells() {
|
|
922
|
+
if (Date.now() - cached.at < 60000)
|
|
923
|
+
return cached.cells;
|
|
924
|
+
const cells = [...tally()].filter(([, entry]) => entry.total >= PENALTY_AT).map(([cell]) => {
|
|
925
|
+
const [cx = 0, cy = 0] = cell.split(",").map(Number);
|
|
926
|
+
return cx * 1024 + cy;
|
|
927
|
+
});
|
|
928
|
+
cached = { at: Date.now(), cells };
|
|
929
|
+
return cells;
|
|
930
|
+
}
|
|
931
|
+
function reportTrouble() {
|
|
932
|
+
const worst = [...tally()].sort(([, a], [, b]) => b.total - a.total).slice(0, 8);
|
|
933
|
+
if (!worst.length)
|
|
934
|
+
return;
|
|
935
|
+
const place = (cell) => {
|
|
936
|
+
const [cx = 0, cy = 0] = cell.split(",").map(Number);
|
|
937
|
+
return `${(cx << CELL) + 4},${(cy << CELL) + 4}`;
|
|
938
|
+
};
|
|
939
|
+
logger("trouble").info(`[trouble] ${worst.map(([cell, e]) => `${place(cell)} \xD7${e.total} (${[...e.kinds].map(([k, n]) => `${k} ${n}`).join(", ")})`).join("; ")}`);
|
|
940
|
+
}
|
|
941
|
+
var CELL = 3, HOLD_MS, PENALTY_AT, events, cached;
|
|
942
|
+
var init_trouble = __esm(() => {
|
|
943
|
+
init_lasting();
|
|
944
|
+
init_log();
|
|
945
|
+
init_tuning();
|
|
946
|
+
HOLD_MS = tuning.trouble.holdHours * 3600000;
|
|
947
|
+
PENALTY_AT = tuning.trouble.penaltyAt;
|
|
948
|
+
events = lasting("trouble");
|
|
949
|
+
cached = { at: 0, cells: [] };
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
// src/terrain.ts
|
|
953
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
954
|
+
import { join as join2 } from "path";
|
|
955
|
+
import { inflateSync } from "zlib";
|
|
956
|
+
function directionTo(from, to) {
|
|
957
|
+
return directions2.findIndex(([x, y]) => x === Math.sign(to.x - from.x) && y === Math.sign(to.y - from.y));
|
|
958
|
+
}
|
|
959
|
+
var directions2, Terrain;
|
|
960
|
+
var init_terrain = __esm(() => {
|
|
961
|
+
init_tiledata();
|
|
962
|
+
init_uop();
|
|
963
|
+
init_links();
|
|
964
|
+
init_path_pool();
|
|
965
|
+
init_path_search();
|
|
966
|
+
init_trouble();
|
|
967
|
+
init_tuning();
|
|
968
|
+
directions2 = [
|
|
969
|
+
[0, -1],
|
|
970
|
+
[1, -1],
|
|
971
|
+
[1, 0],
|
|
972
|
+
[1, 1],
|
|
973
|
+
[0, 1],
|
|
974
|
+
[-1, 1],
|
|
975
|
+
[-1, 0],
|
|
976
|
+
[-1, -1]
|
|
977
|
+
];
|
|
978
|
+
Terrain = class Terrain {
|
|
979
|
+
directory;
|
|
980
|
+
facet;
|
|
981
|
+
tileCache = new Map;
|
|
982
|
+
localSearch;
|
|
983
|
+
shared;
|
|
984
|
+
cacheKeys = new Uint32Array(32768);
|
|
985
|
+
cacheCursor = 0;
|
|
986
|
+
map;
|
|
987
|
+
index;
|
|
988
|
+
statics;
|
|
989
|
+
tiles;
|
|
990
|
+
cache = new Map;
|
|
991
|
+
static instances = new Map;
|
|
992
|
+
static shared(directory, facet = 1, buffers) {
|
|
993
|
+
const key = `${directory}:${facet}`;
|
|
994
|
+
let terrain = Terrain.instances.get(key);
|
|
995
|
+
if (!terrain) {
|
|
996
|
+
terrain = new Terrain(directory, facet, buffers);
|
|
997
|
+
Terrain.instances.set(key, terrain);
|
|
998
|
+
}
|
|
999
|
+
return terrain;
|
|
1000
|
+
}
|
|
1001
|
+
constructor(directory, facet = 1, buffers) {
|
|
1002
|
+
this.directory = directory;
|
|
1003
|
+
this.facet = facet;
|
|
1004
|
+
if (buffers) {
|
|
1005
|
+
this.map = Buffer.from(buffers.map);
|
|
1006
|
+
this.index = Buffer.from(buffers.index);
|
|
1007
|
+
this.statics = Buffer.from(buffers.statics);
|
|
1008
|
+
this.tiles = Buffer.from(buffers.tiles);
|
|
1009
|
+
this.shared = buffers;
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
this.tiles = readFileSync5(join2(directory, "tiledata.mul"));
|
|
1013
|
+
if (this.tiles.length !== 3188736)
|
|
1014
|
+
throw new Error("Terrain requires the modern 64-bit tiledata format");
|
|
1015
|
+
this.index = readFileSync5(join2(directory, `staidx${facet}.mul`));
|
|
1016
|
+
this.statics = readFileSync5(join2(directory, `statics${facet}.mul`));
|
|
1017
|
+
const uop = readFileSync5(join2(directory, `map${facet}LegacyMUL.uop`));
|
|
1018
|
+
if (uop.readUInt32LE(0) !== 5265741)
|
|
1019
|
+
throw new Error("Invalid map UOP");
|
|
1020
|
+
const entries = new Map;
|
|
1021
|
+
const visited = new Set;
|
|
1022
|
+
for (let block = Number(uop.readBigUInt64LE(12));block !== 0; ) {
|
|
1023
|
+
if (visited.has(block))
|
|
1024
|
+
throw new Error("Cyclic map UOP");
|
|
1025
|
+
visited.add(block);
|
|
1026
|
+
const count = uop.readUInt32LE(block);
|
|
1027
|
+
const next = Number(uop.readBigUInt64LE(block + 4));
|
|
1028
|
+
for (let i = 0;i < count; i++) {
|
|
1029
|
+
const at = block + 12 + i * 34;
|
|
1030
|
+
const offset = Number(uop.readBigUInt64LE(at));
|
|
1031
|
+
if (!offset)
|
|
1032
|
+
continue;
|
|
1033
|
+
const start = offset + uop.readUInt32LE(at + 8);
|
|
1034
|
+
const length = uop.readUInt32LE(at + 12);
|
|
1035
|
+
const bytes = uop.subarray(start, start + length);
|
|
1036
|
+
const value = uop.readUInt16LE(at + 32) === 1 ? inflateSync(bytes) : bytes;
|
|
1037
|
+
if (value.length !== uop.readUInt32LE(at + 16))
|
|
1038
|
+
throw new Error("Truncated map chunk");
|
|
1039
|
+
entries.set(uop.readBigUInt64LE(at + 20), value);
|
|
1040
|
+
}
|
|
1041
|
+
block = next;
|
|
1042
|
+
}
|
|
1043
|
+
const chunks = [];
|
|
1044
|
+
const expected = 7168 * 4096 / 64 * 196;
|
|
1045
|
+
for (let i = 0;chunks.reduce((sum, chunk) => sum + chunk.length, 0) < expected; i++) {
|
|
1046
|
+
const chunk = entries.get(nameHash(`build/map${facet}legacymul/${String(i).padStart(8, "0")}.dat`));
|
|
1047
|
+
if (!chunk)
|
|
1048
|
+
throw new Error(`Missing map chunk ${i}`);
|
|
1049
|
+
chunks.push(chunk);
|
|
1050
|
+
}
|
|
1051
|
+
this.map = Buffer.concat(chunks);
|
|
1052
|
+
if (this.map.length !== expected)
|
|
1053
|
+
throw new Error("Unexpected map dimensions");
|
|
1054
|
+
}
|
|
1055
|
+
sharedBuffers() {
|
|
1056
|
+
if (!this.shared) {
|
|
1057
|
+
const copy = (bytes) => {
|
|
1058
|
+
const memory = new SharedArrayBuffer(bytes.length);
|
|
1059
|
+
new Uint8Array(memory).set(bytes);
|
|
1060
|
+
return memory;
|
|
1061
|
+
};
|
|
1062
|
+
this.shared = {
|
|
1063
|
+
map: copy(this.map),
|
|
1064
|
+
index: copy(this.index),
|
|
1065
|
+
statics: copy(this.statics),
|
|
1066
|
+
tiles: copy(this.tiles)
|
|
1067
|
+
};
|
|
1068
|
+
this.map = Buffer.from(this.shared.map);
|
|
1069
|
+
this.index = Buffer.from(this.shared.index);
|
|
1070
|
+
this.statics = Buffer.from(this.shared.statics);
|
|
1071
|
+
this.tiles = Buffer.from(this.shared.tiles);
|
|
1072
|
+
}
|
|
1073
|
+
return this.shared;
|
|
1074
|
+
}
|
|
1075
|
+
tile(id, item = false) {
|
|
1076
|
+
const key = id + (item ? 65536 : 0);
|
|
1077
|
+
const cached = this.tileCache.get(key);
|
|
1078
|
+
if (cached)
|
|
1079
|
+
return cached;
|
|
1080
|
+
const base = item ? 493568 : 0;
|
|
1081
|
+
const size = item ? 41 : 30;
|
|
1082
|
+
const offset = base + (id >> 5) * (4 + 32 * size) + 4 + (id & 31) * size;
|
|
1083
|
+
const value = {
|
|
1084
|
+
flags: this.tiles.readUInt32LE(offset),
|
|
1085
|
+
height: item ? this.tiles.readUInt8(offset + 20) : 0,
|
|
1086
|
+
layer: item ? this.tiles.readUInt8(offset + 9) : 0,
|
|
1087
|
+
name: this.tiles.subarray(offset + (item ? 21 : 10), offset + size).toString("latin1").split("\x00")[0] ?? ""
|
|
1088
|
+
};
|
|
1089
|
+
this.tileCache.set(key, value);
|
|
1090
|
+
return value;
|
|
1091
|
+
}
|
|
1092
|
+
findStatics(ids, origin, radius) {
|
|
1093
|
+
const matches = [];
|
|
1094
|
+
const minX = Math.max(0, origin.x - radius >> 3);
|
|
1095
|
+
const maxX = Math.min(895, origin.x + radius >> 3);
|
|
1096
|
+
const minY = Math.max(0, origin.y - radius >> 3);
|
|
1097
|
+
const maxY = Math.min(511, origin.y + radius >> 3);
|
|
1098
|
+
for (let bx = minX;bx <= maxX; bx++)
|
|
1099
|
+
for (let by = minY;by <= maxY; by++) {
|
|
1100
|
+
const block = bx * 512 + by;
|
|
1101
|
+
const start = this.index.readInt32LE(block * 12);
|
|
1102
|
+
const length = this.index.readInt32LE(block * 12 + 4);
|
|
1103
|
+
if (start < 0 || length <= 0)
|
|
1104
|
+
continue;
|
|
1105
|
+
for (let offset = start;offset < start + length; offset += 7) {
|
|
1106
|
+
const id = this.statics.readUInt16LE(offset);
|
|
1107
|
+
if (!ids.has(id))
|
|
1108
|
+
continue;
|
|
1109
|
+
const x = bx * 8 + this.statics.readUInt8(offset + 2);
|
|
1110
|
+
const y = by * 8 + this.statics.readUInt8(offset + 3);
|
|
1111
|
+
if (Math.max(Math.abs(x - origin.x), Math.abs(y - origin.y)) <= radius)
|
|
1112
|
+
matches.push({ id, x, y, z: this.statics.readInt8(offset + 4) });
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
return matches.sort((a, b) => Math.max(Math.abs(a.x - origin.x), Math.abs(a.y - origin.y)) - Math.max(Math.abs(b.x - origin.x), Math.abs(b.y - origin.y)));
|
|
1116
|
+
}
|
|
1117
|
+
sight(from, to) {
|
|
1118
|
+
const steps = Math.max(Math.abs(to.x - from.x), Math.abs(to.y - from.y));
|
|
1119
|
+
if (steps === 0)
|
|
1120
|
+
return true;
|
|
1121
|
+
const fromZ = from.z ?? 0;
|
|
1122
|
+
const toZ = to.z ?? fromZ;
|
|
1123
|
+
for (let step = 1;step < steps; step++) {
|
|
1124
|
+
const x = Math.round(from.x + (to.x - from.x) * step / steps);
|
|
1125
|
+
const y = Math.round(from.y + (to.y - from.y) * step / steps);
|
|
1126
|
+
const z = fromZ + (toZ - fromZ) * step / steps;
|
|
1127
|
+
const { land, statics } = this.cell(x, y);
|
|
1128
|
+
if (this.tile(land.id).flags & (TileFlag.Wall | TileFlag.Impassable))
|
|
1129
|
+
return false;
|
|
1130
|
+
for (const staticTile of statics) {
|
|
1131
|
+
if (staticTile.z > z + 16 || staticTile.z + 20 < z - 4)
|
|
1132
|
+
continue;
|
|
1133
|
+
if (this.tile(staticTile.id, true).flags & (TileFlag.Wall | TileFlag.Impassable | TileFlag.Window | TileFlag.NoShoot))
|
|
1134
|
+
return false;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
return true;
|
|
1138
|
+
}
|
|
1139
|
+
cell(x, y) {
|
|
1140
|
+
if (x < 0 || x >= 7167 || y < 0 || y >= 4095)
|
|
1141
|
+
throw new Error("Terrain coordinate outside supported facet");
|
|
1142
|
+
const key = x * 4096 + y;
|
|
1143
|
+
const cached = this.cache.get(key);
|
|
1144
|
+
if (cached)
|
|
1145
|
+
return cached;
|
|
1146
|
+
const block = (x >> 3) * 512 + (y >> 3);
|
|
1147
|
+
const at = block * 196 + 4 + ((y & 7) * 8 + (x & 7)) * 3;
|
|
1148
|
+
const land = { x, y, id: this.map.readUInt16LE(at) & 16383, z: this.map.readInt8(at + 2) };
|
|
1149
|
+
const statics = [];
|
|
1150
|
+
const offset = this.index.readInt32LE(block * 12);
|
|
1151
|
+
const length = this.index.readInt32LE(block * 12 + 4);
|
|
1152
|
+
if (offset >= 0 && length > 0)
|
|
1153
|
+
for (let i = offset;i < offset + length; i += 7) {
|
|
1154
|
+
if (this.statics[i + 2] === (x & 7) && this.statics[i + 3] === (y & 7))
|
|
1155
|
+
statics.push({ x, y, id: this.statics.readUInt16LE(i), z: this.statics.readInt8(i + 4) });
|
|
1156
|
+
}
|
|
1157
|
+
const value = { land, statics };
|
|
1158
|
+
const slot = this.cacheCursor++ & 32767;
|
|
1159
|
+
const evicted = this.cacheKeys[slot];
|
|
1160
|
+
if (this.cache.size >= 32768 && evicted !== undefined)
|
|
1161
|
+
this.cache.delete(evicted);
|
|
1162
|
+
this.cacheKeys[slot] = key;
|
|
1163
|
+
this.cache.set(key, value);
|
|
1164
|
+
return value;
|
|
1165
|
+
}
|
|
1166
|
+
stand(x, y, fromZ, from) {
|
|
1167
|
+
const z = this.standingZ(x, y, fromZ, from);
|
|
1168
|
+
return z === undefined ? null : { x, y, z };
|
|
1169
|
+
}
|
|
1170
|
+
standingZ(x, y, fromZ, from) {
|
|
1171
|
+
const cell = this.cell(x, y);
|
|
1172
|
+
const a = cell.land.z, b = this.cell(x + 1, y).land.z, c = this.cell(x, y + 1).land.z, d = this.cell(x + 1, y + 1).land.z;
|
|
1173
|
+
const landZ = Math.floor(Math.abs(a - d) > Math.abs(b - c) ? (b + c) / 2 : (a + d) / 2);
|
|
1174
|
+
let startTop = fromZ;
|
|
1175
|
+
if (from) {
|
|
1176
|
+
const a = this.cell(from.x, from.y).land.z, b = this.cell(from.x + 1, from.y).land.z;
|
|
1177
|
+
const c = this.cell(from.x, from.y + 1).land.z, d = this.cell(from.x + 1, from.y + 1).land.z;
|
|
1178
|
+
const average = Math.floor(Math.abs(a - d) > Math.abs(b - c) ? (b + c) / 2 : (a + d) / 2);
|
|
1179
|
+
if (fromZ >= average && !(this.tile(this.cell(from.x, from.y).land.id).flags & 192))
|
|
1180
|
+
startTop = Math.max(fromZ, a, b, c, d);
|
|
1181
|
+
for (const item of this.cell(from.x, from.y).statics) {
|
|
1182
|
+
const tile = this.tile(item.id, true);
|
|
1183
|
+
const top = item.z + (tile.flags & 1024 ? Math.floor(tile.height / 2) : tile.height);
|
|
1184
|
+
if (tile.flags & TileFlag.Surface && top <= fromZ)
|
|
1185
|
+
startTop = Math.max(startTop, item.z + tile.height);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
let best;
|
|
1189
|
+
let delta = Infinity;
|
|
1190
|
+
const landAllowed = !(this.tile(cell.land.id).flags & 192);
|
|
1191
|
+
for (let index = landAllowed ? -1 : 0;index < cell.statics.length; index++) {
|
|
1192
|
+
let z;
|
|
1193
|
+
let stepTop;
|
|
1194
|
+
if (index < 0) {
|
|
1195
|
+
z = landZ;
|
|
1196
|
+
stepTop = Math.min(a, b, c, d);
|
|
1197
|
+
} else {
|
|
1198
|
+
const item = cell.statics[index];
|
|
1199
|
+
if (!item)
|
|
1200
|
+
continue;
|
|
1201
|
+
const tile = this.tile(item.id, true);
|
|
1202
|
+
if ((tile.flags & 576) !== 512)
|
|
1203
|
+
continue;
|
|
1204
|
+
z = item.z + (tile.flags & 1024 ? Math.floor(tile.height / 2) : tile.height);
|
|
1205
|
+
stepTop = item.z + (tile.flags & 1024 ? 0 : tile.height);
|
|
1206
|
+
}
|
|
1207
|
+
if (stepTop > startTop + 2 || z < fromZ - 16 || Math.abs(z - fromZ) >= delta)
|
|
1208
|
+
continue;
|
|
1209
|
+
let clear = true;
|
|
1210
|
+
for (const item of cell.statics) {
|
|
1211
|
+
const tile = this.tile(item.id, true);
|
|
1212
|
+
const height = tile.flags & 1024 ? Math.floor(tile.height / 2) : tile.height;
|
|
1213
|
+
if (tile.flags & 576 && item.z < z + 16 && item.z + height > z) {
|
|
1214
|
+
clear = false;
|
|
1215
|
+
break;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
if (clear) {
|
|
1219
|
+
best = z;
|
|
1220
|
+
delta = Math.abs(z - fromZ);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return best;
|
|
1224
|
+
}
|
|
1225
|
+
path(start, goal, range = 0, blocked = new Set, targetZ) {
|
|
1226
|
+
this.localSearch ??= new RouteSearch;
|
|
1227
|
+
return this.localSearch.run(this, { start, goal, range, blocked: [...blocked], targetZ }).path;
|
|
1228
|
+
}
|
|
1229
|
+
async pathAsync(start, goal, range = 0, blocked = new Set, targetZ, signal, partial = false) {
|
|
1230
|
+
const request = (wide) => PathPool.get(this.directory, this.facet, this.sharedBuffers()).run({
|
|
1231
|
+
start,
|
|
1232
|
+
goal,
|
|
1233
|
+
range,
|
|
1234
|
+
blocked: [...blocked, ...teleporterTiles.filter((tile) => tile !== `${goal.x},${goal.y}`)],
|
|
1235
|
+
penalties: troubleCells(),
|
|
1236
|
+
penaltyCost: tuning.trouble.stepCost,
|
|
1237
|
+
targetZ,
|
|
1238
|
+
partial,
|
|
1239
|
+
wide,
|
|
1240
|
+
seed: routeSeed(goal)
|
|
1241
|
+
}, signal);
|
|
1242
|
+
let result = await request(false);
|
|
1243
|
+
const end = result.path?.at(-1);
|
|
1244
|
+
const short = !end || Math.max(Math.abs(end.x - goal.x), Math.abs(end.y - goal.y)) > range;
|
|
1245
|
+
if (short && result.exhausted && result.expanded >= 8192 && !signal?.aborted) {
|
|
1246
|
+
const wide = await request(true);
|
|
1247
|
+
if (wide.path)
|
|
1248
|
+
result = wide;
|
|
1249
|
+
}
|
|
1250
|
+
return result.path;
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
// src/path-worker.ts
|
|
1256
|
+
import { parentPort, workerData } from "worker_threads";
|
|
1257
|
+
|
|
1258
|
+
// src/path-corridors.ts
|
|
1259
|
+
init_direct_route();
|
|
1260
|
+
init_path_search();
|
|
1261
|
+
|
|
1262
|
+
// src/route-waypoints.ts
|
|
1263
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1264
|
+
var publicRoutes = JSON.parse(readFileSync2(new URL("../locations/route-corridors.json", import.meta.url), "utf8"));
|
|
1265
|
+
var distance2 = (a, b) => Math.max(Math.abs(a.x - b.x), Math.abs(a.y - b.y));
|
|
1266
|
+
var point = (row) => ({ x: row[0] ?? 0, y: row[1] ?? 0, z: row[2] ?? 0 });
|
|
1267
|
+
|
|
1268
|
+
class RouteWaypoints {
|
|
1269
|
+
corridors;
|
|
1270
|
+
constructor(corridors = publicRoutes.corridors) {
|
|
1271
|
+
this.corridors = corridors;
|
|
1272
|
+
}
|
|
1273
|
+
run(terrain, request, search, cancelled) {
|
|
1274
|
+
if (!request.partial || terrain.facet !== publicRoutes.facet)
|
|
1275
|
+
return null;
|
|
1276
|
+
const remaining = distance2(request.start, request.goal);
|
|
1277
|
+
const choices = [];
|
|
1278
|
+
for (const corridor of this.corridors) {
|
|
1279
|
+
for (const rows of corridor.both ? [corridor.points, [...corridor.points].reverse()] : [corridor.points]) {
|
|
1280
|
+
const last = rows.at(-1);
|
|
1281
|
+
if (!last)
|
|
1282
|
+
continue;
|
|
1283
|
+
const end = point(last);
|
|
1284
|
+
if (distance2(end, request.goal) >= remaining - 8)
|
|
1285
|
+
continue;
|
|
1286
|
+
for (let i = 0;i < rows.length - 1; i++) {
|
|
1287
|
+
const row = rows[i];
|
|
1288
|
+
const following = rows[i + 1];
|
|
1289
|
+
if (!row || !following)
|
|
1290
|
+
continue;
|
|
1291
|
+
const join = point(row);
|
|
1292
|
+
const d = distance2(request.start, join);
|
|
1293
|
+
if (d > 128)
|
|
1294
|
+
continue;
|
|
1295
|
+
choices.push({ join, next: point(following), score: d + distance2(end, request.goal) * 0.05 });
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
choices.sort((a, b) => a.score - b.score);
|
|
1300
|
+
for (const choice of choices.slice(0, 4)) {
|
|
1301
|
+
if (cancelled())
|
|
1302
|
+
return null;
|
|
1303
|
+
const joined = search.run(terrain, { ...request, goal: choice.join, range: 0, targetZ: choice.join.z, partial: false }, cancelled);
|
|
1304
|
+
if (!joined.path)
|
|
1305
|
+
continue;
|
|
1306
|
+
const start = joined.path.at(-1) ?? request.start;
|
|
1307
|
+
const onward = search.run(terrain, { ...request, start, goal: choice.next, range: 0, targetZ: choice.next.z, partial: false }, cancelled);
|
|
1308
|
+
if (onward.path?.length)
|
|
1309
|
+
return {
|
|
1310
|
+
...onward,
|
|
1311
|
+
path: [...joined.path, ...onward.path],
|
|
1312
|
+
expanded: joined.expanded + onward.expanded,
|
|
1313
|
+
elapsedMs: joined.elapsedMs + onward.elapsedMs
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
return null;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// src/path-corridors.ts
|
|
1321
|
+
class RouteCorridors {
|
|
1322
|
+
routes = new Map;
|
|
1323
|
+
search = new RouteSearch;
|
|
1324
|
+
waypoints = new RouteWaypoints;
|
|
1325
|
+
run(terrain, request, cancelled) {
|
|
1326
|
+
const began = performance.now();
|
|
1327
|
+
const blocked = new Set(request.blocked);
|
|
1328
|
+
const distance = (a, b) => Math.max(Math.abs(a.x - b.x), Math.abs(a.y - b.y));
|
|
1329
|
+
const key = `${request.goal.x},${request.goal.y},${request.range},${request.targetZ ?? ""}`;
|
|
1330
|
+
if (distance(request.start, request.goal) > 96) {
|
|
1331
|
+
const local = this.waypoints.run(terrain, request, this.search, cancelled);
|
|
1332
|
+
if (local)
|
|
1333
|
+
return { ...local, elapsedMs: performance.now() - began };
|
|
1334
|
+
}
|
|
1335
|
+
const direct = directRoute(terrain, request.start, request.goal, request.range, blocked, request.targetZ, 1024);
|
|
1336
|
+
if (direct && distance(direct.at(-1) ?? request.start, request.goal) <= request.range)
|
|
1337
|
+
return { path: direct, expanded: 0, exhausted: false, elapsedMs: performance.now() - began };
|
|
1338
|
+
const corridor = this.routes.get(key);
|
|
1339
|
+
if (corridor && !corridor.some((p) => blocked.has(`${p.x},${p.y}`))) {
|
|
1340
|
+
let index = -1;
|
|
1341
|
+
let best = 49;
|
|
1342
|
+
for (const [i, point] of corridor.entries()) {
|
|
1343
|
+
const d = distance(request.start, point);
|
|
1344
|
+
if (d <= best) {
|
|
1345
|
+
best = d;
|
|
1346
|
+
index = i;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
const join = corridor[index];
|
|
1350
|
+
if (join) {
|
|
1351
|
+
const prefix = this.search.run(terrain, { ...request, goal: join, targetZ: join.z, range: 0, partial: false }, cancelled);
|
|
1352
|
+
if (prefix.path)
|
|
1353
|
+
return {
|
|
1354
|
+
...prefix,
|
|
1355
|
+
path: [...prefix.path, ...corridor.slice(index + 1)],
|
|
1356
|
+
elapsedMs: performance.now() - began
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
const result = this.search.run(terrain, request, cancelled);
|
|
1361
|
+
const end = result.path?.at(-1) ?? request.start;
|
|
1362
|
+
if (result.path && result.path.length > 64 && distance(end, request.goal) <= request.range && (request.targetZ === undefined || Math.abs(end.z - request.targetZ) <= 8)) {
|
|
1363
|
+
const oldest = this.routes.keys().next().value;
|
|
1364
|
+
if (this.routes.size >= 64 && oldest !== undefined)
|
|
1365
|
+
this.routes.delete(oldest);
|
|
1366
|
+
this.routes.set(key, result.path);
|
|
1367
|
+
}
|
|
1368
|
+
return result;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// src/path-worker.ts
|
|
1373
|
+
init_terrain();
|
|
1374
|
+
var terrain = Terrain.shared(workerData.directory, workerData.facet, workerData.buffers);
|
|
1375
|
+
var search = new RouteCorridors;
|
|
1376
|
+
parentPort?.on("message", (message) => {
|
|
1377
|
+
const cancelled = new Int32Array(message.cancellation);
|
|
1378
|
+
try {
|
|
1379
|
+
const result = search.run(terrain, message.request, () => Atomics.load(cancelled, 0) !== 0);
|
|
1380
|
+
parentPort?.postMessage({ id: message.id, result });
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
parentPort?.postMessage({ id: message.id, error: error instanceof Error ? error.message : String(error) });
|
|
1383
|
+
}
|
|
1384
|
+
});
|