# 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. 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. **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. 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 ``` ## 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.