From c43e1c6fac12b61fd0efd3ef8e2faeb2d9de87e8 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 2 May 2026 02:27:54 +0100 Subject: [PATCH] Fix Ink scene entrypoint validation --- examples/ink/cold-wake-sanctuary-intake.ink | 2 ++ tools/validate_ink_examples.py | 39 +++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/examples/ink/cold-wake-sanctuary-intake.ink b/examples/ink/cold-wake-sanctuary-intake.ink index 949b0dd..d1eab02 100644 --- a/examples/ink/cold-wake-sanctuary-intake.ink +++ b/examples/ink/cold-wake-sanctuary-intake.ink @@ -4,6 +4,8 @@ VAR maer_obligation = 60 VAR bay_reserved = false VAR packet_shown = false +-> start + === start === # ghostlight.scene: scene-02-sanctuary-intake # ghostlight.fixture: examples/agent-state.cold-wake-story-lab.json diff --git a/tools/validate_ink_examples.py b/tools/validate_ink_examples.py index 3978276..ffb6e59 100644 --- a/tools/validate_ink_examples.py +++ b/tools/validate_ink_examples.py @@ -55,6 +55,12 @@ def npx_command() -> str: return command +def node_command() -> str: + command = shutil.which("node.exe") or shutil.which("node") + require(command is not None, "node is required to smoke-test compiled Ink examples") + return command + + def compile_ink(path: Path) -> None: with tempfile.TemporaryDirectory(prefix="ghostlight-ink-") as temp_dir: output_path = Path(temp_dir) / f"{path.stem}.json" @@ -73,6 +79,39 @@ def compile_ink(path: Path) -> None: require(output_path.exists(), f"{path} did not produce compiled Ink JSON") compiled = output_path.read_text(encoding="utf-8-sig") require(compiled.strip().startswith("{"), f"{path} compiled output is not JSON") + validate_story_reaches_initial_choices(path, output_path) + + +def validate_story_reaches_initial_choices(source_path: Path, compiled_path: Path) -> None: + script = """ +const fs = require("fs"); +const { Story } = require("inkjs"); +const storyJson = fs.readFileSync(process.argv[1], "utf8").replace(/^\\uFEFF/, ""); +const story = new Story(storyJson); +let safety = 0; +while (story.canContinue && safety < 1000) { + story.Continue(); + safety += 1; +} +if (safety >= 1000) { + throw new Error("story did not settle before safety limit"); +} +if (story.currentChoices.length === 0) { + throw new Error("story starts without any available choices"); +} +""" + result = subprocess.run( + [node_command(), "-e", script, str(compiled_path)], + cwd=ROOT, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + require( + result.returncode == 0, + f"{source_path} compiled, but does not reach initial choices\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}", + ) def validate_text_blocks(items: Any, path: str) -> None: