From d1bd20582a7f32769e2f232a10c5a9057d15f179 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sat, 11 Apr 2026 00:01:25 -0400 Subject: [PATCH] chore: Fix gdlint/gdformat and harden pre-push on Windows - main.gd: class member order (prvvars before @onready); wrap doc lines to 100 cols. - isometric_follow_camera.gd: wrap doc lines to 100 cols. - position_authority_client.gd: gdformat. - pre-push: resolve .venv-gd/Scripts/gdlint.exe and gdformat.exe (Windows venv). - Add scripts/install-git-hooks.ps1; README notes hook install and why CI can still fail. --- client/README.md | 12 ++++++++++-- client/scripts/isometric_follow_camera.gd | 12 +++++++----- client/scripts/main.gd | 21 ++++++++++++--------- client/scripts/position_authority_client.gd | 4 +--- scripts/git-hooks/pre-push | 4 ++++ scripts/install-git-hooks.ps1 | 19 +++++++++++++++++++ 6 files changed, 53 insertions(+), 19 deletions(-) create mode 100644 scripts/install-git-hooks.ps1 diff --git a/client/README.md b/client/README.md index b371d21..cfb8079 100644 --- a/client/README.md +++ b/client/README.md @@ -120,12 +120,20 @@ On **Linux** (including GitHub Actions), the path must be **`gdUnit4`** with a c **Dev (NEON-28):** **`dev_toggle_occluder_obstacle`** (default **Ctrl+Shift+K**) toggles the prototype `Obstacle` visibility/collision in **`main.gd`**. **F9** / **F10** are used by the embedded debugger; use this action instead when the game is running in the editor. -**Git hook (recommended):** install the repo’s local **pre-push** GDScript lint hook from the repo root: +**Git hook (recommended):** install the repo’s local **pre-push** GDScript lint hook from the repo root (once per clone): ```bash ./scripts/install-git-hooks.sh ``` -It runs **`gdlint client/scripts client/test`** and **`gdformat --check client/scripts client/test`** before each push, preferring the repo-local **`.venv-gd/`** tools when present. +On **Windows** (PowerShell), same hook: + +```powershell +pwsh -File scripts/install-git-hooks.ps1 +``` + +It runs **`gdlint client/scripts client/test`** and **`gdformat --check client/scripts client/test`** before each push. It prefers **`.venv-gd/bin/`** (Unix) or **`.venv-gd/Scripts/`** (Windows venv) when present; without that venv or a global install, the hook **fails** and blocks the push — same as CI. + +**If CI fails gdlint but your push succeeded:** the hook was not installed, **`gdlint`/`gdformat` were missing** when the hook ran, or push bypassed hooks (`--no-verify`). **Reports:** GdUnit writes under **`reports/`** (gitignored); ignore locally generated HTML/XML when committing. diff --git a/client/scripts/isometric_follow_camera.gd b/client/scripts/isometric_follow_camera.gd index b792497..f05d90f 100644 --- a/client/scripts/isometric_follow_camera.gd +++ b/client/scripts/isometric_follow_camera.gd @@ -4,7 +4,8 @@ extends Node3D ## NEON-26: discrete zoom bands via [member zoom_band_config]; wheel / `camera_zoom_*` actions. ## NEON-28: occluder overrides are keyed by [method Object.get_instance_id] only — never [Object] ## keys, or [method Dictionary.keys] / [method Dictionary.erase] can freeze after an occluder is -## [method Node.queue_free]'d (dev smoke: [InputMap] [code]dev_toggle_occluder_obstacle[/code] in [code]main.gd[/code]). +## [method Node.queue_free]'d (dev smoke: action [code]dev_toggle_occluder_obstacle[/code] in +## [code]main.gd[/code]). ## Pitch / roll are fixed by presentation exports; **orbit yaw** in state stays **0** while ## [member allow_yaw] is false (no rotate input bound). Future orbit: read relative input here, ## add to `_orbit_yaw_rad`, clamp with [member max_yaw_deg]. @@ -231,7 +232,7 @@ static func occlusion_policy_is_valid(policy: Resource) -> bool: ## True if [param body] is a live [Node3D] suitable as an [member _occluder_overrides] key. ## Used before occlusion ray work and from tests (NEON-28). -## [code]is_instance_valid[/code] first: [code]is Node3D[/code] on a freed reference errors at runtime. +## [code]is_instance_valid[/code] first; [code]is Node3D[/code] on a freed ref errors at runtime. static func occluder_override_key_is_valid(body: Variant) -> bool: if not is_instance_valid(body): return false @@ -378,8 +379,9 @@ func _restore_all_occluders() -> void: _restore_occluder_id(occ_id as int) -## Public hook for [code]dev_toggle_occluder_obstacle[/code] smoke ([code]main.gd[/code]): restore materials and clear [member _occluder_overrides] **while bodies -## are still live**, then the caller may [method Node.queue_free] the occluder. Avoids any post-free -## occlusion pass touching a tearing-down [StaticBody3D] (the semi-transparent faded box case). +## Public hook for [code]dev_toggle_occluder_obstacle[/code] smoke; [code]main.gd[/code] dev toggle. +## Restore materials and clear [member _occluder_overrides] **while bodies are still live**, +## then the caller may [method Node.queue_free] the occluder. Avoids post-free occlusion touching a +## tearing-down [StaticBody3D] (semi-transparent faded box). func restore_all_occluder_materials_now() -> void: _restore_all_occluders() diff --git a/client/scripts/main.gd b/client/scripts/main.gd index 6a4b183..da00c39 100644 --- a/client/scripts/main.gd +++ b/client/scripts/main.gd @@ -13,10 +13,13 @@ const MOVE_REJECT_MSG_SECONDS: float = 4.0 ## Bump on each rejection so older one-shot timers do not clear a newer message. var _move_reject_msg_token: int = 0 -## [InputMap] [code]dev_toggle_occluder_obstacle[/code] (default **Ctrl+Shift+K**): restored when the obstacle is shown again. +## [InputMap] [code]dev_toggle_occluder_obstacle[/code] (default **Ctrl+Shift+K**): +## Restored when the obstacle is shown again. var _dev_saved_obstacle_layer: int = -1 var _dev_saved_obstacle_mask: int = -1 var _dev_saved_obstacle_process_mode: Node.ProcessMode = Node.PROCESS_MODE_INHERIT +## Cached `Obstacle` (path breaks after reparent for nav bake; no fixed [NodePath]). +var _dev_obstacle_smoke: Node3D @onready var _camera: Camera3D = $World/IsometricFollowCamera/Camera3D @onready var _world: Node3D = $World @@ -28,9 +31,6 @@ var _dev_saved_obstacle_process_mode: Node.ProcessMode = Node.PROCESS_MODE_INHER @onready var _move_reject_label: Label = $UICanvas/MoveRejectLabel @onready var _floor: StaticBody3D = $World/NavigationRegion3D/Floor -## Cached `Obstacle` body (path breaks after reparent for nav bake; do not use a fixed [NodePath]). -var _dev_obstacle_smoke: Node3D - func _ready() -> void: set_process_unhandled_key_input(true) @@ -80,15 +80,18 @@ func _on_move_rejected(reason_code: String) -> void: ) -## Dev smoke (NEON-28 occluder): input action [code]dev_toggle_occluder_obstacle[/code] (default **Ctrl+Shift+K**) -## toggles `Obstacle` **visibility + collision** — it does **not** call [method Node.queue_free]. -## Uses [member Node.process_mode] [code]PROCESS_MODE_DISABLED[/code] so the [StaticBody3D] is **removed -## from the physics simulation** (default [member CollisionObject3D.disable_mode] is [code]REMOVE[/code]). +## Dev smoke (NEON-28 occluder): action [code]dev_toggle_occluder_obstacle[/code] +## (default **Ctrl+Shift+K**). Toggles `Obstacle` **visibility + collision** — +## does not call [method Node.queue_free]. +## Uses [member Node.process_mode] [code]PROCESS_MODE_DISABLED[/code] so the [StaticBody3D] is +## **removed from the physics simulation** +## (default [member CollisionObject3D.disable_mode] is [code]REMOVE[/code]). ## Layer/shape tweaks alone left a **Jolt ghost collider** (invisible wall) in practice. ## When hidden, reparents `Obstacle` under [member _world]: [NavigationMesh] source geometry scans ## [NavigationRegion3D] children only ([code]SOURCE_GEOMETRY_ROOT_NODE_CHILDREN[/code]), and the ## default [code]geometry_parsed_geometry_type[/code] is [code]PARSED_GEOMETRY_BOTH[/code], so a -## child [MeshInstance3D] still carved a hole even with collision disabled — rebake alone was not enough. +## child [MeshInstance3D] still carved a hole even with collision disabled — rebake alone was not +## enough. ## After each toggle, rebakes (main thread) and syncs the player [NavigationAgent3D]. ## **F9** / **F10** are debugger shortcuts in the embedded game; use the Input Map action. ## For a true **freed-node** occluder test, reload the scene or remove the body in the editor. diff --git a/client/scripts/position_authority_client.gd b/client/scripts/position_authority_client.gd index 1807311..3166a57 100644 --- a/client/scripts/position_authority_client.gd +++ b/client/scripts/position_authority_client.gd @@ -99,9 +99,7 @@ func _on_request_completed( _result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray ) -> void: if _result != HTTPRequest.RESULT_SUCCESS: - push_warning( - "PositionAuthorityClient: HTTP failed (result=%s); releasing busy." % _result - ) + push_warning("PositionAuthorityClient: HTTP failed (result=%s); releasing busy." % _result) _busy = false _try_flush_pending_move() return diff --git a/scripts/git-hooks/pre-push b/scripts/git-hooks/pre-push index aab61d4..9bf2009 100755 --- a/scripts/git-hooks/pre-push +++ b/scripts/git-hooks/pre-push @@ -9,12 +9,16 @@ gdformat_bin="" if [[ -x "$repo_root/.venv-gd/bin/gdlint" ]]; then gdlint_bin="$repo_root/.venv-gd/bin/gdlint" +elif [[ -x "$repo_root/.venv-gd/Scripts/gdlint.exe" ]]; then + gdlint_bin="$repo_root/.venv-gd/Scripts/gdlint.exe" elif command -v gdlint >/dev/null 2>&1; then gdlint_bin="$(command -v gdlint)" fi if [[ -x "$repo_root/.venv-gd/bin/gdformat" ]]; then gdformat_bin="$repo_root/.venv-gd/bin/gdformat" +elif [[ -x "$repo_root/.venv-gd/Scripts/gdformat.exe" ]]; then + gdformat_bin="$repo_root/.venv-gd/Scripts/gdformat.exe" elif command -v gdformat >/dev/null 2>&1; then gdformat_bin="$(command -v gdformat)" fi diff --git a/scripts/install-git-hooks.ps1 b/scripts/install-git-hooks.ps1 new file mode 100644 index 0000000..d1d86e6 --- /dev/null +++ b/scripts/install-git-hooks.ps1 @@ -0,0 +1,19 @@ +# Installs repo pre-push hook (gdlint + gdformat). Same behavior as install-git-hooks.sh. +# Run from repo root: pwsh -File scripts/install-git-hooks.ps1 +$ErrorActionPreference = "Stop" +$repoRoot = (git rev-parse --show-toplevel).Trim() +$hookDir = Join-Path $repoRoot ".git/hooks" +$hookPath = Join-Path $hookDir "pre-push" +New-Item -ItemType Directory -Force -Path $hookDir | Out-Null +$unixRoot = $repoRoot -replace "\\", "/" +$lines = @( + "#!/bin/sh", + "set -e", + "repo_root=`"$unixRoot`"", + 'cd "$repo_root" || exit 1', + 'exec sh "$repo_root/scripts/git-hooks/pre-push" "$@"' +) +$content = ($lines -join "`n") + "`n" +$utf8 = New-Object System.Text.UTF8Encoding $false +[System.IO.File]::WriteAllText($hookPath, $content, $utf8) +Write-Host "Installed pre-push hook at $hookPath"