Files
stickman/plans/PHASE_11_PROP_INTERACTION.md
ryan 043f114083 Implement interactive prop handling for StickmanRig
- 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.
2026-09-23 21:26:13 -04:00

13 KiB

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.

  1. 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.

  1. 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.

  1. 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.