From 72897a49242821316665ba8d3b257d94f0b376dc Mon Sep 17 00:00:00 2001 From: VinPropane Date: Mon, 6 Apr 2026 00:41:32 -0400 Subject: [PATCH] NS-24: Fix GDScript lint (line length, gdlint, gdformat) - Shorten file-header and doc comments to satisfy max-line-length. - Rename bump collision preload const to BUMP_COLLISION_CONSTS_SCRIPT. - Refactor idle floor-angle and walk-step logic for return/format rules. --- client/scripts/main.gd | 3 +- client/scripts/player.gd | 107 +++++++----------- .../random_floor_bump_collision_constants.gd | 8 +- client/scripts/random_floor_bumps.gd | 30 ++--- 4 files changed, 61 insertions(+), 87 deletions(-) diff --git a/client/scripts/main.gd b/client/scripts/main.gd index 4939620..de0aa5e 100644 --- a/client/scripts/main.gd +++ b/client/scripts/main.gd @@ -3,7 +3,8 @@ extends Node3D ## NS-16: composes ground pick + server authority; see `ground_pick.gd` / ## `position_authority_client.gd`. NS-18: interaction POST + radius glow preview (see child nodes). ## NS-23: bakes `NavigationRegion3D` on startup; boot snap vs nav goal from authority signal. -## Prototype: two **random short bumps** (sibling `StaticBody3D` under `NavigationRegion3D`; see `random_floor_bumps.gd`) **before** nav bake. +## Prototype: two random short bumps (sibling StaticBody3D under NavigationRegion3D; see +## `random_floor_bumps.gd`) before nav bake. const MOVE_REJECT_MSG_SECONDS: float = 4.0 diff --git a/client/scripts/player.gd b/client/scripts/player.gd index c480ba3..d49d6b0 100644 --- a/client/scripts/player.gd +++ b/client/scripts/player.gd @@ -1,66 +1,42 @@ extends CharacterBody3D -## NS-23: Follow server move target on walkable geometry. -## NS-24: Idle — **`velocity = 0`**, **`floor_snap_length = FLOOR_SNAP_IDLE`** (~**11 cm**), **`move_and_slide()`** -## only. (Post-slide **ray Y lerp** was removed: on bump **rims** hit counts / spread can cross thresholds -## frame-to-frame and **fight** the solver.) Walking snap **`FLOOR_SNAP_MOVING`**. **`snap_to_server`** calls -## **`reset_physics_interpolation()`** when **`physics/common/physics_interpolation`** is on. -## **`project.godot`:** **`physics/3d/physics_engine`** = **Jolt Physics**; **`physics/common/physics_interpolation`** -## = **off** — when **on**, micro **position** changes on bump **rims** blend across frames and read as **ghosting / jitter**. -## **`Player`** under **`World`**. -## **Rendering (main.tscn / project):** curved default capsule + directional light → **specular highlight** -## and **floor z-fight** often read as “jitter” even when `global_position` is stable — player uses -## **no-specular** material, capsule mesh **Y** lift vs **`CapsuleShape3D`** (**z-fight** vs walkables), **no cast shadow** on mesh, **MSAA 3D**, **`light_specular=0`**. -## **Timing:** **120** physics TPS. Jitter that vanishes at **2× run speed** points at **render vs physics** cadence. +## NS-23 path-follow; NS-24 idle. Details in `client/README.md` (movement, idle). ## -## Horizontal velocity toward nav waypoint or goal only; `velocity.y` stays 0 while walking. -## Move **legality** is server-only. -## -## Nav detours except when goal Y is below us: then xz toward goal (waypoints sit under bump rims). -## -## **Upright + floor angle (NS-24):** **`_snap_capsule_upright()`** each tick after motion — **`Transform3D(Basis.IDENTITY, pos)`** -## so the capsule cannot **bank** on rim contacts. **`floor_max_angle`** is **looser while walking**; **stricter when idle** on -## flat floor; **`_floor_angle_loose_ticks`** after a walk **stops**. **Rim / straddle idle:** **moving** `floor_max_angle` when -## **`get_floor_normal()`** is **shallow** or **`_idle_ridged_floor_contacts()`** (slab + cylinder **edge**). After idle slide, -## **`_maybe_idle_rim_settle_nudge()`** + **`_maybe_idle_bump_proximity_escape()`** (uses **`random_floor_bump_mesh`** group mesh radius vs -## **`random_floor_bump_collision_constants.gd`** collision lip, including **cylinder wall** hug). Do **not** reset **`global_transform`** in **`_process`** — that can **desync** -## **`CharacterBody3D`** from the physics/render path and read as **ghosting**. **`NavigationAgent3D.avoidance_enabled`** = **false**. -## **`_try_walk_step_assist()`** — **Y nudge** when the nav goal is **higher**, **`test_move`** says there is **headroom**, -## we have **support** (floor or slide normal with upward component), a **wall-ish** slide opposes **XZ** toward the goal, -## and **horizontal motion** is **not** clearly advancing. A **second** **`move_and_slide()`** runs after a nudge to fix overlap -## (avoids “floating” off the bump edge). +## Summary: Jolt, no interp; dual floor_max_angle; rim/straddle, bump lip escape. +## Snap upright after motion. Do not set global_transform in _process. +## Walk assist may run a second move_and_slide(). const MOVE_SPEED: float = 5.0 -## `is_on_floor` / snap: **moving** — seam / curved-side transitions onto NS-19 bumps (~original 50°). +## Moving: floor angle for seams onto stepped QA bumps (~50°). const FLOOR_MAX_ANGLE_MOVING_DEG: float = 50.0 -## **Idle** — tighter to reject **vertex / vertical-face** flicker when stably at rest. +## Idle: tighter angle to reduce vertex / vertical-face flicker (~35°). const FLOOR_MAX_ANGLE_IDLE_DEG: float = 35.0 -## Physics ticks to keep **moving** floor angle after **`_has_walk_goal`** becomes false (arrival / clear). +## Physics ticks to keep moving floor angle after walk goal clears (arrival). const FLOOR_ANGLE_LOOSE_TICKS_AFTER_STOP: int = 96 const ARRIVE_EPS: float = 0.35 const VERT_ARRIVE_EPS: float = 0.055 const DIRECT_APPROACH_RADIUS: float = 0.85 const FLOOR_SNAP_MOVING: float = 0.32 -## Idle snap — slightly **strong** to pin capsule on **rim** contacts without a second slide pass. +## Idle floor snap length (stronger to pin rim contacts). const FLOOR_SNAP_IDLE: float = 0.11 -## Idle on a **rim**: if **`get_floor_normal().y`** dot world up is **below** this, use **moving** `floor_max_angle` for that tick. +## Below this floor-normal dot up, idle tick uses **moving** `floor_max_angle` (rim). const IDLE_RIM_MIN_FLOOR_UP_DOT: float = 0.968 -## Per-tick horizontal pull when idle on a **rim** or **straddling** slab + bump (see **`_maybe_idle_rim_settle_nudge`**). +## Horizontal nudge per tick for rim / straddle settle (**`_maybe_idle_rim_settle_nudge`**). const IDLE_RIM_SETTLE_STEP: float = 0.004 const DESCEND_GOAL_Y_MARGIN: float = 0.06 -## Per-tick lift when **stuck** on a low vertical face while the nav goal is **higher** (NS-19 bump height ~0.15 m). +## Y lift when blocked by wall-ish slide but nav goal is higher (stepped bumps). const WALK_STEP_ASSIST_DELTA: float = 0.11 -## If **`vel_h.dot(want) >` this**, we are making enough XZ progress — do not assist (corners may still slide slowly). +## Skip assist when horizontal velocity already aligns enough with want-dir (corners slide slowly). const WALK_STEP_ASSIST_MAX_FORWARD_DOT: float = 0.52 const WALK_STEP_ASSIST_COOLDOWN_TICKS: int = 8 -## Capsule **`height`** in **`main.tscn`** (**1.0**) → origin at **center**; feet ≈ **`y - 0.5`**. +## Capsule height 1.0 in scene: origin at center, feet ~ **`y - 0.5`**. const CAPSULE_HALF_HEIGHT: float = 0.5 -const _random_floor_bump_collision: Script = preload( +const BUMP_COLLISION_CONSTS_SCRIPT: Script = preload( "res://scripts/random_floor_bump_collision_constants.gd" ) -## Idle-only radial push off bump **lip** / **wall** (m/tick at **120** TPS). +## Idle radial push per tick off bump lip / wall (meters; 120 Hz physics). const IDLE_BUMP_ESCAPE_STEP: float = 0.014 -## Player **`CapsuleShape3D.radius`** in **`main.tscn`** — wall contact can occur with axis distance **>** **`col_r`**. +## Player capsule radius; wall touch can happen past axis distance **`col_r`**. const PLAYER_CAPSULE_RADIUS: float = 0.4 var _has_walk_goal: bool = false @@ -138,9 +114,7 @@ func _step_assist_can_raise_by(dy: float) -> bool: func _try_walk_step_assist() -> bool: - if not _has_walk_goal: - return false - if _auth_walk_goal.y < global_position.y + 0.025: + if not _has_walk_goal or _auth_walk_goal.y < global_position.y + 0.025: return false if not _step_assist_has_support(): return false @@ -149,16 +123,12 @@ func _try_walk_step_assist() -> bool: if to_h.length_squared() < 0.03: return false var want: Vector3 = to_h.normalized() - if not _step_assist_wallish_blocks(want): - return false var vel_h: Vector3 = Vector3(velocity.x, 0.0, velocity.z) - if vel_h.dot(want) > WALK_STEP_ASSIST_MAX_FORWARD_DOT: + if not _step_assist_wallish_blocks(want) or vel_h.dot(want) > WALK_STEP_ASSIST_MAX_FORWARD_DOT: return false var lift: float = minf(WALK_STEP_ASSIST_DELTA, _auth_walk_goal.y - global_position.y + 0.45) lift = minf(lift, maxf(0.0, _auth_walk_goal.y + 0.55 - global_position.y)) - if lift <= 0.002: - return false - if not _step_assist_can_raise_by(lift): + if lift <= 0.002 or not _step_assist_can_raise_by(lift): return false global_position.y += lift return true @@ -206,8 +176,7 @@ func _maybe_idle_rim_settle_nudge() -> void: if get_slide_collision_count() < 1: return var shallow_floor: bool = ( - is_on_floor() - and get_floor_normal().dot(Vector3.UP) < IDLE_RIM_MIN_FLOOR_UP_DOT + is_on_floor() and get_floor_normal().dot(Vector3.UP) < IDLE_RIM_MIN_FLOOR_UP_DOT ) if not shallow_floor and not _idle_ridged_floor_contacts(): return @@ -232,7 +201,7 @@ func _maybe_idle_rim_settle_nudge() -> void: func _maybe_idle_bump_proximity_escape() -> void: var feet_y: float = global_position.y - CAPSULE_HALF_HEIGHT for node: Node in get_tree().get_nodes_in_group( - _random_floor_bump_collision.RANDOM_FLOOR_BUMP_MESH_GROUP + BUMP_COLLISION_CONSTS_SCRIPT.RANDOM_FLOOR_BUMP_MESH_GROUP ): if not node is MeshInstance3D: continue @@ -245,8 +214,8 @@ func _maybe_idle_bump_proximity_escape() -> void: var r_mesh: float = cyl.top_radius var h: float = cyl.height var col_r: float = minf( - r_mesh + _random_floor_bump_collision.COLLISION_RADIUS_EXTRA, - _random_floor_bump_collision.COLLISION_RADIUS_MAX + r_mesh + BUMP_COLLISION_CONSTS_SCRIPT.COLLISION_RADIUS_EXTRA, + BUMP_COLLISION_CONSTS_SCRIPT.COLLISION_RADIUS_MAX ) var dx: float = global_position.x - c.x var dz: float = global_position.z - c.z @@ -266,11 +235,16 @@ func _maybe_idle_bump_proximity_escape() -> void: global_position += away return # On / above the disc, hugging the **visual** rim — step onto open slab. - if feet_y >= bottom_y - 0.06 and feet_y <= top_y + 0.1 and d >= r_mesh * 0.86 and d <= r_mesh + 0.14: + if ( + feet_y >= bottom_y - 0.06 + and feet_y <= top_y + 0.1 + and d >= r_mesh * 0.86 + and d <= r_mesh + 0.14 + ): if not test_move(global_transform, away, null, safe_margin): global_position += away return - # Floor beside **vertical** cylinder side: axis distance can exceed **`col_r`** while capsule still touches wall. + # Beside vertical cylinder: axis distance may exceed **`col_r`** while capsule still touches wall. if ( feet_y <= top_y + 0.14 and d > r_mesh * 0.82 @@ -283,24 +257,19 @@ func _maybe_idle_bump_proximity_escape() -> void: func _snap_capsule_upright() -> void: - # TODO(NS-24 follow-on): When the avatar has **facing yaw**, clear **pitch/roll** only — keep **Y** rotation; - # full **`Basis.IDENTITY`** would erase look direction. + # TODO(NS-24 follow-on): With facing yaw, clear pitch/roll only; keep Y rotation. + # Full **`Basis.IDENTITY`** would erase look direction. var p: Vector3 = global_position global_transform = Transform3D(Basis.IDENTITY, p) func _physics_process(_delta: float) -> void: - var use_loose_floor_angle: bool = ( - _has_walk_goal or _floor_angle_loose_ticks > 0 - ) - if not _has_walk_goal and ( - ( - is_on_floor() - and get_floor_normal().dot(Vector3.UP) < IDLE_RIM_MIN_FLOOR_UP_DOT - ) - or _idle_ridged_floor_contacts() - ): - use_loose_floor_angle = true + var use_loose_floor_angle: bool = _has_walk_goal or _floor_angle_loose_ticks > 0 + if not _has_walk_goal: + var floor_up_dot: float = get_floor_normal().dot(Vector3.UP) + var shallow_idle_floor: bool = is_on_floor() and floor_up_dot < IDLE_RIM_MIN_FLOOR_UP_DOT + if shallow_idle_floor or _idle_ridged_floor_contacts(): + use_loose_floor_angle = true floor_max_angle = deg_to_rad( FLOOR_MAX_ANGLE_MOVING_DEG if use_loose_floor_angle else FLOOR_MAX_ANGLE_IDLE_DEG ) diff --git a/client/scripts/random_floor_bump_collision_constants.gd b/client/scripts/random_floor_bump_collision_constants.gd index 2e9ad4b..bf9b758 100644 --- a/client/scripts/random_floor_bump_collision_constants.gd +++ b/client/scripts/random_floor_bump_collision_constants.gd @@ -1,10 +1,10 @@ extends RefCounted -## Shared **`CylinderShape3D.radius`** fudge vs mesh for procedural **random floor QA bumps** (`random_floor_bumps.gd`). -## Used by **`player.gd`** (idle lip escape). Change here only. -## (Movement/step validation UX was originally tracked in Jira **NS-19** — not part of this script’s name.) +## CylinderShape3D.radius fudge vs mesh for procedural QA bumps (spawned in random_floor_bumps.gd). +## Used by `player.gd` (idle lip escape). Change here only. +## (NS-19 tracked movement/step UX; unrelated to this script name.) -## **`MeshInstance3D.add_to_group`** for spawned bumps; **`player.gd`** queries this for proximity escape. +## Group name for spawned bump meshes; `player.gd` queries it for proximity escape. const RANDOM_FLOOR_BUMP_MESH_GROUP: String = "random_floor_bump_mesh" const COLLISION_RADIUS_EXTRA: float = 0.105 diff --git a/client/scripts/random_floor_bumps.gd b/client/scripts/random_floor_bumps.gd index d1c68a8..9b249dc 100644 --- a/client/scripts/random_floor_bumps.gd +++ b/client/scripts/random_floor_bumps.gd @@ -1,11 +1,12 @@ extends RefCounted -## Prototype QA: two short **cylinder** bumps, each on its own **`StaticBody3D`** **sibling** of **`Floor`** (same parent: -## **`NavigationRegion3D`**). Avoids **compound internal edges** (floor box + cylinder on one body), which caused strong -## idle jitter beside the bump. Meshes use group **`random_floor_bump_mesh`** (see **`random_floor_bump_collision_constants.gd`**). Loaded via **`main.gd`** **`load().call`**. -## **Collision radius > mesh radius** (lip before visual rim); values in **`random_floor_bump_collision_constants.gd`**. Spawn **before** `NavigationRegion3D.bake_navigation_mesh()`. +## Prototype QA: two short bumps on StaticBody3D siblings of Floor (same NavigationRegion3D parent). +## Avoids one body with floor + cylinder (compound internal edges caused idle jitter beside bumps). +## Bump mesh group and collision fudge: random_floor_bump_collision_constants.gd. +## Spawner called from main.gd before nav bake. +## Collision radius > mesh radius; spawn before `NavigationRegion3D.bake_navigation_mesh()`. -const _random_floor_bump_collision: Script = preload( +const BUMP_COLLISION_CONSTS_SCRIPT: Script = preload( "res://scripts/random_floor_bump_collision_constants.gd" ) @@ -20,6 +21,7 @@ const MIN_PAIR_SEPARATION: float = 2.85 const MAX_PLACE_ATTEMPTS: int = 64 const EXCLUSION_MARGIN: float = 0.72 + static func spawn_short_random_bumps(floor_node: StaticBody3D) -> void: var rng := RandomNumberGenerator.new() rng.randomize() @@ -34,10 +36,7 @@ static func spawn_short_random_bumps(floor_node: StaticBody3D) -> void: var xz: Vector2 = Vector2.ZERO var ok := false for _a: int in MAX_PLACE_ATTEMPTS: - xz = Vector2( - rng.randf_range(-XZ_RANGE, XZ_RANGE), - rng.randf_range(-XZ_RANGE, XZ_RANGE) - ) + xz = Vector2(rng.randf_range(-XZ_RANGE, XZ_RANGE), rng.randf_range(-XZ_RANGE, XZ_RANGE)) if _xz_in_exclusion(xz): continue var far_enough := true @@ -50,7 +49,12 @@ static func spawn_short_random_bumps(floor_node: StaticBody3D) -> void: ok = true break if not ok: - push_warning("RandomFloorBumps: could not place bump %d after %d tries" % [idx, MAX_PLACE_ATTEMPTS]) + push_warning( + ( + "RandomFloorBumps: could not place bump %d after %d tries" + % [idx, MAX_PLACE_ATTEMPTS] + ) + ) continue placed_xz.append(xz) var h: float = rng.randf_range(HEIGHT_MIN, HEIGHT_MAX) @@ -99,7 +103,7 @@ static func _add_cylinder_bump( var mesh_inst := MeshInstance3D.new() mesh_inst.name = "RandomTestBump%d_mesh" % idx - mesh_inst.add_to_group(_random_floor_bump_collision.RANDOM_FLOOR_BUMP_MESH_GROUP) + mesh_inst.add_to_group(BUMP_COLLISION_CONSTS_SCRIPT.RANDOM_FLOOR_BUMP_MESH_GROUP) var cm := CylinderMesh.new() cm.height = height cm.top_radius = radius @@ -115,8 +119,8 @@ static func _add_cylinder_bump( var csh := CylinderShape3D.new() csh.height = height csh.radius = minf( - radius + _random_floor_bump_collision.COLLISION_RADIUS_EXTRA, - _random_floor_bump_collision.COLLISION_RADIUS_MAX + radius + BUMP_COLLISION_CONSTS_SCRIPT.COLLISION_RADIUS_EXTRA, + BUMP_COLLISION_CONSTS_SCRIPT.COLLISION_RADIUS_MAX ) col.shape = csh col.position = Vector3.ZERO