# quic_ecs_dt — Project Guide for Claude ## What & why Source repo for **"QUIC + ECS as Complementary Transport and Runtime Substrates for Industrial Digital Twins"** — UCAmI 2026 (Plantevin & Francillette, UQAC). Third paper in a sequence; the first two are at IEEE SWC 2026: - `plantevin2026ecs` — ECS as runtime substrate for industrial DT (200k assets @ 114 Hz on Pi 5). - `plantevin2026quic` — QUIC partial reliability for DT sensor streams (94% P99 reduction vs TCP at 5% loss). **UCAmI hypothesis (the composition question):** prior work shows ECS and QUIC each work as substrates *independently*. Does integrating real QUIC traffic into a Bevy ECS ingest path introduce coupling that degrades either one's claimed properties? The paper argues no, and measures it. ## Architecture Three-tier QUIC ↔ ECS bridge, headless Bevy runtime: | Tier | QUIC primitive | Use case | Channel cap | |------|----------------|----------|-------------| | T1 | Unreliable datagrams (RFC 9221) | High-freq ephemeral telemetry; drops OK | 1024, lossy backpressure | | T2 | Unidirectional streams | Ordered threshold events; reliable | 512, fully drained | | T3 | Bidirectional streams | Actuator commands w/ ACK | 256, fully drained | QUIC server runs on a dedicated OS thread with a Tokio multi-thread runtime; pushes decoded `QuicMessage` (UUID + stream_id + f64 + ts + seq) into `tokio::sync::mpsc` per tier; Bevy `IngestSystem` drains in `PreUpdate`. Pattern is in [substrate/src/transport/ecs.rs](substrate/src/transport/ecs.rs). **Target hardware:** CM5 (BCM2712, Cortex-A76, 4 GB) as DT runtime; M4 Max as traffic generator; 1 Gbps direct Ethernet. Both rigs are in hand. ## Repo map ``` quic_ecs_dt/ ├── paper/ Quarto + LNCS source — single index.qmd, refs in references.bib ├── substrate/ Rust crate: Bevy 0.18 + Quinn 0.11 + rustls 0.23 + Tokio │ └── src/ │ ├── main.rs App::new, MinimalPlugins, EcsQuicTransportPlugin │ ├── config.rs figment chain: defaults → config.toml → APP_* env │ └── transport/ │ ├── mod.rs QuicMessage struct │ ├── ecs.rs Plugin: tokio thread + 3 mpsc + PreUpdate ingest │ └── server.rs run_substrate_server (EMPTY STUB) ├── simulator/ Rust crate: stub today; will be Quinn client + Bevy sensor generators ├── data/ (created by M6) loopback/, two_machine/ — raw CSVs committed, *_processed ignored ├── Cargo.toml workspace └── Makefile render, preview, build, build-cm5, deploy-cm5 ``` ## Status | Area | State | |------|-------| | `AppConfig` figment loader (defaults → TOML → env) | Done — [substrate/src/config.rs:42](substrate/src/config.rs#L42) | | 3-tier MPSC bridge scaffolding (Tokio thread + Bevy plugin) | Done — [substrate/src/transport/ecs.rs](substrate/src/transport/ecs.rs) | | `QuicMessage` struct (no codec yet) | Defined — [substrate/src/transport/mod.rs:4](substrate/src/transport/mod.rs#L4) | | Quinn server (accept loop, demux, decode) | **Empty stub** — [substrate/src/transport/server.rs:4](substrate/src/transport/server.rs#L4) | | TLS / self-signed cert | Done (M1) — `certs/server.{crt,key}` via `make certs`, gitignored | | Wire codec for `QuicMessage` (38 B fixed LE) | Done (M1) — [substrate/src/transport/mod.rs:35](substrate/src/transport/mod.rs#L35); 4 unit tests passing | | `tracing-subscriber` init w/ `RUST_LOG` | Done (M1) — [substrate/src/main.rs:8-12](substrate/src/main.rs#L8-L12) | | ECS components (`RawSensorData`) + 5 systems (Ingest/Sim/Export/FaultInjection/Diagnostics) | Missing — placeholder at [substrate/src/transport/ecs.rs:26](substrate/src/transport/ecs.rs#L26) | | VictoriaMetrics + Grafana export | Missing | | Simulator (Quinn client + sensor generators) | `Hello, world!` — [simulator/src/main.rs](simulator/src/main.rs) | | `config.toml` at repo root | Done (M1) — [config.toml](config.toml); loaded by [substrate/src/main.rs:9](substrate/src/main.rs#L9) | | Benchmark harness (sweep + CSV writer) | Missing | | CM5 cross-compile / deploy | Wired in [Makefile:30](Makefile#L30); not exercised | `cargo run -p substrate` boots, prints the loaded config, and idles on the (still-empty) Quinn server. `MinimalPlugins` busy-loops the ECS schedule by default — expected, will gate to `tick_rate_hz` in M4. ## Roadmap Each milestone has one verification gate. Update Status here as we go. - **M1 — Wire codec & root config.** ✅ Done 2026-05-04. Hand-rolled little-endian codec on `QuicMessage` (38 B fixed: 16 UUID + 2 stream_id + 8 f64 + 8 ts_us + 4 seq) with roundtrip + layout + length-error tests; `config.toml` at repo root; dev TLS via `make certs`; structured `tracing-subscriber` init reads `RUST_LOG` (default `info`). - **M2 — Quinn server + self-signed TLS.** Fill [substrate/src/transport/server.rs](substrate/src/transport/server.rs): `Endpoint::server`, accept loop, demux T1=datagrams / T2=uni / T3=bi, push into matching `mpsc::Sender`. Use `rcgen` for a dev cert at boot. *Verify:* a Quinn smoke client connects, server logs handshake. - **M3 — Simulator client.** Replace [simulator/src/main.rs](simulator/src/main.rs) with a Bevy app: Quinn client, N synthetic devices, configurable per-tier rates. *Verify:* end-to-end loopback drains messages on all three tiers. - **M4 — ECS world.** Define `RawSensorData` and the 5 systems the paper names (`FaultInjectionSystem`, `IngestSystem`, `SimulationSystem`, `ExportSystem`, `DiagnosticsSystem`). Wire `IngestSystem` into the existing `PreUpdate` slot. *Verify:* with 10k simulated devices, entity count stabilizes; `DiagnosticsSystem` logs steady tick rate. - **M5 — Observability (VictoriaMetrics + Grafana).** Substrate exposes Prometheus-format `/metrics` (use `metrics` + `metrics-exporter-prometheus`): tick rate, RSS, per-tier P50/P99/P999, channel depth, drop count. Commit a Grafana dashboard JSON. *Verify:* `curl :PORT/metrics` returns labeled samples; dashboard renders against VM. - **M6 — Benchmark harness.** Sweep `entity_count ∈ {10k, 50k, 100k, 200k}` × `loss_rate ∈ {0%, 1%, 5%}` with 2k warmup + 5k measurement ticks. Loss via `tc netem` or in-app injection. Writes `data/loopback/final_table.csv`. *Verify:* one full sweep on M4 Max produces a CSV the Quarto figures consume. - **M7 — CM5 cross-compile & deploy.** Exercise [Makefile:30](Makefile#L30) (`build-cm5`, `deploy-cm5`); set real `CM5_HOST`. *Verify:* binary runs on CM5 with a feed from M4 Max over 1 Gbps Ethernet. - **M8 — Two-machine run + paper render.** Sweep with simulator on M4 Max → substrate on CM5; populate `data/two_machine/final_table.csv`; `make render` produces a PDF. **Update §Evaluation prose to reflect actual numbers.** Current paper figures (241 Hz, 64 µs / 15.8 ms P99, 2.6 µs jitter, 1.02 MB/1k, R²=0.9999) are **aspirational placeholders** — they may move and the conclusions may shift; that's expected. ## Conventions - **Rust:** edition 2024; workspace at root with `simulator` + `substrate`; `opt-level=1` dev, `opt-level=3` for deps. - **Pinned crates:** Bevy 0.18, Quinn 0.11, rustls 0.23, Tokio 1 (full), figment 0.10 (toml + env), uuid 1.23 (v4), serde 1. - **Config:** `figment` chain — defaults in [substrate/src/config.rs:25](substrate/src/config.rs#L25) → `config.toml` → env `APP_*` (double-underscore for nesting, e.g. `APP_NETWORK__SERVER_PORT=9000`). - **Bevy:** headless — `MinimalPlugins` only; do not pull rendering plugins. - **Tokio↔Bevy:** keep the dedicated-thread + mpsc pattern in [substrate/src/transport/ecs.rs:49](substrate/src/transport/ecs.rs#L49); do not block the ECS schedule on async work. - **Paper:** Quarto + LNCS template ([paper/_extensions/template.tex](paper/_extensions/template.tex), [paper/_quarto.yml](paper/_quarto.yml)). **Never commit `llncs.cls` or `splncs04.bst`** — CTAN licensing; download per [README.md:25-34](README.md#L25-L34). - **Data:** raw CSVs under `data/` are committed; `*_processed.csv` is gitignored. Paper figures consume `data/loopback/final_table.csv` and `data/two_machine/final_table.csv`. - **Build artifacts:** `target/`, `paper/_output/`, `paper/figures/`, `paper/.quarto/`, `paper/index.tex` all gitignored. ## Run / verify ```bash make certs # generate certs/server.{crt,key} (ECDSA P-256, SAN: localhost/cm5.local/127.0.0.1/::1) make build # cargo build --release (native, depends on certs) make build-cm5 # aarch64 cross-build for the CM5 (depends on certs) make deploy-cm5 # scp to $CM5_HOST (set in env or override Makefile var) make render # build the paper PDF make preview # live-reload paper preview at :4848 make clean # cargo clean + drop generated paper outputs ``` `certs/` is gitignored; `make build` regenerates the dev cert if missing. From the repo root: `cargo run -p substrate` boots, prints the loaded `AppConfig`, and idles. `config.toml` and cert paths are resolved relative to the cwd — always launch from the repo root. ## Key references - Prior self-citations: `plantevin2026ecs`, `plantevin2026quic` (both IEEE SWC 2026, "to appear"). - QUIC: RFC 9000 (core), RFC 9221 (unreliable datagrams). - DT foundations: Tao et al. 2019; Grieves & Vickers 2017; Minerva et al. 2020. - ECS: Nystrom 2014, *Game Programming Patterns*. - Mixed-reliability transport: Peeck et al. (W2RP for DDS). - DT sync metrics: Çakır et al. 2023 (Twin Alignment Ratio); Bellavista et al. 2023 (ODTE). - Industrial QUIC/IIoT: Fernández et al. 2021; Boeding et al. 2025. - Full bibliography: [paper/references.bib](paper/references.bib).