User feedback: 'anchorpoints sind hüftgelenke und können sich nicht
frei drehen. hüfte bleibt zum körper statisch'.
Architecture:
- legs.parent = body (was lower_control). Hip position cascades from
body.world.rot, so hips stay at body-frame left/right regardless of
body rotation. lower_control virtual bone removed (no longer needed).
- Procedural 'leg_orientation' sets leg.local.rot = walk_dir - body.rot
each frame, so leg.world.rot = walk_dir (independent of body cascade).
Requires puppet v0.4.2 per-channel write_bone (walk_anim writes leg.scl
while procedural writes leg.rot — disjoint channels).
Result: legs visually attach at body's anatomical left/right hips no
matter where body faces; stride oscillation aligns with WASD walk
direction. Hip position no longer drifts with walk direction.
User feedback: 'Beine sollten in die richtung oszillieren, in die
gegangen wird, nicht nach norden/süden relativ zum fenster.'
Old: lower_control.rot = wedge_deg (substrate's body-relative ±30°
sub-angle), legs always oscillated near world-up axis.
New: lower_control.rot = atan(move_x, -move_y) = walk direction in
sprite-top-up convention. Legs visually rotate to align with WASD
direction → stride animation cycles along the walk axis.
Side-effect: 4-direction-anim picker collapsed to single
walk_fwd_lower (the rotation handles directional differentiation;
4 anims kept on disk for later gait-style work). Bumps puppet dep
to 0.4.1 (head south-flick fix).
Iteration-3 implemented Subterrain control model but forgot to bump
the consumer manifest. Engine lib-resolver does exact strcmp so
'0.3.3' fails against published 0.4.0 → ENGINE_ERR_LIB_DEP_MISSING
FATAL at module load.
Recurring pattern (cf. feedback_sporel_dep_versions memory) — every
lib-bump must update all consumer manifests in the same commit-set.
head_001.png is drawn with face pointing +X (right) at rot=0, but the
look-at math assumes sprite-top (-Y) is forward. Without compensation,
head visually appears 90° clockwise off when math says 'point at mouse'.
sprite_rot=-90 on head bone aligns sprite-top with bone-forward at
render time. Body, shoulder, and arm sprites already match the
sprite-top=forward convention so no sprite_rot needed there.
Also bumps puppet dep to 0.3.3 and removes the temporary debug print.
Iteration 2 bumped lib-core.puppet to v0.3.2 (persistent look_at_state
fix) but the vagrant-skeleton manifest dep was not updated. Engine
lib-resolver does exact strcmp → module fails to load with
ENGINE_ERR_LIB_DEP_MISSING at launcher select.
- humanoid.rig.json: root removed from upper track; body look_at + slow speed
(180 deg/s, lazy); head fast speed (480 deg/s) + +-90 deg limits; puppet
dep bumped to 0.3.1
- animations/idle_lower.anim.json (NEW): leg+foot scl=[mirror, 0] keeps legs
invisible at idle (locomotion skips animated bones; foot cascade zeros out)
- init.lua: set_procedural 'facing' callback writes root.rot from
last_walk_rot_deg each frame; WASD-vector updates last_walk_rot_deg
via atan(dx, -dy); idle_lower played parallel to idle, stopped during walk
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Engine lib-resolver does exact strcmp (not semver-range), so '^0.3.0'
fails to match published 0.3.0. Also pin lib-asset.prototype-subterrain
to 0.1.1 (the published patch version).
Surfaced during Phase-4 install-reference verify after vagrant-skeleton
v0.3.0 cutover.
Manifest changes:
- Version 0.1.0 -> 0.2.0
- New asset_aliases block: player + tiles + world all map to
lib-asset.prototype-subterrain
- deps bumped: lib-core.maps 0.1.1 -> 0.1.2, lib-core.puppet
0.1.0 -> 0.2.0
- New dep: lib-asset.prototype-subterrain 0.1.0
Rig, animations, tilemap, and init.lua updates land in follow-up
commits in this slice.
Composer module: single-player character as puppet (humanoid 4-bone
rig: torso, head, leg_l, leg_r) at world (320, 240) on a 20x20
v0.1-format tilemap.
- WASD direct movement, normalized for diagonals, 120 px/s.
- Camera-chase via lib-core.camera target-provider (camera follows
puppet position frame-to-frame).
- Idle and walk keyframe animations: idle is a 2s torso-sway on the
upper track; walk is a 0.6s leg-cycle on the lower track.
- Animation-state transitions driven by movement input: walk while
WASD-active, idle when released.
- Headtrack: head bone (marked look_at: true in rig) rotates each
frame to face the mouse cursor's world position via
puppet.set_look_target.
- ESC -> launcher.
CI hooks (env-var-gated, no-op in normal play):
- SPOREL_CI=1: emits 'vagrant: frame_avg_ms=N' after 60 frames and
'vagrant: render_frame_ok' per frame from the render hook, then
engine.exit(0). Smoke stages consume these markers.