Files
stickman/docs/archive/plans/PHASE_4c_AREA.md

177 lines
6.8 KiB
Markdown

Phase 4.4 Diagnostic — Trigger Areas Not Firing
markdown
# Phase 4.4 Diagnostic — Trigger Areas Not Firing
## 1. Overview
The AGENTS.md documentation claims `_update_area_entry()` exists and evaluates `entered_area` rules, but the user reports **nothing happens when a rule is applied to an area**. This plan is a diagnostic + fix procedure to isolate and repair the break.
## 2. Suspected Root Causes (Ranked)
| # | Suspected Cause | Symptom |
| --- | ---------------------------------------------------------- | ------------------------------------------ |
| 1 | `_update_area_entry()` never called in PLAY | No events fire at all |
| 2 | Rule's `trigger.target` reference goes stale (bad weakref) | Event fires but action target invalid |
| 3 | Edge-trigger dictionary uses wrong key | Event fires once, then never re-fires |
| 4 | Area rect coordinates wrong (local vs. global) | Stickman visually inside, geometry says no |
| 5 | `TriggerArea` not a child of `World` | Detection loop never sees the area |
| 6 | Rule builder doesn't actually save `entered_area` rules | Rule appears created, `_event_rules` empty |
| 7 | Stickman's feet anchor wrong | Uses bone position instead of feet |
## 3. Diagnostic Procedure (Do First)
Ask the developer to add **4 temporary debug prints** and run a test in PLAY mode. Report which prints appear.
### Print 1 — Top of `_update_area_entry()`
```gdscript
print("[AREA] checking ", _world.get_child_count(), " world children")
```
If this never prints: \_update_area_entry() is not called from \_physics_process in PLAY. Check the call site.
Print 2 — Before rule matching
```gdscript
print("[AREA] area=", area.name, " rect=", area.get_area_rect(), " movable=", movable.name, " pos=", pos)
```
If this never prints: the detection loop isn't iterating areas, or the area isn't a TriggerArea child of World.
Print 3 — When a rule matches
```gdscript
print("[RULE] matched rule id=", rule.id, " for area=", area.name)
```
If this never prints but Print 2 does: the rule's trigger.target.get_ref() doesn't equal area. Reference is stale or wrong type.
Print 4 — Before action executes
```gdscript
print("[ACTION] executing ", action.type, " on target=", action.target.get_ref())
```
If this never prints but Print 3 does: the rule matched but actions array is empty, or execution is guarded out.
4. Fix Plan (Per Root Cause)
Fix 1 — Missing \_physics_process Call
The event engine must run only in PLAY mode:
```gdscript
func _physics_process(delta: float) -> void:
if current_mode != StageMode.PLAY:
return
_update_area_entry()
_update_stickman_prop_collision()
```
Verify the call exists and is guarded by the PLAY check.
Fix 2 — Stale trigger.target Reference
Rules must store a live weakref to the TriggerArea node, not a serialized position or instance ID. At rule creation:
```gdscript
rule.trigger.target = weakref(area_node)
```
At evaluation:
```gdscript
var target = rule.trigger.target.get_ref()
if target == null:
continue # skip stale rule
if target != area:
continue
```
If the reference is stored as anything else, rewrite the rule builder to use weakref().
Fix 3 — Wrong Edge-Trigger Key
The edge-trigger dictionary must key on BOTH the area ID and the movable ID:
```gdscript
var edge_key := str(area.get_instance_id()) + "_" + str(movable.get_instance_id())
if _area_fired_events.has(edge_key):
continue
_area_fired_events[edge_key] = true
# fire rules...
```
Using only the area ID means only one stickman can fire the event. Using only the movable ID means the same stickman can't trigger two different areas.
Fix 4 — Wrong Area Coordinates
TriggerArea.get_area_rect() must return a world-space Rect2:
```gdscript
func get_area_rect() -> Rect2:
return Rect2(global_position - size * 0.5, size)
```
Do not use position (local) — the area may be a child of a moved parent.
For the movable's position, use the stickman's feet:
```gdscript
var pos := stickman.global_position + StickmanRig.FOOT_OFFSET
```
Fix 5 — Area Not in World
The palette spawner must add the TriggerArea as a direct child of World, not \_ghost_holder or any other intermediate node. The spawner's commit path (\_place_at) should call:
```gdscript
_world.add_child(area)
```
Verify the placement path — the ghost is spawned first (into \_ghost_holder), then the actual object is added to World on commit. If the commit path is broken for areas specifically, fix \_spawn_area().
Fix 6 — Rule Builder Doesn't Save entered_area
The "⚡ When..." flow must actually append a rule to \_event_rules with:
```gdscript
{
"id": rule_id,
"trigger": {
"type": "entered_area",
"target": weakref(area_node),
},
"actions": [ ... ]
}
```
Add a debug print in \_finalize_rule() to verify the rule is committed with the correct shape.
Fix 7 — Wrong Feet Anchor
Use stickman.global_position + StickmanRig.FOOT_OFFSET for the "position" tested against the area rect. Do not use bone positions or the rig root alone.
5. Acceptance Criteria
- [ ] Placing an Area via the palette creates a TriggerArea in World.
- [ ] A rule with an entered_area trigger can be created (targeting a specific area).
- [ ] Walking a stickman into the area in PLAY fires the rule.
- [ ] The rule's action executes on the correct target.
- [ ] The event fires once per entry (edge-triggered).
- [ ] Leaving and re-entering the area re-fires the event.
- [ ] Multiple areas + multiple stickmen work independently.
- [ ] No console warnings/errors when Play starts.
6. Test Cases
| # | Test | Expected |
| --- | ---------------------------------------------- | --------------------------- |
| 1 | Stickman walks into area speaks | Speech bubble appears once |
| 2 | Stickman walks out and back in speaks again | Speech bubble appears twice |
| 3 | Stickman stands still in area for 5s no repeat | Speech bubble appears once |
| 4 | Two stickmen enter same area each fires | Two events fire |
| 5 | Stickman enters area with no rule | Nothing happens, no errors |
| 6 | Rule targets deleted area | No crash warning logged |
| 7 | Area moved in Edit | Play New position used |
| 8 | 5 areas + 5 stickmen + 5 rules | All fire independently |
7. Deliverable
A fix commit that resolves whichever of the 7 root causes the 4 debug prints isolate. Include a brief note in the commit message describing which root cause was found and what was changed.
8. Out of Scope
New trigger types (e.g., exited_area, stay_in_area).