Feature/hardware#43
Merged
Merged
Conversation
- Bootstrap lpc-node-registry with freshness-only artifact layer - Acquire/release refcount, revision bumps, structured read failures - Transient read_bytes via LpFs; delete lpc-slot-mockup - Roadmap and M1 plan docs Plan: docs/roadmaps/2026-05-21-artifact-routed-file-reload/m1-artifact-store/ Co-authored-by: Cursor <cursoragent@cursor.com>
…(M2) - Implement registry types, def walker/shell, load_root bootstrap, and sync diff - Report NodeDefUpdates with shell/body rules and kind-change semantics - Add NodeDefView stub; 22 unit tests including gate scenarios T1–T5 Plan: docs/roadmaps/2026-05-21-artifact-routed-file-reload/m2-node-def-registry/ Co-authored-by: Cursor <cursoragent@cursor.com>
…load - Per-node error state when SVG mapping resolve fails during project load - Update fixture mapping tests to assert node-level failure - Add early artifact-routed reload planning notes; format M5 roadmap tables Co-authored-by: Cursor <cursoragent@cursor.com>
- Add SourceFileSlot custom codec in lpc-model ($path, shorthand, inline) - Add SourceFileRef, resolve_source_file, and materialize_source in parallel stack - Use LpPath::extension for file extension hints; effective version max(slot, artifact) Co-authored-by: Cursor <cursoragent@cursor.com>
- Registry owns ArtifactStore; load_root and sync return SyncResult - Source revision bumps for GLSL/SVG-only file edits via per-def deps - DefChangeDetail classification and S1–S6 integration scenario tests Co-authored-by: Cursor <cursoragent@cursor.com>
- Add standalone changeset-change-management roadmap with M1–M7 - Gate parent engine cutover on ChangeSet M6 diff + equivalence - Document overlay-in-registry architecture and effective-read contract - Add parent M10 ExplainSlot probe milestone; defer provenance from hot path Co-authored-by: Cursor <cursoragent@cursor.com>
- Design overlay inside NodeDefRegistry with apply/discard lifecycle - Four implementation phases: types, overlay store, apply, validation Co-authored-by: Cursor <cursoragent@cursor.com>
- Add change language types with serde and ChangeOverlay on NodeDefRegistry - Implement apply/discard for SetBytes and Delete; slot ops deferred to M4 - Add overlay lifecycle integration tests for D1 and D3 Co-authored-by: Cursor <cursoragent@cursor.com>
- Design read_effective_bytes and NodeDefView effective get API - Four phases: bytes read, def parse, view projection, validation Co-authored-by: Cursor <cursoragent@cursor.com>
- Add overlay-first effective reads and TOML parse path - Wire NodeDefView::get/state to effective entry projection - Keep registry.get committed-only; add effective_projection tests Co-authored-by: Cursor <cursoragent@cursor.com>
- materialize_source checks ChangeOverlay before committed store - Add NodeDefRegistry::materialize_source wrapper - Add C4a–d asset overlay integration tests Co-authored-by: Cursor <cursoragent@cursor.com>
- Apply SetSlot/Map*/OptionSet to overlay slot drafts forked from committed defs - Serialize draft trees to TOML bytes for effective reads and commit prep - Route inline child paths through invocation body mutation; add slot mutation helpers Co-authored-by: Cursor <cursoragent@cursor.com>
- Add CommitError and commit_overlay flush/re-derive path - Expose NodeDefRegistry::commit returning SyncResult - Add D2/D5/C2 integration tests in commit_promotion.rs - Backfill M3/M4/M5 roadmap summaries and commit contract Co-authored-by: Cursor <cursoragent@cursor.com>
- Add ProjectSnapshot, diff(base, target), and assert_equivalent - Slot-tree def diff with apply verification via serialized TOML - A1/B1 gate tests against examples/basic and basic2 - Fix MapInsert for record maps and project nodes[*].def SetSlot routing Co-authored-by: Cursor <cursoragent@cursor.com>
Keep project snapshot diff and equivalence checks host/CI-only so embedded consumers can omit the harness via default-features = false. Co-authored-by: Cursor <cursoragent@cursor.com>
Replace overloaded Change* names with edit/ module types (EditOp, ArtifactEdit, EditBatch, SlotOverlay) and update registry APIs. Add M8 session/sync plan docs, ChangeSet roadmap summary, and M7 doc cleanup. Plan: docs/roadmaps/2026-05-21-changeset-change-management/m8-edit-session-sync/ Co-authored-by: Cursor <cursoragent@cursor.com>
- Route registry ingress through SyncOp batches and SyncOutcome - Keep pending state in SlotOverlay only (Apply/Remove/ClearPending CRUD) - Rename FsChange to FsEvent across lpfs, server, cli, and firmware - Add pending_sync tests and update M8 plan docs Co-authored-by: Cursor <cursoragent@cursor.com>
Replace NodeDefRef/custom codec with a slotted Ref|Def invocation model, generic VariantSet/SetSlot apply, and ref= TOML wire across examples and tests. Co-authored-by: Cursor <cursoragent@cursor.com>
Allow reserved child slots with unset = {} wire form; skip unset invocations
in the registry and reject them at project load time.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Tagged ArtifactEdit::{Slot, Asset} replaces flat mixed ops list
- Rename SetBytes to AssetEdit::ReplaceBody; slot ops use AssignValue/UseOption naming
- Align apply/diff and harness tests with overlay DefDraft vs Bytes split
- Add M10 plan docs and update change-language spec
Co-authored-by: Cursor <cursoragent@cursor.com>
- Key ArtifactStore by ArtifactId; drop raw u32 map keys and handle naming - Use PascalCase ArtifactEdit kind tags on wire; transparent ArtifactId serde - Add engine-registry-cutover roadmap with M0.1 stabilization design notes - Point artifact-routed M6 at the promoted cutover roadmap Co-authored-by: Cursor <cursoragent@cursor.com>
Move durable path↔ArtifactId registration into ArtifactStore; registry consumes store lookups and unregisters only unreferenced artifacts. Co-authored-by: Cursor <cursoragent@cursor.com>
…entity - Remove ArtifactId; catalog, registry, and edits key by ArtifactLocation - Serialize locations as file:/… URI strings on the wire - Simplify ArtifactStore to a single by_location map without id indirection Co-authored-by: Cursor <cursoragent@cursor.com>
- Rename type and module; align helpers and errors to specifier terminology - Update registry, engine, lpv-model call sites and active plan/roadmap docs Co-authored-by: Cursor <cursoragent@cursor.com>
- Remove asset deps, path index, SourceRevisionBump, and sync_source_path - Asset fs changes only bump ArtifactStore revision; nodes pull on prepare - Register asset paths from defs for catalog reconcile; add artifact_revision_for_path Co-authored-by: Cursor <cursoragent@cursor.com>
…verlay - Add ArtifactOverlay keyed by ArtifactLoc with ordered slot upserts and PendingAsset - Fold pending edits in projection and commit; remove DefDraft and SlotOverlay - Add pending introspection API and implementation plan docs Co-authored-by: Cursor <cursoragent@cursor.com>
…pserts - Store slot pending as Vec<SlotEdit> with upsert via SlotEdit::pending_target - Remove string slot key helpers; simplify has_pending_at_path to path equality Co-authored-by: Cursor <cursoragent@cursor.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Internally-tagged enums (#[serde(tag = ...)]) force serde's Content buffering machinery, which cascades a second Deserialize instantiation through every nested type. Flipping all 17 wire/snapshot enums to the externally-tagged default eliminates the entire Content cluster from the esp32c6 firmware (76 symbols -> 0). Authoring formats are unaffected: TOML 'kind = ...' keys parse via the Slotted codec, and 'sampling = ...' via FromLpValue, not serde. Constraint stays untagged by design (peer-key authoring inference). fw-esp32 esp32c6,server: .text -54,968 B, .rodata -16,696 B (-71.7 KB) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Vendor lp-collection (lp-common @ c27c24b: ChunkedVec, ChunkedHashMap) into lp-base/lp-collection and add VecMap/VecSet: sorted-Vec map/set with the BTreeMap/BTreeSet API subset the workspace uses (incl. Entry, range, Index, serde). Switch the workspace dep from git to path. Swap all BTreeMap/BTreeSet uses in lpc-model, lpc-engine, lpc-wire, lpc-registry, lpc-shared, lpc-hardware, fw-esp32, and lp-cli's project scaffold. Every distinct BTreeMap (K, V) pair cost ~2-5 KB of B-tree node machinery in flash; the sorted-vec forms compile to binary search plus memmove and share Vec's existing codegen. Iteration order and key uniqueness semantics are unchanged. fw-esp32 esp32c6,server: .text -254,008 B, .rodata -25,416 B (-279 KB) Cumulative with external serde tagging: image 3,447,616 -> ~3,096,500 B, back under the 3 MB app partition. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Swap remaining BTreeMap/BTreeSet uses in lps-glsl, lps-shared, lpir,
lpvm, lp-shader, lpvm-native, lpvm-cranelift, and lps-frontend onto
lp_collection::{VecMap, VecSet}.
Note: lps-frontend's test target has a pre-existing compile error
(rfind on ChunkedVec iter at lib.rs:602) that predates this change.
fw-esp32 esp32c6,server: .text -72,232 B, .rodata -7,896 B (-80 KB)
Image: 3,096,464 -> ~3,016,300 B (~129 KB under the 3 MB partition)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Slotted derive inlined every embedded record's full shape at each use
site, so the shape-registry snapshot re-described the same records many
times over. NodeArtifact (EnumSlot<NodeDef>) alone serialized to 17.3 KB
by inlining all 11 node-def variants — larger than the device's 16 KB
project-read chunk budget, so the debug shape-sync frame carrying it
truncated and the client failed to parse the registry.
Make the record FieldSlot impl emit SlotShape::Ref { id: SHAPE_ID } when a
record is embedded as a field or enum-variant payload. The canonical
slot_shape() keeps the full record; embedding sites resolve through the
registry. Safe because the codegen registers every Slotted struct as its
own catalog entry, and the data codec already resolves Ref on read/write.
Use #[slot(record)] to force the old inline behavior.
Full catalog snapshot: 46,637 -> 22,114 B; largest single shape now 3.5 KB,
so the existing limit=4 shape paging keeps every frame under budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The RMT hardware driver layer was silent — only the provider logged a channel handle, so a failed/misrouted RMT init or wrong GPIO gave no signal. Add INFO logs at the driver open boundary: Esp32RmtWs281xDriver::open (endpoint + resolved GPIO + byte_count), ensure_rmt_initialized (init / reuse / conflict branches), and LedChannel::new (RMT channel config). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pre-existing lints that blocked `just ci`'s clippy gate once earlier crates compiled: manual div_ceil and a missing is_empty in ChunkedVec, a redundant field name in lpc-registry, and uninlined format args in lpc-engine. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
These compile errors were masked in CI by an earlier-aborting crate; once that cleared they blocked the test build: - lps-filetests: texture_specs is now VecMap (was BTreeMap), LpirModule.functions is VecMap, and LpirBody replaces Vec<LpirOp> (add .into()); add the lp-collection dependency. - lps-frontend: LpirBody.iter() is not double-ended; rfind -> filter().last(). - lp-cli: AssetSlot::path_value was removed; use artifact_value().to_string(). Cargo.lock carries the new lps-filetests -> lp-collection edge (plus pre-existing dependency-resolution churn already present on the branch). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AssetSlot derived plain struct serde, so it serialized as {value, revision}
instead of the compact authored form the semantic-slot test expects
(asset = "shader.glsl"). Give AssetSlot the ValueSlot pattern (serialize the
bare value, stamp the ambient revision on deserialize), and hand-write a
streaming Visitor for AssetSlotValue: an artifact path round-trips as a bare
string, inline bodies as tables.
Deliberately NOT #[serde(untagged)] — untagged (like internally-tagged) buffers
the whole input into serde's `Content` tree and re-parses, monomorphizing that
heavy machinery into the deserialize graph: the exact flash cost the
externally-tagged-enum work removed. A Visitor dispatches on input shape in one
streaming pass.
source_slot_sync: `source` is now an AssetSlot whose synced snapshot is the bare
artifact path value, not a record with a `path` field; navigate `source`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ambient revision is a process-wide atomic that parallel tests both set and read while stamping deserialized slots, so libtest's thread pool made round-trip equality tests (node_invocation_round_trips) flaky. Back it with a per-thread cell under cfg(test) (production keeps the atomic; fw is never cfg(test)). Quarantine events_example_merges_bus_maps_into_visual_shader: it renders black under heavy parallel CPU load (reproduces under `just ci`, passes standalone). Ruled out the revision race and wall-clock; looks like a render/JIT data race. Pre-existing; tracked separately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
d3c0c17 to
f4a5ff2
Compare
Resolves a divergence where the branch's hardware refactor (lpc-hardware extraction, Hw* renames, asset slots, sorted-vec collections) overlapped main's work. Key resolutions: - esp deps: adopt main's published crates (esp-radio 0.18.0) over the branch's local ../oss/esp-hal checkout (esp-radio 1.0.0-beta.0), which only built on one machine. Fixed the one resulting API delta: esp_hal::usb::usb_serial_jtag -> esp_hal::usb_serial_jtag (the local fork's reorg). - Convergent fixes (both sides did them): shader nested-shape refs, LpirBody reverse-search for the Return op, clippy/fmt — took main's versions. - Ported main's permissive_emu_hardware_manifest + HwResource clear_reservation/with_display_label into the extracted lpc-hardware crate with Hw* names. - Adapted main's new fixture-diagnostics tests and fyeah-button example to the branch's APIs (add_child NodeInvocation arg, tick/render registry params, HwRegistry, ref-style node authoring). - Kept the branch's espnow driver and fw-emu (active WIP) over main's variants. just ci green (host tests, clippy, fmt, fw-esp32 build, filetests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…failures The `events_example_merges_bus_maps_into_visual_shader` test was quarantined as a "render/JIT data race" because it rendered black under heavy parallel load. Root cause is not a race: `ShaderNode::ensure_compiled` zero-fills the target and returns Ok when compilation fails (and a compile panic is caught and funneled to the same fallback), so a shader that fails to compile renders a silent black frame. The render path itself is deterministic. - assert_bright_event_pixels now surfaces any node compile/runtime error (via a status-refresh tick + tree scan) instead of an opaque "not bright" assertion, so a future flake reports the real cranelift/frontend message. - Remove #[ignore]; the test passes on both LpsGlsl and Naga frontends and under the full `just test` CI path. - Add log::warn! breadcrumbs at the shader render/sample black-fallback sites so a swallowed compile failure leaves a trace at runtime too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The in-progress ESP-NOW ownership refactor was written against an esp-radio API that does not exist in the pinned 0.18.0: `WifiController::new` and `controller.esp_now()`. It did not compile. Keep the intended design — split the interface into manager/sender/receiver and drop the `Rc<RefCell>` check-in/out dance — but construct via the real 0.18.0 surface: `esp_radio::wifi::new(...) -> (WifiController, Interfaces)`, hold the controller alive, and take + `split()` the single `interfaces.esp_now` the first time the endpoint is opened (the registry lease enforces exclusive access; the firmware opens the radio once and never closes it, so `split` consuming the interface is fine). The device now owns the sender/receiver directly: `receiver.receive()` for RX, `sender.send(&BROADCAST_ADDRESS, ..).wait()` for TX. Validated on an esp32c6 via the `test_espnow` smoke test: board boots, "radio ready ... channel=11", and broadcasts one message/sec with send returning Ok. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI was failing for three reasons: - `unwinding` E0308: the crate is bound to the nightly `core::intrinsics:: catch_unwind` ABI (0.2.8 returns an integer; 0.2.9 switched to the bool return added in a later nightly). With an unpinned `nightly`, CI drifted onto a newer toolchain than local dev, breaking the build-std compile. Pin rust-toolchain.toml (and the workflow) to nightly-2026-04-27 and add rust-src so the toolchain and `unwinding` 0.2.8 stay in lockstep. Bump the date and the unwinding version together when moving forward. - unused `lp_collection::VecMap` import in lpvm-native rt_jit/compile_job.rs (the one use is fully qualified) tripped `-D warnings`. - rustfmt: collapse a match arm added in 61b5910 that was committed unformatted. Validated: `just check` (fmt-check + clippy-host + clippy-rv32, which compiles fw-esp32/unwinding under the pinned toolchain) passes with no warnings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ckstep Bumping the pinned nightly requires touching three things that must move together: rust-toolchain.toml, the CI workflow, and the `unwinding` crate version (its `catch_unwind` ABI is welded to the nightly). Automate it: - scripts/bump-nightly.sh DATE (default today, UTC): rewrites both pins, runs `just check`, and only advances `unwinding` if the current version fails on the new nightly; re-checks; leaves all changes in the tree for review (never commits), reverting only a speculative unwinding bump on failure. - `just bump-nightly [DATE]` wrapper. - docs/toolchain-notes.md: explain the pin, the unwinding/nightly ABI coupling, and the bump procedure. Also pins the commented-out validate-x64 job's toolchain for consistency (the script keeps all `toolchain:` refs in sync so re-enabling a job can't reintroduce nightly drift). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previous commit pinned the workspace rust-toolchain.toml but missed
lp-fw/fw-esp32/rust-toolchain.toml, which independently said `channel =
"nightly"`. Recipes `cd` into lp-fw/fw-esp32, so that file wins there — the
firmware build-std step then ran on a rolling nightly that CI never installed
rust-src for ("library/Cargo.lock does not exist", clippy-fw-esp32). It passed
locally only because the local rolling nightly already had rust-src.
Pin the per-crate file to nightly-2026-04-27 with rust-src, and make
bump-nightly sync every rust-toolchain.toml in the repo (not just the root) so
the pins can't drift apart again. Validated: `cd lp-fw/fw-esp32` now resolves
nightly-2026-04-27 and `just clippy-fw-esp32` builds clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Validation
Contribution Terms
By opening this pull request, I confirm that I have the right to submit these
changes and agree to the contribution terms in
CONTRIBUTING.md.