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.
This commit is contained in:
@@ -0,0 +1,476 @@
|
||||
# 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: `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).
|
||||
Reference in New Issue
Block a user