From 54bc763dff8db24a17d85a4499d882fc07a59360 Mon Sep 17 00:00:00 2001 From: daddy Date: Sat, 5 Sep 2026 23:30:00 +0000 Subject: [PATCH] Fix backwards controls + the exe's bare-capsule avatar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two real bugs from the last change, both only visible in a real run: 1. Controls reversed: the third-person SpringArm3D had a 180° Y rotation that put the camera IN FRONT of the player looking back -- a mirror, so W read as backward and mouse pitch as inverted. SpringArm3D extends its child along +Z, not -Z; with no rotation the camera sits behind the player (+Z world) and looks -Z straight at them. Verified the vectors headless: camera behind, W decreases Z, mouse-up looks up. 2. Bare capsule instead of an avatar in the .exe: avatar_catalog.gd reads assets/avatars/catalog.json with FileAccess.open(), but the export used `all_resources` + empty include_filter, which does NOT pack plain .json files. ensure_catalog() failed -> no loadout -> build_current_avatar() returned null -> capsule stayed visible. From source it worked because res:// is the real filesystem. Fixed: include_filter="*.json" in both export presets (verified catalog.json + the .glb pack are now in the exported .pck). - scripts/verify_export_contents.gd + a checkpoint-runner step: export a pack and assert the non-resource runtime files are inside it. This is the guard for the "works from source, broken in the .exe" class. Client-side -- rebuild the exe. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ --- export_presets.cfg | 4 +- scenes/player.tscn | 3 +- scripts/run_checkpoint_tests.py | 11 ++++++ scripts/verify_export_contents.gd | 66 +++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 scripts/verify_export_contents.gd diff --git a/export_presets.cfg b/export_presets.cfg index fcd3c73..dae8dbc 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -7,7 +7,7 @@ advanced_options=false dedicated_server=false custom_features="" export_filter="all_resources" -include_filter="" +include_filter="*.json" exclude_filter="" export_path="export/linux/agp-game.x86_64" encryption_include_filters="" @@ -38,7 +38,7 @@ advanced_options=false dedicated_server=false custom_features="" export_filter="all_resources" -include_filter="" +include_filter="*.json" exclude_filter="" export_path="export/windows/agp-game.exe" encryption_include_filters="" diff --git a/scenes/player.tscn b/scenes/player.tscn index 4047391..5a62e4a 100644 --- a/scenes/player.tscn +++ b/scenes/player.tscn @@ -20,10 +20,9 @@ shape = SubResource("CapsuleShape1") mesh = SubResource("CapsuleMesh1") [node name="CameraPivot" type="Node3D" parent="."] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.7, 0) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.4, 0) [node name="SpringArm3D" type="SpringArm3D" parent="CameraPivot"] -transform = Transform3D(-1, 0, -8.7422777e-08, 0, 1, 0, 8.7422777e-08, 0, -1, 0, 0, 0) spring_length = 4.0 margin = 0.2 diff --git a/scripts/run_checkpoint_tests.py b/scripts/run_checkpoint_tests.py index a8f8d9e..a022246 100755 --- a/scripts/run_checkpoint_tests.py +++ b/scripts/run_checkpoint_tests.py @@ -180,6 +180,17 @@ def main(): fc_env["GODOT_BIN"] = godot_bin r = run(["bash", str(fc_script)], env=fc_env) results["fresh-clone boot"] = r.returncode == 0 + + # Export packaging: the game reads some files (catalog.json) + # via raw FileAccess, which `all_resources` export doesn't + # pack unless include_filter covers them. Passes from source, + # bare capsule in the .exe. This guards that. + ec_script = ROOT / "scripts" / "verify_export_contents.gd" + if ec_script.exists(): + print("\n--- scripts/verify_export_contents.gd ---") + r = run([godot_bin, "--headless", "--path", str(ROOT), + "--script", "res://scripts/verify_export_contents.gd"], env=godot_env) + results["export packaging"] = r.returncode == 0 finally: server_proc.terminate() try: diff --git a/scripts/verify_export_contents.gd b/scripts/verify_export_contents.gd new file mode 100644 index 0000000..0c9411d --- /dev/null +++ b/scripts/verify_export_contents.gd @@ -0,0 +1,66 @@ +extends SceneTree +## Exports a resource pack and checks that the files the game reads at runtime +## via raw FileAccess (not as Godot resources) are actually inside it. +## +## `export_filter="all_resources"` packs scenes/scripts/imported assets but +## NOT arbitrary files like assets/avatars/catalog.json -- which the avatar +## system opens with FileAccess.open(). Missing it means the game runs fine +## from source but the exported .exe shows a bare capsule instead of an +## avatar. This is the guard for that class of bug. +## +## Run: godot --headless --path . --script res://scripts/verify_export_contents.gd + +const PACK_PATH := "res://.export_check.pck" + +## Files that MUST be in the pack for the game to work, that aren't Godot +## resources (so they only get packed via export_presets include_filter). +const REQUIRED := [ + "res://assets/avatars/catalog.json", +] + +## A resource we know should be there, as a sanity check that the pack loaded. +const SANITY := "res://scenes/game.tscn" + + +func _initialize() -> void: + var abs_pack := ProjectSettings.globalize_path(PACK_PATH) + + var export := OS.execute(_godot_bin(), [ + "--headless", "--path", ProjectSettings.globalize_path("res://"), + "--export-pack", "Linux", abs_pack, + ], [], true) + if not FileAccess.file_exists(PACK_PATH): + _fail("export-pack produced no file:\n%s" % export) + + if not ProjectSettings.load_resource_pack(PACK_PATH, false): + _fail("could not load the exported pack back") + + if not FileAccess.file_exists(SANITY): + _fail("pack sanity check failed -- %s not found (pack may not have loaded)" % SANITY) + + var missing := [] + for f in REQUIRED: + if not FileAccess.file_exists(f): + missing.append(f) + + DirAccess.remove_absolute(abs_pack) + + if missing.is_empty(): + print("=== SMOKE RESULT: PASS === (required non-resource files are in the export)") + quit(0) + else: + print("=== SMOKE RESULT: FAIL ===") + for f in missing: + printerr(" MISSING from export: ", f, + " -- add its glob to export_presets.cfg include_filter") + quit(1) + + +func _godot_bin() -> String: + return OS.get_executable_path() + + +func _fail(msg: String) -> void: + printerr(msg) + print("=== SMOKE RESULT: FAIL ===") + quit(1)