177 lines
6.8 KiB
Markdown
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).
|