From 420ad70d1b29d0df61a8a0cc673f3eed41c7df95 Mon Sep 17 00:00:00 2001 From: valgulnecron <39313199+ValgulNecron@users.noreply.github.com> Date: Mon, 5 Oct 2026 05:59:46 +0200 Subject: [PATCH 1/5] fix(nuclear-option): correct players capability YAML structure The kick, ban, and unban fields were incorrectly nested as objects with a 'command' key, but the GameTemplate CRD defines these as string fields directly. Restructured to match the CRD and the format used by other modules (minecraft-java, rust). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NsN6bnAMRM9XQtrTQ6KC7U Signed-off-by: valgulnecron <39313199+ValgulNecron@users.noreply.github.com> --- nuclear-option/module.yaml | 2 +- nuclear-option/template.yaml | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/nuclear-option/module.yaml b/nuclear-option/module.yaml index 5daf788..0c83516 100644 --- a/nuclear-option/module.yaml +++ b/nuclear-option/module.yaml @@ -2,7 +2,7 @@ apiVersion: gameplane.local/module/v1 name: nuclear-option displayName: Nuclear Option -version: 1.0.0 +version: 1.0.1 game: nuclear-option categories: [Simulation, PvP] summary: Nuclear Option dedicated server with remote-command console, runtime-downloaded via SteamCMD. diff --git a/nuclear-option/template.yaml b/nuclear-option/template.yaml index 55ebfa6..9abfd13 100644 --- a/nuclear-option/template.yaml +++ b/nuclear-option/template.yaml @@ -166,12 +166,9 @@ spec: list: command: "get-player-list" entryRegex: '"steamId":\s*"(?P[^"]*)"' - kick: - command: "kick-player {{.Player}}" - ban: - command: "banlist-add {{.Player}}" - unban: - command: "banlist-remove {{.Player}}" + kick: "kick-player {{.Player}}" + ban: "banlist-add {{.Player}}" + unban: "banlist-remove {{.Player}}" # Note: the server returns 2000 (success) for kick/ban/unban even when # the Steam ID is not present, so the agent cannot distinguish a # successful moderation from a silent no-op. From 83100b19c52891377fa2600ec2a670a35cb01ea2 Mon Sep 17 00:00:00 2001 From: valgulnecron <39313199+ValgulNecron@users.noreply.github.com> Date: Mon, 5 Oct 2026 06:00:39 +0200 Subject: [PATCH 2/5] chore(validate): enforce GameTemplate spec.version == module.yaml version Add rule 10 to validate.py: GameTemplate spec.version must match the module.yaml version exactly, preventing drift like minecraft-java's (2.8.1 vs 2.9.0) that caused the wizard to display outdated versions. Also fix minecraft-java template spec.version to 2.9.0. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NsN6bnAMRM9XQtrTQ6KC7U Signed-off-by: valgulnecron <39313199+ValgulNecron@users.noreply.github.com> --- minecraft-java/template.yaml | 2 +- validate.py | 42 ++++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/minecraft-java/template.yaml b/minecraft-java/template.yaml index 70866f1..083f722 100644 --- a/minecraft-java/template.yaml +++ b/minecraft-java/template.yaml @@ -23,7 +23,7 @@ spec: displayName: Minecraft (Java Edition) game: minecraft-java # Kept in lockstep with module.yaml's bundle version from 2.5.0 onward. - version: 2.8.1 + version: 2.9.0 categories: [Sandbox, Survival, Building, Modded, Creative] # Brand color the dashboard tints this game's icon/accents with. accentColor: "#5b9a3e" diff --git a/validate.py b/validate.py index b55d477..5dbfcc2 100644 --- a/validate.py +++ b/validate.py @@ -1026,6 +1026,31 @@ def rule_credential_fields_must_be_password(spec: dict) -> list[Finding]: return findings +def rule_template_version_matches_module(spec: dict, module_version: str | None, template_path: Path) -> list[Finding]: + """Rule 10: GameTemplate spec.version must match module.yaml version. + + Historical bug: minecraft-java template.yaml had spec.version 2.8.1 while + module.yaml was 2.9.0, causing the wizard to display an outdated version. + The template must stay in lockstep with the module's declared version. + """ + if module_version is None: + return [] + + spec_version = spec.get("version", "") + if spec_version == module_version: + return [] + + return [ + Finding( + ERROR, + "template-version-drift", + f"{template_path.name}: spec.version={spec_version!r} but module.yaml version={module_version!r} — " + "these must match exactly (template versions say 'lockstep'). Update template spec.version to " + f"{module_version!r}.", + ) + ] + + LAYOUT_ENFORCED_MODULES = { "cs2", "palworld", @@ -1103,7 +1128,7 @@ def collect_image_refs(spec: dict) -> list[str]: return out -def validate_module(spec: dict, cache: dict[str, dict]) -> list[Finding]: +def validate_module(spec: dict, cache: dict[str, dict], module_dir: Path | None = None) -> list[Finding]: findings: list[Finding] = [] image_refs = collect_image_refs(spec) if not image_refs: @@ -1129,6 +1154,19 @@ def validate_module(spec: dict, cache: dict[str, dict]) -> list[Finding]: findings += rule_mods_loaders_requires_versions(spec) findings += rule_credential_fields_must_be_password(spec) findings += rule_images_pinned(spec) + + # Check template version matches module.yaml if we have the module dir + if module_dir: + module_yaml_path = module_dir / "module.yaml" + if module_yaml_path.exists(): + try: + module_doc = yaml.safe_load(module_yaml_path.read_text()) + module_version = (module_doc or {}).get("version") + template_path = module_dir / "template.yaml" + findings += rule_template_version_matches_module(spec, module_version, template_path) + except yaml.YAMLError: + pass # module.yaml parse error already reported elsewhere if relevant + return findings @@ -1348,7 +1386,7 @@ def main(argv: list[str]) -> int: continue spec = (doc or {}).get("spec") or {} - findings = rule_directory_layout(module_dir) + validate_module(spec, cache) + findings = rule_directory_layout(module_dir) + validate_module(spec, cache, module_dir) if module_dir.name in offline_findings: findings.extend(offline_findings[module_dir.name]) print(f"== {module_dir.name} ==") From e3c0986aa143fc13a9756a9169e76053ce3eb16d Mon Sep 17 00:00:00 2001 From: valgulnecron <39313199+ValgulNecron@users.noreply.github.com> Date: Mon, 5 Oct 2026 06:00:58 +0200 Subject: [PATCH 3/5] fix(modules): unify dinosaur category spelling to Dinosaurs (plural) Standardize both ark-survival-evolved and the-isle to use "Dinosaurs" (plural) for the dinosaur-themed game category. Avoids fragmenting the catalog with two spellings of the same concept; aligns with English conventions for game categories (Survival, Sandbox, etc.). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NsN6bnAMRM9XQtrTQ6KC7U Signed-off-by: valgulnecron <39313199+ValgulNecron@users.noreply.github.com> --- the-isle/module.yaml | 2 +- the-isle/template.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/the-isle/module.yaml b/the-isle/module.yaml index a06d9d6..15541d8 100644 --- a/the-isle/module.yaml +++ b/the-isle/module.yaml @@ -4,7 +4,7 @@ name: the-isle displayName: The Isle version: 1.0.0 game: the-isle -categories: [Survival, Dinosaur, Open World, Multiplayer] +categories: [Survival, Dinosaurs, Open World, Multiplayer] summary: The Isle dedicated server (Unreal Engine 4) with Source RCON remote administration and persistent world save management. homepage: https://findtheisle.com/ license: MIT diff --git a/the-isle/template.yaml b/the-isle/template.yaml index 501b014..4cf6272 100644 --- a/the-isle/template.yaml +++ b/the-isle/template.yaml @@ -17,7 +17,7 @@ spec: displayName: The Isle game: the-isle version: 1.0.0 - categories: [Survival, Dinosaur, Open World, Multiplayer] + categories: [Survival, Dinosaurs, Open World, Multiplayer] accentColor: "#22543d" description: | The Isle dedicated multiplayer server running on Unreal Engine. From 42c4bea9cead5560608ee5bd97bbc1c4362b3dcf Mon Sep 17 00:00:00 2001 From: valgulnecron <39313199+ValgulNecron@users.noreply.github.com> Date: Mon, 5 Oct 2026 06:12:29 +0200 Subject: [PATCH 4/5] fix(nuclear-option): keep template spec.version in lockstep with module.yaml The new validate.py rule requires spec.version to equal module.yaml, which the players fix bumped to 1.0.1. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NsN6bnAMRM9XQtrTQ6KC7U Signed-off-by: valgulnecron <39313199+ValgulNecron@users.noreply.github.com> --- nuclear-option/template.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuclear-option/template.yaml b/nuclear-option/template.yaml index 9abfd13..434a2d6 100644 --- a/nuclear-option/template.yaml +++ b/nuclear-option/template.yaml @@ -28,7 +28,7 @@ spec: displayName: Nuclear Option game: nuclear-option # Kept in lockstep with module.yaml's bundle version. - version: 1.0.0 + version: 1.0.1 categories: [Simulation, PvP] # Brand color for the dashboard. accentColor: "#ff6b6b" From 0c88a710007b8ad901d0fa8a1874e79f8c9ce439 Mon Sep 17 00:00:00 2001 From: valgulnecron <39313199+ValgulNecron@users.noreply.github.com> Date: Mon, 5 Oct 2026 06:12:37 +0200 Subject: [PATCH 5/5] chore(validate): number the version-lockstep rule 11 Rule 10 was already taken by rule_images_pinned. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NsN6bnAMRM9XQtrTQ6KC7U Signed-off-by: valgulnecron <39313199+ValgulNecron@users.noreply.github.com> --- validate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validate.py b/validate.py index 5dbfcc2..91614c1 100644 --- a/validate.py +++ b/validate.py @@ -1027,7 +1027,7 @@ def rule_credential_fields_must_be_password(spec: dict) -> list[Finding]: def rule_template_version_matches_module(spec: dict, module_version: str | None, template_path: Path) -> list[Finding]: - """Rule 10: GameTemplate spec.version must match module.yaml version. + """Rule 11: GameTemplate spec.version must match module.yaml version. Historical bug: minecraft-java template.yaml had spec.version 2.8.1 while module.yaml was 2.9.0, causing the wizard to display an outdated version.