gdharness 1.3.54 → 1.3.56

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/build/cli.js CHANGED
@@ -800,6 +800,30 @@ public static class GdhProcessTable
800
800
  return "";
801
801
  }
802
802
 
803
+ /// <summary>"pid seconds" on a line for each running one of the comma-separated pids, from one snapshot.</summary>
804
+ public static string ProcessorSecondsOf(string pids)
805
+ {
806
+ HashSet<int> wanted = new HashSet<int>();
807
+ foreach (string one in pids.Split(','))
808
+ {
809
+ int pid;
810
+ if (int.TryParse(one, NumberStyles.Integer, CultureInfo.InvariantCulture, out pid))
811
+ {
812
+ wanted.Add(pid);
813
+ }
814
+ }
815
+ StringBuilder said = new StringBuilder();
816
+ foreach (Entry entry in Snapshot())
817
+ {
818
+ if (wanted.Contains(entry.Pid))
819
+ {
820
+ said.Append(Number(entry.Pid)).Append(' ')
821
+ .Append((entry.Processor / 1e7).ToString("R", CultureInfo.InvariantCulture)).Append('\n');
822
+ }
823
+ }
824
+ return said.ToString();
825
+ }
826
+
803
827
  /// <summary>How many more bytes Windows will commit.</summary>
804
828
  public static string CommitLeft()
805
829
  {
@@ -1693,7 +1717,6 @@ var init_whole_file = __esm(() => {
1693
1717
  // src/engine-slots.ts
1694
1718
  import {
1695
1719
  closeSync as closeSync2,
1696
- existsSync as existsSync2,
1697
1720
  mkdirSync as mkdirSync2,
1698
1721
  openSync as openSync2,
1699
1722
  readdirSync,
@@ -1737,8 +1760,14 @@ function machineSlots(asked = {
1737
1760
  function nonEmpty(value) {
1738
1761
  return value === undefined || value === "" ? undefined : value;
1739
1762
  }
1763
+ function keptSlotsOf(count) {
1764
+ return Math.max(2, Math.floor(count / 4));
1765
+ }
1766
+ function gamesKeptOf(kept) {
1767
+ return Math.max(0, Math.min(Math.floor(kept / 2), kept - GATE_KEPT - 1));
1768
+ }
1740
1769
  function slotsDrawnOn(count, taker, queue) {
1741
- const gated = Math.max(1, count - KEPT_SLOTS);
1770
+ const gated = Math.max(1, count - keptSlotsOf(count));
1742
1771
  if (taker === "gate") {
1743
1772
  return { numbers: Array.from({ length: gated }, (_, slot) => slot), limit: "gate" };
1744
1773
  }
@@ -1750,10 +1779,7 @@ function slotsDrawnOn(count, taker, queue) {
1750
1779
  if (taker === "tier") {
1751
1780
  return { numbers: [...kept].reverse(), limit: "tier" };
1752
1781
  }
1753
- if (queue === "waiting") {
1754
- return { numbers: [], limit: "held" };
1755
- }
1756
- return { numbers: kept.slice(0, 1), limit: "kept" };
1782
+ return { numbers: kept.slice(0, Math.max(1, kept.length - GATE_KEPT)), limit: "kept" };
1757
1783
  }
1758
1784
  function inAGate(env = process4.env) {
1759
1785
  return nonEmpty(env[SHARE_VARIABLE]) !== undefined && nonEmpty(env[SHARE_COUNT_VARIABLE]) !== undefined;
@@ -1828,11 +1854,16 @@ function holderOf(content) {
1828
1854
  if (typeof parsed !== "object" || parsed === null) {
1829
1855
  return null;
1830
1856
  }
1831
- const { pid, started, by } = parsed;
1857
+ const { pid, started, by, game } = parsed;
1832
1858
  if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || typeof started !== "number") {
1833
1859
  return null;
1834
1860
  }
1835
- return typeof by === "string" && by !== "" ? { pid, started, by } : { pid, started };
1861
+ return {
1862
+ pid,
1863
+ started,
1864
+ ...typeof by === "string" && by !== "" ? { by } : {},
1865
+ ...game === true ? { game } : {}
1866
+ };
1836
1867
  } catch {
1837
1868
  return null;
1838
1869
  }
@@ -1873,7 +1904,7 @@ class EngineSlots {
1873
1904
  this.taker = taker;
1874
1905
  this.drawn = slotsDrawnOn(count, taker(), "none");
1875
1906
  }
1876
- async take(signal, waiting, blocked) {
1907
+ async take(signal, waiting, blocked, game = false) {
1877
1908
  mkdirSync2(this.directory, { recursive: true });
1878
1909
  const began = Date.now();
1879
1910
  let said = Number.NEGATIVE_INFINITY;
@@ -1882,7 +1913,7 @@ class EngineSlots {
1882
1913
  try {
1883
1914
  for (;; ) {
1884
1915
  signal?.throwIfAborted();
1885
- const lock = await this.takeFree();
1916
+ const lock = await this.takeFree(game);
1886
1917
  if (lock !== null) {
1887
1918
  return lock;
1888
1919
  }
@@ -1914,7 +1945,7 @@ class EngineSlots {
1914
1945
  }
1915
1946
  }
1916
1947
  }
1917
- async takeFree() {
1948
+ async takeFree(game = false) {
1918
1949
  mkdirSync2(this.directory, { recursive: true });
1919
1950
  await this.sweep();
1920
1951
  const inherited = this.inherited === null ? await this.inheritedLock() : null;
@@ -1924,9 +1955,18 @@ class EngineSlots {
1924
1955
  }
1925
1956
  const taker = this.taker();
1926
1957
  this.drawn = slotsDrawnOn(this.count, taker, taker === "gate" || taker === "timing" ? "none" : await this.gateQueue());
1958
+ if (game && this.drawn.limit === "kept") {
1959
+ const share = slotsDrawnOn(this.count, "tier", "queued").numbers;
1960
+ const games = await this.heldByGames(share);
1961
+ if (games.length >= gamesKeptOf(share.length)) {
1962
+ this.drawn = { numbers: games, limit: "games" };
1963
+ return null;
1964
+ }
1965
+ }
1966
+ const holder = game ? { ...this.holder, game: true } : this.holder;
1927
1967
  for (const slot of this.drawn.numbers) {
1928
1968
  const lock = join5(this.directory, `${String(slot)}.lock`);
1929
- if (this.made(lock) || await this.takenOver(lock)) {
1969
+ if (this.made(lock, holder) || await this.takenOver(lock, holder)) {
1930
1970
  this.held.add(lock);
1931
1971
  return lock;
1932
1972
  }
@@ -1943,6 +1983,14 @@ class EngineSlots {
1943
1983
  const tree = await processTree();
1944
1984
  return tree !== undefined && inheritsSlot(tree, process4.pid, holder.pid, basename3(process4.execPath)) ? lock : null;
1945
1985
  }
1986
+ markWaiting() {
1987
+ const mark = this.waitMarked();
1988
+ return () => {
1989
+ if (mark !== undefined && this.marks.delete(mark)) {
1990
+ removeWhole(mark);
1991
+ }
1992
+ };
1993
+ }
1946
1994
  waitMarked() {
1947
1995
  const marks = join5(this.directory, WAITING_FOLDER);
1948
1996
  waitsMarked += 1;
@@ -1958,6 +2006,17 @@ class EngineSlots {
1958
2006
  this.marks.add(mark);
1959
2007
  return mark;
1960
2008
  }
2009
+ async heldByGames(numbers) {
2010
+ const games = [];
2011
+ for (const slot of numbers) {
2012
+ const lock = join5(this.directory, `${String(slot)}.lock`);
2013
+ const content = readWhole(lock);
2014
+ if (content !== null && holderOf(content)?.game === true && !await this.free(lock)) {
2015
+ games.push(slot);
2016
+ }
2017
+ }
2018
+ return games;
2019
+ }
1961
2020
  async gateQueue() {
1962
2021
  const marks = join5(this.directory, QUEUED_FOLDER);
1963
2022
  let names;
@@ -1976,10 +2035,8 @@ class EngineSlots {
1976
2035
  if (await this.free(mark)) {
1977
2036
  removeWhole(mark);
1978
2037
  removeWhole(`${mark}${SEATED_SUFFIX}`);
1979
- } else if (!existsSync2(`${mark}${SEATED_SUFFIX}`)) {
1980
- live = "waiting";
1981
- } else if (live === "none") {
1982
- live = "seated";
2038
+ } else {
2039
+ live = "queued";
1983
2040
  }
1984
2041
  }
1985
2042
  return live;
@@ -2023,7 +2080,7 @@ class EngineSlots {
2023
2080
  }
2024
2081
  return handed;
2025
2082
  }
2026
- made(path) {
2083
+ made(path, holder = this.holder) {
2027
2084
  let made;
2028
2085
  try {
2029
2086
  made = openSync2(path, "wx");
@@ -2031,7 +2088,7 @@ class EngineSlots {
2031
2088
  return false;
2032
2089
  }
2033
2090
  try {
2034
- writeSync2(made, JSON.stringify(this.holder));
2091
+ writeSync2(made, JSON.stringify(holder));
2035
2092
  } finally {
2036
2093
  closeSync2(made);
2037
2094
  }
@@ -2051,10 +2108,10 @@ class EngineSlots {
2051
2108
  }
2052
2109
  }
2053
2110
  }
2054
- takenOver(lock) {
2055
- return this.cleared(lock, true);
2111
+ takenOver(lock, holder) {
2112
+ return this.cleared(lock, true, holder);
2056
2113
  }
2057
- async cleared(lock, retake) {
2114
+ async cleared(lock, retake, holder = this.holder) {
2058
2115
  if (!await this.free(lock)) {
2059
2116
  return false;
2060
2117
  }
@@ -2076,7 +2133,7 @@ class EngineSlots {
2076
2133
  } catch {
2077
2134
  return false;
2078
2135
  }
2079
- return retake ? this.made(lock) : true;
2136
+ return retake ? this.made(lock, holder) : true;
2080
2137
  } finally {
2081
2138
  rmSync4(steal, { force: true });
2082
2139
  }
@@ -2112,7 +2169,8 @@ function handSlot(lock, from, to) {
2112
2169
  } catch {
2113
2170
  return false;
2114
2171
  }
2115
- return content !== null && holderOf(content)?.pid === from && writeWhole(lock, JSON.stringify(to));
2172
+ const holder = content === null ? null : holderOf(content);
2173
+ return holder?.pid === from && writeWhole(lock, JSON.stringify(holder.game === true ? { ...to, game: true } : to));
2116
2174
  }
2117
2175
  function giveBackSlot(lock, pid) {
2118
2176
  try {
@@ -2139,15 +2197,12 @@ function nameSlotTaker(by) {
2139
2197
  }
2140
2198
  function slotWaitSaid(waitedMs, count, directory, holders = [], drawn = { numbers: [] }) {
2141
2199
  const so = `Waiting${waitedMs >= 1000 ? ` ${String(Math.round(waitedMs / 1000))} s so far` : ""} for an engine`;
2142
- if (drawn.limit === "held") {
2143
- return `${so}: ${GATE_UNSEATED} (${directory}), and the engine starts once every queued gate has its seat.`;
2144
- }
2145
2200
  const taking = drawn.limit === undefined ? count : drawn.numbers.length;
2146
2201
  const slots = `${slotsSaid(taking, count, directory, drawn.limit)} ${taking === 1 ? "is" : "are"} in use`;
2147
2202
  const named = holdersNamed(taking, holders);
2148
2203
  const waiting = `${so}: ${slots}`;
2149
2204
  const back = `the engine starts when ${taking === 1 ? "it" : "one"} is given back`;
2150
- const starts = drawn.limit === "kept" || drawn.limit === "tier" ? `${back} or no gate is queued` : back;
2205
+ const starts = drawn.limit === "kept" || drawn.limit === "tier" || drawn.limit === "games" ? `${back} or no gate is queued` : back;
2151
2206
  if (named.length === 0) {
2152
2207
  return `${waiting}, by a gate or another tool, and ${starts}.`;
2153
2208
  }
@@ -2167,13 +2222,13 @@ function slotsSaid(taking, count, directory, limit) {
2167
2222
  if (limit === "tier") {
2168
2223
  return `${taking === 1 ? "the one engine slot" : `the ${String(taking)} engine slots`} of the ${String(count)} ${where} a gate's own steps take while a gate is queued`;
2169
2224
  }
2225
+ if (limit === "games") {
2226
+ return `${taking === 1 ? "the one engine slot" : `the ${String(taking)} engine slots`} of the ${String(count)} ${where} that games editor_run started may hold while a gate is queued, the rest kept for short calls,`;
2227
+ }
2170
2228
  return count === 1 ? `the one engine slot ${where}` : `all ${String(count)} engine slots ${where}`;
2171
2229
  }
2172
2230
  function slotWaitedSaid(waitedMs, count, directory, holders, drawn = { numbers: [] }) {
2173
2231
  const waited = `This call waited ${String(Math.round(waitedMs / 1000))} s for an engine slot in all.`;
2174
- if (drawn.limit === "held") {
2175
- return `${waited} When it first waited, ${GATE_UNSEATED} (${directory}), and engines outside a gate wait until every queued gate has its seat.`;
2176
- }
2177
2232
  const taking = drawn.limit === undefined ? count : drawn.numbers.length;
2178
2233
  const slots = `${slotsSaid(taking, count, directory, drawn.limit)} ${taking === 1 ? "was" : "were"} in use`;
2179
2234
  const named = holdersNamed(taking, holders);
@@ -2196,7 +2251,7 @@ function holdersNamed(count, holders) {
2196
2251
  function listed(named) {
2197
2252
  return named.length === 1 ? named.join("") : `${named.slice(0, -1).join(", ")} and ${named.at(-1) ?? ""}`;
2198
2253
  }
2199
- async function slotForCall() {
2254
+ async function slotForCall(game = false) {
2200
2255
  const slots = await processSlots();
2201
2256
  const progress = callProgress();
2202
2257
  const waits = callSlotWaits();
@@ -2208,7 +2263,7 @@ async function slotForCall() {
2208
2263
  }, () => {
2209
2264
  blockedBy = slots.holders();
2210
2265
  blockedOn = slots.drawn;
2211
- });
2266
+ }, game);
2212
2267
  if (waits !== undefined && blockedBy !== undefined) {
2213
2268
  waits.ms += Date.now() - began;
2214
2269
  const heldBy = blockedBy;
@@ -2235,7 +2290,7 @@ async function inASlot(body) {
2235
2290
  held.release();
2236
2291
  }
2237
2292
  }
2238
- var HELD_VARIABLE = "GODOT_ENGINE_HELD", SLOTS_VARIABLE = "GODOT_ENGINE_SLOTS", SLOT_COUNT_VARIABLE = "GODOT_ENGINE_SLOT_COUNT", PROCESSORS_A_SLOT = 2, START_TOLERANCE_MS = 2000, UNWRITTEN_MS = 1e4, SLOT_LOOK_MS = 1000, SLOT_SETTLE_MS = 50, WAIT_SAID_MS = 20000, COMMAND_WAIT_SAID_MS = 2000, START_READ_MS = 1e4, WAITING_FOLDER = "waiting", QUEUED_FOLDER = "queued", SHARE_VARIABLE = "GODOT_ENGINE_SHARE", SHARE_COUNT_VARIABLE = "GODOT_ENGINE_SHARE_COUNT", KEPT_SLOTS = 2, TIMED_VARIABLE = "GODOT_ENGINE_TIMED", TIER_VARIABLE = "GODOT_ENGINE_TIER", SEATED_SUFFIX = ".seated", GATE_UNSEATED = "a gate in this machine's queue is waiting for its seat", waitsMarked = 0, ours, saidTo, takenBy;
2293
+ var HELD_VARIABLE = "GODOT_ENGINE_HELD", SLOTS_VARIABLE = "GODOT_ENGINE_SLOTS", SLOT_COUNT_VARIABLE = "GODOT_ENGINE_SLOT_COUNT", PROCESSORS_A_SLOT = 2, START_TOLERANCE_MS = 2000, UNWRITTEN_MS = 1e4, SLOT_LOOK_MS = 1000, SLOT_SETTLE_MS = 50, WAIT_SAID_MS = 20000, COMMAND_WAIT_SAID_MS = 2000, START_READ_MS = 1e4, WAITING_FOLDER = "waiting", QUEUED_FOLDER = "queued", SHARE_VARIABLE = "GODOT_ENGINE_SHARE", SHARE_COUNT_VARIABLE = "GODOT_ENGINE_SHARE_COUNT", GATE_KEPT = 1, TIMED_VARIABLE = "GODOT_ENGINE_TIMED", TIER_VARIABLE = "GODOT_ENGINE_TIER", SEATED_SUFFIX = ".seated", waitsMarked = 0, ours, saidTo, takenBy;
2239
2294
  var init_engine_slots = __esm(() => {
2240
2295
  init_alive();
2241
2296
  init_cache_home();
@@ -2245,6 +2300,184 @@ var init_engine_slots = __esm(() => {
2245
2300
  init_whole_file();
2246
2301
  });
2247
2302
 
2303
+ // src/process-time.ts
2304
+ async function cpuSecondsOf(pid) {
2305
+ try {
2306
+ return process.platform === "win32" ? await secondsOnWindows(pid) : await askPosix2(pid);
2307
+ } catch {
2308
+ return;
2309
+ }
2310
+ }
2311
+ async function secondsOnWindows(pid) {
2312
+ const said = (await askProcessTable(`ProcessorSeconds(${pid})`, ASK_TIMEOUT_MS2)).trim();
2313
+ const seconds = Number(said);
2314
+ return said !== "" && Number.isFinite(seconds) ? seconds : undefined;
2315
+ }
2316
+ async function askPosix2(pid) {
2317
+ const { stdout } = await runHidden("ps", ["-o", "time=", "-p", String(pid)], { timeout: ASK_TIMEOUT_MS2 });
2318
+ return secondsFromClock(stdout.trim());
2319
+ }
2320
+ function secondsFromClock(printed) {
2321
+ if (printed === "") {
2322
+ return;
2323
+ }
2324
+ const dashed = printed.includes("-");
2325
+ const days = dashed ? printed.slice(0, printed.indexOf("-")) : null;
2326
+ const clock = dashed ? printed.slice(printed.indexOf("-") + 1) : printed;
2327
+ const parts = clock.split(":").map((part) => Number(part));
2328
+ if (parts.length === 0 || parts.some((part) => !Number.isFinite(part))) {
2329
+ return;
2330
+ }
2331
+ const seconds = parts.reduce((total, part) => total * 60 + part, 0);
2332
+ const extra = days === null ? 0 : Number(days) * 86400;
2333
+ return Number.isFinite(extra) ? seconds + extra : undefined;
2334
+ }
2335
+ var ASK_TIMEOUT_MS2 = 6000;
2336
+ var init_process_time = __esm(() => {
2337
+ init_process_table();
2338
+ init_spawn();
2339
+ });
2340
+
2341
+ // src/engine-spent.ts
2342
+ import { readFileSync as readFileSync7 } from "node:fs";
2343
+ import { availableParallelism as availableParallelism2, cpus } from "node:os";
2344
+ import { performance as performance2 } from "node:perf_hooks";
2345
+ function crowded(stretch, work, busy, cores) {
2346
+ return stretch > 0 && Math.max(0, busy - work) / (stretch * cores) >= BUSY;
2347
+ }
2348
+
2349
+ class Spent {
2350
+ byClock = 0;
2351
+ byWork = 0;
2352
+ since;
2353
+ cores;
2354
+ last;
2355
+ constructor(since, first, pid, cores = availableParallelism2()) {
2356
+ this.since = since;
2357
+ this.cores = cores;
2358
+ this.last = {
2359
+ at: since,
2360
+ machine: first?.machine ?? Number.NaN,
2361
+ processor: pid === undefined ? undefined : first?.processors?.get(pid)
2362
+ };
2363
+ }
2364
+ counted(sample, pid) {
2365
+ if (sample.at <= this.last.at) {
2366
+ return;
2367
+ }
2368
+ const stretch = sample.at - this.last.at;
2369
+ const processor = pid === undefined ? undefined : sample.processors?.get(pid);
2370
+ const work = processor === undefined || this.last.processor === undefined ? undefined : Math.max(0, processor - this.last.processor);
2371
+ if (work !== undefined && crowded(stretch, work, sample.machine - this.last.machine, this.cores)) {
2372
+ this.byWork += Math.min(work, stretch);
2373
+ } else {
2374
+ this.byClock += stretch;
2375
+ }
2376
+ this.last = { at: sample.at, machine: sample.machine, processor };
2377
+ }
2378
+ restart(at) {
2379
+ this.since = at;
2380
+ this.byClock = 0;
2381
+ this.byWork = 0;
2382
+ this.last = { ...this.last, at: Math.max(this.last.at, at) };
2383
+ }
2384
+ clock(now) {
2385
+ return now - this.since;
2386
+ }
2387
+ past(limit, now) {
2388
+ if (this.clock(now) > limit * STARVED) {
2389
+ return "clock";
2390
+ }
2391
+ return this.byClock + this.byWork > limit ? "own" : null;
2392
+ }
2393
+ }
2394
+ function nowSeconds() {
2395
+ return performance2.now() / 1000;
2396
+ }
2397
+ function machineSeconds() {
2398
+ return cpus().reduce((sum, core) => sum + core.times.user + core.times.nice + core.times.sys + core.times.irq, 0) / 1000;
2399
+ }
2400
+ function watching(pid, on) {
2401
+ if (on) {
2402
+ watched.add(pid);
2403
+ } else {
2404
+ watched.delete(pid);
2405
+ }
2406
+ }
2407
+ function sampled() {
2408
+ const now = Date.now();
2409
+ if (latest === null || now - latest.taken >= SAMPLE_MS) {
2410
+ latest = { taken: now, sample: take() };
2411
+ }
2412
+ return latest.sample;
2413
+ }
2414
+ async function take() {
2415
+ const at = nowSeconds();
2416
+ const machine = machineSeconds();
2417
+ const previous = quietAt;
2418
+ quietAt = { at, machine, processors: null };
2419
+ const share = previous === null || at <= previous.at ? 1 : (machine - previous.machine) / ((at - previous.at) * availableParallelism2());
2420
+ if (share < BUSY || watched.size === 0) {
2421
+ return quietAt;
2422
+ }
2423
+ return { at, machine, processors: await processorSecondsOf([...watched]) };
2424
+ }
2425
+ async function processorSecondsOf(pids) {
2426
+ const found = new Map;
2427
+ try {
2428
+ if (process.platform === "win32") {
2429
+ const said = await askProcessTable(`ProcessorSecondsOf('${pids.join(",")}')`, ASK_TIMEOUT_MS3);
2430
+ for (const line of said.split(/\r?\n/)) {
2431
+ const [pid, seconds] = line.trim().split(" ").map(Number);
2432
+ if (pid !== undefined && seconds !== undefined && Number.isFinite(pid) && Number.isFinite(seconds)) {
2433
+ found.set(pid, seconds);
2434
+ }
2435
+ }
2436
+ } else if (process.platform === "linux") {
2437
+ const ticks = await ticksASecond();
2438
+ for (const pid of pids) {
2439
+ try {
2440
+ const fields = (readFileSync7(`/proc/${String(pid)}/stat`, "utf8").split(")").pop() ?? "").trim().split(/\s+/);
2441
+ const used = Number(fields[11]) + Number(fields[12]);
2442
+ if (Number.isFinite(used)) {
2443
+ found.set(pid, used / ticks);
2444
+ }
2445
+ } catch {}
2446
+ }
2447
+ } else {
2448
+ const asked = (some) => runHidden("ps", ["-o", "pid=,time=", "-p", some.join(",")], { timeout: ASK_TIMEOUT_MS3 }).then((done) => done.stdout, (error) => {
2449
+ const printed = error.stdout;
2450
+ return typeof printed === "string" ? printed : "";
2451
+ });
2452
+ let stdout = await asked(pids);
2453
+ if (stdout.trim() === "" && pids.length > 1) {
2454
+ stdout = (await Promise.all(pids.map((pid) => asked([pid])))).join(`
2455
+ `);
2456
+ }
2457
+ for (const line of stdout.split(`
2458
+ `)) {
2459
+ const [pid, time] = line.trim().split(/\s+/);
2460
+ const seconds = time === undefined ? undefined : secondsFromClock(time);
2461
+ if (pid !== undefined && seconds !== undefined) {
2462
+ found.set(Number(pid), seconds);
2463
+ }
2464
+ }
2465
+ }
2466
+ } catch {}
2467
+ return found;
2468
+ }
2469
+ function ticksASecond() {
2470
+ ticks ??= runHidden("getconf", ["CLK_TCK"], { timeout: ASK_TIMEOUT_MS3 }).then(({ stdout }) => Number(stdout.trim()) || 100).catch(() => 100);
2471
+ return ticks;
2472
+ }
2473
+ var STARVED = 8, BUSY = 0.25, SAMPLE_MS = 1000, ASK_TIMEOUT_MS3 = 3000, watched, latest = null, quietAt = null, ticks = null;
2474
+ var init_engine_spent = __esm(() => {
2475
+ init_process_table();
2476
+ init_process_time();
2477
+ init_spawn();
2478
+ watched = new Set;
2479
+ });
2480
+
2248
2481
  // node_modules/yaml/dist/nodes/identity.js
2249
2482
  var require_identity = __commonJS(function(exports) {
2250
2483
  var ALIAS = Symbol.for("yaml.alias");
@@ -9332,7 +9565,7 @@ var init_config_formats = __esm(() => {
9332
9565
  });
9333
9566
 
9334
9567
  // src/runner.ts
9335
- import { existsSync as existsSync3, realpathSync as realpathSync2 } from "node:fs";
9568
+ import { existsSync as existsSync2, realpathSync as realpathSync2 } from "node:fs";
9336
9569
  import { delimiter, dirname as dirname3, extname, join as join6, resolve as resolve3 } from "node:path";
9337
9570
  import process5 from "node:process";
9338
9571
  function currentRunner() {
@@ -9376,12 +9609,12 @@ function realPath(path) {
9376
9609
  }
9377
9610
  function alongside(execPath, name) {
9378
9611
  const runner = join6(dirname3(execPath), process5.platform === "win32" ? `${name}.cmd` : name);
9379
- return existsSync3(runner) ? runner : undefined;
9612
+ return existsSync2(runner) ? runner : undefined;
9380
9613
  }
9381
9614
  var init_runner = () => {};
9382
9615
 
9383
9616
  // src/harnesses.ts
9384
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync7, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync5 } from "node:fs";
9617
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync8, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync5 } from "node:fs";
9385
9618
  import { homedir as homedir3 } from "node:os";
9386
9619
  import { basename as basename4, dirname as dirname4, isAbsolute as isAbsolute2, join as join7, relative as relative2, resolve as resolve4 } from "node:path";
9387
9620
  import process6 from "node:process";
@@ -9472,15 +9705,15 @@ function harnessById(id) {
9472
9705
  }
9473
9706
  function present(harness, projectPath) {
9474
9707
  const path = configPath(harness, projectPath);
9475
- if (existsSync4(path)) {
9708
+ if (existsSync3(path)) {
9476
9709
  return true;
9477
9710
  }
9478
9711
  const root = harness.scope === "project" ? projectPath : homedir3();
9479
9712
  const own = dirname4(path);
9480
- if (own !== root && existsSync4(own)) {
9713
+ if (own !== root && existsSync3(own)) {
9481
9714
  return true;
9482
9715
  }
9483
- return harness.marker !== undefined && existsSync4(join7(root, harness.marker));
9716
+ return harness.marker !== undefined && existsSync3(join7(root, harness.marker));
9484
9717
  }
9485
9718
  function candidates(projectPath) {
9486
9719
  const found = [];
@@ -9493,7 +9726,7 @@ function candidates(projectPath) {
9493
9726
  }
9494
9727
  if (present(harness, projectPath)) {
9495
9728
  found.push({ harness, reason: "configured" });
9496
- } else if (harness.home !== undefined && existsSync4(join7(homedir3(), harness.home))) {
9729
+ } else if (harness.home !== undefined && existsSync3(join7(homedir3(), harness.home))) {
9497
9730
  found.push({ harness, reason: "installed" });
9498
9731
  }
9499
9732
  }
@@ -9515,10 +9748,10 @@ function groupByFile(harnesses, projectPath) {
9515
9748
  }
9516
9749
  function registered(harness, projectPath) {
9517
9750
  const path = configPath(harness, projectPath);
9518
- if (harness.snippet !== undefined || !existsSync4(path)) {
9751
+ if (harness.snippet !== undefined || !existsSync3(path)) {
9519
9752
  return false;
9520
9753
  }
9521
- const text = readFileSync7(path, "utf8");
9754
+ const text = readFileSync8(path, "utf8");
9522
9755
  if (harness.toml !== undefined) {
9523
9756
  return text.includes(harness.toml.identity ?? harness.toml.header);
9524
9757
  }
@@ -9544,7 +9777,7 @@ function recordedEnginePath(projectPath) {
9544
9777
  }
9545
9778
  const named = reach(entryEnvironment(harness, projectPath), "GODOT_PATH");
9546
9779
  const path = typeof named !== "string" || named === "" ? null : isAbsolute2(named) ? named : resolve4(projectPath, named);
9547
- if (path !== null && existsSync4(path)) {
9780
+ if (path !== null && existsSync3(path)) {
9548
9781
  return path;
9549
9782
  }
9550
9783
  }
@@ -9552,7 +9785,7 @@ function recordedEnginePath(projectPath) {
9552
9785
  }
9553
9786
  function mainCheckoutOf(projectPath) {
9554
9787
  let top = resolve4(projectPath);
9555
- while (!existsSync4(join7(top, ".git"))) {
9788
+ while (!existsSync3(join7(top, ".git"))) {
9556
9789
  const up = dirname4(top);
9557
9790
  if (up === top) {
9558
9791
  return null;
@@ -9563,25 +9796,25 @@ function mainCheckoutOf(projectPath) {
9563
9796
  if (!statSync4(marker).isFile()) {
9564
9797
  return null;
9565
9798
  }
9566
- const pointer = /^gitdir:\s*(.+)$/m.exec(readFileSync7(marker, "utf8"))?.[1]?.trim();
9799
+ const pointer = /^gitdir:\s*(.+)$/m.exec(readFileSync8(marker, "utf8"))?.[1]?.trim();
9567
9800
  if (pointer === undefined) {
9568
9801
  return null;
9569
9802
  }
9570
9803
  const entry = resolve4(top, pointer);
9571
9804
  const commonFile = join7(entry, "commondir");
9572
- if (!existsSync4(commonFile)) {
9805
+ if (!existsSync3(commonFile)) {
9573
9806
  return null;
9574
9807
  }
9575
- const common = resolve4(entry, readFileSync7(commonFile, "utf8").trim());
9808
+ const common = resolve4(entry, readFileSync8(commonFile, "utf8").trim());
9576
9809
  if (basename4(common) !== ".git") {
9577
9810
  return null;
9578
9811
  }
9579
9812
  const main = join7(dirname4(common), relative2(top, resolve4(projectPath)));
9580
- return isSameDirectory(main, projectPath) || !existsSync4(join7(main, "project.godot")) ? null : main;
9813
+ return isSameDirectory(main, projectPath) || !existsSync3(join7(main, "project.godot")) ? null : main;
9581
9814
  }
9582
9815
  function worktreeCheckoutsOf(projectPath) {
9583
9816
  let top = resolve4(projectPath);
9584
- while (!existsSync4(join7(top, ".git"))) {
9817
+ while (!existsSync3(join7(top, ".git"))) {
9585
9818
  const up = dirname4(top);
9586
9819
  if (up === top) {
9587
9820
  return [];
@@ -9589,18 +9822,18 @@ function worktreeCheckoutsOf(projectPath) {
9589
9822
  top = up;
9590
9823
  }
9591
9824
  const listed = join7(top, ".git", "worktrees");
9592
- if (!statSync4(join7(top, ".git")).isDirectory() || !existsSync4(listed)) {
9825
+ if (!statSync4(join7(top, ".git")).isDirectory() || !existsSync3(listed)) {
9593
9826
  return [];
9594
9827
  }
9595
9828
  const below = relative2(top, resolve4(projectPath));
9596
9829
  return readdirSync2(listed).flatMap((name) => {
9597
9830
  const pointer = join7(listed, name, "gitdir");
9598
- if (!existsSync4(pointer)) {
9831
+ if (!existsSync3(pointer)) {
9599
9832
  return [];
9600
9833
  }
9601
- const checkout = dirname4(resolve4(listed, name, readFileSync7(pointer, "utf8").trim()));
9834
+ const checkout = dirname4(resolve4(listed, name, readFileSync8(pointer, "utf8").trim()));
9602
9835
  const project = join7(checkout, below);
9603
- return existsSync4(join7(project, "project.godot")) ? [project] : [];
9836
+ return existsSync3(join7(project, "project.godot")) ? [project] : [];
9604
9837
  });
9605
9838
  }
9606
9839
  function entryEnvironment(harness, projectPath) {
@@ -9608,12 +9841,12 @@ function entryEnvironment(harness, projectPath) {
9608
9841
  return;
9609
9842
  }
9610
9843
  const path = configPath(harness, projectPath);
9611
- if (!existsSync4(path)) {
9844
+ if (!existsSync3(path)) {
9612
9845
  return;
9613
9846
  }
9614
9847
  let parsed;
9615
9848
  try {
9616
- parsed = JSON.parse(readFileSync7(path, "utf8"));
9849
+ parsed = JSON.parse(readFileSync8(path, "utf8"));
9617
9850
  } catch {
9618
9851
  return;
9619
9852
  }
@@ -9629,13 +9862,13 @@ function reach(held, key) {
9629
9862
  }
9630
9863
  function disconnect(harness, projectPath) {
9631
9864
  const path = configPath(harness, projectPath);
9632
- if (!existsSync4(path)) {
9865
+ if (!existsSync3(path)) {
9633
9866
  return { harness, path, action: "absent" };
9634
9867
  }
9635
9868
  if (harness.snippet !== undefined) {
9636
9869
  return { harness, path, action: "manual" };
9637
9870
  }
9638
- const text = readFileSync7(path, "utf8");
9871
+ const text = readFileSync8(path, "utf8");
9639
9872
  if (harness.toml !== undefined) {
9640
9873
  return taken(harness, path, text, removeToml(text, harness.toml));
9641
9874
  }
@@ -9644,7 +9877,7 @@ function disconnect(harness, projectPath) {
9644
9877
  }
9645
9878
  let existing;
9646
9879
  try {
9647
- existing = JSON.parse(readFileSync7(path, "utf8"));
9880
+ existing = JSON.parse(readFileSync8(path, "utf8"));
9648
9881
  } catch {
9649
9882
  return { harness, path, action: "absent" };
9650
9883
  }
@@ -9716,7 +9949,7 @@ function connect(harness, projectPath, launch) {
9716
9949
  if (harness.snippet !== undefined) {
9717
9950
  return { harness, path, action: "snippet", snippet: harness.snippet(launch) };
9718
9951
  }
9719
- const held = existsSync4(path) ? readFileSync7(path, "utf8") : "";
9952
+ const held = existsSync3(path) ? readFileSync8(path, "utf8") : "";
9720
9953
  if (harness.toml !== undefined) {
9721
9954
  const already = held.includes(harness.toml.identity ?? harness.toml.header);
9722
9955
  return put(harness, path, writeToml(held, harness.toml, launch), already);
@@ -10882,7 +11115,7 @@ var init_gdscript_source = __esm(() => {
10882
11115
  });
10883
11116
 
10884
11117
  // src/class-cache.ts
10885
- import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync5 } from "node:fs";
11118
+ import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync5 } from "node:fs";
10886
11119
  import { join as join8, posix } from "node:path";
10887
11120
  function isToolScript(source) {
10888
11121
  return TOOL.test(codeOf(source));
@@ -10946,7 +11179,7 @@ function declaredClassOf(source) {
10946
11179
  }
10947
11180
  function eachScript(projectPath, each) {
10948
11181
  const visit = (directory, prefix) => {
10949
- if (existsSync5(join8(directory, ".gdignore"))) {
11182
+ if (existsSync4(join8(directory, ".gdignore"))) {
10950
11183
  return;
10951
11184
  }
10952
11185
  for (const entry of readdirSync3(directory, { withFileTypes: true })) {
@@ -10957,7 +11190,7 @@ function eachScript(projectPath, each) {
10957
11190
  if (entry.isDirectory()) {
10958
11191
  visit(path, `${prefix}${entry.name}/`);
10959
11192
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
10960
- each(`res://${prefix}${entry.name}`, readFileSync8(path, "utf8"));
11193
+ each(`res://${prefix}${entry.name}`, readFileSync9(path, "utf8"));
10961
11194
  }
10962
11195
  }
10963
11196
  };
@@ -11342,7 +11575,7 @@ function uncachedForTheEngineNote(projectPath, types) {
11342
11575
  }
11343
11576
  const one = types.length === 1;
11344
11577
  const named = types.map((type) => `${type.type} (${type.declaredIn})`).join(", ");
11345
- const cacheExists = existsSync5(join8(projectPath, ".godot", "global_script_class_cache.cfg"));
11578
+ const cacheExists = existsSync4(join8(projectPath, ".godot", "global_script_class_cache.cfg"));
11346
11579
  const state = cacheExists ? `missing from .godot/global_script_class_cache.cfg, which is where the engine that compiled these scripts resolves a class name from, so the error is the cache being behind the files rather than the script.` : `the engine that compiled these scripts resolves a class name from .godot/global_script_class_cache.cfg, which has not been written yet, as in a project no editor has opened, so the error is the missing cache rather than the script.`;
11347
11580
  return `${named} ${one ? "is" : "are"} declared in this project${cacheExists ? " and" : ", but"} ${state} ` + "project_import refresh_classes writes the cache from the declarations on disk; editor_run start " + "writes it before a game, and an editor opening the project writes it when it scans.";
11348
11581
  }
@@ -11368,7 +11601,7 @@ function declaredSince(projectPath, paths, names, since) {
11368
11601
  });
11369
11602
  }
11370
11603
  function cachedAtMissingPaths(cached, projectPath) {
11371
- return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync5(join8(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
11604
+ return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync4(join8(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
11372
11605
  }
11373
11606
  function staleAgainst(cached, projectPath) {
11374
11607
  return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
@@ -12095,7 +12328,7 @@ var init_junit = __esm(() => {
12095
12328
  });
12096
12329
 
12097
12330
  // src/suite-judge.ts
12098
- import { readFileSync as readFileSync9 } from "node:fs";
12331
+ import { readFileSync as readFileSync10 } from "node:fs";
12099
12332
  import { stripVTControlCharacters } from "node:util";
12100
12333
  function secondsOf(time) {
12101
12334
  let ms = 0;
@@ -12272,7 +12505,7 @@ function readRun(printed) {
12272
12505
  settleAfterCase("after");
12273
12506
  const joined = lines.join(`
12274
12507
  `);
12275
- const starved = STARVED.flatMap((pattern) => [...joined.matchAll(pattern)].map((found) => found[0]));
12508
+ const starved = STARVED2.flatMap((pattern) => [...joined.matchAll(pattern)].map((found) => found[0]));
12276
12509
  const unloadable = scriptErrorsPrinted(lines).map((error) => `${error.path}${error.line === null ? "" : `:${String(error.line)}`}: ${error.message}`);
12277
12510
  return {
12278
12511
  lines,
@@ -12306,7 +12539,7 @@ function versionFault(said, wanted) {
12306
12539
  function readExpected(file) {
12307
12540
  const declared = (value) => typeof value === "object" && value !== null ? value : {};
12308
12541
  try {
12309
- const parsed = JSON.parse(readFileSync9(file, "utf8"));
12542
+ const parsed = JSON.parse(readFileSync10(file, "utf8"));
12310
12543
  const read = typeof parsed === "object" && parsed !== null ? parsed : {};
12311
12544
  return { errors: declared(read["errors"]), warnings: declared(read["warnings"]) };
12312
12545
  } catch {
@@ -12652,7 +12885,7 @@ ${String(error.warning)}`);
12652
12885
  const neverPrinted = [...owed.values()].flatMap((debt) => debt.texts.map((text) => ({ suite: debt.suite, during: debt.during, text, warning: debt.warning })));
12653
12886
  return { printed: unexpected, neverPrinted };
12654
12887
  }
12655
- var SUITE_OPENED, TIME, CASE_LINE, STATISTICS, ERROR_LINE, WARNING_LINE, EXECUTED, MISMATCHED_ROW, UNRUNNABLE_CASE, BANNER, LEFT_OUT, DRIVER_FALLBACK, MACHINE_WARNED, ORPHANS_REPORTED, STARVED, NESTED_ERROR = "While attempting to print an error, another error was printed:", WINDOWS_EXITS, MEMORY_EXITS, GDUNIT_FAILED = 100, GDUNIT_ORPHANS = 101, GDUNIT_EXITS, GODOT_EXITS, CRASH_HEADER, CRASH_BUILD, QUEUE_OVERFLOW_LINE = "Object was deleted while awaiting a callback.", QUEUE_OVERFLOW_FRAMES, TAIL = 60, MOST_NAMED = 8, COMPILED_SAID = "gdharness compiled every script together";
12888
+ var SUITE_OPENED, TIME, CASE_LINE, STATISTICS, ERROR_LINE, WARNING_LINE, EXECUTED, MISMATCHED_ROW, UNRUNNABLE_CASE, BANNER, LEFT_OUT, DRIVER_FALLBACK, MACHINE_WARNED, ORPHANS_REPORTED, STARVED2, NESTED_ERROR = "While attempting to print an error, another error was printed:", WINDOWS_EXITS, MEMORY_EXITS, GDUNIT_FAILED = 100, GDUNIT_ORPHANS = 101, GDUNIT_EXITS, GODOT_EXITS, CRASH_HEADER, CRASH_BUILD, QUEUE_OVERFLOW_LINE = "Object was deleted while awaiting a callback.", QUEUE_OVERFLOW_FRAMES, TAIL = 60, MOST_NAMED = 8, COMPILED_SAID = "gdharness compiled every script together";
12656
12889
  var init_suite_judge = __esm(() => {
12657
12890
  init_engine_leaks();
12658
12891
  init_junit();
@@ -12670,7 +12903,7 @@ var init_suite_judge = __esm(() => {
12670
12903
  DRIVER_FALLBACK = /^\s*WARNING: Your video card drivers .*, switching to /;
12671
12904
  MACHINE_WARNED = /^\s*at: .*\((?:\.\/)?(?:platform|drivers)\//;
12672
12905
  ORPHANS_REPORTED = /^\s*WARNING: Detected \d+ (?:possible )?orphan nodes\.\s*$/;
12673
- STARVED = [
12906
+ STARVED2 = [
12674
12907
  /Parameter "mem(?:_new)?" is null\.?\s*\n\s*at: \w+ \((?:\.\/)?core\/(?:os\/memory\.cpp|templates\/cowdata\.h):/g,
12675
12908
  /ERROR: Failed decoding WebP image\.\s*\n\s*at: _webp_unpack/g
12676
12909
  ];
@@ -12702,20 +12935,28 @@ var init_suite_judge = __esm(() => {
12702
12935
  // src/engine-launch.ts
12703
12936
  import {
12704
12937
  closeSync as closeSync4,
12705
- existsSync as existsSync6,
12938
+ existsSync as existsSync5,
12706
12939
  fstatSync as fstatSync2,
12707
12940
  openSync as openSync4,
12708
12941
  readdirSync as readdirSync4,
12942
+ readFileSync as readFileSync11,
12709
12943
  readSync as readSync2,
12710
12944
  rmSync as rmSync7,
12711
12945
  statSync as statSync6,
12712
12946
  writeFileSync as writeFileSync6,
12713
12947
  writeSync as writeSync4
12714
12948
  } from "node:fs";
12715
- import { availableParallelism as availableParallelism2, freemem, totalmem } from "node:os";
12949
+ import { availableParallelism as availableParallelism3, freemem, totalmem } from "node:os";
12716
12950
  import { isAbsolute as isAbsolute3, join as join9, relative as relative3, resolve as resolve5, sep as sep2 } from "node:path";
12717
12951
  import process7 from "node:process";
12718
12952
  import { setTimeout as delay3 } from "node:timers/promises";
12953
+ function countedBy(spent, past, at) {
12954
+ const clock = Math.round(spent.clock(at));
12955
+ if (past === "clock") {
12956
+ return `, ${String(STARVED)} times over on the clock (${String(clock)} s), the machine kept busy by other work`;
12957
+ }
12958
+ return spent.byWork === 0 ? "" : ` of its own, counted by its processor time while other work kept the machine busy (${String(clock)} s on the clock)`;
12959
+ }
12719
12960
  function plural(count, noun) {
12720
12961
  return `${String(count)} ${noun}${count === 1 ? "" : "s"}`;
12721
12962
  }
@@ -12734,14 +12975,14 @@ function godotNamed(value, cwd) {
12734
12975
  return null;
12735
12976
  }
12736
12977
  const path = resolve5(cwd, value);
12737
- if (!existsSync6(path) || statSync6(path).isDirectory()) {
12978
+ if (!existsSync5(path) || statSync6(path).isDirectory()) {
12738
12979
  throw new EngineUsageError(`--godot names ${path}, which is not an engine executable.`);
12739
12980
  }
12740
12981
  return path;
12741
12982
  }
12742
12983
  function coresNamed(value) {
12743
12984
  const cores = wholeFlag(value, "--cores");
12744
- const most = Math.min(availableParallelism2(), 64);
12985
+ const most = Math.min(availableParallelism3(), 64);
12745
12986
  if (cores !== null && cores > most) {
12746
12987
  throw new EngineUsageError(`--cores ${String(cores)} is more than the ${String(most)} processors a run can be held to here.`);
12747
12988
  }
@@ -12765,7 +13006,7 @@ function resPathOf(entry, projectPath, cwd) {
12765
13006
  const file = caseAt?.[1] ?? entry;
12766
13007
  const fromProject = resolve5(projectPath, file);
12767
13008
  const fromHere = resolve5(cwd, file);
12768
- const absolute = isAbsolute3(file) || !existsSync6(fromProject) && existsSync6(fromHere) ? fromHere : fromProject;
13009
+ const absolute = isAbsolute3(file) || !existsSync5(fromProject) && existsSync5(fromHere) ? fromHere : fromProject;
12769
13010
  const inside = relative3(projectPath, absolute);
12770
13011
  if (inside.startsWith("..") || isAbsolute3(inside)) {
12771
13012
  throw new EngineUsageError(`${entry} is not inside the project at ${projectPath}.`);
@@ -12777,7 +13018,7 @@ function homeOf(projectPath) {
12777
13018
  return mainCheckoutOf(projectPath) ?? projectPath;
12778
13019
  }
12779
13020
  function sweepRunDirectories(runs, kept = KEPT_RUN_DIRECTORIES) {
12780
- if (!existsSync6(runs)) {
13021
+ if (!existsSync5(runs)) {
12781
13022
  return;
12782
13023
  }
12783
13024
  const ended = [];
@@ -12807,6 +13048,9 @@ function roomFor(most, processors, available, said = "of memory free", processor
12807
13048
  held: byMemory < processors ? `the machine has ${gigabytes(Math.max(0, available))} ${said}, ${gigabytes(MEMORY_KEPT)} of it kept for everything else and ${gigabytes(MEMORY_PER_ENGINE)} taken for each engine` : processorsSaid
12808
13049
  };
12809
13050
  }
13051
+ function roomForAnother(available, starting) {
13052
+ return available - MEMORY_KEPT - starting * MEMORY_PER_ENGINE >= MEMORY_PER_ENGINE;
13053
+ }
12810
13054
  async function memoryAvailable() {
12811
13055
  const level = await macMemoryLevel();
12812
13056
  if (level !== null) {
@@ -12932,19 +13176,49 @@ async function watchedEngine(launch, lock, args, directory, log, silenceSeconds,
12932
13176
  exited.then(() => {
12933
13177
  finished.now = true;
12934
13178
  });
13179
+ const hasFinished = () => finished.now;
12935
13180
  const reading = openSync4(log, "r");
13181
+ const engineAt = () => {
13182
+ if (helper === null) {
13183
+ return child.pid;
13184
+ }
13185
+ try {
13186
+ const pid = Number(readFileSync11(helper.pid, "utf8").trim());
13187
+ return Number.isInteger(pid) && pid > 0 ? pid : undefined;
13188
+ } catch {
13189
+ return;
13190
+ }
13191
+ };
13192
+ let pid = engineAt();
13193
+ if (pid !== undefined) {
13194
+ watching(pid, true);
13195
+ }
12936
13196
  try {
12937
13197
  let size = 0;
12938
- const started = Date.now();
12939
- let lastGrowth = started;
13198
+ const sample = launch.sample ?? sampled;
13199
+ const first = await sample();
13200
+ const running = new Spent(nowSeconds(), first, pid);
13201
+ const quiet = new Spent(nowSeconds(), first, pid);
12940
13202
  let carried = "";
12941
13203
  let allowed = { size: -1, seconds: silenceSeconds };
12942
13204
  while (!finished.now) {
12943
13205
  await delay3(LOOK_MS);
13206
+ if (pid === undefined) {
13207
+ pid = engineAt();
13208
+ if (pid !== undefined) {
13209
+ watching(pid, true);
13210
+ }
13211
+ }
13212
+ const taken = await sample();
13213
+ running.counted(taken, pid);
13214
+ quiet.counted(taken, pid);
13215
+ const at = nowSeconds();
13216
+ const gone = hasFinished() || pid !== undefined && !alive(pid);
12944
13217
  const deadline = launch.deadlineSeconds;
12945
- if (ended === null && deadline !== null && Date.now() - started > deadline * 1000) {
13218
+ const over = deadline === null || gone ? null : running.past(deadline, at);
13219
+ if (ended === null && deadline !== null && over !== null) {
12946
13220
  const stoppedIn = readRun(readLog(log)).stoppedIn;
12947
- ended = `the engine ran past the ${String(deadline)} s deadline${stoppedIn === null ? "" : ` while running ${stoppedIn}`}, so it was ended`;
13221
+ ended = `the engine ran past the ${String(deadline)} s deadline${countedBy(running, over, at)}${stoppedIn === null ? "" : ` while running ${stoppedIn}`}, so it was ended`;
12948
13222
  stop();
12949
13223
  }
12950
13224
  const now = fstatSync2(reading).size;
@@ -12964,24 +13238,25 @@ async function watchedEngine(launch, lock, args, directory, log, silenceSeconds,
12964
13238
  at += got;
12965
13239
  }
12966
13240
  size = now;
12967
- lastGrowth = Date.now();
13241
+ quiet.restart(at);
12968
13242
  if (ended === null && size > LOG_LIMIT_BYTES) {
12969
13243
  ended = `the engine printed more than the ${String(LOG_LIMIT_BYTES / 1024 / 1024)} MiB a log may hold, so it was ended`;
12970
13244
  stop();
12971
13245
  }
12972
- } else if (ended === null && Date.now() - lastGrowth > silenceSeconds * 1000) {
13246
+ } else if (ended === null && !gone && quiet.past(silenceSeconds, at) !== null) {
12973
13247
  if (allowed.size !== size) {
12974
13248
  allowed = {
12975
13249
  size,
12976
13250
  seconds: silenceIn === undefined ? silenceSeconds : Math.max(silenceSeconds, silenceIn(readRun(readLog(log))))
12977
13251
  };
12978
13252
  }
12979
- if (Date.now() - lastGrowth <= allowed.seconds * 1000) {
13253
+ const silent = quiet.past(allowed.seconds, at);
13254
+ if (silent === null) {
12980
13255
  continue;
12981
13256
  }
12982
13257
  const { stoppedIn } = readRun(readLog(log));
12983
- const unstarted = helper !== null && !existsSync6(helper.pid) ? `, and it never started ${helper.how}: ${helperStep(helper.step)}` : "";
12984
- ended = `the engine printed nothing for ${String(allowed.seconds)} s${stoppedIn === null ? "" : ` while running ${stoppedIn}`}, so it was ended${unstarted}`;
13258
+ const unstarted = helper !== null && !existsSync5(helper.pid) ? `, and it never started ${helper.how}: ${helperStep(helper.step)}` : "";
13259
+ ended = `the engine printed nothing for ${String(allowed.seconds)} s${countedBy(quiet, silent, at)}${stoppedIn === null ? "" : ` while running ${stoppedIn}`}, so it was ended${unstarted}`;
12985
13260
  stop();
12986
13261
  }
12987
13262
  }
@@ -12991,6 +13266,9 @@ async function watchedEngine(launch, lock, args, directory, log, silenceSeconds,
12991
13266
  } finally {
12992
13267
  closeSync4(reading);
12993
13268
  launch.running.delete(stop);
13269
+ if (pid !== undefined) {
13270
+ watching(pid, false);
13271
+ }
12994
13272
  }
12995
13273
  const { code, signal } = await exited;
12996
13274
  const freeBytes = code !== null && code >>> 0 >= WINDOWS_EXCEPTION ? freemem() : undefined;
@@ -13047,6 +13325,7 @@ var init_engine_launch = __esm(() => {
13047
13325
  init_alive();
13048
13326
  init_desktop();
13049
13327
  init_engine_slots();
13328
+ init_engine_spent();
13050
13329
  init_errors();
13051
13330
  init_harnesses();
13052
13331
  init_log_read();
@@ -13062,7 +13341,7 @@ var init_engine_launch = __esm(() => {
13062
13341
  });
13063
13342
 
13064
13343
  // src/detection.ts
13065
- import { existsSync as existsSync7, readdirSync as readdirSync5, statSync as statSync7 } from "node:fs";
13344
+ import { existsSync as existsSync6, readdirSync as readdirSync5, statSync as statSync7 } from "node:fs";
13066
13345
  import { homedir as homedir4 } from "node:os";
13067
13346
  import { join as join10 } from "node:path";
13068
13347
  function resolveHomeDirectory() {
@@ -13073,7 +13352,7 @@ function resolveHomeDirectory() {
13073
13352
  }
13074
13353
  }
13075
13354
  function scanDirectoryForGodotBinaries(directory, platform) {
13076
- if (!directory || !existsSync7(directory)) {
13355
+ if (!directory || !existsSync6(directory)) {
13077
13356
  return [];
13078
13357
  }
13079
13358
  let entries;
@@ -13341,7 +13620,7 @@ var init_launch = __esm(() => {
13341
13620
  });
13342
13621
 
13343
13622
  // src/godot-path.ts
13344
- import { existsSync as existsSync8 } from "node:fs";
13623
+ import { existsSync as existsSync7 } from "node:fs";
13345
13624
  import { isAbsolute as isAbsolute4, normalize, resolve as resolve6 } from "node:path";
13346
13625
  function versionStartOf(error) {
13347
13626
  const failed = error ?? {};
@@ -13419,7 +13698,7 @@ class GodotLocator {
13419
13698
  if (known !== undefined) {
13420
13699
  return known;
13421
13700
  }
13422
- if (path !== "godot" && !existsSync8(path)) {
13701
+ if (path !== "godot" && !existsSync7(path)) {
13423
13702
  this.answered.set(path, "does not exist");
13424
13703
  return "does not exist";
13425
13704
  }
@@ -13801,7 +14080,7 @@ var init_engine_run = __esm(() => {
13801
14080
  });
13802
14081
 
13803
14082
  // src/doc-cache.ts
13804
- import { existsSync as existsSync9, readdirSync as readdirSync6, statSync as statSync8 } from "node:fs";
14083
+ import { existsSync as existsSync8, readdirSync as readdirSync6, statSync as statSync8 } from "node:fs";
13805
14084
  import { homedir as homedir5 } from "node:os";
13806
14085
  import { basename as basename5, dirname as dirname5, isAbsolute as isAbsolute5, join as join11 } from "node:path";
13807
14086
  import process9 from "node:process";
@@ -13809,7 +14088,7 @@ function editorCacheDirectory(godotPath, platform = process9.platform, env = pro
13809
14088
  const beside = dirname5(godotPath);
13810
14089
  const bundle = platform === "darwin" && basename5(beside) === "MacOS" && basename5(dirname5(beside)) === "Contents" ? dirname5(dirname5(dirname5(beside))) : null;
13811
14090
  for (const folder of bundle === null ? [beside] : [beside, bundle]) {
13812
- if (existsSync9(join11(folder, "._sc_")) || existsSync9(join11(folder, "_sc_"))) {
14091
+ if (existsSync8(join11(folder, "._sc_")) || existsSync8(join11(folder, "_sc_"))) {
13813
14092
  return join11(folder, "editor_data", "cache");
13814
14093
  }
13815
14094
  }
@@ -13857,7 +14136,7 @@ var init_doc_cache = __esm(() => {
13857
14136
  });
13858
14137
 
13859
14138
  // src/project-scan.ts
13860
- import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync10, statSync as statSync9 } from "node:fs";
14139
+ import { existsSync as existsSync9, readdirSync as readdirSync7, readFileSync as readFileSync12, statSync as statSync9 } from "node:fs";
13861
14140
  import { basename as basename6, extname as extname2, join as join12, relative as relative4, sep as sep3 } from "node:path";
13862
14141
  function skipped(name) {
13863
14142
  return name.startsWith(".") || SKIPPED.has(name);
@@ -13866,10 +14145,10 @@ function steppedOver(directory) {
13866
14145
  return gdignored(directory) || installedByUs(directory);
13867
14146
  }
13868
14147
  function gdignored(directory) {
13869
- return existsSync10(join12(directory, ".gdignore"));
14148
+ return existsSync9(join12(directory, ".gdignore"));
13870
14149
  }
13871
14150
  function installedByUs(directory) {
13872
- return existsSync10(join12(directory, VERSION_MARKER));
14151
+ return existsSync9(join12(directory, VERSION_MARKER));
13873
14152
  }
13874
14153
  function projectStructure(projectPath) {
13875
14154
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
@@ -13982,7 +14261,7 @@ function scriptsWithoutUid(projectPath) {
13982
14261
  continue;
13983
14262
  }
13984
14263
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
13985
- if (CARRIES_A_UID.has(extension) && !existsSync10(join12(directory, `${entry.name}.uid`))) {
14264
+ if (CARRIES_A_UID.has(extension) && !existsSync9(join12(directory, `${entry.name}.uid`))) {
13986
14265
  found.push(spelled);
13987
14266
  }
13988
14267
  }
@@ -13993,7 +14272,7 @@ function scriptsWithoutUid(projectPath) {
13993
14272
  function pathsInUidCache(projectPath) {
13994
14273
  let data;
13995
14274
  try {
13996
- data = readFileSync10(join12(projectPath, ".godot", "uid_cache.bin"));
14275
+ data = readFileSync12(join12(projectPath, ".godot", "uid_cache.bin"));
13997
14276
  } catch {
13998
14277
  return null;
13999
14278
  }
@@ -14055,7 +14334,7 @@ function projectTexts(projectPath) {
14055
14334
  if (!entry.isFile() || BINARY_EXTENSIONS.has(extension)) {
14056
14335
  continue;
14057
14336
  }
14058
- const bytes = readFileSync10(absolute);
14337
+ const bytes = readFileSync12(absolute);
14059
14338
  if (!isBinary(bytes)) {
14060
14339
  found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
14061
14340
  }
@@ -14085,7 +14364,7 @@ function ignoredTexts(projectPath) {
14085
14364
  if (!under || !entry.isFile() || BINARY_EXTENSIONS.has(extension) || entry.name === ".gdignore") {
14086
14365
  continue;
14087
14366
  }
14088
- const bytes = readFileSync10(absolute);
14367
+ const bytes = readFileSync12(absolute);
14089
14368
  if (!isBinary(bytes)) {
14090
14369
  found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
14091
14370
  }
@@ -14133,7 +14412,7 @@ function searchProject(projectPath, options) {
14133
14412
  if (extensions !== null && !extensions.has(extension)) {
14134
14413
  return;
14135
14414
  }
14136
- const bytes = readFileSync10(entryPath);
14415
+ const bytes = readFileSync12(entryPath);
14137
14416
  if (extensions === null && isBinary(bytes)) {
14138
14417
  return;
14139
14418
  }
@@ -14249,7 +14528,7 @@ var init_project_scan = __esm(() => {
14249
14528
 
14250
14529
  // src/import-freshness.ts
14251
14530
  import { createHash as createHash2 } from "node:crypto";
14252
- import { existsSync as existsSync11, readdirSync as readdirSync8, readFileSync as readFileSync11, statSync as statSync10 } from "node:fs";
14531
+ import { existsSync as existsSync10, readdirSync as readdirSync8, readFileSync as readFileSync13, statSync as statSync10 } from "node:fs";
14253
14532
  import { join as join13 } from "node:path";
14254
14533
  function md5Of(file) {
14255
14534
  let stats;
@@ -14264,7 +14543,7 @@ function md5Of(file) {
14264
14543
  }
14265
14544
  let md5;
14266
14545
  try {
14267
- md5 = createHash2("md5").update(readFileSync11(file)).digest("hex");
14546
+ md5 = createHash2("md5").update(readFileSync13(file)).digest("hex");
14268
14547
  } catch {
14269
14548
  return null;
14270
14549
  }
@@ -14298,7 +14577,7 @@ function importedIntoTheProject(projectPath, resPaths) {
14298
14577
  for (const resPath of resPaths) {
14299
14578
  let sidecar;
14300
14579
  try {
14301
- sidecar = readFileSync11(`${onDisk(projectPath, resPath)}.import`, "utf8");
14580
+ sidecar = readFileSync13(`${onDisk(projectPath, resPath)}.import`, "utf8");
14302
14581
  } catch {
14303
14582
  continue;
14304
14583
  }
@@ -14331,7 +14610,7 @@ function importedSidecarMd5s(projectPath) {
14331
14610
  }
14332
14611
  let text;
14333
14612
  try {
14334
- text = readFileSync11(join13(editor, `filesystem_cache${newest}`), "utf8");
14613
+ text = readFileSync13(join13(editor, `filesystem_cache${newest}`), "utf8");
14335
14614
  } catch {
14336
14615
  return found;
14337
14616
  }
@@ -14375,7 +14654,7 @@ function behind(projectPath, resPath, sidecarFile, sidecar, importedFrom) {
14375
14654
  return "its import file names no imported output, as one written before the engine imports from it does";
14376
14655
  }
14377
14656
  }
14378
- if (outputs.some((output) => !existsSync11(onDisk(projectPath, output)))) {
14657
+ if (outputs.some((output) => !existsSync10(onDisk(projectPath, output)))) {
14379
14658
  return "its imported output is not on disk";
14380
14659
  }
14381
14660
  const source = unquoted(deps.get("source_file"));
@@ -14386,7 +14665,7 @@ function behind(projectPath, resPath, sidecarFile, sidecar, importedFrom) {
14386
14665
  const record = join13(projectPath, ".godot", "imported", `${name}-${createHash2("md5").update(resPath, "utf8").digest("hex")}.md5`);
14387
14666
  let recorded = "";
14388
14667
  try {
14389
- recorded = /^source_md5="([0-9a-f]*)"/m.exec(readFileSync11(record, "utf8"))?.[1] ?? "";
14668
+ recorded = /^source_md5="([0-9a-f]*)"/m.exec(readFileSync13(record, "utf8"))?.[1] ?? "";
14390
14669
  } catch {
14391
14670
  recorded = "";
14392
14671
  }
@@ -14400,7 +14679,7 @@ function staleImports(projectPath) {
14400
14679
  const stale = [];
14401
14680
  const importedFrom = importedSidecarMd5s(projectPath);
14402
14681
  const visit = (directory, resDirectory) => {
14403
- if (existsSync11(join13(directory, ".gdignore")) || existsSync11(join13(directory, VERSION_MARKER))) {
14682
+ if (existsSync10(join13(directory, ".gdignore")) || existsSync10(join13(directory, VERSION_MARKER))) {
14404
14683
  return;
14405
14684
  }
14406
14685
  let entries;
@@ -14430,7 +14709,7 @@ function staleImports(projectPath) {
14430
14709
  const sidecarFile = join13(directory, entry.name);
14431
14710
  let sidecar;
14432
14711
  try {
14433
- sidecar = readFileSync11(sidecarFile, "utf8");
14712
+ sidecar = readFileSync13(sidecarFile, "utf8");
14434
14713
  } catch {
14435
14714
  continue;
14436
14715
  }
@@ -14491,10 +14770,10 @@ var init_import_freshness = __esm(() => {
14491
14770
 
14492
14771
  // src/library-copies.ts
14493
14772
  import {
14494
- existsSync as existsSync12,
14773
+ existsSync as existsSync11,
14495
14774
  mkdirSync as mkdirSync4,
14496
14775
  readdirSync as readdirSync9,
14497
- readFileSync as readFileSync12,
14776
+ readFileSync as readFileSync14,
14498
14777
  renameSync as renameSync3,
14499
14778
  unlinkSync,
14500
14779
  writeFileSync as writeFileSync7
@@ -14509,7 +14788,7 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
14509
14788
  for (const library of windowsLibraries(projectPath)) {
14510
14789
  const folder = dirname6(library);
14511
14790
  const copy = `~${basename7(library)}`;
14512
- if (existsSync12(join14(folder, copy))) {
14791
+ if (existsSync11(join14(folder, copy))) {
14513
14792
  const aside = join14(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
14514
14793
  try {
14515
14794
  renameSync3(join14(folder, copy), aside);
@@ -14535,12 +14814,26 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
14535
14814
  }
14536
14815
  function seedExtensionList(projectPath) {
14537
14816
  const list = join14(projectPath, ".godot", "extension_list.cfg");
14538
- if (existsSync12(list)) {
14817
+ if (existsSync11(list)) {
14539
14818
  return null;
14540
14819
  }
14820
+ const found = unlistedExtensions(projectPath);
14821
+ if (found.length === 0) {
14822
+ return [];
14823
+ }
14824
+ mkdirSync4(dirname6(list), { recursive: true });
14825
+ writeFileSync7(list, `${found.join(`
14826
+ `)}
14827
+ `);
14828
+ return found;
14829
+ }
14830
+ function unlistedExtensions(projectPath) {
14831
+ if (existsSync11(join14(projectPath, ".godot", "extension_list.cfg"))) {
14832
+ return [];
14833
+ }
14541
14834
  const found = [];
14542
14835
  const visit = (directory, resDirectory) => {
14543
- if (existsSync12(join14(directory, ".gdignore"))) {
14836
+ if (existsSync11(join14(directory, ".gdignore"))) {
14544
14837
  return;
14545
14838
  }
14546
14839
  let entries;
@@ -14561,15 +14854,7 @@ function seedExtensionList(projectPath) {
14561
14854
  }
14562
14855
  };
14563
14856
  visit(projectPath, "res://");
14564
- if (found.length === 0) {
14565
- return [];
14566
- }
14567
- found.sort();
14568
- mkdirSync4(dirname6(list), { recursive: true });
14569
- writeFileSync7(list, `${found.join(`
14570
- `)}
14571
- `);
14572
- return found;
14857
+ return found.sort();
14573
14858
  }
14574
14859
  function windowsLibraries(projectPath) {
14575
14860
  const listed = readOrNull(join14(projectPath, ".godot", "extension_list.cfg"));
@@ -14610,7 +14895,7 @@ function resolveResource(projectPath, relativeTo, path) {
14610
14895
  }
14611
14896
  function readOrNull(path) {
14612
14897
  try {
14613
- return readFileSync12(path, "utf8");
14898
+ return readFileSync14(path, "utf8");
14614
14899
  } catch {
14615
14900
  return null;
14616
14901
  }
@@ -14619,16 +14904,16 @@ var init_library_copies = () => {};
14619
14904
 
14620
14905
  // src/reimport.ts
14621
14906
  import { createHash as createHash3 } from "node:crypto";
14622
- import { existsSync as existsSync13, readFileSync as readFileSync13, rmSync as rmSync8, statSync as statSync11, utimesSync, writeFileSync as writeFileSync8 } from "node:fs";
14907
+ import { existsSync as existsSync12, readFileSync as readFileSync15, rmSync as rmSync8, statSync as statSync11, utimesSync, writeFileSync as writeFileSync8 } from "node:fs";
14623
14908
  import { basename as basename8, join as join15 } from "node:path";
14624
14909
  function forceNextImport(projectPath, resourcePath) {
14625
14910
  const relative = resourcePath.replace(/^res:\/\//, "");
14626
14911
  const sidecar = join15(projectPath, `${relative}.import`);
14627
- if (!existsSync13(sidecar)) {
14912
+ if (!existsSync12(sidecar)) {
14628
14913
  return;
14629
14914
  }
14630
14915
  const { atime, mtimeMs } = statSync11(sidecar);
14631
- writeFileSync8(sidecar, readFileSync13(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
14916
+ writeFileSync8(sidecar, readFileSync15(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
14632
14917
  utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
14633
14918
  rmSync8(importRecord(projectPath, resourcePath), { force: true });
14634
14919
  }
@@ -14639,7 +14924,7 @@ function importRecord(projectPath, resourcePath) {
14639
14924
  var init_reimport = () => {};
14640
14925
 
14641
14926
  // src/scratch.ts
14642
- import { existsSync as existsSync14, mkdtempSync as mkdtempSync2, readdirSync as readdirSync10, rmSync as rmSync9 } from "node:fs";
14927
+ import { existsSync as existsSync13, mkdtempSync as mkdtempSync2, readdirSync as readdirSync10, rmSync as rmSync9 } from "node:fs";
14643
14928
  import { tmpdir as tmpdir5 } from "node:os";
14644
14929
  import { join as join16 } from "node:path";
14645
14930
  import process10 from "node:process";
@@ -14698,7 +14983,7 @@ function tryAgain(path, attempt) {
14698
14983
  lastTryOnExit();
14699
14984
  }
14700
14985
  const after = AGAIN_AFTER_MS[attempt];
14701
- if (after === undefined || !existsSync14(path)) {
14986
+ if (after === undefined || !existsSync13(path)) {
14702
14987
  waiting.delete(path);
14703
14988
  if (after === undefined) {
14704
14989
  atExit.add(path);
@@ -14854,7 +15139,7 @@ var init_script_origins = __esm(() => {
14854
15139
  });
14855
15140
 
14856
15141
  // src/headless.ts
14857
- import { existsSync as existsSync15, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "node:fs";
15142
+ import { existsSync as existsSync14, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "node:fs";
14858
15143
  import { dirname as dirname7, join as join18 } from "node:path";
14859
15144
  function snakeCased(params) {
14860
15145
  const result = emptyRecord();
@@ -15143,10 +15428,10 @@ async function operationOutcome(engine, operation, params, projectPath) {
15143
15428
  };
15144
15429
  }
15145
15430
  function answerIn(file) {
15146
- if (!existsSync15(file)) {
15431
+ if (!existsSync14(file)) {
15147
15432
  return { kind: "none" };
15148
15433
  }
15149
- const text = readFileSync14(file, "utf8");
15434
+ const text = readFileSync16(file, "utf8");
15150
15435
  try {
15151
15436
  const parsed = JSON.parse(text);
15152
15437
  return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? { kind: "answer", payload: parsed } : { kind: "unreadable", why: `it is JSON but not an object: ${text.slice(0, 200)}` };
@@ -15172,7 +15457,7 @@ var init_headless = __esm(() => {
15172
15457
  });
15173
15458
 
15174
15459
  // src/update-check.ts
15175
- import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "node:fs";
15460
+ import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "node:fs";
15176
15461
  import { homedir as homedir6, tmpdir as tmpdir6 } from "node:os";
15177
15462
  import { join as join19 } from "node:path";
15178
15463
  function packageUrl(root) {
@@ -15214,10 +15499,10 @@ function cacheFile(environment = process.env) {
15214
15499
  }
15215
15500
  function readCache(path) {
15216
15501
  try {
15217
- if (!existsSync16(path)) {
15502
+ if (!existsSync15(path)) {
15218
15503
  return null;
15219
15504
  }
15220
- const parsed = JSON.parse(readFileSync15(path, "utf8"));
15505
+ const parsed = JSON.parse(readFileSync17(path, "utf8"));
15221
15506
  if (typeof parsed !== "object" || parsed === null) {
15222
15507
  return null;
15223
15508
  }
@@ -15365,7 +15650,7 @@ var init_update_check = __esm(() => {
15365
15650
  });
15366
15651
 
15367
15652
  // src/server-version.ts
15368
- import { readFileSync as readFileSync16 } from "node:fs";
15653
+ import { readFileSync as readFileSync18 } from "node:fs";
15369
15654
  function staleAddon(addonDigest, shippedDigest) {
15370
15655
  return addonDigest !== shippedDigest;
15371
15656
  }
@@ -15424,7 +15709,7 @@ var init_server_version = __esm(() => {
15424
15709
  DEBUG_MODE = process.env["GDHARNESS_DEBUG"] === "1";
15425
15710
  SERVER_VERSION = (() => {
15426
15711
  try {
15427
- const pkg = JSON.parse(readFileSync16(new URL("../package.json", import.meta.url), "utf8"));
15712
+ const pkg = JSON.parse(readFileSync18(new URL("../package.json", import.meta.url), "utf8"));
15428
15713
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
15429
15714
  } catch {
15430
15715
  return "0.0.0";
@@ -15694,7 +15979,7 @@ var init_ports = __esm(() => {
15694
15979
  });
15695
15980
 
15696
15981
  // src/lsp_client.ts
15697
- import { existsSync as existsSync17, realpathSync as realpathSync3 } from "node:fs";
15982
+ import { existsSync as existsSync16, realpathSync as realpathSync3 } from "node:fs";
15698
15983
  import { readFile, realpath } from "node:fs/promises";
15699
15984
  import { createConnection } from "node:net";
15700
15985
  import { dirname as dirname8, extname as extname3, join as join20, resolve as resolve7 } from "node:path";
@@ -16375,7 +16660,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
16375
16660
  } catch {
16376
16661
  throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
16377
16662
  }
16378
- if (!existsSync17(join20(projectPath, "project.godot"))) {
16663
+ if (!existsSync16(join20(projectPath, "project.godot"))) {
16379
16664
  throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
16380
16665
  }
16381
16666
  const contained = resolveWithinProject(projectPath, scriptPathValue);
@@ -16540,7 +16825,7 @@ var init_lsp_client = __esm(() => {
16540
16825
  });
16541
16826
 
16542
16827
  // src/resources.ts
16543
- import { readFileSync as readFileSync18, realpathSync as realpathSync4 } from "node:fs";
16828
+ import { readFileSync as readFileSync20, realpathSync as realpathSync4 } from "node:fs";
16544
16829
  import { extname as extname4, resolve as resolve9 } from "node:path";
16545
16830
  function ensureProjectPath(getProjectPath) {
16546
16831
  const projectPath = getProjectPath();
@@ -16736,7 +17021,7 @@ function readResourceText(uri, getProjectPath) {
16736
17021
  const parsedUri = parseGodotUri(uri);
16737
17022
  if (parsedUri.kind === "project-info") {
16738
17023
  const projectFilePath = resolveProjectFile(projectPath, "project.godot");
16739
- const rawProject = readFileSync18(projectFilePath, "utf-8");
17024
+ const rawProject = readFileSync20(projectFilePath, "utf-8");
16740
17025
  const parsedProject = parseProjectGodot(rawProject);
16741
17026
  return {
16742
17027
  mimeType: "application/json",
@@ -16745,7 +17030,7 @@ function readResourceText(uri, getProjectPath) {
16745
17030
  }
16746
17031
  const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
16747
17032
  ensureAllowedExtension(parsedUri.kind, filePath);
16748
- const text = readFileSync18(filePath, "utf-8");
17033
+ const text = readFileSync20(filePath, "utf-8");
16749
17034
  return {
16750
17035
  mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
16751
17036
  text
@@ -16889,10 +17174,10 @@ function readParams(params, key) {
16889
17174
  import { createHash as createHash5 } from "node:crypto";
16890
17175
  import {
16891
17176
  cpSync,
16892
- existsSync as existsSync19,
17177
+ existsSync as existsSync18,
16893
17178
  mkdirSync as mkdirSync6,
16894
17179
  readdirSync as readdirSync12,
16895
- readFileSync as readFileSync19,
17180
+ readFileSync as readFileSync21,
16896
17181
  rmdirSync,
16897
17182
  rmSync as rmSync10,
16898
17183
  statSync as statSync12,
@@ -16926,7 +17211,7 @@ function addonsDigest(names, from) {
16926
17211
  } else {
16927
17212
  hash.update(`${inside}
16928
17213
  `);
16929
- hash.update(readFileSync19(path));
17214
+ hash.update(readFileSync21(path));
16930
17215
  hash.update(`
16931
17216
  `);
16932
17217
  }
@@ -16972,10 +17257,10 @@ function installedRuntimeDigest(projectPath) {
16972
17257
  }
16973
17258
  function digestMarker(projectPath, addon) {
16974
17259
  const marker = join22(projectPath, "addons", addon, DIGEST_MARKER);
16975
- if (!existsSync19(marker)) {
17260
+ if (!existsSync18(marker)) {
16976
17261
  return null;
16977
17262
  }
16978
- const said = readFileSync19(marker, "utf8").trim();
17263
+ const said = readFileSync21(marker, "utf8").trim();
16979
17264
  return said === "" ? null : said;
16980
17265
  }
16981
17266
  function shippedAddonsDirectory() {
@@ -16989,10 +17274,10 @@ function shippedLoader() {
16989
17274
  }
16990
17275
  function loaderState(projectPath) {
16991
17276
  const copy = join22(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
16992
- if (!existsSync19(copy)) {
17277
+ if (!existsSync18(copy)) {
16993
17278
  return "missing";
16994
17279
  }
16995
- const lines = (path) => readFileSync19(path, "utf8").replaceAll(`\r
17280
+ const lines = (path) => readFileSync21(path, "utf8").replaceAll(`\r
16996
17281
  `, `
16997
17282
  `);
16998
17283
  return lines(copy) === lines(shippedLoader()) ? "current" : "differs";
@@ -17003,12 +17288,12 @@ function writeLoader(projectPath) {
17003
17288
  }
17004
17289
  const copy = join22(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
17005
17290
  mkdirSync6(dirname9(copy), { recursive: true });
17006
- writeFileSync11(copy, readFileSync19(shippedLoader()));
17291
+ writeFileSync11(copy, readFileSync21(shippedLoader()));
17007
17292
  return true;
17008
17293
  }
17009
17294
  function removeLoader(projectPath) {
17010
17295
  const copy = join22(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
17011
- if (!existsSync19(copy)) {
17296
+ if (!existsSync18(copy)) {
17012
17297
  return false;
17013
17298
  }
17014
17299
  rmSync10(copy);
@@ -17024,10 +17309,10 @@ function shippedListedRunner() {
17024
17309
  }
17025
17310
  function gdUnit4Unusable(projectPath) {
17026
17311
  const marker = "addons/gdUnit4/plugin.cfg";
17027
- if (!existsSync19(join22(projectPath, ...marker.split("/")))) {
17312
+ if (!existsSync18(join22(projectPath, ...marker.split("/")))) {
17028
17313
  return { missing: true, reason: `gdUnit4 is not installed in this project: no ${marker}` };
17029
17314
  }
17030
- if (!existsSync19(join22(projectPath, ...GDUNIT4_CI_RUNNER.split("/")))) {
17315
+ if (!existsSync18(join22(projectPath, ...GDUNIT4_CI_RUNNER.split("/")))) {
17031
17316
  return {
17032
17317
  missing: false,
17033
17318
  reason: `gdUnit4 in this project is older than 6.0, which the shipped runner needs: no ${GDUNIT4_CI_RUNNER}`
@@ -17039,11 +17324,11 @@ function installAddons(projectPath, from = shippedAddonsDirectory()) {
17039
17324
  const installed = [];
17040
17325
  for (const name of ADDONS) {
17041
17326
  const source = join22(from, name);
17042
- if (!existsSync19(join22(source, addonMarking(name)))) {
17327
+ if (!existsSync18(join22(source, addonMarking(name)))) {
17043
17328
  throw new Error(`The package holds no ${name} addon at ${source}.`);
17044
17329
  }
17045
17330
  const target = join22(projectPath, "addons", name);
17046
- const replaced = existsSync19(target);
17331
+ const replaced = existsSync18(target);
17047
17332
  rmSync10(target, { recursive: true, force: true });
17048
17333
  mkdirSync6(dirname9(target), { recursive: true });
17049
17334
  cpSync(source, target, { recursive: true });
@@ -17059,23 +17344,23 @@ function installAddons(projectPath, from = shippedAddonsDirectory()) {
17059
17344
  }
17060
17345
  function installedAddonVersion(projectPath) {
17061
17346
  const marker = join22(projectPath, "addons", ADDONS[0], VERSION_MARKER);
17062
- if (!existsSync19(marker)) {
17347
+ if (!existsSync18(marker)) {
17063
17348
  return null;
17064
17349
  }
17065
- const said = readFileSync19(marker, "utf8").trim();
17350
+ const said = readFileSync21(marker, "utf8").trim();
17066
17351
  return said === "" ? null : said;
17067
17352
  }
17068
17353
  function removeAddons(projectPath) {
17069
17354
  const removed = [];
17070
17355
  for (const name of ADDONS) {
17071
17356
  const target = join22(projectPath, "addons", name);
17072
- if (existsSync19(target)) {
17357
+ if (existsSync18(target)) {
17073
17358
  rmSync10(target, { recursive: true, force: true });
17074
17359
  removed.push(target);
17075
17360
  }
17076
17361
  }
17077
17362
  const addons = join22(projectPath, "addons");
17078
- if (removed.length > 0 && existsSync19(addons) && readdirSync12(addons).length === 0) {
17363
+ if (removed.length > 0 && existsSync18(addons) && readdirSync12(addons).length === 0) {
17079
17364
  rmdirSync(addons);
17080
17365
  removed.push(addons);
17081
17366
  }
@@ -17117,7 +17402,7 @@ function loaderAmong(projectPath, named) {
17117
17402
  }
17118
17403
  let source = null;
17119
17404
  try {
17120
- source = readFileSync19(join22(projectPath, path), "utf8");
17405
+ source = readFileSync21(join22(projectPath, path), "utf8");
17121
17406
  } catch {}
17122
17407
  if (source?.includes(RUNTIME_SCRIPT)) {
17123
17408
  return { name: entry, path, exists: true };
@@ -17175,8 +17460,8 @@ function inspectProject(projectPath) {
17175
17460
  const addons = ADDONS.map((name) => {
17176
17461
  const directory = join22(projectPath, "addons", name);
17177
17462
  const marker = join22(directory, VERSION_MARKER);
17178
- const installed = existsSync19(directory);
17179
- const version = existsSync19(marker) ? readFileSync19(marker, "utf8").trim() : null;
17463
+ const installed = existsSync18(directory);
17464
+ const version = existsSync18(marker) ? readFileSync21(marker, "utf8").trim() : null;
17180
17465
  const current = version === SERVER_VERSION;
17181
17466
  if (!installed) {
17182
17467
  problems.push(`addons/${name} is not installed; run gdharness setup`);
@@ -17185,7 +17470,7 @@ function inspectProject(projectPath) {
17185
17470
  }
17186
17471
  return { name, installed, version, current };
17187
17472
  });
17188
- const settings = parseProjectGodot(readFileSync19(join22(projectPath, "project.godot"), "utf8"));
17473
+ const settings = parseProjectGodot(readFileSync21(join22(projectPath, "project.godot"), "utf8"));
17189
17474
  const pluginsEnabled = enabledPlugins(settings);
17190
17475
  for (const name of EDITOR_PLUGINS) {
17191
17476
  if (!pluginsEnabled.includes(name)) {
@@ -17209,7 +17494,7 @@ function inspectProject(projectPath) {
17209
17494
  const ignoredAutoloads = [];
17210
17495
  const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
17211
17496
  for (const [name, path] of named) {
17212
- if (!existsSync19(join22(projectPath, path))) {
17497
+ if (!existsSync18(join22(projectPath, path))) {
17213
17498
  if (!addonMissing(path)) {
17214
17499
  problems.push(`the ${name} autoload names res://${path}, which is not in this project, so it boots with a missing script; restore that file, or point the autoload at one that is here`);
17215
17500
  }
@@ -17905,7 +18190,7 @@ var init_tool_definitions = __esm(() => {
17905
18190
  {
17906
18191
  name: "project_export",
17907
18192
  title: "Export",
17908
- description: "Export presets and exports. add_preset writes a preset to export_presets.cfg with the keys the editor's Project > Export writes for a new one and every option a new preset of its platform shows, each at its default, and makes it the platform's runnable preset when the platform has none. set_preset changes a preset's export path, its resource filter and the files it lists, its include and exclude filters, its custom features and its platform's options, and answers under changed what each held before and holds now, and under unchanged what already held the value given, with saved false when that is all of it, since the file is then not written; nothing is written when any of it is refused. The option names are the exporter's own, as the Export dialog's Options tab shows them by their paths, such as binary_format/embed_pck, and a name the platform does not have is refused with the nearest it has, or all of them when none is near. An option the dialog shows only while another is on, such as codesign/identity behind codesign/enable, is taken when the preset has that one on or the same call turns it on, and otherwise refused naming what shows it. A secret option, such as a signing password, is written to .godot/export_credentials.cfg, where the editor keeps secrets out of the committed file, and its value is never answered. A file an option names that is not there is written and listed under missing_files, or for a secret its option under secretsNamingNoFile. remove_preset takes a preset out, and when it was its platform's runnable preset the next of that platform becomes it, under runnableNow, which is null when the platform has no other; a secret it held is taken out with it. It answers where this engine's export templates belong and whether the preset's platform has them, since an export needs them; list says the same for every preset. An editor open on the project does not see a preset added this way until it restarts. run answers bytes, the size of the output it wrote, and names, as alsoWritten, what else the export wrote beside its output, such as the .pck a Windows or Linux build cannot start without. Its entries are the errors and warnings the export printed, the newest two hundred distinct ones, one printed more than once carrying times, and errors and warnings count them; exported is true only when this run wrote the output and printed no error.",
18193
+ description: "Export presets and exports. add_preset writes a preset to export_presets.cfg with the keys the editor's Project > Export writes for a new one and every option a new preset of its platform shows, each at its default, and makes it the platform's runnable preset when the platform has none. set_preset changes a preset's export path, its resource filter and the files it lists, its include and exclude filters, its custom features and its platform's options, and answers under changed what each held before and holds now, and under unchanged what already held the value given, with saved false when that is all of it, since the file is then not written; nothing is written when any of it is refused. The option names are the exporter's own, as the Export dialog's Options tab shows them by their paths, such as binary_format/embed_pck, and a name the platform does not have is refused with the nearest it has, or all of them when none is near. An option the dialog shows only while another is on, such as codesign/identity behind codesign/enable, is taken when the preset has that one on or the same call turns it on, and otherwise refused naming what shows it. A secret option, such as a signing password, is written to .godot/export_credentials.cfg, where the editor keeps secrets out of the committed file, and its value is never answered. A file an option names that is not there is written and listed under missing_files, or for a secret its option under secretsNamingNoFile. remove_preset takes a preset out, and when it was its platform's runnable preset the next of that platform becomes it, under runnableNow, which is null when the platform has no other; a secret it held is taken out with it. It answers where this engine's export templates belong and whether the preset's platform has them, since an export needs them; list says the same for every preset. An editor open on the project does not see a preset added this way until it restarts. run first imports whatever changed on disk since it was imported, named under importedFirst, so the build carries each file as it is now. It answers bytes, the size of the output it wrote, and names, as alsoWritten, what else the export wrote beside its output, such as the .pck a Windows or Linux build cannot start without. Its entries are the errors and warnings the export printed, the newest two hundred distinct ones, one printed more than once carrying times, and errors and warnings count them; exported is true only when this run wrote the output and printed no error.",
17909
18194
  parameters: {
17910
18195
  projectPath: PROJECT_PATH,
17911
18196
  preset: {
@@ -18093,7 +18378,7 @@ var init_tool_definitions = __esm(() => {
18093
18378
  name: "scene_node",
18094
18379
  title: "Scene nodes",
18095
18380
  description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, paint TileMap cells, or bake a navigation region. Any ClassDB node type or script class can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, and another scene is instanced by an add with instancePath. A set with properties {"name": "Boss"} renames a node, and answers nodePath as its new path. A rename or a reparent carries the node paths naming the node, as the editor's rename does: NodePath values on nodes (in arrays, dictionaries, built-in resources, %unique paths) and the tracks its players' animations hold, saving a library kept in a .tres of its own, and answers each rewrite under retargeted and each path it left under notRetargeted. The move is followed into every other .tscn that instances or inherits this scene at any depth: the editable children, values, added nodes and connections named by the old path are rewritten, since Godot finds a moved node's values by unique_id but editable children only by path and drops the values kept below them on the next save, and that scene's node paths and tracks are carried as above. A delete rewrites no path, and answers under stillNaming what named the deleted node and now names nothing. A property holding a Resource takes the res:// path of one, so a texture, a material or a theme is a set like any other. A write is refused while the editor holds unsaved changes to the scene. A node this scene inherits takes values of this scene's own, and so does a node inside an instance of another scene once that instance's editableChildren is true; neither can be moved, renamed, copied or deleted, since the other scene makes it again on load. ${WITHOUT_AN_EDITOR}`,
18096
- answer: "get answers nodeType, the node's engine class, with scriptClass when its script declares a class_name; nodePath, its path from the root however it was named; setHere, the properties this scene's own file sets on it; and valuesFrom, the scene every other value comes from, when another scene makes the node. A set that gives a property the value the node has without it (what the scene it comes from gives, or its default) leaves it out of the file, as the editor does, and answers it under notStored, with notStoredNote saying what it follows now. retargeted lists each path a rename or a reparent rewrote, as {scenePath, nodePath, property or animation and track, from, to}, and notRetargeted each it left, with a reason: a track kept in another scene or an imported file, or a value another scene gives that the move left naming nothing. followed names the other scenes a move rewrote, and notFollowed any left as they were, with why. scriptsNaming lists each line of a script that reads a node a rename, a reparent or a delete took by the path it had, as {scenePath, nodePath, script, line, from, to}: the script is on the node at nodePath, or is one that node's script extends, in this scene or one instancing or inheriting it, and to is the path that reaches the node from there now, absent after a delete. It reads $Path, %Unique, and a string handed to get_node, get_node_or_null or has_node on the node itself; a path built at run time is not seen. Scripts are left as they were, as the editor's own rename leaves them, so each line listed fails until it is changed. A 2D bake_navigation that read no obstacle says in its note that all its outlines enclose is walkable, and names the nodes giving obstacles that sit under no region. stillNaming lists each node path and track, here and in the scenes instancing or inheriting this one, that named a deleted node; a property typed as a node, or an array or dictionary of nodes, holds the node itself, so the one that held a deleted node holds null there, as the editor saves it, and its entry adds cleared: true. deletedWithIt lists each node of this scene's own that went with it, the nodes inside an instance going with the instance. groups lists, sorted, every group a node is in, on a get, an add that gave addGroups and every set that changes them; get adds groupsHere, the groups this file puts it in, when another scene puts it in others, and layers, each layer mask it answers (collision_layer, collision_mask, visibility_layer) as the layers it holds, {layer, name} with the name project.godot gives the layer. Each of these lists is answered only when it names anything. add and set answer each property given under properties as the node holds it once written; alsoChanged gives the other properties the engine changed in step with a set, as a Control's offsets with its size, and those a reparent changed to keep the node where it is drawn or that the new parent sets, as a container gives a Control its layout_mode, as {was, now}; namesNoNode, each node path written naming no node; sharedWith, for a property written through a resource built into the scene, each other {node, property} holding it, which changed too; setByScript, what a script that is not a tool set as a headless write ran, which the file does not hold; loadsAs, a value given that the file holds and the node's own script changes as the scene loads, as it loads. get adds placement, a Node3D's position, rotation_degrees and scale from its transform. " + ALSO_SAVED,
18381
+ answer: "get answers nodeType, the node's engine class, with scriptClass when its script declares a class_name; nodePath, its path from the root however it was named; setHere, the properties this scene's own file sets on it; and valuesFrom, the scene every other value comes from, when another scene makes the node. A set that gives a property the value the node has without it (what the scene it comes from gives, or its default) leaves it out of the file, as the editor does, and answers it under notStored, with notStoredNote saying what it follows now. retargeted lists each path a rename or a reparent rewrote, as {scenePath, nodePath, property or animation and track, from, to}, and notRetargeted each it left, with a reason: a track kept in another scene or an imported file, or a value another scene gives that the move left naming nothing. followed names the other scenes a move rewrote, and notFollowed any left as they were, with why. scriptsNaming lists each line of a script that reads a node a rename, a reparent or a delete took by the path it had, as {scenePath, nodePath, script, line, from, to}: the script is on the node at nodePath, or is one that node's script extends, in this scene or one instancing or inheriting it, and to is the path that reaches the node from there now, absent after a delete. It reads $Path, %Unique, and a string handed to get_node, get_node_or_null or has_node on the node itself; a path built at run time is not seen. Scripts are left as they were, as the editor's own rename leaves them, so each line listed fails until it is changed. A 2D bake_navigation that read no obstacle says in its note that all its outlines enclose is walkable, and names the nodes giving obstacles that sit under no region. stillNaming lists each node path and track, here and in the scenes instancing or inheriting this one, that named a deleted node; a property typed as a node, or an array or dictionary of nodes, holds the node itself, so the one that held a deleted node holds null there, as the editor saves it, and its entry adds cleared: true. deletedWithIt lists each node of this scene's own that went with it, the nodes inside an instance going with the instance. connectionsRemoved lists each connection the file held from or to a node a delete took, as {sourceNodePath, signalName, targetNodePath, methodName}. groups lists, sorted, every group a node is in, on a get, an add that gave addGroups and every set that changes them; get adds groupsHere, the groups this file puts it in, when another scene puts it in others, and layers, each layer mask it answers (collision_layer, collision_mask, visibility_layer) as the layers it holds, {layer, name} with the name project.godot gives the layer. Each of these lists is answered only when it names anything. add and set answer each property given under properties as the node holds it once written; alsoChanged gives the other properties the engine changed in step with a set, as a Control's offsets with its size, and those a reparent changed to keep the node where it is drawn or that the new parent sets, as a container gives a Control its layout_mode, as {was, now}; namesNoNode, each node path written naming no node; sharedWith, for a property written through a resource built into the scene, each other {node, property} holding it, which changed too; setByScript, what a script that is not a tool set as a headless write ran, which the file does not hold; loadsAs, a value given that the file holds and the node's own script changes as the scene loads, as it loads. get adds placement, a Node3D's position, rotation_degrees and scale from its transform. " + ALSO_SAVED,
18097
18382
  parameters: {
18098
18383
  projectPath: PROJECT_PATH,
18099
18384
  scenePath: SCENE_PATH,
@@ -19780,7 +20065,7 @@ var init_tool_reference = __esm(() => {
19780
20065
  });
19781
20066
 
19782
20067
  // src/suite-plan.ts
19783
- import { existsSync as existsSync23, mkdirSync as mkdirSync9, readFileSync as readFileSync22 } from "node:fs";
20068
+ import { existsSync as existsSync22, mkdirSync as mkdirSync9, readFileSync as readFileSync24 } from "node:fs";
19784
20069
  import { dirname as dirname11, join as join26, posix as posix3 } from "node:path";
19785
20070
  function within(path, folder) {
19786
20071
  return path === folder || path.startsWith(`${folder}/`);
@@ -19797,7 +20082,7 @@ class ProjectScripts {
19797
20082
  this.projectPath = projectPath;
19798
20083
  let settings = {};
19799
20084
  try {
19800
- settings = parseProjectGodot(readFileSync22(join26(projectPath, "project.godot"), "utf8"))["gdharness"] ?? {};
20085
+ settings = parseProjectGodot(readFileSync24(join26(projectPath, "project.godot"), "utf8"))["gdharness"] ?? {};
19801
20086
  } catch {}
19802
20087
  const [section, key] = [NOT_SEARCHED_SETTING, GUESS_WINDOW_SETTING].map((setting) => setting.slice("gdharness/".length));
19803
20088
  const listed = settings[section ?? ""];
@@ -19912,7 +20197,7 @@ class ProjectScripts {
19912
20197
  }
19913
20198
  function readTimes(file) {
19914
20199
  try {
19915
- const parsed = JSON.parse(readFileSync22(file, "utf8"));
20200
+ const parsed = JSON.parse(readFileSync24(file, "utf8"));
19916
20201
  return typeof parsed === "object" && parsed !== null ? parsed : {};
19917
20202
  } catch {
19918
20203
  return {};
@@ -20192,25 +20477,25 @@ function windowNeedsOf(scripts, paths) {
20192
20477
  }
20193
20478
  function capsFrames(projectPath) {
20194
20479
  const file = `${projectPath}/project.godot`;
20195
- if (!existsSync23(file)) {
20480
+ if (!existsSync22(file)) {
20196
20481
  return false;
20197
20482
  }
20198
- const cap = parseProjectGodot(readFileSync22(file, "utf8"))["application"]?.["run/max_fps"];
20483
+ const cap = parseProjectGodot(readFileSync24(file, "utf8"))["application"]?.["run/max_fps"];
20199
20484
  return typeof cap === "number" && cap > 0;
20200
20485
  }
20201
20486
  function gdUnitFailsOnPushErrors(projectPath) {
20202
20487
  const file = `${projectPath}/project.godot`;
20203
- if (!existsSync23(file)) {
20488
+ if (!existsSync22(file)) {
20204
20489
  return false;
20205
20490
  }
20206
- return parseProjectGodot(readFileSync22(file, "utf8"))["gdunit4"]?.["report/godot/push_error"] === true;
20491
+ return parseProjectGodot(readFileSync24(file, "utf8"))["gdunit4"]?.["report/godot/push_error"] === true;
20207
20492
  }
20208
20493
  function viewportOf(projectPath) {
20209
20494
  const file = `${projectPath}/project.godot`;
20210
- if (!existsSync23(file)) {
20495
+ if (!existsSync22(file)) {
20211
20496
  return null;
20212
20497
  }
20213
- const text = readFileSync22(file, "utf8");
20498
+ const text = readFileSync24(file, "utf8");
20214
20499
  const width = /^window\/size\/viewport_width\s*=\s*(\d+)/m.exec(text)?.[1];
20215
20500
  const height = /^window\/size\/viewport_height\s*=\s*(\d+)/m.exec(text)?.[1];
20216
20501
  return width === undefined || height === undefined ? null : { width: Number(width), height: Number(height) };
@@ -41829,13 +42114,13 @@ var init_stdio = __esm(() => {
41829
42114
  });
41830
42115
 
41831
42116
  // src/autoload-failure.ts
41832
- import { existsSync as existsSync25, readFileSync as readFileSync25 } from "node:fs";
42117
+ import { existsSync as existsSync24, readFileSync as readFileSync27 } from "node:fs";
41833
42118
  import { join as join29 } from "node:path";
41834
42119
  function scriptsAutoloaded(projectPath) {
41835
42120
  const scripts = new Map;
41836
42121
  let settings;
41837
42122
  try {
41838
- settings = parseProjectGodot(readFileSync25(join29(projectPath, "project.godot"), "utf8"));
42123
+ settings = parseProjectGodot(readFileSync27(join29(projectPath, "project.godot"), "utf8"));
41839
42124
  } catch {
41840
42125
  return scripts;
41841
42126
  }
@@ -41845,11 +42130,11 @@ function scriptsAutoloaded(projectPath) {
41845
42130
  continue;
41846
42131
  }
41847
42132
  const scene = join29(projectPath, path);
41848
- if (!existsSync25(scene)) {
42133
+ if (!existsSync24(scene)) {
41849
42134
  continue;
41850
42135
  }
41851
42136
  try {
41852
- for (const named of readFileSync25(scene, "utf8").matchAll(/^\[ext_resource\b[^\]]*\btype="Script"[^\]]*\bpath="(res:\/\/[^"]+)"/gm)) {
42137
+ for (const named of readFileSync27(scene, "utf8").matchAll(/^\[ext_resource\b[^\]]*\btype="Script"[^\]]*\bpath="(res:\/\/[^"]+)"/gm)) {
41853
42138
  scripts.set(named[1] ?? "", name);
41854
42139
  }
41855
42140
  } catch {}
@@ -41868,11 +42153,11 @@ var init_autoload_failure = __esm(() => {
41868
42153
  });
41869
42154
 
41870
42155
  // src/runtime-client.ts
41871
- import { existsSync as existsSync26, readdirSync as readdirSync16, readFileSync as readFileSync26, statSync as statSync15, unlinkSync as unlinkSync3 } from "node:fs";
42156
+ import { existsSync as existsSync25, readdirSync as readdirSync16, readFileSync as readFileSync28, statSync as statSync15, unlinkSync as unlinkSync3 } from "node:fs";
41872
42157
  import { createConnection as createConnection2 } from "node:net";
41873
42158
  import { tmpdir as tmpdir7 } from "node:os";
41874
42159
  import { dirname as dirname14, join as join30, resolve as resolve12 } from "node:path";
41875
- import { setTimeout as delay5 } from "node:timers/promises";
42160
+ import { setTimeout as delay6 } from "node:timers/promises";
41876
42161
  function runtimeDirectory(variables = process.env) {
41877
42162
  const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
41878
42163
  if (explicit) {
@@ -41892,7 +42177,7 @@ function runtimeDirectories(variables = process.env) {
41892
42177
  function errorReportOf(pid, directories = runtimeDirectories()) {
41893
42178
  for (const directory of directories) {
41894
42179
  const path = join30(directory, `runtime-${pid}.log`);
41895
- if (existsSync26(path)) {
42180
+ if (existsSync25(path)) {
41896
42181
  return path;
41897
42182
  }
41898
42183
  }
@@ -41923,7 +42208,7 @@ function reportAlreadyInTranscript(transcript, report) {
41923
42208
  function parseAnnouncement(file, pid) {
41924
42209
  let fields;
41925
42210
  try {
41926
- fields = asParams(JSON.parse(readFileSync26(file, "utf8")));
42211
+ fields = asParams(JSON.parse(readFileSync28(file, "utf8")));
41927
42212
  } catch {
41928
42213
  return { kind: "rubbish" };
41929
42214
  }
@@ -41995,7 +42280,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
41995
42280
  function wentAway(file, pid) {
41996
42281
  let project = "";
41997
42282
  try {
41998
- const fields = asParams(JSON.parse(readFileSync26(file, "utf8")));
42283
+ const fields = asParams(JSON.parse(readFileSync28(file, "utf8")));
41999
42284
  project = readString(readParams(fields, "project") ?? {}, "path") ?? "";
42000
42285
  } catch {
42001
42286
  return;
@@ -42038,7 +42323,7 @@ function strangersAmong(candidates, now, startTimes = startTimesOf) {
42038
42323
  }).map((one) => one.file));
42039
42324
  }
42040
42325
  function announcedIn(directory) {
42041
- if (!existsSync26(directory)) {
42326
+ if (!existsSync25(directory)) {
42042
42327
  return [];
42043
42328
  }
42044
42329
  const found = [];
@@ -42091,7 +42376,7 @@ function announcedIn(directory) {
42091
42376
  return found;
42092
42377
  }
42093
42378
  async function announcementEnded(pid, directories = runtimeDirectories()) {
42094
- const files = directories.map((directory) => join30(directory, `runtime-${pid}.json`)).filter((file) => existsSync26(file));
42379
+ const files = directories.map((directory) => join30(directory, `runtime-${pid}.json`)).filter((file) => existsSync25(file));
42095
42380
  const first = files[0];
42096
42381
  if (first === undefined) {
42097
42382
  return;
@@ -42102,7 +42387,7 @@ async function announcementEnded(pid, directories = runtimeDirectories()) {
42102
42387
  if (left <= 0) {
42103
42388
  return;
42104
42389
  }
42105
- await delay5(Math.min(LOOK_EVERY_MS, left));
42390
+ await delay6(Math.min(LOOK_EVERY_MS, left));
42106
42391
  }
42107
42392
  wentAway(first, pid);
42108
42393
  for (const file of files) {
@@ -42134,7 +42419,7 @@ async function announcedSince(projectPath, before, waiting = {}) {
42134
42419
  if (left <= 0 || waiting.giveUp?.() === true) {
42135
42420
  return [];
42136
42421
  }
42137
- await delay5(Math.min(LOOK_EVERY_MS, left));
42422
+ await delay6(Math.min(LOOK_EVERY_MS, left));
42138
42423
  }
42139
42424
  }
42140
42425
  function describe2(endpoint) {
@@ -42510,14 +42795,14 @@ var init_runtime_client = __esm(() => {
42510
42795
  });
42511
42796
 
42512
42797
  // src/boot-note.ts
42513
- import { mkdirSync as mkdirSync11, readFileSync as readFileSync27, writeFileSync as writeFileSync14 } from "node:fs";
42798
+ import { mkdirSync as mkdirSync11, readFileSync as readFileSync29, writeFileSync as writeFileSync14 } from "node:fs";
42514
42799
  import { dirname as dirname15, join as join31 } from "node:path";
42515
42800
  function bootNotePath(projectPath) {
42516
42801
  return join31(projectPath, ".godot", "gdharness-boot.json");
42517
42802
  }
42518
42803
  function readScenes(projectPath) {
42519
42804
  try {
42520
- return asParams(asParams(JSON.parse(readFileSync27(bootNotePath(projectPath), "utf8")))["scenes"]);
42805
+ return asParams(asParams(JSON.parse(readFileSync29(bootNotePath(projectPath), "utf8")))["scenes"]);
42521
42806
  } catch {
42522
42807
  return {};
42523
42808
  }
@@ -42559,7 +42844,7 @@ var init_boot_note = __esm(() => {
42559
42844
  });
42560
42845
 
42561
42846
  // src/breakpoint-note.ts
42562
- import { mkdirSync as mkdirSync12, readFileSync as readFileSync28, rmSync as rmSync14, writeFileSync as writeFileSync15 } from "node:fs";
42847
+ import { mkdirSync as mkdirSync12, readFileSync as readFileSync30, rmSync as rmSync14, writeFileSync as writeFileSync15 } from "node:fs";
42563
42848
  import { dirname as dirname16, isAbsolute as isAbsolute7, join as join32, relative as relative6 } from "node:path";
42564
42849
  function breakpointNotePath(projectPath) {
42565
42850
  return join32(projectPath, ".godot", "gdharness-breakpoints.json");
@@ -42588,7 +42873,7 @@ function writeBreakpointNote(projectPath, held) {
42588
42873
  }
42589
42874
  function readBreakpointNote(projectPath) {
42590
42875
  try {
42591
- const parsed = JSON.parse(readFileSync28(breakpointNotePath(projectPath), "utf8"));
42876
+ const parsed = JSON.parse(readFileSync30(breakpointNotePath(projectPath), "utf8"));
42592
42877
  if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.breakpoints)) {
42593
42878
  return [];
42594
42879
  }
@@ -42621,14 +42906,14 @@ var init_breakpoint_note = __esm(() => {
42621
42906
  });
42622
42907
 
42623
42908
  // src/class-note.ts
42624
- import { mkdirSync as mkdirSync13, readFileSync as readFileSync29 } from "node:fs";
42909
+ import { mkdirSync as mkdirSync13, readFileSync as readFileSync31 } from "node:fs";
42625
42910
  import { dirname as dirname17, join as join33 } from "node:path";
42626
42911
  function classNotePath(projectPath) {
42627
42912
  return join33(projectPath, ".godot", "gdharness-classes.json");
42628
42913
  }
42629
42914
  function readClassNote(projectPath) {
42630
42915
  try {
42631
- const fields = asParams(JSON.parse(readFileSync29(classNotePath(projectPath), "utf8")));
42916
+ const fields = asParams(JSON.parse(readFileSync31(classNotePath(projectPath), "utf8")));
42632
42917
  const rebuiltWith = readArray(fields, "rebuiltWith");
42633
42918
  if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
42634
42919
  return null;
@@ -42670,16 +42955,16 @@ var init_class_note = __esm(() => {
42670
42955
  });
42671
42956
 
42672
42957
  // src/config-pin.ts
42673
- import { existsSync as existsSync27, readFileSync as readFileSync30 } from "node:fs";
42958
+ import { existsSync as existsSync26, readFileSync as readFileSync32 } from "node:fs";
42674
42959
  import { join as join34 } from "node:path";
42675
42960
  function versionsNamedInConfig(projectPath) {
42676
42961
  const config = join34(projectPath, ".mcp.json");
42677
- if (!existsSync27(config)) {
42962
+ if (!existsSync26(config)) {
42678
42963
  return [];
42679
42964
  }
42680
42965
  let text;
42681
42966
  try {
42682
- text = readFileSync30(config, "utf8");
42967
+ text = readFileSync32(config, "utf8");
42683
42968
  JSON.parse(text);
42684
42969
  } catch {
42685
42970
  return [];
@@ -42700,7 +42985,7 @@ var init_config_pin = __esm(() => {
42700
42985
  });
42701
42986
 
42702
42987
  // src/cut-test-run.ts
42703
- import { existsSync as existsSync28, mkdirSync as mkdirSync14, readFileSync as readFileSync31, rmSync as rmSync15, writeFileSync as writeFileSync16 } from "node:fs";
42988
+ import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync33, rmSync as rmSync15, writeFileSync as writeFileSync16 } from "node:fs";
42704
42989
  import { dirname as dirname18, join as join35 } from "node:path";
42705
42990
  function keptAt(projectPath) {
42706
42991
  return join35(projectPath, ".godot", "gdharness-reports", "cut-run.json");
@@ -42715,11 +43000,11 @@ function keepCutTestRun(projectPath, run) {
42715
43000
  }
42716
43001
  function takeCutTestRun(projectPath) {
42717
43002
  const path = keptAt(projectPath);
42718
- if (!existsSync28(path)) {
43003
+ if (!existsSync27(path)) {
42719
43004
  return null;
42720
43005
  }
42721
43006
  try {
42722
- const run = JSON.parse(readFileSync31(path, "utf8"));
43007
+ const run = JSON.parse(readFileSync33(path, "utf8"));
42723
43008
  rmSync15(path, { force: true });
42724
43009
  return typeof run.cutAt === "string" && Array.isArray(run.paths) && run.paths.every((path) => typeof path === "string") ? run : null;
42725
43010
  } catch {
@@ -42761,7 +43046,7 @@ var init_game_debugger = __esm(() => {
42761
43046
 
42762
43047
  // src/dap_client.ts
42763
43048
  import { createConnection as createConnection3 } from "node:net";
42764
- import { setTimeout as delay6 } from "node:timers/promises";
43049
+ import { setTimeout as delay7 } from "node:timers/promises";
42765
43050
  function fileKey(path) {
42766
43051
  return process.platform === "win32" ? path.replaceAll("\\", "/").toLowerCase() : path;
42767
43052
  }
@@ -43241,7 +43526,7 @@ class GodotDAPClient {
43241
43526
  await this.sendRequest(command, { threadId: resolvedThreadId });
43242
43527
  const deadline = Date.now() + this.stepPatienceMs;
43243
43528
  while (this.stops === before && this.connected && Date.now() < deadline) {
43244
- await delay6(50);
43529
+ await delay7(50);
43245
43530
  }
43246
43531
  return this.stops !== before;
43247
43532
  }
@@ -43267,7 +43552,7 @@ class GodotDAPClient {
43267
43552
  return answered;
43268
43553
  }
43269
43554
  } catch {}
43270
- await delay6(200);
43555
+ await delay7(200);
43271
43556
  }
43272
43557
  return null;
43273
43558
  }
@@ -43288,7 +43573,7 @@ class GodotDAPClient {
43288
43573
  break;
43289
43574
  }
43290
43575
  } catch {}
43291
- await delay6(200);
43576
+ await delay7(200);
43292
43577
  }
43293
43578
  if (scopes.length === 0) {
43294
43579
  throw new ReadRefusal(`The scopes of frame ${String(frame["id"])} did not arrive from the game within ${this.scopesPatienceMs / 1000}s. They come from the game through the editor, so a busy machine can take longer: ask again`);
@@ -43321,7 +43606,7 @@ class GodotDAPClient {
43321
43606
  return { frame, value, ...typeof type === "string" ? { type } : {} };
43322
43607
  }
43323
43608
  } catch {}
43324
- await delay6(200);
43609
+ await delay7(200);
43325
43610
  }
43326
43611
  throw new ReadRefusal(`What ${expression} comes to did not arrive from the game within ${this.evaluatePatienceMs / 1000}s. It comes from the game through the editor, and the first expression a game evaluates loads the script of every global class in the project first, static initialisers included, so one of those may still be running: ask again`);
43327
43612
  }
@@ -43483,10 +43768,10 @@ var init_dap_client = __esm(() => {
43483
43768
  // src/editor-log.ts
43484
43769
  import {
43485
43770
  closeSync as closeSync5,
43486
- existsSync as existsSync29,
43771
+ existsSync as existsSync28,
43487
43772
  mkdirSync as mkdirSync15,
43488
43773
  openSync as openSync5,
43489
- readFileSync as readFileSync32,
43774
+ readFileSync as readFileSync34,
43490
43775
  readSync as readSync3,
43491
43776
  rmSync as rmSync16,
43492
43777
  statSync as statSync16,
@@ -43501,7 +43786,7 @@ function editorLogNotePath(projectPath) {
43501
43786
  }
43502
43787
  function readEditorLogNote(projectPath) {
43503
43788
  try {
43504
- const fields = asParams(JSON.parse(readFileSync32(editorLogNotePath(projectPath), "utf8")));
43789
+ const fields = asParams(JSON.parse(readFileSync34(editorLogNotePath(projectPath), "utf8")));
43505
43790
  const pid = readNumber(fields, "pid");
43506
43791
  const startedAt = readString(fields, "startedAt");
43507
43792
  const processStartedAt = readNumber(fields, "processStartedAt");
@@ -43547,7 +43832,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
43547
43832
  return { kind: "another editor", notedPid: noted?.pid ?? null };
43548
43833
  }
43549
43834
  const path = editorLogPath(projectPath);
43550
- if (!existsSync29(path)) {
43835
+ if (!existsSync28(path)) {
43551
43836
  return { kind: "not written" };
43552
43837
  }
43553
43838
  const log = new GameLog;
@@ -43605,7 +43890,7 @@ function foldProgress(text) {
43605
43890
  function theFirstOf(path) {
43606
43891
  const whole = statSync16(path).size;
43607
43892
  if (whole <= MOST_CONSOLE_BYTES) {
43608
- return { text: readFileSync32(path, "utf8") };
43893
+ return { text: readFileSync34(path, "utf8") };
43609
43894
  }
43610
43895
  const handle = openSync5(path, "r");
43611
43896
  try {
@@ -43766,7 +44051,7 @@ var init_editor_log = __esm(() => {
43766
44051
  });
43767
44052
 
43768
44053
  // src/rename.ts
43769
- import { existsSync as existsSync30, mkdirSync as mkdirSync16, readFileSync as readFileSync33, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync18 } from "node:fs";
44054
+ import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync35, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync18 } from "node:fs";
43770
44055
  import { dirname as dirname20, posix as posix4 } from "node:path";
43771
44056
 
43772
44057
  class Script {
@@ -44487,11 +44772,11 @@ function rewritesOf(plan, texts, move) {
44487
44772
  }
44488
44773
  function applyRewrites(rewrites) {
44489
44774
  for (const rewrite of rewrites) {
44490
- const now = readFileSync33(rewrite.absolute, "utf8");
44775
+ const now = readFileSync35(rewrite.absolute, "utf8");
44491
44776
  if (now !== rewrite.before) {
44492
44777
  throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
44493
44778
  }
44494
- if (rewrite.writeTo !== rewrite.absolute && existsSync30(rewrite.writeTo)) {
44779
+ if (rewrite.writeTo !== rewrite.absolute && existsSync29(rewrite.writeTo)) {
44495
44780
  throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
44496
44781
  }
44497
44782
  }
@@ -44505,7 +44790,7 @@ function applyRewrites(rewrites) {
44505
44790
  let movedUid = false;
44506
44791
  if (rewrite.writeTo !== rewrite.absolute) {
44507
44792
  unlinkSync4(rewrite.absolute);
44508
- if (existsSync30(`${rewrite.absolute}.uid`)) {
44793
+ if (existsSync29(`${rewrite.absolute}.uid`)) {
44509
44794
  renameSync4(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
44510
44795
  movedUid = true;
44511
44796
  }
@@ -44551,7 +44836,7 @@ var init_rename = __esm(() => {
44551
44836
  });
44552
44837
 
44553
44838
  // src/rename-symbol.ts
44554
- import { existsSync as existsSync31, mkdirSync as mkdirSync17, readdirSync as readdirSync17, realpathSync as realpathSync5, rmSync as rmSync17, writeFileSync as writeFileSync19 } from "node:fs";
44839
+ import { existsSync as existsSync30, mkdirSync as mkdirSync17, readdirSync as readdirSync17, realpathSync as realpathSync5, rmSync as rmSync17, writeFileSync as writeFileSync19 } from "node:fs";
44555
44840
  import { basename as basename10, dirname as dirname21, join as join37, relative as relative7, resolve as resolve13 } from "node:path";
44556
44841
  function resourcePathOf(projectPath, absolute) {
44557
44842
  const inside = relative7(onDiskSpelling(projectPath), onDiskSpelling(absolute));
@@ -44619,7 +44904,7 @@ async function renameSymbol(request, services) {
44619
44904
  reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
44620
44905
  };
44621
44906
  }
44622
- if (existsSync31(request.newScriptPath)) {
44907
+ if (existsSync30(request.newScriptPath)) {
44623
44908
  return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
44624
44909
  }
44625
44910
  }
@@ -44796,7 +45081,7 @@ async function renameSymbol(request, services) {
44796
45081
  scripts,
44797
45082
  moves: new Map([[declaringPath, moveTo]]),
44798
45083
  named: [declaringPath],
44799
- exists: (path) => existsSync31(`${projectPath}/${path.slice("res://".length)}`)
45084
+ exists: (path) => existsSync30(`${projectPath}/${path.slice("res://".length)}`)
44800
45085
  }, plan);
44801
45086
  }
44802
45087
  plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? [] : [declaringPath]);
@@ -45071,10 +45356,10 @@ var init_rename_symbol = __esm(() => {
45071
45356
 
45072
45357
  // src/file-move.ts
45073
45358
  import {
45074
- existsSync as existsSync32,
45359
+ existsSync as existsSync31,
45075
45360
  mkdirSync as mkdirSync18,
45076
45361
  readdirSync as readdirSync18,
45077
- readFileSync as readFileSync34,
45362
+ readFileSync as readFileSync36,
45078
45363
  renameSync as renameSync5,
45079
45364
  statSync as statSync17,
45080
45365
  writeFileSync as writeFileSync20
@@ -45104,7 +45389,7 @@ function unseenByTheEngine(projectPath, path) {
45104
45389
  }
45105
45390
  if (at < steps.length - 1) {
45106
45391
  directory = join38(directory, step);
45107
- if (existsSync32(join38(directory, ".gdignore"))) {
45392
+ if (existsSync31(join38(directory, ".gdignore"))) {
45108
45393
  return `${path} is under a directory holding a .gdignore, which the engine does not load`;
45109
45394
  }
45110
45395
  }
@@ -45116,12 +45401,12 @@ function installedAddonOf(projectPath, path) {
45116
45401
  if (steps[0] !== "addons" || steps[1] === undefined) {
45117
45402
  return null;
45118
45403
  }
45119
- return existsSync32(join38(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
45404
+ return existsSync31(join38(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
45120
45405
  }
45121
45406
  function binaryResources(projectPath) {
45122
45407
  const found = [];
45123
45408
  const visit = (directory, prefix) => {
45124
- if (existsSync32(join38(directory, ".gdignore"))) {
45409
+ if (existsSync31(join38(directory, ".gdignore"))) {
45125
45410
  return;
45126
45411
  }
45127
45412
  for (const entry of readdirSync18(directory, { withFileTypes: true })) {
@@ -45158,7 +45443,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
45158
45443
  if (to === null) {
45159
45444
  return { ok: false, reason: `newPath must be a path inside the project, and ${absoluteTo} is not.` };
45160
45445
  }
45161
- if (!existsSync32(absoluteFrom)) {
45446
+ if (!existsSync31(absoluteFrom)) {
45162
45447
  return { ok: false, reason: `There is nothing at ${from} to move.` };
45163
45448
  }
45164
45449
  if (from === to) {
@@ -45187,7 +45472,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
45187
45472
  if (isDirectory && to.startsWith(`${from}/`)) {
45188
45473
  return { ok: false, reason: `${from} cannot be moved into itself.` };
45189
45474
  }
45190
- if (existsSync32(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
45475
+ if (existsSync31(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
45191
45476
  return { ok: false, reason: `${to} already exists, so nothing was moved there.` };
45192
45477
  }
45193
45478
  const unseen = unseenByTheEngine(projectPath, to);
@@ -45195,7 +45480,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
45195
45480
  return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
45196
45481
  }
45197
45482
  if (!isDirectory) {
45198
- const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync32(join38(projectPath, path.slice("res://".length))));
45483
+ const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync31(join38(projectPath, path.slice("res://".length))));
45199
45484
  if (owner !== undefined && owner !== null) {
45200
45485
  return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
45201
45486
  }
@@ -45217,7 +45502,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
45217
45502
  } else {
45218
45503
  moves.set(from, to);
45219
45504
  for (const sidecar of SIDECARS) {
45220
- if (existsSync32(`${absoluteFrom}${sidecar}`)) {
45505
+ if (existsSync31(`${absoluteFrom}${sidecar}`)) {
45221
45506
  moves.set(`${from}${sidecar}`, `${to}${sidecar}`);
45222
45507
  }
45223
45508
  }
@@ -45229,12 +45514,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
45229
45514
  scripts,
45230
45515
  moves,
45231
45516
  named: [from],
45232
- exists: (path) => existsSync32(join38(projectPath, path.slice("res://".length)))
45517
+ exists: (path) => existsSync31(join38(projectPath, path.slice("res://".length)))
45233
45518
  }, { edits: [], mentions: [] });
45234
45519
  plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
45235
45520
  const binaries = [];
45236
45521
  for (const path of binaryResources(projectPath)) {
45237
- const bytes = readFileSync34(join38(projectPath, path.slice("res://".length)));
45522
+ const bytes = readFileSync36(join38(projectPath, path.slice("res://".length)));
45238
45523
  const names = [...moves.keys()].find((moving) => bytes.includes(moving));
45239
45524
  if (names !== undefined) {
45240
45525
  binaries.push({
@@ -45295,18 +45580,18 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
45295
45580
  }
45296
45581
  function applyFileMove(outcome) {
45297
45582
  for (const rewrite of outcome.inPlace) {
45298
- if (readFileSync34(rewrite.absolute, "utf8") !== rewrite.before) {
45583
+ if (readFileSync36(rewrite.absolute, "utf8") !== rewrite.before) {
45299
45584
  throw new Error(`${rewrite.path} changed on disk while the move was being worked out; nothing was written`);
45300
45585
  }
45301
45586
  }
45302
45587
  for (const file of outcome.moved) {
45303
- if (!existsSync32(file.absoluteFrom)) {
45588
+ if (!existsSync31(file.absoluteFrom)) {
45304
45589
  throw new Error(`${file.from} is gone from disk; nothing was written`);
45305
45590
  }
45306
- if (file.text !== null && readFileSync34(file.absoluteFrom, "utf8") !== file.text.before) {
45591
+ if (file.text !== null && readFileSync36(file.absoluteFrom, "utf8") !== file.text.before) {
45307
45592
  throw new Error(`${file.from} changed on disk while the move was being worked out; nothing was written`);
45308
45593
  }
45309
- if (existsSync32(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
45594
+ if (existsSync31(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
45310
45595
  throw new Error(`${file.to} already exists; nothing was written`);
45311
45596
  }
45312
45597
  }
@@ -49218,44 +49503,6 @@ function notListeningNote(servers, greetedAgoMs) {
49218
49503
  var STARTING_MS = 20000;
49219
49504
  var init_port_probe = () => {};
49220
49505
 
49221
- // src/process-time.ts
49222
- async function cpuSecondsOf(pid) {
49223
- try {
49224
- return process.platform === "win32" ? await secondsOnWindows(pid) : await askPosix2(pid);
49225
- } catch {
49226
- return;
49227
- }
49228
- }
49229
- async function secondsOnWindows(pid) {
49230
- const said = (await askProcessTable(`ProcessorSeconds(${pid})`, ASK_TIMEOUT_MS2)).trim();
49231
- const seconds = Number(said);
49232
- return said !== "" && Number.isFinite(seconds) ? seconds : undefined;
49233
- }
49234
- async function askPosix2(pid) {
49235
- const { stdout } = await runHidden("ps", ["-o", "time=", "-p", String(pid)], { timeout: ASK_TIMEOUT_MS2 });
49236
- return secondsFromClock(stdout.trim());
49237
- }
49238
- function secondsFromClock(printed) {
49239
- if (printed === "") {
49240
- return;
49241
- }
49242
- const dashed = printed.includes("-");
49243
- const days = dashed ? printed.slice(0, printed.indexOf("-")) : null;
49244
- const clock = dashed ? printed.slice(printed.indexOf("-") + 1) : printed;
49245
- const parts = clock.split(":").map((part) => Number(part));
49246
- if (parts.length === 0 || parts.some((part) => !Number.isFinite(part))) {
49247
- return;
49248
- }
49249
- const seconds = parts.reduce((total, part) => total * 60 + part, 0);
49250
- const extra = days === null ? 0 : Number(days) * 86400;
49251
- return Number.isFinite(extra) ? seconds + extra : undefined;
49252
- }
49253
- var ASK_TIMEOUT_MS2 = 6000;
49254
- var init_process_time = __esm(() => {
49255
- init_process_table();
49256
- init_spawn();
49257
- });
49258
-
49259
49506
  // src/prompts.ts
49260
49507
  function listedPrompts() {
49261
49508
  return PROMPT_SPECS.map((spec) => ({
@@ -49393,7 +49640,7 @@ var init_prompts = __esm(() => {
49393
49640
  });
49394
49641
 
49395
49642
  // src/restart-note.ts
49396
- import { mkdirSync as mkdirSync20, readFileSync as readFileSync35, rmSync as rmSync18, writeFileSync as writeFileSync21 } from "node:fs";
49643
+ import { mkdirSync as mkdirSync20, readFileSync as readFileSync37, rmSync as rmSync18, writeFileSync as writeFileSync21 } from "node:fs";
49397
49644
  import { dirname as dirname24, join as join40 } from "node:path";
49398
49645
  function restartNotePath(projectPath) {
49399
49646
  return join40(projectPath, ".godot", "gdharness-restart.json");
@@ -49410,7 +49657,7 @@ function noteRestartBegun(note) {
49410
49657
  }
49411
49658
  function restartOwed(projectPath) {
49412
49659
  try {
49413
- const parsed = JSON.parse(readFileSync35(restartNotePath(projectPath), "utf8"));
49660
+ const parsed = JSON.parse(readFileSync37(restartNotePath(projectPath), "utf8"));
49414
49661
  if (typeof parsed !== "object" || parsed === null) {
49415
49662
  return null;
49416
49663
  }
@@ -49436,7 +49683,7 @@ var init_restart_note = __esm(() => {
49436
49683
 
49437
49684
  // src/run-record.ts
49438
49685
  import { createHash as createHash7 } from "node:crypto";
49439
- import { mkdirSync as mkdirSync21, openSync as openSync6, readdirSync as readdirSync19, readFileSync as readFileSync36, rmSync as rmSync19, statSync as statSync19 } from "node:fs";
49686
+ import { mkdirSync as mkdirSync21, openSync as openSync6, readdirSync as readdirSync19, readFileSync as readFileSync38, rmSync as rmSync19, statSync as statSync19 } from "node:fs";
49440
49687
  import { basename as basename12, join as join41, resolve as resolve14 } from "node:path";
49441
49688
  function runsDirectory() {
49442
49689
  return join41(runtimeDirectory(), "runs");
@@ -49460,7 +49707,7 @@ function writeLatestRun(servedBy, latest) {
49460
49707
  }
49461
49708
  function readLatestRun(servedBy, directory = runtimeDirectory()) {
49462
49709
  try {
49463
- const fields = JSON.parse(readFileSync36(latestRunPath(servedBy, directory), "utf8"));
49710
+ const fields = JSON.parse(readFileSync38(latestRunPath(servedBy, directory), "utf8"));
49464
49711
  if (typeof fields !== "object" || fields === null) {
49465
49712
  return null;
49466
49713
  }
@@ -49509,7 +49756,7 @@ function readEditorRunNote(projectPath) {
49509
49756
  function editorNoteAt(path) {
49510
49757
  let parsed;
49511
49758
  try {
49512
- parsed = JSON.parse(readFileSync36(path, "utf8"));
49759
+ parsed = JSON.parse(readFileSync38(path, "utf8"));
49513
49760
  } catch {
49514
49761
  return null;
49515
49762
  }
@@ -49559,7 +49806,7 @@ function everyRunRecord() {
49559
49806
  function recordAt(path) {
49560
49807
  let parsed;
49561
49808
  try {
49562
- parsed = JSON.parse(readFileSync36(path, "utf8"));
49809
+ parsed = JSON.parse(readFileSync38(path, "utf8"));
49563
49810
  } catch {
49564
49811
  return null;
49565
49812
  }
@@ -49640,7 +49887,7 @@ async function runningAs(pid) {
49640
49887
  const began = await startedAt(pid, null);
49641
49888
  const when = began === null ? {} : { startedAt: began };
49642
49889
  if (process.platform === "linux") {
49643
- const raw = readFileSync36(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
49890
+ const raw = readFileSync38(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
49644
49891
  return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
49645
49892
  }
49646
49893
  const args = (await runHidden("ps", ["-p", String(pid), "-o", "args="], { timeout: 15000 })).stdout.trim();
@@ -49836,10 +50083,10 @@ var init_run_record = __esm(() => {
49836
50083
  });
49837
50084
 
49838
50085
  // src/script-profile.ts
49839
- import { readFileSync as readFileSync37 } from "node:fs";
50086
+ import { readFileSync as readFileSync39 } from "node:fs";
49840
50087
  function readProfileFile(path) {
49841
50088
  try {
49842
- const parsed = JSON.parse(readFileSync37(path, "utf8"));
50089
+ const parsed = JSON.parse(readFileSync39(path, "utf8"));
49843
50090
  return typeof parsed === "object" && typeof parsed.connected === "boolean" ? parsed : null;
49844
50091
  } catch {
49845
50092
  return null;
@@ -49897,7 +50144,7 @@ function writeProfileMark(path, mark) {
49897
50144
  }
49898
50145
  function readProfileMark(path) {
49899
50146
  try {
49900
- const parsed = JSON.parse(readFileSync37(path, "utf8"));
50147
+ const parsed = JSON.parse(readFileSync39(path, "utf8"));
49901
50148
  return typeof parsed === "object" && typeof parsed.totals === "object" ? parsed : null;
49902
50149
  } catch {
49903
50150
  return null;
@@ -50070,10 +50317,10 @@ var init_server_instructions = __esm(() => {
50070
50317
  });
50071
50318
 
50072
50319
  // src/suites-scene.ts
50073
- import { existsSync as existsSync33, mkdirSync as mkdirSync22, writeFileSync as writeFileSync22 } from "node:fs";
50320
+ import { existsSync as existsSync32, mkdirSync as mkdirSync22, writeFileSync as writeFileSync22 } from "node:fs";
50074
50321
  import { join as join42, relative as relative9, sep as sep5 } from "node:path";
50075
50322
  function hasSuitesScene(projectPath) {
50076
- return existsSync33(join42(projectPath, SUITES_SCENE));
50323
+ return existsSync32(join42(projectPath, SUITES_SCENE));
50077
50324
  }
50078
50325
  function suitesSceneArgs(projectPath, suites) {
50079
50326
  const runs = join42(projectPath, ".godot", "gdharness", "suite-scenes");
@@ -50108,7 +50355,7 @@ var init_suites_scene = __esm(() => {
50108
50355
  });
50109
50356
 
50110
50357
  // src/test-selection.ts
50111
- import { existsSync as existsSync34 } from "node:fs";
50358
+ import { existsSync as existsSync33 } from "node:fs";
50112
50359
  import { basename as basename13, join as join43 } from "node:path";
50113
50360
  function casesOutOfPath(projectPath, given, tests) {
50114
50361
  const entries = Array.isArray(given) ? given : [given];
@@ -50153,12 +50400,12 @@ function casesIn(scripts, suite) {
50153
50400
  }
50154
50401
  function scriptsWithoutCases(projectPath, asked) {
50155
50402
  const scripts = new ProjectScripts(projectPath);
50156
- return asked.filter((path) => path.endsWith(".gd") && existsSync34(join43(projectPath, path.slice("res://".length))) && casesIn(scripts, path).length === 0);
50403
+ return asked.filter((path) => path.endsWith(".gd") && existsSync33(join43(projectPath, path.slice("res://".length))) && casesIn(scripts, path).length === 0);
50157
50404
  }
50158
50405
  function suitesUnder(projectPath, asked) {
50159
50406
  const scripts = projectFiles(projectPath, [".gd"]).map((path) => `res://${path}`);
50160
50407
  return [
50161
- ...new Set(asked.flatMap((path) => path.endsWith(".gd") ? existsSync34(join43(projectPath, path.slice("res://".length))) ? [path] : [] : scripts.filter((script) => script.startsWith(`${path}/`))))
50408
+ ...new Set(asked.flatMap((path) => path.endsWith(".gd") ? existsSync33(join43(projectPath, path.slice("res://".length))) ? [path] : [] : scripts.filter((script) => script.startsWith(`${path}/`))))
50162
50409
  ];
50163
50410
  }
50164
50411
  function selectTests(projectPath, asked, given) {
@@ -50224,12 +50471,12 @@ import {
50224
50471
  closeSync as closeSync6,
50225
50472
  constants,
50226
50473
  copyFileSync as copyFileSync2,
50227
- existsSync as existsSync35,
50474
+ existsSync as existsSync34,
50228
50475
  fstatSync as fstatSync3,
50229
50476
  mkdirSync as mkdirSync23,
50230
50477
  openSync as openSync7,
50231
50478
  readdirSync as readdirSync20,
50232
- readFileSync as readFileSync38,
50479
+ readFileSync as readFileSync40,
50233
50480
  readSync as readSync4,
50234
50481
  realpathSync as realpathSync6,
50235
50482
  rmdirSync as rmdirSync2,
@@ -50237,7 +50484,7 @@ import {
50237
50484
  writeFileSync as writeFileSync23
50238
50485
  } from "node:fs";
50239
50486
  import { basename as basename14, dirname as dirname25, extname as extname6, isAbsolute as isAbsolute8, join as join44, normalize as normalize2, relative as relative10 } from "node:path";
50240
- import { setTimeout as delay7 } from "node:timers/promises";
50487
+ import { setTimeout as delay8 } from "node:timers/promises";
50241
50488
  import { fileURLToPath as fileURLToPath5 } from "node:url";
50242
50489
  function editorRestartTimeoutMs() {
50243
50490
  return waitFromEnv("GDHARNESS_EDITOR_RESTART_TIMEOUT_MS", 90000);
@@ -50250,9 +50497,9 @@ function greetingWaitMs() {
50250
50497
  return waitFromEnv("GDHARNESS_GREETING_WAIT_MS", 1e4);
50251
50498
  }
50252
50499
  function pictureToSend(pngPath, jpegPath) {
50253
- const png = readFileSync38(pngPath);
50500
+ const png = readFileSync40(pngPath);
50254
50501
  const beside = pngPath.replace(/\.png$/, ".jpg");
50255
- const jpeg = jpegPath !== undefined && normalize2(jpegPath) === normalize2(beside) && existsSync35(beside) ? readFileSync38(beside) : null;
50502
+ const jpeg = jpegPath !== undefined && normalize2(jpegPath) === normalize2(beside) && existsSync34(beside) ? readFileSync40(beside) : null;
50256
50503
  const sent = jpeg !== null && jpeg.length < png.length ? jpeg : png;
50257
50504
  return {
50258
50505
  data: sent.toString("base64"),
@@ -50292,7 +50539,7 @@ function writtenFileRefusal(path, rule) {
50292
50539
  return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
50293
50540
  }
50294
50541
  const directory = realAsFarAsItExists(dirname25(path));
50295
- if (!existsSync35(directory)) {
50542
+ if (!existsSync34(directory)) {
50296
50543
  return `${named}'s directory ${dirname25(path)} does not exist; ${what} creates no directories.`;
50297
50544
  }
50298
50545
  const where = join44(directory, basename14(path));
@@ -50300,7 +50547,7 @@ function writtenFileRefusal(path, rule) {
50300
50547
  if (inside !== undefined) {
50301
50548
  return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
50302
50549
  }
50303
- const taken = rule.writes(where).find((file) => existsSync35(file));
50550
+ const taken = rule.writes(where).find((file) => existsSync34(file));
50304
50551
  if (taken !== undefined) {
50305
50552
  return taken === where ? `${named} ${path} already exists; ${what} is not written over a file. Name a new one.` : `${named} ${path} would write ${taken}, which already exists; ${what} is not written over a file. Name a new one.`;
50306
50553
  }
@@ -50524,7 +50771,7 @@ async function untilGone(pids, withinMs = stopWaitMs()) {
50524
50771
  const gone = () => pids.every((pid) => !alive(pid));
50525
50772
  const deadline = Date.now() + withinMs;
50526
50773
  while (!gone() && Date.now() < deadline) {
50527
- await delay7(50);
50774
+ await delay8(50);
50528
50775
  }
50529
50776
  return gone();
50530
50777
  }
@@ -50728,7 +50975,7 @@ function endedToStartThis(ended) {
50728
50975
  return ` ${which} was ended to start this one; its output is no longer what editor_output answers about.`;
50729
50976
  }
50730
50977
  function lineNothingStopsOn(absolutePath, relativePath, line) {
50731
- const text = readFileSync38(absolutePath, "utf8");
50978
+ const text = readFileSync40(absolutePath, "utf8");
50732
50979
  const lines = text === "" ? 0 : text.split(`
50733
50980
  `).length - (text.endsWith(`
50734
50981
  `) ? 1 : 0);
@@ -50939,7 +51186,7 @@ function notRunNote(notRun, failFast) {
50939
51186
  function presetsIn(projectPath) {
50940
51187
  let text;
50941
51188
  try {
50942
- text = readFileSync38(join44(projectPath, "export_presets.cfg"), "utf8");
51189
+ text = readFileSync40(join44(projectPath, "export_presets.cfg"), "utf8");
50943
51190
  } catch {
50944
51191
  return null;
50945
51192
  }
@@ -50983,7 +51230,7 @@ function exportPresetRefusal(projectPath, preset) {
50983
51230
  }
50984
51231
  return names.length === 0 ? `export_presets.cfg names no preset, so there is no '${preset}' to export with. project_export add_preset adds one.` : `This project has no export preset named '${preset}'. Its presets are ${names.map((name) => `'${name}'`).join(", ")}.`;
50985
51232
  }
50986
- function exportAnswer(ending, asked, before, after, beside = [], docCacheNote = null, bytes = null) {
51233
+ function exportAnswer(ending, asked, before, after, beside = [], docCacheNote = null, bytes = null, imports = {}) {
50987
51234
  const { log } = ending;
50988
51235
  const written = after !== null && after !== before;
50989
51236
  const notCopied = librariesNotCopied(log.all);
@@ -50997,6 +51244,7 @@ function exportAnswer(ending, asked, before, after, beside = [], docCacheNote =
50997
51244
  warnings: log.count("warning"),
50998
51245
  ...written && bytes !== null ? { bytes } : {},
50999
51246
  ...beside.length === 0 ? {} : { alsoWritten: beside },
51247
+ ...imports,
51000
51248
  ...notCopied.length === 0 ? {} : {
51001
51249
  librariesNotLoaded: notCopied,
51002
51250
  extensionNote: `The export could not load ${notCopied.join(" and ")}: on Windows an engine running as the editor, which an export is, loads a GDExtension from a ~ copy beside the library, and another engine on this project held that copy: it could not be moved aside before the export, or another engine made a new one in between. Export again once that engine has loaded or exited.`
@@ -51085,7 +51333,7 @@ function unsavedInEditorNote(scripts) {
51085
51333
  return `${named} ${one ? "is" : "are"} open in the editor's script ${one ? "tab" : "tabs"} with unsaved edits, and saving ${one ? "that tab" : "them"} there writes ${one ? "its" : "their"} text over what was written here: close ${one ? "it" : "them"} without saving, or save first and edit again.`;
51086
51334
  }
51087
51335
  function editorAddonMissing(projectPath) {
51088
- if (!existsSync35(join44(projectPath, "project.godot"))) {
51336
+ if (!existsSync34(join44(projectPath, "project.godot"))) {
51089
51337
  return null;
51090
51338
  }
51091
51339
  const report = inspectProject(projectPath);
@@ -51114,7 +51362,7 @@ function rewriteBusNamed(projectPath, from, to) {
51114
51362
  const rewritten = [];
51115
51363
  for (const file of projectFiles(projectPath, [".tscn", ".tres"])) {
51116
51364
  const absolute = join44(projectPath, file);
51117
- const lines = readFileSync38(absolute, "utf8").split(`
51365
+ const lines = readFileSync40(absolute, "utf8").split(`
51118
51366
  `);
51119
51367
  const at = [];
51120
51368
  const after = lines.map((line, index) => {
@@ -51133,7 +51381,7 @@ function rewriteBusNamed(projectPath, from, to) {
51133
51381
  }
51134
51382
  const namedInScripts = [];
51135
51383
  for (const file of projectFiles(projectPath, [".gd"])) {
51136
- readFileSync38(join44(projectPath, file), "utf8").split(`
51384
+ readFileSync40(join44(projectPath, file), "utf8").split(`
51137
51385
  `).forEach((line, index) => {
51138
51386
  if (line.includes(`"${from}"`)) {
51139
51387
  namedInScripts.push({ file: `res://${file}`, line: index + 1, text: line.trim() });
@@ -51418,7 +51666,7 @@ function profileFrameFunctions(projectPath) {
51418
51666
  let projectGodot = null;
51419
51667
  if (projectPath !== null) {
51420
51668
  try {
51421
- projectGodot = readFileSync38(join44(projectPath, "project.godot"), "utf8");
51669
+ projectGodot = readFileSync40(join44(projectPath, "project.godot"), "utf8");
51422
51670
  } catch {}
51423
51671
  }
51424
51672
  return frameFunctionsFor(queuedMessagesOf(projectGodot));
@@ -51442,7 +51690,7 @@ function projectSourceOf(projectPath) {
51442
51690
  let source = null;
51443
51691
  if (inside.ok) {
51444
51692
  try {
51445
- source = readFileSync38(inside.absolutePath, "utf8");
51693
+ source = readFileSync40(inside.absolutePath, "utf8");
51446
51694
  } catch {}
51447
51695
  }
51448
51696
  read.set(script, source);
@@ -51614,7 +51862,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
51614
51862
  }
51615
51863
  function gdUnitFailsOnScriptErrors(projectPath) {
51616
51864
  try {
51617
- return settingKeys(readFileSync38(join44(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
51865
+ return settingKeys(readFileSync40(join44(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
51618
51866
  } catch {
51619
51867
  return true;
51620
51868
  }
@@ -51629,7 +51877,7 @@ function uncaughtNote(uncaught) {
51629
51877
  }
51630
51878
  function elsewhereIn(projectPath, asked) {
51631
51879
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
51632
- if (wanted === "" || existsSync35(join44(projectPath, wanted))) {
51880
+ if (wanted === "" || existsSync34(join44(projectPath, wanted))) {
51633
51881
  return "";
51634
51882
  }
51635
51883
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
@@ -51652,7 +51900,7 @@ function windowNoteFor(needing, sharing) {
51652
51900
  return `${named(needing)} ${one ? "needs" : "need"} a window, as gdharness test reads ${one ? "it" : "them"}, so the run had one: the engine ran without --headless, at the size project.godot gives its viewport, with no sound${process.platform === "win32" ? ", on a desktop of its own, where its window neither shows nor takes the keyboard" : ""}. ${why}${shared}`;
51653
51901
  }
51654
51902
  function testPathsIn(projectPath, given, named = "path") {
51655
- const defaults = TEST_DIRECTORIES.filter((directory) => existsSync35(join44(projectPath, directory)));
51903
+ const defaults = TEST_DIRECTORIES.filter((directory) => existsSync34(join44(projectPath, directory)));
51656
51904
  const entries = given === undefined || given === null || given === "" ? defaults.length > 0 ? defaults : TEST_DIRECTORIES : given;
51657
51905
  const listed = Array.isArray(entries) ? entries : [entries];
51658
51906
  if (listed.length === 0) {
@@ -51761,11 +52009,11 @@ function isDirectory(path) {
51761
52009
  }
51762
52010
  }
51763
52011
  function mainSceneOf(projectFile) {
51764
- const scene = parseProjectGodot(readFileSync38(projectFile, "utf8"))["application"]?.["run/main_scene"];
52012
+ const scene = parseProjectGodot(readFileSync40(projectFile, "utf8"))["application"]?.["run/main_scene"];
51765
52013
  return typeof scene === "string" && scene !== "" ? scene : null;
51766
52014
  }
51767
52015
  function editorPlaysHeadless(projectFile) {
51768
- const runArgs = parseProjectGodot(readFileSync38(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
52016
+ const runArgs = parseProjectGodot(readFileSync40(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
51769
52017
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
51770
52018
  }
51771
52019
  function ownArguments(params) {
@@ -51939,7 +52187,7 @@ class GodotServer {
51939
52187
  taken = true;
51940
52188
  this.logDebug(`Took port ${wanted} over from pid ${announced.pid}`);
51941
52189
  } catch {
51942
- await delay7(HANDOVER_POLL_MS);
52190
+ await delay8(HANDOVER_POLL_MS);
51943
52191
  }
51944
52192
  }
51945
52193
  this.notHandedOverBy = taken ? null : announced.pid;
@@ -52077,7 +52325,7 @@ class GodotServer {
52077
52325
  argv: process.argv.slice(1),
52078
52326
  cwd,
52079
52327
  project: this.ownProject,
52080
- mcpJson: existsSync35(mcpJson) ? mcpJson : null
52328
+ mcpJson: existsSync34(mcpJson) ? mcpJson : null
52081
52329
  });
52082
52330
  if (predecessor !== null) {
52083
52331
  writeLifecycle("replaced", { project: this.ownProject, predecessor });
@@ -52219,7 +52467,7 @@ class GodotServer {
52219
52467
  return checked.response;
52220
52468
  }
52221
52469
  const named = args["projectPath"];
52222
- if (typeof named === "string" && existsSync35(join44(named, "project.godot"))) {
52470
+ if (typeof named === "string" && existsSync34(join44(named, "project.godot"))) {
52223
52471
  this.lastProjectPath = named;
52224
52472
  }
52225
52473
  this.updates.refresh();
@@ -52855,7 +53103,7 @@ class GodotServer {
52855
53103
  return this.createErrorResponse("line is required: the one-based line to stop on.");
52856
53104
  }
52857
53105
  if (op === "set") {
52858
- if (!existsSync35(located.absolutePath)) {
53106
+ if (!existsSync34(located.absolutePath)) {
52859
53107
  return this.missingScript(project.value.path, located.relativePath);
52860
53108
  }
52861
53109
  const nothing = lineNothingStopsOn(located.absolutePath, located.relativePath, line);
@@ -52960,7 +53208,7 @@ class GodotServer {
52960
53208
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
52961
53209
  }
52962
53210
  const file = join44(path, "project.godot");
52963
- if (!existsSync35(file)) {
53211
+ if (!existsSync34(file)) {
52964
53212
  return {
52965
53213
  ok: false,
52966
53214
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -53707,7 +53955,7 @@ class GodotServer {
53707
53955
  if (!engine.ok) {
53708
53956
  return engine.response;
53709
53957
  }
53710
- const application = parseProjectGodot(readFileSync38(project.value.file, "utf8"))["application"] ?? {};
53958
+ const application = parseProjectGodot(readFileSync40(project.value.file, "utf8"))["application"] ?? {};
53711
53959
  const name = application["config/name"];
53712
53960
  const mainScene = application["run/main_scene"];
53713
53961
  const info = {
@@ -53786,7 +54034,7 @@ class GodotServer {
53786
54034
  if (place !== null && !place.ok) {
53787
54035
  return this.createErrorResponse(place.reason);
53788
54036
  }
53789
- if (place !== null && !existsSync35(place.absolutePath)) {
54037
+ if (place !== null && !existsSync34(place.absolutePath)) {
53790
54038
  const folders = readdirSync20(project.value.path, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
53791
54039
  return this.createErrorResponse(`There is nothing at res://${place.relativePath} to search.${folders.length === 0 ? "" : ` The project's folders are ${folders.join(", ")}.`}`);
53792
54040
  }
@@ -53964,6 +54212,7 @@ ${platform}`;
53964
54212
  if (pack && extension !== ".pck" && extension !== ".zip") {
53965
54213
  return this.createErrorResponse(`A pack is written as a .pck or a .zip, and ${output.relativePath} is neither: the exporter picks the format by the extension.`, ["Give outputPath ending in .pck, or .zip for a zip of the resources"]);
53966
54214
  }
54215
+ const imported = await this.importWhatChanged(project.value.path);
53967
54216
  const exportLogs = scratchDirectory("export");
53968
54217
  const exportArgs = [
53969
54218
  "--headless",
@@ -53976,7 +54225,7 @@ ${platform}`;
53976
54225
  output.absolutePath
53977
54226
  ];
53978
54227
  this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
53979
- const writtenAt = () => existsSync35(output.absolutePath) ? statSync20(output.absolutePath).mtimeMs : null;
54228
+ const writtenAt = () => existsSync34(output.absolutePath) ? statSync20(output.absolutePath).mtimeMs : null;
53980
54229
  const caches = editorCacheDirectory(engine.value);
53981
54230
  const exportOnce = async () => {
53982
54231
  const before = writtenAt();
@@ -54006,7 +54255,7 @@ ${platform}`;
54006
54255
  return this.createErrorResponse(`Export could not be run: ${ending.failure ?? "the engine gave no exit status"}`);
54007
54256
  }
54008
54257
  const beside = writtenBeside(listedBefore, filesIn(folder), basename14(output.absolutePath), dirname25(output.relativePath).replaceAll("\\", "/"));
54009
- return exportAnswer(ending, { preset, outputPath: output.relativePath, debug, ...pack ? { pack } : {} }, before, writtenAt(), beside, docCacheNote, existsSync35(output.absolutePath) ? statSync20(output.absolutePath).size : null);
54258
+ return exportAnswer(ending, { preset, outputPath: output.relativePath, debug, ...pack ? { pack } : {} }, before, writtenAt(), beside, docCacheNote, existsSync34(output.absolutePath) ? statSync20(output.absolutePath).size : null, importedFirstAnswer(imported));
54010
54259
  }
54011
54260
  reportTestProgress(log) {
54012
54261
  const progress = callProgress();
@@ -54067,7 +54316,7 @@ ${platform}`;
54067
54316
  if (movie !== null) {
54068
54317
  return this.createErrorResponse(movie);
54069
54318
  }
54070
- const missing = paths.value.filter((path) => !existsSync35(join44(project.value.path, path.slice("res://".length))));
54319
+ const missing = paths.value.filter((path) => !existsSync34(join44(project.value.path, path.slice("res://".length))));
54071
54320
  const projectScripts = new ProjectScripts(project.value.path);
54072
54321
  const present = paths.value.filter((path) => !missing.includes(path));
54073
54322
  const asked = projectScripts.searchedAsTold(present);
@@ -54259,7 +54508,7 @@ ${platform}`;
54259
54508
  await untilGone([run.process.pid]);
54260
54509
  }
54261
54510
  await run.settled;
54262
- const helperFailed = onDesktop ? helperFailure(helperFiles.error) ?? (existsSync35(helperFiles.pid) ? null : `it never started, and ${helperStep(helperFiles.step)}`) : null;
54511
+ const helperFailed = onDesktop ? helperFailure(helperFiles.error) ?? (existsSync34(helperFiles.pid) ? null : `it never started, and ${helperStep(helperFiles.step)}`) : null;
54263
54512
  if (helperFailed !== null) {
54264
54513
  discard(join44(project.value.path, ".godot", ...ours.split("/")), userData);
54265
54514
  return this.createErrorResponse(`No tests ran: ${named(needing)} ${needing.length === 1 ? "needs" : "need"} a window, and the engine could not be started on a desktop of its own: ${helperFailed}`);
@@ -54275,13 +54524,13 @@ ${platform}`;
54275
54524
  const reportsDir = join44(project.value.path, ".godot", ...ours.split("/"));
54276
54525
  let report = null;
54277
54526
  let reportProblem = null;
54278
- const casePrinted = withNestedErrors(readRun(existsSync35(engineLog) ? readFileSync38(engineLog, "utf8") : ""), run.log.all);
54527
+ const casePrinted = withNestedErrors(readRun(existsSync34(engineLog) ? readFileSync40(engineLog, "utf8") : ""), run.log.all);
54279
54528
  const declaredErrors = readExpected(expectedFile);
54280
54529
  try {
54281
- const written = existsSync35(reportsDir) && notCompiled === null ? readdirSync20(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
54530
+ const written = existsSync34(reportsDir) && notCompiled === null ? readdirSync20(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
54282
54531
  const newest = written.at(-1);
54283
54532
  if (newest !== undefined) {
54284
- report = parseJUnit(readFileSync38(join44(reportsDir, newest, "results.xml"), "utf8"));
54533
+ report = parseJUnit(readFileSync40(join44(reportsDir, newest, "results.xml"), "utf8"));
54285
54534
  }
54286
54535
  } catch (error) {
54287
54536
  reportProblem = errorMessage(error);
@@ -54605,7 +54854,7 @@ ${error.text}`;
54605
54854
  if (!location.ok) {
54606
54855
  return this.createErrorResponse(location.reason, PATH_SOLUTIONS);
54607
54856
  }
54608
- if (!existsSync35(location.absolutePath)) {
54857
+ if (!existsSync34(location.absolutePath)) {
54609
54858
  return this.missingScript(project.value.path, location.relativePath);
54610
54859
  }
54611
54860
  ignored = scriptsUnderGdignore(project.value.path, [location.relativePath]);
@@ -54695,7 +54944,7 @@ ${error.text}`;
54695
54944
  const wanted = `res://${relativePath}`;
54696
54945
  const folder = dirname25(relativePath.replaceAll("\\", "/"));
54697
54946
  const inFolder = join44(projectPath, folder);
54698
- const beside = existsSync35(inFolder) ? readdirSync20(inFolder, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => `res://${folder === "." ? "" : `${folder}/`}${entry.name}`) : [];
54947
+ const beside = existsSync34(inFolder) ? readdirSync20(inFolder, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => `res://${folder === "." ? "" : `${folder}/`}${entry.name}`) : [];
54699
54948
  return this.createErrorResponse(withNearestFiles(`Script file does not exist: ${wanted}`, wanted, scripts, beside));
54700
54949
  }
54701
54950
  async compileScripts(projectPath, paths) {
@@ -54823,11 +55072,11 @@ ${error.text}`;
54823
55072
  const cached = cachedClasses(projectPath);
54824
55073
  const sourceOf = (resourcePath) => {
54825
55074
  const contained = resolveWithinProject(projectPath, resourcePath);
54826
- if (!contained.ok || !existsSync35(contained.absolutePath)) {
55075
+ if (!contained.ok || !existsSync34(contained.absolutePath)) {
54827
55076
  return null;
54828
55077
  }
54829
55078
  try {
54830
- return readFileSync38(contained.absolutePath, "utf8");
55079
+ return readFileSync40(contained.absolutePath, "utf8");
54831
55080
  } catch {
54832
55081
  return null;
54833
55082
  }
@@ -54917,11 +55166,11 @@ ${error.text}`;
54917
55166
  }
54918
55167
  sourceOfClass(projectPath, resourcePath) {
54919
55168
  const contained = resolveWithinProject(projectPath, resourcePath);
54920
- if (!contained.ok || !existsSync35(contained.absolutePath)) {
55169
+ if (!contained.ok || !existsSync34(contained.absolutePath)) {
54921
55170
  return null;
54922
55171
  }
54923
55172
  try {
54924
- return readFileSync38(contained.absolutePath, "utf8");
55173
+ return readFileSync40(contained.absolutePath, "utf8");
54925
55174
  } catch {
54926
55175
  return null;
54927
55176
  }
@@ -55219,7 +55468,7 @@ ${error.text}`;
55219
55468
  try {
55220
55469
  const askedAt = Date.now();
55221
55470
  const asked = this.godotBridge.invokeTool("playing_status", {}, { counted: false });
55222
- const answer = await Promise.race([asked, delay7(PLAYING_STATUS_MS).then(() => null)]);
55471
+ const answer = await Promise.race([asked, delay8(PLAYING_STATUS_MS).then(() => null)]);
55223
55472
  if (answer === null) {
55224
55473
  this.logDebug(`playing_status did not answer within ${PLAYING_STATUS_MS}ms`);
55225
55474
  asked.then(() => {
@@ -55244,7 +55493,7 @@ ${error.text}`;
55244
55493
  try {
55245
55494
  const answer = await Promise.race([
55246
55495
  this.godotBridge.invokeTool("scan_status", {}, { counted: false }),
55247
- delay7(PLAYING_STATUS_MS).then(() => null)
55496
+ delay8(PLAYING_STATUS_MS).then(() => null)
55248
55497
  ]);
55249
55498
  if (answer === null) {
55250
55499
  return null;
@@ -55386,7 +55635,7 @@ ${error.text}`;
55386
55635
  if (answer.serversNote === undefined || adapterFailed || Date.now() >= deadline) {
55387
55636
  return answer;
55388
55637
  }
55389
- await delay7(500);
55638
+ await delay8(500);
55390
55639
  }
55391
55640
  }
55392
55641
  async rebindTheDebugAdapter(servers) {
@@ -55440,7 +55689,7 @@ ${error.text}`;
55440
55689
  return null;
55441
55690
  }
55442
55691
  try {
55443
- return settingKeys(readFileSync38(join44(projectPath, "project.godot"), "utf8"));
55692
+ return settingKeys(readFileSync40(join44(projectPath, "project.godot"), "utf8"));
55444
55693
  } catch {
55445
55694
  return null;
55446
55695
  }
@@ -55647,7 +55896,7 @@ ${error.text}`;
55647
55896
  if (Date.now() >= deadline) {
55648
55897
  return false;
55649
55898
  }
55650
- await delay7(250);
55899
+ await delay8(250);
55651
55900
  }
55652
55901
  }
55653
55902
  async handleLaunchEditor(args) {
@@ -55785,7 +56034,7 @@ ${error.text}`;
55785
56034
  if (!paths.ok) {
55786
56035
  return refused(paths.reason, PATH_SOLUTIONS);
55787
56036
  }
55788
- const missing = paths.value.filter((path) => !existsSync35(join44(projectPath, path.slice("res://".length))));
56037
+ const missing = paths.value.filter((path) => !existsSync34(join44(projectPath, path.slice("res://".length))));
55789
56038
  if (missing.length > 0) {
55790
56039
  return refused(`Nothing at ${missing.join(" and ")} in the project at ${projectPath}.`, PATH_SOLUTIONS);
55791
56040
  }
@@ -55830,7 +56079,7 @@ ${error.text}`;
55830
56079
  return this.createErrorResponse(sceneToRun.reason, PATH_SOLUTIONS);
55831
56080
  }
55832
56081
  const build = readNonEmptyString(args, "executable");
55833
- if (build === undefined && sceneToRun?.ok && !existsSync35(sceneToRun.absolutePath)) {
56082
+ if (build === undefined && sceneToRun?.ok && !existsSync34(sceneToRun.absolutePath)) {
55834
56083
  return this.createErrorResponse(`No scene at res://${sceneToRun.relativePath} in the project at ${project.value.path}.`, PATH_SOLUTIONS);
55835
56084
  }
55836
56085
  const exported = build === undefined ? null : exportedBuild(project.value.path, build);
@@ -55958,7 +56207,7 @@ ${error.text}`;
55958
56207
  }
55959
56208
  async runtimeUp(projectPath, before, budget, withArgs) {
55960
56209
  const budgetMs = budget.ms;
55961
- if (!existsSync35(join44(projectPath, RUNTIME_SCRIPT))) {
56210
+ if (!existsSync34(join44(projectPath, RUNTIME_SCRIPT))) {
55962
56211
  return runtimeVerdict(null, {
55963
56212
  addon: false,
55964
56213
  budgetMs,
@@ -56096,7 +56345,7 @@ ${error.text}`;
56096
56345
  const sendingFrom = Date.now();
56097
56346
  const breakpoints = await this.dapHoldingBreakpointsOf(projectPath).reapplyBreakpoints();
56098
56347
  this.logDebug(`Breakpoints were sent again after ${Date.now() - sendingFrom}ms`);
56099
- const slot = await slotForCall();
56348
+ const slot = await slotForCall(true);
56100
56349
  if (profiled) {
56101
56350
  const armed = await this.handleViaBridge("profile_start", {
56102
56351
  arm: true,
@@ -56276,7 +56525,7 @@ ${error.text}`;
56276
56525
  return 0;
56277
56526
  }
56278
56527
  try {
56279
- return reportAlreadyInTranscript(readFileSync38(game.transcript), readFileSync38(report));
56528
+ return reportAlreadyInTranscript(readFileSync40(game.transcript), readFileSync40(report));
56280
56529
  } catch (error) {
56281
56530
  this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
56282
56531
  return 0;
@@ -56299,7 +56548,7 @@ ${error.text}`;
56299
56548
  if (Date.now() >= deadline) {
56300
56549
  return false;
56301
56550
  }
56302
- await delay7(100);
56551
+ await delay8(100);
56303
56552
  }
56304
56553
  }
56305
56554
  async endActiveGame(reason, going, graceful) {
@@ -56559,11 +56808,11 @@ ${error.text}`;
56559
56808
  return Object.assign(started, { settled, stop });
56560
56809
  }
56561
56810
  async launchKeptGame(godotPath, cmdArgs, projectPath, env, desktop, profiled = false, debugged = true) {
56811
+ const slot = await slotForCall(true);
56562
56812
  const startedAt = Date.now();
56563
56813
  const transcript = openTranscript(startedAt);
56564
56814
  closeSync6(transcript.fd);
56565
56815
  const profile = profiled ? profilePath(startedAt) : undefined;
56566
- const slot = await slotForCall();
56567
56816
  const gameEnv = onTheSlot(profiled ? { ...env ?? process.env, [PROFILE_FROM_START]: "1" } : env ?? process.env, slot.lock);
56568
56817
  let launched;
56569
56818
  try {
@@ -57033,7 +57282,7 @@ ${error.text}`;
57033
57282
  };
57034
57283
  const going = await this.runStillGoing(run);
57035
57284
  const projectPath = run.projectPath;
57036
- const projectGone = projectPath !== null && !existsSync35(join44(projectPath, "project.godot"));
57285
+ const projectGone = projectPath !== null && !existsSync34(join44(projectPath, "project.godot"));
57037
57286
  if (projectGone && callersName !== undefined) {
57038
57287
  return this.createErrorResponse(`callersOf reads the project's scripts, and the run's project at ${projectPath} has no project.godot any more.`, ["editor_run profile without callersOf answers with the profile itself"]);
57039
57288
  }
@@ -57191,7 +57440,7 @@ ${error.text}`;
57191
57440
  return true;
57192
57441
  }
57193
57442
  }
57194
- await delay7(100);
57443
+ await delay8(100);
57195
57444
  }
57196
57445
  return false;
57197
57446
  }
@@ -57211,7 +57460,7 @@ ${error.text}`;
57211
57460
  while (cancelled?.aborted !== true && await this.runStillGoing(run) && Date.now() < until) {
57212
57461
  this.drainEditorOutput(run);
57213
57462
  this.drainTranscript(run);
57214
- await delay7(RUN_POLL_MS);
57463
+ await delay8(RUN_POLL_MS);
57215
57464
  }
57216
57465
  return await this.handleGetDebugOutput(args, await this.runStillGoing(run) ? budgetMs : undefined);
57217
57466
  }
@@ -57264,7 +57513,7 @@ ${error.text}`;
57264
57513
  if (Date.now() >= until) {
57265
57514
  return false;
57266
57515
  }
57267
- await delay7(50);
57516
+ await delay8(50);
57268
57517
  }
57269
57518
  }
57270
57519
  async handleEditorConsole(args) {
@@ -57418,7 +57667,7 @@ ${error.text}`;
57418
57667
  notes.push(endedWithoutACode({
57419
57668
  throughEditor: run.throughEditor,
57420
57669
  announced: announced !== undefined || run.announcedPid !== undefined,
57421
- couldAnnounce: run.projectPath !== null && existsSync35(join44(run.projectPath, RUNTIME_SCRIPT)),
57670
+ couldAnnounce: run.projectPath !== null && existsSync34(join44(run.projectPath, RUNTIME_SCRIPT)),
57422
57671
  endedHere: typeof run.endedHere === "string",
57423
57672
  pickedUp: run.pickedUp === true
57424
57673
  }));
@@ -58300,7 +58549,7 @@ ${error.text}`;
58300
58549
  if (!scriptPath.ok) {
58301
58550
  return this.createErrorResponse(scriptPath.reason);
58302
58551
  }
58303
- if (!existsSync35(scriptPath.absolutePath)) {
58552
+ if (!existsSync34(scriptPath.absolutePath)) {
58304
58553
  return this.missingScript(projectPath, scriptPath.relativePath);
58305
58554
  }
58306
58555
  const asked = readNonEmptyString(args, "newScriptPath");
@@ -58747,7 +58996,7 @@ ${error.text}`;
58747
58996
  }
58748
58997
  const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
58749
58998
  const declared = new RegExp(`^\\s*(?:@\\w+(?:\\([^)]*\\))?\\s+)*class_name\\s+${escaped}\\b`, "m");
58750
- if (declared.test(readFileSync38(scriptPath, "utf8"))) {
58999
+ if (declared.test(readFileSync40(scriptPath, "utf8"))) {
58751
59000
  return null;
58752
59001
  }
58753
59002
  const checked = await this.classesTheEditorCannotSee(args);
@@ -58944,7 +59193,7 @@ ${error.text}`;
58944
59193
  async ownRunOnItsWay(projectPath, budgetMs) {
58945
59194
  const run = await this.currentRun();
58946
59195
  const project = projectPath ?? run?.projectPath ?? null;
58947
- if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync35(join44(project, RUNTIME_SCRIPT)) || !await this.runStillGoing(run)) {
59196
+ if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync34(join44(project, RUNTIME_SCRIPT)) || !await this.runStillGoing(run)) {
58948
59197
  return null;
58949
59198
  }
58950
59199
  const already = runtimesAnnounced().running;
@@ -59397,7 +59646,7 @@ var init_server = __esm(() => {
59397
59646
  init_bridge_announce();
59398
59647
  init_engine_launch();
59399
59648
  init_engine_slots();
59400
- import { existsSync as existsSync36 } from "node:fs";
59649
+ import { existsSync as existsSync35 } from "node:fs";
59401
59650
  import { dirname as dirname26, join as join45, resolve as resolve15 } from "node:path";
59402
59651
 
59403
59652
  // src/error-mode.ts
@@ -59436,7 +59685,7 @@ init_alive();
59436
59685
  init_engine_launch();
59437
59686
  init_lsp_client();
59438
59687
  init_process_children();
59439
- import { existsSync as existsSync18, readdirSync as readdirSync11, readFileSync as readFileSync17, unlinkSync as unlinkSync2 } from "node:fs";
59688
+ import { existsSync as existsSync17, readdirSync as readdirSync11, readFileSync as readFileSync19, unlinkSync as unlinkSync2 } from "node:fs";
59440
59689
  import { connect as connect2 } from "node:net";
59441
59690
  import { join as join21, resolve as resolve8 } from "node:path";
59442
59691
  var EDITOR_RECORD_PROTOCOL = 1;
@@ -59455,7 +59704,7 @@ function editorRecordsIn(projectPath) {
59455
59704
  for (const name of names) {
59456
59705
  const file = join21(directory, name);
59457
59706
  try {
59458
- const said = JSON.parse(readFileSync17(file, "utf8"));
59707
+ const said = JSON.parse(readFileSync19(file, "utf8"));
59459
59708
  const { protocol, pid, lsp_port: lspPort, written_at_ms: writtenAtMs } = said;
59460
59709
  if (protocol === EDITOR_RECORD_PROTOCOL && typeof pid === "number" && typeof lspPort === "number" && lspPort > 0 && typeof writtenAtMs === "number") {
59461
59710
  records.push({ file, pid, lspPort, writtenAtMs });
@@ -59548,7 +59797,7 @@ function lspOptionsOf(args, cwd) {
59548
59797
  }
59549
59798
  }
59550
59799
  const projectPath = resolve8(cwd, named ?? ".");
59551
- if (!existsSync18(join21(projectPath, "project.godot"))) {
59800
+ if (!existsSync17(join21(projectPath, "project.godot"))) {
59552
59801
  throw new EngineUsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
59553
59802
  }
59554
59803
  return { projectPath, port, waitSeconds };
@@ -59655,7 +59904,7 @@ init_setup();
59655
59904
  init_setup();
59656
59905
  init_tool_definitions();
59657
59906
  init_tool_reference();
59658
- import { existsSync as existsSync20, mkdirSync as mkdirSync7, readdirSync as readdirSync13, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
59907
+ import { existsSync as existsSync19, mkdirSync as mkdirSync7, readdirSync as readdirSync13, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
59659
59908
  import { basename as basename9, dirname as dirname10, join as join23 } from "node:path";
59660
59909
  var SKILL_NAME = "gdharness";
59661
59910
  var SHARED_SKILLS = join23(".agents", "skills");
@@ -59927,7 +60176,7 @@ function everySkillDirectory(projectPath, harnesses) {
59927
60176
  function removeSkill(directories, projectPath) {
59928
60177
  const removed = [];
59929
60178
  for (const directory of directories) {
59930
- if (existsSync20(directory)) {
60179
+ if (existsSync19(directory)) {
59931
60180
  rmSync11(directory, { recursive: true, force: true });
59932
60181
  pruneEmpty(dirname10(directory), projectPath);
59933
60182
  removed.push(directory);
@@ -59949,7 +60198,7 @@ function pruneEmpty(directory, projectPath) {
59949
60198
  function writeSkill(directories, version) {
59950
60199
  const files = skillFiles(version);
59951
60200
  return directories.map((directory) => {
59952
- const replaced = existsSync20(directory);
60201
+ const replaced = existsSync19(directory);
59953
60202
  rmSync11(directory, { recursive: true, force: true });
59954
60203
  for (const [name, contents] of files) {
59955
60204
  const path = join23(directory, name);
@@ -59966,8 +60215,8 @@ init_engine_slots();
59966
60215
  init_log_read();
59967
60216
  init_setup();
59968
60217
  init_suite_judge();
59969
- import { existsSync as existsSync21, mkdirSync as mkdirSync8, readFileSync as readFileSync20, rmSync as rmSync12 } from "node:fs";
59970
- import { availableParallelism as availableParallelism3 } from "node:os";
60218
+ import { existsSync as existsSync20, mkdirSync as mkdirSync8, readFileSync as readFileSync22, rmSync as rmSync12 } from "node:fs";
60219
+ import { availableParallelism as availableParallelism4 } from "node:os";
59971
60220
  import { join as join24, resolve as resolve10 } from "node:path";
59972
60221
  import process13 from "node:process";
59973
60222
  var SHARE = "--gdh-share=";
@@ -60008,9 +60257,9 @@ function spreadOptionsOf(args, cwd) {
60008
60257
  }
60009
60258
  const first = positional[0];
60010
60259
  const named = first === undefined ? null : resolve10(cwd, first);
60011
- const projectNamed = named !== null && existsSync21(join24(named, "project.godot"));
60260
+ const projectNamed = named !== null && existsSync20(join24(named, "project.godot"));
60012
60261
  const projectPath = projectNamed ? named : resolve10(cwd);
60013
- if (!existsSync21(join24(projectPath, "project.godot"))) {
60262
+ if (!existsSync20(join24(projectPath, "project.godot"))) {
60014
60263
  throw new EngineUsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
60015
60264
  }
60016
60265
  const scenes = positional.slice(projectNamed ? 1 : 0);
@@ -60018,7 +60267,7 @@ function spreadOptionsOf(args, cwd) {
60018
60267
  throw new EngineUsageError(scenes.length === 0 ? "gdharness spread needs the scene of the bench to play." : `gdharness spread plays one scene, and was given ${String(scenes.length)}: ${scenes.join(", ")}.`);
60019
60268
  }
60020
60269
  const scene = resPathOf(scenes[0] ?? "", projectPath, cwd);
60021
- if (!existsSync21(join24(projectPath, scene.slice("res://".length)))) {
60270
+ if (!existsSync20(join24(projectPath, scene.slice("res://".length)))) {
60022
60271
  throw new EngineUsageError(`Nothing at ${scene} in the project at ${projectPath}.`);
60023
60272
  }
60024
60273
  const benchArguments = dashes === -1 ? [] : args.slice(dashes + 1);
@@ -60089,7 +60338,7 @@ function shareFaults(played) {
60089
60338
  return faults;
60090
60339
  }
60091
60340
  async function runSpreadCommand(options, godotPath) {
60092
- if (!existsSync21(join24(options.projectPath, SHARE_SCRIPT))) {
60341
+ if (!existsSync20(join24(options.projectPath, SHARE_SCRIPT))) {
60093
60342
  throw new EngineUsageError(`res://${SHARE_SCRIPT} is not in the project, so its bench cannot ask GdhShare for outcomes. Run gdharness setup.`);
60094
60343
  }
60095
60344
  const startedAt = Date.now();
@@ -60123,7 +60372,7 @@ async function runSpreadCommand(options, godotPath) {
60123
60372
  let workers = options.workers;
60124
60373
  if (workers === null) {
60125
60374
  const memory = await memoryAvailable();
60126
- const processors = options.cores ?? availableParallelism3();
60375
+ const processors = options.cores ?? availableParallelism4();
60127
60376
  const room = roomFor(slotCount, processors, memory.bytes, memory.said);
60128
60377
  workers = room.engines;
60129
60378
  console.log(`${options.scene} over ${plural(workers, "worker")}${room.held === undefined ? "" : `, since ${room.held}`}.`);
@@ -60154,7 +60403,7 @@ async function runSpreadCommand(options, godotPath) {
60154
60403
  const faults = left.flatMap((worker) => engineFaults(worker, options.godotVersion));
60155
60404
  const arms = left.map((_, index) => {
60156
60405
  const file = join24(shareDirectory, `worker-${String(index)}.arms`);
60157
- return existsSync21(file) ? armsPlayed(readFileSync20(file, "utf8")) : [];
60406
+ return existsSync20(file) ? armsPlayed(readFileSync22(file, "utf8")) : [];
60158
60407
  });
60159
60408
  faults.push(...shareFaults(arms));
60160
60409
  if (faults.length > 0) {
@@ -60164,7 +60413,7 @@ async function runSpreadCommand(options, godotPath) {
60164
60413
  console.log(readLog(gather.log).replace(CONTROL_SEQUENCE, "").trimEnd());
60165
60414
  const gatherFaults = engineFaults(gather, options.godotVersion);
60166
60415
  const gatheredFile = join24(shareDirectory, "gathered.arms");
60167
- const gathered = new Set((existsSync21(gatheredFile) ? armsPlayed(readFileSync20(gatheredFile, "utf8")) ?? [] : []).map((arm) => arm.arm));
60416
+ const gathered = new Set((existsSync20(gatheredFile) ? armsPlayed(readFileSync22(gatheredFile, "utf8")) ?? [] : []).map((arm) => arm.arm));
60168
60417
  const ungathered = (arms[0] ?? []).filter((arm) => !gathered.has(arm.arm)).map((arm) => arm.arm);
60169
60418
  if (ungathered.length > 0) {
60170
60419
  gatherFaults.push(`the gathering run never asked for ${ungathered.join(", ")}, which the workers played: it went another way through the arms than they did`);
@@ -60215,7 +60464,7 @@ function engineFaults(engine, godotVersion) {
60215
60464
  const said = code === null ? "without an exit code" : code >>> 0 >= WINDOWS_EXCEPTION2 ? exitNamed(code) : String(code);
60216
60465
  faults.push(`${engine.name}: the engine exited ${said}`);
60217
60466
  }
60218
- if (!existsSync21(engine.log)) {
60467
+ if (!existsSync20(engine.log)) {
60219
60468
  return faults;
60220
60469
  }
60221
60470
  const printed = readRun(readLog(engine.log));
@@ -60231,7 +60480,7 @@ function engineFaults(engine, godotVersion) {
60231
60480
  }
60232
60481
  function progressOf(logs) {
60233
60482
  const each = logs.map((log, index) => {
60234
- const text = existsSync21(log) ? readLog(log) : "";
60483
+ const text = existsSync20(log) ? readLog(log) : "";
60235
60484
  const last = [...text.matchAll(PROGRESS)].at(-1);
60236
60485
  return last === undefined ? `worker ${String(index)} has played no arm yet` : `worker ${String(index)} at ${last[1] ?? ""} ${last[2] ?? ""} of ${last[3] ?? ""}`;
60237
60486
  });
@@ -60263,23 +60512,24 @@ init_reimport();
60263
60512
  init_setup();
60264
60513
  import {
60265
60514
  copyFileSync,
60266
- existsSync as existsSync24,
60515
+ existsSync as existsSync23,
60267
60516
  mkdirSync as mkdirSync10,
60268
60517
  readdirSync as readdirSync15,
60269
- readFileSync as readFileSync24,
60518
+ readFileSync as readFileSync26,
60270
60519
  rmSync as rmSync13,
60271
60520
  statSync as statSync14,
60272
60521
  writeFileSync as writeFileSync13
60273
60522
  } from "node:fs";
60274
- import { availableParallelism as availableParallelism4 } from "node:os";
60523
+ import { availableParallelism as availableParallelism5 } from "node:os";
60275
60524
  import { dirname as dirname13, join as join28, relative as relative5, resolve as resolve11, sep as sep4 } from "node:path";
60276
60525
  import process14 from "node:process";
60526
+ import { setTimeout as delay5 } from "node:timers/promises";
60277
60527
 
60278
60528
  // src/suite-inputs.ts
60279
60529
  init_class_cache();
60280
60530
  init_gdscript_source();
60281
60531
  import { createHash as createHash6 } from "node:crypto";
60282
- import { existsSync as existsSync22, readdirSync as readdirSync14, readFileSync as readFileSync21, statSync as statSync13 } from "node:fs";
60532
+ import { existsSync as existsSync21, readdirSync as readdirSync14, readFileSync as readFileSync23, statSync as statSync13 } from "node:fs";
60283
60533
  import { join as join25, posix as posix2 } from "node:path";
60284
60534
  var EVERY_FILE = "res://";
60285
60535
  var CACHE_HELD = "cache:";
@@ -60297,13 +60547,13 @@ var ADDON = /^res:\/\/addons\/[^/]+\//;
60297
60547
  var TEXT_RESOURCE = /\.(?:tscn|tres|gdextension)$/;
60298
60548
  function reachedBySuite(file) {
60299
60549
  const bySuite = new Map;
60300
- if (!existsSync22(file)) {
60550
+ if (!existsSync21(file)) {
60301
60551
  return bySuite;
60302
60552
  }
60303
60553
  const paths = (value) => Array.isArray(value) ? value.filter((path) => typeof path === "string") : [];
60304
60554
  let autoloads = [];
60305
60555
  const resident = new Map;
60306
- for (const line of readFileSync21(file, "utf8").split(`
60556
+ for (const line of readFileSync23(file, "utf8").split(`
60307
60557
  `)) {
60308
60558
  if (line.trim() === "") {
60309
60559
  continue;
@@ -60398,7 +60648,7 @@ ${this.read(path).digest}
60398
60648
  let modified = 0;
60399
60649
  walkSeen(this.scripts.projectPath, (file, under, stat) => {
60400
60650
  hash.update(`${under}
60401
- `).update(readFileSync21(file));
60651
+ `).update(readFileSync23(file));
60402
60652
  modified = Math.max(modified, stat.mtimeMs);
60403
60653
  });
60404
60654
  return { digest: hash.digest("hex"), follows: [], also: [], modified };
@@ -60407,10 +60657,10 @@ ${this.read(path).digest}
60407
60657
  const hash = createHash6("sha256");
60408
60658
  let modified = 0;
60409
60659
  const directory = join25(this.scripts.projectPath, root.slice("res://".length));
60410
- if (existsSync22(directory)) {
60660
+ if (existsSync21(directory)) {
60411
60661
  walkSeen(directory, (file, under, stat) => {
60412
60662
  hash.update(`${under}
60413
- `).update(readFileSync21(file));
60663
+ `).update(readFileSync23(file));
60414
60664
  modified = Math.max(modified, stat.mtimeMs);
60415
60665
  });
60416
60666
  }
@@ -60418,7 +60668,7 @@ ${this.read(path).digest}
60418
60668
  }
60419
60669
  readAnew(path) {
60420
60670
  const file = join25(this.scripts.projectPath, path.slice("res://".length));
60421
- if (!existsSync22(file)) {
60671
+ if (!existsSync21(file)) {
60422
60672
  return { digest: "missing", follows: [], also: [], modified: 0 };
60423
60673
  }
60424
60674
  const stat = statSync13(file);
@@ -60427,14 +60677,14 @@ ${this.read(path).digest}
60427
60677
  return { digest: inside.join(`
60428
60678
  `), follows: [], also: [], modified: stat.mtimeMs };
60429
60679
  }
60430
- const bytes = readFileSync21(file);
60680
+ const bytes = readFileSync23(file);
60431
60681
  const digest = createHash6("sha256").update(bytes).digest("hex");
60432
- const also = [`${path}.import`, `${path}.uid`].filter((beside) => existsSync22(join25(this.scripts.projectPath, beside.slice("res://".length))));
60682
+ const also = [`${path}.import`, `${path}.uid`].filter((beside) => existsSync21(join25(this.scripts.projectPath, beside.slice("res://".length))));
60433
60683
  const follows = [];
60434
60684
  if (TEXT_RESOURCE.test(path)) {
60435
60685
  for (const [named] of bytes.toString("latin1").matchAll(RES_LITERAL)) {
60436
60686
  const target = join25(this.scripts.projectPath, named.slice("res://".length));
60437
- (existsSync22(target) && statSync13(target).isDirectory() ? also : follows).push(named);
60687
+ (existsSync21(target) && statSync13(target).isDirectory() ? also : follows).push(named);
60438
60688
  }
60439
60689
  } else if (path.endsWith(".gd")) {
60440
60690
  const reaches = this.scriptReaches(path);
@@ -60621,7 +60871,7 @@ function joinedString(expression, named) {
60621
60871
  }
60622
60872
  function walkSeen(root, seen) {
60623
60873
  const walk = (directory, under) => {
60624
- if (existsSync22(join25(directory, ".gdignore"))) {
60874
+ if (existsSync21(join25(directory, ".gdignore"))) {
60625
60875
  return;
60626
60876
  }
60627
60877
  for (const entry of readdirSync14(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
@@ -60639,7 +60889,7 @@ function walkSeen(root, seen) {
60639
60889
  }
60640
60890
  function extensionsOf(projectPath) {
60641
60891
  const list = join25(projectPath, ".godot", "extension_list.cfg");
60642
- return existsSync22(list) ? readFileSync21(list, "utf8").split(`
60892
+ return existsSync21(list) ? readFileSync23(list, "utf8").split(`
60643
60893
  `).map((line) => line.trim()).filter((line) => line.startsWith("res://")) : [];
60644
60894
  }
60645
60895
 
@@ -60648,7 +60898,7 @@ init_suite_judge();
60648
60898
  init_suite_plan();
60649
60899
 
60650
60900
  // src/version.ts
60651
- import { readFileSync as readFileSync23 } from "node:fs";
60901
+ import { readFileSync as readFileSync25 } from "node:fs";
60652
60902
  import { dirname as dirname12, join as join27 } from "node:path";
60653
60903
  import { fileURLToPath as fileURLToPath3 } from "node:url";
60654
60904
  function getLocalVersion() {
@@ -60659,7 +60909,7 @@ function getLocalVersion() {
60659
60909
  ];
60660
60910
  for (const packagePath of packageCandidates) {
60661
60911
  try {
60662
- const value = JSON.parse(readFileSync23(packagePath, "utf-8"));
60912
+ const value = JSON.parse(readFileSync25(packagePath, "utf-8"));
60663
60913
  if (typeof value === "object" && value !== null && "name" in value && value.name === "gdharness" && "version" in value && typeof value.version === "string") {
60664
60914
  return value.version;
60665
60915
  }
@@ -60670,6 +60920,7 @@ function getLocalVersion() {
60670
60920
 
60671
60921
  // src/suite-run.ts
60672
60922
  var LAST_RUN_FILE = "last-run.json";
60923
+ var COMPILED_FILE = "compiled-together.json";
60673
60924
  var SLOWEST_NAMED = 5;
60674
60925
  var SILENCE_TIMES_TAKEN = 4;
60675
60926
  function silenceWhile(running, times, floorSeconds) {
@@ -60727,9 +60978,9 @@ function testOptionsOf(args, cwd) {
60727
60978
  }
60728
60979
  const first = positional[0];
60729
60980
  const named = first === undefined ? null : resolve11(cwd, first);
60730
- const projectNamed = named !== null && existsSync24(join28(named, "project.godot"));
60981
+ const projectNamed = named !== null && existsSync23(join28(named, "project.godot"));
60731
60982
  const projectPath = projectNamed ? named : resolve11(cwd);
60732
- if (!existsSync24(join28(projectPath, "project.godot"))) {
60983
+ if (!existsSync23(join28(projectPath, "project.godot"))) {
60733
60984
  throw new EngineUsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
60734
60985
  }
60735
60986
  const last = (flag) => valued.get(flag)?.at(-1);
@@ -60764,12 +61015,12 @@ function seedOf(value) {
60764
61015
  return seed;
60765
61016
  }
60766
61017
  function rootsOf(options) {
60767
- return options.roots.length > 0 ? options.roots : TEST_DIRECTORIES.filter((root) => existsSync24(join28(options.projectPath, root))).map((root) => `res://${root}`);
61018
+ return options.roots.length > 0 ? options.roots : TEST_DIRECTORIES.filter((root) => existsSync23(join28(options.projectPath, root))).map((root) => `res://${root}`);
60768
61019
  }
60769
61020
  function searchedRoots(options) {
60770
61021
  return options.selected.length === 0 ? rootsOf(options) : options.selected.filter((entry) => {
60771
61022
  const directory = join28(options.projectPath, entry.slice("res://".length));
60772
- return existsSync24(directory) && statSync14(directory).isDirectory();
61023
+ return existsSync23(directory) && statSync14(directory).isDirectory();
60773
61024
  });
60774
61025
  }
60775
61026
  function selectedSuites(scripts, options) {
@@ -60786,9 +61037,9 @@ function selectedSuites(scripts, options) {
60786
61037
  const caseAt = /^(res:\/\/.*\.gd):(\w+)$/.exec(entry);
60787
61038
  const path = caseAt?.[1] ?? entry;
60788
61039
  const directory = join28(options.projectPath, path.slice(6));
60789
- const suites = existsSync24(directory) && statSync14(directory).isDirectory() ? scripts.suitesUnder([path.replace(/\/$/, "")]) : [scripts.suite(path)].filter((suite) => suite !== null);
61040
+ const suites = existsSync23(directory) && statSync14(directory).isDirectory() ? scripts.suitesUnder([path.replace(/\/$/, "")]) : [scripts.suite(path)].filter((suite) => suite !== null);
60790
61041
  if (suites.length === 0) {
60791
- throw new EngineUsageError(existsSync24(directory) ? `${path} holds no gdUnit4 suite: nothing there extends GdUnitTestSuite and declares a test_ function.` : `Nothing at ${path} in the project at ${options.projectPath}.`);
61042
+ throw new EngineUsageError(existsSync23(directory) ? `${path} holds no gdUnit4 suite: nothing there extends GdUnitTestSuite and declares a test_ function.` : `Nothing at ${path} in the project at ${options.projectPath}.`);
60792
61043
  }
60793
61044
  for (const suite of suites) {
60794
61045
  const picked = chosen.get(suite.path) ?? { suite, cases: new Set };
@@ -60912,11 +61163,11 @@ async function runTestCommand(options, godotPath) {
60912
61163
  failsOnPushErrors: gdUnitFailsOnPushErrors(options.projectPath)
60913
61164
  };
60914
61165
  try {
60915
- const neverImported = cachedClasses(options.projectPath) === null;
60916
- if (neverImported && !options.importFirst) {
60917
- console.log("The project has never been imported, so it is imported first.");
61166
+ const needed = options.importFirst ? null : importNeeded(options.projectPath);
61167
+ if (needed !== null) {
61168
+ console.log(needed);
60918
61169
  }
60919
- if (options.importFirst || neverImported) {
61170
+ if (options.importFirst || needed !== null) {
60920
61171
  const imported = await importProject(launch);
60921
61172
  if (imported !== null) {
60922
61173
  logs.keep();
@@ -60928,12 +61179,13 @@ async function runTestCommand(options, godotPath) {
60928
61179
  const suitesBegan = Date.now();
60929
61180
  const always = [
60930
61181
  getLocalVersion(),
60931
- readFileSync24(shippedListedRunner(), "utf8"),
61182
+ readFileSync26(shippedListedRunner(), "utf8"),
60932
61183
  engineSaid(godotPath),
60933
61184
  ...options.reachAlso.map((path) => `${path}
60934
- ${existsSync24(path) ? readFileSync24(path, "latin1") : "missing"}`)
61185
+ ${existsSync23(path) ? readFileSync26(path, "latin1") : "missing"}`)
60935
61186
  ];
60936
61187
  const everySuite = suites;
61188
+ const compileKept = options.changedOnly ? { file: join28(state, COMPILED_FILE), scripts, always } : null;
60937
61189
  if (options.changedOnly) {
60938
61190
  const inputs = new SuiteInputs(scripts, always);
60939
61191
  const passedBefore = readPassed(passedFile);
@@ -60947,7 +61199,7 @@ ${existsSync24(path) ? readFileSync24(path, "latin1") : "missing"}`)
60947
61199
  }
60948
61200
  if (suites.length === 0) {
60949
61201
  console.log("Nothing has changed since every suite last passed.");
60950
- const compiled = await compileCheck(launch, everySuite);
61202
+ const compiled = await compileUnlessUnchanged(launch, everySuite, compileKept);
60951
61203
  const problems = [...compiled.faults.map((fault) => `${COMPILE_CHECK}: ${fault}`), ...uncollected];
60952
61204
  if (problems.length === 0) {
60953
61205
  rmSync13(runDirectory, { recursive: true, force: true });
@@ -60970,12 +61222,18 @@ ${existsSync24(path) ? readFileSync24(path, "latin1") : "missing"}`)
60970
61222
  if (memory.unread !== undefined) {
60971
61223
  console.log(`Windows did not say how much memory it can still commit (${memory.unread}), so engines are sized by the physical memory free.`);
60972
61224
  }
60973
- const room = options.cores === null ? roomFor(options.engines, availableParallelism4(), memory.bytes, memory.said) : roomFor(options.engines, options.cores, memory.bytes, memory.said, `--cores holds it to ${plural(options.cores, "processor")}`);
61225
+ const roomWith = (bytes) => options.cores === null ? roomFor(options.engines, availableParallelism5(), bytes, memory.said) : roomFor(options.engines, options.cores, bytes, memory.said, `--cores holds it to ${plural(options.cores, "processor")}`);
61226
+ const byMemory = roomWith(memory.bytes);
61227
+ const unheld = roomWith(Number.POSITIVE_INFINITY);
61228
+ const unheldUnits = unitsOf(suites, times, splitPastFor(suites, times, unheld.engines));
61229
+ const heldLanes = lanesOf(dealt(unheldUnits, declared, unheld.engines));
61230
+ const memoryHeld = options.order === null && byMemory.engines < heldLanes;
61231
+ const room = memoryHeld ? unheld : byMemory;
60974
61232
  if (options.cores !== null && process14.platform === "darwin") {
60975
61233
  console.log("macOS offers no way to hold a process to some processors, so --cores holds only the number of engines here.");
60976
61234
  }
60977
61235
  const splitPast = splitPastFor(suites, times, room.engines);
60978
- const units = unitsOf(suites, times, splitPast);
61236
+ const units = memoryHeld ? unheldUnits : unitsOf(suites, times, splitPast);
60979
61237
  const past = splitPast > SPLIT_PAST_SECONDS ? `an engine's share of this run (${String(Math.round(splitPast))} s)` : `${String(SPLIT_PAST_SECONDS)} s`;
60980
61238
  const plans = options.order === null ? dealt(units, declared, room.engines) : [inOneEngine(ordered(units, options.order), declared)];
60981
61239
  const functions = suites.reduce((sum, suite) => sum + suite.cases.length, 0);
@@ -61008,24 +61266,28 @@ ${existsSync24(path) ? readFileSync24(path, "latin1") : "missing"}`)
61008
61266
  say(`Kept whole in one engine, since ${one ? "it declares" : "each declares"} GDHARNESS_TOGETHER: ${named(keptWhole)}.`);
61009
61267
  }
61010
61268
  const lanes = [...new Set(plans.map((plan) => plan.lane))].map((lane) => plans.flatMap((plan, index) => plan.lane === lane ? [{ plan, index }] : []));
61269
+ const startable = memoryHeld ? Math.min(byMemory.engines, lanes.length) : lanes.length;
61011
61270
  const taken = [];
61012
- while (taken.length < lanes.length) {
61271
+ while (taken.length < startable) {
61013
61272
  const lock = await slots.takeFree();
61014
61273
  if (lock === null) {
61015
61274
  break;
61016
61275
  }
61017
61276
  taken.push(lock);
61018
61277
  }
61019
- const shared = options.order === null && lanes.length > 1 && taken.length < lanes.length;
61020
- if (shared) {
61021
- say(sharesSaid(taken.length, lanes.length));
61278
+ if (memoryHeld) {
61279
+ say(memorySaid(byMemory.engines, lanes.length, byMemory.held ?? ""));
61280
+ }
61281
+ const shared = options.order === null && lanes.length > 1 && (memoryHeld || taken.length < lanes.length);
61282
+ if (shared && taken.length < startable) {
61283
+ say(sharesSaid(taken.length, startable));
61022
61284
  }
61023
61285
  const handed = new HandedSlot;
61024
- const compiling = compileCheck(launch, everySuite, handed);
61286
+ const compiling = compileUnlessUnchanged(launch, everySuite, compileKept, handed);
61025
61287
  compiling.catch(() => {
61026
61288
  return;
61027
61289
  });
61028
- let ran = shared ? await runShares(launch, units, declared, lanes.length, taken, times, handed) : (await Promise.all(lanes.map((lane, at) => runLane(launch, lane, times, taken[at] ?? null, handed)))).flat().sort((a, b) => a.index - b.index);
61290
+ let ran = shared ? await runShares(launch, units, declared, lanes.length, taken, times, handed, memoryHeld ? new MemoryGate(slots, async () => (await memoryAvailable()).bytes) : undefined) : (await Promise.all(lanes.map((lane, at) => runLane(launch, lane, times, taken[at] ?? null, handed)))).flat().sort((a, b) => a.index - b.index);
61029
61291
  if (ran.some((engine) => engine.verdict.starved) && ran.some((engine) => !engine.verdict.passed)) {
61030
61292
  say("An engine ran out of memory, so every engine that failed runs again, one at a time.");
61031
61293
  const again = [];
@@ -61052,6 +61314,55 @@ ${existsSync24(path) ? readFileSync24(path, "latin1") : "missing"}`)
61052
61314
  function sharesSaid(free, lanes) {
61053
61315
  return `${free === 0 ? "None" : String(free)} of the ${String(lanes)} slots this run uses ${free === 1 ? "was" : "were"} free as it began, so its engines are handed the suites a share at a time as each starts, and an engine starts as each slot comes free.`;
61054
61316
  }
61317
+ function memorySaid(room, lanes, held) {
61318
+ return `Memory had room for ${room === 1 ? "one" : String(room)} of these ${String(lanes)} engines at once as the run began, since ${held}, so its engines are handed the suites a share at a time, and another starts whenever memory has room for it.`;
61319
+ }
61320
+ var MEMORY_LOOK_MS = 2000;
61321
+ var ENGINE_SETTLING_MS = 30000;
61322
+
61323
+ class MemoryGate {
61324
+ slots;
61325
+ read;
61326
+ now;
61327
+ lookMs;
61328
+ going = new Set;
61329
+ turn = Promise.resolve();
61330
+ constructor(slots, read, now = Date.now, lookMs = MEMORY_LOOK_MS) {
61331
+ this.slots = slots;
61332
+ this.read = read;
61333
+ this.now = now;
61334
+ this.lookMs = lookMs;
61335
+ }
61336
+ admit(signal) {
61337
+ const admitted = this.turn.then(() => this.room(signal));
61338
+ this.turn = admitted.catch(() => {
61339
+ return;
61340
+ });
61341
+ return admitted;
61342
+ }
61343
+ async room(signal) {
61344
+ let unmark;
61345
+ try {
61346
+ for (;; ) {
61347
+ signal.throwIfAborted();
61348
+ const at = this.now();
61349
+ const starting = [...this.going].filter((one) => at - one.at < ENGINE_SETTLING_MS).length;
61350
+ if (this.going.size === 0 || roomForAnother(await this.read(), starting)) {
61351
+ break;
61352
+ }
61353
+ unmark ??= this.slots.markWaiting();
61354
+ await delay5(this.lookMs, undefined, { signal });
61355
+ }
61356
+ } finally {
61357
+ unmark?.();
61358
+ }
61359
+ const engine = { at: this.now() };
61360
+ this.going.add(engine);
61361
+ return () => {
61362
+ this.going.delete(engine);
61363
+ };
61364
+ }
61365
+ }
61055
61366
  function roomSaid(engines, lanes, unheld, held, asked) {
61056
61367
  if (held === undefined || lanes >= unheld) {
61057
61368
  return "";
@@ -61061,7 +61372,7 @@ function roomSaid(engines, lanes, unheld, held, asked) {
61061
61372
  function readPassed(file) {
61062
61373
  let parsed;
61063
61374
  try {
61064
- parsed = JSON.parse(readFileSync24(file, "utf8"));
61375
+ parsed = JSON.parse(readFileSync26(file, "utf8"));
61065
61376
  } catch {
61066
61377
  return {};
61067
61378
  }
@@ -61077,7 +61388,7 @@ function readPassed(file) {
61077
61388
  function recordPassed(file, projectPath, ran, failedAcross, inputs, began) {
61078
61389
  const passed = readPassed(file);
61079
61390
  for (const path of Object.keys(passed)) {
61080
- if (!existsSync24(join28(projectPath, path.slice("res://".length)))) {
61391
+ if (!existsSync23(join28(projectPath, path.slice("res://".length)))) {
61081
61392
  delete passed[path];
61082
61393
  }
61083
61394
  }
@@ -61126,6 +61437,9 @@ class HandedSlot {
61126
61437
  lock = new Promise((resolve) => {
61127
61438
  this.handOver = resolve;
61128
61439
  });
61440
+ decline() {
61441
+ this.wanted = false;
61442
+ }
61129
61443
  give(lock) {
61130
61444
  if (!this.wanted) {
61131
61445
  return false;
@@ -61177,7 +61491,42 @@ async function compileCheck(launch, suites, handed) {
61177
61491
  } finally {
61178
61492
  launch.slots.release(lock);
61179
61493
  }
61180
- return { faults: judgeCompile(readRun(readFileSync24(log, "utf8")), end), log };
61494
+ return { faults: judgeCompile(readRun(readFileSync26(log, "utf8")), end), log };
61495
+ }
61496
+ function compiledPrint(kept, projectPath, suites) {
61497
+ const inputs = new SuiteInputs(kept.scripts, kept.always);
61498
+ const classes = [...cachedClasses(projectPath)?.values() ?? []];
61499
+ const read = inputs.inputsOf([...suites.map((suite) => suite.path), ...classes]);
61500
+ return { inputs, read, print: inputs.printOf(read) };
61501
+ }
61502
+ async function compileUnlessUnchanged(launch, suites, kept, handed) {
61503
+ if (kept === null) {
61504
+ return compileCheck(launch, suites, handed);
61505
+ }
61506
+ const before = compiledPrint(kept, launch.projectPath, suites).print;
61507
+ if (readKeptPrint(kept.file) === before) {
61508
+ handed?.decline();
61509
+ console.log("Every script compiled together on these same files when last checked, so it is not compiled again.");
61510
+ return { faults: [], log: "" };
61511
+ }
61512
+ const began = Date.now();
61513
+ const compiled = await compileCheck(launch, suites, handed);
61514
+ const after = compiledPrint(kept, launch.projectPath, suites);
61515
+ if (compiled.faults.length === 0 && !after.inputs.modifiedAfter(after.read, began)) {
61516
+ writeJson(kept.file, { print: after.print });
61517
+ } else {
61518
+ rmSync13(kept.file, { force: true });
61519
+ }
61520
+ return compiled;
61521
+ }
61522
+ function readKeptPrint(file) {
61523
+ try {
61524
+ const parsed = JSON.parse(readFileSync26(file, "utf8"));
61525
+ const print = parsed?.print;
61526
+ return typeof print === "string" ? print : null;
61527
+ } catch {
61528
+ return null;
61529
+ }
61181
61530
  }
61182
61531
  function printCompiled(compiled, logs) {
61183
61532
  if (compiled.faults.length === 0) {
@@ -61203,6 +61552,21 @@ function importFault(printed) {
61203
61552
  ].join(" and ");
61204
61553
  return `it printed ${counted}, the first ${first.warning ? "warning" : "error"} ${JSON.stringify(first.text)}`;
61205
61554
  }
61555
+ function importNeeded(projectPath) {
61556
+ const cached = cachedClasses(projectPath);
61557
+ if (cached === null) {
61558
+ return "The project has never been imported, so it is imported first.";
61559
+ }
61560
+ const unlisted = unlistedExtensions(projectPath);
61561
+ if (unlisted.length > 0) {
61562
+ return `No import has listed the project's extensions, ${named(unlisted)}, so the engine would load none of them, and the project is imported first.`;
61563
+ }
61564
+ const uncached = staleAgainst(cached, projectPath).sort();
61565
+ if (uncached.length > 0) {
61566
+ return `The class cache does not list ${named(uncached)} where the scripts declare ${uncached.length === 1 ? "it" : "them"}, so the project is imported first.`;
61567
+ }
61568
+ return null;
61569
+ }
61206
61570
  async function importProject(launch) {
61207
61571
  const seeded = seedExtensionList(launch.projectPath);
61208
61572
  if (seeded !== null && seeded.length > 0) {
@@ -61250,35 +61614,48 @@ async function runLane(launch, lane, times, taken, handed) {
61250
61614
  doneWith(launch, lock, handed);
61251
61615
  }
61252
61616
  }
61253
- async function runShares(launch, units, declared, lanes, taken, times, handed) {
61617
+ async function runShares(launch, units, declared, lanes, taken, times, handed, memory) {
61254
61618
  const shares = new Shares(units, declared, lanes);
61255
61619
  const handedOut = new AbortController;
61256
61620
  let engines = 0;
61621
+ const empty = () => shares.empty;
61257
61622
  const lane = async (at) => {
61258
61623
  let lock = taken[at] ?? null;
61259
- if (lock === null) {
61260
- try {
61261
- lock = await launch.slots.take(handedOut.signal);
61262
- } catch (error) {
61263
- if (handedOut.signal.aborted) {
61264
- return [];
61265
- }
61266
- throw error;
61267
- }
61268
- }
61624
+ const ran = [];
61269
61625
  try {
61270
- const ran = [];
61271
- for (let plan = shares.next(at);plan !== null; plan = shares.next(at)) {
61272
- if (shares.empty) {
61626
+ while (!empty()) {
61627
+ let ended;
61628
+ try {
61629
+ ended = await memory?.admit(handedOut.signal);
61630
+ lock ??= await launch.slots.take(handedOut.signal);
61631
+ } catch (error) {
61632
+ ended?.();
61633
+ if (handedOut.signal.aborted) {
61634
+ break;
61635
+ }
61636
+ throw error;
61637
+ }
61638
+ const plan = shares.next(at);
61639
+ if (plan === null) {
61640
+ ended?.();
61641
+ break;
61642
+ }
61643
+ if (empty()) {
61273
61644
  handedOut.abort();
61274
61645
  }
61275
61646
  const index = engines;
61276
61647
  engines += 1;
61277
- ran.push(await runEngine2(launch, plan, index, times, "", lock));
61648
+ try {
61649
+ ran.push(await runEngine2(launch, plan, index, times, "", lock));
61650
+ } finally {
61651
+ ended?.();
61652
+ }
61278
61653
  }
61279
61654
  return ran;
61280
61655
  } finally {
61281
- doneWith(launch, lock, handed);
61656
+ if (lock !== null) {
61657
+ doneWith(launch, lock, handed);
61658
+ }
61282
61659
  }
61283
61660
  };
61284
61661
  return (await Promise.all(Array.from({ length: lanes }, (_, at) => lane(at)))).flat().sort((a, b) => a.index - b.index);
@@ -61373,7 +61750,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
61373
61750
  }
61374
61751
  var REPORT_FILES_NAMED = 3;
61375
61752
  function reportsFault(reports) {
61376
- const files = existsSync24(reports) ? readdirSync15(reports, { recursive: true, encoding: "utf8" }).filter((file) => statSync14(join28(reports, file)).isFile()).sort() : [];
61753
+ const files = existsSync23(reports) ? readdirSync15(reports, { recursive: true, encoding: "utf8" }).filter((file) => statSync14(join28(reports, file)).isFile()).sort() : [];
61377
61754
  if (files.length === 0) {
61378
61755
  return null;
61379
61756
  }
@@ -61386,7 +61763,7 @@ function report(ran, compiled, across, uncollected, startedAt, logs, printOutput
61386
61763
  for (const engine of ran) {
61387
61764
  console.log("");
61388
61765
  console.log(`--- what engine ${String(engine.index)} printed, running ${named(engine.plan.suites.map((suite) => suite.path))}:`);
61389
- console.log(existsSync24(engine.log) ? readLog(engine.log).replace(CONTROL_SEQUENCE, "").trimEnd() : "(no log was written)");
61766
+ console.log(existsSync23(engine.log) ? readLog(engine.log).replace(CONTROL_SEQUENCE, "").trimEnd() : "(no log was written)");
61390
61767
  }
61391
61768
  }
61392
61769
  const failed = ran.filter((engine) => !engine.verdict.passed);
@@ -61397,7 +61774,7 @@ function report(ran, compiled, across, uncollected, startedAt, logs, printOutput
61397
61774
  for (const fault of engine.verdict.faults) {
61398
61775
  console.log(` ${fault}`);
61399
61776
  }
61400
- if (existsSync24(engine.log)) {
61777
+ if (existsSync23(engine.log)) {
61401
61778
  const verdict = [
61402
61779
  `gdharness: ${heading.slice(4)}`,
61403
61780
  ...engine.verdict.faults.map((fault) => ` ${fault}`)
@@ -61492,6 +61869,9 @@ function report(ran, compiled, across, uncollected, startedAt, logs, printOutput
61492
61869
  const problems = failed.reduce((sum, engine) => sum + engine.verdict.faults.length, compiled.faults.length + across.length + uncollected.length);
61493
61870
  console.log(`Failed: ${plural(problems, "problem")}, listed above. Ran ${totals}`);
61494
61871
  console.log(`Every engine's output is kept under ${logs.shown} until ${String(KEPT_RUN_DIRECTORIES)} later runs have failed.`);
61872
+ if (logs.shown !== logs.directory) {
61873
+ console.log(`That is a copy beside the main checkout. The run wrote them under ${logs.directory} in this worktree, and they go with it when the worktree is removed.`);
61874
+ }
61495
61875
  return 1;
61496
61876
  }
61497
61877
  function writeEarlyResult(targets, startedAt, problems, logs) {
@@ -61533,7 +61913,7 @@ class UsageError extends Refusal {
61533
61913
  function projectArgument(at) {
61534
61914
  const named = args.slice(at).find((value) => !value.startsWith("--"));
61535
61915
  const projectPath = resolve15(named ?? ".");
61536
- if (!existsSync36(join45(projectPath, "project.godot"))) {
61916
+ if (!existsSync35(join45(projectPath, "project.godot"))) {
61537
61917
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
61538
61918
  }
61539
61919
  return projectPath;
@@ -61597,7 +61977,7 @@ async function registerRuntime(godot, projectPath) {
61597
61977
  }
61598
61978
  const path = (named ?? "").replace(/^res:\/\//, "");
61599
61979
  console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
61600
- if (!existsSync36(join45(projectPath, path))) {
61980
+ if (!existsSync35(join45(projectPath, path))) {
61601
61981
  console.log(" that file is not in this project, so it boots with a missing script");
61602
61982
  }
61603
61983
  console.log(` ${useTheShippedLoader(RUNTIME_AUTOLOAD.name, path)}`);
@@ -61730,7 +62110,7 @@ async function setup() {
61730
62110
  console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
61731
62111
  }
61732
62112
  if (!args.includes("--no-skill")) {
61733
- const directories = skillDirectories(chosen.harnesses, projectPath, existsSync36);
62113
+ const directories = skillDirectories(chosen.harnesses, projectPath, existsSync35);
61734
62114
  for (const written of writeSkill(directories, getLocalVersion())) {
61735
62115
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
61736
62116
  }
@@ -61850,7 +62230,7 @@ async function upgrade() {
61850
62230
  moved.push(took.from);
61851
62231
  }
61852
62232
  }
61853
- for (const written of writeSkill(skillDirectories(already, projectPath, existsSync36), version)) {
62233
+ for (const written of writeSkill(skillDirectories(already, projectPath, existsSync35), version)) {
61854
62234
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
61855
62235
  }
61856
62236
  console.log(`