# LANTERN on Bevy — Greenfield Architecture Guide **Status:** thought-experiment / spike reference — NOT an adopted direction. The operative direction remains LANTERN-on-Unity per [[DR-048_Lantern_Adoption_Full_Pivot]]; this document exists so the engine question can be evaluated concretely at the one moment it is cheap: while [[Lantern_World_Model_Spike]] is still open and no world code exists. **Date:** 2026-07-26 **Stack:** Rust (stable) · Bevy **0.18** · Avian **0.6** (`avian3d`) · lightyear **0.26.x** **Related:** [[Identity_Lantern]] · [[Roadmap_Lantern_Slice]] · [[Lantern_Strip_Mothball_Inventory]] · [[Art_Direction_Lantern]] --- ## 0. What this is / isn't This is a high-level but detailed map of how LANTERN — co-op action-RPG, *light is territory*, seed-pinned pocket-graph, SoD manual-aim skillshots, suit-frames + Sparks + mutations, server-authoritative with client prediction — would be architected greenfield on the Rust stack. It covers workspace shape, netcode model, world model, physics, animation, rendering, testing, tooling, milestones, and risks. It is **not** a migration plan for the Unity codebase. The premise is greenfield: the Unity repo stays untouched; what ports is *knowledge* (tuning tables, design rules, scar tissue), not code. **The core thesis** (from the engine-fork discussion, 2026-07-26): LANTERN's two biggest *unwritten* systems — the runtime-generated pocket-graph world and the light-as-territory field — are the systems Unity's bake-centric DOTS pipeline fights hardest and Bevy's runtime-first model fits best. The biggest *written* system (combat feel) ports as pure data because it is tick-indexed numbers. That asymmetry is what makes this fork worth documenting at all. --- ## 1. Design invariants that survive the engine Everything in this section is engine-agnostic and ports verbatim. Encode these as data + tests in week one; they are the most expensive-to-rediscover assets the project owns. ### 1.1 The tuning tables (port as literal numbers) Keep the sim at **60 Hz fixed tick** — identical to Unity NFE's default — so every tick-indexed number transfers 1:1 with no re-derivation: | Knob | Locked value (07-21, [[2026-07-21_Heavy_Feel_Lock_F]]) | |---|---| | Melee contact ticks (swing 1/2/3) | +20 / +12 / +25 | | Recover | 36 ticks · combo grace 28 · input buffer 10 | | Measured swing gaps | 36 / 36 / 54 | | Move speed during swing | 0.25× | | Finisher | hold 7 frames · kick 1.1 · range ×1.25 | | Cast facing window | 26 ticks (step-3 contact 25 sits ONE tick under it — never raise contact without the window) | | Reach | 2.2 · axe scale ×1.25 | | DPS target | 60 | | Hit react / stagger | react @1.4× / stagger @1.2× speed · poise threshold 50 | ### 1.2 The design rules (port as conventions + regression tests) - **Rollback discipline:** anything in the predicted/fixed-tick path is deterministic, idempotent, wall-clock-free, `Random`-free. (lightyear re-runs FixedUpdate on rollback exactly as NFE re-runs `PredictedSimulationSystemGroup`.) - **Derive, don't replicate:** any state computable from already-replicated state is computed on both sides, never sent. (Player `Dead` from `Health<=0`; and — new, load-bearing — the entire light field from replicated emitter positions. See §7.) - **Tick wrap discipline:** network ticks wrap (lightyear's `Tick` is a wrapping u16, tighter than Unity's u32 — the hazard is *worse*, not gone). All stored-tick comparison goes through wrap-safe helpers; a defaulted/zero scheduled tick means *not-ready → skip/lazy-stamp*, never *fire* (the 07-21 storm class). - **Dual timelines:** predicted entities live ~RTT/2 ahead of the server; interpolated entities live behind it. A visual cue anchored to an interpolated entity's own effect must use the interpolation timeline, not the predicted one (the ZoneTelegraph lesson — lightyear exposes both ticks explicitly via its sync manager, so this rule has a first-class home). - **Swept, not point:** every hit/area test covers the segment traversed this tick; cover with a tunnelling regression test from day one. - **One-press-one-action:** input abstractions accumulate/stick in every netcode stack. Gate one-shot actions on a *press edge relative to the previous tick's input*, and write the "one tap = one cast across a cooldown reopen" regression test before shipping the first ability (the 07-21e bug, now automatable headless — see §12). - **Honesty:** a telegraph is the truth of the sim (arc-reveal ends at contact; fill direction encodes the actual schedule). Single-source-of-truth structures (§7) make this *by construction* rather than by audit. - **Soft-fail resource spends:** afford→act else soft-fail with no cooldown burn; read the ledger live in-loop; commit withdraw + reservation atomically inside the request handler. - **Grid math:** corner-origin, center-returning, half-open cell bounds, floor(); lock cell/plot size as a coordinate space once. - **Interest-management re-audit:** whenever a second region/room shares an existing tag or query, re-audit every global query over that tag (the DR-031/040 class — rooms don't remove this failure mode, they relocate it). - **Present forks, don't auto-decide;** operator gates state *Changed:* + *Looking for:*. Process rules port too. ### 1.3 What deliberately does NOT port - All Unity plumbing knowledge: Burst hazards, asmdef/source-gen rules, subscene baking, `.meta` discipline, MCP editor-bridge workarounds, Rukhanka graft math, Entities-Graphics deformation shaders, URP asset versioning. On this stack those categories cease to exist (they are replaced by a smaller, different set — see §15). - The prior quarry verdicts in [[Lantern_Strip_Mothball_Inventory]] — greenfield means the quarry is *design*, not code. --- ## 2. Stack and version matrix **The pinning rule:** lightyear's supported Bevy version is the constraint solver for the whole matrix. Pin everything in the workspace `Cargo.toml`, upgrade as a deliberate scheduled task (never ambiently), and treat a Bevy minor bump as a milestone-sized event with a migration-guide read. | Crate | Version (as of 2026-07) | Role | Notes | |---|---|---|---| | `bevy` | **0.18** (Mar 2026) | engine | ~quarterly breaking releases; no LTS | | `avian3d` | **0.6** (Mar 2026) | physics | ECS-native; 0.6 adds **built-in move-and-slide** (the character controller problem has a first-class answer now), joint motors, BVH | | `lightyear` | **0.26.x** | netcode | server-authoritative replication, client prediction + rollback, interpolation, input redundancy, rooms; ships an **Avian integration crate** — this junction is the most version-sensitive point in the stack, verify it first at spike time | | `leafwing-input-manager` | latest compatible | input | lightyear has native integration; action-state model | | `bevy_egui` + `bevy-inspector-egui` | latest compatible | dev tooling | the inspector/tuning-bench substrate | | `serde` + `ron` | stable | data | profiles, spark/mutation defs, gen params | | `rand` + `rand_chacha` | stable | determinism | seeded, portable RNG streams | | `petgraph` | stable | pocket graph | graph gen + queries | | `bevy_hanabi` | latest compatible | GPU particles | murk particulate, hit effects | | `bevy_kira_audio` (or stock `bevy_audio`) | latest compatible | audio | procedural clip generation possible | | `insta`, `proptest` | stable | testing | snapshot-pin worldgen; property tests | | `tracing` | stable | logging | structured, greppable | **Fallback noted up front:** if lightyear stalls (single-maintainer risk), `bevy_replicon` + hand-rolled prediction is the documented retreat. §3's crate boundaries are drawn so that retreat replaces one crate, not the game. --- ## 3. Workspace layout Cargo workspace; the Unity asmdef split maps cleanly, with one upgrade — a **zero-Bevy core crate** that holds the most valuable logic: ``` lantern/ ├── Cargo.toml # workspace + pinned version matrix ├── crates/ │ ├── lantern_core/ # ★ NO bevy/lightyear/avian deps. Pure Rust: │ │ ├── worldgen/ # seed → PocketGraph (petgraph), gen invariants │ │ ├── lightfield/ # grid field math, emitter → field computation │ │ ├── tickmath/ # wrap-safe tick compare, schedule sentinels │ │ ├── gridmath/ # half-open cells, corner-origin (ports BaseGridMath) │ │ ├── combat/ # cadence tables, damage/poise math, cone/segment tests │ │ └── tuning/ # FeelProfile types (serde), code defaults │ ├── lantern_protocol/ # components + lightyear registration: channels, │ │ # messages, inputs, prediction/interp modes │ ├── lantern_sim/ # shared FixedUpdate systems (movement, melee, │ │ # abilities, enemies, light-territory ticks) │ ├── lantern_client/ # render, input gather, presentation, HUD, audio, VFX │ ├── lantern_server/ # spawning, AI decisions, waves, persistence, validation │ └── lantern_tools/ # CLIs: seed-gallery, feel-probe, gen-audit, screenshot ├── src/main.rs # launcher: --server | --client | --host (both) ├── assets/ │ ├── models/ # glTF (Blender-native export) │ ├── profiles/ # RON feel profiles (hot-reload) │ ├── content/ # sparks, mutations, frames, enemy defs (RON) │ └── shaders/ # WGSL, in-repo, hot-reload └── tests/ # integration: server + N clients in-process ``` Mapping from the Unity split: `ProjectM.Simulation` → `lantern_core` + `lantern_sim`; `ProjectM.Client` → `lantern_client`; `ProjectM.Server` → `lantern_server`; `ProjectM.Authoring` → **deleted as a concept** — there is no bake step; authoring is RON data + spawn functions. **Why `lantern_core` matters most:** it compiles in seconds with zero engine deps, so worldgen/combat/tick math get sub-second test cycles, are immune to Bevy's quarterly churn, and survive even an engine retreat. It is the project's actual IP; everything else is adapters. --- ## 4. App architecture ### 4.1 Plugins, schedules, states - **Plugins are the feature unit** (mirror of the Unity feature folders): `WorldGenPlugin`, `LightFieldPlugin`, `MovementPlugin`, `MeleePlugin`, `AbilityPlugin`, `EnemyPlugin`, `WavePlugin`, `HudPlugin`, `MurkRenderPlugin`, `DevToolsPlugin`. Client/server bins compose different plugin sets over the same `lantern_sim` middle. - **All gameplay simulation runs in `FixedUpdate` at 60 Hz** (lightyear's tick duration is set to match: `1.0/60.0`). This is the rollback boundary — lightyear re-runs FixedUpdate on misprediction, so every §1.2 rollback rule applies to everything scheduled there. - **Presentation runs in `Update`** (render-rate, once per frame, never rolled back) and is observe-only — the `PresentationSystemGroup` discipline ports wholesale: presentation systems read sim state, own their caches, prune them via `RemovedComponents` (despawn detection is first-class — the "prune the cache each frame" pattern gets engine support), and never mutate sim state. - **Explicit ordering via `SystemSet`s** with `.configure_sets` — e.g. `SimSet::{Input, Movement, Combat, Cleanup}` chained. Two upgrades over Unity: Bevy's **ambiguity detector** can be enabled in CI to flag unordered read/write pairs, and ordering cycles surface when the schedule is built *in tests* (see §12), not only at Play-time world creation. - **Game flow via `States`:** `Menu → Connecting → Loading → InGame`. State-scoped entities/systems replace the entire menu-world / world-lifecycle dance ([[DR-019_Frontend_Menu_Settings_Saves_Build]]'s hardest-won section becomes ~free). ### 4.2 Session topology - `--server`: headless bin, `MinimalPlugins` + server set. Small native binary, fast boot, cheap to host — dedicated co-op servers become economically trivial, and CI can boot real servers per-test. - `--client `: full render client. - `--host`: **single app running `ClientPlugins` + `ServerPlugins` together** (lightyear supports both plugin sets in one app). This is single-player and listen-server co-op in one code path — no on-demand world creation/disposal, no frame-boundary coroutine hazards. - Transport: UDP native; **WebTransport for wasm** — a browser-playable client is a real option (playtests become a link), something the Unity stack effectively cannot do. ### 4.3 ECS concept map (Unity DOTS → Bevy) | Unity DOTS / NFE | Bevy / lightyear | Notes | |---|---|---| | `IComponentData` struct | `#[derive(Component)]` | no unmanaged/managed split to manage | | `DynamicBuffer` | `Vec` field, or child entities via relationships | pick per access pattern | | `IEnableableComponent` | bool field (hot toggles) or marker insert/remove (archetype move) | insert/remove is Bevy's structural change — same batching instincts apply | | `ISystem` + Burst | plain `fn` system | native codegen; **no Burst ICEs, no source-gen, no `*.g.cs`** — that entire CLAUDE.md section dies | | `EntityCommandBuffer` | `Commands` | applied at sync points; same "batch structural changes" instinct | | `SystemAPI.Query` (7-arg cap) | `Query<(…)>` (larger tuple cap, `QueryData` derive beyond) | the `ComponentLookup` workaround class mostly disappears | | Singleton component | `Resource` | `TuningConfig`, run info, ledger | | Baker / Authoring / subscene | RON asset + spawn function | no bake, no `.meta`, no re-bake rules | | `SystemGroup` + `[UpdateBefore/After]` | Schedules + `SystemSet` + `.before/.after/.chain` | ambiguity detection available in CI | --- ## 5. Netcode model (lightyear) ### 5.1 Concept map | Unity NFE | lightyear 0.26 | The ported scar | |---|---|---| | Ghost + `[GhostField]` | component registered in the protocol with a replication + sync mode | registration is code in `lantern_protocol`, greppable, no authoring component | | Predicted ghost (owner) | client-side **Predicted** entity mirror; FixedUpdate re-runs on rollback (`RollbackPolicy { state, input, max_rollback_ticks }`) | all §1.2 rollback rules | | Interpolated ghost | client-side **Interpolated** mirror on the delayed timeline | interpolated-tick cue rule (§1.2 dual timelines) | | `IInputComponentData` / `InputEvent` | lightyear input plugin (native or leafwing `ActionState`); inputs auto-sent with **N-tick redundancy** against loss | one-press-one-action edge gating + regression test, day one | | `IRpcCommand` | message on a reliable channel | payloads plain serde structs; server validates; atomic commit in handler | | `GhostRelevancy` `SetIsIrrelevant` | **`Room`** membership (`clients` + `entities` sets) | default-relevant-unless-hidden posture ports: global/untagged state lives outside rooms; re-audit rule (§1.2) | | `NetworkTick` (u32) | `Tick` (wrapping **u16**) | wrap discipline is MORE critical, not less | | Prediction/interpolation tick split | sync manager: prediction time ~RTT/2 ahead, interpolation time behind | first-class home for the ZoneTelegraph lesson | ### 5.2 LANTERN's replication diet The design goal: replicate *dramatically less* than a generic game, because LANTERN's world is a pure function of a seed. 1. **World = seed.** `(u64 seed, GenParams version)` sent once at join. Both sides run `lantern_core::worldgen`; the server spawns sim entities (colliders, spawners, nodes), the client spawns visuals. Environment replication: zero bytes. 2. **Light field = derived.** Never replicated (see §7); computed on both sides from the replicated emitter set. 3. **Players** — predicted: position/velocity (via the Avian integration), facing yaw, health, socket/ability state, melee combo state. The `PlayerFacing` model ports exactly: body-yaw only, every gameplay direction through a `resolve_aim(input_aim, facing)` in `lantern_core`. 4. **Enemies/projectiles** — interpolated, minimal: position, health, attack-state byte, kind byte. Client derives animation (§10) exactly as `EnemyAnimationDriveSystem` does today. 5. **Run/meta singletons** — resources ledger, run info: replicated resource or singleton entity on a reliable channel; lives *outside* rooms (the untagged-director-ghost rule, ported). 6. **One-shot actions** — build placement, deposits: reliable messages, server-validated, atomic afford→commit in the handler. Rooms map to pockets (or pocket clusters) for interest management once the world outgrows one arena; global state stays room-less. --- ## 6. World model: seed-pinned pocket-graph This is the section Unity was going to fight and Bevy doesn't. - **Pure generator:** `lantern_core::worldgen::generate(seed: Seed, params: &GenParams) -> PocketGraph` — petgraph graph of pockets (nodes: shape, biome-flavor, light budget, spawn tables) and corridors (edges: width, length, gates). No engine types anywhere in it. - **Determinism rules:** `ChaCha8Rng` streams forked per subsystem (`layout`, `population`, `loot` — so adding a loot roll doesn't reshuffle layout); no `HashMap` iteration-order dependence (BTree or sorted collections in gen paths); prefer integer/cell coordinates in structural decisions. - **Generator versioning:** `GenParams` carries a version; a change to generation logic bumps it and is a *new epoch* for seeds — the exact discipline already used for `SaveData` versions ([[DR-019_Frontend_Menu_Settings_Saves_Build]]). Canonical seeds are snapshot-pinned with `insta` so accidental drift fails CI. - **Invariants enforced by the generator, tested by property tests** — this is the greenfield superpower: guarantees at gen-time replace runtime hacks. Concretely: - full connectivity (every pocket reachable) across N-thousand random seeds; - **corridor min-width ≥ k × character radius** — this *deletes the frozen-Husk bug class at the source* (the Depenetrate/tangent-slide/nudge chain existed because hand-placed cover created unnavigable wedges; a generator can simply never emit them); - spawn points depenetrated by construction (no embedded spawns); - light-budget and pacing distributions inside designed bands (Monte-Carlo balance in `cargo test`). - **Runtime spawning:** server iterates the graph → spawns Avian static colliders + gameplay entities; client iterates the same graph → spawns meshes, murk volumes, decor (seeded decoration RNG, so co-op clients see identical worlds without replicating a single prop). - **Operator eyes-on:** `lantern_tools seed-gallery --count 50` renders top-down maps to PNGs for review — the browse-many-seeds loop the design ([[Identity_Lantern]]) will need, impossible to do cheaply through an editor. --- ## 7. The light field — *light is territory* as one data structure The core mechanic gets a single source of truth shared by sim and renderer. - **Representation:** per-pocket coarse grid (`0.5–1 m` cells, `u8` or `f32` intensity), stored in `lantern_core::lightfield`. Computed as a pure function of the **emitter set**: lanterns, placed lures, spark glows, mutation auras — each `(position, radius, falloff, flicker-seed)`. - **Derive, don't replicate:** emitters are already-replicated entities; both server and client run the same field computation in FixedUpdate. Zero bandwidth, rollback-correct (the field re-derives on rollback because it is a function of rolled-back state), and *server and screen cannot disagree* — the honesty class of bugs (socket honesty, arc honesty, zone-fill timing) is prevented structurally rather than audited per-feature. - **Gameplay queries:** `field.light_at(pos) -> f32` drives territory ticks, enemy courage/aggression curves, mutation triggers, harvest eligibility — all in `lantern_sim` FixedUpdate systems. - **Rendering:** the same grid is uploaded per-frame as a texture/storage buffer into a custom murk pass — a fullscreen render-graph node (or extended material) that darkens toward the field's inverse, with WGSL handling flicker, edge glow, and particulate density. The `EmissiveGloam` idea ports directly as a WGSL material — and on this stack, hand-written shader files in-repo are the *norm*, hot-reloaded on save, not a workaround for un-scriptable ShaderGraphs. - **Cosmetic light layer:** actual Bevy point lights on emitters for local highlights — clustered forward handles many small dynamic lights, which is precisely the load profile of a lantern game (sparks, lures, glows everywhere). --- ## 8. Physics (Avian) — sized to the game, with an escape hatch - **World collision:** static Avian colliders spawned from pocket-graph geometry at runtime (hulls/boxes per wall segment — generated, so they *are* the visual truth; the entire `ColliderFitTools` audit-and-refit problem class disappears). - **Character:** kinematic body + **Avian 0.6's built-in move-and-slide** — the collide-and-slide controller that needed a dedicated Unity package now ships in the physics crate. `LockedAxes` pins rotation (the `FreezeRotation`-not-honored gotcha has a first-class answer). Top-down config ports: no ground snap, rotation owned by the facing system, zero gravity feed (underwater). - **Enemies:** kinematic circles with the same move-and-slide; **no pathfinding at first** — but note the §6 point: generator-guaranteed corridor widths remove the main reason enemy sliding ever soft-locked. - **Hits:** Avian `SpatialQuery` (ray/shape casts) for all swept segment/cone tests; the swept-not-point rule and its tunnelling regression test port unchanged. - **Rollback integration:** lightyear's Avian integration crate handles physics-state rollback for predicted entities. **This is the single most version-sensitive junction in the stack — it is the first thing the M0 spike must validate** (§14). - **Determinism scope:** Avian is not cross-platform bit-deterministic. That is fine for this architecture (snapshot server-authority, same as Unity Physics today) — but it forecloses lockstep; don't design anything that assumes bit-identical simulation across machines. - **Escape hatch (deliberate):** all gameplay collision queries go through a thin `lantern_sim::physics` facade. If Avian rollback cost or behavior bites, the facade's alternate backend is a bespoke deterministic 2.5-D layer (circles vs. segments over the pocket graph — a few hundred lines, property-testable). The game's actual needs are modest enough that this is a real option, and the facade keeps it a swap instead of a rewrite. --- ## 9. Combat port The most valuable existing work, and the cheapest to port — it is tick-indexed data plus small systems. - **Cadence tables** (§1.1) land in `lantern_core::combat` as constants + `FeelProfile` overrides. At 60 Hz they are correct on arrival. - **Damage-at-contact:** the `MeleeCleavePending` schedule-and-consume pattern ports as a component + FixedUpdate system: stamp `contact_tick` at swing start (wrap-safe, §1.2 sentinel rules), consume when reached, resolve the swept cone via the physics facade, apply damage + poise, emit cue state. - **Event caution (new-stack gotcha, flagged now):** Bevy's frame-buffered `Event`s interact badly with rollback re-runs — a rolled-back FixedUpdate can double-emit or lose them. Sim-authoritative "events" (damage, deaths) are **components/queues on entities**, drained by consuming systems; renderer-facing cues read state changes. Verify current lightyear guidance at spike time; until then, no `EventWriter` inside rolled-back systems. - **Buffered input, combo grace, dash-cancel rules:** port as written — they are tick logic, and (§12) each ships with the headless regression test the Unity version could only get via live-server probing. - **Feel profiles:** `assets/profiles/*.ron`, hot-reloaded by the asset server, overlaid via an egui panel with A/B buttons — `FeelProfileService` rebuilt in its native idiom. Same rule as 07-20c: profiles are experiments; winners fold into `lantern_core` defaults. - **The probe becomes a test:** `CombatProbe` (server-measured per-swing start/contact/damage ticks, cadence, DPS) is reimplemented as an integration test that scripts inputs against a headless server and **asserts the locked numbers**. Feel regressions fail CI instead of waiting for an operator session. --- ## 10. Animation The one area that gets *harder*, plus one that gets much easier. - **Pipeline:** Blender → **glTF** (Bevy's native format; Blender's best-supported export — the FBX/Humanoid/`CreateFromThisModel` gauntlet disappears). Clips load by name from the glTF; `AnimationGraph` + transitions replace the AnimatorController. - **The loss — no Humanoid retargeting.** Unity's muscle retarget let one clip library serve every rig; Bevy clips are per-skeleton. Mitigations, in order: (1) **small rig roster** — player frame + a handful of enemy skeletons, a real constraint to hold; (2) **Blender-side retarget-and-bake automation** — the existing Blender MCP scripting ([[blender-mcp-and-unity-mcp-v10]]) becomes a batch script: shared action library → apply per rig → bake → per-rig glTF. The clip *library* stays single-source; the bake fans out mechanically. - **The win — attachments are trivial.** Bevy skeleton bones are plain entities: the salvage axe is a child entity of the hand-bone entity with a local offset. The entire 07-16 graft recipe — rigid-skinning, bindpose rebasing, scale-stripping, tangent recalculation — is replaced by `commands.entity(hand_bone).add_child(axe)`. The grip-anchor tuning flow survives as a bone-local offset in RON. - **Client-derived animation ports as-is:** a client-only Update system reads replicated state (velocity from transform deltas for interpolated entities, combat state bytes) and drives graph parameters — `PlayerAnimationDriveSystem` / `EnemyAnimationDriveSystem`, same shape, no replication. Hit reacts keep the windup-honesty gate (light react only when not mid-attack) and the poise threshold. --- ## 11. Rendering, presentation, UI - **The LANTERN look ports as a specification** ([[Art_Direction_Lantern]]): deep-water clear color, exponential teal murk (now field-driven, §7), flat dim ambient, ACES-family tonemapping (Bevy ships filmic tonemappers), bloom for gloam accents, vignette. One look, one config module — `ScenePolicy` becomes a plugin choice, not a scene-name check. - **Shaders:** all WGSL text in `assets/shaders/`, hot-reloaded. Extended/custom materials for gloam-emissive and murk-aware surfaces; skinned meshes work with custom materials without the deformation-graph dance. - **VFX:** `bevy_hanabi` GPU particles (murk particulate, bubble trails, contact bursts); smear/ribbon meshes generated in code as today. - **Juice discipline ports wholesale:** observe-only Update systems, per-frame cache pruning via `RemovedComponents`, despawn-inferred death VFX, hit-stop as camera punch never timescale, procedural audio fallback (kira supports raw sample buffers). - **UI:** code-built `bevy_ui` for the HUD (health, ability bar with drain overlays/countdowns/ready flashes — the AbilityBar spec ports); `bevy_egui` for every dev surface (tuning bench, inspector, seed browser, debug ops). The UITK trap list (PanelSettings/EventSystem/theme wiring) has no equivalent; bevy_ui brings its own smaller quirks (focus/interaction model) — expect a new, shorter gotcha list. --- ## 12. Testing & CI — the payoff section This is where the stack pays for itself; roughly five ★-class Unity limitations become ordinary tests. 1. **Pure-core tests** (`lantern_core`): grid math, tick wrap, worldgen invariants, combat tables — sub-second, no engine. 2. **Schedule-true app tests:** build a real `App` with `MinimalPlugins` + sim plugins, tick `FixedUpdate` manually, assert. Because the *actual schedule* is constructed, **system-ordering cycles and ambiguities surface in tests** — the "invisible to EditMode tests, throws only at Play world-creation" class dies. Enable Bevy's ambiguity detector in CI. 3. **In-process netcode tests:** server app + N client apps over in-memory/local transport in a single `cargo test` — the capability `NetCodeTestWorld is internal` walled off. Day-one regression suite: one-press-one-cast across a cooldown reopen (the 07-21e bug, with *synthetic inputs through the real input path*); buffered-attack edge validity; join-mid-run replication; room-transition relevancy. 4. **Determinism harness:** run identical seed + scripted inputs twice, hash sim state per tick, assert equal. Wall-clock leaks, unseeded RNG, and iteration-order nondeterminism get caught *mechanically* — today this is enforced only by review discipline. 5. **Feel-probe tests** (§9): locked cadence numbers asserted headless. 6. **Worldgen property + snapshot tests** (§6): thousands of seeds per CI run. 7. **CI:** `cargo fmt --check` · `clippy -D warnings` · full test suite · wasm build check — all headless, no editor, no GPU required for the core suite. (Rendered-screenshot tests exist but are CI-flaky; keep them optional/local.) **Workflow consequence for agent-driven development:** the entire verify loop — compile, unit, netcode, determinism, feel — is CLI-only. No editor bridge, no focus requirements, no stale-assembly hazards, no Play-mode-only truths. An agent can take a combat change from edit to "cadence still 36/36/54, determinism holds, one-press regression green" without a human or an editor in the loop. --- ## 13. Dev workflow & tooling - **Inner loop:** `cargo check` for seconds-fast validation; dev profile with `bevy/dynamic_linking` + a fast linker for quick rebuild-and-run; `opt-level` bumped for deps so debug play is smooth. Full clean builds are slow (Rust tax) — cache in CI, never clean locally without cause. - **Hot reload:** RON profiles, WGSL shaders, and glTF assets reload on save via the asset watcher — tuning and look iteration without restarts. (Code hot-patching exists as experimental ecosystem work; treat as a bonus, not a plan.) - **Live-world introspection (BRP):** Bevy ships the first-party **Bevy Remote Protocol** — a JSON-RPC layer into a *running* app, headless server included. Community MCP servers (`bevy_brp_mcp`, `bevy_debugger_mcp`) sit thinly on top: query/watch/mutate live components, trigger events, launch apps. This is the agent's runtime eyes, and it is protocol-native rather than editor-automation — it works against a dedicated server or any dev build, with no editor session, focus state, or compile-freshness hazards. Enable the `bevy_remote` feature in dev builds from day one; unlike the Unity bridge, this bridge is *optional* (the primary loop stays CLI + text), which is the structural reason the MCP scar list stays short. - **Operator surfaces** (replacing the editor, honestly the biggest workflow cost — budget for it): - `--host` + egui: inspector, tuning bench with profile A/B, debug-op buttons (the `DebugOp` byte model ports as reliable messages), F-key toggles; - `lantern_tools seed-gallery` for worldgen review; `lantern_tools staging` (orbit-cam scene viewer) as the ArtStaging replacement; - screenshot-to-file on demand for async eyes-on gates — operator gate protocol ([[operator-gates-state-change-and-ask]]) unchanged. - **Content-as-data design loop:** sparks/mutations/frames/enemy defs in `assets/content/*.ron`. An agent can generate a candidate mutation, run it through a headless Monte-Carlo gauntlet (N seeded runs → TTK/DPS/survival distributions), and present measured results at the fork — the [[tuning-session-defaults-then-adjust]] cadence, with simulation replacing much of the guesswork. - **Docs/knowledge layers unchanged:** vault + DRs + session logs + basic-memory all port as-is; a new repo gets a fresh CLAUDE.md seeded from §16. --- ## 14. Milestones (gates, not dates — operator owns scheduling) Each milestone ends at an operator gate with explicit *Changed:* / *Looking for:*. - **M0 — the spike, doubled.** Build [[Lantern_World_Model_Spike]] *in Bevy*, in a fresh repo, Unity untouched. Scope: seed → pocket-graph → runtime colliders → light field → murk render → two clients over localhost walking pockets, light revealing territory, one melee swing with damage-at-contact + probe test. **Must-validate list:** lightyear↔Avian rollback integration (§8), input edge semantics (§5), event-vs-rollback pattern (§9), wasm build compiles. *Gate:* world-model design review (already owed) **plus** the engine verdict — the spike answers both questions for one price. If either fails, the loss is one spike and the design learnings transfer back to Unity. - **M1 — combat-feel parity.** Target dummy, three-swing cadence + finisher, hit reacts, feel profiles, probe tests asserting §1.1 numbers. *Gate:* operator eyes-on — does the locked feel survive translation? - **M2 — enemies + waves + territory.** Enemy kinds on the drive-by-derived-state pattern, wave direction, light-field-driven aggression, pocket rooms/interest management. - **M3 — sockets, sparks, mutations.** Content-as-RON, ability archetypes on the resolve-aim model, gauntlet-sim balance harness. - **M4 — co-op hardening + reach.** Join-mid-run, disconnect/rejoin, dedicated-server deploy, wasm playtest build shared as a link. - **M5 — look lock.** Murk pass, gloam materials, particulate, audio bed; ArtStaging-equivalent review via the staging tool. --- ## 15. Risks & mitigations (honest ledger) | Risk | Severity | Mitigation | |---|---|---| | **lightyear bus factor** (essentially one maintainer) | High | protocol isolated in `lantern_protocol`; documented `bevy_replicon`+DIY-prediction retreat; M0 validates the riskiest junction first | | **Bevy quarterly breaking releases** vs. LLM training-data lag | Medium-High | pinned matrix, deliberate upgrade tasks, migration guides read at upgrade time; docs fetched at code-time (the context7 rule ports verbatim) | | **Avian↔lightyear rollback integration** version coupling | High (localized) | first item on the M0 must-validate list; physics facade (§8) bounds the blast radius | | **No editor for the operator** — eyes-on art/feel loop degrades | High (workflow) | budgeted egui/staging/seed-gallery tooling (§13); screenshot gates; accept this is the biggest real cost of the switch | | **No animation retargeting** | Medium | small rig roster + Blender batch retarget-bake automation (§10) | | **New scar set** (borrow checker in system params, WGSL debugging, rollback-event traps) | Medium | it replaces a *larger* documented scar set; start the new gotcha ledger at M0, day one | | **Rewrite forfeits 430 green tests + shipped systems** | Accepted premise | operator explicitly not attached; §1 ports the knowledge; M0-before-commitment structure keeps the reversible option open | | **Rust ramp for the operator** | Low-Medium | operator's surface is RON data, egui panels, and Blender — not Rust internals; agents own the Rust | --- ## 16. Day-one conventions (CLAUDE.md seed for the new repo) 1. Sim logic lives in `FixedUpdate` only; presentation in `Update`, observe-only. 2. No wall clock, no unseeded RNG, no `HashMap`-order dependence anywhere in `lantern_core` or `lantern_sim`; the determinism harness is the enforcement. 3. Derive, don't replicate — if it's a function of replicated state, compute it on both sides. 4. Ticks are wrapping; compare only through `tickmath`; zero/default stored tick = not-ready, never fire. 5. No Bevy `Event`s in rolled-back systems; sim events are components/queues. 6. All hits swept; every new hit path ships with a tunnelling test. 7. One-shot actions gate on press *edges*; every ability ships with a one-press-one-action netcode test. 8. Every knob: default in `lantern_core`, override in a RON profile; profiles are experiments, winners fold into defaults. 9. `lantern_core` never imports bevy/lightyear/avian — enforce with a CI dep-check. 10. Version-pin the matrix; engine/netcode upgrades are scheduled tasks with migration-guide reads, never drive-by bumps. 11. Generator changes bump `GenParams` version; canonical seeds are snapshot-pinned. 12. Present gameplay forks to the operator with *Changed:* / *Looking for:*; feel gates get measured probe numbers alongside the ask. --- ## Appendix A — what happens to the current CLAUDE.md, by section | Unity CLAUDE.md section | Fate on this stack | |---|---| | Assemblies/asmdefs/source-gen | **dies** (crate graph + rustc replace it) | | Burst hazards ★ | **dies** (no Burst; native codegen, honest compile errors) | | Netcode/prediction ★ | **ports as principles** (§1.2, §5) — the single most transferable section | | Physics & CC | **shrinks** — swept-hit + config rules port; bake/collider-fit/nudge hacks die (§6, §8) | | Build/structures/grid | **ports** (grid math, atomicity, soft-fail — §1.2) | | Presentation/juice | **ports wholesale** (§11) | | Art import (HDRP→URP) | **dies** (glTF-native pipeline; look ports as a spec) | | Aim/facing (SoD) ★ | **ports verbatim** (`resolve_aim` in `lantern_core`) | | Animation (Rukhanka) ★ | **half dies** (graft/deformation math gone), **half ports** (drive-by-derived-state), one new cost (per-rig clips) | | MCP/editor workflow ★ | **dies** (the CLI *is* the workflow) — replaced by ~10 lines of cargo conventions | | Testing | **upgrades** (§12 — the internal-test-world and Play-only-validation ceilings lift) | *Sources consulted 2026-07-26: lightyear 0.26.4 docs (docs.rs — plugin/tick setup, `Room`, `RollbackPolicy`, input redundancy, sync manager), Avian releases (0.6 move-and-slide, Mar 2026), Bevy 0.18 release notes (Mar 2026).*