Docs: Bevy/Rust/Avian/lightyear greenfield architecture guide for LANTERN (thought-experiment companion to the world-model spike)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,349 @@
|
|||||||
|
# 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 <addr> | --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 <addr>`: 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<T>` | `Vec<T>` 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.)
|
||||||
|
- **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).*
|
||||||
Reference in New Issue
Block a user