Both jobs failed, at genuinely informative points:
- linux-x64: DllNotFoundException loading lingua_physics — the .so
committed for scripts/run-sample.sh's local-dev convenience was built on
this dev machine's own (newer) glibc, and didn't load on ubuntu-latest's
runner. liblingua_audio.so, built the same way, loaded fine there — this
wasn't a generic "the file isn't where expected" problem, it was
specific to what that one binary happened to require. Fixed by having CI
rebuild and overwrite the native libs fresh for both platforms, every
run, rather than trusting the committed one for anything but casual
local use — only a binary built on the actual target platform is
trustworthy on it.
- win-x64: "WGL: The driver does not appear to support OpenGL" — from
inside the --headless verification run. --headless only controls
whether Engine.Host's own loop pumps a window; it does nothing to stop
a *loaded* engine.windowing/engine.render from creating a real window
and GL context regardless, which the full PhysicsDemo project always
does. Fine on a real desktop, fatal on windows-latest's GPU-less
runner. Fixed with a separate, minimal verification stage — engine.
physics only, project.ci-headless.json/scene.ci-headless.json, no
windowing/render/audio/game plugin at all — alongside the original full
stage, which still produces the real uploaded artifact. engine.physics
needs no display, so this is what CI can actually check without one;
audio's own correctness is separately covered by Engine.Audio.Tests
(forced onto miniaudio's null backend already) on both platforms.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N1qPfzq8TDCUMFMV3UwV5N
samples/PhysicsDemo ties physics, audio, input, and rendering together
through nothing but the kernel's own vocabulary — no plugin here
references another plugin's implementation, only Contracts. Press Space
to drop a box; engine.physics simulates it falling onto a static ground;
CubeRenderer (new — engine.render's QuadRenderer drew flat cards, no good
for a physics demo where a BoxCollider needs to actually look like a box)
draws it; physics-demo-game watches each spawned box's own Y velocity via
IPhysicsService and plays a bounce sound via IAudioService the moment a
real fall settles. A looping ambient track plays throughout via
AudioSource's own PlayOnAwake. project.json deliberately excludes
engine.editor — this is the shippable configuration M4's "done when"
actually asks for, not the dev one.
"Landed" isn't a Box3D contact event — the native shim never exposed one
(nothing but scalars crosses that boundary, see lingua_physics.c). Watching
velocity every frame is the honest, right-sized alternative for a demo
this size, not a shortcut around missing infrastructure.
Verified two ways. First, real Space-key input isn't simulable here (no
xdotool/ydotool under this Wayland session) — so PhysicsDemoGame.Tests
drives the actual PhysicsDemoGamePlugin.Configure/Tick through the real
Schedule with a controllable fake IEngineInput (and fake IPhysicsService/
IAudioService, since engine.physics/engine.audio's own correctness is
already covered elsewhere): spawn-on-press with edge detection, the
Rigidbody/BoxCollider/CubeRenderer combo the spawned box actually gets,
and — the case most likely to be subtly wrong — a box that never actually
falls doesn't false-positive as "landed," only one that fell past
FallingThreshold and then settled does, exactly once. All 5 passed
immediately. Second, a real windowed run: a box placed in scene.json above
the ground visibly falls and settles onto it on screen after a real
few-second wait — screenshotting by --screenshot-after-frames alone turned
out not to prove this (VSync is off, so frames race by far faster than
real physics time passes; enough elapsed frames isn't enough elapsed
seconds), an interactive run with a real sleep before the screenshot
command is what actually shows it.
Full suite: 92 tests. Remaining for M4: the Linux + Windows build pipeline.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N1qPfzq8TDCUMFMV3UwV5N