477 lines
28 KiB
Markdown
477 lines
28 KiB
Markdown
# Phase 11 — Prop Interactions and Event Triggers
|
||
|
||
Status: **Approved for implementation (2026-09-24).**
|
||
|
||
Throw UX amendment: **Approved for implementation (2026-09-25).**
|
||
|
||
Throw landing-target amendment: **Approved for implementation (2026-09-25).**
|
||
|
||
Queue-aware throw-preview amendment: **Approved for implementation (2026-09-25).**
|
||
|
||
Source: `docs/archive/plans/PHASE_11_PROP_INTERACTIONS.md`. This spec keeps the requested
|
||
pickup, drop, throw, ragdoll handoff, and “When…” features while adapting them to
|
||
the current Sandbox Stage architecture and closing the design gaps identified
|
||
during review.
|
||
|
||
## 1. Approved design decisions
|
||
|
||
1. A throw action stores a **fixed stage-world target position**. At run time the
|
||
trajectory starts at the actor’s current held-prop transform and aims at
|
||
that saved point. Actor-relative throws are deferred.
|
||
2. Phase 11 supports **one prop held in one compatible hand per stickman**.
|
||
Two-handed grips and holding multiple props are deferred.
|
||
3. Each placed prop has a **persistent per-instance stage ID**. The ID is
|
||
separate from the asset’s `spawn_id` and from Godot’s session-only instance
|
||
ID. Duplicates of the same library prop therefore remain distinguishable.
|
||
|
||
## 1.1 Previously approved throw UX amendment (2026-09-25; superseded by §1.2)
|
||
|
||
This amendment refines the approved throw action and does not add fields to its
|
||
saved payload. It supersedes the exact-landing interpretation of decision 1
|
||
and §3.3 while keeping `target_position` as a fixed stage-world point.
|
||
|
||
- **Cursor meaning:** the saved point is an aim-and-power handle. At release,
|
||
the vector from the predicted release point to that handle sets the launch
|
||
bearing; its length sets launch speed. The prop is allowed to land short of
|
||
or beyond the cursor as gravity and collisions dictate.
|
||
- **Power curve:** map cursor distance monotonically and smoothly to launch
|
||
speed, clamp at a maximum, and keep the limits in one set of tunable
|
||
constants. Initial tuning recommendation: 300 px/s at minimum input, rising
|
||
to a 1,250 px/s cap at 700 px of cursor distance. Distances beyond that stay
|
||
at the cap. Retain the existing near-zero-distance no-op guard.
|
||
- **Prediction:** preview and execution use the same release point, cursor
|
||
vector, power mapping, velocity, gravity, and prop gravity scale. Draw the
|
||
sampled ballistic path and show the cursor separately from the predicted
|
||
impact point. Use the held prop's collision shape to estimate its first
|
||
world collision along the path, ignoring the actor and held prop; if no
|
||
collision occurs within the existing 2.0-second maximum horizon, mark the
|
||
horizon endpoint. This remains an estimate for moving bodies.
|
||
- **Overhand motion:** replace the short backward/upward wrist offset with a
|
||
head-relative IK arc. Lift the held prop behind and beside the head, carry
|
||
it above the crown, then sweep forward over the head. Release during the
|
||
forward sweep and finish with a follow-through. Derive points from the live
|
||
rig pose and facing, and clamp them within the selected arm's reachable
|
||
length so the IK solver does not snap or overextend. A small torso lean may
|
||
support the motion; do not reposition the legs.
|
||
- **Release origin:** calculate the preview from the prop's predicted
|
||
attachment transform at the release waypoint, not its pre-windup transform.
|
||
Recompute the same origin at the release frame before applying the bounded
|
||
launch velocity. Keep the action payload as `{ type, target_position }`;
|
||
power is derived at runtime, so existing saved queues remain readable.
|
||
|
||
Acceptance checks for the amendment: launch speed increases monotonically with
|
||
cursor distance until the cap; launch bearing follows the cursor vector;
|
||
preview points agree with the chosen release pose and solver; the overhand
|
||
hand path clears the head and remains within arm reach; release happens during
|
||
the forward sweep; the predicted impact marker is distinct from the cursor.
|
||
|
||
## 1.2 Approved throw landing-target amendment (2026-09-25)
|
||
|
||
This approved amendment supersedes §1.1's aim-and-power mapping. It keeps the
|
||
saved `{ type, target_position }` payload and treats its fixed stage-world point
|
||
as the requested landing/aim point.
|
||
|
||
- **Range cap:** cap the raw cursor point to a 3,600 px radius from the
|
||
stickman's root position at preview time and again when the throw action
|
||
starts. If it is farther away, project it onto that radius along the
|
||
root-to-cursor vector. The root is the intuitive range reference; the actual
|
||
ballistic path still starts at the prop's predicted hand-release transform.
|
||
A cursor within the existing 4 px no-op threshold of the root skips the
|
||
throw and leaves the prop held.
|
||
Keep the raw saved world point unchanged, so queued actions remain readable.
|
||
When a queued actor has moved by the time the throw starts, recompute only
|
||
the effective capped point from its current root position.
|
||
- **Target solver:** in unobstructed flight, solve the launch velocity so the
|
||
prop reaches the effective target on the descending part of its arc. Continue
|
||
using project gravity and the prop's `gravity_scale`. Search flight times
|
||
from 0.35 s to the 2.0 s horizon and select the shortest valid time whose
|
||
launch speed does not exceed 2,100 px/s. This favors a fast trajectory while
|
||
preserving the selected endpoint. If no valid candidate exists, show the
|
||
target as unreachable and skip the throw without releasing the prop.
|
||
- **Preview and physical collisions:** preview and execution use the same
|
||
effective target, release transform, selected flight time, velocity, gravity,
|
||
and prop gravity scale. Shape-cast the held prop along the path, excluding
|
||
the thrower's collision bodies and the prop itself. A world collision before
|
||
the target ends the guide at its first safe contact point. If no collision
|
||
occurs before the requested endpoint, the guide reaches that endpoint. If
|
||
the point is in open air, the prop continues under normal physics after
|
||
passing it; the guide marks the requested point rather than the rest of that
|
||
continuing flight. Moving-body impacts remain an estimate.
|
||
- **Target markers:** show the raw cursor separately from the effective
|
||
capped target. When a collision truncates the guide before that target, show
|
||
the collision point separately as well. Thus a cursor at 1,200 px from the
|
||
root shows an effective target at 3,600 px, while retaining the cursor marker
|
||
at 1,200 px.
|
||
- **Prop weight/material:** keep one shared range and speed policy in this
|
||
change. Physical mass does not change gravitational acceleration. A future
|
||
balance pass may add an explicit gameplay throwability factor derived from
|
||
prop weight or material; it must not silently change the physics mass or
|
||
existing `.prop` schema.
|
||
|
||
The previously approved overhand IK path, its reach clamp, forward-sweep
|
||
release, follow-through, and release-origin prediction remain in force.
|
||
|
||
Acceptance checks for this amendment: a target within 3,600 px remains exact;
|
||
farther targets clamp to a 3,600 px radius from the current stickman root; the
|
||
cursor and effective target markers remain distinct; the solver chooses the
|
||
shortest valid descending arc under the speed cap; earlier collisions truncate
|
||
the guide; unreachable targets release no prop; and queued throws preserve the
|
||
raw fixed world point while recomputing a bound when necessary.
|
||
|
||
## 1.3 Approved queue-aware throw-planning preview (2026-09-25)
|
||
|
||
At queue-edit time, a throw may follow a queued pickup that has not executed yet.
|
||
The preview must model that planned held-prop state without mutating the live
|
||
rig, prop, queue, reservations, or physics. This is a planning estimate; runtime
|
||
continues to solve from the actual held prop and release transform.
|
||
|
||
- **Queue prefix:** for an edited throw, inspect authored actions before its
|
||
stable action ID. For a newly appended queue or Direct throw, inspect the
|
||
current queue as the prefix. Start from the rig's current held-prop state,
|
||
then fold only pending actions in order: a valid `pick_up_prop` selects its
|
||
stage prop ID and requested hand; `drop_prop` or `throw_prop` clears the
|
||
virtual held prop. Completed actions are already reflected in live state and
|
||
are not replayed. A missing or incompatible prop leaves the preview
|
||
unavailable.
|
||
- **Predicted pickup pose:** use the same grip-selection and pickup stand-off
|
||
calculations as runtime. For an out-of-reach pickup, predict the approach
|
||
root position from the selected grip and `APPROACH_GAP`; also account for
|
||
preceding authored `walk_to` targets. This predicts the ordinary queue case
|
||
without starting locomotion or attaching the prop. If an earlier action makes
|
||
the future actor or prop transform indeterminate (for example, an active
|
||
ragdoll or a prior physical throw), do not present the path as exact; show a
|
||
planning/uncertain status and use the best available pose estimate.
|
||
- **Pure preview context:** pass the resolved prop, chosen grip/hand, and
|
||
predicted root transform as explicit preview inputs to the existing
|
||
gravity-aware solver and collision shape cast. Do not temporarily set the
|
||
interactor's `_held_prop`, reserve the prop, change the hand target, or move
|
||
either scene object to make the preview work.
|
||
- **Feedback:** show a plan-preview hint such as “Assumes Step 2 pickup
|
||
succeeds.” Keep raw cursor, capped target, and collision markers. Reserve red
|
||
for a genuinely unreachable solved target; if no planned or live prop can be
|
||
resolved, show a neutral explanation instead of a misleading straight red
|
||
trajectory. Preview status must update as the queue prefix or target changes.
|
||
- **Scope:** queue edit, queue append, and Direct throw captures have a known
|
||
authored prefix and use this model. Rule-authored throws have no fixed action
|
||
prefix, so they use the live held-prop state and report preview unavailable
|
||
when no prop is currently held.
|
||
|
||
Acceptance checks for this amendment: at `t = 0`, editing a pending throw after
|
||
a pending pickup displays a gravity/collision-aware planning guide; the preview
|
||
does not change prop ownership, transforms, reservations, the queue, or live
|
||
physics; a preceding drop/throw clears the inferred held prop; the predicted
|
||
pickup approach moves the guide origin; and actual execution recomputes the
|
||
trajectory from the real release transform. A missing, invalid, or ambiguous
|
||
prefix never masquerades as a solver-unreachable red trajectory.
|
||
|
||
## 2. Existing contracts and scope
|
||
|
||
- `StickmanRig` is the public runtime facade. It already composes separate
|
||
Locomotion, Ragdoll, and ActionRunner components under `RigComponents`.
|
||
- `StickmanActionRunner` owns authored queue IDs and emits one start/finish
|
||
pair per authored action. Walking is currently a normal queue action.
|
||
Pickup’s internal approach must not be inserted into the authored queue:
|
||
pickup remains one authored action, with one action ID and one completion.
|
||
- `StickmanLocomotion.walk_to()` drives the existing navigation/terrain
|
||
behavior, but its final root displacement is horizontal. Prop approach must
|
||
choose a grounded stand point and verify hand reach; it cannot assume
|
||
arbitrary 2D walking.
|
||
- `PropAnchorData` already normalizes grip roles, allowed hands, local
|
||
transforms, and attachment-transform math. `PropFile` already serializes
|
||
anchors, but `PropLibrary` currently drops them and `StageSpawner` does not
|
||
pass them into `PropBlock`. Phase 11 must close that runtime data path.
|
||
- `StickmanRig` uses animated IK target markers. A prop hold/throw pose needs a
|
||
single explicit owner for the active hand target after animation updates and
|
||
before the IK solve. Do not let an AnimationPlayer track and the interaction
|
||
code fight over the same target.
|
||
- Rule/action templates live in `TriggerRegistry` and `ActionRegistry`.
|
||
`SandboxStage` handles rule matching and event dispatch; there is no current
|
||
`StageEventRegistry`. Extend these existing paths.
|
||
- `PropBlock.begin_play_instance_scale()` moves authored instance scale into
|
||
its visuals/collision during PLAY. Grip alignment must use that effective
|
||
scale, not assume the RigidBody root retains the EDIT scale.
|
||
|
||
In scope: one-hand pickup, drop, and targeted throw; automatic approach;
|
||
prop/rig state handoff; stage-scene persistence for prop identity and queue
|
||
payloads; Direct and queue/rule authoring; and three edge-triggered prop rules.
|
||
|
||
Out of scope: two-hand or multiple-prop holding; torso/leg bending to improve
|
||
pickup reach; prop-to-prop mounting; melee hitboxes; moving-target tracking;
|
||
actor-relative throws; a general-purpose event bus rewrite; and document-wide
|
||
undo/redo.
|
||
|
||
## 3. Runtime ownership and action lifecycle
|
||
|
||
Add a `StickmanPropInteractor` component under each rig’s `RigComponents`.
|
||
It owns the selected hand, held prop reference, chosen grip ID, reservation,
|
||
saved collision/freeze settings, active interaction phase, and recent held
|
||
transform samples. `StickmanRig` exposes a narrow facade to the ActionRunner
|
||
and calls the interactor’s ragdoll/reset hooks. The ActionRunner remains the
|
||
owner of authored queue progression; it delegates prop work to the interactor
|
||
and waits in a prop-interaction phase until that action succeeds, fails, or is
|
||
cancelled.
|
||
|
||
The interaction state is:
|
||
|
||
| State | Contract |
|
||
| --- | --- |
|
||
| Idle | No prop is held and no pickup is reserved. |
|
||
| Approaching | The pickup action owns one prop reservation and uses locomotion to reach a grounded stand point. It is still one active authored action. |
|
||
| Reaching | The chosen arm target blends to the chosen grip; the prop is not yet reported as held. |
|
||
| Held | Exactly one prop is aligned to the solved wrist and cannot be claimed by another rig. |
|
||
| Throwing | The arm performs windup/follow-through; release is committed once at the release boundary. |
|
||
|
||
Cancellation before pickup attachment stops the approach/arm blend and releases
|
||
the prop reservation. Cancellation of a throw before its release boundary
|
||
leaves the prop held; cancellation after release never pulls it back and only
|
||
ends the remaining follow-through.
|
||
|
||
Action payloads:
|
||
|
||
| Type | Runtime payload | Behavior |
|
||
| --- | --- | --- |
|
||
| `pick_up_prop` | `{ type, target_prop_id: String, hand: "auto" | "left" | "right" }` | Resolve and reserve that exact prop, approach if needed, then attach one compatible grip. |
|
||
| `drop_prop` | `{ type }` | Release the currently held prop. If none is held, complete as a skipped no-op. |
|
||
| `throw_prop` | `{ type, target_position: Vector2 }` | Throw the held prop toward the saved stage-world point. If none is held, complete as a skipped no-op. |
|
||
|
||
Skipped means the queue item reaches its existing completed state with a
|
||
no-op outcome; it does not add a new persistent queue status. The existing
|
||
`action_finished` signal continues to mean that a queue item ended. The
|
||
dedicated `prop_*` triggers below are the success signals for actual prop
|
||
state changes.
|
||
|
||
### 3.1 Pickup selection and auto-approach
|
||
|
||
At action start, resolve `target_prop_id` to a live `PropBlock` in this stage.
|
||
Reject a missing ID, a placeholder, an already-held/reserved prop, a rig
|
||
already holding a prop, or a hand/anchor combination with no compatible grip.
|
||
These failures finish the queue item as skipped and emit no pickup event.
|
||
|
||
Use the prop’s normalized authored anchors. For `hand: "auto"`, consider
|
||
compatible grip/hand pairs, prefer `primary_grip`, then choose the reachable
|
||
pair with the shortest wrist-to-grip distance. An explicit left/right request
|
||
must use that hand; do not silently switch hands when its selected grip forbids
|
||
it. If no editable grip exists, use a generated one-hand fallback grip at the
|
||
center of the prop’s visual bounds. Built-in props use that fallback unless
|
||
their template provides an explicit grip.
|
||
|
||
Measure reach from the selected solved wrist to the transformed grip, not from
|
||
the rig root to the prop origin. If the grip is out of reach, compute a grounded
|
||
stand point on a reachable side of the prop and ask existing locomotion to walk
|
||
there. The stand point must account for current terrain height and the chosen
|
||
hand. After arrival, re-resolve the prop, recompute its grip transform, and
|
||
confirm actual reach before starting the arm blend. A moving prop may cause at
|
||
most one approach replan; if it is still unreachable, release the reservation
|
||
and skip the action. No unbounded “chase the prop” loop is allowed.
|
||
|
||
Reservations are claimed synchronously when pickup begins so two actors cannot
|
||
both approach and attach the same prop. Release the reservation on success,
|
||
failure, cancellation, prop deletion, or actor deletion. Do not emit a
|
||
`walk_to` completion or waypoint-arrival event for the internal approach.
|
||
|
||
### 3.2 Attachment and hold
|
||
|
||
Interpolate the selected hand’s IK target to the grip over a short reach blend.
|
||
Once the arm is solved, align the prop with
|
||
`PropAnchorData.solve_attachment_transform()`, using the solved wrist
|
||
transform, the grip’s local transform, effective PLAY scale, and the
|
||
left-facing mirror convention. Apply the anchor’s z-index offset relative to
|
||
the rig’s draw order.
|
||
|
||
Keep the `PropBlock` under the stage World. Do not reparent it to the animated
|
||
rig or use reparenting as the attachment mechanism. During hold, use
|
||
`RigidBody2D.FREEZE_MODE_KINEMATIC`, update the prop transform from the solved
|
||
wrist in the interaction update, and temporarily disable its collision layer
|
||
and mask so it cannot snag terrain, its owner’s collision proxies, or generate
|
||
ordinary contact events while held. Save and restore all modified physics
|
||
settings on release. The hold transform must be updated only after the wrist
|
||
pose has been solved for that frame.
|
||
|
||
Sample the held prop transform each physics frame. Those samples provide
|
||
release linear/angular velocity for drops and ragdoll disarm; do not use
|
||
`_last_grounded_walk_velocity_x` as full actor momentum. It is a grounded,
|
||
horizontal locomotion value and does not describe airborne or wrist motion.
|
||
|
||
### 3.3 Drop and throw
|
||
|
||
A manual drop detaches at the current prop transform, restores its collision
|
||
settings and dynamic state, and applies the most recent finite sampled
|
||
attachment velocity. A throw uses the same release handoff, then overrides
|
||
linear velocity with the trajectory solver result and adds a small deterministic
|
||
spin. Release and its event are committed exactly once.
|
||
|
||
The approved baseline uses a bounded ballistic solver and no-op guards. The
|
||
approved §1.2 amendment changes its target handling as follows:
|
||
|
||
1. Keep the saved absolute `target_position`. At preview and throw start,
|
||
compute the effective point by clamping its radial distance from the current
|
||
stickman root to `MAX_THROW_RANGE` (3,600 px). Use the prop's actual predicted
|
||
attachment transform at the release waypoint as the ballistic origin.
|
||
2. For a candidate flight time `t`, compute the required initial velocity as
|
||
`(effective_target - release_position - 0.5 * gravity * t * t) / t`. Search
|
||
`[0.35 s, 2.0 s]` and choose the shortest finite candidate that reaches the
|
||
effective point on descent and stays at or under `MAX_THROW_SPEED`
|
||
(2,100 px/s). The time interval is the preferred tuning seam if playtesting
|
||
shows that the default arc needs to be more or less lofted.
|
||
3. If no candidate can reach the target within those bounds, mark the preview
|
||
invalid and leave the prop held when the action runs. Never substitute an
|
||
undershot point while presenting it as the requested landing target.
|
||
|
||
The preview uses the same effective target, release transform, flight time,
|
||
velocity, gravity, and prop gravity scale as execution. Shape-cast the prop
|
||
along the path up to the effective target, excluding the thrower's collision
|
||
bodies and the held prop. An earlier world collision truncates the guide at
|
||
first safe contact; otherwise the guide reaches the effective target. In open
|
||
air, the target is an aim point and the physical prop continues until a later
|
||
collision. Show the raw cursor, effective capped target, and an earlier
|
||
collision point as separate markers.
|
||
|
||
A queue-authored throw may use the queue-aware planning preview in §1.3. Its
|
||
inferred prop, grip/hand, and estimated root pose are immutable solver inputs;
|
||
the preview must not simulate pickup by changing live ownership or transforms.
|
||
Runtime release always recomputes from actual state.
|
||
|
||
A cursor within the 4 px zero-distance threshold of the stickman root is a
|
||
no-op and leaves the prop held. Godot's positive-Y-down gravity vector must be used consistently. Set
|
||
`linear_velocity` from the solved velocity; do not pass a velocity vector to
|
||
`apply_central_impulse()`. Never pass a NaN or infinite vector into physics.
|
||
Prop mass does not change the solver result; prop-specific throwability remains
|
||
future tuning work.
|
||
|
||
The rig faces toward the saved target before throw windup when the horizontal
|
||
offset is outside a small dead zone; it retains that direction afterward.
|
||
Use a scripted arm IK pose for windup, release, and follow-through. Do not
|
||
directly rotate the torso or reposition the legs in this phase.
|
||
|
||
## 4. Ragdoll and Sandbox lifecycle
|
||
|
||
Before `StickmanRig.set_ragdoll(true)` resets locomotion or begins the ragdoll
|
||
handoff, ask the interactor to detach any held prop. Restore its collision and
|
||
physics settings and apply the latest sampled attachment velocity. This path is
|
||
idempotent and works for direct ragdoll, collision-triggered ragdoll, and a
|
||
queued ragdoll action.
|
||
|
||
Ragdoll disarm emits one `prop_dropped` event with reason `ragdoll`; it does
|
||
not run the manual drop animation. A manual drop emits the same event with
|
||
reason `manual`. A scene reset or leaving PLAY detaches silently, clears
|
||
reservations/pose overrides, then lets the existing authored-state restore
|
||
return props to their starting transforms. It must not create a gameplay rule
|
||
event during cleanup.
|
||
|
||
Pause/resume preserves a held attachment and its sampled transform state.
|
||
Reset/restart restores the unheld authored prop state. Prop interaction actions
|
||
require an animated rig; if queued while ragdolled, use the existing recovery
|
||
barrier behavior before pickup/throw. A no-held drop/throw after recovery
|
||
remains a skipped no-op.
|
||
|
||
## 5. Events and authoring
|
||
|
||
Add ActionRegistry entries and labels/icons for `pick_up_prop`, `drop_prop`,
|
||
and `throw_prop`. Extend ActionEditor/QueuePanel serialization and the Direct
|
||
action popup so actions can be added and edited. Direct popup labels should
|
||
read **🫴 Pick Up Prop…**, **⬇️ Drop Prop**, and **🏹 Throw Prop…**. Pick Up
|
||
captures a prop; Throw captures a stage-world point; Drop queues immediately.
|
||
The When/Then action editor must support those same payloads, including the
|
||
second stage capture needed to choose the prop or throw point after the acting
|
||
stickman is selected.
|
||
|
||
Add TriggerRegistry entries for:
|
||
|
||
| Type | Source | Target | Fire condition |
|
||
| --- | --- | --- | --- |
|
||
| `prop_picked_up` | Acting stickman | Picked-up prop | After grip alignment and held-state ownership commit. |
|
||
| `prop_dropped` | Acting stickman | Released prop | After manual or ragdoll release commits; event payload includes the reason. |
|
||
| `prop_thrown` | Acting stickman | Thrown prop | At the exact release boundary, after physics velocity is assigned. |
|
||
|
||
Prop trigger rules select a stickman source and prop target. Use the existing
|
||
ordered, all-matching rule evaluation and normal reactive action queues. Emit
|
||
events from the interactor’s successful state transitions, not merely because
|
||
an ActionRunner item finished. Do not emit while a prop is being continuously
|
||
held, after a failed action, or during scene cleanup. Rule actions can target a
|
||
different stickman and can refer to their own prop ID/throw point.
|
||
|
||
User-authored rules may form a real cycle (for example, pick up → drop → pick
|
||
up). Do not synthesize rules or dispatch prop events inline from a failed
|
||
attempt. Keep transition events edge-triggered and preserve the existing
|
||
stage-run/event-dispatch guards.
|
||
|
||
## 6. Stage identity, anchors, and saved data
|
||
|
||
Add a non-empty, opaque `stage_object_id` to every spawned `PropBlock`.
|
||
Generate it for each placement/instance; never copy it from the asset or from a
|
||
duplicated prop. Keep `spawn_id` exclusively as library/template provenance.
|
||
The stage owns resolution and uniqueness checks. Missing custom-prop
|
||
placeholders preserve the same ID so replacing a missing asset with its real
|
||
asset restores queue/rule references.
|
||
|
||
Bump StageSceneIO to version 1.1. Keep reading 1.0 scenes; migration assigns
|
||
each prop’s current unique `file_id` as its initial `stage_object_id`.
|
||
Write `stage_object_id` on each prop entry thereafter. Reject duplicate or
|
||
empty IDs in a 1.1 file before applying it, preserving the current scene on
|
||
load failure. Existing per-save `file_id` mappings continue to serialize
|
||
runtime rule references; do not use Godot instance IDs as saved references.
|
||
|
||
StageSceneIO queue encode/decode and rule-action conversion must preserve
|
||
`target_prop_id` as a stable string and `target_position` as finite vector
|
||
data. A queued action whose prop ID no longer resolves stays in the queue and
|
||
finishes as skipped when run. A missing-prop placeholder is inert and cannot
|
||
be picked up. A rule action with a missing prop target is skipped at execution;
|
||
it must not crash or silently retarget another prop.
|
||
|
||
Carry anchors from `PropLibrary.make_custom_entry()` through `StageSpawner`
|
||
into `PropBlock`. Built-in props get a generated centered one-hand grip from
|
||
their visual bounds unless their template already defines one. Preserve the
|
||
existing .prop anchor schema; do not add runtime-only hand state to .prop
|
||
assets.
|
||
|
||
## 7. Implementation sequence
|
||
|
||
1. **Identity and data path:** persistent stage IDs, 1.0→1.1 scene migration,
|
||
action payload serialization, and custom/built-in grip availability.
|
||
2. **Pickup/drop vertical slice:** PropInteractor, one-hand state ownership,
|
||
approach/revalidation, hold pose, drop, action cancellation, and Direct /
|
||
queue editing.
|
||
3. **Throw:** fixed world-target capture, root-relative 3,600 px range clamp,
|
||
shortest-valid bounded landing solver, facing/overhand arm arc, release
|
||
velocity, collision-aware trajectory preview, and queue-prefix planning
|
||
context for pending throw targets.
|
||
4. **Lifecycle:** ragdoll disarm, PLAY pause/resume/reset, deletion, and
|
||
recovery-barrier integration.
|
||
5. **Rules:** three prop triggers, source/target capture, When/Then prop
|
||
actions, persistence, and missing-target behavior.
|
||
|
||
Each step should be headless-verifiable with pure serialization/solver cases
|
||
and followed by editor/runtime verification in the Sandbox Stage.
|
||
|
||
## 8. Acceptance criteria
|
||
|
||
- A distant pickup approaches using one active queue item, then attaches one
|
||
compatible grip to one hand without emitting a synthetic walk event.
|
||
- Left/right facing, authored anchor position/rotation, prop scale, and
|
||
z-index offset produce stable grip alignment. Built-in props remain pickable.
|
||
- Missing, deleted, reserved, incompatible, or unreachable pickup targets fail
|
||
safely, release their reservation, and emit no successful prop event.
|
||
- Drop restores physics/collision properties and transfers sampled release
|
||
motion. Drop and throw with no held prop complete as skipped no-ops.
|
||
- A throw queued before the actor moves keeps its saved stage-world point; if
|
||
the range cap binds at runtime, the effective point is clamped from the
|
||
actor's then-current root. Preview and runtime use the same gravity and
|
||
target solver.
|
||
- A throw within range reaches its selected point on the descending arc when
|
||
unobstructed; targets beyond range show the 3,600 px effective point separately
|
||
from the cursor. Earlier collisions stop the guide at the collision point.
|
||
- At `t = 0`, a pending throw after a pending pickup has a non-mutating,
|
||
queue-aware planning guide; no held prop or invalid prefix shows a neutral
|
||
explanation instead of a false red ballistic path.
|
||
- Ragdoll releases a held prop before hiding the animated rig, transfers
|
||
sampled motion, and emits exactly one ragdoll-reason drop event.
|
||
- Pause/resume preserves the hold; PLAY reset/exit releases silently and
|
||
restores authored state.
|
||
- Prop pickup/drop/throw triggers fire exactly once after their respective
|
||
committed transitions and execute matching rules in existing rule order.
|
||
- Prop IDs and new queue/rule action payloads survive save/load, missing custom
|
||
prop placeholders, and object reordering without binding to the wrong copy.
|
||
|
||
Final user sign-off on §1.3 was received 2026-09-25. Implementation and editor
|
||
verification are complete (2026-09-25).
|