THE WALK-THROUGH BUG WAS NOT THE COLLIDERS. Knight::tick wrote a triangle wave
straight into self.pos, and find(|e| e.tag == "knight") returned None — no
mover, no sweep, no Character block. He passed through benches, houses and
trees alike because there was nothing to collide WITH. The collider maths was
right all along, which is exactly why the house-wall and tree-trunk tests
passed while the user kept reporting walk-through three times over.
Dumping the bench's real collider before changing anything confirmed it:
graveyard-kit/bench yields 1.36 x 0.9 x 0.78 at ground level — a perfectly good
obstacle that nothing was ever tested against.
The Knight is now a BodyKind::Mover (hidden, so the mesh stays his appearance).
tick sets a HEADING; desired_velocity feeds entity.vel; his rendered position
is read back AFTER the sweep. Intent goes in, physics decides where he ends up.
Two things that fell out: his half-extents would have been 1.4 m wide and 3.6 m
tall, because spawn takes FULL size; and his patrol line ran straight through
the bench row, which — now that he genuinely collides — would jam him against
the first bench forever, so he walks the pavement between road edge and
furniture. Walking AROUND obstacles is NPC behaviour, not layout.
prop_collision.rs loads the real bench GLB, reproduces compose_village's
scaling, and walks a Knight-sized mover into it. spawn() now routes through
push_entity rather than entities.push, so the sorted-id invariant is asserted
rather than assumed.
VARIETY WIRED: 5 house designs instead of one model five times, 4 distinct
pines, a real lamp post instead of a CACTUS, two real benches instead of a
coaster-train carriage and a park entrance. Two genuine bugs in the variety
layer, both making find()'s correct answer worse:
- dominant_pack SUMMED 60 hits, so mass beat quality: nature-kit's incidental
"tall" matches out-summed racing-kit's three lightPosts, and a lamp query
returned a cactus. Only hits within 25% of the top score count now
- Spread::Mixed wanders on multi-word queries — "park bench wooden" let bench,
coaster-train-wooden and park-entrance each pass on one word
RANKING: whole_query_bonus tested only the ENTIRE query, so "fence" scored the
real fence 28 while "wooden fence" scored it 8 — tied with everything and
decided alphabetically, which is how asking for a fence returned arena/wall.
STATIC SHADOWS 15 -> 69 CASTERS: base_y was the MAXIMUM static top, and the
per-prop colliders are static entities, so the receiver plane sat at roof
height and every prop projected onto a plane above itself. Each prop now uses
its own lowest point — also correct on a slope.
Fog 0.004 -> 0.0015 (24% -> ~10% wash at the treeline), set on the demo rather
than SkyConfig::default() which gamemaker also reads. Fence spacing derived
from the panel's own scaled width. Crate stack is a 3-2-1 pyramid, not a
six-high chimney. Aspect guard so a short wide model can't explode sideways
into a coloured slab — any library picked by description eventually returns
something oddly proportioned.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stage as presentation, never simulation. Stage{mode, origin, yaw, scale} is
applied as the scene draw list's view_transform uniform — one value per
frame, so sky/terrain/cubes/skinned characters all move together, cached
static slabs never invalidate (they hold stage-independent world transforms)
and per-instance cost is zero. The view matrix cannot carry this: in XR the
platform overwrites camera_view with the runtime's eye matrices every frame
(openxr_opengl.rs:20-25), discarding anything the app wrote.
- MrDiorama: world scaled onto an anchored slab, sky/fog/terrain-horizon
suppressed (the room IS the environment), shadow-catcher quad so it looks
planted. VrFullScale: 1:1, environment intact. Flat: unchanged
- stage_invariants.rs proves the design point: two worlds run 120 ticks, one
switching flat->MR->VR->MR(new anchor)->flat mid-run, hashes bit-identical
at every switch, with an assert_ne against a fresh world so the equality
isn't vacuous. The stage has no API by which it could reach the world
- Stereo needed NO work, and rendering twice would have been wrong: XR uses
single-pass GL_OVR_multiview2, so draw_pass.camera_view compiles to an
indexed [VIEW_ID] lookup and the GPU rasterizes both eyes from one
encoding. The existing draw path is already stereo-correct
- XR input maps controllers/pinch onto the same per-player InputState the
net layer sends — an XR player is just another player to the sim
- Quest APK packages (arm64-v8a, passthrough/handtracking/anchor/colocation
manifest bits) with no cargo_makepad changes
- Settings panel: provider/model pickers, masked key entry, pair button
showing LAN URL + confirm code, active-tier status line
- Authoring inbox: keyless clients' Intent::Authoring queued for the host's
agent, bounded and refusing with a reason — every entry eventually costs a
paid call and it is filled by peers who hold the lobby key but are not
trusted terminals
Pre-existing startup crash fixed: run_tick held a RefMut across
self.world.borrow() (`let _ = w` drops the reborrow, not the RefMut it came
from), aborting the demo path. Reproduced at HEAD with changes stashed.
Not wired, needs a device: passthrough is requested via
StageMode::wants_passthrough() but not handed to xr_passthrough (arcade has
no XR root widget yet); env-depth occlusion has a hook and no consumer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>