gdharness 1.3.55 → 1.3.57

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
@@ -1717,7 +1717,6 @@ var init_whole_file = __esm(() => {
1717
1717
  // src/engine-slots.ts
1718
1718
  import {
1719
1719
  closeSync as closeSync2,
1720
- existsSync as existsSync2,
1721
1720
  mkdirSync as mkdirSync2,
1722
1721
  openSync as openSync2,
1723
1722
  readdirSync,
@@ -1761,8 +1760,14 @@ function machineSlots(asked = {
1761
1760
  function nonEmpty(value) {
1762
1761
  return value === undefined || value === "" ? undefined : value;
1763
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
+ }
1764
1769
  function slotsDrawnOn(count, taker, queue) {
1765
- const gated = Math.max(1, count - KEPT_SLOTS);
1770
+ const gated = Math.max(1, count - keptSlotsOf(count));
1766
1771
  if (taker === "gate") {
1767
1772
  return { numbers: Array.from({ length: gated }, (_, slot) => slot), limit: "gate" };
1768
1773
  }
@@ -1774,20 +1779,21 @@ function slotsDrawnOn(count, taker, queue) {
1774
1779
  if (taker === "tier") {
1775
1780
  return { numbers: [...kept].reverse(), limit: "tier" };
1776
1781
  }
1777
- if (queue === "waiting") {
1778
- return { numbers: [], limit: "held" };
1779
- }
1780
- return { numbers: kept.slice(0, 1), limit: "kept" };
1782
+ return { numbers: kept.slice(0, Math.max(1, kept.length - GATE_KEPT)), limit: "kept" };
1781
1783
  }
1782
1784
  function inAGate(env = process4.env) {
1783
1785
  return nonEmpty(env[SHARE_VARIABLE]) !== undefined && nonEmpty(env[SHARE_COUNT_VARIABLE]) !== undefined;
1784
1786
  }
1785
- function inheritsSlot(tree, pid, holder, executable) {
1787
+ function inheritsSlot(tree, pid, holder, executable, engineStarted) {
1786
1788
  const program = (name) => name.toLowerCase().replace(/\.(?:exe|cmd|bat|com)$/, "");
1787
1789
  const own = program(executable);
1788
- for (const above of ancestorsIn(tree, pid, holder)) {
1790
+ for (const above of ancestorsIn(tree, pid, holder.pid)) {
1789
1791
  if (program(readCommandLine(tree.get(above)?.command ?? "").executable) !== own) {
1790
- return above === holder;
1792
+ if (above === holder.pid) {
1793
+ return true;
1794
+ }
1795
+ const started = tree.get(above)?.startedAt ?? engineStarted;
1796
+ return above === holder.engine?.pid && started !== undefined && Math.abs(started - holder.engine.started) <= START_TOLERANCE_MS;
1791
1797
  }
1792
1798
  }
1793
1799
  return false;
@@ -1852,15 +1858,29 @@ function holderOf(content) {
1852
1858
  if (typeof parsed !== "object" || parsed === null) {
1853
1859
  return null;
1854
1860
  }
1855
- const { pid, started, by } = parsed;
1856
- if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || typeof started !== "number") {
1861
+ const { by, game, engine } = parsed;
1862
+ const named = namedProcess(parsed);
1863
+ if (named === null) {
1857
1864
  return null;
1858
1865
  }
1859
- return typeof by === "string" && by !== "" ? { pid, started, by } : { pid, started };
1866
+ const runs = namedProcess(engine);
1867
+ return {
1868
+ ...named,
1869
+ ...typeof by === "string" && by !== "" ? { by } : {},
1870
+ ...game === true ? { game } : {},
1871
+ ...runs === null ? {} : { engine: runs }
1872
+ };
1860
1873
  } catch {
1861
1874
  return null;
1862
1875
  }
1863
1876
  }
1877
+ function namedProcess(value) {
1878
+ if (typeof value !== "object" || value === null) {
1879
+ return null;
1880
+ }
1881
+ const { pid, started } = value;
1882
+ return typeof pid === "number" && Number.isInteger(pid) && pid > 0 && typeof started === "number" ? { pid, started } : null;
1883
+ }
1864
1884
  function lockIsFree(content, writtenAt, now, isAlive, startOf) {
1865
1885
  const holder = holderOf(content);
1866
1886
  if (holder === null) {
@@ -1897,7 +1917,7 @@ class EngineSlots {
1897
1917
  this.taker = taker;
1898
1918
  this.drawn = slotsDrawnOn(count, taker(), "none");
1899
1919
  }
1900
- async take(signal, waiting, blocked) {
1920
+ async take(signal, waiting, blocked, game = false) {
1901
1921
  mkdirSync2(this.directory, { recursive: true });
1902
1922
  const began = Date.now();
1903
1923
  let said = Number.NEGATIVE_INFINITY;
@@ -1906,7 +1926,7 @@ class EngineSlots {
1906
1926
  try {
1907
1927
  for (;; ) {
1908
1928
  signal?.throwIfAborted();
1909
- const lock = await this.takeFree();
1929
+ const lock = await this.takeFree(game);
1910
1930
  if (lock !== null) {
1911
1931
  return lock;
1912
1932
  }
@@ -1938,7 +1958,7 @@ class EngineSlots {
1938
1958
  }
1939
1959
  }
1940
1960
  }
1941
- async takeFree() {
1961
+ async takeFree(game = false) {
1942
1962
  mkdirSync2(this.directory, { recursive: true });
1943
1963
  await this.sweep();
1944
1964
  const inherited = this.inherited === null ? await this.inheritedLock() : null;
@@ -1948,9 +1968,18 @@ class EngineSlots {
1948
1968
  }
1949
1969
  const taker = this.taker();
1950
1970
  this.drawn = slotsDrawnOn(this.count, taker, taker === "gate" || taker === "timing" ? "none" : await this.gateQueue());
1971
+ if (game && this.drawn.limit === "kept") {
1972
+ const share = slotsDrawnOn(this.count, "tier", "queued").numbers;
1973
+ const games = await this.heldByGames(share);
1974
+ if (games.length >= gamesKeptOf(share.length)) {
1975
+ this.drawn = { numbers: games, limit: "games" };
1976
+ return null;
1977
+ }
1978
+ }
1979
+ const holder = game ? { ...this.holder, game: true } : this.holder;
1951
1980
  for (const slot of this.drawn.numbers) {
1952
1981
  const lock = join5(this.directory, `${String(slot)}.lock`);
1953
- if (this.made(lock) || await this.takenOver(lock)) {
1982
+ if (this.made(lock, holder) || await this.takenOver(lock, holder)) {
1954
1983
  this.held.add(lock);
1955
1984
  return lock;
1956
1985
  }
@@ -1965,7 +1994,17 @@ class EngineSlots {
1965
1994
  return null;
1966
1995
  }
1967
1996
  const tree = await processTree();
1968
- return tree !== undefined && inheritsSlot(tree, process4.pid, holder.pid, basename3(process4.execPath)) ? lock : null;
1997
+ const engine = holder.engine?.pid;
1998
+ const engineStarted = engine === undefined || tree?.get(engine)?.startedAt !== undefined ? undefined : (await startTimesOf([engine])).get(engine);
1999
+ return tree !== undefined && inheritsSlot(tree, process4.pid, holder, basename3(process4.execPath), engineStarted) ? lock : null;
2000
+ }
2001
+ markWaiting() {
2002
+ const mark = this.waitMarked();
2003
+ return () => {
2004
+ if (mark !== undefined && this.marks.delete(mark)) {
2005
+ removeWhole(mark);
2006
+ }
2007
+ };
1969
2008
  }
1970
2009
  waitMarked() {
1971
2010
  const marks = join5(this.directory, WAITING_FOLDER);
@@ -1982,6 +2021,17 @@ class EngineSlots {
1982
2021
  this.marks.add(mark);
1983
2022
  return mark;
1984
2023
  }
2024
+ async heldByGames(numbers) {
2025
+ const games = [];
2026
+ for (const slot of numbers) {
2027
+ const lock = join5(this.directory, `${String(slot)}.lock`);
2028
+ const content = readWhole(lock);
2029
+ if (content !== null && holderOf(content)?.game === true && !await this.free(lock)) {
2030
+ games.push(slot);
2031
+ }
2032
+ }
2033
+ return games;
2034
+ }
1985
2035
  async gateQueue() {
1986
2036
  const marks = join5(this.directory, QUEUED_FOLDER);
1987
2037
  let names;
@@ -2000,10 +2050,8 @@ class EngineSlots {
2000
2050
  if (await this.free(mark)) {
2001
2051
  removeWhole(mark);
2002
2052
  removeWhole(`${mark}${SEATED_SUFFIX}`);
2003
- } else if (!existsSync2(`${mark}${SEATED_SUFFIX}`)) {
2004
- live = "waiting";
2005
- } else if (live === "none") {
2006
- live = "seated";
2053
+ } else {
2054
+ live = "queued";
2007
2055
  }
2008
2056
  }
2009
2057
  return live;
@@ -2047,7 +2095,7 @@ class EngineSlots {
2047
2095
  }
2048
2096
  return handed;
2049
2097
  }
2050
- made(path) {
2098
+ made(path, holder = this.holder) {
2051
2099
  let made;
2052
2100
  try {
2053
2101
  made = openSync2(path, "wx");
@@ -2055,7 +2103,7 @@ class EngineSlots {
2055
2103
  return false;
2056
2104
  }
2057
2105
  try {
2058
- writeSync2(made, JSON.stringify(this.holder));
2106
+ writeSync2(made, JSON.stringify(holder));
2059
2107
  } finally {
2060
2108
  closeSync2(made);
2061
2109
  }
@@ -2075,10 +2123,10 @@ class EngineSlots {
2075
2123
  }
2076
2124
  }
2077
2125
  }
2078
- takenOver(lock) {
2079
- return this.cleared(lock, true);
2126
+ takenOver(lock, holder) {
2127
+ return this.cleared(lock, true, holder);
2080
2128
  }
2081
- async cleared(lock, retake) {
2129
+ async cleared(lock, retake, holder = this.holder) {
2082
2130
  if (!await this.free(lock)) {
2083
2131
  return false;
2084
2132
  }
@@ -2100,7 +2148,7 @@ class EngineSlots {
2100
2148
  } catch {
2101
2149
  return false;
2102
2150
  }
2103
- return retake ? this.made(lock) : true;
2151
+ return retake ? this.made(lock, holder) : true;
2104
2152
  } finally {
2105
2153
  rmSync4(steal, { force: true });
2106
2154
  }
@@ -2136,7 +2184,47 @@ function handSlot(lock, from, to) {
2136
2184
  } catch {
2137
2185
  return false;
2138
2186
  }
2139
- return content !== null && holderOf(content)?.pid === from && writeWhole(lock, JSON.stringify(to));
2187
+ const holder = content === null ? null : holderOf(content);
2188
+ return holder?.pid === from && writeWhole(lock, JSON.stringify(holder.game === true ? { ...to, game: true } : to));
2189
+ }
2190
+ function nameSlotEngine(lock, holder, engine) {
2191
+ let content;
2192
+ try {
2193
+ content = readWhole(lock);
2194
+ } catch {
2195
+ return false;
2196
+ }
2197
+ const named = content === null ? null : holderOf(content);
2198
+ return named?.pid === holder && writeWhole(lock, JSON.stringify({ ...named, engine }));
2199
+ }
2200
+ async function nameSlotEngineOf(lock, holder, pid) {
2201
+ const started = (await startTimesOf([pid])).get(pid);
2202
+ if (started !== undefined) {
2203
+ nameSlotEngine(lock, holder, { pid, started });
2204
+ }
2205
+ }
2206
+ async function slotEngineNamed(lock, run, pidFile) {
2207
+ if (pidFile === undefined) {
2208
+ if (run.pid !== null) {
2209
+ await nameSlotEngineOf(lock, process4.pid, run.pid);
2210
+ }
2211
+ return;
2212
+ }
2213
+ const state = { settled: false };
2214
+ run.settled.finally(() => {
2215
+ state.settled = true;
2216
+ });
2217
+ while (!state.settled) {
2218
+ let written = Number.NaN;
2219
+ try {
2220
+ written = Number(readFileSync6(pidFile, "utf8").trim());
2221
+ } catch {}
2222
+ if (Number.isInteger(written) && written > 0) {
2223
+ await nameSlotEngineOf(lock, process4.pid, written);
2224
+ return;
2225
+ }
2226
+ await delay2(ENGINE_PID_LOOK_MS);
2227
+ }
2140
2228
  }
2141
2229
  function giveBackSlot(lock, pid) {
2142
2230
  try {
@@ -2163,15 +2251,12 @@ function nameSlotTaker(by) {
2163
2251
  }
2164
2252
  function slotWaitSaid(waitedMs, count, directory, holders = [], drawn = { numbers: [] }) {
2165
2253
  const so = `Waiting${waitedMs >= 1000 ? ` ${String(Math.round(waitedMs / 1000))} s so far` : ""} for an engine`;
2166
- if (drawn.limit === "held") {
2167
- return `${so}: ${GATE_UNSEATED} (${directory}), and the engine starts once every queued gate has its seat.`;
2168
- }
2169
2254
  const taking = drawn.limit === undefined ? count : drawn.numbers.length;
2170
2255
  const slots = `${slotsSaid(taking, count, directory, drawn.limit)} ${taking === 1 ? "is" : "are"} in use`;
2171
2256
  const named = holdersNamed(taking, holders);
2172
2257
  const waiting = `${so}: ${slots}`;
2173
2258
  const back = `the engine starts when ${taking === 1 ? "it" : "one"} is given back`;
2174
- const starts = drawn.limit === "kept" || drawn.limit === "tier" ? `${back} or no gate is queued` : back;
2259
+ const starts = drawn.limit === "kept" || drawn.limit === "tier" || drawn.limit === "games" ? `${back} or no gate is queued` : back;
2175
2260
  if (named.length === 0) {
2176
2261
  return `${waiting}, by a gate or another tool, and ${starts}.`;
2177
2262
  }
@@ -2191,13 +2276,13 @@ function slotsSaid(taking, count, directory, limit) {
2191
2276
  if (limit === "tier") {
2192
2277
  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`;
2193
2278
  }
2279
+ if (limit === "games") {
2280
+ 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,`;
2281
+ }
2194
2282
  return count === 1 ? `the one engine slot ${where}` : `all ${String(count)} engine slots ${where}`;
2195
2283
  }
2196
2284
  function slotWaitedSaid(waitedMs, count, directory, holders, drawn = { numbers: [] }) {
2197
2285
  const waited = `This call waited ${String(Math.round(waitedMs / 1000))} s for an engine slot in all.`;
2198
- if (drawn.limit === "held") {
2199
- return `${waited} When it first waited, ${GATE_UNSEATED} (${directory}), and engines outside a gate wait until every queued gate has its seat.`;
2200
- }
2201
2286
  const taking = drawn.limit === undefined ? count : drawn.numbers.length;
2202
2287
  const slots = `${slotsSaid(taking, count, directory, drawn.limit)} ${taking === 1 ? "was" : "were"} in use`;
2203
2288
  const named = holdersNamed(taking, holders);
@@ -2220,7 +2305,7 @@ function holdersNamed(count, holders) {
2220
2305
  function listed(named) {
2221
2306
  return named.length === 1 ? named.join("") : `${named.slice(0, -1).join(", ")} and ${named.at(-1) ?? ""}`;
2222
2307
  }
2223
- async function slotForCall() {
2308
+ async function slotForCall(game = false) {
2224
2309
  const slots = await processSlots();
2225
2310
  const progress = callProgress();
2226
2311
  const waits = callSlotWaits();
@@ -2232,7 +2317,7 @@ async function slotForCall() {
2232
2317
  }, () => {
2233
2318
  blockedBy = slots.holders();
2234
2319
  blockedOn = slots.drawn;
2235
- });
2320
+ }, game);
2236
2321
  if (waits !== undefined && blockedBy !== undefined) {
2237
2322
  waits.ms += Date.now() - began;
2238
2323
  const heldBy = blockedBy;
@@ -2259,7 +2344,7 @@ async function inASlot(body) {
2259
2344
  held.release();
2260
2345
  }
2261
2346
  }
2262
- 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;
2347
+ 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, ENGINE_PID_LOOK_MS = 100, ours, saidTo, takenBy;
2263
2348
  var init_engine_slots = __esm(() => {
2264
2349
  init_alive();
2265
2350
  init_cache_home();
@@ -9534,7 +9619,7 @@ var init_config_formats = __esm(() => {
9534
9619
  });
9535
9620
 
9536
9621
  // src/runner.ts
9537
- import { existsSync as existsSync3, realpathSync as realpathSync2 } from "node:fs";
9622
+ import { existsSync as existsSync2, realpathSync as realpathSync2 } from "node:fs";
9538
9623
  import { delimiter, dirname as dirname3, extname, join as join6, resolve as resolve3 } from "node:path";
9539
9624
  import process5 from "node:process";
9540
9625
  function currentRunner() {
@@ -9578,12 +9663,12 @@ function realPath(path) {
9578
9663
  }
9579
9664
  function alongside(execPath, name) {
9580
9665
  const runner = join6(dirname3(execPath), process5.platform === "win32" ? `${name}.cmd` : name);
9581
- return existsSync3(runner) ? runner : undefined;
9666
+ return existsSync2(runner) ? runner : undefined;
9582
9667
  }
9583
9668
  var init_runner = () => {};
9584
9669
 
9585
9670
  // src/harnesses.ts
9586
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync8, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync5 } from "node:fs";
9671
+ 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";
9587
9672
  import { homedir as homedir3 } from "node:os";
9588
9673
  import { basename as basename4, dirname as dirname4, isAbsolute as isAbsolute2, join as join7, relative as relative2, resolve as resolve4 } from "node:path";
9589
9674
  import process6 from "node:process";
@@ -9674,15 +9759,15 @@ function harnessById(id) {
9674
9759
  }
9675
9760
  function present(harness, projectPath) {
9676
9761
  const path = configPath(harness, projectPath);
9677
- if (existsSync4(path)) {
9762
+ if (existsSync3(path)) {
9678
9763
  return true;
9679
9764
  }
9680
9765
  const root = harness.scope === "project" ? projectPath : homedir3();
9681
9766
  const own = dirname4(path);
9682
- if (own !== root && existsSync4(own)) {
9767
+ if (own !== root && existsSync3(own)) {
9683
9768
  return true;
9684
9769
  }
9685
- return harness.marker !== undefined && existsSync4(join7(root, harness.marker));
9770
+ return harness.marker !== undefined && existsSync3(join7(root, harness.marker));
9686
9771
  }
9687
9772
  function candidates(projectPath) {
9688
9773
  const found = [];
@@ -9695,7 +9780,7 @@ function candidates(projectPath) {
9695
9780
  }
9696
9781
  if (present(harness, projectPath)) {
9697
9782
  found.push({ harness, reason: "configured" });
9698
- } else if (harness.home !== undefined && existsSync4(join7(homedir3(), harness.home))) {
9783
+ } else if (harness.home !== undefined && existsSync3(join7(homedir3(), harness.home))) {
9699
9784
  found.push({ harness, reason: "installed" });
9700
9785
  }
9701
9786
  }
@@ -9717,7 +9802,7 @@ function groupByFile(harnesses, projectPath) {
9717
9802
  }
9718
9803
  function registered(harness, projectPath) {
9719
9804
  const path = configPath(harness, projectPath);
9720
- if (harness.snippet !== undefined || !existsSync4(path)) {
9805
+ if (harness.snippet !== undefined || !existsSync3(path)) {
9721
9806
  return false;
9722
9807
  }
9723
9808
  const text = readFileSync8(path, "utf8");
@@ -9746,7 +9831,7 @@ function recordedEnginePath(projectPath) {
9746
9831
  }
9747
9832
  const named = reach(entryEnvironment(harness, projectPath), "GODOT_PATH");
9748
9833
  const path = typeof named !== "string" || named === "" ? null : isAbsolute2(named) ? named : resolve4(projectPath, named);
9749
- if (path !== null && existsSync4(path)) {
9834
+ if (path !== null && existsSync3(path)) {
9750
9835
  return path;
9751
9836
  }
9752
9837
  }
@@ -9754,7 +9839,7 @@ function recordedEnginePath(projectPath) {
9754
9839
  }
9755
9840
  function mainCheckoutOf(projectPath) {
9756
9841
  let top = resolve4(projectPath);
9757
- while (!existsSync4(join7(top, ".git"))) {
9842
+ while (!existsSync3(join7(top, ".git"))) {
9758
9843
  const up = dirname4(top);
9759
9844
  if (up === top) {
9760
9845
  return null;
@@ -9771,7 +9856,7 @@ function mainCheckoutOf(projectPath) {
9771
9856
  }
9772
9857
  const entry = resolve4(top, pointer);
9773
9858
  const commonFile = join7(entry, "commondir");
9774
- if (!existsSync4(commonFile)) {
9859
+ if (!existsSync3(commonFile)) {
9775
9860
  return null;
9776
9861
  }
9777
9862
  const common = resolve4(entry, readFileSync8(commonFile, "utf8").trim());
@@ -9779,11 +9864,11 @@ function mainCheckoutOf(projectPath) {
9779
9864
  return null;
9780
9865
  }
9781
9866
  const main = join7(dirname4(common), relative2(top, resolve4(projectPath)));
9782
- return isSameDirectory(main, projectPath) || !existsSync4(join7(main, "project.godot")) ? null : main;
9867
+ return isSameDirectory(main, projectPath) || !existsSync3(join7(main, "project.godot")) ? null : main;
9783
9868
  }
9784
9869
  function worktreeCheckoutsOf(projectPath) {
9785
9870
  let top = resolve4(projectPath);
9786
- while (!existsSync4(join7(top, ".git"))) {
9871
+ while (!existsSync3(join7(top, ".git"))) {
9787
9872
  const up = dirname4(top);
9788
9873
  if (up === top) {
9789
9874
  return [];
@@ -9791,18 +9876,18 @@ function worktreeCheckoutsOf(projectPath) {
9791
9876
  top = up;
9792
9877
  }
9793
9878
  const listed = join7(top, ".git", "worktrees");
9794
- if (!statSync4(join7(top, ".git")).isDirectory() || !existsSync4(listed)) {
9879
+ if (!statSync4(join7(top, ".git")).isDirectory() || !existsSync3(listed)) {
9795
9880
  return [];
9796
9881
  }
9797
9882
  const below = relative2(top, resolve4(projectPath));
9798
9883
  return readdirSync2(listed).flatMap((name) => {
9799
9884
  const pointer = join7(listed, name, "gitdir");
9800
- if (!existsSync4(pointer)) {
9885
+ if (!existsSync3(pointer)) {
9801
9886
  return [];
9802
9887
  }
9803
9888
  const checkout = dirname4(resolve4(listed, name, readFileSync8(pointer, "utf8").trim()));
9804
9889
  const project = join7(checkout, below);
9805
- return existsSync4(join7(project, "project.godot")) ? [project] : [];
9890
+ return existsSync3(join7(project, "project.godot")) ? [project] : [];
9806
9891
  });
9807
9892
  }
9808
9893
  function entryEnvironment(harness, projectPath) {
@@ -9810,7 +9895,7 @@ function entryEnvironment(harness, projectPath) {
9810
9895
  return;
9811
9896
  }
9812
9897
  const path = configPath(harness, projectPath);
9813
- if (!existsSync4(path)) {
9898
+ if (!existsSync3(path)) {
9814
9899
  return;
9815
9900
  }
9816
9901
  let parsed;
@@ -9831,7 +9916,7 @@ function reach(held, key) {
9831
9916
  }
9832
9917
  function disconnect(harness, projectPath) {
9833
9918
  const path = configPath(harness, projectPath);
9834
- if (!existsSync4(path)) {
9919
+ if (!existsSync3(path)) {
9835
9920
  return { harness, path, action: "absent" };
9836
9921
  }
9837
9922
  if (harness.snippet !== undefined) {
@@ -9918,7 +10003,7 @@ function connect(harness, projectPath, launch) {
9918
10003
  if (harness.snippet !== undefined) {
9919
10004
  return { harness, path, action: "snippet", snippet: harness.snippet(launch) };
9920
10005
  }
9921
- const held = existsSync4(path) ? readFileSync8(path, "utf8") : "";
10006
+ const held = existsSync3(path) ? readFileSync8(path, "utf8") : "";
9922
10007
  if (harness.toml !== undefined) {
9923
10008
  const already = held.includes(harness.toml.identity ?? harness.toml.header);
9924
10009
  return put(harness, path, writeToml(held, harness.toml, launch), already);
@@ -11084,7 +11169,7 @@ var init_gdscript_source = __esm(() => {
11084
11169
  });
11085
11170
 
11086
11171
  // src/class-cache.ts
11087
- import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync5 } from "node:fs";
11172
+ import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync5 } from "node:fs";
11088
11173
  import { join as join8, posix } from "node:path";
11089
11174
  function isToolScript(source) {
11090
11175
  return TOOL.test(codeOf(source));
@@ -11148,7 +11233,7 @@ function declaredClassOf(source) {
11148
11233
  }
11149
11234
  function eachScript(projectPath, each) {
11150
11235
  const visit = (directory, prefix) => {
11151
- if (existsSync5(join8(directory, ".gdignore"))) {
11236
+ if (existsSync4(join8(directory, ".gdignore"))) {
11152
11237
  return;
11153
11238
  }
11154
11239
  for (const entry of readdirSync3(directory, { withFileTypes: true })) {
@@ -11544,7 +11629,7 @@ function uncachedForTheEngineNote(projectPath, types) {
11544
11629
  }
11545
11630
  const one = types.length === 1;
11546
11631
  const named = types.map((type) => `${type.type} (${type.declaredIn})`).join(", ");
11547
- const cacheExists = existsSync5(join8(projectPath, ".godot", "global_script_class_cache.cfg"));
11632
+ const cacheExists = existsSync4(join8(projectPath, ".godot", "global_script_class_cache.cfg"));
11548
11633
  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.`;
11549
11634
  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.";
11550
11635
  }
@@ -11570,7 +11655,7 @@ function declaredSince(projectPath, paths, names, since) {
11570
11655
  });
11571
11656
  }
11572
11657
  function cachedAtMissingPaths(cached, projectPath) {
11573
- return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync5(join8(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
11658
+ return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync4(join8(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
11574
11659
  }
11575
11660
  function staleAgainst(cached, projectPath) {
11576
11661
  return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
@@ -12904,7 +12989,7 @@ var init_suite_judge = __esm(() => {
12904
12989
  // src/engine-launch.ts
12905
12990
  import {
12906
12991
  closeSync as closeSync4,
12907
- existsSync as existsSync6,
12992
+ existsSync as existsSync5,
12908
12993
  fstatSync as fstatSync2,
12909
12994
  openSync as openSync4,
12910
12995
  readdirSync as readdirSync4,
@@ -12944,7 +13029,7 @@ function godotNamed(value, cwd) {
12944
13029
  return null;
12945
13030
  }
12946
13031
  const path = resolve5(cwd, value);
12947
- if (!existsSync6(path) || statSync6(path).isDirectory()) {
13032
+ if (!existsSync5(path) || statSync6(path).isDirectory()) {
12948
13033
  throw new EngineUsageError(`--godot names ${path}, which is not an engine executable.`);
12949
13034
  }
12950
13035
  return path;
@@ -12975,7 +13060,7 @@ function resPathOf(entry, projectPath, cwd) {
12975
13060
  const file = caseAt?.[1] ?? entry;
12976
13061
  const fromProject = resolve5(projectPath, file);
12977
13062
  const fromHere = resolve5(cwd, file);
12978
- const absolute = isAbsolute3(file) || !existsSync6(fromProject) && existsSync6(fromHere) ? fromHere : fromProject;
13063
+ const absolute = isAbsolute3(file) || !existsSync5(fromProject) && existsSync5(fromHere) ? fromHere : fromProject;
12979
13064
  const inside = relative3(projectPath, absolute);
12980
13065
  if (inside.startsWith("..") || isAbsolute3(inside)) {
12981
13066
  throw new EngineUsageError(`${entry} is not inside the project at ${projectPath}.`);
@@ -12987,7 +13072,7 @@ function homeOf(projectPath) {
12987
13072
  return mainCheckoutOf(projectPath) ?? projectPath;
12988
13073
  }
12989
13074
  function sweepRunDirectories(runs, kept = KEPT_RUN_DIRECTORIES) {
12990
- if (!existsSync6(runs)) {
13075
+ if (!existsSync5(runs)) {
12991
13076
  return;
12992
13077
  }
12993
13078
  const ended = [];
@@ -13017,6 +13102,9 @@ function roomFor(most, processors, available, said = "of memory free", processor
13017
13102
  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
13018
13103
  };
13019
13104
  }
13105
+ function roomForAnother(available, starting) {
13106
+ return available - MEMORY_KEPT - starting * MEMORY_PER_ENGINE >= MEMORY_PER_ENGINE;
13107
+ }
13020
13108
  async function memoryAvailable() {
13021
13109
  const level = await macMemoryLevel();
13022
13110
  if (level !== null) {
@@ -13155,9 +13243,13 @@ async function watchedEngine(launch, lock, args, directory, log, silenceSeconds,
13155
13243
  return;
13156
13244
  }
13157
13245
  };
13246
+ const found = (engine) => {
13247
+ watching(engine, true);
13248
+ nameSlotEngineOf(lock, process7.pid, engine);
13249
+ };
13158
13250
  let pid = engineAt();
13159
13251
  if (pid !== undefined) {
13160
- watching(pid, true);
13252
+ found(pid);
13161
13253
  }
13162
13254
  try {
13163
13255
  let size = 0;
@@ -13172,7 +13264,7 @@ async function watchedEngine(launch, lock, args, directory, log, silenceSeconds,
13172
13264
  if (pid === undefined) {
13173
13265
  pid = engineAt();
13174
13266
  if (pid !== undefined) {
13175
- watching(pid, true);
13267
+ found(pid);
13176
13268
  }
13177
13269
  }
13178
13270
  const taken = await sample();
@@ -13221,7 +13313,7 @@ async function watchedEngine(launch, lock, args, directory, log, silenceSeconds,
13221
13313
  continue;
13222
13314
  }
13223
13315
  const { stoppedIn } = readRun(readLog(log));
13224
- const unstarted = helper !== null && !existsSync6(helper.pid) ? `, and it never started ${helper.how}: ${helperStep(helper.step)}` : "";
13316
+ const unstarted = helper !== null && !existsSync5(helper.pid) ? `, and it never started ${helper.how}: ${helperStep(helper.step)}` : "";
13225
13317
  ended = `the engine printed nothing for ${String(allowed.seconds)} s${countedBy(quiet, silent, at)}${stoppedIn === null ? "" : ` while running ${stoppedIn}`}, so it was ended${unstarted}`;
13226
13318
  stop();
13227
13319
  }
@@ -13307,7 +13399,7 @@ var init_engine_launch = __esm(() => {
13307
13399
  });
13308
13400
 
13309
13401
  // src/detection.ts
13310
- import { existsSync as existsSync7, readdirSync as readdirSync5, statSync as statSync7 } from "node:fs";
13402
+ import { existsSync as existsSync6, readdirSync as readdirSync5, statSync as statSync7 } from "node:fs";
13311
13403
  import { homedir as homedir4 } from "node:os";
13312
13404
  import { join as join10 } from "node:path";
13313
13405
  function resolveHomeDirectory() {
@@ -13318,7 +13410,7 @@ function resolveHomeDirectory() {
13318
13410
  }
13319
13411
  }
13320
13412
  function scanDirectoryForGodotBinaries(directory, platform) {
13321
- if (!directory || !existsSync7(directory)) {
13413
+ if (!directory || !existsSync6(directory)) {
13322
13414
  return [];
13323
13415
  }
13324
13416
  let entries;
@@ -13586,7 +13678,7 @@ var init_launch = __esm(() => {
13586
13678
  });
13587
13679
 
13588
13680
  // src/godot-path.ts
13589
- import { existsSync as existsSync8 } from "node:fs";
13681
+ import { existsSync as existsSync7 } from "node:fs";
13590
13682
  import { isAbsolute as isAbsolute4, normalize, resolve as resolve6 } from "node:path";
13591
13683
  function versionStartOf(error) {
13592
13684
  const failed = error ?? {};
@@ -13664,7 +13756,7 @@ class GodotLocator {
13664
13756
  if (known !== undefined) {
13665
13757
  return known;
13666
13758
  }
13667
- if (path !== "godot" && !existsSync8(path)) {
13759
+ if (path !== "godot" && !existsSync7(path)) {
13668
13760
  this.answered.set(path, "does not exist");
13669
13761
  return "does not exist";
13670
13762
  }
@@ -13989,6 +14081,9 @@ function runStarted(executable, args, lock, options, signal) {
13989
14081
  env: onTheSlot(process.env, lock),
13990
14082
  ...signal === undefined ? {} : { signal }
13991
14083
  });
14084
+ if (child.pid !== undefined) {
14085
+ nameSlotEngineOf(lock, process.pid, child.pid);
14086
+ }
13992
14087
  child.stdout.on("data", (chunk) => {
13993
14088
  log.append("stdout", chunk);
13994
14089
  });
@@ -14046,7 +14141,7 @@ var init_engine_run = __esm(() => {
14046
14141
  });
14047
14142
 
14048
14143
  // src/doc-cache.ts
14049
- import { existsSync as existsSync9, readdirSync as readdirSync6, statSync as statSync8 } from "node:fs";
14144
+ import { existsSync as existsSync8, readdirSync as readdirSync6, statSync as statSync8 } from "node:fs";
14050
14145
  import { homedir as homedir5 } from "node:os";
14051
14146
  import { basename as basename5, dirname as dirname5, isAbsolute as isAbsolute5, join as join11 } from "node:path";
14052
14147
  import process9 from "node:process";
@@ -14054,7 +14149,7 @@ function editorCacheDirectory(godotPath, platform = process9.platform, env = pro
14054
14149
  const beside = dirname5(godotPath);
14055
14150
  const bundle = platform === "darwin" && basename5(beside) === "MacOS" && basename5(dirname5(beside)) === "Contents" ? dirname5(dirname5(dirname5(beside))) : null;
14056
14151
  for (const folder of bundle === null ? [beside] : [beside, bundle]) {
14057
- if (existsSync9(join11(folder, "._sc_")) || existsSync9(join11(folder, "_sc_"))) {
14152
+ if (existsSync8(join11(folder, "._sc_")) || existsSync8(join11(folder, "_sc_"))) {
14058
14153
  return join11(folder, "editor_data", "cache");
14059
14154
  }
14060
14155
  }
@@ -14102,7 +14197,7 @@ var init_doc_cache = __esm(() => {
14102
14197
  });
14103
14198
 
14104
14199
  // src/project-scan.ts
14105
- import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync12, statSync as statSync9 } from "node:fs";
14200
+ import { existsSync as existsSync9, readdirSync as readdirSync7, readFileSync as readFileSync12, statSync as statSync9 } from "node:fs";
14106
14201
  import { basename as basename6, extname as extname2, join as join12, relative as relative4, sep as sep3 } from "node:path";
14107
14202
  function skipped(name) {
14108
14203
  return name.startsWith(".") || SKIPPED.has(name);
@@ -14111,10 +14206,10 @@ function steppedOver(directory) {
14111
14206
  return gdignored(directory) || installedByUs(directory);
14112
14207
  }
14113
14208
  function gdignored(directory) {
14114
- return existsSync10(join12(directory, ".gdignore"));
14209
+ return existsSync9(join12(directory, ".gdignore"));
14115
14210
  }
14116
14211
  function installedByUs(directory) {
14117
- return existsSync10(join12(directory, VERSION_MARKER));
14212
+ return existsSync9(join12(directory, VERSION_MARKER));
14118
14213
  }
14119
14214
  function projectStructure(projectPath) {
14120
14215
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
@@ -14227,7 +14322,7 @@ function scriptsWithoutUid(projectPath) {
14227
14322
  continue;
14228
14323
  }
14229
14324
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
14230
- if (CARRIES_A_UID.has(extension) && !existsSync10(join12(directory, `${entry.name}.uid`))) {
14325
+ if (CARRIES_A_UID.has(extension) && !existsSync9(join12(directory, `${entry.name}.uid`))) {
14231
14326
  found.push(spelled);
14232
14327
  }
14233
14328
  }
@@ -14494,7 +14589,7 @@ var init_project_scan = __esm(() => {
14494
14589
 
14495
14590
  // src/import-freshness.ts
14496
14591
  import { createHash as createHash2 } from "node:crypto";
14497
- import { existsSync as existsSync11, readdirSync as readdirSync8, readFileSync as readFileSync13, statSync as statSync10 } from "node:fs";
14592
+ import { existsSync as existsSync10, readdirSync as readdirSync8, readFileSync as readFileSync13, statSync as statSync10 } from "node:fs";
14498
14593
  import { join as join13 } from "node:path";
14499
14594
  function md5Of(file) {
14500
14595
  let stats;
@@ -14620,7 +14715,7 @@ function behind(projectPath, resPath, sidecarFile, sidecar, importedFrom) {
14620
14715
  return "its import file names no imported output, as one written before the engine imports from it does";
14621
14716
  }
14622
14717
  }
14623
- if (outputs.some((output) => !existsSync11(onDisk(projectPath, output)))) {
14718
+ if (outputs.some((output) => !existsSync10(onDisk(projectPath, output)))) {
14624
14719
  return "its imported output is not on disk";
14625
14720
  }
14626
14721
  const source = unquoted(deps.get("source_file"));
@@ -14645,7 +14740,7 @@ function staleImports(projectPath) {
14645
14740
  const stale = [];
14646
14741
  const importedFrom = importedSidecarMd5s(projectPath);
14647
14742
  const visit = (directory, resDirectory) => {
14648
- if (existsSync11(join13(directory, ".gdignore")) || existsSync11(join13(directory, VERSION_MARKER))) {
14743
+ if (existsSync10(join13(directory, ".gdignore")) || existsSync10(join13(directory, VERSION_MARKER))) {
14649
14744
  return;
14650
14745
  }
14651
14746
  let entries;
@@ -14736,7 +14831,7 @@ var init_import_freshness = __esm(() => {
14736
14831
 
14737
14832
  // src/library-copies.ts
14738
14833
  import {
14739
- existsSync as existsSync12,
14834
+ existsSync as existsSync11,
14740
14835
  mkdirSync as mkdirSync4,
14741
14836
  readdirSync as readdirSync9,
14742
14837
  readFileSync as readFileSync14,
@@ -14754,7 +14849,7 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
14754
14849
  for (const library of windowsLibraries(projectPath)) {
14755
14850
  const folder = dirname6(library);
14756
14851
  const copy = `~${basename7(library)}`;
14757
- if (existsSync12(join14(folder, copy))) {
14852
+ if (existsSync11(join14(folder, copy))) {
14758
14853
  const aside = join14(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
14759
14854
  try {
14760
14855
  renameSync3(join14(folder, copy), aside);
@@ -14780,12 +14875,26 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
14780
14875
  }
14781
14876
  function seedExtensionList(projectPath) {
14782
14877
  const list = join14(projectPath, ".godot", "extension_list.cfg");
14783
- if (existsSync12(list)) {
14878
+ if (existsSync11(list)) {
14784
14879
  return null;
14785
14880
  }
14881
+ const found = unlistedExtensions(projectPath);
14882
+ if (found.length === 0) {
14883
+ return [];
14884
+ }
14885
+ mkdirSync4(dirname6(list), { recursive: true });
14886
+ writeFileSync7(list, `${found.join(`
14887
+ `)}
14888
+ `);
14889
+ return found;
14890
+ }
14891
+ function unlistedExtensions(projectPath) {
14892
+ if (existsSync11(join14(projectPath, ".godot", "extension_list.cfg"))) {
14893
+ return [];
14894
+ }
14786
14895
  const found = [];
14787
14896
  const visit = (directory, resDirectory) => {
14788
- if (existsSync12(join14(directory, ".gdignore"))) {
14897
+ if (existsSync11(join14(directory, ".gdignore"))) {
14789
14898
  return;
14790
14899
  }
14791
14900
  let entries;
@@ -14806,15 +14915,7 @@ function seedExtensionList(projectPath) {
14806
14915
  }
14807
14916
  };
14808
14917
  visit(projectPath, "res://");
14809
- if (found.length === 0) {
14810
- return [];
14811
- }
14812
- found.sort();
14813
- mkdirSync4(dirname6(list), { recursive: true });
14814
- writeFileSync7(list, `${found.join(`
14815
- `)}
14816
- `);
14817
- return found;
14918
+ return found.sort();
14818
14919
  }
14819
14920
  function windowsLibraries(projectPath) {
14820
14921
  const listed = readOrNull(join14(projectPath, ".godot", "extension_list.cfg"));
@@ -14864,12 +14965,12 @@ var init_library_copies = () => {};
14864
14965
 
14865
14966
  // src/reimport.ts
14866
14967
  import { createHash as createHash3 } from "node:crypto";
14867
- import { existsSync as existsSync13, readFileSync as readFileSync15, rmSync as rmSync8, statSync as statSync11, utimesSync, writeFileSync as writeFileSync8 } from "node:fs";
14968
+ import { existsSync as existsSync12, readFileSync as readFileSync15, rmSync as rmSync8, statSync as statSync11, utimesSync, writeFileSync as writeFileSync8 } from "node:fs";
14868
14969
  import { basename as basename8, join as join15 } from "node:path";
14869
14970
  function forceNextImport(projectPath, resourcePath) {
14870
14971
  const relative = resourcePath.replace(/^res:\/\//, "");
14871
14972
  const sidecar = join15(projectPath, `${relative}.import`);
14872
- if (!existsSync13(sidecar)) {
14973
+ if (!existsSync12(sidecar)) {
14873
14974
  return;
14874
14975
  }
14875
14976
  const { atime, mtimeMs } = statSync11(sidecar);
@@ -14884,7 +14985,7 @@ function importRecord(projectPath, resourcePath) {
14884
14985
  var init_reimport = () => {};
14885
14986
 
14886
14987
  // src/scratch.ts
14887
- import { existsSync as existsSync14, mkdtempSync as mkdtempSync2, readdirSync as readdirSync10, rmSync as rmSync9 } from "node:fs";
14988
+ import { existsSync as existsSync13, mkdtempSync as mkdtempSync2, readdirSync as readdirSync10, rmSync as rmSync9 } from "node:fs";
14888
14989
  import { tmpdir as tmpdir5 } from "node:os";
14889
14990
  import { join as join16 } from "node:path";
14890
14991
  import process10 from "node:process";
@@ -14943,7 +15044,7 @@ function tryAgain(path, attempt) {
14943
15044
  lastTryOnExit();
14944
15045
  }
14945
15046
  const after = AGAIN_AFTER_MS[attempt];
14946
- if (after === undefined || !existsSync14(path)) {
15047
+ if (after === undefined || !existsSync13(path)) {
14947
15048
  waiting.delete(path);
14948
15049
  if (after === undefined) {
14949
15050
  atExit.add(path);
@@ -15099,7 +15200,7 @@ var init_script_origins = __esm(() => {
15099
15200
  });
15100
15201
 
15101
15202
  // src/headless.ts
15102
- import { existsSync as existsSync15, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "node:fs";
15203
+ import { existsSync as existsSync14, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "node:fs";
15103
15204
  import { dirname as dirname7, join as join18 } from "node:path";
15104
15205
  function snakeCased(params) {
15105
15206
  const result = emptyRecord();
@@ -15388,7 +15489,7 @@ async function operationOutcome(engine, operation, params, projectPath) {
15388
15489
  };
15389
15490
  }
15390
15491
  function answerIn(file) {
15391
- if (!existsSync15(file)) {
15492
+ if (!existsSync14(file)) {
15392
15493
  return { kind: "none" };
15393
15494
  }
15394
15495
  const text = readFileSync16(file, "utf8");
@@ -15417,7 +15518,7 @@ var init_headless = __esm(() => {
15417
15518
  });
15418
15519
 
15419
15520
  // src/update-check.ts
15420
- import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "node:fs";
15521
+ import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "node:fs";
15421
15522
  import { homedir as homedir6, tmpdir as tmpdir6 } from "node:os";
15422
15523
  import { join as join19 } from "node:path";
15423
15524
  function packageUrl(root) {
@@ -15459,7 +15560,7 @@ function cacheFile(environment = process.env) {
15459
15560
  }
15460
15561
  function readCache(path) {
15461
15562
  try {
15462
- if (!existsSync16(path)) {
15563
+ if (!existsSync15(path)) {
15463
15564
  return null;
15464
15565
  }
15465
15566
  const parsed = JSON.parse(readFileSync17(path, "utf8"));
@@ -15939,7 +16040,7 @@ var init_ports = __esm(() => {
15939
16040
  });
15940
16041
 
15941
16042
  // src/lsp_client.ts
15942
- import { existsSync as existsSync17, realpathSync as realpathSync3 } from "node:fs";
16043
+ import { existsSync as existsSync16, realpathSync as realpathSync3 } from "node:fs";
15943
16044
  import { readFile, realpath } from "node:fs/promises";
15944
16045
  import { createConnection } from "node:net";
15945
16046
  import { dirname as dirname8, extname as extname3, join as join20, resolve as resolve7 } from "node:path";
@@ -16620,7 +16721,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
16620
16721
  } catch {
16621
16722
  throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
16622
16723
  }
16623
- if (!existsSync17(join20(projectPath, "project.godot"))) {
16724
+ if (!existsSync16(join20(projectPath, "project.godot"))) {
16624
16725
  throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
16625
16726
  }
16626
16727
  const contained = resolveWithinProject(projectPath, scriptPathValue);
@@ -17134,7 +17235,7 @@ function readParams(params, key) {
17134
17235
  import { createHash as createHash5 } from "node:crypto";
17135
17236
  import {
17136
17237
  cpSync,
17137
- existsSync as existsSync19,
17238
+ existsSync as existsSync18,
17138
17239
  mkdirSync as mkdirSync6,
17139
17240
  readdirSync as readdirSync12,
17140
17241
  readFileSync as readFileSync21,
@@ -17217,7 +17318,7 @@ function installedRuntimeDigest(projectPath) {
17217
17318
  }
17218
17319
  function digestMarker(projectPath, addon) {
17219
17320
  const marker = join22(projectPath, "addons", addon, DIGEST_MARKER);
17220
- if (!existsSync19(marker)) {
17321
+ if (!existsSync18(marker)) {
17221
17322
  return null;
17222
17323
  }
17223
17324
  const said = readFileSync21(marker, "utf8").trim();
@@ -17234,7 +17335,7 @@ function shippedLoader() {
17234
17335
  }
17235
17336
  function loaderState(projectPath) {
17236
17337
  const copy = join22(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
17237
- if (!existsSync19(copy)) {
17338
+ if (!existsSync18(copy)) {
17238
17339
  return "missing";
17239
17340
  }
17240
17341
  const lines = (path) => readFileSync21(path, "utf8").replaceAll(`\r
@@ -17253,7 +17354,7 @@ function writeLoader(projectPath) {
17253
17354
  }
17254
17355
  function removeLoader(projectPath) {
17255
17356
  const copy = join22(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
17256
- if (!existsSync19(copy)) {
17357
+ if (!existsSync18(copy)) {
17257
17358
  return false;
17258
17359
  }
17259
17360
  rmSync10(copy);
@@ -17269,10 +17370,10 @@ function shippedListedRunner() {
17269
17370
  }
17270
17371
  function gdUnit4Unusable(projectPath) {
17271
17372
  const marker = "addons/gdUnit4/plugin.cfg";
17272
- if (!existsSync19(join22(projectPath, ...marker.split("/")))) {
17373
+ if (!existsSync18(join22(projectPath, ...marker.split("/")))) {
17273
17374
  return { missing: true, reason: `gdUnit4 is not installed in this project: no ${marker}` };
17274
17375
  }
17275
- if (!existsSync19(join22(projectPath, ...GDUNIT4_CI_RUNNER.split("/")))) {
17376
+ if (!existsSync18(join22(projectPath, ...GDUNIT4_CI_RUNNER.split("/")))) {
17276
17377
  return {
17277
17378
  missing: false,
17278
17379
  reason: `gdUnit4 in this project is older than 6.0, which the shipped runner needs: no ${GDUNIT4_CI_RUNNER}`
@@ -17284,11 +17385,11 @@ function installAddons(projectPath, from = shippedAddonsDirectory()) {
17284
17385
  const installed = [];
17285
17386
  for (const name of ADDONS) {
17286
17387
  const source = join22(from, name);
17287
- if (!existsSync19(join22(source, addonMarking(name)))) {
17388
+ if (!existsSync18(join22(source, addonMarking(name)))) {
17288
17389
  throw new Error(`The package holds no ${name} addon at ${source}.`);
17289
17390
  }
17290
17391
  const target = join22(projectPath, "addons", name);
17291
- const replaced = existsSync19(target);
17392
+ const replaced = existsSync18(target);
17292
17393
  rmSync10(target, { recursive: true, force: true });
17293
17394
  mkdirSync6(dirname9(target), { recursive: true });
17294
17395
  cpSync(source, target, { recursive: true });
@@ -17304,7 +17405,7 @@ function installAddons(projectPath, from = shippedAddonsDirectory()) {
17304
17405
  }
17305
17406
  function installedAddonVersion(projectPath) {
17306
17407
  const marker = join22(projectPath, "addons", ADDONS[0], VERSION_MARKER);
17307
- if (!existsSync19(marker)) {
17408
+ if (!existsSync18(marker)) {
17308
17409
  return null;
17309
17410
  }
17310
17411
  const said = readFileSync21(marker, "utf8").trim();
@@ -17314,13 +17415,13 @@ function removeAddons(projectPath) {
17314
17415
  const removed = [];
17315
17416
  for (const name of ADDONS) {
17316
17417
  const target = join22(projectPath, "addons", name);
17317
- if (existsSync19(target)) {
17418
+ if (existsSync18(target)) {
17318
17419
  rmSync10(target, { recursive: true, force: true });
17319
17420
  removed.push(target);
17320
17421
  }
17321
17422
  }
17322
17423
  const addons = join22(projectPath, "addons");
17323
- if (removed.length > 0 && existsSync19(addons) && readdirSync12(addons).length === 0) {
17424
+ if (removed.length > 0 && existsSync18(addons) && readdirSync12(addons).length === 0) {
17324
17425
  rmdirSync(addons);
17325
17426
  removed.push(addons);
17326
17427
  }
@@ -17420,8 +17521,8 @@ function inspectProject(projectPath) {
17420
17521
  const addons = ADDONS.map((name) => {
17421
17522
  const directory = join22(projectPath, "addons", name);
17422
17523
  const marker = join22(directory, VERSION_MARKER);
17423
- const installed = existsSync19(directory);
17424
- const version = existsSync19(marker) ? readFileSync21(marker, "utf8").trim() : null;
17524
+ const installed = existsSync18(directory);
17525
+ const version = existsSync18(marker) ? readFileSync21(marker, "utf8").trim() : null;
17425
17526
  const current = version === SERVER_VERSION;
17426
17527
  if (!installed) {
17427
17528
  problems.push(`addons/${name} is not installed; run gdharness setup`);
@@ -17454,7 +17555,7 @@ function inspectProject(projectPath) {
17454
17555
  const ignoredAutoloads = [];
17455
17556
  const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
17456
17557
  for (const [name, path] of named) {
17457
- if (!existsSync19(join22(projectPath, path))) {
17558
+ if (!existsSync18(join22(projectPath, path))) {
17458
17559
  if (!addonMissing(path)) {
17459
17560
  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`);
17460
17561
  }
@@ -18338,7 +18439,7 @@ var init_tool_definitions = __esm(() => {
18338
18439
  name: "scene_node",
18339
18440
  title: "Scene nodes",
18340
18441
  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}`,
18341
- 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,
18442
+ 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,
18342
18443
  parameters: {
18343
18444
  projectPath: PROJECT_PATH,
18344
18445
  scenePath: SCENE_PATH,
@@ -19079,7 +19180,7 @@ var init_tool_definitions = __esm(() => {
19079
19180
  },
19080
19181
  wait: {
19081
19182
  effect: "read",
19082
- summary: "wait for the run to end, then answer as editor_output does",
19183
+ summary: "wait for the run to end, or to be held by its debugger at a breakpoint, a pause or a step, then answer as editor_output does",
19083
19184
  requires: []
19084
19185
  },
19085
19186
  profile: {
@@ -20025,7 +20126,7 @@ var init_tool_reference = __esm(() => {
20025
20126
  });
20026
20127
 
20027
20128
  // src/suite-plan.ts
20028
- import { existsSync as existsSync23, mkdirSync as mkdirSync9, readFileSync as readFileSync24 } from "node:fs";
20129
+ import { existsSync as existsSync22, mkdirSync as mkdirSync9, readFileSync as readFileSync24 } from "node:fs";
20029
20130
  import { dirname as dirname11, join as join26, posix as posix3 } from "node:path";
20030
20131
  function within(path, folder) {
20031
20132
  return path === folder || path.startsWith(`${folder}/`);
@@ -20437,7 +20538,7 @@ function windowNeedsOf(scripts, paths) {
20437
20538
  }
20438
20539
  function capsFrames(projectPath) {
20439
20540
  const file = `${projectPath}/project.godot`;
20440
- if (!existsSync23(file)) {
20541
+ if (!existsSync22(file)) {
20441
20542
  return false;
20442
20543
  }
20443
20544
  const cap = parseProjectGodot(readFileSync24(file, "utf8"))["application"]?.["run/max_fps"];
@@ -20445,14 +20546,14 @@ function capsFrames(projectPath) {
20445
20546
  }
20446
20547
  function gdUnitFailsOnPushErrors(projectPath) {
20447
20548
  const file = `${projectPath}/project.godot`;
20448
- if (!existsSync23(file)) {
20549
+ if (!existsSync22(file)) {
20449
20550
  return false;
20450
20551
  }
20451
20552
  return parseProjectGodot(readFileSync24(file, "utf8"))["gdunit4"]?.["report/godot/push_error"] === true;
20452
20553
  }
20453
20554
  function viewportOf(projectPath) {
20454
20555
  const file = `${projectPath}/project.godot`;
20455
- if (!existsSync23(file)) {
20556
+ if (!existsSync22(file)) {
20456
20557
  return null;
20457
20558
  }
20458
20559
  const text = readFileSync24(file, "utf8");
@@ -42074,7 +42175,7 @@ var init_stdio = __esm(() => {
42074
42175
  });
42075
42176
 
42076
42177
  // src/autoload-failure.ts
42077
- import { existsSync as existsSync25, readFileSync as readFileSync27 } from "node:fs";
42178
+ import { existsSync as existsSync24, readFileSync as readFileSync27 } from "node:fs";
42078
42179
  import { join as join29 } from "node:path";
42079
42180
  function scriptsAutoloaded(projectPath) {
42080
42181
  const scripts = new Map;
@@ -42090,7 +42191,7 @@ function scriptsAutoloaded(projectPath) {
42090
42191
  continue;
42091
42192
  }
42092
42193
  const scene = join29(projectPath, path);
42093
- if (!existsSync25(scene)) {
42194
+ if (!existsSync24(scene)) {
42094
42195
  continue;
42095
42196
  }
42096
42197
  try {
@@ -42113,11 +42214,11 @@ var init_autoload_failure = __esm(() => {
42113
42214
  });
42114
42215
 
42115
42216
  // src/runtime-client.ts
42116
- import { existsSync as existsSync26, readdirSync as readdirSync16, readFileSync as readFileSync28, statSync as statSync15, unlinkSync as unlinkSync3 } from "node:fs";
42217
+ import { existsSync as existsSync25, readdirSync as readdirSync16, readFileSync as readFileSync28, statSync as statSync15, unlinkSync as unlinkSync3 } from "node:fs";
42117
42218
  import { createConnection as createConnection2 } from "node:net";
42118
42219
  import { tmpdir as tmpdir7 } from "node:os";
42119
42220
  import { dirname as dirname14, join as join30, resolve as resolve12 } from "node:path";
42120
- import { setTimeout as delay5 } from "node:timers/promises";
42221
+ import { setTimeout as delay6 } from "node:timers/promises";
42121
42222
  function runtimeDirectory(variables = process.env) {
42122
42223
  const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
42123
42224
  if (explicit) {
@@ -42137,7 +42238,7 @@ function runtimeDirectories(variables = process.env) {
42137
42238
  function errorReportOf(pid, directories = runtimeDirectories()) {
42138
42239
  for (const directory of directories) {
42139
42240
  const path = join30(directory, `runtime-${pid}.log`);
42140
- if (existsSync26(path)) {
42241
+ if (existsSync25(path)) {
42141
42242
  return path;
42142
42243
  }
42143
42244
  }
@@ -42283,7 +42384,7 @@ function strangersAmong(candidates, now, startTimes = startTimesOf) {
42283
42384
  }).map((one) => one.file));
42284
42385
  }
42285
42386
  function announcedIn(directory) {
42286
- if (!existsSync26(directory)) {
42387
+ if (!existsSync25(directory)) {
42287
42388
  return [];
42288
42389
  }
42289
42390
  const found = [];
@@ -42336,7 +42437,7 @@ function announcedIn(directory) {
42336
42437
  return found;
42337
42438
  }
42338
42439
  async function announcementEnded(pid, directories = runtimeDirectories()) {
42339
- const files = directories.map((directory) => join30(directory, `runtime-${pid}.json`)).filter((file) => existsSync26(file));
42440
+ const files = directories.map((directory) => join30(directory, `runtime-${pid}.json`)).filter((file) => existsSync25(file));
42340
42441
  const first = files[0];
42341
42442
  if (first === undefined) {
42342
42443
  return;
@@ -42347,7 +42448,7 @@ async function announcementEnded(pid, directories = runtimeDirectories()) {
42347
42448
  if (left <= 0) {
42348
42449
  return;
42349
42450
  }
42350
- await delay5(Math.min(LOOK_EVERY_MS, left));
42451
+ await delay6(Math.min(LOOK_EVERY_MS, left));
42351
42452
  }
42352
42453
  wentAway(first, pid);
42353
42454
  for (const file of files) {
@@ -42379,7 +42480,7 @@ async function announcedSince(projectPath, before, waiting = {}) {
42379
42480
  if (left <= 0 || waiting.giveUp?.() === true) {
42380
42481
  return [];
42381
42482
  }
42382
- await delay5(Math.min(LOOK_EVERY_MS, left));
42483
+ await delay6(Math.min(LOOK_EVERY_MS, left));
42383
42484
  }
42384
42485
  }
42385
42486
  function describe2(endpoint) {
@@ -42915,11 +43016,11 @@ var init_class_note = __esm(() => {
42915
43016
  });
42916
43017
 
42917
43018
  // src/config-pin.ts
42918
- import { existsSync as existsSync27, readFileSync as readFileSync32 } from "node:fs";
43019
+ import { existsSync as existsSync26, readFileSync as readFileSync32 } from "node:fs";
42919
43020
  import { join as join34 } from "node:path";
42920
43021
  function versionsNamedInConfig(projectPath) {
42921
43022
  const config = join34(projectPath, ".mcp.json");
42922
- if (!existsSync27(config)) {
43023
+ if (!existsSync26(config)) {
42923
43024
  return [];
42924
43025
  }
42925
43026
  let text;
@@ -42945,7 +43046,7 @@ var init_config_pin = __esm(() => {
42945
43046
  });
42946
43047
 
42947
43048
  // src/cut-test-run.ts
42948
- import { existsSync as existsSync28, mkdirSync as mkdirSync14, readFileSync as readFileSync33, rmSync as rmSync15, writeFileSync as writeFileSync16 } from "node:fs";
43049
+ import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync33, rmSync as rmSync15, writeFileSync as writeFileSync16 } from "node:fs";
42949
43050
  import { dirname as dirname18, join as join35 } from "node:path";
42950
43051
  function keptAt(projectPath) {
42951
43052
  return join35(projectPath, ".godot", "gdharness-reports", "cut-run.json");
@@ -42960,7 +43061,7 @@ function keepCutTestRun(projectPath, run) {
42960
43061
  }
42961
43062
  function takeCutTestRun(projectPath) {
42962
43063
  const path = keptAt(projectPath);
42963
- if (!existsSync28(path)) {
43064
+ if (!existsSync27(path)) {
42964
43065
  return null;
42965
43066
  }
42966
43067
  try {
@@ -43006,7 +43107,7 @@ var init_game_debugger = __esm(() => {
43006
43107
 
43007
43108
  // src/dap_client.ts
43008
43109
  import { createConnection as createConnection3 } from "node:net";
43009
- import { setTimeout as delay6 } from "node:timers/promises";
43110
+ import { setTimeout as delay7 } from "node:timers/promises";
43010
43111
  function fileKey(path) {
43011
43112
  return process.platform === "win32" ? path.replaceAll("\\", "/").toLowerCase() : path;
43012
43113
  }
@@ -43486,7 +43587,7 @@ class GodotDAPClient {
43486
43587
  await this.sendRequest(command, { threadId: resolvedThreadId });
43487
43588
  const deadline = Date.now() + this.stepPatienceMs;
43488
43589
  while (this.stops === before && this.connected && Date.now() < deadline) {
43489
- await delay6(50);
43590
+ await delay7(50);
43490
43591
  }
43491
43592
  return this.stops !== before;
43492
43593
  }
@@ -43512,7 +43613,7 @@ class GodotDAPClient {
43512
43613
  return answered;
43513
43614
  }
43514
43615
  } catch {}
43515
- await delay6(200);
43616
+ await delay7(200);
43516
43617
  }
43517
43618
  return null;
43518
43619
  }
@@ -43533,7 +43634,7 @@ class GodotDAPClient {
43533
43634
  break;
43534
43635
  }
43535
43636
  } catch {}
43536
- await delay6(200);
43637
+ await delay7(200);
43537
43638
  }
43538
43639
  if (scopes.length === 0) {
43539
43640
  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`);
@@ -43566,7 +43667,7 @@ class GodotDAPClient {
43566
43667
  return { frame, value, ...typeof type === "string" ? { type } : {} };
43567
43668
  }
43568
43669
  } catch {}
43569
- await delay6(200);
43670
+ await delay7(200);
43570
43671
  }
43571
43672
  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`);
43572
43673
  }
@@ -43728,7 +43829,7 @@ var init_dap_client = __esm(() => {
43728
43829
  // src/editor-log.ts
43729
43830
  import {
43730
43831
  closeSync as closeSync5,
43731
- existsSync as existsSync29,
43832
+ existsSync as existsSync28,
43732
43833
  mkdirSync as mkdirSync15,
43733
43834
  openSync as openSync5,
43734
43835
  readFileSync as readFileSync34,
@@ -43792,7 +43893,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
43792
43893
  return { kind: "another editor", notedPid: noted?.pid ?? null };
43793
43894
  }
43794
43895
  const path = editorLogPath(projectPath);
43795
- if (!existsSync29(path)) {
43896
+ if (!existsSync28(path)) {
43796
43897
  return { kind: "not written" };
43797
43898
  }
43798
43899
  const log = new GameLog;
@@ -44011,7 +44112,7 @@ var init_editor_log = __esm(() => {
44011
44112
  });
44012
44113
 
44013
44114
  // src/rename.ts
44014
- import { existsSync as existsSync30, mkdirSync as mkdirSync16, readFileSync as readFileSync35, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync18 } from "node:fs";
44115
+ import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync35, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync18 } from "node:fs";
44015
44116
  import { dirname as dirname20, posix as posix4 } from "node:path";
44016
44117
 
44017
44118
  class Script {
@@ -44736,7 +44837,7 @@ function applyRewrites(rewrites) {
44736
44837
  if (now !== rewrite.before) {
44737
44838
  throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
44738
44839
  }
44739
- if (rewrite.writeTo !== rewrite.absolute && existsSync30(rewrite.writeTo)) {
44840
+ if (rewrite.writeTo !== rewrite.absolute && existsSync29(rewrite.writeTo)) {
44740
44841
  throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
44741
44842
  }
44742
44843
  }
@@ -44750,7 +44851,7 @@ function applyRewrites(rewrites) {
44750
44851
  let movedUid = false;
44751
44852
  if (rewrite.writeTo !== rewrite.absolute) {
44752
44853
  unlinkSync4(rewrite.absolute);
44753
- if (existsSync30(`${rewrite.absolute}.uid`)) {
44854
+ if (existsSync29(`${rewrite.absolute}.uid`)) {
44754
44855
  renameSync4(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
44755
44856
  movedUid = true;
44756
44857
  }
@@ -44796,7 +44897,7 @@ var init_rename = __esm(() => {
44796
44897
  });
44797
44898
 
44798
44899
  // src/rename-symbol.ts
44799
- import { existsSync as existsSync31, mkdirSync as mkdirSync17, readdirSync as readdirSync17, realpathSync as realpathSync5, rmSync as rmSync17, writeFileSync as writeFileSync19 } from "node:fs";
44900
+ import { existsSync as existsSync30, mkdirSync as mkdirSync17, readdirSync as readdirSync17, realpathSync as realpathSync5, rmSync as rmSync17, writeFileSync as writeFileSync19 } from "node:fs";
44800
44901
  import { basename as basename10, dirname as dirname21, join as join37, relative as relative7, resolve as resolve13 } from "node:path";
44801
44902
  function resourcePathOf(projectPath, absolute) {
44802
44903
  const inside = relative7(onDiskSpelling(projectPath), onDiskSpelling(absolute));
@@ -44864,7 +44965,7 @@ async function renameSymbol(request, services) {
44864
44965
  reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
44865
44966
  };
44866
44967
  }
44867
- if (existsSync31(request.newScriptPath)) {
44968
+ if (existsSync30(request.newScriptPath)) {
44868
44969
  return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
44869
44970
  }
44870
44971
  }
@@ -45041,7 +45142,7 @@ async function renameSymbol(request, services) {
45041
45142
  scripts,
45042
45143
  moves: new Map([[declaringPath, moveTo]]),
45043
45144
  named: [declaringPath],
45044
- exists: (path) => existsSync31(`${projectPath}/${path.slice("res://".length)}`)
45145
+ exists: (path) => existsSync30(`${projectPath}/${path.slice("res://".length)}`)
45045
45146
  }, plan);
45046
45147
  }
45047
45148
  plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? [] : [declaringPath]);
@@ -45316,7 +45417,7 @@ var init_rename_symbol = __esm(() => {
45316
45417
 
45317
45418
  // src/file-move.ts
45318
45419
  import {
45319
- existsSync as existsSync32,
45420
+ existsSync as existsSync31,
45320
45421
  mkdirSync as mkdirSync18,
45321
45422
  readdirSync as readdirSync18,
45322
45423
  readFileSync as readFileSync36,
@@ -45349,7 +45450,7 @@ function unseenByTheEngine(projectPath, path) {
45349
45450
  }
45350
45451
  if (at < steps.length - 1) {
45351
45452
  directory = join38(directory, step);
45352
- if (existsSync32(join38(directory, ".gdignore"))) {
45453
+ if (existsSync31(join38(directory, ".gdignore"))) {
45353
45454
  return `${path} is under a directory holding a .gdignore, which the engine does not load`;
45354
45455
  }
45355
45456
  }
@@ -45361,12 +45462,12 @@ function installedAddonOf(projectPath, path) {
45361
45462
  if (steps[0] !== "addons" || steps[1] === undefined) {
45362
45463
  return null;
45363
45464
  }
45364
- return existsSync32(join38(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
45465
+ return existsSync31(join38(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
45365
45466
  }
45366
45467
  function binaryResources(projectPath) {
45367
45468
  const found = [];
45368
45469
  const visit = (directory, prefix) => {
45369
- if (existsSync32(join38(directory, ".gdignore"))) {
45470
+ if (existsSync31(join38(directory, ".gdignore"))) {
45370
45471
  return;
45371
45472
  }
45372
45473
  for (const entry of readdirSync18(directory, { withFileTypes: true })) {
@@ -45403,7 +45504,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
45403
45504
  if (to === null) {
45404
45505
  return { ok: false, reason: `newPath must be a path inside the project, and ${absoluteTo} is not.` };
45405
45506
  }
45406
- if (!existsSync32(absoluteFrom)) {
45507
+ if (!existsSync31(absoluteFrom)) {
45407
45508
  return { ok: false, reason: `There is nothing at ${from} to move.` };
45408
45509
  }
45409
45510
  if (from === to) {
@@ -45432,7 +45533,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
45432
45533
  if (isDirectory && to.startsWith(`${from}/`)) {
45433
45534
  return { ok: false, reason: `${from} cannot be moved into itself.` };
45434
45535
  }
45435
- if (existsSync32(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
45536
+ if (existsSync31(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
45436
45537
  return { ok: false, reason: `${to} already exists, so nothing was moved there.` };
45437
45538
  }
45438
45539
  const unseen = unseenByTheEngine(projectPath, to);
@@ -45440,7 +45541,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
45440
45541
  return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
45441
45542
  }
45442
45543
  if (!isDirectory) {
45443
- 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))));
45544
+ 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))));
45444
45545
  if (owner !== undefined && owner !== null) {
45445
45546
  return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
45446
45547
  }
@@ -45462,7 +45563,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
45462
45563
  } else {
45463
45564
  moves.set(from, to);
45464
45565
  for (const sidecar of SIDECARS) {
45465
- if (existsSync32(`${absoluteFrom}${sidecar}`)) {
45566
+ if (existsSync31(`${absoluteFrom}${sidecar}`)) {
45466
45567
  moves.set(`${from}${sidecar}`, `${to}${sidecar}`);
45467
45568
  }
45468
45569
  }
@@ -45474,7 +45575,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
45474
45575
  scripts,
45475
45576
  moves,
45476
45577
  named: [from],
45477
- exists: (path) => existsSync32(join38(projectPath, path.slice("res://".length)))
45578
+ exists: (path) => existsSync31(join38(projectPath, path.slice("res://".length)))
45478
45579
  }, { edits: [], mentions: [] });
45479
45580
  plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
45480
45581
  const binaries = [];
@@ -45545,13 +45646,13 @@ function applyFileMove(outcome) {
45545
45646
  }
45546
45647
  }
45547
45648
  for (const file of outcome.moved) {
45548
- if (!existsSync32(file.absoluteFrom)) {
45649
+ if (!existsSync31(file.absoluteFrom)) {
45549
45650
  throw new Error(`${file.from} is gone from disk; nothing was written`);
45550
45651
  }
45551
45652
  if (file.text !== null && readFileSync36(file.absoluteFrom, "utf8") !== file.text.before) {
45552
45653
  throw new Error(`${file.from} changed on disk while the move was being worked out; nothing was written`);
45553
45654
  }
45554
- if (existsSync32(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
45655
+ if (existsSync31(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
45555
45656
  throw new Error(`${file.to} already exists; nothing was written`);
45556
45657
  }
45557
45658
  }
@@ -50277,10 +50378,10 @@ var init_server_instructions = __esm(() => {
50277
50378
  });
50278
50379
 
50279
50380
  // src/suites-scene.ts
50280
- import { existsSync as existsSync33, mkdirSync as mkdirSync22, writeFileSync as writeFileSync22 } from "node:fs";
50381
+ import { existsSync as existsSync32, mkdirSync as mkdirSync22, writeFileSync as writeFileSync22 } from "node:fs";
50281
50382
  import { join as join42, relative as relative9, sep as sep5 } from "node:path";
50282
50383
  function hasSuitesScene(projectPath) {
50283
- return existsSync33(join42(projectPath, SUITES_SCENE));
50384
+ return existsSync32(join42(projectPath, SUITES_SCENE));
50284
50385
  }
50285
50386
  function suitesSceneArgs(projectPath, suites) {
50286
50387
  const runs = join42(projectPath, ".godot", "gdharness", "suite-scenes");
@@ -50315,7 +50416,7 @@ var init_suites_scene = __esm(() => {
50315
50416
  });
50316
50417
 
50317
50418
  // src/test-selection.ts
50318
- import { existsSync as existsSync34 } from "node:fs";
50419
+ import { existsSync as existsSync33 } from "node:fs";
50319
50420
  import { basename as basename13, join as join43 } from "node:path";
50320
50421
  function casesOutOfPath(projectPath, given, tests) {
50321
50422
  const entries = Array.isArray(given) ? given : [given];
@@ -50360,12 +50461,12 @@ function casesIn(scripts, suite) {
50360
50461
  }
50361
50462
  function scriptsWithoutCases(projectPath, asked) {
50362
50463
  const scripts = new ProjectScripts(projectPath);
50363
- return asked.filter((path) => path.endsWith(".gd") && existsSync34(join43(projectPath, path.slice("res://".length))) && casesIn(scripts, path).length === 0);
50464
+ return asked.filter((path) => path.endsWith(".gd") && existsSync33(join43(projectPath, path.slice("res://".length))) && casesIn(scripts, path).length === 0);
50364
50465
  }
50365
50466
  function suitesUnder(projectPath, asked) {
50366
50467
  const scripts = projectFiles(projectPath, [".gd"]).map((path) => `res://${path}`);
50367
50468
  return [
50368
- ...new Set(asked.flatMap((path) => path.endsWith(".gd") ? existsSync34(join43(projectPath, path.slice("res://".length))) ? [path] : [] : scripts.filter((script) => script.startsWith(`${path}/`))))
50469
+ ...new Set(asked.flatMap((path) => path.endsWith(".gd") ? existsSync33(join43(projectPath, path.slice("res://".length))) ? [path] : [] : scripts.filter((script) => script.startsWith(`${path}/`))))
50369
50470
  ];
50370
50471
  }
50371
50472
  function selectTests(projectPath, asked, given) {
@@ -50431,7 +50532,7 @@ import {
50431
50532
  closeSync as closeSync6,
50432
50533
  constants,
50433
50534
  copyFileSync as copyFileSync2,
50434
- existsSync as existsSync35,
50535
+ existsSync as existsSync34,
50435
50536
  fstatSync as fstatSync3,
50436
50537
  mkdirSync as mkdirSync23,
50437
50538
  openSync as openSync7,
@@ -50444,7 +50545,7 @@ import {
50444
50545
  writeFileSync as writeFileSync23
50445
50546
  } from "node:fs";
50446
50547
  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";
50447
- import { setTimeout as delay7 } from "node:timers/promises";
50548
+ import { setTimeout as delay8 } from "node:timers/promises";
50448
50549
  import { fileURLToPath as fileURLToPath5 } from "node:url";
50449
50550
  function editorRestartTimeoutMs() {
50450
50551
  return waitFromEnv("GDHARNESS_EDITOR_RESTART_TIMEOUT_MS", 90000);
@@ -50459,7 +50560,7 @@ function greetingWaitMs() {
50459
50560
  function pictureToSend(pngPath, jpegPath) {
50460
50561
  const png = readFileSync40(pngPath);
50461
50562
  const beside = pngPath.replace(/\.png$/, ".jpg");
50462
- const jpeg = jpegPath !== undefined && normalize2(jpegPath) === normalize2(beside) && existsSync35(beside) ? readFileSync40(beside) : null;
50563
+ const jpeg = jpegPath !== undefined && normalize2(jpegPath) === normalize2(beside) && existsSync34(beside) ? readFileSync40(beside) : null;
50463
50564
  const sent = jpeg !== null && jpeg.length < png.length ? jpeg : png;
50464
50565
  return {
50465
50566
  data: sent.toString("base64"),
@@ -50499,7 +50600,7 @@ function writtenFileRefusal(path, rule) {
50499
50600
  return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
50500
50601
  }
50501
50602
  const directory = realAsFarAsItExists(dirname25(path));
50502
- if (!existsSync35(directory)) {
50603
+ if (!existsSync34(directory)) {
50503
50604
  return `${named}'s directory ${dirname25(path)} does not exist; ${what} creates no directories.`;
50504
50605
  }
50505
50606
  const where = join44(directory, basename14(path));
@@ -50507,7 +50608,7 @@ function writtenFileRefusal(path, rule) {
50507
50608
  if (inside !== undefined) {
50508
50609
  return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
50509
50610
  }
50510
- const taken = rule.writes(where).find((file) => existsSync35(file));
50611
+ const taken = rule.writes(where).find((file) => existsSync34(file));
50511
50612
  if (taken !== undefined) {
50512
50613
  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.`;
50513
50614
  }
@@ -50731,7 +50832,7 @@ async function untilGone(pids, withinMs = stopWaitMs()) {
50731
50832
  const gone = () => pids.every((pid) => !alive(pid));
50732
50833
  const deadline = Date.now() + withinMs;
50733
50834
  while (!gone() && Date.now() < deadline) {
50734
- await delay7(50);
50835
+ await delay8(50);
50735
50836
  }
50736
50837
  return gone();
50737
50838
  }
@@ -51293,7 +51394,7 @@ function unsavedInEditorNote(scripts) {
51293
51394
  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.`;
51294
51395
  }
51295
51396
  function editorAddonMissing(projectPath) {
51296
- if (!existsSync35(join44(projectPath, "project.godot"))) {
51397
+ if (!existsSync34(join44(projectPath, "project.godot"))) {
51297
51398
  return null;
51298
51399
  }
51299
51400
  const report = inspectProject(projectPath);
@@ -51837,7 +51938,7 @@ function uncaughtNote(uncaught) {
51837
51938
  }
51838
51939
  function elsewhereIn(projectPath, asked) {
51839
51940
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
51840
- if (wanted === "" || existsSync35(join44(projectPath, wanted))) {
51941
+ if (wanted === "" || existsSync34(join44(projectPath, wanted))) {
51841
51942
  return "";
51842
51943
  }
51843
51944
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
@@ -51860,7 +51961,7 @@ function windowNoteFor(needing, sharing) {
51860
51961
  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}`;
51861
51962
  }
51862
51963
  function testPathsIn(projectPath, given, named = "path") {
51863
- const defaults = TEST_DIRECTORIES.filter((directory) => existsSync35(join44(projectPath, directory)));
51964
+ const defaults = TEST_DIRECTORIES.filter((directory) => existsSync34(join44(projectPath, directory)));
51864
51965
  const entries = given === undefined || given === null || given === "" ? defaults.length > 0 ? defaults : TEST_DIRECTORIES : given;
51865
51966
  const listed = Array.isArray(entries) ? entries : [entries];
51866
51967
  if (listed.length === 0) {
@@ -52147,7 +52248,7 @@ class GodotServer {
52147
52248
  taken = true;
52148
52249
  this.logDebug(`Took port ${wanted} over from pid ${announced.pid}`);
52149
52250
  } catch {
52150
- await delay7(HANDOVER_POLL_MS);
52251
+ await delay8(HANDOVER_POLL_MS);
52151
52252
  }
52152
52253
  }
52153
52254
  this.notHandedOverBy = taken ? null : announced.pid;
@@ -52285,7 +52386,7 @@ class GodotServer {
52285
52386
  argv: process.argv.slice(1),
52286
52387
  cwd,
52287
52388
  project: this.ownProject,
52288
- mcpJson: existsSync35(mcpJson) ? mcpJson : null
52389
+ mcpJson: existsSync34(mcpJson) ? mcpJson : null
52289
52390
  });
52290
52391
  if (predecessor !== null) {
52291
52392
  writeLifecycle("replaced", { project: this.ownProject, predecessor });
@@ -52427,7 +52528,7 @@ class GodotServer {
52427
52528
  return checked.response;
52428
52529
  }
52429
52530
  const named = args["projectPath"];
52430
- if (typeof named === "string" && existsSync35(join44(named, "project.godot"))) {
52531
+ if (typeof named === "string" && existsSync34(join44(named, "project.godot"))) {
52431
52532
  this.lastProjectPath = named;
52432
52533
  }
52433
52534
  this.updates.refresh();
@@ -53063,7 +53164,7 @@ class GodotServer {
53063
53164
  return this.createErrorResponse("line is required: the one-based line to stop on.");
53064
53165
  }
53065
53166
  if (op === "set") {
53066
- if (!existsSync35(located.absolutePath)) {
53167
+ if (!existsSync34(located.absolutePath)) {
53067
53168
  return this.missingScript(project.value.path, located.relativePath);
53068
53169
  }
53069
53170
  const nothing = lineNothingStopsOn(located.absolutePath, located.relativePath, line);
@@ -53168,7 +53269,7 @@ class GodotServer {
53168
53269
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
53169
53270
  }
53170
53271
  const file = join44(path, "project.godot");
53171
- if (!existsSync35(file)) {
53272
+ if (!existsSync34(file)) {
53172
53273
  return {
53173
53274
  ok: false,
53174
53275
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -53994,7 +54095,7 @@ class GodotServer {
53994
54095
  if (place !== null && !place.ok) {
53995
54096
  return this.createErrorResponse(place.reason);
53996
54097
  }
53997
- if (place !== null && !existsSync35(place.absolutePath)) {
54098
+ if (place !== null && !existsSync34(place.absolutePath)) {
53998
54099
  const folders = readdirSync20(project.value.path, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
53999
54100
  return this.createErrorResponse(`There is nothing at res://${place.relativePath} to search.${folders.length === 0 ? "" : ` The project's folders are ${folders.join(", ")}.`}`);
54000
54101
  }
@@ -54185,7 +54286,7 @@ ${platform}`;
54185
54286
  output.absolutePath
54186
54287
  ];
54187
54288
  this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
54188
- const writtenAt = () => existsSync35(output.absolutePath) ? statSync20(output.absolutePath).mtimeMs : null;
54289
+ const writtenAt = () => existsSync34(output.absolutePath) ? statSync20(output.absolutePath).mtimeMs : null;
54189
54290
  const caches = editorCacheDirectory(engine.value);
54190
54291
  const exportOnce = async () => {
54191
54292
  const before = writtenAt();
@@ -54215,7 +54316,7 @@ ${platform}`;
54215
54316
  return this.createErrorResponse(`Export could not be run: ${ending.failure ?? "the engine gave no exit status"}`);
54216
54317
  }
54217
54318
  const beside = writtenBeside(listedBefore, filesIn(folder), basename14(output.absolutePath), dirname25(output.relativePath).replaceAll("\\", "/"));
54218
- return exportAnswer(ending, { preset, outputPath: output.relativePath, debug, ...pack ? { pack } : {} }, before, writtenAt(), beside, docCacheNote, existsSync35(output.absolutePath) ? statSync20(output.absolutePath).size : null, importedFirstAnswer(imported));
54319
+ return exportAnswer(ending, { preset, outputPath: output.relativePath, debug, ...pack ? { pack } : {} }, before, writtenAt(), beside, docCacheNote, existsSync34(output.absolutePath) ? statSync20(output.absolutePath).size : null, importedFirstAnswer(imported));
54219
54320
  }
54220
54321
  reportTestProgress(log) {
54221
54322
  const progress = callProgress();
@@ -54276,7 +54377,7 @@ ${platform}`;
54276
54377
  if (movie !== null) {
54277
54378
  return this.createErrorResponse(movie);
54278
54379
  }
54279
- const missing = paths.value.filter((path) => !existsSync35(join44(project.value.path, path.slice("res://".length))));
54380
+ const missing = paths.value.filter((path) => !existsSync34(join44(project.value.path, path.slice("res://".length))));
54280
54381
  const projectScripts = new ProjectScripts(project.value.path);
54281
54382
  const present = paths.value.filter((path) => !missing.includes(path));
54282
54383
  const asked = projectScripts.searchedAsTold(present);
@@ -54468,7 +54569,7 @@ ${platform}`;
54468
54569
  await untilGone([run.process.pid]);
54469
54570
  }
54470
54571
  await run.settled;
54471
- const helperFailed = onDesktop ? helperFailure(helperFiles.error) ?? (existsSync35(helperFiles.pid) ? null : `it never started, and ${helperStep(helperFiles.step)}`) : null;
54572
+ const helperFailed = onDesktop ? helperFailure(helperFiles.error) ?? (existsSync34(helperFiles.pid) ? null : `it never started, and ${helperStep(helperFiles.step)}`) : null;
54472
54573
  if (helperFailed !== null) {
54473
54574
  discard(join44(project.value.path, ".godot", ...ours.split("/")), userData);
54474
54575
  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}`);
@@ -54484,10 +54585,10 @@ ${platform}`;
54484
54585
  const reportsDir = join44(project.value.path, ".godot", ...ours.split("/"));
54485
54586
  let report = null;
54486
54587
  let reportProblem = null;
54487
- const casePrinted = withNestedErrors(readRun(existsSync35(engineLog) ? readFileSync40(engineLog, "utf8") : ""), run.log.all);
54588
+ const casePrinted = withNestedErrors(readRun(existsSync34(engineLog) ? readFileSync40(engineLog, "utf8") : ""), run.log.all);
54488
54589
  const declaredErrors = readExpected(expectedFile);
54489
54590
  try {
54490
- 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))) : [];
54591
+ 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))) : [];
54491
54592
  const newest = written.at(-1);
54492
54593
  if (newest !== undefined) {
54493
54594
  report = parseJUnit(readFileSync40(join44(reportsDir, newest, "results.xml"), "utf8"));
@@ -54814,7 +54915,7 @@ ${error.text}`;
54814
54915
  if (!location.ok) {
54815
54916
  return this.createErrorResponse(location.reason, PATH_SOLUTIONS);
54816
54917
  }
54817
- if (!existsSync35(location.absolutePath)) {
54918
+ if (!existsSync34(location.absolutePath)) {
54818
54919
  return this.missingScript(project.value.path, location.relativePath);
54819
54920
  }
54820
54921
  ignored = scriptsUnderGdignore(project.value.path, [location.relativePath]);
@@ -54904,7 +55005,7 @@ ${error.text}`;
54904
55005
  const wanted = `res://${relativePath}`;
54905
55006
  const folder = dirname25(relativePath.replaceAll("\\", "/"));
54906
55007
  const inFolder = join44(projectPath, folder);
54907
- const beside = existsSync35(inFolder) ? readdirSync20(inFolder, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => `res://${folder === "." ? "" : `${folder}/`}${entry.name}`) : [];
55008
+ const beside = existsSync34(inFolder) ? readdirSync20(inFolder, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => `res://${folder === "." ? "" : `${folder}/`}${entry.name}`) : [];
54908
55009
  return this.createErrorResponse(withNearestFiles(`Script file does not exist: ${wanted}`, wanted, scripts, beside));
54909
55010
  }
54910
55011
  async compileScripts(projectPath, paths) {
@@ -55032,7 +55133,7 @@ ${error.text}`;
55032
55133
  const cached = cachedClasses(projectPath);
55033
55134
  const sourceOf = (resourcePath) => {
55034
55135
  const contained = resolveWithinProject(projectPath, resourcePath);
55035
- if (!contained.ok || !existsSync35(contained.absolutePath)) {
55136
+ if (!contained.ok || !existsSync34(contained.absolutePath)) {
55036
55137
  return null;
55037
55138
  }
55038
55139
  try {
@@ -55126,7 +55227,7 @@ ${error.text}`;
55126
55227
  }
55127
55228
  sourceOfClass(projectPath, resourcePath) {
55128
55229
  const contained = resolveWithinProject(projectPath, resourcePath);
55129
- if (!contained.ok || !existsSync35(contained.absolutePath)) {
55230
+ if (!contained.ok || !existsSync34(contained.absolutePath)) {
55130
55231
  return null;
55131
55232
  }
55132
55233
  try {
@@ -55428,7 +55529,7 @@ ${error.text}`;
55428
55529
  try {
55429
55530
  const askedAt = Date.now();
55430
55531
  const asked = this.godotBridge.invokeTool("playing_status", {}, { counted: false });
55431
- const answer = await Promise.race([asked, delay7(PLAYING_STATUS_MS).then(() => null)]);
55532
+ const answer = await Promise.race([asked, delay8(PLAYING_STATUS_MS).then(() => null)]);
55432
55533
  if (answer === null) {
55433
55534
  this.logDebug(`playing_status did not answer within ${PLAYING_STATUS_MS}ms`);
55434
55535
  asked.then(() => {
@@ -55453,7 +55554,7 @@ ${error.text}`;
55453
55554
  try {
55454
55555
  const answer = await Promise.race([
55455
55556
  this.godotBridge.invokeTool("scan_status", {}, { counted: false }),
55456
- delay7(PLAYING_STATUS_MS).then(() => null)
55557
+ delay8(PLAYING_STATUS_MS).then(() => null)
55457
55558
  ]);
55458
55559
  if (answer === null) {
55459
55560
  return null;
@@ -55595,7 +55696,7 @@ ${error.text}`;
55595
55696
  if (answer.serversNote === undefined || adapterFailed || Date.now() >= deadline) {
55596
55697
  return answer;
55597
55698
  }
55598
- await delay7(500);
55699
+ await delay8(500);
55599
55700
  }
55600
55701
  }
55601
55702
  async rebindTheDebugAdapter(servers) {
@@ -55856,7 +55957,7 @@ ${error.text}`;
55856
55957
  if (Date.now() >= deadline) {
55857
55958
  return false;
55858
55959
  }
55859
- await delay7(250);
55960
+ await delay8(250);
55860
55961
  }
55861
55962
  }
55862
55963
  async handleLaunchEditor(args) {
@@ -55994,7 +56095,7 @@ ${error.text}`;
55994
56095
  if (!paths.ok) {
55995
56096
  return refused(paths.reason, PATH_SOLUTIONS);
55996
56097
  }
55997
- const missing = paths.value.filter((path) => !existsSync35(join44(projectPath, path.slice("res://".length))));
56098
+ const missing = paths.value.filter((path) => !existsSync34(join44(projectPath, path.slice("res://".length))));
55998
56099
  if (missing.length > 0) {
55999
56100
  return refused(`Nothing at ${missing.join(" and ")} in the project at ${projectPath}.`, PATH_SOLUTIONS);
56000
56101
  }
@@ -56039,7 +56140,7 @@ ${error.text}`;
56039
56140
  return this.createErrorResponse(sceneToRun.reason, PATH_SOLUTIONS);
56040
56141
  }
56041
56142
  const build = readNonEmptyString(args, "executable");
56042
- if (build === undefined && sceneToRun?.ok && !existsSync35(sceneToRun.absolutePath)) {
56143
+ if (build === undefined && sceneToRun?.ok && !existsSync34(sceneToRun.absolutePath)) {
56043
56144
  return this.createErrorResponse(`No scene at res://${sceneToRun.relativePath} in the project at ${project.value.path}.`, PATH_SOLUTIONS);
56044
56145
  }
56045
56146
  const exported = build === undefined ? null : exportedBuild(project.value.path, build);
@@ -56167,7 +56268,7 @@ ${error.text}`;
56167
56268
  }
56168
56269
  async runtimeUp(projectPath, before, budget, withArgs) {
56169
56270
  const budgetMs = budget.ms;
56170
- if (!existsSync35(join44(projectPath, RUNTIME_SCRIPT))) {
56271
+ if (!existsSync34(join44(projectPath, RUNTIME_SCRIPT))) {
56171
56272
  return runtimeVerdict(null, {
56172
56273
  addon: false,
56173
56274
  budgetMs,
@@ -56305,7 +56406,7 @@ ${error.text}`;
56305
56406
  const sendingFrom = Date.now();
56306
56407
  const breakpoints = await this.dapHoldingBreakpointsOf(projectPath).reapplyBreakpoints();
56307
56408
  this.logDebug(`Breakpoints were sent again after ${Date.now() - sendingFrom}ms`);
56308
- const slot = await slotForCall();
56409
+ const slot = await slotForCall(true);
56309
56410
  if (profiled) {
56310
56411
  const armed = await this.handleViaBridge("profile_start", {
56311
56412
  arm: true,
@@ -56508,7 +56609,7 @@ ${error.text}`;
56508
56609
  if (Date.now() >= deadline) {
56509
56610
  return false;
56510
56611
  }
56511
- await delay7(100);
56612
+ await delay8(100);
56512
56613
  }
56513
56614
  }
56514
56615
  async endActiveGame(reason, going, graceful) {
@@ -56673,6 +56774,7 @@ ${error.text}`;
56673
56774
  try {
56674
56775
  const run = this.spawnHeld(godotPath, cmdArgs, onTheSlot(env ?? process.env, held.lock), onDesktop);
56675
56776
  run.settled.finally(held.release);
56777
+ slotEngineNamed(held.lock, run, onDesktop?.files.pid);
56676
56778
  return run;
56677
56779
  } catch (error) {
56678
56780
  held.release();
@@ -56768,11 +56870,11 @@ ${error.text}`;
56768
56870
  return Object.assign(started, { settled, stop });
56769
56871
  }
56770
56872
  async launchKeptGame(godotPath, cmdArgs, projectPath, env, desktop, profiled = false, debugged = true) {
56873
+ const slot = await slotForCall(true);
56771
56874
  const startedAt = Date.now();
56772
56875
  const transcript = openTranscript(startedAt);
56773
56876
  closeSync6(transcript.fd);
56774
56877
  const profile = profiled ? profilePath(startedAt) : undefined;
56775
- const slot = await slotForCall();
56776
56878
  const gameEnv = onTheSlot(profiled ? { ...env ?? process.env, [PROFILE_FROM_START]: "1" } : env ?? process.env, slot.lock);
56777
56879
  let launched;
56778
56880
  try {
@@ -57242,7 +57344,7 @@ ${error.text}`;
57242
57344
  };
57243
57345
  const going = await this.runStillGoing(run);
57244
57346
  const projectPath = run.projectPath;
57245
- const projectGone = projectPath !== null && !existsSync35(join44(projectPath, "project.godot"));
57347
+ const projectGone = projectPath !== null && !existsSync34(join44(projectPath, "project.godot"));
57246
57348
  if (projectGone && callersName !== undefined) {
57247
57349
  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"]);
57248
57350
  }
@@ -57400,7 +57502,7 @@ ${error.text}`;
57400
57502
  return true;
57401
57503
  }
57402
57504
  }
57403
- await delay7(100);
57505
+ await delay8(100);
57404
57506
  }
57405
57507
  return false;
57406
57508
  }
@@ -57415,14 +57517,33 @@ ${error.text}`;
57415
57517
  return elsewhere;
57416
57518
  }
57417
57519
  const budgetMs = readPositiveNumber(args, "timeoutMs") ?? WAIT_FOR_RUN_MS;
57418
- const until = Date.now() + budgetMs;
57520
+ const began = Date.now();
57521
+ const until = began + budgetMs;
57419
57522
  const cancelled = callSignal();
57523
+ let held = false;
57524
+ let holdAsked = 0;
57420
57525
  while (cancelled?.aborted !== true && await this.runStillGoing(run) && Date.now() < until) {
57421
57526
  this.drainEditorOutput(run);
57422
57527
  this.drainTranscript(run);
57423
- await delay7(RUN_POLL_MS);
57528
+ if (Date.now() - holdAsked >= HOLD_ASKED_EVERY_MS) {
57529
+ holdAsked = Date.now();
57530
+ if (await this.heldByItsDebugger(run)) {
57531
+ held = true;
57532
+ break;
57533
+ }
57534
+ }
57535
+ await delay8(RUN_POLL_MS);
57536
+ }
57537
+ if (!await this.runStillGoing(run)) {
57538
+ return await this.handleGetDebugOutput(args);
57424
57539
  }
57425
- return await this.handleGetDebugOutput(args, await this.runStillGoing(run) ? budgetMs : undefined);
57540
+ return await this.handleGetDebugOutput(args, held ? { heldAfterMs: Date.now() - began } : { ranOutMs: budgetMs });
57541
+ }
57542
+ async heldByItsDebugger(run) {
57543
+ if (!run.throughEditor) {
57544
+ return run.keeper !== undefined && await this.keptHold(run.keeper) !== null;
57545
+ }
57546
+ return this.dapClient?.holdIsKnown() === true && this.dapClient.whereItStopped() !== null;
57426
57547
  }
57427
57548
  async runStillGoing(run, editorSays) {
57428
57549
  if (!run.throughEditor) {
@@ -57473,7 +57594,7 @@ ${error.text}`;
57473
57594
  if (Date.now() >= until) {
57474
57595
  return false;
57475
57596
  }
57476
- await delay7(50);
57597
+ await delay8(50);
57477
57598
  }
57478
57599
  }
57479
57600
  async handleEditorConsole(args) {
@@ -57571,7 +57692,7 @@ ${error.text}`;
57571
57692
  "editor_status names every game running on the machine under runtimes"
57572
57693
  ]);
57573
57694
  }
57574
- async handleGetDebugOutput(args, waitedMs) {
57695
+ async handleGetDebugOutput(args, waited) {
57575
57696
  if (readString(args, "op") === "editor") {
57576
57697
  return this.handleEditorConsole(args);
57577
57698
  }
@@ -57608,8 +57729,11 @@ ${error.text}`;
57608
57729
  if (wanted && cpuSeconds === undefined && stillRunning(run)) {
57609
57730
  notes.push(processToAsk === null ? "cpu was asked for and there is no process to ask: the editor is playing this run and holds it, and the game announced no runtime, so nothing here has its process id. A game with the runtime addon announces one; editor_run start without a connected editor, or with args, starts the game here instead and that run answers cpuSeconds either way." : "cpu was asked for and this platform would not say what the run has used.");
57610
57731
  }
57611
- if (waitedMs !== undefined) {
57612
- notes.push(`The wait of ${waitedMs}ms ran out and the run is still going, so this is what it had printed by then rather than everything it will print. editor_run wait again to keep waiting.`);
57732
+ if (waited !== undefined && "ranOutMs" in waited) {
57733
+ notes.push(`The wait of ${waited.ranOutMs}ms ran out and the run is still going, so this is what it had printed by then rather than everything it will print. editor_run wait again to keep waiting.`);
57734
+ }
57735
+ if (waited !== undefined && "heldAfterMs" in waited) {
57736
+ notes.push(`The wait answered after ${waited.heldAfterMs}ms because the game is held by its debugger, where heldAt says, and a held game does not end: debug_control continue lets it go, and editor_run wait then waits on.`);
57613
57737
  }
57614
57738
  const restarts = run.restartedFrom ?? [];
57615
57739
  if (restarts.length > 0) {
@@ -57627,7 +57751,7 @@ ${error.text}`;
57627
57751
  notes.push(endedWithoutACode({
57628
57752
  throughEditor: run.throughEditor,
57629
57753
  announced: announced !== undefined || run.announcedPid !== undefined,
57630
- couldAnnounce: run.projectPath !== null && existsSync35(join44(run.projectPath, RUNTIME_SCRIPT)),
57754
+ couldAnnounce: run.projectPath !== null && existsSync34(join44(run.projectPath, RUNTIME_SCRIPT)),
57631
57755
  endedHere: typeof run.endedHere === "string",
57632
57756
  pickedUp: run.pickedUp === true
57633
57757
  }));
@@ -58509,7 +58633,7 @@ ${error.text}`;
58509
58633
  if (!scriptPath.ok) {
58510
58634
  return this.createErrorResponse(scriptPath.reason);
58511
58635
  }
58512
- if (!existsSync35(scriptPath.absolutePath)) {
58636
+ if (!existsSync34(scriptPath.absolutePath)) {
58513
58637
  return this.missingScript(projectPath, scriptPath.relativePath);
58514
58638
  }
58515
58639
  const asked = readNonEmptyString(args, "newScriptPath");
@@ -59153,7 +59277,7 @@ ${error.text}`;
59153
59277
  async ownRunOnItsWay(projectPath, budgetMs) {
59154
59278
  const run = await this.currentRun();
59155
59279
  const project = projectPath ?? run?.projectPath ?? null;
59156
- if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync35(join44(project, RUNTIME_SCRIPT)) || !await this.runStillGoing(run)) {
59280
+ if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync34(join44(project, RUNTIME_SCRIPT)) || !await this.runStillGoing(run)) {
59157
59281
  return null;
59158
59282
  }
59159
59283
  const already = runtimesAnnounced().running;
@@ -59467,7 +59591,7 @@ async function runGodotServer() {
59467
59591
  const server = new GodotServer;
59468
59592
  await server.run();
59469
59593
  }
59470
- var TYPE_ARGUMENTS, UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, FAILED_BUDGET = 1e6, ANSWER_BUDGET = 4000000, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.", OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, SUCCESSOR_SOUGHT_WITHIN_MS = 20000, CLOSE_ASK_MS = 3000, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_SETTLED_MS = 500, SLOW_ANSWER_MS = 1000, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_TIMEOUT_MS = 600000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", LOOPING_AFTER = 100, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, DESTINATION_ARGUMENTS, OUTPUT_PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, MOST_PRINTED_LINES = 50, MOST_UNDECLARED_ERRORS = 20, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", RAISED_WARNING = " (Warning treated as error.)", PARSE_ERROR2 = "Parse Error: ", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
59594
+ var TYPE_ARGUMENTS, UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, FAILED_BUDGET = 1e6, ANSWER_BUDGET = 4000000, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.", OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, SUCCESSOR_SOUGHT_WITHIN_MS = 20000, CLOSE_ASK_MS = 3000, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_SETTLED_MS = 500, SLOW_ANSWER_MS = 1000, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_TIMEOUT_MS = 600000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", LOOPING_AFTER = 100, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, HOLD_ASKED_EVERY_MS = 1000, PATH_SOLUTIONS, DESTINATION_ARGUMENTS, OUTPUT_PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, MOST_PRINTED_LINES = 50, MOST_UNDECLARED_ERRORS = 20, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", RAISED_WARNING = " (Warning treated as error.)", PARSE_ERROR2 = "Parse Error: ", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
59471
59595
  var init_server = __esm(() => {
59472
59596
  init_dist2();
59473
59597
  init_stdio();
@@ -59606,7 +59730,7 @@ var init_server = __esm(() => {
59606
59730
  init_bridge_announce();
59607
59731
  init_engine_launch();
59608
59732
  init_engine_slots();
59609
- import { existsSync as existsSync36 } from "node:fs";
59733
+ import { existsSync as existsSync35 } from "node:fs";
59610
59734
  import { dirname as dirname26, join as join45, resolve as resolve15 } from "node:path";
59611
59735
 
59612
59736
  // src/error-mode.ts
@@ -59645,7 +59769,7 @@ init_alive();
59645
59769
  init_engine_launch();
59646
59770
  init_lsp_client();
59647
59771
  init_process_children();
59648
- import { existsSync as existsSync18, readdirSync as readdirSync11, readFileSync as readFileSync19, unlinkSync as unlinkSync2 } from "node:fs";
59772
+ import { existsSync as existsSync17, readdirSync as readdirSync11, readFileSync as readFileSync19, unlinkSync as unlinkSync2 } from "node:fs";
59649
59773
  import { connect as connect2 } from "node:net";
59650
59774
  import { join as join21, resolve as resolve8 } from "node:path";
59651
59775
  var EDITOR_RECORD_PROTOCOL = 1;
@@ -59757,7 +59881,7 @@ function lspOptionsOf(args, cwd) {
59757
59881
  }
59758
59882
  }
59759
59883
  const projectPath = resolve8(cwd, named ?? ".");
59760
- if (!existsSync18(join21(projectPath, "project.godot"))) {
59884
+ if (!existsSync17(join21(projectPath, "project.godot"))) {
59761
59885
  throw new EngineUsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
59762
59886
  }
59763
59887
  return { projectPath, port, waitSeconds };
@@ -59864,7 +59988,7 @@ init_setup();
59864
59988
  init_setup();
59865
59989
  init_tool_definitions();
59866
59990
  init_tool_reference();
59867
- import { existsSync as existsSync20, mkdirSync as mkdirSync7, readdirSync as readdirSync13, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
59991
+ import { existsSync as existsSync19, mkdirSync as mkdirSync7, readdirSync as readdirSync13, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
59868
59992
  import { basename as basename9, dirname as dirname10, join as join23 } from "node:path";
59869
59993
  var SKILL_NAME = "gdharness";
59870
59994
  var SHARED_SKILLS = join23(".agents", "skills");
@@ -60136,7 +60260,7 @@ function everySkillDirectory(projectPath, harnesses) {
60136
60260
  function removeSkill(directories, projectPath) {
60137
60261
  const removed = [];
60138
60262
  for (const directory of directories) {
60139
- if (existsSync20(directory)) {
60263
+ if (existsSync19(directory)) {
60140
60264
  rmSync11(directory, { recursive: true, force: true });
60141
60265
  pruneEmpty(dirname10(directory), projectPath);
60142
60266
  removed.push(directory);
@@ -60158,7 +60282,7 @@ function pruneEmpty(directory, projectPath) {
60158
60282
  function writeSkill(directories, version) {
60159
60283
  const files = skillFiles(version);
60160
60284
  return directories.map((directory) => {
60161
- const replaced = existsSync20(directory);
60285
+ const replaced = existsSync19(directory);
60162
60286
  rmSync11(directory, { recursive: true, force: true });
60163
60287
  for (const [name, contents] of files) {
60164
60288
  const path = join23(directory, name);
@@ -60175,7 +60299,7 @@ init_engine_slots();
60175
60299
  init_log_read();
60176
60300
  init_setup();
60177
60301
  init_suite_judge();
60178
- import { existsSync as existsSync21, mkdirSync as mkdirSync8, readFileSync as readFileSync22, rmSync as rmSync12 } from "node:fs";
60302
+ import { existsSync as existsSync20, mkdirSync as mkdirSync8, readFileSync as readFileSync22, rmSync as rmSync12 } from "node:fs";
60179
60303
  import { availableParallelism as availableParallelism4 } from "node:os";
60180
60304
  import { join as join24, resolve as resolve10 } from "node:path";
60181
60305
  import process13 from "node:process";
@@ -60217,9 +60341,9 @@ function spreadOptionsOf(args, cwd) {
60217
60341
  }
60218
60342
  const first = positional[0];
60219
60343
  const named = first === undefined ? null : resolve10(cwd, first);
60220
- const projectNamed = named !== null && existsSync21(join24(named, "project.godot"));
60344
+ const projectNamed = named !== null && existsSync20(join24(named, "project.godot"));
60221
60345
  const projectPath = projectNamed ? named : resolve10(cwd);
60222
- if (!existsSync21(join24(projectPath, "project.godot"))) {
60346
+ if (!existsSync20(join24(projectPath, "project.godot"))) {
60223
60347
  throw new EngineUsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
60224
60348
  }
60225
60349
  const scenes = positional.slice(projectNamed ? 1 : 0);
@@ -60227,7 +60351,7 @@ function spreadOptionsOf(args, cwd) {
60227
60351
  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(", ")}.`);
60228
60352
  }
60229
60353
  const scene = resPathOf(scenes[0] ?? "", projectPath, cwd);
60230
- if (!existsSync21(join24(projectPath, scene.slice("res://".length)))) {
60354
+ if (!existsSync20(join24(projectPath, scene.slice("res://".length)))) {
60231
60355
  throw new EngineUsageError(`Nothing at ${scene} in the project at ${projectPath}.`);
60232
60356
  }
60233
60357
  const benchArguments = dashes === -1 ? [] : args.slice(dashes + 1);
@@ -60298,7 +60422,7 @@ function shareFaults(played) {
60298
60422
  return faults;
60299
60423
  }
60300
60424
  async function runSpreadCommand(options, godotPath) {
60301
- if (!existsSync21(join24(options.projectPath, SHARE_SCRIPT))) {
60425
+ if (!existsSync20(join24(options.projectPath, SHARE_SCRIPT))) {
60302
60426
  throw new EngineUsageError(`res://${SHARE_SCRIPT} is not in the project, so its bench cannot ask GdhShare for outcomes. Run gdharness setup.`);
60303
60427
  }
60304
60428
  const startedAt = Date.now();
@@ -60363,7 +60487,7 @@ async function runSpreadCommand(options, godotPath) {
60363
60487
  const faults = left.flatMap((worker) => engineFaults(worker, options.godotVersion));
60364
60488
  const arms = left.map((_, index) => {
60365
60489
  const file = join24(shareDirectory, `worker-${String(index)}.arms`);
60366
- return existsSync21(file) ? armsPlayed(readFileSync22(file, "utf8")) : [];
60490
+ return existsSync20(file) ? armsPlayed(readFileSync22(file, "utf8")) : [];
60367
60491
  });
60368
60492
  faults.push(...shareFaults(arms));
60369
60493
  if (faults.length > 0) {
@@ -60373,7 +60497,7 @@ async function runSpreadCommand(options, godotPath) {
60373
60497
  console.log(readLog(gather.log).replace(CONTROL_SEQUENCE, "").trimEnd());
60374
60498
  const gatherFaults = engineFaults(gather, options.godotVersion);
60375
60499
  const gatheredFile = join24(shareDirectory, "gathered.arms");
60376
- const gathered = new Set((existsSync21(gatheredFile) ? armsPlayed(readFileSync22(gatheredFile, "utf8")) ?? [] : []).map((arm) => arm.arm));
60500
+ const gathered = new Set((existsSync20(gatheredFile) ? armsPlayed(readFileSync22(gatheredFile, "utf8")) ?? [] : []).map((arm) => arm.arm));
60377
60501
  const ungathered = (arms[0] ?? []).filter((arm) => !gathered.has(arm.arm)).map((arm) => arm.arm);
60378
60502
  if (ungathered.length > 0) {
60379
60503
  gatherFaults.push(`the gathering run never asked for ${ungathered.join(", ")}, which the workers played: it went another way through the arms than they did`);
@@ -60424,7 +60548,7 @@ function engineFaults(engine, godotVersion) {
60424
60548
  const said = code === null ? "without an exit code" : code >>> 0 >= WINDOWS_EXCEPTION2 ? exitNamed(code) : String(code);
60425
60549
  faults.push(`${engine.name}: the engine exited ${said}`);
60426
60550
  }
60427
- if (!existsSync21(engine.log)) {
60551
+ if (!existsSync20(engine.log)) {
60428
60552
  return faults;
60429
60553
  }
60430
60554
  const printed = readRun(readLog(engine.log));
@@ -60440,7 +60564,7 @@ function engineFaults(engine, godotVersion) {
60440
60564
  }
60441
60565
  function progressOf(logs) {
60442
60566
  const each = logs.map((log, index) => {
60443
- const text = existsSync21(log) ? readLog(log) : "";
60567
+ const text = existsSync20(log) ? readLog(log) : "";
60444
60568
  const last = [...text.matchAll(PROGRESS)].at(-1);
60445
60569
  return last === undefined ? `worker ${String(index)} has played no arm yet` : `worker ${String(index)} at ${last[1] ?? ""} ${last[2] ?? ""} of ${last[3] ?? ""}`;
60446
60570
  });
@@ -60472,7 +60596,7 @@ init_reimport();
60472
60596
  init_setup();
60473
60597
  import {
60474
60598
  copyFileSync,
60475
- existsSync as existsSync24,
60599
+ existsSync as existsSync23,
60476
60600
  mkdirSync as mkdirSync10,
60477
60601
  readdirSync as readdirSync15,
60478
60602
  readFileSync as readFileSync26,
@@ -60483,12 +60607,13 @@ import {
60483
60607
  import { availableParallelism as availableParallelism5 } from "node:os";
60484
60608
  import { dirname as dirname13, join as join28, relative as relative5, resolve as resolve11, sep as sep4 } from "node:path";
60485
60609
  import process14 from "node:process";
60610
+ import { setTimeout as delay5 } from "node:timers/promises";
60486
60611
 
60487
60612
  // src/suite-inputs.ts
60488
60613
  init_class_cache();
60489
60614
  init_gdscript_source();
60490
60615
  import { createHash as createHash6 } from "node:crypto";
60491
- import { existsSync as existsSync22, readdirSync as readdirSync14, readFileSync as readFileSync23, statSync as statSync13 } from "node:fs";
60616
+ import { existsSync as existsSync21, readdirSync as readdirSync14, readFileSync as readFileSync23, statSync as statSync13 } from "node:fs";
60492
60617
  import { join as join25, posix as posix2 } from "node:path";
60493
60618
  var EVERY_FILE = "res://";
60494
60619
  var CACHE_HELD = "cache:";
@@ -60506,7 +60631,7 @@ var ADDON = /^res:\/\/addons\/[^/]+\//;
60506
60631
  var TEXT_RESOURCE = /\.(?:tscn|tres|gdextension)$/;
60507
60632
  function reachedBySuite(file) {
60508
60633
  const bySuite = new Map;
60509
- if (!existsSync22(file)) {
60634
+ if (!existsSync21(file)) {
60510
60635
  return bySuite;
60511
60636
  }
60512
60637
  const paths = (value) => Array.isArray(value) ? value.filter((path) => typeof path === "string") : [];
@@ -60616,7 +60741,7 @@ ${this.read(path).digest}
60616
60741
  const hash = createHash6("sha256");
60617
60742
  let modified = 0;
60618
60743
  const directory = join25(this.scripts.projectPath, root.slice("res://".length));
60619
- if (existsSync22(directory)) {
60744
+ if (existsSync21(directory)) {
60620
60745
  walkSeen(directory, (file, under, stat) => {
60621
60746
  hash.update(`${under}
60622
60747
  `).update(readFileSync23(file));
@@ -60627,7 +60752,7 @@ ${this.read(path).digest}
60627
60752
  }
60628
60753
  readAnew(path) {
60629
60754
  const file = join25(this.scripts.projectPath, path.slice("res://".length));
60630
- if (!existsSync22(file)) {
60755
+ if (!existsSync21(file)) {
60631
60756
  return { digest: "missing", follows: [], also: [], modified: 0 };
60632
60757
  }
60633
60758
  const stat = statSync13(file);
@@ -60638,12 +60763,12 @@ ${this.read(path).digest}
60638
60763
  }
60639
60764
  const bytes = readFileSync23(file);
60640
60765
  const digest = createHash6("sha256").update(bytes).digest("hex");
60641
- const also = [`${path}.import`, `${path}.uid`].filter((beside) => existsSync22(join25(this.scripts.projectPath, beside.slice("res://".length))));
60766
+ const also = [`${path}.import`, `${path}.uid`].filter((beside) => existsSync21(join25(this.scripts.projectPath, beside.slice("res://".length))));
60642
60767
  const follows = [];
60643
60768
  if (TEXT_RESOURCE.test(path)) {
60644
60769
  for (const [named] of bytes.toString("latin1").matchAll(RES_LITERAL)) {
60645
60770
  const target = join25(this.scripts.projectPath, named.slice("res://".length));
60646
- (existsSync22(target) && statSync13(target).isDirectory() ? also : follows).push(named);
60771
+ (existsSync21(target) && statSync13(target).isDirectory() ? also : follows).push(named);
60647
60772
  }
60648
60773
  } else if (path.endsWith(".gd")) {
60649
60774
  const reaches = this.scriptReaches(path);
@@ -60830,7 +60955,7 @@ function joinedString(expression, named) {
60830
60955
  }
60831
60956
  function walkSeen(root, seen) {
60832
60957
  const walk = (directory, under) => {
60833
- if (existsSync22(join25(directory, ".gdignore"))) {
60958
+ if (existsSync21(join25(directory, ".gdignore"))) {
60834
60959
  return;
60835
60960
  }
60836
60961
  for (const entry of readdirSync14(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
@@ -60848,7 +60973,7 @@ function walkSeen(root, seen) {
60848
60973
  }
60849
60974
  function extensionsOf(projectPath) {
60850
60975
  const list = join25(projectPath, ".godot", "extension_list.cfg");
60851
- return existsSync22(list) ? readFileSync23(list, "utf8").split(`
60976
+ return existsSync21(list) ? readFileSync23(list, "utf8").split(`
60852
60977
  `).map((line) => line.trim()).filter((line) => line.startsWith("res://")) : [];
60853
60978
  }
60854
60979
 
@@ -60879,6 +61004,7 @@ function getLocalVersion() {
60879
61004
 
60880
61005
  // src/suite-run.ts
60881
61006
  var LAST_RUN_FILE = "last-run.json";
61007
+ var COMPILED_FILE = "compiled-together.json";
60882
61008
  var SLOWEST_NAMED = 5;
60883
61009
  var SILENCE_TIMES_TAKEN = 4;
60884
61010
  function silenceWhile(running, times, floorSeconds) {
@@ -60936,9 +61062,9 @@ function testOptionsOf(args, cwd) {
60936
61062
  }
60937
61063
  const first = positional[0];
60938
61064
  const named = first === undefined ? null : resolve11(cwd, first);
60939
- const projectNamed = named !== null && existsSync24(join28(named, "project.godot"));
61065
+ const projectNamed = named !== null && existsSync23(join28(named, "project.godot"));
60940
61066
  const projectPath = projectNamed ? named : resolve11(cwd);
60941
- if (!existsSync24(join28(projectPath, "project.godot"))) {
61067
+ if (!existsSync23(join28(projectPath, "project.godot"))) {
60942
61068
  throw new EngineUsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
60943
61069
  }
60944
61070
  const last = (flag) => valued.get(flag)?.at(-1);
@@ -60973,12 +61099,12 @@ function seedOf(value) {
60973
61099
  return seed;
60974
61100
  }
60975
61101
  function rootsOf(options) {
60976
- return options.roots.length > 0 ? options.roots : TEST_DIRECTORIES.filter((root) => existsSync24(join28(options.projectPath, root))).map((root) => `res://${root}`);
61102
+ return options.roots.length > 0 ? options.roots : TEST_DIRECTORIES.filter((root) => existsSync23(join28(options.projectPath, root))).map((root) => `res://${root}`);
60977
61103
  }
60978
61104
  function searchedRoots(options) {
60979
61105
  return options.selected.length === 0 ? rootsOf(options) : options.selected.filter((entry) => {
60980
61106
  const directory = join28(options.projectPath, entry.slice("res://".length));
60981
- return existsSync24(directory) && statSync14(directory).isDirectory();
61107
+ return existsSync23(directory) && statSync14(directory).isDirectory();
60982
61108
  });
60983
61109
  }
60984
61110
  function selectedSuites(scripts, options) {
@@ -60995,9 +61121,9 @@ function selectedSuites(scripts, options) {
60995
61121
  const caseAt = /^(res:\/\/.*\.gd):(\w+)$/.exec(entry);
60996
61122
  const path = caseAt?.[1] ?? entry;
60997
61123
  const directory = join28(options.projectPath, path.slice(6));
60998
- const suites = existsSync24(directory) && statSync14(directory).isDirectory() ? scripts.suitesUnder([path.replace(/\/$/, "")]) : [scripts.suite(path)].filter((suite) => suite !== null);
61124
+ const suites = existsSync23(directory) && statSync14(directory).isDirectory() ? scripts.suitesUnder([path.replace(/\/$/, "")]) : [scripts.suite(path)].filter((suite) => suite !== null);
60999
61125
  if (suites.length === 0) {
61000
- 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}.`);
61126
+ 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}.`);
61001
61127
  }
61002
61128
  for (const suite of suites) {
61003
61129
  const picked = chosen.get(suite.path) ?? { suite, cases: new Set };
@@ -61121,11 +61247,11 @@ async function runTestCommand(options, godotPath) {
61121
61247
  failsOnPushErrors: gdUnitFailsOnPushErrors(options.projectPath)
61122
61248
  };
61123
61249
  try {
61124
- const neverImported = cachedClasses(options.projectPath) === null;
61125
- if (neverImported && !options.importFirst) {
61126
- console.log("The project has never been imported, so it is imported first.");
61250
+ const needed = options.importFirst ? null : importNeeded(options.projectPath);
61251
+ if (needed !== null) {
61252
+ console.log(needed);
61127
61253
  }
61128
- if (options.importFirst || neverImported) {
61254
+ if (options.importFirst || needed !== null) {
61129
61255
  const imported = await importProject(launch);
61130
61256
  if (imported !== null) {
61131
61257
  logs.keep();
@@ -61140,9 +61266,10 @@ async function runTestCommand(options, godotPath) {
61140
61266
  readFileSync26(shippedListedRunner(), "utf8"),
61141
61267
  engineSaid(godotPath),
61142
61268
  ...options.reachAlso.map((path) => `${path}
61143
- ${existsSync24(path) ? readFileSync26(path, "latin1") : "missing"}`)
61269
+ ${existsSync23(path) ? readFileSync26(path, "latin1") : "missing"}`)
61144
61270
  ];
61145
61271
  const everySuite = suites;
61272
+ const compileKept = options.changedOnly ? { file: join28(state, COMPILED_FILE), scripts, always } : null;
61146
61273
  if (options.changedOnly) {
61147
61274
  const inputs = new SuiteInputs(scripts, always);
61148
61275
  const passedBefore = readPassed(passedFile);
@@ -61156,7 +61283,7 @@ ${existsSync24(path) ? readFileSync26(path, "latin1") : "missing"}`)
61156
61283
  }
61157
61284
  if (suites.length === 0) {
61158
61285
  console.log("Nothing has changed since every suite last passed.");
61159
- const compiled = await compileCheck(launch, everySuite);
61286
+ const compiled = await compileUnlessUnchanged(launch, everySuite, compileKept);
61160
61287
  const problems = [...compiled.faults.map((fault) => `${COMPILE_CHECK}: ${fault}`), ...uncollected];
61161
61288
  if (problems.length === 0) {
61162
61289
  rmSync13(runDirectory, { recursive: true, force: true });
@@ -61179,12 +61306,18 @@ ${existsSync24(path) ? readFileSync26(path, "latin1") : "missing"}`)
61179
61306
  if (memory.unread !== undefined) {
61180
61307
  console.log(`Windows did not say how much memory it can still commit (${memory.unread}), so engines are sized by the physical memory free.`);
61181
61308
  }
61182
- const room = options.cores === null ? roomFor(options.engines, availableParallelism5(), memory.bytes, memory.said) : roomFor(options.engines, options.cores, memory.bytes, memory.said, `--cores holds it to ${plural(options.cores, "processor")}`);
61309
+ 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")}`);
61310
+ const byMemory = roomWith(memory.bytes);
61311
+ const unheld = roomWith(Number.POSITIVE_INFINITY);
61312
+ const unheldUnits = unitsOf(suites, times, splitPastFor(suites, times, unheld.engines));
61313
+ const heldLanes = lanesOf(dealt(unheldUnits, declared, unheld.engines));
61314
+ const memoryHeld = options.order === null && byMemory.engines < heldLanes;
61315
+ const room = memoryHeld ? unheld : byMemory;
61183
61316
  if (options.cores !== null && process14.platform === "darwin") {
61184
61317
  console.log("macOS offers no way to hold a process to some processors, so --cores holds only the number of engines here.");
61185
61318
  }
61186
61319
  const splitPast = splitPastFor(suites, times, room.engines);
61187
- const units = unitsOf(suites, times, splitPast);
61320
+ const units = memoryHeld ? unheldUnits : unitsOf(suites, times, splitPast);
61188
61321
  const past = splitPast > SPLIT_PAST_SECONDS ? `an engine's share of this run (${String(Math.round(splitPast))} s)` : `${String(SPLIT_PAST_SECONDS)} s`;
61189
61322
  const plans = options.order === null ? dealt(units, declared, room.engines) : [inOneEngine(ordered(units, options.order), declared)];
61190
61323
  const functions = suites.reduce((sum, suite) => sum + suite.cases.length, 0);
@@ -61217,24 +61350,28 @@ ${existsSync24(path) ? readFileSync26(path, "latin1") : "missing"}`)
61217
61350
  say(`Kept whole in one engine, since ${one ? "it declares" : "each declares"} GDHARNESS_TOGETHER: ${named(keptWhole)}.`);
61218
61351
  }
61219
61352
  const lanes = [...new Set(plans.map((plan) => plan.lane))].map((lane) => plans.flatMap((plan, index) => plan.lane === lane ? [{ plan, index }] : []));
61353
+ const startable = memoryHeld ? Math.min(byMemory.engines, lanes.length) : lanes.length;
61220
61354
  const taken = [];
61221
- while (taken.length < lanes.length) {
61355
+ while (taken.length < startable) {
61222
61356
  const lock = await slots.takeFree();
61223
61357
  if (lock === null) {
61224
61358
  break;
61225
61359
  }
61226
61360
  taken.push(lock);
61227
61361
  }
61228
- const shared = options.order === null && lanes.length > 1 && taken.length < lanes.length;
61229
- if (shared) {
61230
- say(sharesSaid(taken.length, lanes.length));
61362
+ if (memoryHeld) {
61363
+ say(memorySaid(byMemory.engines, lanes.length, byMemory.held ?? ""));
61364
+ }
61365
+ const shared = options.order === null && lanes.length > 1 && (memoryHeld || taken.length < lanes.length);
61366
+ if (shared && taken.length < startable) {
61367
+ say(sharesSaid(taken.length, startable));
61231
61368
  }
61232
61369
  const handed = new HandedSlot;
61233
- const compiling = compileCheck(launch, everySuite, handed);
61370
+ const compiling = compileUnlessUnchanged(launch, everySuite, compileKept, handed);
61234
61371
  compiling.catch(() => {
61235
61372
  return;
61236
61373
  });
61237
- 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);
61374
+ 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);
61238
61375
  if (ran.some((engine) => engine.verdict.starved) && ran.some((engine) => !engine.verdict.passed)) {
61239
61376
  say("An engine ran out of memory, so every engine that failed runs again, one at a time.");
61240
61377
  const again = [];
@@ -61261,6 +61398,55 @@ ${existsSync24(path) ? readFileSync26(path, "latin1") : "missing"}`)
61261
61398
  function sharesSaid(free, lanes) {
61262
61399
  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.`;
61263
61400
  }
61401
+ function memorySaid(room, lanes, held) {
61402
+ 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.`;
61403
+ }
61404
+ var MEMORY_LOOK_MS = 2000;
61405
+ var ENGINE_SETTLING_MS = 30000;
61406
+
61407
+ class MemoryGate {
61408
+ slots;
61409
+ read;
61410
+ now;
61411
+ lookMs;
61412
+ going = new Set;
61413
+ turn = Promise.resolve();
61414
+ constructor(slots, read, now = Date.now, lookMs = MEMORY_LOOK_MS) {
61415
+ this.slots = slots;
61416
+ this.read = read;
61417
+ this.now = now;
61418
+ this.lookMs = lookMs;
61419
+ }
61420
+ admit(signal) {
61421
+ const admitted = this.turn.then(() => this.room(signal));
61422
+ this.turn = admitted.catch(() => {
61423
+ return;
61424
+ });
61425
+ return admitted;
61426
+ }
61427
+ async room(signal) {
61428
+ let unmark;
61429
+ try {
61430
+ for (;; ) {
61431
+ signal.throwIfAborted();
61432
+ const at = this.now();
61433
+ const starting = [...this.going].filter((one) => at - one.at < ENGINE_SETTLING_MS).length;
61434
+ if (this.going.size === 0 || roomForAnother(await this.read(), starting)) {
61435
+ break;
61436
+ }
61437
+ unmark ??= this.slots.markWaiting();
61438
+ await delay5(this.lookMs, undefined, { signal });
61439
+ }
61440
+ } finally {
61441
+ unmark?.();
61442
+ }
61443
+ const engine = { at: this.now() };
61444
+ this.going.add(engine);
61445
+ return () => {
61446
+ this.going.delete(engine);
61447
+ };
61448
+ }
61449
+ }
61264
61450
  function roomSaid(engines, lanes, unheld, held, asked) {
61265
61451
  if (held === undefined || lanes >= unheld) {
61266
61452
  return "";
@@ -61286,7 +61472,7 @@ function readPassed(file) {
61286
61472
  function recordPassed(file, projectPath, ran, failedAcross, inputs, began) {
61287
61473
  const passed = readPassed(file);
61288
61474
  for (const path of Object.keys(passed)) {
61289
- if (!existsSync24(join28(projectPath, path.slice("res://".length)))) {
61475
+ if (!existsSync23(join28(projectPath, path.slice("res://".length)))) {
61290
61476
  delete passed[path];
61291
61477
  }
61292
61478
  }
@@ -61335,6 +61521,9 @@ class HandedSlot {
61335
61521
  lock = new Promise((resolve) => {
61336
61522
  this.handOver = resolve;
61337
61523
  });
61524
+ decline() {
61525
+ this.wanted = false;
61526
+ }
61338
61527
  give(lock) {
61339
61528
  if (!this.wanted) {
61340
61529
  return false;
@@ -61388,6 +61577,41 @@ async function compileCheck(launch, suites, handed) {
61388
61577
  }
61389
61578
  return { faults: judgeCompile(readRun(readFileSync26(log, "utf8")), end), log };
61390
61579
  }
61580
+ function compiledPrint(kept, projectPath, suites) {
61581
+ const inputs = new SuiteInputs(kept.scripts, kept.always);
61582
+ const classes = [...cachedClasses(projectPath)?.values() ?? []];
61583
+ const read = inputs.inputsOf([...suites.map((suite) => suite.path), ...classes]);
61584
+ return { inputs, read, print: inputs.printOf(read) };
61585
+ }
61586
+ async function compileUnlessUnchanged(launch, suites, kept, handed) {
61587
+ if (kept === null) {
61588
+ return compileCheck(launch, suites, handed);
61589
+ }
61590
+ const before = compiledPrint(kept, launch.projectPath, suites).print;
61591
+ if (readKeptPrint(kept.file) === before) {
61592
+ handed?.decline();
61593
+ console.log("Every script compiled together on these same files when last checked, so it is not compiled again.");
61594
+ return { faults: [], log: "" };
61595
+ }
61596
+ const began = Date.now();
61597
+ const compiled = await compileCheck(launch, suites, handed);
61598
+ const after = compiledPrint(kept, launch.projectPath, suites);
61599
+ if (compiled.faults.length === 0 && !after.inputs.modifiedAfter(after.read, began)) {
61600
+ writeJson(kept.file, { print: after.print });
61601
+ } else {
61602
+ rmSync13(kept.file, { force: true });
61603
+ }
61604
+ return compiled;
61605
+ }
61606
+ function readKeptPrint(file) {
61607
+ try {
61608
+ const parsed = JSON.parse(readFileSync26(file, "utf8"));
61609
+ const print = parsed?.print;
61610
+ return typeof print === "string" ? print : null;
61611
+ } catch {
61612
+ return null;
61613
+ }
61614
+ }
61391
61615
  function printCompiled(compiled, logs) {
61392
61616
  if (compiled.faults.length === 0) {
61393
61617
  return;
@@ -61412,6 +61636,21 @@ function importFault(printed) {
61412
61636
  ].join(" and ");
61413
61637
  return `it printed ${counted}, the first ${first.warning ? "warning" : "error"} ${JSON.stringify(first.text)}`;
61414
61638
  }
61639
+ function importNeeded(projectPath) {
61640
+ const cached = cachedClasses(projectPath);
61641
+ if (cached === null) {
61642
+ return "The project has never been imported, so it is imported first.";
61643
+ }
61644
+ const unlisted = unlistedExtensions(projectPath);
61645
+ if (unlisted.length > 0) {
61646
+ 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.`;
61647
+ }
61648
+ const uncached = staleAgainst(cached, projectPath).sort();
61649
+ if (uncached.length > 0) {
61650
+ return `The class cache does not list ${named(uncached)} where the scripts declare ${uncached.length === 1 ? "it" : "them"}, so the project is imported first.`;
61651
+ }
61652
+ return null;
61653
+ }
61415
61654
  async function importProject(launch) {
61416
61655
  const seeded = seedExtensionList(launch.projectPath);
61417
61656
  if (seeded !== null && seeded.length > 0) {
@@ -61459,35 +61698,48 @@ async function runLane(launch, lane, times, taken, handed) {
61459
61698
  doneWith(launch, lock, handed);
61460
61699
  }
61461
61700
  }
61462
- async function runShares(launch, units, declared, lanes, taken, times, handed) {
61701
+ async function runShares(launch, units, declared, lanes, taken, times, handed, memory) {
61463
61702
  const shares = new Shares(units, declared, lanes);
61464
61703
  const handedOut = new AbortController;
61465
61704
  let engines = 0;
61705
+ const empty = () => shares.empty;
61466
61706
  const lane = async (at) => {
61467
61707
  let lock = taken[at] ?? null;
61468
- if (lock === null) {
61469
- try {
61470
- lock = await launch.slots.take(handedOut.signal);
61471
- } catch (error) {
61472
- if (handedOut.signal.aborted) {
61473
- return [];
61474
- }
61475
- throw error;
61476
- }
61477
- }
61708
+ const ran = [];
61478
61709
  try {
61479
- const ran = [];
61480
- for (let plan = shares.next(at);plan !== null; plan = shares.next(at)) {
61481
- if (shares.empty) {
61710
+ while (!empty()) {
61711
+ let ended;
61712
+ try {
61713
+ ended = await memory?.admit(handedOut.signal);
61714
+ lock ??= await launch.slots.take(handedOut.signal);
61715
+ } catch (error) {
61716
+ ended?.();
61717
+ if (handedOut.signal.aborted) {
61718
+ break;
61719
+ }
61720
+ throw error;
61721
+ }
61722
+ const plan = shares.next(at);
61723
+ if (plan === null) {
61724
+ ended?.();
61725
+ break;
61726
+ }
61727
+ if (empty()) {
61482
61728
  handedOut.abort();
61483
61729
  }
61484
61730
  const index = engines;
61485
61731
  engines += 1;
61486
- ran.push(await runEngine2(launch, plan, index, times, "", lock));
61732
+ try {
61733
+ ran.push(await runEngine2(launch, plan, index, times, "", lock));
61734
+ } finally {
61735
+ ended?.();
61736
+ }
61487
61737
  }
61488
61738
  return ran;
61489
61739
  } finally {
61490
- doneWith(launch, lock, handed);
61740
+ if (lock !== null) {
61741
+ doneWith(launch, lock, handed);
61742
+ }
61491
61743
  }
61492
61744
  };
61493
61745
  return (await Promise.all(Array.from({ length: lanes }, (_, at) => lane(at)))).flat().sort((a, b) => a.index - b.index);
@@ -61582,7 +61834,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
61582
61834
  }
61583
61835
  var REPORT_FILES_NAMED = 3;
61584
61836
  function reportsFault(reports) {
61585
- const files = existsSync24(reports) ? readdirSync15(reports, { recursive: true, encoding: "utf8" }).filter((file) => statSync14(join28(reports, file)).isFile()).sort() : [];
61837
+ const files = existsSync23(reports) ? readdirSync15(reports, { recursive: true, encoding: "utf8" }).filter((file) => statSync14(join28(reports, file)).isFile()).sort() : [];
61586
61838
  if (files.length === 0) {
61587
61839
  return null;
61588
61840
  }
@@ -61595,7 +61847,7 @@ function report(ran, compiled, across, uncollected, startedAt, logs, printOutput
61595
61847
  for (const engine of ran) {
61596
61848
  console.log("");
61597
61849
  console.log(`--- what engine ${String(engine.index)} printed, running ${named(engine.plan.suites.map((suite) => suite.path))}:`);
61598
- console.log(existsSync24(engine.log) ? readLog(engine.log).replace(CONTROL_SEQUENCE, "").trimEnd() : "(no log was written)");
61850
+ console.log(existsSync23(engine.log) ? readLog(engine.log).replace(CONTROL_SEQUENCE, "").trimEnd() : "(no log was written)");
61599
61851
  }
61600
61852
  }
61601
61853
  const failed = ran.filter((engine) => !engine.verdict.passed);
@@ -61606,7 +61858,7 @@ function report(ran, compiled, across, uncollected, startedAt, logs, printOutput
61606
61858
  for (const fault of engine.verdict.faults) {
61607
61859
  console.log(` ${fault}`);
61608
61860
  }
61609
- if (existsSync24(engine.log)) {
61861
+ if (existsSync23(engine.log)) {
61610
61862
  const verdict = [
61611
61863
  `gdharness: ${heading.slice(4)}`,
61612
61864
  ...engine.verdict.faults.map((fault) => ` ${fault}`)
@@ -61701,6 +61953,9 @@ function report(ran, compiled, across, uncollected, startedAt, logs, printOutput
61701
61953
  const problems = failed.reduce((sum, engine) => sum + engine.verdict.faults.length, compiled.faults.length + across.length + uncollected.length);
61702
61954
  console.log(`Failed: ${plural(problems, "problem")}, listed above. Ran ${totals}`);
61703
61955
  console.log(`Every engine's output is kept under ${logs.shown} until ${String(KEPT_RUN_DIRECTORIES)} later runs have failed.`);
61956
+ if (logs.shown !== logs.directory) {
61957
+ 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.`);
61958
+ }
61704
61959
  return 1;
61705
61960
  }
61706
61961
  function writeEarlyResult(targets, startedAt, problems, logs) {
@@ -61742,7 +61997,7 @@ class UsageError extends Refusal {
61742
61997
  function projectArgument(at) {
61743
61998
  const named = args.slice(at).find((value) => !value.startsWith("--"));
61744
61999
  const projectPath = resolve15(named ?? ".");
61745
- if (!existsSync36(join45(projectPath, "project.godot"))) {
62000
+ if (!existsSync35(join45(projectPath, "project.godot"))) {
61746
62001
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
61747
62002
  }
61748
62003
  return projectPath;
@@ -61806,7 +62061,7 @@ async function registerRuntime(godot, projectPath) {
61806
62061
  }
61807
62062
  const path = (named ?? "").replace(/^res:\/\//, "");
61808
62063
  console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
61809
- if (!existsSync36(join45(projectPath, path))) {
62064
+ if (!existsSync35(join45(projectPath, path))) {
61810
62065
  console.log(" that file is not in this project, so it boots with a missing script");
61811
62066
  }
61812
62067
  console.log(` ${useTheShippedLoader(RUNTIME_AUTOLOAD.name, path)}`);
@@ -61939,7 +62194,7 @@ async function setup() {
61939
62194
  console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
61940
62195
  }
61941
62196
  if (!args.includes("--no-skill")) {
61942
- const directories = skillDirectories(chosen.harnesses, projectPath, existsSync36);
62197
+ const directories = skillDirectories(chosen.harnesses, projectPath, existsSync35);
61943
62198
  for (const written of writeSkill(directories, getLocalVersion())) {
61944
62199
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
61945
62200
  }
@@ -62059,7 +62314,7 @@ async function upgrade() {
62059
62314
  moved.push(took.from);
62060
62315
  }
62061
62316
  }
62062
- for (const written of writeSkill(skillDirectories(already, projectPath, existsSync36), version)) {
62317
+ for (const written of writeSkill(skillDirectories(already, projectPath, existsSync35), version)) {
62063
62318
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
62064
62319
  }
62065
62320
  console.log(`