Skip to content

Copper 1.3.0

Choose a tag to compare

@gbin gbin released this 15 Oct 06:02
· 56 commits to master since this release

v1.3.0 - 2026-10-15

Copper 1.3 adds self-describing logs that can be decoded without the producing source or build, bounded recording storage, and explicit scheduling with automatic tuning that preserves determinism. It also adds first-class clock synchronization, with PTP integration and extensible clock references.

Self-Describing Logs

Reading a log normally requires an application-specific decoder built for its payload types. Copper already records the application's version and Git provenance so you can recover the producing source and rebuild it for resim and deterministic replay. That remains essential. But checking out and rebuilding a past version can be impractical when you only want to inspect recorded fields or images.

Experimental cu29/self-describing-logs embeds a versioned schema describing the recorded encoding, units, and mission layouts. Generic readers can decode these recordings without the producing build or access to the project's source and history. You can share data outside the project, and the schema travels with the log so future readers retain its original decoding contract. (#1443, #1447)

cu29-logextract and the Rust/Python catalog readers use this contract for inspection, export, and deep validation. The catalog is built once at startup; normal recording keeps its native payload encoding, including on no_std targets. Try just in examples/cu_self_describing_logs. (#1449, Self-describing log guide)

Profile Guided Scheduling

1. Describe the exact schedule

Explicit execution plans let you specify the schedule you want: worker assignments, execution order, CPU placement, dependencies across workers and cycles, and the number of in-flight CopperLists. Save that schedule as a portable CuPlan in RON; Copper validates it against the task graph and compiles it into the runtime. You can also select the built-in Serial or Pipeline plans. (#1400, #1409)

2. Let Copper autotune it

As a graph grows, those scheduling choices interact. Finding a good combination by hand can quickly become impractical when several sensor rates and end-to-end deadlines must be satisfied together. Profile Guided Scheduling (PGS) searches that space using timings from a representative run and your source periods, deadlines, available CPUs, and timing margin. Build and measure the candidate plans, then rank their delivered source rates and p99 latency. (#1414)

Autotuning runs offline and produces the same explicit plans you can author yourself. The selected plan is validated and compiled into the application, preserving Copper's deterministic execution and replay model. Generated host templates provide pgs-baseline, pgs-optimize, pgs-candidate, and pgs-measure. (#1423)

Stateless Tasks

Declaring a task as CuStatelessTask gives Copper an explicit optimization hint: calls for different CopperLists can run independently, giving the planner more freedom to overlap work and choose efficient schedules. Its per-cycle methods borrow &self, expressing that contract. Use it for work such as image conversion; accumulated controller state still belongs in a regular task. Stateless tasks require Send + Sync and need no Freezable implementation. (#1401, #1470)

Log Rollover and Run Selection

.rollover(bytes) bounds onboard recording storage, reclaiming old data sections while retaining application metadata and payload catalogs. list-runs and --run N let you inspect a particular restart or mission run, even after its startup records have rolled out. Append remains the way to continue a cleanly closed recording; it was introduced in 1.2. (#1391, #1465, #1387)

Live Twin Configuration and Archives

Live twins expose receiver and storage budgets. Size record limits, archive sections, replay queues, and display buffers for your actual payloads. .with_log_rollover(bytes) bounds ground-station storage too. Matching generated twins can write self-describing catalogs, and archives preserve the sender's run and mission identity through late joins and rollover. (#1478, #1474, #1473, #1475)

Clock Synchronization

Clock synchronization is now part of the runtime. By default, RobotClock still starts at zero when created at application startup, giving each process its own local timeline. Enable clock-sync and select a parent reference to use its epoch, such as TAI through PTP, GPS, or an application-defined epoch. Tasks continue to timestamp messages with ctx.now(). (#1483, #1484)

The clock keeps its live execution guarantees. Initial epoch alignment completes before tasks start; subsequent corrections preserve continuity and monotonic timestamps through bounded slew. Clock clones share the same timeline; reads remain allocation-free and perform no reference I/O. Raw time remains independent for elapsed-time measurements and deadlines, while recorded corrections let resim reproduce the execution timeline. The runtime tracks uncertainty and holdover and stops iterations when the configured synchronization budget expires. (#1483, #1484)

PTP is integrated through cu-ptp, and references are extensible. Linux adapters consume an existing synchronized PTP clock. Implement ClockReference and ClockReferenceBundle for your own reference, and select parents through RON to build the clock hierarchy your robot needs. (#1485)

ConsoleMon's CLK panel shows what the clock is referenced to: reference identity, source, and epoch, alongside synchronization state, offset, and uncertainty. You can distinguish the upstream clock source from the transport carrying it, such as a GNSS source distributed through PTP. This initial integration is experimental and supports the Serial plan. Try examples/cu_clock_sync. (#1490, #1495)

Zenoh Receive Queues

The default one-entry ring replaces stale sensor data instead of building an ever-older backlog. Select queue_mode: "fifo" for commands or events when every sample matters, and ensure the consumer can keep up. (#1407)

ConsoleMon and BevyMon

HOP navigates large graphs one node at a time, showing neighbors, connections, status, and timing in terminal and Bevy monitors. Live diagnostics now stay in the active LOG pane instead of disrupting the display through stdout. (#1392, #1492)

Graph and Schedule Viewers

Viewer helpers use just graph, sched, graph-log, and sched-log. Templates accept application and recording arguments and provide shared launchers for topology and measured schedules. (#1418, #1420, #1423)

ROS 2 Navigation Payloads

cu-ros2-payloads adds OccupancyGrid and MapMetaData for exchanging occupancy maps with ROS 2 navigation tools. (#1408)

Workspace Integration

ZED, ViTFly, and cu-bincode sources now live in the main workspace, simplifying builds against one checkout. (#1452)

ViTFly MKL Inference

ViTFly gains optional Intel MKL inference for the flight simulator. (#1497)

Fixes

  • Background tasks skip empty inputs and emit each completed result once. Set background_process_empty: true when empty-input dispatch is intentional. This correction also shipped in 1.2.2 and 1.1.4. (#1437)
  • ROS 2 publishers advertise their best-effort QoS correctly; error formatting avoids an unnecessary allocation; fsck derives run timing from all recorded message slots. The 1.2 patch fixes for CopperList initialization, large TUI graphs, append validation, and project scaffolding are included. (#1399, #1471, #1467)

Upgrading

  • parallel-rt alone now leaves execution serial. Select runtime.planner: (kind: Pipeline, config: {"max_in_flight": 4}) and reserve at least four slots with logging.copperlist_count to use that pipeline. Older planner configuration must move to the new plan selection.
  • Raw time uses an explicit clock handle. Replace Instant / CuInstant::now() with CuInstant readings from clock.raw_now(). Call clock.busy_wait_for(...) or clock.busy_wait_until(...) instead of the removed free functions, using the same clock or its clones for deadlines. Ordinary ctx.now() remains local unless you configure synchronization.
  • Use Copper's codec consistently. Import derives from cu29::bincode and add #[bincode(crate = "cu29::bincode")] to derived payloads. Keep applications, streaming peers, and replay/export tools on matching code and encoding features. Full task replay still needs the application, and structured text extraction still needs its cu29_log_index.