Files
stickman/plans/PHASE_11_PROP_INTERACTIONS.md
T
ryan 6a1359f244 Add prop interaction system and throw mechanics specification
- Introduced `stickman_prop_interactor.gd` to manage prop interactions.
- Created `phase_11_prop_interactions.md` detailing the design and implementation of prop pickup, drop, and throw mechanics.
- Defined interaction states, action lifecycles, and event triggers for prop handling.
- Specified constraints for throw actions, including target positioning and velocity calculations.
- Established acceptance criteria for prop interactions and lifecycle management.
2026-09-25 23:42:26 -04:00

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

    Edit: Torso position should also move up/down based on grip anchor. The stickman should look like it is bending or extending legs to make pickup look more realistic.

  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.

    EDIT: When the user clicks the stickman in DIRECT mode, 3 new additions should appear in the action context menu

      1. Pick up
      2. Drop
      3. Throw
      [Give these proper emoji]
    
    • If Drop is added to the queue, and the action is run when the stickman holds nothing, the action is skipped (marked as done)
    • If Throw is added to the queue, and the action is run when the stickman holds nothing, the action is skipped (marked as done)
    • These 3 actions should also be selectable when handling a 'When' event
  • 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. EDIT: The arm should go overhead - not an underhand throw.

  • 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

7. Design Questions / Rules

  1. When creating a Throw action, the user must create an arc defining the throw. However, when the scene is playing, the stickman might be in a whole different position for when the throw must happen. Should we define a new arc and make a best effort to throw the prop towards the (x,y) location selected or should we use the same arc, but translate the throw endpoint relevant to the stickman's current position.
  2. The stickman profile direction should change to whichever direction the prop will be thrown.

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.