gdharness 1.2.4 → 1.2.6
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 +69 -27
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +116 -8
- package/build/index.js +7 -0
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -8129,7 +8129,7 @@ function alive(pid) {
|
|
|
8129
8129
|
var init_alive = () => {};
|
|
8130
8130
|
|
|
8131
8131
|
// src/scratch.ts
|
|
8132
|
-
import { existsSync as existsSync6, mkdtempSync, readdirSync as
|
|
8132
|
+
import { existsSync as existsSync6, mkdtempSync, readdirSync as readdirSync3, rmSync as rmSync4 } from "node:fs";
|
|
8133
8133
|
import { tmpdir } from "node:os";
|
|
8134
8134
|
import { join as join6 } from "node:path";
|
|
8135
8135
|
import process6 from "node:process";
|
|
@@ -8139,7 +8139,7 @@ function scratchDirectory(kind) {
|
|
|
8139
8139
|
function sweepAbandonedScratch(directory = tmpdir()) {
|
|
8140
8140
|
let entries;
|
|
8141
8141
|
try {
|
|
8142
|
-
entries =
|
|
8142
|
+
entries = readdirSync3(directory);
|
|
8143
8143
|
} catch {
|
|
8144
8144
|
return;
|
|
8145
8145
|
}
|
|
@@ -9461,7 +9461,7 @@ var init_gdscript_source = __esm(() => {
|
|
|
9461
9461
|
});
|
|
9462
9462
|
|
|
9463
9463
|
// src/class-cache.ts
|
|
9464
|
-
import { existsSync as existsSync9, readdirSync as
|
|
9464
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
|
|
9465
9465
|
import { join as join9, posix } from "node:path";
|
|
9466
9466
|
function isToolScript(source) {
|
|
9467
9467
|
return TOOL.test(codeOf(source));
|
|
@@ -9481,7 +9481,7 @@ function eachScript(projectPath, each) {
|
|
|
9481
9481
|
if (existsSync9(join9(directory, ".gdignore"))) {
|
|
9482
9482
|
return;
|
|
9483
9483
|
}
|
|
9484
|
-
for (const entry of
|
|
9484
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
9485
9485
|
if (entry.name.startsWith(".")) {
|
|
9486
9486
|
continue;
|
|
9487
9487
|
}
|
|
@@ -17266,7 +17266,7 @@ import {
|
|
|
17266
17266
|
cpSync,
|
|
17267
17267
|
existsSync as existsSync10,
|
|
17268
17268
|
mkdirSync as mkdirSync4,
|
|
17269
|
-
readdirSync as
|
|
17269
|
+
readdirSync as readdirSync5,
|
|
17270
17270
|
readFileSync as readFileSync9,
|
|
17271
17271
|
rmdirSync,
|
|
17272
17272
|
rmSync as rmSync5,
|
|
@@ -17284,7 +17284,7 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
|
|
|
17284
17284
|
function addonsDigest(names, from) {
|
|
17285
17285
|
const hash = createHash3("sha256");
|
|
17286
17286
|
const walk = (directory, relative) => {
|
|
17287
|
-
for (const name of
|
|
17287
|
+
for (const name of readdirSync5(directory).sort()) {
|
|
17288
17288
|
if (name === VERSION_MARKER || name === DIGEST_MARKER) {
|
|
17289
17289
|
continue;
|
|
17290
17290
|
}
|
|
@@ -17386,7 +17386,7 @@ function removeAddons(projectPath) {
|
|
|
17386
17386
|
}
|
|
17387
17387
|
}
|
|
17388
17388
|
const addons = join10(projectPath, "addons");
|
|
17389
|
-
if (removed.length > 0 && existsSync10(addons) &&
|
|
17389
|
+
if (removed.length > 0 && existsSync10(addons) && readdirSync5(addons).length === 0) {
|
|
17390
17390
|
rmdirSync(addons);
|
|
17391
17391
|
removed.push(addons);
|
|
17392
17392
|
}
|
|
@@ -18898,6 +18898,12 @@ var init_tool_definitions = __esm(() => {
|
|
|
18898
18898
|
ops: ["until"],
|
|
18899
18899
|
description: "until: the value to wait for, fitted to the property's type and compared at the precision the property keeps, so -1.1 meets a 32-bit float holding -1.10000002. A value the property would have to cut is refused: a fraction on a whole number, a number other than 0 or 1 on a bool. On a property holding an object, whether declared to or untyped and holding one or null, the text null means null. One declared to hold an object refuses any value other than null, since it can never hold it."
|
|
18900
18900
|
},
|
|
18901
|
+
compare: {
|
|
18902
|
+
type: "string",
|
|
18903
|
+
enum: ["==", "!=", ">=", "<=", ">", "<"],
|
|
18904
|
+
ops: ["until"],
|
|
18905
|
+
description: "until, with a property: how what the property reads stands to value before the wait is met. Default ==. != waits for it to read as anything else. >=, <=, > and < put a number in order, met on the first look past the value or level with it at the precision the property keeps, which is how a counter that steps rather than lands is waited on: a day counter on a fast clock went from 31 to 34 between two looks, and a wait for 32 ran out. For these the value is the number it is rather than fitted to the property, so a fraction on a whole-number property is a threshold rather than refused, and a property holding anything but a number is refused."
|
|
18906
|
+
},
|
|
18901
18907
|
says: {
|
|
18902
18908
|
type: "string",
|
|
18903
18909
|
ops: ["until"],
|
|
@@ -33891,7 +33897,7 @@ var init_stdio2 = __esm(() => {
|
|
|
33891
33897
|
});
|
|
33892
33898
|
|
|
33893
33899
|
// src/runtime-client.ts
|
|
33894
|
-
import { existsSync as existsSync12, readdirSync as
|
|
33900
|
+
import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync11, statSync as statSync6, unlinkSync } from "node:fs";
|
|
33895
33901
|
import { createConnection } from "node:net";
|
|
33896
33902
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
33897
33903
|
import { dirname as dirname8, join as join13, resolve as resolve4 } from "node:path";
|
|
@@ -34061,7 +34067,7 @@ function announcedIn(directory) {
|
|
|
34061
34067
|
const found = [];
|
|
34062
34068
|
const candidates = [];
|
|
34063
34069
|
const entries = [];
|
|
34064
|
-
for (const entry of
|
|
34070
|
+
for (const entry of readdirSync7(directory)) {
|
|
34065
34071
|
const report = ERROR_REPORT_PATTERN.exec(entry);
|
|
34066
34072
|
if (report) {
|
|
34067
34073
|
sweepErrorReport(join13(directory, entry), Number.parseInt(report[1] ?? "", 10));
|
|
@@ -40548,7 +40554,7 @@ var init_process_time = __esm(() => {
|
|
|
40548
40554
|
});
|
|
40549
40555
|
|
|
40550
40556
|
// src/project-scan.ts
|
|
40551
|
-
import { existsSync as existsSync17, readdirSync as
|
|
40557
|
+
import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync18 } from "node:fs";
|
|
40552
40558
|
import { join as join21 } from "node:path";
|
|
40553
40559
|
function skipped(name) {
|
|
40554
40560
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
@@ -40562,7 +40568,7 @@ function projectStructure(projectPath) {
|
|
|
40562
40568
|
if (steppedOver(directory)) {
|
|
40563
40569
|
return;
|
|
40564
40570
|
}
|
|
40565
|
-
for (const entry of
|
|
40571
|
+
for (const entry of readdirSync8(directory, { withFileTypes: true })) {
|
|
40566
40572
|
if (skipped(entry.name)) {
|
|
40567
40573
|
continue;
|
|
40568
40574
|
}
|
|
@@ -40591,7 +40597,7 @@ function scriptsWithoutUid(projectPath) {
|
|
|
40591
40597
|
if (steppedOver(directory)) {
|
|
40592
40598
|
return;
|
|
40593
40599
|
}
|
|
40594
|
-
for (const entry of
|
|
40600
|
+
for (const entry of readdirSync8(directory, { withFileTypes: true })) {
|
|
40595
40601
|
if (skipped(entry.name)) {
|
|
40596
40602
|
continue;
|
|
40597
40603
|
}
|
|
@@ -40621,7 +40627,7 @@ function projectTexts(projectPath) {
|
|
|
40621
40627
|
if (steppedOver(directory)) {
|
|
40622
40628
|
return;
|
|
40623
40629
|
}
|
|
40624
|
-
for (const entry of
|
|
40630
|
+
for (const entry of readdirSync8(directory, { withFileTypes: true })) {
|
|
40625
40631
|
if (skipped(entry.name)) {
|
|
40626
40632
|
continue;
|
|
40627
40633
|
}
|
|
@@ -40648,7 +40654,7 @@ function ignoredTexts(projectPath) {
|
|
|
40648
40654
|
const found = [];
|
|
40649
40655
|
const visit = (directory, prefix, ignored) => {
|
|
40650
40656
|
const under = ignored || steppedOver(directory);
|
|
40651
|
-
for (const entry of
|
|
40657
|
+
for (const entry of readdirSync8(directory, { withFileTypes: true })) {
|
|
40652
40658
|
if (skipped(entry.name)) {
|
|
40653
40659
|
continue;
|
|
40654
40660
|
}
|
|
@@ -40685,7 +40691,7 @@ function searchProject(projectPath, options) {
|
|
|
40685
40691
|
if (steppedOver(directory)) {
|
|
40686
40692
|
return;
|
|
40687
40693
|
}
|
|
40688
|
-
for (const entry of
|
|
40694
|
+
for (const entry of readdirSync8(directory, { withFileTypes: true })) {
|
|
40689
40695
|
if (full()) {
|
|
40690
40696
|
return;
|
|
40691
40697
|
}
|
|
@@ -41544,7 +41550,7 @@ var init_rename = __esm(() => {
|
|
|
41544
41550
|
});
|
|
41545
41551
|
|
|
41546
41552
|
// src/rename-symbol.ts
|
|
41547
|
-
import { existsSync as existsSync19, mkdirSync as mkdirSync12, readdirSync as
|
|
41553
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync12, readdirSync as readdirSync9, realpathSync as realpathSync5, rmSync as rmSync10, writeFileSync as writeFileSync14 } from "node:fs";
|
|
41548
41554
|
import { basename as basename5, dirname as dirname16, join as join22, relative as relative4, resolve as resolve6 } from "node:path";
|
|
41549
41555
|
function resourcePathOf(projectPath, absolute) {
|
|
41550
41556
|
const inside = relative4(onDiskSpelling(projectPath), onDiskSpelling(absolute));
|
|
@@ -41992,7 +41998,7 @@ function writeLeftAloneDetail(projectPath, symbol, detail) {
|
|
|
41992
41998
|
const path = join22(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
|
|
41993
41999
|
writeFileSync14(path, `${JSON.stringify(detail, null, 2)}
|
|
41994
42000
|
`, "utf8");
|
|
41995
|
-
const kept =
|
|
42001
|
+
const kept = readdirSync9(directory).filter((name) => name.endsWith(".json")).sort().reverse();
|
|
41996
42002
|
for (const old of kept.slice(10)) {
|
|
41997
42003
|
rmSync10(join22(directory, old), { force: true });
|
|
41998
42004
|
}
|
|
@@ -42081,7 +42087,7 @@ import { createHash as createHash4 } from "node:crypto";
|
|
|
42081
42087
|
import {
|
|
42082
42088
|
mkdirSync as mkdirSync14,
|
|
42083
42089
|
openSync as openSync2,
|
|
42084
|
-
readdirSync as
|
|
42090
|
+
readdirSync as readdirSync10,
|
|
42085
42091
|
readFileSync as readFileSync21,
|
|
42086
42092
|
realpathSync as realpathSync6,
|
|
42087
42093
|
rmSync as rmSync12,
|
|
@@ -42207,7 +42213,7 @@ function everyRunRecord() {
|
|
|
42207
42213
|
for (const directory of runtimeDirectories()) {
|
|
42208
42214
|
let entries;
|
|
42209
42215
|
try {
|
|
42210
|
-
entries =
|
|
42216
|
+
entries = readdirSync10(join24(directory, "runs"));
|
|
42211
42217
|
} catch {
|
|
42212
42218
|
continue;
|
|
42213
42219
|
}
|
|
@@ -42448,7 +42454,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
42448
42454
|
]));
|
|
42449
42455
|
let entries;
|
|
42450
42456
|
try {
|
|
42451
|
-
entries =
|
|
42457
|
+
entries = readdirSync10(directory);
|
|
42452
42458
|
} catch {
|
|
42453
42459
|
return;
|
|
42454
42460
|
}
|
|
@@ -42734,7 +42740,7 @@ import {
|
|
|
42734
42740
|
fstatSync,
|
|
42735
42741
|
mkdirSync as mkdirSync15,
|
|
42736
42742
|
openSync as openSync3,
|
|
42737
|
-
readdirSync as
|
|
42743
|
+
readdirSync as readdirSync11,
|
|
42738
42744
|
readFileSync as readFileSync23,
|
|
42739
42745
|
readSync as readSync2,
|
|
42740
42746
|
rmdirSync as rmdirSync2,
|
|
@@ -43618,7 +43624,7 @@ function elsewhereIn(projectPath, asked) {
|
|
|
43618
43624
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
43619
43625
|
let near = [];
|
|
43620
43626
|
try {
|
|
43621
|
-
near =
|
|
43627
|
+
near = readdirSync11(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
|
|
43622
43628
|
} catch {
|
|
43623
43629
|
return "";
|
|
43624
43630
|
}
|
|
@@ -43712,7 +43718,7 @@ function scriptsUnder(directory, prefix) {
|
|
|
43712
43718
|
return [];
|
|
43713
43719
|
}
|
|
43714
43720
|
const found = [];
|
|
43715
|
-
for (const entry of
|
|
43721
|
+
for (const entry of readdirSync11(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
43716
43722
|
if (entry.name.startsWith(".")) {
|
|
43717
43723
|
continue;
|
|
43718
43724
|
}
|
|
@@ -45387,7 +45393,7 @@ class GodotServer {
|
|
|
45387
45393
|
let report = null;
|
|
45388
45394
|
let reportProblem = null;
|
|
45389
45395
|
try {
|
|
45390
|
-
const written = existsSync20(reportsDir) ?
|
|
45396
|
+
const written = existsSync20(reportsDir) ? readdirSync11(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
45391
45397
|
const newest = written.at(-1);
|
|
45392
45398
|
if (newest !== undefined) {
|
|
45393
45399
|
report = parseJUnit(readFileSync23(join26(reportsDir, newest, "results.xml"), "utf8"));
|
|
@@ -48698,6 +48704,7 @@ ${staleNote}`}`
|
|
|
48698
48704
|
path: nodePath,
|
|
48699
48705
|
property: readString(args, "property") ?? "",
|
|
48700
48706
|
value: args["value"],
|
|
48707
|
+
...readString(args, "compare") === undefined ? {} : { compare: readString(args, "compare") },
|
|
48701
48708
|
...readNonEmptyString(args, "says") === undefined ? {} : { says: readNonEmptyString(args, "says") },
|
|
48702
48709
|
...readBoolean(args, "includeHidden") === undefined ? {} : { include_hidden: readBoolean(args, "includeHidden") },
|
|
48703
48710
|
timeout_ms: timeoutMs
|
|
@@ -48815,7 +48822,7 @@ import { existsSync as existsSync21 } from "node:fs";
|
|
|
48815
48822
|
import { join as join27, resolve as resolve8 } from "node:path";
|
|
48816
48823
|
|
|
48817
48824
|
// src/harnesses.ts
|
|
48818
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
48825
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
48819
48826
|
import { homedir as homedir2 } from "node:os";
|
|
48820
48827
|
import { basename as basename2, dirname as dirname3, join as join4, relative as relative2, resolve as resolve2 } from "node:path";
|
|
48821
48828
|
import process4 from "node:process";
|
|
@@ -49562,6 +49569,30 @@ function mainCheckoutOf(projectPath) {
|
|
|
49562
49569
|
const main = join4(dirname3(common), relative2(top, resolve2(projectPath)));
|
|
49563
49570
|
return isSameDirectory(main, projectPath) || !existsSync4(join4(main, "project.godot")) ? null : main;
|
|
49564
49571
|
}
|
|
49572
|
+
function worktreeCheckoutsOf(projectPath) {
|
|
49573
|
+
let top = resolve2(projectPath);
|
|
49574
|
+
while (!existsSync4(join4(top, ".git"))) {
|
|
49575
|
+
const up = dirname3(top);
|
|
49576
|
+
if (up === top) {
|
|
49577
|
+
return [];
|
|
49578
|
+
}
|
|
49579
|
+
top = up;
|
|
49580
|
+
}
|
|
49581
|
+
const listed = join4(top, ".git", "worktrees");
|
|
49582
|
+
if (!statSync2(join4(top, ".git")).isDirectory() || !existsSync4(listed)) {
|
|
49583
|
+
return [];
|
|
49584
|
+
}
|
|
49585
|
+
const below = relative2(top, resolve2(projectPath));
|
|
49586
|
+
return readdirSync2(listed).flatMap((name) => {
|
|
49587
|
+
const pointer = join4(listed, name, "gitdir");
|
|
49588
|
+
if (!existsSync4(pointer)) {
|
|
49589
|
+
return [];
|
|
49590
|
+
}
|
|
49591
|
+
const checkout = dirname3(resolve2(listed, name, readFileSync2(pointer, "utf8").trim()));
|
|
49592
|
+
const project = join4(checkout, below);
|
|
49593
|
+
return existsSync4(join4(project, "project.godot")) ? [project] : [];
|
|
49594
|
+
});
|
|
49595
|
+
}
|
|
49565
49596
|
function entryEnvironment(harness, projectPath) {
|
|
49566
49597
|
if (harness.toml || harness.yaml || harness.snippet) {
|
|
49567
49598
|
return;
|
|
@@ -49753,7 +49784,7 @@ init_setup();
|
|
|
49753
49784
|
init_setup();
|
|
49754
49785
|
init_tool_definitions();
|
|
49755
49786
|
init_tool_reference();
|
|
49756
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as
|
|
49787
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync6, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
49757
49788
|
import { basename as basename4, dirname as dirname6, join as join11 } from "node:path";
|
|
49758
49789
|
var SKILL_NAME = "gdharness";
|
|
49759
49790
|
var SHARED_SKILLS = join11(".agents", "skills");
|
|
@@ -50000,7 +50031,7 @@ function pruneEmpty(directory, projectPath) {
|
|
|
50000
50031
|
const shared = join11(projectPath, ".agents");
|
|
50001
50032
|
let path = directory;
|
|
50002
50033
|
while (path !== projectPath && (basename4(path) === "skills" || path === shared)) {
|
|
50003
|
-
if (
|
|
50034
|
+
if (readdirSync6(path).length > 0) {
|
|
50004
50035
|
return;
|
|
50005
50036
|
}
|
|
50006
50037
|
rmSync6(path, { recursive: true, force: true });
|
|
@@ -50353,6 +50384,17 @@ On ${version}. What is left for you:`);
|
|
|
50353
50384
|
` + " editor_launch restart tool, which closes and reopens the window.");
|
|
50354
50385
|
const serving = await serverServing(projectPath);
|
|
50355
50386
|
console.log(harnessNote({ moved, written, byHand }, [launch.command, ...launch.args].join(" "), serving === null ? null : { server: serving, isThisVersion: serving.version === version }));
|
|
50387
|
+
const trailing = worktreeCheckoutsOf(projectPath).flatMap((path) => {
|
|
50388
|
+
const addon = inspectProject(path).addons.find((one) => one.installed);
|
|
50389
|
+
return addon === undefined || addon.version === version ? [] : [{ path, version: addon.version }];
|
|
50390
|
+
});
|
|
50391
|
+
if (trailing.length > 0) {
|
|
50392
|
+
console.log(`
|
|
50393
|
+
Git worktrees of this project keep untracked addons of their own, which this upgrade does not reach:`);
|
|
50394
|
+
for (const worktree of trailing) {
|
|
50395
|
+
console.log(` ${worktree.path}: ${worktree.version ?? "an unmarked copy"}. Run gdharness upgrade ${worktree.path}`);
|
|
50396
|
+
}
|
|
50397
|
+
}
|
|
50356
50398
|
}
|
|
50357
50399
|
function doctorReport(projectPath) {
|
|
50358
50400
|
const report = inspectProject(projectPath);
|
|
@@ -21,6 +21,11 @@ const MOST_FRAMES: int = 600
|
|
|
21
21
|
## gives looking about a tenth of the time.
|
|
22
22
|
const LOOKS_APART: int = 9
|
|
23
23
|
|
|
24
|
+
## How a property wait compares what it reads with the value: equality by default, and order for a
|
|
25
|
+
## number that steps rather than lands, as a day counter on a fast clock goes from 31 to 34 between
|
|
26
|
+
## two looks and a wait for 32 never sees it.
|
|
27
|
+
const COMPARES: Array[String] = ["==", "!=", ">=", "<=", ">", "<"]
|
|
28
|
+
|
|
24
29
|
var _host: Node
|
|
25
30
|
var _values: Values
|
|
26
31
|
|
|
@@ -107,8 +112,10 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
107
112
|
# path, a property compares one value on one node. Taking says and dropping the other left a
|
|
108
113
|
# caller watching a screen while believing they were watching a property, and the answer says
|
|
109
114
|
# which of them it is about only if you already know that says wins.
|
|
110
|
-
if not says.is_empty() and (not property.is_empty() or params.has("value")):
|
|
111
|
-
var other: String =
|
|
115
|
+
if not says.is_empty() and (not property.is_empty() or params.has("value") or params.has("compare")):
|
|
116
|
+
var other: String = (
|
|
117
|
+
property if not property.is_empty() else ("value" if params.has("value") else "compare")
|
|
118
|
+
)
|
|
112
119
|
return {
|
|
113
120
|
"type": "error",
|
|
114
121
|
"message":
|
|
@@ -123,10 +130,10 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
123
130
|
if not says.is_empty():
|
|
124
131
|
var include_hidden: bool = Read.as_bool(params.get("include_hidden", false))
|
|
125
132
|
return await _wait_until_said(node_path, says, timeout_ms, include_hidden)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
133
|
+
var unasked: String = _property_wait_unasked(property, params)
|
|
134
|
+
if not unasked.is_empty():
|
|
135
|
+
return {"type": "error", "message": unasked}
|
|
136
|
+
var compare: String = str(params.get("compare", "=="))
|
|
130
137
|
|
|
131
138
|
var node: Node = standing["node"]
|
|
132
139
|
|
|
@@ -139,6 +146,8 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
139
146
|
return watched
|
|
140
147
|
|
|
141
148
|
var current: Variant = watched["value"]
|
|
149
|
+
if compare in [">=", "<=", ">", "<"]:
|
|
150
|
+
return await _wait_in_order(node, params, current, timeout_ms)
|
|
142
151
|
var wanted: Variant = _values.fitted(params["value"], typeof(current))
|
|
143
152
|
# Read against what the property is declared as, not only against what it holds this frame: an
|
|
144
153
|
# object property holding null holds nothing typed, so any value was taken as it came and compared
|
|
@@ -173,9 +182,10 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
173
182
|
# At the precision the property keeps, as set compares: a float arrives as 64 bits and most
|
|
174
183
|
# engine properties keep 32, so h_offset waited on as -1.1 read -1.10000002384186 every frame and
|
|
175
184
|
# the wait ran out on a value the game had reached.
|
|
185
|
+
var differs: bool = compare == "!="
|
|
176
186
|
while (
|
|
177
187
|
Values.comparable(current, wanted)
|
|
178
|
-
and
|
|
188
|
+
and Changes.held_already(wanted, current) == differs
|
|
179
189
|
and Time.get_ticks_msec() - started < timeout_ms
|
|
180
190
|
):
|
|
181
191
|
await _host.get_tree().process_frame
|
|
@@ -191,13 +201,111 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
191
201
|
"type": "condition",
|
|
192
202
|
"path": node_path,
|
|
193
203
|
"property": property,
|
|
194
|
-
"
|
|
204
|
+
"compare": compare,
|
|
205
|
+
"met": Values.comparable(current, wanted) and Changes.held_already(wanted, current) != differs,
|
|
195
206
|
"value": _values.serialize(current),
|
|
196
207
|
"elapsed_ms": Time.get_ticks_msec() - started,
|
|
197
208
|
"frames": frames,
|
|
198
209
|
}
|
|
199
210
|
|
|
200
211
|
|
|
212
|
+
## What a property wait is missing or was given wrongly, or "" when it can go ahead.
|
|
213
|
+
static func _property_wait_unasked(property: String, params: Dictionary) -> String:
|
|
214
|
+
if property.is_empty():
|
|
215
|
+
return "A property and a value, or says, are required"
|
|
216
|
+
if not params.has("value"):
|
|
217
|
+
return "A value to wait for is required"
|
|
218
|
+
var compare: String = str(params.get("compare", "=="))
|
|
219
|
+
if not COMPARES.has(compare):
|
|
220
|
+
return 'compare is one of %s, not "%s"' % [", ".join(COMPARES), compare]
|
|
221
|
+
return ""
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
## [method wait_until] for a number compared by order: met on the first look at which the property
|
|
225
|
+
## is past the value, or level with it at the precision the property keeps, as `==` compares.
|
|
226
|
+
##
|
|
227
|
+
## The value is read as the number it is rather than fitted to the property's type, since fitting
|
|
228
|
+
## truncates: 31.5 on a whole-number property became 31, and a wait for "at least 31.5" was met at 31.
|
|
229
|
+
func _wait_in_order(node: Node, params: Dictionary, current: Variant, timeout_ms: int) -> Dictionary:
|
|
230
|
+
var node_path: String = str(params.get("path", ""))
|
|
231
|
+
var property: String = str(params.get("property", ""))
|
|
232
|
+
var compare: String = str(params.get("compare", ""))
|
|
233
|
+
var given: Variant = params["value"]
|
|
234
|
+
# The property first: a string waited on in order is refused for being a string, whatever value
|
|
235
|
+
# came with it, since no number would make the wait answerable.
|
|
236
|
+
var unordered: String = _no_order(current, node_path, property, compare)
|
|
237
|
+
if not unordered.is_empty():
|
|
238
|
+
return {"type": "error", "message": unordered}
|
|
239
|
+
var number: Variant = given
|
|
240
|
+
if given is String:
|
|
241
|
+
var text: String = given
|
|
242
|
+
number = Read.json_or_null(text)
|
|
243
|
+
if typeof(number) != TYPE_INT and typeof(number) != TYPE_FLOAT:
|
|
244
|
+
return {
|
|
245
|
+
"type": "error",
|
|
246
|
+
"message":
|
|
247
|
+
(
|
|
248
|
+
"compare %s puts a number in order, and the value to wait for is %s: give a number."
|
|
249
|
+
% [compare, str(given)]
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
var limit: float = Read.as_float(number)
|
|
253
|
+
|
|
254
|
+
var started: int = Time.get_ticks_msec()
|
|
255
|
+
var frames: int = 0
|
|
256
|
+
while not _in_order(compare, limit, current) and Time.get_ticks_msec() - started < timeout_ms:
|
|
257
|
+
await _host.get_tree().process_frame
|
|
258
|
+
frames += 1
|
|
259
|
+
if not is_instance_valid(node):
|
|
260
|
+
return {"type": "error", "message": "%s was freed while waiting" % node_path}
|
|
261
|
+
var watched: Dictionary = _watched(node, node_path, property)
|
|
262
|
+
if watched.has("message"):
|
|
263
|
+
return watched
|
|
264
|
+
current = watched["value"]
|
|
265
|
+
unordered = _no_order(current, node_path, property, compare)
|
|
266
|
+
if not unordered.is_empty():
|
|
267
|
+
return {"type": "error", "message": unordered}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
"type": "condition",
|
|
271
|
+
"path": node_path,
|
|
272
|
+
"property": property,
|
|
273
|
+
"compare": compare,
|
|
274
|
+
"met": _in_order(compare, limit, current),
|
|
275
|
+
"value": _values.serialize(current),
|
|
276
|
+
"elapsed_ms": Time.get_ticks_msec() - started,
|
|
277
|
+
"frames": frames,
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
## Why [param current] cannot be put in order, or "" when it is a number.
|
|
282
|
+
static func _no_order(current: Variant, node_path: String, property: String, compare: String) -> String:
|
|
283
|
+
if typeof(current) == TYPE_INT or typeof(current) == TYPE_FLOAT:
|
|
284
|
+
return ""
|
|
285
|
+
return (
|
|
286
|
+
"%s.%s holds %s, which has no order, so compare %s cannot be asked of it: use == or !=."
|
|
287
|
+
% [node_path, property, type_string(typeof(current)), compare]
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
## Whether [param current], a number, stands to [param limit] as [param compare] says. Level counts
|
|
292
|
+
## at the precision the property keeps, so a 32-bit float holding 0.30000001 is at least 0.3.
|
|
293
|
+
static func _in_order(compare: String, limit: float, current: Variant) -> bool:
|
|
294
|
+
var reading: float = Read.as_float(current)
|
|
295
|
+
var level: bool = (
|
|
296
|
+
reading == limit or (typeof(current) == TYPE_FLOAT and Changes.held_already(limit, current))
|
|
297
|
+
)
|
|
298
|
+
match compare:
|
|
299
|
+
">=":
|
|
300
|
+
return level or reading > limit
|
|
301
|
+
"<=":
|
|
302
|
+
return level or reading < limit
|
|
303
|
+
">":
|
|
304
|
+
return not level and reading > limit
|
|
305
|
+
_:
|
|
306
|
+
return not level and reading < limit
|
|
307
|
+
|
|
308
|
+
|
|
201
309
|
## Why [param given] would have to be cut to be waited on a whole number or a truth, or "" when not.
|
|
202
310
|
##
|
|
203
311
|
## A number arrives from JSON as a float, so one is converted to what the property holds, and the
|
package/build/index.js
CHANGED
|
@@ -35025,6 +35025,12 @@ var TOOL_SPECS = [
|
|
|
35025
35025
|
ops: ["until"],
|
|
35026
35026
|
description: "until: the value to wait for, fitted to the property's type and compared at the precision the property keeps, so -1.1 meets a 32-bit float holding -1.10000002. A value the property would have to cut is refused: a fraction on a whole number, a number other than 0 or 1 on a bool. On a property holding an object, whether declared to or untyped and holding one or null, the text null means null. One declared to hold an object refuses any value other than null, since it can never hold it."
|
|
35027
35027
|
},
|
|
35028
|
+
compare: {
|
|
35029
|
+
type: "string",
|
|
35030
|
+
enum: ["==", "!=", ">=", "<=", ">", "<"],
|
|
35031
|
+
ops: ["until"],
|
|
35032
|
+
description: "until, with a property: how what the property reads stands to value before the wait is met. Default ==. != waits for it to read as anything else. >=, <=, > and < put a number in order, met on the first look past the value or level with it at the precision the property keeps, which is how a counter that steps rather than lands is waited on: a day counter on a fast clock went from 31 to 34 between two looks, and a wait for 32 ran out. For these the value is the number it is rather than fitted to the property, so a fraction on a whole-number property is a threshold rather than refused, and a property holding anything but a number is refused."
|
|
35033
|
+
},
|
|
35028
35034
|
says: {
|
|
35029
35035
|
type: "string",
|
|
35030
35036
|
ops: ["until"],
|
|
@@ -41195,6 +41201,7 @@ ${staleNote}`}`
|
|
|
41195
41201
|
path: nodePath,
|
|
41196
41202
|
property: readString(args, "property") ?? "",
|
|
41197
41203
|
value: args["value"],
|
|
41204
|
+
...readString(args, "compare") === undefined ? {} : { compare: readString(args, "compare") },
|
|
41198
41205
|
...readNonEmptyString(args, "says") === undefined ? {} : { says: readNonEmptyString(args, "says") },
|
|
41199
41206
|
...readBoolean(args, "includeHidden") === undefined ? {} : { include_hidden: readBoolean(args, "includeHidden") },
|
|
41200
41207
|
timeout_ms: timeoutMs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"mcpName": "io.github.Aureliolo/gdharness",
|
|
5
5
|
"description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
|
|
6
6
|
"type": "module",
|