Storage containers + ITR mag interaction + Phase 0 dead sweep #36

Merged
merith-tk merged 14 commits from muse-lantern/da-game:storage-containers into main 2026-11-10 00:34:26 +00:00
Member

Base: main at 3e512be (PR #32). Head: muse-lantern/da-game:storage-containers, tip 92985ec (12 commits).

What it contains

Volume container (42960d5, c8f203a, 3390360, e7cb50d, e7794bc): 1×0.5×0.5 m box on the slab (cyan wireframe open, solid deep teal closed), bucket walls, physics-lock stores (an idle-locked item inside the volume is stored), hand-stays-back (volume-scoped), and the release fix — the release store check now uses the item's position, not the grip, so an item held against the outside of a wall can't be "stored" while the hand is inside. New suite phases cover the wall case.

ITR mag interaction (d2431fe, 6df85cf, plus Zach's 8aa6b7b): FILO (rack/fire/chamber pop the last child), procedural cylinder bullets (GLBs deleted), mag grip_offset / grip_rotate + opening {pos, rot}, insert at the opening, hold-trigger eject (floating grabbable bullet, drops on release if not grabbed), first-out bullet rendered at the top of a loaded mag, floor fix (tiny cylinders get a 0.02 m box collision proxy; rounds float ~7 mm as a known artifact). New suites mag_filo_check, mag_interaction_check, floor_bullet_check.

Registry cleanup (fde2e55): 25 → 15 items (plus the tracer added later). Detector delete (af67f61). Mobile renderer (4cb4580): renderer/rendering_method="mobile".

Phase 0 dead sweep (92985ec, no behavior change): damage_mult (3 ammo JSONs), the two_handed boolean (3 weapon JSONs), flat_fov, Grabbable.current_pivot, GenericItem.resolve_grip (+ its module_check blocks), save_snapshot / load_snapshot + serialize_items / deserialize_items (only called by the snapshots; the per-item primitives stay for Phase 4). MECHANICS.md + mechanics.json regenerated (118 values, was 121); modding.md example fixed.

Verification (lantern-vm)

  • Double --import: 0 ERROR both passes. tools/check.py: 6/6.
  • Headless suites: 31/31 PASS (terrain_check skipped, not on branch), 1212 checks counted, 0 FAIL lines.

Known: bridge e2e FAILS 3 checks on this tip

Pre-existing, not from this branch's work — verified by running the e2e at clean 8aa6b7b, where it fails identically. Root cause is 8aa6b7b's grip_rotate: [90, 0, 0] on the pistol ammo: the e2e aims the grip at the opening, and the rotated bullet sits outside the 0.06 m insert radius. The headless mag suites pass (they aim the bullet at the opening). Needs Zach's call: fix the e2e's aim or adjust the grip values.

Base: `main` at `3e512be` (PR #32). Head: `muse-lantern/da-game:storage-containers`, tip `92985ec` (12 commits). ## What it contains **Volume container** (`42960d5`, `c8f203a`, `3390360`, `e7cb50d`, `e7794bc`): 1×0.5×0.5 m box on the slab (cyan wireframe open, solid deep teal closed), bucket walls, physics-lock stores (an idle-locked item inside the volume is stored), hand-stays-back (volume-scoped), and the release fix — the release store check now uses the **item's** position, not the grip, so an item held against the outside of a wall can't be "stored" while the hand is inside. New suite phases cover the wall case. **ITR mag interaction** (`d2431fe`, `6df85cf`, plus Zach's `8aa6b7b`): FILO (rack/fire/chamber pop the last child), procedural cylinder bullets (GLBs deleted), mag `grip_offset` / `grip_rotate` + `opening {pos, rot}`, insert at the opening, hold-trigger eject (floating grabbable bullet, drops on release if not grabbed), first-out bullet rendered at the top of a loaded mag, floor fix (tiny cylinders get a 0.02 m box collision proxy; rounds float ~7 mm as a known artifact). New suites `mag_filo_check`, `mag_interaction_check`, `floor_bullet_check`. **Registry cleanup** (`fde2e55`): 25 → 15 items (plus the tracer added later). **Detector delete** (`af67f61`). **Mobile renderer** (`4cb4580`): `renderer/rendering_method="mobile"`. **Phase 0 dead sweep** (`92985ec`, no behavior change): `damage_mult` (3 ammo JSONs), the `two_handed` boolean (3 weapon JSONs), `flat_fov`, `Grabbable.current_pivot`, `GenericItem.resolve_grip` (+ its `module_check` blocks), `save_snapshot` / `load_snapshot` + `serialize_items` / `deserialize_items` (only called by the snapshots; the per-item primitives stay for Phase 4). `MECHANICS.md` + `mechanics.json` regenerated (118 values, was 121); `modding.md` example fixed. ## Verification (lantern-vm) - Double `--import`: 0 ERROR both passes. `tools/check.py`: 6/6. - Headless suites: **31/31 PASS** (terrain_check skipped, not on branch), **1212 checks counted, 0 FAIL lines**. ## Known: bridge e2e FAILS 3 checks on this tip Pre-existing, not from this branch's work — verified by running the e2e at clean `8aa6b7b`, where it fails identically. Root cause is `8aa6b7b`'s `grip_rotate: [90, 0, 0]` on the pistol ammo: the e2e aims the **grip** at the opening, and the rotated bullet sits outside the 0.06 m insert radius. The headless mag suites pass (they aim the **bullet** at the opening). Needs Zach's call: fix the e2e's aim or adjust the grip values.
- VolumeContainer (1 x 0.5 x 0.5 m) on the DebugSlab: cyan wireframe
  when open, solid deep teal when closed; no boundary colliders,
  containment is by release-inside check only.
- Release inside stores the item frozen relative to the volume space;
  grabbing a stored item takes it out. Closed hides/deactivates
  stored items (instances retained). Toggle on the sandbox menu.
- Primitives pre-placed and table-spawned at half size via
  WorldItem.set_content_scale (a RigidBody3D node scale is reset by
  the physics server, so the content children carry the scale).
- Table spawn panel by the PrimSpots lists the three primitives.
- New suite tests/volume_container_check.gd (70 checks).

Mockup only: no tag system, no caliber, no item-store/mag visual.
Task A — one grab origin: the hand-grab sphere, laser, and
range-grab cone now all start at the controller (grip) position
pushed 0.05 m forward along the beam, not at the wrist center.
New tunable player.grab_origin_forward_m (rules/base.json);
PlayerRig moves each pointer forward from its scene-authored
base position; HandInteractor.grab_origin() is the pointer
position (grip fallback) and all reach/region checks use it.

Task B — hand collision: new HandCollider (RigidBody3D, frozen
kinematic, 0.06 m sphere at the controller, PLAYER layer,
WORLD|ITEM mask), one per grip, active when the rig is live;
the two colliders except each other. Grabbing stays
interactor/proximity-based.

Task C — held-object collision: Grabbable._follow now moves the
held body with swept move_and_collide in <=0.1 m substeps
instead of setting the transform directly, so a held item bumps
into walls/objects/stored items instead of ghosting through.
Resting-contact recovery (lift straight up when the target is
above, nudge along the normal on a 0-travel hit) so a resting
item can still be picked up. The holding hand's collider is
excepted from the held body while held. Rotation is still set
directly (not collision-checked).

Task D — stored-item collision: VolumeContainer no longer
reparents stored items (reparenting a frozen body leaves the
physics server's sweep broadphase stale, so a held sweep
passed straight through it). Stored items stay in the level,
frozen STATIC, tracked in _stored; open stored items keep
ITEM layer/mask (they were already collidable for dynamics —
a dropped cube rests on one), closed still hides/deactivates
them. Known gaps, not fixed here: no boundary colliders (a
free outside object can still hit stored items through where
a wall would be), and a closed box has no solid collider.

Suite: volume_container_check 70 -> 95 checks (grab-origin
rule + pointer offset + grab_origin(), hand-collider presence/
shape/layer/mask, held-vs-stored sweep stop + stored item
static, dropped cube rests on stored cube, hand collider
rests on the slab). Two old checks updated: stored items stay
in the level instead of reparenting under the container.

Known follow-up (documented in the suite): _follow's
_physics_process sweep does not see a stored item that was
moved while frozen kinematic — body_test_motion during the
physics step is blind to it, while the same sweep from idle
context stops at the exact contact. The push is therefore
verified with a direct sweep; making the follow see stored
items is follow-up work.

Verified: double --import 0 ERROR both passes; check.py 6/6;
28/28 suites PASS (1027 checks; only the 2 pre-existing
firearm_check SCRIPT ERROR lines); bridge e2e PASS.
- Laser origin back at the pointer node authored position: removed
  the +0.05 m forward push from c8f203a (it overshot toward the
  fingertips). Cone apex sits player.cone_setback_m (0.08 m) back
  along the beam from the pointer spot.
- Ranged-grab deadzone player.ranged_grab_deadzone_m (0.25 m):
  items closer than that are hand-grab only. 0.25 = hand-grab
  reach 0.14 + 0.06 tolerance + 0.05 grab-origin offset, so
  physical and ranged grab meet with no gap or overlap; ranged
  grab still reaches 3.0 m beyond the deadzone.
- The ranged-grab highlight now also marks hand-grab targets
  (same full/non-sided indicator, via RangedGrab.set_hand_target).
- Hand-grab sphere origin unchanged (pointer + 0.05 m forward).
- Suites: ranged_grab_check +14 (deadzone + hand indicator),
  volume_container_check +6 (cone setback, incl. a fix for the
  stale inventory slot a direct Grabbable release leaves behind).
Removed (recorded in docs/registry-cleanup.md; to be remade when their
systems are implemented):
- deadair:flashlight (no light/recharge system, no model)
- deadair:food_pack (servings prop read nowhere, no consumable table)
- deadair:medical_stim (no properties, no consumable table)
- deadair:survival_knife (no melee system, no model)
- deadair:transmitter (no comms/radio system, no model)
- deadair:water_device (no ticker/degradation system, no model)
- devkit:artifact/sample_test (no artifact system)
- devkit:junk/parts_test (no salvage/trade system)
- devkit:quest/data_drive_test (no quest system)
- devkit:tool/detector_test (no detector system)

Registry now holds 15 items: 3 deadair primitives, 10 devkit, 2 testgun.
Start loadout still resolves (only kept items).

food_pack was the canonical replaces fixture, so the replaces tests were
retargeted to deadair:primitive_cube (module_check, modloader_check,
example_module better_ration, docs/modding.md) — same coverage, no
weakening. deadair item count 9 -> 3.
The Detector system is removed for the rebuild: detector.gd and its
branches in WorldItem / ItemInstance are gone. The system gets built
fresh later, once interactions handling is correct. The chest-slot
comment in body_slots.gd is a slot label, not the Detector system,
and stays.
Two generic bullets, built in Blender (build_bullets.py) and exported
as GLB: pistol (29 mm, red band near the base) and rifle (55 mm,
green band near the base). The ammo items now reference them via
model_path, replacing the shape-box stand-ins. Mags carry a matching
coloured band at the bottom base (red = pistol, green = rifle, keyed
off what the mag accepts), so a mag reads its identity and orientation
at a glance. Magazine machinery untouched.
Zach 2026-11-08: apply this patch, going forward with the mobile
renderer. The two lines (base + .mobile override, both
gl_compatibility) collapse to the single mobile line. Own commit so
it is independently revertible.
Bucket walls (engine #34): the volume container is an open-top
bucket — four thin walls (visual + colliders) around the 1x0.5x0.5 m
box so objects cannot pass through the sides; the top stays open.
Walls are a StaticBody3D on WORLD, always present (open and
closed); the closed-box solid collider is separate work.

Physics-lock inside a volume stores the item: WorldItem._lock()
now stores the item in the volume it locked inside (via
VolumeContainer.find_at), so a thrown object can settle on the
container floor and is then stored and counted. store() guards
closed / full / tag-gate / double-store.

Hand-stays-back, volume-scoped: the hand itself has no collisions
inside a volume (the HandCollider goes inert while the grip is
inside one, and the held object — not the hand — is the probe).
When the held object is inside a volume and its follow is blocked,
the collider clamps its desired position by the follow overreach,
so it does not advance past where the object stopped. Grabbable
records the block (follow_blocked / follow_block_normal /
follow_overreach). The glove visual is runtime-driven and is NOT
clamped here; the general hand-physics question stays parked.

Suite (volume_container_check, 134 checks): bucket-wall sweep,
lock-store, hand-stays-back + reactivation. The walls changed two
pre-existing paths, so the suite now routes them over the rim:
the release test lifts the cube up, east above the box, then down
into it, and the slab test rises straight up before going west
over the wall. The push test now asserts the follow stop (the
follow DOES see this stored cube) plus a direct sweep from a
cleared position.
Mags are FILO stacks now: rack/fire/chamber pop the LAST child
(the tracer loaded 3rd of 13 fires 11th, not 3rd). Bullets are
procedural cylinders (the bullet GLBs are deleted); loose rounds
carry a red/green base band keyed off the ammo tag, mirroring the
mag base band. Mags get a grip_offset (primary grab point up/forward
so the hand holds the lower part) and an opening (top face centre,
where rounds enter).

Insert: a loose round at the opening goes in on its own; a stack
held at the opening loads one round per trigger press (caliber gate
kept). Eject: hold trigger on a held mag pops the next round out as
a loose bullet floating above the opening; releasing drops it unless
the other hand grabbed it. The first-out round renders at the top of
a loaded mag.

Suites: mag_filo_check (FILO order, caliber gate, rack/fire feed,
first-out render, grip offset, bands) and mag_interaction_check
(eject float, stack load, grab + re-insert, drop). Bridge e2e gains
test_mag_interaction (drop mag from the gun, eject, grab, re-insert,
drop). spawn_check updated for procedural bullets.
Zach's in-headset corrections (2026-11-09 12:30):

- grip_offset: pistol mag/ammo now [0, -0.03, 0.01] (Zach's values;
  the d2431fe [0, 0.03, -0.015] had the sign inverted - grip_offset
  adds to the PRIMARY grab point, so negative Y = hand holds the
  lower part, mag extends up). Rifle flipped the same way.
- grip_rot: new model-block knob (pitch, yaw, roll degrees) added to
  the PRIMARY grab point after the authored pose, so grip orientation
  is tunable from JSON. Default [0,0,0].
- magazine opening is now {pos, rot}: rot [-90, 0, 0] (pitch) lays
  the bullet along the mag with its base at the rear (NOT [90,0,0]
  - +90 puts the base at the front). Insert, eject float and the
  first-out render all use the opening basis; the first-out round
  now sits AT the opening instead of sunk by a quarter length.
- Floor swallows bullets: a 9 mm round's collision sank ~10 mm
  below the floor, so the ranged-grab line of sight hit the floor
  first and the round could never be acquired. Root cause is the
  size, not the shape: a 0.009-thick box proxy sinks the same way,
  while a 0.02-thick box rests. Tiny cylinders (radius < 0.02)
  now get a box collision proxy at the 0.02 m minimum the box arm
  already uses; the visual stays a cylinder. New regression suite
  tests/floor_bullet_check.gd (in CI).

Verified on lantern-vm: double import 0/0, check.py 6/6, 31 suites
PASS (1213 checks; only the pre-existing LevelLoader add_child +
firearm is_trigger_down SCRIPT ERROR noise), bridge e2e PASS.
The VolumeContainer store check in _release used grab_origin(), so a
cube held against the outside of a bucket wall was stored when the
hand was inside the box. Check the item's actual position
(wi.global_position) instead.

The volume suite gains three phases (9-11, old 9-19 renumbered to
12-22): spawn a cube west of the box, drag it straight east so the
-x wall stops it while the grip reaches the centre, then release and
verify it is not stored, is outside the box, and stays a free world
item. The wall cube is freed after the drop and the grip moved out
of the volume so the later collision tests still see a live hand
collider.
Phase 0 dead sweep: remove dead ammo/weapon/rule/code
Some checks failed
tests / test (pull_request) Failing after 1m51s
92985ecf59
No behaviour change. All targets re-verified dead at build time
(zero .gd readers):
- ammo damage_mult (3 ammo JSONs; the only damage_mult reads are
  NPC-head shape-node meta, a different thing). The ammo table is
  now empty; is_type("ammo") still matches on the key.
- two_handed boolean (3 weapon JSONs; subsumed by the
  weapon_one_hand / weapon_two_hand type key).
- flat_fov (base.json rule + gen_mechanics.py PLAYER_GROUP).
- Grabbable.current_pivot (zero callers) + the stale midpoint
  header comment (now states the #33 primary-grip revision).
- GenericItem.resolve_grip + _detect_from_da_rig (game-dead;
  module_check tested them directly, those blocks go too, and
  cases 6-10 renumber to 5-9).
- save_snapshot / load_snapshot / serialize_items /
  deserialize_items (save_game.gd, zero callers). The per-item
  serialize_item / deserialize_item primitives stay: Phase 4
  persistence builds on them.
- gen_mechanics.py also emitted a "two handed" row from the dead
  boolean (defaulting to false once the key was gone); that
  emission is removed too.

MECHANICS.md + mechanics.json regenerated (118 values, was 121):
flat fov and the three "two handed" rows are gone. modding.md
Laser Rifle example drops two_handed.
e2e: force the Compatibility renderer under Xvfb
All checks were successful
tests / test (pull_request) Successful in 2m11s
425ffdf1ca
The project is mobile (Vulkan) since 4cb4580, but the e2e launches the game under Xvfb expecting the Compatibility renderer. On a machine with a Vulkan device, Vulkan cannot present under Xvfb (no DRI3), so the X11 display driver fails and the game exits at launch before the bridge starts. Forcing --rendering-driver opengl3 uses Mesa software GL under Xvfb, which works.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
dead-air/engine!36
No description provided.