NEON-28: drop freed occluder keys without material restore
parent
e225f1c550
commit
b702b14be4
|
|
@ -238,20 +238,28 @@ static func occluder_override_key_is_valid(body: Variant) -> bool:
|
|||
|
||||
func _restore_occluder_overrides_list(overrides: Array) -> void:
|
||||
for entry in overrides:
|
||||
var mi: MeshInstance3D = entry["mesh"]
|
||||
if is_instance_valid(mi):
|
||||
mi.set_surface_override_material(entry["surface"], entry["original"])
|
||||
var mi = entry.get("mesh")
|
||||
if not is_instance_valid(mi) or not mi is MeshInstance3D:
|
||||
continue
|
||||
var s: int = int(entry.get("surface", -1))
|
||||
if s < 0:
|
||||
continue
|
||||
var original: Variant = entry.get("original")
|
||||
if original != null:
|
||||
if typeof(original) != TYPE_OBJECT or not is_instance_valid(original):
|
||||
continue
|
||||
(mi as MeshInstance3D).set_surface_override_material(s, original)
|
||||
|
||||
|
||||
## Drop invalid keys from [member _occluder_overrides] and restore any surviving meshes.
|
||||
## Drop invalid keys from [member _occluder_overrides] without touching meshes.
|
||||
## When the occluder [Node3D] is freed, its [MeshInstance3D] children and materials are gone or
|
||||
## tearing down — restoring overrides here triggers "freed instance" errors. Erasing the entry is
|
||||
## enough; live bodies stay on the valid path ([method _restore_occluder]).
|
||||
func _purge_invalid_occluder_override_keys() -> void:
|
||||
var keys: Array = _occluder_overrides.keys()
|
||||
for body in keys:
|
||||
if occluder_override_key_is_valid(body):
|
||||
continue
|
||||
var overrides: Variant = _occluder_overrides.get(body)
|
||||
if overrides != null:
|
||||
_restore_occluder_overrides_list(overrides)
|
||||
_occluder_overrides.erase(body)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ See Epic 1 **Slice 2 — Locked isometric camera**: follow-center, zoom bands, o
|
|||
|
||||
- **Click-through input (NEON-30):** `scripts/ground_pick.gd` unconditionally skips bodies in the `"occluder"` group during click-to-move ground-pick raycasts. When a cast hits an occluder the ray origin advances `OCCLUDER_PICK_THROUGH` meters past the hit point and continues, independent of whether `OcclusionPolicy` is currently fading that body. This keeps the `"occluder"` group as the sole tagging convention shared between the camera occlusion system and ground-pick input — no dedicated collision layer is added for this case.
|
||||
|
||||
- **Integration hardening (NEON-28):** [Consumer contract](#consumer-contract-neon-28) + `camera_state.gd` header; **E6.M2** adjacency notes ([E6_M2_ConsentAndRiskUxSignals.md](E6_M2_ConsentAndRiskUxSignals.md)); invalid / freed occluder bodies are **purged** from `_occluder_overrides` before each occlusion ray pass and before full restore (`occluder_override_key_is_valid`, tests in `isometric_follow_camera_test.gd`). **Follow / state / occlusion** run in **`_physics_process`** with **`process_physics_priority = 1`** so the rig updates **after** `Player` (default **0**) and tracks **`move_and_slide`** without display-vs-physics jitter.
|
||||
- **Integration hardening (NEON-28):** [Consumer contract](#consumer-contract-neon-28) + `camera_state.gd` header; **E6.M2** adjacency notes ([E6_M2_ConsentAndRiskUxSignals.md](E6_M2_ConsentAndRiskUxSignals.md)); invalid / freed occluder bodies are **dropped** from `_occluder_overrides` before each occlusion ray pass and before full restore (`occluder_override_key_is_valid`, tests in `isometric_follow_camera_test.gd`) — **no** material restore on that path (freed subtree would error); live bodies still restore via `_restore_occluder` when they leave the ray. **Follow / state / occlusion** run in **`_physics_process`** with **`process_physics_priority = 1`** so the rig updates **after** `Player` (default **0**) and tracks **`move_and_slide`** without display-vs-physics jitter.
|
||||
|
||||
## Jira backlog
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
**Additional in-repo hardening (engineering, supports “stable contracts”)**
|
||||
|
||||
- **`IsometricFollowCamera` occluder lifecycle:** `_occluder_overrides` must not **leak or fault** when occluder bodies are **freed** or invalid; purge invalid keys and restore surviving `MeshInstance3D` overrides.
|
||||
- **`IsometricFollowCamera` occluder lifecycle:** `_occluder_overrides` must not **leak or fault** when occluder bodies are **freed** or invalid; **erase** stale dict keys without calling `set_surface_override_material` on a freed subtree (that errors); live bodies still restore when they leave the ray.
|
||||
|
||||
**Out of scope (from Jira)**
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ Jira acceptance criteria (verbatim intent):
|
|||
- Reconcile code and docs. Add **policy flags** to `CameraState` only if the E6.M2 / E1.M2 contract text commits to them; otherwise explicitly document **“no policy flags in state yet; read rig exports.”**
|
||||
|
||||
3. **`isometric_follow_camera.gd` hardening**
|
||||
- At start of `_update_occlusion` (and/or restore passes): drop dictionary keys whose body is **`not is_instance_valid`**; restore materials where `MeshInstance3D` is still valid; then erase entries.
|
||||
- At start of `_update_occlusion` (and/or restore passes): **erase** dictionary keys whose body is **`not is_instance_valid`** (do **not** restore materials on that path — freed occluder’s meshes are gone). Harden `_restore_occluder_overrides_list` with `is_instance_valid` on mesh and non-null `original` for the live-body restore path.
|
||||
|
||||
4. **Optional telemetry**
|
||||
- Keep or add **TODO(E9.M1)** pointers in rig / policy scripts; no new telemetry pipeline unless out of scope exception is agreed.
|
||||
|
|
@ -76,7 +76,7 @@ Jira acceptance criteria (verbatim intent):
|
|||
| Topic | Choice | Rationale |
|
||||
|-------|--------|-----------|
|
||||
| **`CameraState` policy flags** | **Doc-first** | Add fields only if Jira/E6.M2 contract text requires a snapshot; avoid speculative API. |
|
||||
| **Invalid occluder keys** | **Purge + restore** | Prevents stuck materials and leaks when occluders despawn. |
|
||||
| **Invalid occluder keys** | **Erase only** | Freed body ⇒ subtree is gone; restoring overrides hits freed `MeshInstance3D` / materials. Drop dict entry; live bodies restore when they leave the ray. |
|
||||
| **Epic Slice 2 wording** | **Align with rotation policy** | Satisfies Jira AC on “yaw not exposed / default in UX” vs misleading “no rotation.” |
|
||||
| **Rig tick (jitter follow-up)** | **`_physics_process` + `process_physics_priority = 1`** | `Player` default **0** runs before **1**; camera reads post–`move_and_slide`. Avoids `_process` vs 120 Hz physics jitter (camera was above `Player` in scene order). |
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue