current_schema_version() line-scanned res://../project.yaml at runtime. That resolves to the repo root in a dev run and to nothing in an exported build, so a shipped game got the "?.?.?" fallback every time. Since that tag is the Atlas disk cache's ONLY invalidation signal, every exported build stamped and compared the same sentinel: a canvas cached by one build would be served by every later build, forever. T-1239 is what that failure looks like once it happens. loading_screen.gd carried a byte-for-byte copy of the same function, so the version shown to the player was "?.?.?" in exactly the builds where a version string is worth showing. Both call sites now share client/scripts/build_version.gd, which reads application/config/version out of ProjectSettings — a value Godot bakes into the PCK, identical in the editor and in an export by construction rather than by luck. No file IO, no fallback branch. project.yaml stays the source of truth (CLAUDE.md); client/project.godot mirrors it. A mirror nobody checks would be worse than the bug it replaces -- the old code failed loudly everywhere, a stale mirror fails silently -- so tooling/check-client-version compares the two and the pre-push hook runs it unconditionally. Not gated on "were those files in this push": drift persists on main once introduced, and gating would let an existing drift ride along. The test this replaces asserted that current_schema_version() did not return its fallback, and passed -- in the one environment where the code under test worked. Three tests now pin the property that actually matters: a real version, sourced from the baked setting, matching project.yaml. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
72 lines
2.8 KiB
Python
Executable File
72 lines
2.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Fail if client/project.godot's baked version has drifted from project.yaml.
|
|
|
|
project.yaml is the version source of truth (CLAUDE.md). The client cannot read
|
|
it at runtime — an exported build has no repo root — so the value is mirrored
|
|
into `application/config/version` in client/project.godot, which Godot bakes
|
|
into the PCK (T-1241).
|
|
|
|
A mirror nobody checks is worse than the bug it replaced: the old code failed
|
|
LOUDLY in an export ("?.?.?" everywhere, no cache invalidation ever), whereas a
|
|
stale mirror fails SILENTLY — the Atlas disk cache would keep serving canvases
|
|
under a version that stopped matching the build. That is precisely the T-1239
|
|
failure, which cost eight days of a map drawn from a canvas whose generating
|
|
code no longer existed. Hence this check, wired into the pre-push hook.
|
|
|
|
Exit: 0 = in sync, 1 = drifted or unreadable.
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
PROJECT_YAML = ROOT / "project.yaml"
|
|
PROJECT_GODOT = ROOT / "client" / "project.godot"
|
|
|
|
# Anchored to line start so the commentary above `version:` (which mentions
|
|
# earlier versions by number) can never be mistaken for the field itself.
|
|
YAML_VERSION = re.compile(r"^version:\s*(\S+)\s*$", re.MULTILINE)
|
|
GODOT_VERSION = re.compile(r'^config/version\s*=\s*"([^"]*)"\s*$', re.MULTILINE)
|
|
|
|
|
|
def read(path: Path, pattern: re.Pattern, label: str) -> str | None:
|
|
if not path.exists():
|
|
print(f"check-client-version: {path} not found", file=sys.stderr)
|
|
return None
|
|
match = pattern.search(path.read_text(encoding="utf-8"))
|
|
if not match:
|
|
print(f"check-client-version: no {label} in {path}", file=sys.stderr)
|
|
return None
|
|
return match.group(1)
|
|
|
|
|
|
def main() -> int:
|
|
yaml_version = read(PROJECT_YAML, YAML_VERSION, "`version:` line")
|
|
godot_version = read(PROJECT_GODOT, GODOT_VERSION, "`config/version=` line")
|
|
if yaml_version is None or godot_version is None:
|
|
return 1
|
|
|
|
if yaml_version != godot_version:
|
|
print(
|
|
"check-client-version: version drift\n"
|
|
f" project.yaml {yaml_version}\n"
|
|
f" client/project.godot {godot_version}\n"
|
|
"\n"
|
|
"project.yaml is the source of truth. Set config/version in\n"
|
|
"client/project.godot's [application] section to match it.\n"
|
|
"\n"
|
|
"This matters beyond cosmetics: the Atlas disk cache keys its\n"
|
|
"invalidation on this version, so a stale mirror makes a shipped\n"
|
|
"build serve canvases generated by code it no longer runs (T-1239).",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
|
|
print(f"check-client-version: OK — {yaml_version}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|