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.
pull/38/head
VinPropane 2026-04-11 00:01:25 -04:00
parent 398317d64f
commit d1bd20582a
6 changed files with 53 additions and 19 deletions

View File

@ -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 repos local **pre-push** GDScript lint hook from the repo root:
**Git hook (recommended):** install the repos 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.

View File

@ -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()

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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"