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
parent
398317d64f
commit
d1bd20582a
|
|
@ -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.
|
**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
|
```bash
|
||||||
./scripts/install-git-hooks.sh
|
./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.
|
**Reports:** GdUnit writes under **`reports/`** (gitignored); ignore locally generated HTML/XML when committing.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ extends Node3D
|
||||||
## NEON-26: discrete zoom bands via [member zoom_band_config]; wheel / `camera_zoom_*` actions.
|
## 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]
|
## 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
|
## 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
|
## 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,
|
## [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].
|
## 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.
|
## 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).
|
## 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:
|
static func occluder_override_key_is_valid(body: Variant) -> bool:
|
||||||
if not is_instance_valid(body):
|
if not is_instance_valid(body):
|
||||||
return false
|
return false
|
||||||
|
|
@ -378,8 +379,9 @@ func _restore_all_occluders() -> void:
|
||||||
_restore_occluder_id(occ_id as int)
|
_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
|
## Public hook for [code]dev_toggle_occluder_obstacle[/code] smoke; [code]main.gd[/code] dev toggle.
|
||||||
## are still live**, then the caller may [method Node.queue_free] the occluder. Avoids any post-free
|
## Restore materials and clear [member _occluder_overrides] **while bodies are still live**,
|
||||||
## occlusion pass touching a tearing-down [StaticBody3D] (the semi-transparent faded box case).
|
## 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:
|
func restore_all_occluder_materials_now() -> void:
|
||||||
_restore_all_occluders()
|
_restore_all_occluders()
|
||||||
|
|
|
||||||
|
|
@ -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.
|
## Bump on each rejection so older one-shot timers do not clear a newer message.
|
||||||
var _move_reject_msg_token: int = 0
|
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_layer: int = -1
|
||||||
var _dev_saved_obstacle_mask: int = -1
|
var _dev_saved_obstacle_mask: int = -1
|
||||||
var _dev_saved_obstacle_process_mode: Node.ProcessMode = Node.PROCESS_MODE_INHERIT
|
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 _camera: Camera3D = $World/IsometricFollowCamera/Camera3D
|
||||||
@onready var _world: Node3D = $World
|
@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 _move_reject_label: Label = $UICanvas/MoveRejectLabel
|
||||||
@onready var _floor: StaticBody3D = $World/NavigationRegion3D/Floor
|
@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:
|
func _ready() -> void:
|
||||||
set_process_unhandled_key_input(true)
|
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**)
|
## Dev smoke (NEON-28 occluder): action [code]dev_toggle_occluder_obstacle[/code]
|
||||||
## toggles `Obstacle` **visibility + collision** — it does **not** call [method Node.queue_free].
|
## (default **Ctrl+Shift+K**). Toggles `Obstacle` **visibility + collision** —
|
||||||
## Uses [member Node.process_mode] [code]PROCESS_MODE_DISABLED[/code] so the [StaticBody3D] is **removed
|
## does not call [method Node.queue_free].
|
||||||
## from the physics simulation** (default [member CollisionObject3D.disable_mode] is [code]REMOVE[/code]).
|
## 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.
|
## Layer/shape tweaks alone left a **Jolt ghost collider** (invisible wall) in practice.
|
||||||
## When hidden, reparents `Obstacle` under [member _world]: [NavigationMesh] source geometry scans
|
## When hidden, reparents `Obstacle` under [member _world]: [NavigationMesh] source geometry scans
|
||||||
## [NavigationRegion3D] children only ([code]SOURCE_GEOMETRY_ROOT_NODE_CHILDREN[/code]), and the
|
## [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
|
## 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].
|
## 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.
|
## **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.
|
## For a true **freed-node** occluder test, reload the scene or remove the body in the editor.
|
||||||
|
|
|
||||||
|
|
@ -99,9 +99,7 @@ func _on_request_completed(
|
||||||
_result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray
|
_result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray
|
||||||
) -> void:
|
) -> void:
|
||||||
if _result != HTTPRequest.RESULT_SUCCESS:
|
if _result != HTTPRequest.RESULT_SUCCESS:
|
||||||
push_warning(
|
push_warning("PositionAuthorityClient: HTTP failed (result=%s); releasing busy." % _result)
|
||||||
"PositionAuthorityClient: HTTP failed (result=%s); releasing busy." % _result
|
|
||||||
)
|
|
||||||
_busy = false
|
_busy = false
|
||||||
_try_flush_pending_move()
|
_try_flush_pending_move()
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,16 @@ gdformat_bin=""
|
||||||
|
|
||||||
if [[ -x "$repo_root/.venv-gd/bin/gdlint" ]]; then
|
if [[ -x "$repo_root/.venv-gd/bin/gdlint" ]]; then
|
||||||
gdlint_bin="$repo_root/.venv-gd/bin/gdlint"
|
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
|
elif command -v gdlint >/dev/null 2>&1; then
|
||||||
gdlint_bin="$(command -v gdlint)"
|
gdlint_bin="$(command -v gdlint)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -x "$repo_root/.venv-gd/bin/gdformat" ]]; then
|
if [[ -x "$repo_root/.venv-gd/bin/gdformat" ]]; then
|
||||||
gdformat_bin="$repo_root/.venv-gd/bin/gdformat"
|
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
|
elif command -v gdformat >/dev/null 2>&1; then
|
||||||
gdformat_bin="$(command -v gdformat)"
|
gdformat_bin="$(command -v gdformat)"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
Loading…
Reference in New Issue