11375214 fixed the misplaced dependency block by adding a correct copy
inside [dependencies], but the misplaced copy below [features] was still
present -- it had already been moved back by 5eda8056, the commit
11375214 is built on. The result is three dependencies declared twice:
error: duplicate key
so widgets/Cargo.toml still does not parse and every consumer of the
fork is still blocked, just with a different message.
Two people fixed the same bug in parallel. Removing the second, now
redundant, copy; the surviving declaration sits with the other optional
sibling crates in [dependencies].
Verified:
cargo metadata --manifest-path widgets/Cargo.toml \
--features maps,csg,gltf,test
resolves.
The makepad-gltf, makepad-csg, and makepad-test dependencies were incorrectly
placed after the [features] section, causing TOML parsing errors. Moved them
to the [dependencies] section where they belong.
The "Update fork to upstream dev 5d4483f" merge relocated this block:
makepad-gltf = { path = "../libs/gltf", optional = true }
makepad-csg = { path = "../libs/csg/csg", optional = true }
makepad-test = { path = "../libs/makepad_test", optional = true }
from the end of [dependencies] to below the [features] header. TOML has
no way to know these are dependencies once they sit under [features], so
cargo parses each as a feature definition whose value should be an array
of strings and fails:
error: invalid type: map, expected a sequence
--> widgets/Cargo.toml:47:16
Every consumer of this fork is broken as a result. The three crates stop
being dependencies at all, so the features that gate them --
gltf/csg/test, and maps via i_overlay -- no longer exist:
package `nigig-map` depends on `makepad-widgets` with feature `maps`
but `makepad-widgets` does not have that feature.
help: available features: default, serde
At the previous rev (2c5cd97) the same three lines are inside
[dependencies], which is why that rev resolves and this one does not.
This is a pure relocation -- the six moved lines are byte-identical, no
version, path or flag changed. Verified with
cargo metadata --manifest-path widgets/Cargo.toml \
--features maps,csg,gltf,test
which fails on d6d1f99c and succeeds with this commit.
- Add drape.rs for terrain hillshade landcover draping
- Add overlay.rs for route polylines, markers, and position puck
- Add icons.rs and icons/ directory for map icon management
- Update geometry.rs with 3D road elevation and join improvements
- Update tile.rs with unified road mesh rendering
- Update view.rs with seamless 2D/3D mode transitions
- Update style.rs with night themes and emissive roads
- Add i_overlay dependency for polygon boolean operations
- Update maps feature to include i_overlay
Key improvements:
- 3D road elevation and seam continuity
- Building shadow geometry and terrain cast shadows
- Route assistant and navigation layer support
- Clickable themes and night mode
- Water, grass, and shrub rendering
- Optimized road geometry and mode transitions
* PortalList: add `set_flow(cx, flow)` to switch a list between vertical and
horizontal layout flow at runtime. Much faster than using `script_apply_eval`,
and always fully correct because it updates the `vec_index` axis.
It also avoids a full ScriptReapply sequence, which is potentially expensive
across all of an app's widgets.
* AdaptiveView: add `active_variant()` getter so that widgets using AdaptiveView do not have to separately track which variant it should be in. Removes all ambiguity and possibility of divergence... finally!
* Also fixes long-standing TODO item in AdaptiveView about properly handling
weird window geom events, e.g., on macOS sometimes it spits out a 0-width
window update which is total b.s.
* DisplayContext: add `is_desktop_width()` helper.
* Window: ignore spurious zero-size window geom events.
Replace the abandoned arc2 colour-stub with a real path primitive: the
distance to a bare circular-arc CENTERLINE (no baked thickness), so it
chains after move_to/line_to and is covered by a single stroke(w) -- unlike
arc_round_caps/arc_flat_caps which bake their own width. Angles in radians,
0 = +x axis, counter-clockwise positive; last_pos advances to the arc end so
a following line_to connects.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(draw): add sdf.arc_to circular-arc path segment
A `Fill` child under Flow::Right (or `height: Fill` under Flow::Down) is
deferred and given the row's full slack up front, so end_turtle's align
step takes the deferred branch, which distributes width/height to fills
and drops main-axis align entirely. That's correct when a fill consumes
its whole slot, but a fill that draws narrower than its slot (e.g. an
Image that aspect-fits, or `Fill{max: N}` capped below the container)
leaves real slack that then anchors to the start regardless of align.
Add row_align_x_shift / col_align_y_shift: reclaim align * (inner -
actually_drawn) in each deferred branch. They early-return 0 when align
is 0, the inner size is unknown, or the content fills, so the only
behavior change is deferred-flow containers that set main-axis align and
hold an under-filling Fill child.
Add examples to the uizoo "Layout Demos" tab that proves they boht work.
redrawing is significantly more expensive than text comparisons,
and doing this outside of each widget is difficult and less efficient.
this helped avoid a bunch of redraw cycles in robrix,
so it's probably worth doing for all widgets, especially since
setting text or another basic property now does an auto-redraw
(it didn't used to be like that)
* Fix rendering, gradient, sampling, etc issues on older GPUs
* Fix bug in `box_y` sdf function, which caused gradients to be split
into two bands incorrectly. Mostly a problem on lower-res screens.
* Use per-texture filtering instead of GL sampler objects on Linux,
especially for Mesa drivers that ignore min filter samplers.
This should help prevent blocky/pixellated things like emoji/avatars
* Improve rendering sharpness on low-DPI screens (icons, emoji, images)
On 1.0-DPI screens, emoji/SVG-icons/avatars were minified without
adequate sampling and SVG AA was sub-pixel, producing blocky/aliased
output. This reworks each path and adds optional full-window SSAA.
SVG icons (device-aware AA + round caps):
- draw_svg.rs/draw_vector.rs/render.rs: size the fill & stroke AA
fringe and the curve-flatten tolerance in DEVICE pixels (≈constant
regardless of icon size), so edges resolve via the analytic
d/fwidth coverage and curves stay smooth at any scale; re-tessellate
on scale change.
- triangulate.rs: thread the flatten tolerance through path fill/stroke.
- tessellate.rs: emit round caps as a solid disc (u=0.5) instead of a
radial fade that collapsed to a square at small sizes.
- widgets/icon.rs: don't clip the Icon to its Fit bounds, so round
caps that extend past the box render fully instead of being sheared.
Emoji:
- glyph_raster_image.rs/rasterizer.rs: rasterize color emoji near the
on-screen size with an alpha-weighted box downscale (geometric-mean
scale factor) instead of the font's native PNG strike.
Images / avatars (mipmaps):
- image_cache.rs/texture.rs/draw_list.rs/lib.rs: optionally emit a CPU
mipmap chain (VecMipBGRAu8_32) for non-animated images so minified
avatars/thumbnails sample cleanly. Env-gated MAKEPAD_IMAGE_MIPMAPS;
default on for GL on Linux.
- metal.rs: real per-level mip upload. d3d11.rs/vulkan.rs/web_gl.rs:
safe single-level fallback (no crash; real mips TODO).
Full-window supersampling (optional):
- window.rs: render the whole UI into an offscreen target at
MAKEPAD_SUPERSAMPLE× device resolution and downscale-resolve into the
window. Default 2×, env-tunable, 1× disables. Modeled on the existing
GaussStack render-to-texture path.
* don't unconditionally enable supersampling SSAA of 2x by default
it's too expensive and too slow for most older devices
* cleanup, reduce comment verbosity
* improve SVG anti aliasing
* Windows: fix laggy/juddery scroll performance
- Pace the render loop to the display refresh using a DXGI frame-latency
waitable object and present with vsync, replacing the free-spinning,
uncapped Poll loop that caused uneven scroll cadence.
- Coalesce consecutive WM_MOUSEMOVE messages and paint once per loop pass
to stop the judder when moving the mouse during fling deceleration.
- Cache get_dpi_factor() and the WM_NCHITTEST WindowDragQuery result to
avoid per-mouse-move GetDeviceCaps syscalls and widget-tree hit-tests.
- Throttle XInput/DirectInput polling of empty/disconnected controller
slots, which was stalling the UI thread.
- Rework the momentum fling to a native exponential model with a
frame-interval EMA, and stop the tail auto-scroll from fighting an
active fling/drag.
- D3D11: update the glyph atlas and image textures in place via
UpdateSubresource instead of recreating them on every change, and
spread D3D11 shader-object creation across frames.
- Slug atlas: only force a full re-layout on a width change; append rows
on height growth.
* Windows: correctness fixes from review (off the scroll hot path)
- d3d11: close the DXGI frame-latency waitable HANDLE in Drop (it was
leaked once per main-window lifecycle and the field comment was wrong);
keep popup swap chains at frame-latency 1; track the waitable-swapchain
flag for ResizeBuffers instead of inferring it from the handle; present
without the vsync interval during a live resize.
- windows.rs: poll game input on the idle signal tick so a gamepad button
can be serviced while the app is otherwise idle.
- win32_window / window: invalidate the WM_NCHITTEST / WindowDragQuery
caches on window move and on a caption relayout, with a generation
counter guarding against a reentrant invalidation being clobbered.
- windows_game_input: detect a controller already plugged in at launch via
a one-shot full scan on the first poll, probe slot 0 (Player 1) first,
and offset the DirectInput enumeration so it never stacks with the
XInput probe.
- comment/doc corrections.
* Image cache: accept any Arc<D: AsRef<[u8]> + ?Sized> for async image data
The load_image_from_data_async family required Arc<Vec<u8>>, forcing callers
that already hold the bytes as Arc<[u8]> (e.g. a content-addressed media cache)
to copy the whole buffer via .to_vec() just to satisfy the type. Generalize the
data parameter to Arc<D> where D: AsRef<[u8]> + ?Sized, so those callers can pass
their existing Arc by refcount-clone with no byte copy. The decode path only ever
borrowed the bytes (&[u8]), so this is purely a signature relaxation; existing
Arc<Vec<u8>> callers are unaffected (D = Vec<u8>).
* Linux: GL glyph-atlas in-place texture update + X11/Wayland mouse-move coalescing
* Scroll: unified fling model + native trackpad momentum deceleration
Share one kinetic-scroll model between PortalList and ScrollBar (and thus
ScrollXView/ScrollYView/ScrollXYView) via a new widgets/src/scroll_motion.rs:
- Touch-drag flicks use an iOS-style exponential self-decay, frame-rate
independent via a per-frame integrator with dt smoothing.
- Trackpad scrolling applies the OS momentum directly while fast (responsive,
full native speed), then hands off to a gentler self-decaying tail once it
slows past a threshold, so the deceleration is longer and smoother than the
OS's short, choppy tail. Handoff is seeded at the current speed for a
continuous transition; the seed is clamped against degenerate event timing.
- Add ScrollPhase to scroll events, mapped from NSEventPhase/momentumPhase on
macOS and wl_pointer AxisStop on Wayland; None elsewhere (wheels/X11/Windows
behave as before). MAKEPAD_RAW_TRACKPAD_MOMENTUM=1 bypasses the smoothed tail.
- A press catches an in-progress fling (stops the scroll, consumes the press so
it doesn't also activate a child), matching iOS/Android/macOS.
* Shader codegen: prefix Metal/WGSL locals to avoid reserved-word collisions
The Metal/WGSL backends emitted user-declared shader locals verbatim, so a
local named after a reserved type keyword (e.g. `half`) produced invalid
shader source and failed to compile at runtime. Prefix them with `l_` like the
HLSL/GLSL backends already do.
* TextFlow: don't panic on unbalanced HTML close tags
end_code/end_quote unwrapped the area stack, so a stray `</pre>` or
`</blockquote>` in untrusted content (e.g. a chat message) panicked. Return
early instead, and drop a vestigial per-list-item area-stack push that leaked
an entry and could hand a stray close tag the wrong block's area.
* Scroll: expose fling decel, handoff threshold, & tail-decel as `#[live]` fields
This allows app devs to override the scroll feel per-widget in the DSL,
or globally by overriding the base widget's defaults — verified that a DSL
override takes effect.
* Windows: fix frame pacing, paste crash, and wheel input backlog
- Wait on the frame-latency waitable right before each window's vsync
present instead of on every Paint, so input no longer stalls behind
waits that have no matching present. Drain leftover credits after a
live resize.
- Pasting when the clipboard has no text no longer panics.
- The poll loop now handles up to 32 messages (2 ms) per frame and
merges consecutive mouse-wheel messages, so fast wheels can't build a
backlog that keeps scrolling after the gesture ends. Sleep 1 ms when a
frame presents nothing so animation polling doesn't spin a core.
* Image: don't build unused mip chains; fix stale images in recycled widgets
- Only build the CPU mip chain when a backend actually uploads it
(Metal, behind its env var), and build it on the decode thread instead
of the UI thread. Linux GL still gets its mipmaps via glGenerateMipmap
and now retains less CPU memory per image.
- Recycled Image widgets no longer show the previous item's image or
apply an old decode result. Placeholder textures set via set_texture
(like blurhashes) stay visible while the real image decodes, and a
failed load clears the widget instead of leaving old content up.
* Widgets: avoid needless caption redraws; cheaper PortalList height tracking
- Label::set_text does nothing when the text hasn't changed, and the
window caption title is only synced when it actually changes, so mouse
moves and animation ticks no longer redraw the whole window every
event. The caption centering padding requests its own redraw now.
- PortalList records item heights only when new or changed, and only
re-applies the default height after it drifts by half a pixel, so big
lists don't walk every unmeasured item on every scroll frame.
* Text: cache layouts of long texts; stop cloning glyph outlines every frame
- The layout cache now accepts texts of any length (long messages and
code blocks used to re-layout on every scroll frame). It is a real LRU
with a byte budget on top of the entry cap, and texts drawn in the
current frame are never evicted, so one heavy frame can't thrash the
cache into a permanent miss cycle. The shaper cache is LRU now too.
- Glyph outlines are shared via Rc, so drawing a cached glyph no longer
copies its command list, and outline complexity is computed once when
the outline is built instead of every frame.
* Html: fix stale links/spans in recycled widgets and <details> renumbering
- set_text only rebuilds when the content actually changed, so a
recycled link can't open the previous message's URL, and re-setting
identical content keeps the user's <details> open/closed state.
- Custom widgets and <details> are keyed by their node index instead of
a visit-order counter, so toggling a collapsed <details> can't
renumber the widgets after it and rebind them to the wrong nodes.
item_with_scope also recreates its widget when the template changes.
* Linux: fixed-distance wheel scrolling; Wayland frame-callback pacing
- Wheel scrolling moves a fixed 60 px per detent on X11 and Wayland
instead of a timing-based guess that flipped between 12 px and 240 px
depending on how events batched. Wayland reads real detent counts via
AxisValue120 (wl_seat v9, with AxisDiscrete as the older fallback) and
maps keymaps MAP_PRIVATE as v7+ requires. Touchpads are unchanged.
- Wayland frames are paced with wl_surface frame callbacks and swap
interval 0, so redrawing a hidden or minimized window can no longer
hang the whole app inside eglSwapBuffers (compositors withhold frame
callbacks for hidden windows). Windows with a callback in flight skip
presenting and stay dirty; X11 keeps vsync exactly as before.
* Text: bigger layout cache budget, reclaimed at the end of each frame
A maximal ~60 KB message lays out to roughly 4 MB of glyphs, so the 4 MB
budget couldn't hold even one alongside a normal screen. Raise it to
16 MB, and run eviction at the end of every frame so memory over the
budget is freed one frame after its content leaves the screen, instead
of lingering until some later layout happens to insert a new entry.
* Fix oversized uniform slices: the array lengths were in bytes, not f32 elements
* Wayland: flush buffered mouse motion before scroll events, and drop motion for closed windows
* GL: fall back to non-mipmapped filtering when glGenerateMipmap fails on strict GLES3 drivers
* Text: bucket emoji raster scales so zooming reuses atlas slots instead of re-decoding every step
* Image: add has_content() and record texture provenance on cache-hit loads too
* Scroll: native trackpad momentum, Chrome-model bounce; presses catch motion, never click children
* Dock: redraw the newly selected tab immediately when the active tab is closed
* Html: standard link colors with pressed precedence; skip re-parsing unchanged text; color setters
* Image: don't redraw on cache-hit loads of the already-bound image (per-draw reloaders looped forever)
* Scroll: log macOS momentum-end phase bits to check Cancelled (touch-cut) vs Ended (natural fade)
* Scroll: momentum state machine; flicks survive pagination; edge sentinels & once-per-frame actions
* Scroll: remove the MAKEPAD_SCROLL_DEBUG diagnostics
* Scroll: time-based fling velocity window; pointer fan-out guard; parked flings survive pagination
* PortalList: optional reached-start/end margins (Some(0) default); repositioning re-announces the edges
* Linux/Wayland: honor UI zoom across window resizes; scale caption bar with zoom
Keep the wayland-side window geom in native units so the zoomed dpi isn't
read back as "native" on the next Configure — UI zoom no longer resets or
flickers on maximize/tile. Also let the caption bar height scale with the
zoom (pin to native only on macOS, where the buttons are OS traffic lights).
* Windows: D3D11 fixes for drawlist mgmt
trying to help with the `new_batch` bugs
* draw_list.rs — `set_zbias` now returns whether it changed
* d3d11.rs — uploads draw_call_uniforms on the given condition:
`uniforms_dirty || zbias_changed || buffer.is_none()`
and the zbias advance is hoisted above the early-continues
* Scroll: hard flicks carry farther and boost on re-flick; iOS-style touch rubber band with per-edge bounce gating; Android fling spline behind a flag
* Splitter: redraw both pane subtrees on drag; cached views keep fresh fixed sizes in the dirty check
* iOS: re-deliver window-geometry changes dropped by re-entrant UIKit callbacks; Init only ever from the first draw
* final cleanup fixes. Ensure all examples, experiments, `studio` all work
* Image: skip re-parsing an SVG that is already shown, keyed on the caller's shared bytes
* CachedView: fix upside-down offscreen texture on GL/GL-ES
GL/GL-ES store offscreen FBOs bottom-up, unlike Metal/D3D/WGSL.
The DSL->script-shader migration switched the CachedView composite to plain
.sample() (non-flipping sample2d on GL), so cached views rendered
vertically mirrored on GL/GL-ES; Metal was fine on macOS.
Add a `sample_rt` script sampler (emits the V-flipping sample2d_rt on
GLSL, plain no-flip sample elsewhere) and use it in the CachedView and
CachedRoundedView composites.
* Fix `AdaptiveView::redraw()` to actually do something
Deadzone-rescaled right stick feeds the exact mouse-drag pipeline: same
0.01 rad/px orbit through pseudo-pixels (~2.6 rad/s full deflection, stick
up = look up), same look_dx/look_dy for scripts, same chase-rig authority
(stick held = kid owns the camera, recenters after release) and
cam_dragging visibility. Applied before script camera writes each tick,
like real mouse events, so set_cam_yaw still wins its tick. Zeroed under
tape tests for determinism. Camera-only pads now count in device selection.
splashgame.md: right stick documented; new rule — every new ability must
also be reachable from the gamepad (bind to the named actions).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
VoiceWave grows an opt-in ptt_use_escape flag (Escape doubles as cancel/
dismiss elsewhere, so hosts choose); both keys drive the same logical talk
button. Gamemaker opts in via the caption_bar's hidden voice_wave and the
hints now read 'hold Esc' — the big friendly key for kids. Verified the
nested named-child merge lands on the live widget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verdict on the gamemaker judder (measured, 25s A/B runs, same window size):
gauss OFF -> unchanged (25-33ms gaps) [pyramid exonerated]
empty game world -> unchanged [game exonerated]
vsync OFF -> FIXED (119.8fps steady, worst 9ms; slow callbacks 124->2)
The stall is CAMetalLayer display-sync throttling: the compositor (hardware-
mirrored + SwitchResX-scaled 7680x2160) returns drawables unevenly, and
nextDrawable blocks the main thread 10-25ms in phases. The durable fix is
present-gated pacing: track in-flight presents via addPresentedHandler and
skip the paint when the pool is busy instead of blocking the event loop.
MAKEPAD_TIMER_TRACE=1 logs paint-clock fire-to-fire gaps >20ms and slow
callbacks >10ms with a per-step breakdown (live_edit/net/pad/paint/gc).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fixed 8ms timer0 presented ~125Hz into a 120Hz vsync queue: the drawable
pool drifted full and nextDrawable blocked the main thread in a ~25-frame
sawtooth (PerfGraph 'wait' ramps). Timer now arms at 1.002/max_fps across
attached NSScreens — the +0.2% makes NSTimer lateness drain the queue instead
of accumulating. Sawtooth verified gone.
perf_monitor: 'gpu' channel — presented-frame GPU interval tapped from the
existing command-buffer completion aggregation (atomic hand-off, folded at
frame_boundary; concurrent with CPU channels, plotted violet).
metal: MAKEPAD_GPU_PASS_TRACE=1 logs per-pass GPU time ([gpu-pass] name ms)
for frame-budget hunts.
Measured (gamemaker, empty world vs full racing game — identical ~4.7ms GPU):
the frame cost is the UI glass pipeline, not the game — scene capture +
6 mip downsamples + 3 smooth upsamples re-run every frame at 120Hz because
the game pane dirties the window; at low GPU clocks the 11-pass chain's
latency swings past the 8.3ms budget in phases.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
platform: Cx.perf_monitor — per-frame ring (240) of paint-to-paint gap +
per-channel CPU us. Built-in channels: event dispatch (outermost, minus
app-attributed time), script exec, GC, pass encode, nextDrawable wait.
Apps register custom channels: cx.perf_monitor.channel("physics", rgb).
Off until enabled; hooks in event dispatch, macos repaint, metal draw_pass.
widgets: PerfGraph — corner-pinned live panel (DrawVector strips): frame-gap
bars colored against 120/60Hz budgets with guide lines, stacked per-channel
CPU, legend with averages. Self-positions bottom-right (DrawVector geometry
+ deferred turtle alignment don't mix — no aligning parent).
gamemaker: PerfGraph hovers the game pane (F3 toggles), engine feeds script
+ physics channels (incl. hot-reload evals); engine text overlay moved to
F4; per-phase engine window kept for ag perf / AIGAME_PERF=1; new 'ag perf'
harness verb + template guidance (template CLAUDE.md force-added: runtime
resource, blanket CLAUDE.md gitignore had kept it untracked).
Measured on my-game-5: engine frame CPU ~0.3ms; the hiccup is frame pacing —
the 8ms NSTimer paint clock beats against the 120Hz display, the drawable
pool drifts full and nextDrawable blocks the main thread in a ~25-frame
sawtooth (avg 2-3.4ms, spikes 20-30ms). The graph shows it as red ramps.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
libs/tts was never tracked despite being a build dependency of the gamemaker
example. tools/download_tts.sh fetches the public upstream weights (HuggingFace
Kokoro-82M + whisper.cpp) and converts them locally with the in-repo stdlib-only
converter; model artifacts are gitignored.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The parallel batch machinery from be21d627a only ever existed to chase a
default-on multi-threaded win that never came (washer w8 still +54% with
the hybrid on). At w1 — the only configuration this opt-in flag is for —
parallel_for runs inline, so the serial path gives the identical result
for ~340 fewer lines. Wire up the previously-dead
dynamic_tree_self_pairs/cross_pairs into a serial collect_batch_candidates
(three BVTT self/cross traversals -> canonical (a,b,child) sort -> serial
filter into move_results[0]) and delete BatchWork, BatchCtx, batch_drain_*,
BatchFilterCtx, batch_filter_*, bvtt_step, dynamic_tree_bvtt_drain/expand,
and the batch_frontier/worker_* scratch fields.
Determinism preserved exactly: OFF 0x61E35C31/step314 bit-identical, ON
0xBE99C5F7/step313 identical across workers 1/2/4. The debug SET-equality
oracle and the determinism_broad_phase_hybrid_across_worker_counts test
are unchanged and still pass; zero warnings.
PGO: pgo.sh never trained -bp=1, so an off-path-only profile laid the
hybrid branch out cold and collapsed the win to ~-5%. Add one -b=8 -bp=1
training run (neutral for the default path — counts merge, the OFF branch
stays hot) and retrain.
Corrected README numbers to measured values (hybrid-trained profile,
paired -bp toggle, washer w1): pair-finding stage 7.6k -> 3.6k ms/1000
(-52%); total ~-17% (~19.0k vs ~22.6k), which beats C (20661) and narrows
Rapier's lead from ~23% to ~12% — not the "17.7k / within 5% / -19%"
be21d627a claimed. Still default-off (w8 regresses ~+54%), opt-in
single-threaded accelerator for churn-heavy scenes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Broad-phase-hybrid subsection (why washer loses, the batch design, the
determinism proof, the single-thread win / multi-thread floor tradeoff,
and the default-off rationale). Washer row in the single-thread rapier
table annotated with the opt-in number (~17.7s, beats C, ~5% behind
rapier → box3d ahead-or-even on all nine single-threaded).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WorldDef.enable_broad_phase_hybrid (default false; -bp=0/1 bench toggle):
an adaptive batch broad phase for high-churn scenes. When
move_count*4 > proxy_count, replaces the per-moved-proxy tree queries
(8k proxies x 3 root-descents on washer) with three BVTT self/cross
traversals (dynamic self + dynamic x static + dynamic x kinematic) that
share the upper-tree descent, plus an O(n) bottom-up refit instead of
the median rebuild. Both traversal and candidate-filter are parallelized
across the task system (per-worker buffers → merge → canonical sort by
(shape_a,shape_b,child) → deterministic contact creation).
Correctness: a #[cfg(debug_assertions)] SET-equality assertion (batch
candidate set == per-mover set) runs in every test and never fires — the
proof the BVTT finds identical contacts (the hash can't prove it since
creation order legitimately re-baselines). New test
determinism_broad_phase_hybrid_across_worker_counts. OFF hash 0x61E35C31
bit-identical; ON hash 0xBE99C5F7 identical across workers 1/2/4 +
external tasks. 180/186/180/180 tests, zero warnings, profile retrained.
Single-threaded washer -18.7% (17715 vs 21780, broad phase -51%) — beats
C (20661), within ~6% of Rapier (16844). DEFAULT OFF because it regresses
multi-threaded (washer w8 +52%): the batch materializes ~40-50k
candidates/step and serially merges+sorts them (a fundamental floor the
inline per-mover path avoids by filtering in the query callback), so at
w8 the parallel per-mover queries win. Cannot be worker-gated (would
break cross-worker determinism). Correct, deterministic, zero-cost when
off — an opt-in single-threaded accelerator for churn-heavy scenes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the C Box3D column to the nine-scene rapier table, all from one
same-window interleaved single-thread run (2026-07-06). Percentages vs
the box3d Rust column. box3d Rust beats Rapier on 8/9 (junkyard flipped
to a +5% win post-tier-2; only washer lost, Rapier's incremental-BVH
broad phase). vs C: within ~7% geomean, ahead on both pyramid scenes,
worst is junkyard +18%. Replaces the derived † junkyard cell with a
direct measurement. Headline updated 7/9→8/9.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
query_tree_for_pairs was materializing every tree-query hit into a Vec
then re-iterating; C runs try_add_pair inline in the query callback
(b3PairQueryCallback). Rewrote to filter inline like C (both world
borrows are shared, so it compiles); only the rare compound inner-query
still uses a child_hits scratch (no compounds in washer/junkyard/
pyramids/trees, so pair-discovery order is unchanged). Removed the
now-dead PairScratch.hits field.
query_tree_for_pairs is washer's single hottest symbol (8k dynamic cubes
churned by a rotating drum re-query the whole tree every step). washer
broad phase -3% (paired plain + retrained-PGO), junkyard -1.4%,
pyramids/trees neutral. Hash bit-identical (0x61E35C31) — pure
structural, same discovery order. 179/185/179/179 tests, zero warnings.
Profile retrained dual-mode.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Instrumentation used to decompose the washer scene (which phase holds
rapier's ~24% advantage over box3d). Timer.rs drops the profiler-feature
gate so PhysicsPipeline's per-stage counters always measure (std Instant
instead of web_time); bench gains --stages (per-phase ms split) and
--probe (per-step contact/pair/sleep counts). Diagnostic tooling only;
no effect on simulation. Findings: washer's rapier advantage is entirely
broad phase (box3d ~8.5s vs rapier 1.7s), box3d's narrow phase is
actually faster than rapier's.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Derived cells marked with a dagger and the derivation stated: cold-window
baseline x the same-binary paired improvement (thermal-drift-immune);
direct cold-window rerun will replace them. vs C: junkyard +17%->+7% w1,
+22%->+12% w8, geomeans +6%/+8%. vs rapier: junkyard -8% -> ~parity,
geomean +34%, washer now rapier's only win.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New middle tier between full manifold recycling and the full SAT, behind
WorldDef.enable_feature_recycling (default true; OFF path bit-identical,
hash 0x61E35C31 verified):
- Case A, separated-witness early-out: a previously-non-touching contact
revalidates only the cached winning axis; still separating beyond the
speculative distance means done in one test. Sound structurally: any
cached axis is a valid separation witness (understates only, which
falls through to the full SAT). Carried junkyard: 24k skips/step,
full SATs 25.4k -> 4.5k per step.
- Case B, touching feature rebuild: re-clips the cached winning feature
under explicit staleness bounds (SATCache::sat_pose at last full SAT,
translation < 4x recycle distance, rotation < ~4.6 deg, forced refresh
every 8 steps); degenerate rebuilds and touching<->separated
transitions fall through same-step.
Probe-driven (junkyard: 89k full SATs/step on 105k pairs, 13k touching;
rapier maintains 3.4x fewer pairs): paired same-binary -fr=0/1 A/Bs show
junkyard -8% in every pairing (collide phase -15%), washer neutral to
-5%, pyramid/rain guards neutral, OFF costs nothing. Same-session
cross-engine junkyard: rapier's -8% lead closes to ~-3%.
pgo.sh now trains BOTH modes (single-mode training starved the remaining
full-SAT path); checked-in profile retrained dual-mode. SATCache pose
serialized in snapshots. feature_recycled_contact_count in Counters.
README: port-extension subsection, soundness argument, updated notes.
Also: rapier bench --probe mode from the workload-probe session.
179/185/179/179 tests green with the tier ON, zero warnings,
determinism suite passes across runs/workers/task systems.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- libs/rapier/crates/bench now mirrors every box3d benchmark scene
(trees100/50/25, junkyard, rain, washer added to large_pyramid/
many_pyramids/joint_grid), same -b indices as the box3d benchmark.
Geometry, densities, filters, spawn cadence and joint counts match;
body/collider/joint counts verified equal on all nine scenes.
- Fix an index-out-of-bounds panic in the vendored rapier simd-stable
constraint grouping (interaction_groups.rs): bodies in a different
island than the interaction (kinematic drivers, dynamics mid
island-merge) indexed the wrong island's conflict masks. Out-of-island
bodies are now exempt from conflict tracking, matching the solver's
existing boundary treatment (bounds-checked gathers, dropped scatters).
Original three scenes reproduce their previous timings after the fix.
- README: full nine-scene single-threaded matrix vs rapier (box3d wins
7/9, +33% geomean; rapier wins the hull-churn scenes junkyard/washer)
with comparability caveats for the extended scenes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Geomeans over the nine real scenes: +7% w=1 / +9% w=8. The scene stays
in the benchmark binaries (upstream suite parity); its fixed-overhead
story is kept in the known-remainder notes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
junkyard now shows its real post-fix +17%; footnote lattice replaced by
one measurement-conditions note. Geomeans +7% w=1 / +13% w=8.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pure full-update pipeline is +38% vs C (both engines, recycling forced
off), diluted to +17% by the at-parity recycle path. Gap is diffuse
(1.3-1.5x per pipeline function); all concentrated hypotheses measured
~zero, including a staging rewrite that halved build_face_a_contact's
instruction count with zero wall-clock effect (the bloat was cold code).
Note: sample attribution unreliable on PGO binaries (hot/cold splits).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Disassembly attribution on current binaries: junkyard's +23% sits
entirely in the NON-SAT narrow phase (2.06x C) — the edge SAT is now
FASTER than C. Cause: LLVM+PGO mega-inlining (third occurrence) —
update_contact compiled to 5.5x C's instruction count, collide_hulls had
no symbol at all. inline(never) on collide_hulls /
compute_convex_manifold / query_face_directions restores C's layout:
junkyard -3.6% paired (retrained profile), washer neutral, others
untouched.
hull_at coverage extended to build_face_a_contact / build_polygon /
clip_segment_to_hull_face / find_incident_face for contract completeness
— measured NEUTRAL beyond the boundary fix, and the feature's earlier
-3.6% is now captured by the safe attribute instead. README documents
the demotion honestly (the safe fix superseded the unsafe one).
Gates: 179/179/185/179 tests (default/unchecked-hulls/dp/nosimd), hash
0x61E35C31 everywhere, zero warnings. PGO profile retrained.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Matrix cells stay default-build (cold-window run); the hull scenes'
opt-in feature gains are annotated as paired deltas rather than absolute
cross-session numbers (thermal windows differ ~10%, mixing them would
misstate both). Algebraic-float-ops evaluation moved to a dedicated
'Evaluated ideas' section at the bottom.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
unchecked-hulls (off by default): elides bounds checks on hull-topology
indexing in the three SAT hot loops via a cfg'd accessor. Safety contract
= hull connectivity invariants validated at construction (hull.rs
is_valid_hull_impl + create_hull asserts), immutable behind Arc; debug
builds always assert, so every test run exercises the contract. Measured
(paired, retrained PGO): junkyard -3.6%, washer -2%, nothing elsewhere —
documented honestly that the checks were NOT most of the hull residue.
Tests 179 green with and without the feature; hash 0x61E35C31 both.
README: evaluation of the newly-stabilized algebraic float ops idea —
incompatible with the determinism contract as a default (compiler-
version/ISA-dependent results break cross-arch equality and cross-build
replay), modest expected upside since hot paths are already hand-
contracted; possible future opt-in, not planned.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A sweep of every C b3ParallelFor/enqueue dispatch against the port found
the finalize-bodies pass (per-body transforms, AABB updates, sleep
accounting, continuous/TOI) and the bullet pass were left serial when
threading was ported — C runs both under b3ParallelFor. rain's w=8 gap
was almost entirely this serial fraction (Amdahl decomposition showed
its parallel portion already at C parity).
FinalizeCtx mirrors the collide pass's pattern: taken arrays + SyncSlice
disjoint per-body access, per-worker task contexts, deterministic merges
(bitset OR, split-candidate max like C), bullet list via atomic cursor
(C's b3AtomicFetchAddInt mirror). No new unsafe primitives. Pre-solve/
custom-filter callbacks force single-worker like collide.
w=8: rain +42% -> +8%, joint_grid +36% -> +11%, large_pyramid and
many_pyramids and trees25 at parity; geomean +11%. Serial geomean +5%
(Rust wins joint_grid/large_pyramid/many_pyramids outright). PGO profile
retrained; README tables + narrative updated. Remaining known
serial-vs-C difference: the split-island enqueue overlap (documented).
Hash 0x61E35C31 bit-identical (runs, workers 1/2/4, external tasks);
179/185/179 tests, zero warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Training set was missing washer and trees50/25 (washer -1.5% with
coverage, large_pyramid unchanged — no dilution). Profile refreshed.
Small-stage main-only fast path (generalizing C's single-block
shortcut): swept cutoffs 32/64/256 at w=8 — only large_world benefited
(-8% of ~11ms); rain regressed at every cutoff (its small-count stages
are mesh-contact stages with heavy per-item cost — serializing them
starves real parallelism) and joint_grid has few fat stages (grid
coloring = 2-4 colors), so its w=8 gap is NOT thin-stage sync. Reverted
per the measurable-win rule; negative result documented in the README.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Disassembly census showed the joint solvers at exact FMA parity with C
but +110 loads/+54 stores per joint: the full 56-byte BodyState get/set
round trip keeps untouched fields live across the ~1000-instruction
solve bodies. StateAccess::set_velocities (same unsafe contract as set,
velocities only, like C's in-place stores) + get_ref field extraction
across all 16 warm-start/solve functions in the 8 joint types.
joint_grid: 817 vs C 801 ms (was -11%). Full fresh matrix in README:
serial geomean +7% vs C with Rust WINNING large_pyramid (-6%) and
many_pyramids (-3%); w=8 geomean +28%. Checked-in PGO profile retrained
for the new code (stale profile cost ~13% on joint scenes).
Same change was measured neutral for contact scatter and correctly
dropped there (state live ~40 instrs vs ~1000) — both verdicts in the
README as a paired case study.
Hash 0x61E35C31 bit-identical everywhere; 179/185/179 tests, zero
warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cargo build --release on anything in the workspace now gets the PGO'd
box3d automatically (-Cprofile-use=libs/box3d/box3d.profdata; verified:
default build runs at the explicit-PGO binary's speed). The profile is
target-independent — x86_64 cross-build with the ARM-trained profile
compiles clean — and degrades gracefully when stale (unmatched functions
keep normal heuristics). Retrain with libs/box3d/pgo.sh.
Fresh four-way interleaved matrix in the README: default box3d is now
faster than or equal to rapier-simd on all three scenes (1118 vs 1451,
1510 vs 1690, 912 vs 914 ms) and faster than non-PGO C on two of three
(C keeps joint_grid 816 vs 912). 179/185/179 tests green with the
config active.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pgo.sh: instrument -> train on the benchmark scenes -> merge -> rebuild.
Paired same-machine runs: large_pyramid 1177 vs 1457 ms (-19%, now 15%
faster than the non-PGO C build), junkyard -14%, many_pyramids -11%.
Determinism hash unchanged under the PGO binary (0x61E35C31 across
runs/workers/task systems) — PGO changes layout/inlining, never
arithmetic. README notes the C-reference fairness caveat.
Also documented as tried-and-dropped (noise-floor in paired A/B, per
the keep-only-measurable-wins rule): cache-line padding of stage-sync
atomics, narrow velocity-only scatter writes, compound child Arc clone
(already eliminated by the earlier scratch fix).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>