Commit graph

2,282 commits

Author SHA1 Message Date
Admin
a929dafa18 world grid 2.5 degrees (9792 cells) — dense slices fit the 16GB M4 workers' in-RAM frame receive
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 00:45:56 +02:00
Admin
a015fdbf82 mapfleet: atomic slice outputs + twice-failed-on-host cells route to the local worker
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 00:42:21 +02:00
Admin
e1d0500771 Arcade M2a: libs/game/net — host-authoritative LAN transport + micro_serde hardening
Harvest-and-rebuild from xr/src/net after the adversarial audit (verdict:
transport promote-with-fixes, authority model rewrite).

- Authority: explicit Host/Client roles. Client->host Join/Input/Intent/
  Leave/Ping; host->client Welcome/StateBatch/Event/Bye/Pong. No per-object
  authority field and no takeover messages — authority theft (audit H-11)
  is unrepresentable, not merely blocked
- Auth: self-contained SHA-256/HMAC; every datagram and frame is
  magic|version|sender|payload|mac, verified BEFORE any peer state is read
  or written. Closes seq-window poisoning, spoofed kick, address hijack
- Endpoints are pumped, not threaded: nothing blocks, so the connect-flood
  stall (H-5) cannot occur and a full session runs deterministically in one
  test process. The host only ever accepts, never initiates
- Harvested: LZ4 frame codec (check-before-allocate), partial-tail drain,
  budgeted read/write loops, MTU batching, peer/config shapes. Frame cap
  4MiB -> 256KiB now that XR alignment payloads are gone
- Per-entity sequencing (a stale datagram drops only its stale members),
  rejoin seq reset, player cap, peer timeout, snapshot-based mid-join
- Measured 6 clients x 60Hz x 200 entities: 2880 pps, 3.13 MB/s (~25 Mbit
  up) — Quest WiFi viable without delta encoding yet
- Hostile suite: one test per audit attack + 3000-mutation fuzz that must
  never panic or wedge the host. 24/24 green
- micro_serde: String::de_bin no longer panics on invalid UTF-8, checked
  offset arithmetic, Vec::de_bin rejects counts the buffer cannot back and
  never sizes allocation from the wire (11/11)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 00:33:16 +02:00
Admin
bbbbbc54ba Fix makepad-xr build: resource.handle -> has_handle after the per-heap handle change
0ebe270c2 moved CxScriptResource from a single handle to per-heap handles;
these two lookups were missed (xr is not built by the example/app targets
that gated that commit).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 00:33:16 +02:00
Admin
536e0e2be2 mapfleet: 5-degree world grid + chunked socket writes — dense 15-degree cells sliced to 6-23GB, over macOS's 2GB single-write ceiling and worker RAM; oversize slices (>3.5GB) stay on the local worker
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 00:33:13 +02:00
Admin
17263b6666 Arcade M1a: box3d as the dynamics layer (hybrid — movers keep the parity sweep)
- libs/game/sim/dynamics.rs: box3d world inside GameWorld, body mirror
  RECONCILED against the sorted entity list per tick (merge-walk), so
  retain/rollback/reset are correct by construction. Statics + kinematics
  mirrored (kinematics via target transforms so resting rigids inherit
  platform velocity); smooth terrain as a box3d heightfield
- New BodyKind::Rigid + body:"rigid" with density/friction/restitution;
  game.push = mass-scaled impulse; set_pos/set_vel detected via bit-exact
  pose caches (no new dispatch arms). Sphere rigids roll on real spheres
- Entity.orient quat read back per tick; renderer builds quat instance
  transforms for rigids (no shader change). Step order: mover sweep
  verbatim -> reconcile -> world_step(dt, 4) -> readback; rigid-free
  worlds skip the solver
- GameWorld stays Clone via box3d snapshot round-trip (bit-identical
  continuation proven by test)
- Determinism: double-run equality + golden hash 0xa8a2baf71e4a564f
  (aarch64, debug and release). Perf: 0.038 ms/tick for 100 movers +
  50 rigids + 65x65 terrain (budget 2 ms)
- Tape probe BYTE_IDENTICAL; box3d crate untouched; arcade demo gains a
  kicked crate stack (captures verified: settles upright, then topples
  with rotated resting poses)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 00:04:26 +02:00
Admin
ac5ed14285 Arcade M1c: skinned animated characters (glTF skins/clips, KayKit, CPU skinning)
- libs/game/render/skin.rs: GLB container + owned-JSON parser, dense
  accessors, multi-primitive skinned meshes, skin + inverse binds, full
  node-hierarchy palette, T/R/S clips (linear/step), nlerp blending.
  Hermetic tests via an in-code 2-joint GLB; real-asset test skips with a
  hint when the download hasn't run
- DrawGameSkinned shader (PbrVertex + albedo texture, terrain-style
  lighting); skinned batch draws between opaque and alpha passes.
  CPU skinning for now (sub-ms at 3716 verts): the uniform_buffer GPU path
  has zero in-tree runtime consumers — SkinnedModel::palette is the seam
  for the GPU swap
- apps/arcade: download_assets.sh (KayKit pinned commit + sha256,
  idempotent, dir gitignored, CC0 license note in-repo); Knight patrols
  the demo world with idle/walk blending, yaw follows path; captures
  verified (pose advances, depth-occludes correctly)
- renderer binary_search sites use the shared sorted-id helper from M0r

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 23:35:09 +02:00
Admin
d41b6e43ec Arcade M0r: parity-neutral sim internals cleanup
- Entity lookup O(log n): shared entity_index_sorted helper, single
  push_entity spawn path with ascending-id assert + per-tick sorted
  debug_assert — the sorted-Vec invariant is now enforced, not hoped
- WorldSnapshot captures next_id: failed-eval rollback can no longer mint
  colliding ids under surviving entities (review defect fixed)
- game.log buffered (1s/16KiB/eval-boundary flush), agent RPC poll gated
  on one .agent dir-mtime stat — no per-tick file I/O left
- Camera rig authoritative on GameWorld (orbit/chase state); widget keeps
  only device-input accumulation; mailbox drain order unchanged
- Per-tick cumulative script budget: with_instruction_limit reports
  consumption (incl. trap-wipe subtlety), gamemaker tick holds ONE 500k
  pool across on_tick + timers + touch events
- Tape probe verified BYTE-IDENTICAL vs pre-cleanup reference

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 23:34:45 +02:00
Admin
cb6cec91ca mapfleet: drop world.mkmap.prev after successful swap — a retained prev is a full extra world copy against a tight disk budget
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 23:02:52 +02:00
Admin
f2f4b1bb50 Spool v2: streaming frontier — the fleet bakes while pass 4 runs
Pass 4 collects relation jobs, bbox-scans them against the RAM stores,
sorts by NL-spiral distance and publishes store/spool-frontier.txt (key
of the first unfinished relation; spool flushed before every publish).
pbf-base slices a LIVE store behind that gate (--bbox only, torn-tail-
tolerant block reads, pid-scoped sort chunks so concurrent slices never
clobber shared edge blocks), and mapfleet claims block per cell on the
frontier instead of waiting for the whole spool.

Pass-granular resume: passes 2/3 stamp exact spool block lengths +
counters + stats; a restart rolls the spool back to the newest stamp
and reruns only the remaining passes (unit tests + crash drill).

Weave fixes from the Luxembourg end-to-end smoke run: bake outputs land
via temp+rename so the ledger name never exists half-written, and
transmux verification now checks the union of ALL sources with first-
source-wins ownership (it compared source 0 alone and failed every
multi-source weave).

Also: pass-4 worker clamp 12→14, dead paged NodeStore/WayStore and
emit_lines/emit_polygons deleted (flat stores/prepare_* replaced them),
mapfleet --pbf + startup chunk sweep + end-of-spiral drain and final
weave, world-build.sh refreshes the -run binary copies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 22:59:15 +02:00
Admin
7bb0c3be34 Arcade M0 stage B: rendering extracted to libs/game/render + arcade live viewport
- libs/game/render: the 5 game draw shaders (script_mod registration),
  shape geometry + winding test, terrain mesh, static-slab instancing,
  draw_scene pass (sky/terrain/opaque/alpha) as GameRenderer over GameDraws
  (draw structs stay #[live] on the host widget so script theming works),
  CameraRig + scene_state per-view (multi-view ready), HUD + billboard
  label drawing. game_view.rs 4640 -> 3599 lines
- apps/arcade: first engine-only viewport (arcade_view.rs) — GameWorld
  built via the sim API with no script VM, 60Hz tick, orbit camera,
  offscreen pass composite; ARCADE_CAPTURE=<png> GPU-capture test hook
- Verified: sandbox3d fixture evals clean headless; arcade demo frame
  GPU-captured and visually checked; suites green. Sim untouched — stage-A
  tape byte-parity (probe.txt identical vs pre-refactor binary) stands

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 22:56:22 +02:00
Admin
f2228a4654 Arcade M0 stage A: gamemaker sim extracted to libs/game/sim + deterministic math + apps/arcade shell
- libs/game/math: cross-arch bit-deterministic f32 kernel (f64-internal
  fdlibm-style polynomial kernels, exactly-rounded IEEE ops only, zero
  platform-libm transcendentals). Parity golden hashes recorded on aarch64;
  equal hashes on every other target IS the determinism test
- libs/game/sim: the gamemaker world moved verbatim (entity/terrain/world/
  queries/step — float expression order preserved so tapes replay
  bit-identically). No Cx, no draw types, no ScriptObjectRef: script
  callbacks are CallbackSlots resolved through a generation-tagged
  host-side CallbackTable in game_view.rs; sim reset no longer touches audio
- examples/gamemaker: game_view.rs 5545 -> 4640 lines (script boundary,
  rendering, input devices, host I/O remain); per-tick args/input objects
  built via unchecked pushes + release_transient (flat-heap tick path)
- apps/arcade: Makepad Arcade app shell + workspace membership
- Verified: headless sandbox3d fixture (90 entities) boots end-to-end;
  gamemaker/sim/math/script-test suites green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 22:28:06 +02:00
Admin
0ebe270c20 Script VM GC campaign: isolate GC, host-transient release, cross-VM resource handle fix
- Escape barrier: every checked store funnel tags stored objects REFFED
  (stored => REFFED structural), ret==args guards at native completion,
  fn-arg bind-time barrier (closure-captured scopes retained args untagged
  -> latent use-after-free under eager release)
- vm.release_transient() for host per-call objects; *_unchecked pushes are
  the releasable-container path; call_with_scope native branch no longer
  leaks one scope object per Rust->native vm.call()
- pump_widget_async: dead-isolate reclaim every pump + needs_gc-gated
  round-robin mark/sweep — isolate heaps were never collected before
- res.rs: resource path cache was Cx-global and cached handle VALUES across
  VMs, so isolate heaps held main-heap handle indices (exposed by the first
  isolate mark pass ever to run). Now: data shared globally, handles minted
  per-heap, cache keyed (heap_key, path), per-heap detach on GC
- vm.gc() no longer shrink_to_fits every run; explicit gc_and_compact()
- script-test: GC campaign suite (flat-heap tick loops, retained-input
  survival, escaped-scope capture, isolate churn collection)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 22:27:27 +02:00
Admin
1e7cf7723c spool: --no-tiles skips pass 5 (nothing consumes it); chain uses it
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 22:15:31 +02:00
Admin
c7f1dbed8e spool: recursive-bisection polygon tiling — continental boundaries clip in O(n log t)
prepare_polygons clipped the FULL ring (and holes) against every tile in
the bbox: a continent-scale boundary relation (millions of points x
millions of tiles) pinned one core for hours at the planet spool's tail.
Halves clip once per split and recurse (geojson-vt scheme); per-tile
output is geometrically identical (equivalence test: same tiles, equal
areas, identical interior vertices — direct's zero-area boundary spikes
are collapsed, which is strictly cleaner).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 22:08:21 +02:00
Admin
be9d49ceb7 spool.md: largest-first scheduling + clip_ring segment prefilter (ideas 8-9)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 20:54:12 +02:00
Admin
0529960542 spool.md: pass-granular resume (idea 7)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 20:05:02 +02:00
Admin
292a47d7e6 Shader: register _mp_unpack ids in the GLSL/WGSL luts — Linux vertex compile fix
The wrapper names printed as hex LiveId hashes on non-Metal backends
(identifiers starting with digits — GLSL compile failure at startup on
Linux/GLES). Metal had the lut entries; Glsl/Wgsl now do too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 19:53:19 +02:00
Admin
85b292b593 spool.md: v2 bottleneck analysis — spiral-ordered finalization kills the fleet gate
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 18:55:26 +02:00
Admin
b30ff3ede3 Fleet compression architecture: q2 throwaway slices, workers deliver uniform q11
map_bake --recompress re-encodes EVERY tile (both pass-through sites) at
the target quality — compression parallelizes across the whole fleet and
the final dataset is uniformly q11. The slicer ships q2 intermediates
(fast, disk-lean). Bake mode also skips runtime-only work per tile:
building/tree vertex extrusion and POI point parsing never reach the
sink and no longer run on workers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 18:42:21 +02:00
Admin
f202b9d212 mapfleet: slice at brotli q10 — matches bake re-encode, keeps ten bakers fed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 18:42:21 +02:00
Admin
dbfd3844cf mapfleet: slice gate 3-wide for the ten-worker fleet
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 18:42:21 +02:00
Admin
bfb0816816 mapfleet: bootstrap retries every 5min — boxes join when healed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 18:42:21 +02:00
Admin
a5919cd2a4 mapfleet: workers self-gate on the spool marker — hot-joined boxes bootstrap immediately
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 18:42:21 +02:00
Admin
12e296ac77 mapfleet: eager worker bootstrap during the spool wait
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 18:42:21 +02:00
admin2
943a5a2a77 Fix partial Android NDK DMG extraction 2026-08-02 18:29:14 +02:00
Admin
acf5442efe Workspace: members = app + examples + studio + necessary tools + tests; makepad-remote runnable from root
tools/remote joins the root workspace (default-run makepad-remote, so
'cargo run -p makepad-remote -- --server' works from the checkout root
on worker boxes). Libraries stay in the build graph as path deps; the
runnable-package list is the things a human actually launches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:55:44 +02:00
Admin
4c81087010 mapfleet: live-joining compute nodes (--hosts-file polled 30s) + dual slice gate
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
05f40f2ed9 mapfleet: distributed world-cell bake over makepad-remote boxes
Protocol extracted to a shared module + TAG_FILE_PULL (server returns a
file from its cwd, escape-guarded). mapfleet dispatches the NL-spiral:
local slicing against the store (serialized), remote bakes via cargo-run
on each box (bootstrap build cached), pull-back, coalesced weave with
atomic world.mkmap swap. Resume ledger (cell-NNN-baked.mbtiles, empty =
ocean) is shared with world-slabs.sh — run one driver at a time. Failed
cells requeue with backoff; a dead box just stops claiming.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
949365029e Studio: fs-event filter shares the bulk-dir predicate — churn under local/ never reaches reload
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
7534799f29 Studio: fix boot hang — bulk dirs out of the file tree, term history bounded
sample(1) showed the hub thread pegged in FlatFileTree rebuild +
git-status cascade: the world bake writing thousands of files under
local/ stormed fs events and every burst re-sorted the whole repo tree
(incl. target/.rustup/255GB of local). One heavy_nonproject_dir
predicate now excludes them at all scan sites. Separately, .term replay
caps at the last 2MB and files rotate at 4MB — a 208MB terminal history
from the weekend's bake logs was minutes of VT parsing at boot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
a8b52d737d Map: world chain hardening — pipefail (tee masked spool failure), instance locks, mercator-clamped cells
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
882cf6e440 Shader: unpack2f16/unpack4u8 on all backends — GLSL ES3 built-ins, WGSL mapping, headless Rust impls
GLSL emits _mp_ wrappers over unpackHalf2x16/unpackUnorm4x8 (WebGL2
baseline has them native); WGSL names mapped for the future backend;
the headless runtime gets bit-exact CPU decodes. The packed map vertex
format is now portable to the wasm target.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
0a0ad2e65a Map: plain alpha fade restored; established-3D arrivals snap (no fade, no grow)
The dithered dissolve traded one artifact for another (user call): the
real fix is not fading at all in an already-3D scene — fades stay for 2D
and the single flat->3D mode reveal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
f4af4db88d Map: growing-archive watcher — the app reloads as the world spiral weaves cells in
5s mtime watch on the active archive (root.mkidx for mkmap); on change,
Failed placeholders clear and the visible loop re-requests. Workers
already reopen per batch, so an atomic shard-set swap appears live —
apps/route now points at world.mkmap and starts empty until cell-001
(NL) lands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
cf1b977d00 Map: NL-spiral slab driver — slice/bake/weave per cell, live-growing world shards
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
990f63de26 mkmap: multi-source weave transmux — first-source-wins, spiral-cell assembly
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
489d5aa6a1 Map: world-build chain script — planet download-wait -> spool -> base -> bake -> shards
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
152b936084 Map: tile crossfade = ordered-dither dissolve — kills the arrival flash
Alpha-multiplying opaque 3D through a depth-writing fade let the clear
color bleed through for 0.25s per arriving tile. Surviving fragments now
stay fully opaque; a 4x4 Bayer threshold ramps coverage instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
41c517e4b5 Map: grow-heights plays only on the flat->3D mode reveal — established 3D never re-pops
Fresh/evicted/rebucketed tiles arriving into an already-3D scene were
replaying the pop-up (the zoom-crossing flash).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
cf6e6c5025 Map: road-icon append test updated for packed buffers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
1ed8334e4d Map: dissolve eligibility caps monster same-height sets — jetsam-proof bake
Tile ~22000 (Westland greenhouse belt) put thousands of identical-height
rings in one union and the memory spike got the bake SIGKILLed at the
same tile every run. Groups over 800 jobs / 120k ring points stay
per-building; the cap is computed identically at bake and runtime so
signatures agree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
8785b98a25 Map: finalcut runs aliased binaries — dodge the unhunted makepad-map-* name killer
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
0375e344f7 Map: finalcut v6 — integer keyframes 15-18 + v4 dissolved-buildings streams
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
09bbb33616 Map: dissolve fix — one group per connected shape, winding matched to the extruder
Flattened ring lists turned disjoint outers into phantom earcut holes;
i_overlay orientation normalizes via the SAME shoelace form as
polygon_signed_area (outer > 0, holes < 0, y-down).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:44 +02:00
Admin
59ff31ede0 Map: v4 baked building block dissolve — same-height blocks union at bake, zero runtime booleans
BakedFacesBucket v4 carries pre-dissolved (height, tint) building groups
behind their own input signature; the bake sink unions eligible grounded
jobs per group (chunked i_overlay); on a runtime HIT the eligible jobs
swap for the dissolved groups (paint order re-sorted), on MISS the
per-building path runs unchanged. v3 streams parse with an empty section
so the current NL cut keeps working until the next bake.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:43 +02:00
Admin
c10a4d3e81 Map: 3D LOD ring derived from the real frustum extent — visible tiles never lose detail
near = frustum half-diagonal (rotation-safe, full tilt stretch) plus a
tile diagonal of margin; the fixed viewport-height radii bit into
visible tile columns at some zooms (2D band with a hard seam). Rings now
only ever act beyond what the camera can see.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:43 +02:00
Admin
ee96a23a3e macOS: present gate at 3 in flight — the >=2 gate single-buffered the pipeline (50ms lock)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:43 +02:00
Admin
a362f385ec Map: building/tree LOD rings — collinear wall merge, walls/trees bands, crossed-quad far trees
simplify_wall_ring merges sub-2-degree collinear runs (digitization
noise) so adjacent wall quads fuse. fill_3d sub-splits by material:
MAT_WALL and MAT_CANOPY get their own bands; mid ring draws roofs +
two-quad crossed trees (geometric, no atlases), near ring full detail,
heights sink only toward the horizon.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:43 +02:00
Admin
2209701b55 Map: fix packed-stride corruption in road-decal restore — packed vertex format complete
append_cached_road_icons appended logical 19-float records into the
GPU-packed icon buffer with the old stride's index base (fan-triangle
streaks after 2D/3D mode switches). Decals stay logical CPU-side and
pack on the way in. This closes the packed-format bring-up: 76B -> 48B
per vertex across all map buffers, packing on the builder thread, all
three DrawVector-family shaders (vector, map, svg) unpacking via the new
intrinsics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:48:43 +02:00