- Added PropTrajectory class for deterministic ballistic calculations. - Introduced StickmanPropInteraction component to manage prop interactions (pickup, drop, throw). - Implemented grip resolution and attachment logic for props. - Added signals for interaction events (prop_picked_up, prop_dropped, prop_thrown). - Defined specifications for prop interaction lifecycle and event rules. - Ensured compatibility with existing prop systems and added new capabilities for prop pickup.
252 lines
13 KiB
Markdown
252 lines
13 KiB
Markdown
# Architecture Specification: Interactive Prop System & Event Engine
|
|
|
|
This specification details the technical architecture for bringing interactive prop capabilities—including auto-approach, IK socket binding, parabolic throwing, ragdoll disarming, and "When..." rule triggers—into the Sandbox Stage.
|
|
|
|
---
|
|
|
|
## 1. System Overview & Layer Interactions
|
|
|
|
Prop interactions bridge five core subsystems across the runtime stack:
|
|
|
|
```
|
|
[ Director UI / Target Selector ]
|
|
│
|
|
▼
|
|
[ StickmanActionRunner ] ──(Auto-Approach)──► [ StickmanLocomotion ]
|
|
│
|
|
▼
|
|
[ StickmanRig IK ] ──(Socket Binding)──► [ PropBlock Physics ]
|
|
│
|
|
▼
|
|
[ StageEventRegistry ] ──("When..." Rules)──► [ Reactive Actions ]
|
|
|
|
```
|
|
|
|
1. **Director UI & Target Selection:** Captures spatial throw targets and enqueues prop interaction commands.
|
|
|
|
2. **Action Runner & Locomotion:** Evaluates spatial reach; automatically inserts `walk_to` sub-goals when props are out of arm's reach.
|
|
|
|
3. **IK & Bone Socketing:** Extends arm IK targets toward prop `grip` anchors and manages `RemoteTransform2D` binding.
|
|
|
|
4. **Physics Lifecycle:** Unfreezes/freezes `PropBlock` bodies and inherits velocity during release, throw, or ragdoll disarm transitions.
|
|
|
|
5. **Event Registry:** Emits and evaluates edge-triggered prop rules within the stage's "When → Then" engine.
|
|
|
|
---
|
|
|
|
## 2. Dynamic Action Queue & Auto-Approach (`walk_to`)
|
|
|
|
To ensure smooth director interaction, the action queue automatically resolves spatial gaps between the stickman and target props.
|
|
|
|
### Action Payload Schemas
|
|
|
|
- **`pick_up_prop`**: `{ "type": "pick_up_prop", "target_prop_id": String, "hand": "auto" | "left" | "right" }`
|
|
|
|
- **`drop_prop`**: `{ "type": "drop_prop" }`
|
|
|
|
- **`throw_prop`**: `{ "type": "throw_prop", "target_position": Vector2, "force": float }`
|
|
|
|
### Smart Approach Execution
|
|
|
|
When `StickmanActionRunner` receives a `pick_up_prop` payload:
|
|
|
|
```
|
|
┌──────────────────────────────────────────────┐
|
|
│ pick_up_prop Action Start │
|
|
└──────────────────────┬───────────────────────┘
|
|
│
|
|
┌──────────────────────▼───────────────────────┐
|
|
│ Distance = |rig.position - prop.position| │
|
|
└──────────────────────┬───────────────────────┘
|
|
│
|
|
Is Distance > Reach Radius (e.g. 40px)?
|
|
┌───────────────────────┴───────────────────────┐
|
|
│ │
|
|
[ YES ] [ NO ]
|
|
│ │
|
|
┌─────────────────▼─────────────────┐ ┌─────────────────▼─────────────────┐
|
|
│ 1. Pause pick_up_prop sequence │ │ 1. Skip locomotion step │
|
|
│ 2. Prepend walk_to(prop_offset) │ │ 2. Trigger arm IK reach animation │
|
|
│ 3. Resume pickup on arrival │ └───────────────────────────────────┘
|
|
└───────────────────────────────────┘
|
|
|
|
```
|
|
|
|
1. **Distance Check:** Evaluates global horizontal/spatial distance between the stickman root and prop `origin` pivot.
|
|
|
|
2. **Sub-Goal Injection:** If outside reach radius, `StickmanActionRunner` pauses the pickup sequence and internally issues a `walk_to` command to a stand point adjacent to the prop.
|
|
|
|
3. **Sequence Resume:** Upon receiving `StickmanLocomotion.arrived_at_waypoint`, the runner resumes the pickup sequence and triggers arm IK extension.
|
|
|
|
---
|
|
|
|
## 3. Pickup, Drop, and Throw Pipelines
|
|
|
|
### A. Prop Pickup Sequence & IK Socketing
|
|
|
|
1. **IK Reach Phase (150 ms):** `TwoBoneIK` targets for the selected arm interpolate toward the world position of the prop's `grip` anchor.
|
|
|
|
2. **Socket Attachment:**
|
|
|
|
- Set `PropBlock.freeze = true` (kinematic mode) to suspend environmental physics forces.
|
|
|
|
- Instantiate or activate a `RemoteTransform2D` node attached to the lower arm / wrist joint, targeting the `PropBlock`.
|
|
|
|
- Apply anchor local offsets (`x, y, rotation_deg, z_index_offset`) to align the prop grip directly within the hand geometry.
|
|
|
|
3. **Holding Stance Latch:** Lock upper limb IK targets into a carried stance during subsequent walk cycles.
|
|
|
|
### B. Prop Drop Sequence
|
|
|
|
1. **Unbinding:** Clear `RemoteTransform2D` target and reparent `PropBlock` back to the stage world root.
|
|
|
|
2. **Physics Activation:** Set `PropBlock.freeze = false` to re-enable rigid body simulation, gravity, and collision shapes.
|
|
|
|
3. **Inertia Inheritance:** Set `PropBlock.linear_velocity` equal to the stickman's current movement velocity (`_last_grounded_walk_velocity_x`) so dropped items retain forward momentum.
|
|
|
|
4. **Arm Recovery:** Blend wrist IK targets smoothly back to standard locomotion/idle rest positions.
|
|
|
|
### C. Prop Throw Sequence & Trajectory Selection
|
|
|
|
#### Director Target UI Flow
|
|
|
|
- **Targeting Mode:** Selecting "Throw Prop" in Director/Play mode activates an aim reticle cursor and dynamic arc preview.
|
|
|
|
- **Trajectory Math:** Given hand release point $P_0$, target landing point $P_t$, base speed $v_0$, and gravity $g$:
|
|
|
|
$$\theta = \arctan\left(\frac{v_0^2 \pm \sqrt{v_0^4 - g(g \cdot x^2 + 2 y \cdot v_0^2)}}{g \cdot x}\right)$$
|
|
|
|
Calculates exact initial launch velocity components $V_{\text{launch}} = (v_0 \cos\theta, v_0 \sin\theta)$.
|
|
|
|
#### Keyframe Throw Animation Arc
|
|
|
|
```
|
|
[ Windup: 100ms ] ──► [ Release Frame: 150ms ] ──► [ Follow-Through: 50ms ]
|
|
- Torso tilts -15° - Arm sweeps to angle - Arm extends along arc
|
|
- Arm pulls backward - Prop unfreezes + impulse - Blends back to idle
|
|
|
|
```
|
|
|
|
1. **Windup (100 ms):** Torso rotates backward opposite the launch vector while the arm pulls back.
|
|
2. **Release Frame (150 ms):**
|
|
|
|
- Arm sweeps rapidly along the calculated launch angle $\theta$.
|
|
- Unbind `RemoteTransform2D` and reparent prop to world root.
|
|
|
|
- Set `PropBlock.freeze = false` and execute `apply_central_impulse(V_launch)`.
|
|
|
|
- Apply rotational torque proportional to the distance between hand grip and prop center-of-mass.
|
|
|
|
3. **Follow-Through (50 ms):** Arm extends toward launch vector, then smoothly dampens back to default idle/walking pose.
|
|
|
|
---
|
|
|
|
## 4. Physics & State Machine Lifecycle (Ragdoll Disarm Handoff)
|
|
|
|
When `StickmanRig.set_ragdoll(true)` is executed (via impact, command, or keypress), any held prop must be immediately disarmed before kinematic nodes are hidden and ragdoll bodies are built.
|
|
|
|
```
|
|
┌──────────────────────────────────────────────┐
|
|
│ StickmanRig.set_ragdoll(true) │
|
|
└──────────────────────┬───────────────────────┘
|
|
│
|
|
┌──────────────────────▼───────────────────────┐
|
|
│ 1. Pre-Ragdoll Intercept │
|
|
│ Query if held_prop != null │
|
|
└──────────────────────┬───────────────────────┘
|
|
│
|
|
┌──────────────────────▼───────────────────────┐
|
|
│ 2. Execute Disarm Handoff │
|
|
│ - Clear RemoteTransform2D binding │
|
|
│ - Reparent PropBlock to world root │
|
|
│ - Set prop.freeze = false │
|
|
│ - Apply cached momentum (_track_momentum) │
|
|
└──────────────────────┬───────────────────────┘
|
|
│
|
|
┌──────────────────────▼───────────────────────┐
|
|
│ 3. Build Physics Ragdoll │
|
|
│ Hide animated body, build RigidBody2D │
|
|
│ network at current solved bone positions │
|
|
└──────────────────────────────────────────────┘
|
|
|
|
```
|
|
|
|
1. **Pre-Ragdoll Intercept:** Prior to clearing collision proxies or hiding body meshes, check for an active `held_prop` binding.
|
|
|
|
2. **Physics Unbind:**
|
|
|
|
- Detach the `RemoteTransform2D` socket.
|
|
|
|
- Reparent prop to the world root and set `PropBlock.freeze = false`.
|
|
|
|
- Transfer cached rig momentum (`_track_momentum()`) directly to `PropBlock.linear_velocity` and `angular_velocity` so the prop tumbles naturally alongside the falling figure.
|
|
|
|
3. **Ragdoll Construction:** Proceed with standard `_build_ragdoll()` processing at current solved bone transforms.
|
|
|
|
---
|
|
|
|
## 5. Event System Integration ("When..." Triggers)
|
|
|
|
Three new edge-triggered prop event types are added to `StageEventRegistry` (`sandbox_stage.gd`) to support the director's "When → Then" mental model.
|
|
|
|
### Prop Trigger Specifications
|
|
|
|
| Trigger Type | Source Object | Target Object | Firing Condition |
|
|
| ---------------- | --------------- | --------------- | ------------------------------------------------------------------------------- |
|
|
| `prop_picked_up` | Stickman / Prop | Prop / Stickman | Emitted as soon as IK arm reach completes and `RemoteTransform2D` socket locks. |
|
|
|
|
|
|
|
| `prop_dropped` | Stickman / Prop | Prop / Stickman | Emitted when a stickman drops a prop manually or via ragdoll disarm.
|
|
|
|
|
|
|
| `prop_thrown` | Stickman / Prop | Prop / Stickman | Emitted at the exact release frame of a throw animation arc.
|
|
|
|
|
|
|
|
|
### Signal Routing & Evaluation
|
|
|
|
1. `StickmanActionRunner` emits `prop_action_executed(action_type, stickman, prop)` upon completing or releasing an anchor attachment.
|
|
|
|
2. `StageEventRegistry` matches active stage rules where `rule.trigger.type` equals `prop_picked_up`, `prop_dropped`, or `prop_thrown`.
|
|
|
|
3. Matching rules trigger reactive actions on target stickmen or props (e.g., _When Stickman A picks up Sword X $\rightarrow$ Stickman B ragdolls_).
|
|
|
|
---
|
|
|
|
## 6. Implementation Roadmap for Architect Agent
|
|
|
|
```
|
|
[ Phase 1: Action Queue & Locomotion ]
|
|
├─ Add pick_up_prop, drop_prop, and throw_prop to StickmanActionRunner
|
|
└─ Implement distance evaluation & auto-enqueueing of walk_to sub-goals
|
|
|
|
[ Phase 2: Socket Attachment & IK Handshake ]
|
|
├─ Implement RemoteTransform2D socketing to wrist joint with grip offsets
|
|
└─ Implement holding stance arm pose blending
|
|
|
|
[ Phase 3: Throw Mechanics & Trajectory UI ]
|
|
├─ Add trajectory solver & reticle target selector to Director UI
|
|
└─ Build throw animation timeline (windup -> release impulse -> follow-through)
|
|
|
|
[ Phase 4: State Machine Integration & Disarm ]
|
|
├─ Intercept held props during set_ragdoll(true) transitions
|
|
└─ Transfer cached linear/angular momentum to disarmed prop physics body
|
|
|
|
[ Phase 5: Event System Expansion ]
|
|
├─ Register prop_picked_up, prop_dropped, prop_thrown triggers in StageEventRegistry
|
|
└─ Wire action signals to trigger "When -> Then" rule evaluations
|
|
|
|
```
|
|
|
|
### Architectural Acceptance Criteria Checklist
|
|
|
|
- [ ] **Auto-Approach:** Requesting pickup of a distant prop automatically causes the stickman to walk to the prop before reaching out.
|
|
|
|
- [ ] **Grip Alignment:** Hand attaches precisely to the prop's authored `grip` anchor, respecting local position, rotation, and Z-index offsets.
|
|
|
|
- [ ] **Trajectory Accuracy:** Thrown props follow the calculated parabolic arc toward the director's targeted ground point.
|
|
|
|
- [ ] **Ragdoll Disarm:** Entering ragdoll mode immediately detaches held props and applies rig momentum so items tumble realistically.
|
|
|
|
- [ ] **Event Rule Engine:** Rules configured with `When prop_picked_up`, `When prop_dropped`, or `When prop_thrown` execute downstream actions reliably.
|