Spec the future Bevy WebAssembly viewer layer #16

Closed
opened 2026-07-09 20:54:09 +00:00 by coilyco-ops · 3 comments
Owner

Write a design/spec for a future Bevy viewer layer for factory-game-v3. This is a spec task only. Do not implement the viewer yet.

Current architecture context:

  • The project is migrating from Unity/C# toward Rust.
  • The current Rust direction is simulation-kernel first, viewer later.
  • factory_sim should stay pure deterministic Rust and platform-neutral.
  • factory_content owns starter content/scenario data.
  • factory_cli is the current headless observability surface.
  • factory-game-v3#15 added typed dispatch protocol state into the Rust simulation.

Design goal:

Specify a future factory_viewer crate using Bevy that can eventually target desktop and browser/WebAssembly without dragging Bevy or web constraints into factory_sim.

Questions the spec should answer:

  • What should the crate/workspace shape be once a viewer is added?
  • How should Bevy consume factory_sim snapshots/events without owning simulation rules?
  • What boundary should exist between deterministic ticks and Bevy frame/update scheduling?
  • What is the minimum viewer slice worth building first? For example: render source, factory, hauler, current assignment state, inventory counts, and tick controls.
  • What build targets should be supported first: native desktop, wasm32-unknown-unknown, or both?
  • What web packaging path should the repo prefer, likely trunk unless a better Bevy convention exists.
  • How should assets be handled for Wasm: local static assets, generated placeholder art, or reuse of Git LFS Unity-era assets later?
  • What feature gates or platform boundaries are needed for browser vs native behavior?
  • What should remain out of scope until after the first viewer slice?

Constraints:

  • Do not put Bevy in factory_sim.
  • Do not make the viewer the authoritative simulation.
  • Do not block further sim-kernel work on viewer design.
  • Keep the design compatible with Bevy compiling to Wasm.
  • Preserve the current programming-first factory/logistics direction. This should be an observability/debug viewer first, not a Unity replacement first.

Expected output:

  • A concise issue comment with the recommended viewer crate layout, boundaries, first milestone, build/test commands, Wasm packaging approach, and risks.
  • Include proposed acceptance criteria for a later implementation issue, but do not build it in this task.

Out of scope:

  • Writing Bevy code.
  • Changing CI.
  • Adding assets.
  • Reworking factory_sim unless the spec identifies a future boundary issue.

Close this when the Bevy/Wasm viewer spec is posted as a comment.

Write a design/spec for a future Bevy viewer layer for `factory-game-v3`. This is a spec task only. Do not implement the viewer yet. Current architecture context: * The project is migrating from Unity/C# toward Rust. * The current Rust direction is simulation-kernel first, viewer later. * `factory_sim` should stay pure deterministic Rust and platform-neutral. * `factory_content` owns starter content/scenario data. * `factory_cli` is the current headless observability surface. * `factory-game-v3#15` added typed dispatch protocol state into the Rust simulation. Design goal: Specify a future `factory_viewer` crate using Bevy that can eventually target desktop and browser/WebAssembly without dragging Bevy or web constraints into `factory_sim`. Questions the spec should answer: * What should the crate/workspace shape be once a viewer is added? * How should Bevy consume `factory_sim` snapshots/events without owning simulation rules? * What boundary should exist between deterministic ticks and Bevy frame/update scheduling? * What is the minimum viewer slice worth building first? For example: render source, factory, hauler, current assignment state, inventory counts, and tick controls. * What build targets should be supported first: native desktop, `wasm32-unknown-unknown`, or both? * What web packaging path should the repo prefer, likely `trunk` unless a better Bevy convention exists. * How should assets be handled for Wasm: local static assets, generated placeholder art, or reuse of Git LFS Unity-era assets later? * What feature gates or platform boundaries are needed for browser vs native behavior? * What should remain out of scope until after the first viewer slice? Constraints: * Do not put Bevy in `factory_sim`. * Do not make the viewer the authoritative simulation. * Do not block further sim-kernel work on viewer design. * Keep the design compatible with Bevy compiling to Wasm. * Preserve the current programming-first factory/logistics direction. This should be an observability/debug viewer first, not a Unity replacement first. Expected output: * A concise issue comment with the recommended viewer crate layout, boundaries, first milestone, build/test commands, Wasm packaging approach, and risks. * Include proposed acceptance criteria for a later implementation issue, but do not build it in this task. Out of scope: * Writing Bevy code. * Changing CI. * Adding assets. * Reworking `factory_sim` unless the spec identifies a future boundary issue. Close this when the Bevy/Wasm viewer spec is posted as a comment.
Author
Owner

🔎 ward agent advisor

ward agent advisor --harness codex ran a one-shot standard research pass on this question:

Read the issue title, body, and comment thread below as the research brief. Identify the decisions, options, open questions, and risks, then give a clear advisory recommendation that can be posted back on the issue.


Recommendation

Add a single new workspace crate, crates/factory_viewer, as a debug/observability viewer that depends on factory_sim and factory_content, but keep all simulation authority in factory_sim.

The repo already has the right core split for this: factory_sim owns deterministic stepping and emits serializable TickSnapshots, factory_content owns starter data, and factory_cli is the current headless surface. The viewer should sit beside them as a projection layer, not as a rules layer.

As of July 9, 2026, Bevy's official web examples still emphasize WASM + WebGL for broad browser compatibility, while WebGPU is presented separately. That makes a 2D, WebGL2-compatible, observability-first viewer the safest first slice for both native and browser targets. Trunk is still the cleanest packaging path for a small Rust/WASM app.

Keep the workspace shape simple first:

Cargo.toml
crates/
  factory_content/
  factory_sim/
  factory_cli/
  factory_viewer/
    Cargo.toml
    src/
      lib.rs
      main.rs
      app.rs
      projection.rs
      sim_driver.rs
      controls.rs
      ui.rs
      platform/
        mod.rs
        native.rs
        web.rs
    assets/
      ... placeholder/local viewer assets only ...
    web/
      index.html
      Trunk.toml   # optional, only if config grows past one file

Recommended dependency direction:

  • factory_viewer -> factory_sim
  • factory_viewer -> factory_content
  • factory_cli -> factory_sim
  • factory_sim stays Bevy-free

Do not add a separate factory_viewer_web crate yet. One crate with cfg(target_arch = "wasm32") platform shims is enough until there is real divergence.

Boundary between sim and Bevy

The viewer should consume immutable snapshots and events, not simulation internals as mutable ECS state.

Recommended rule:

  • factory_sim owns GameState, deterministic tick order, dispatch rules, inventories, production rules, and authoritative IDs/state.
  • factory_viewer owns camera, scene graph, labels, colors, controls, interpolation policy, and UI state.
  • Bevy entities are a projection cache of the latest TickSnapshot, not the source of truth.

For the first implementation, define a viewer-local driver abstraction in factory_viewer, not in factory_sim yet. For example:

trait SnapshotSource {
    fn current(&self) -> factory_sim::TickSnapshot;
    fn step(&mut self) -> factory_sim::TickSnapshot;
    fn reset(&mut self);
}

Initial adapter:

  • SimSnapshotSource wraps factory_sim::GameState

Future adapters, without changing the viewer architecture:

  • JSONL replay source from factory_cli output
  • recorded scenario playback
  • remote/network source later, if ever needed

That gives the viewer a clean seam without prematurely changing factory_sim.

Boundary between deterministic ticks and Bevy frame scheduling

Use two clocks:

  • Simulation clock: discrete, deterministic, integer tick steps
  • Render/UI clock: Bevy frame updates at whatever rate the platform can sustain

Recommended behavior:

  • Maintain a viewer resource like PlaybackState { paused, ticks_per_second, accumulated_time }.
  • On each Bevy update, accumulate wall-clock time.
  • When enough time has accumulated, advance the sim by whole ticks through the driver.
  • Apply each completed TickSnapshot to the Bevy projection.
  • Render systems only read projected viewer state.

Important guardrails:

  • No simulation rules in Bevy systems.
  • No frame-rate-coupled mutation of sim state.
  • Cap catch-up work per frame, for example max_ticks_per_frame, to avoid a spiral on slow browsers.
  • Support pause, resume, and single-step from day one.

For the first slice, do not interpolate gameplay state between ticks. Stepwise movement is acceptable for a debug viewer and preserves determinism clarity.

Minimum first viewer slice worth building

The smallest slice that justifies the crate is a 2D orthographic debug viewer for the existing iron-bars scenario.

Include:

  • source node
  • factory node
  • hauler
  • fixed route line or lane indicator between source and factory
  • current hauler assignment state
  • inventory counts for source, hauler, and factory
  • current factory craft progress
  • current tick number
  • controls: play/pause, single-step, reset, and at least one faster speed like 4x
  • event log panel showing current tick events from TickSnapshot.events

Presentation can stay intentionally simple:

  • source/factory as colored boxes or circles
  • hauler as a moving marker or sprite
  • text labels for counts and dispatch state
  • no art fidelity requirement

This is enough to validate the architecture without pretending to be the future game client.

Build targets

Recommendation: design for both native and wasm immediately, but make native the primary dev loop.

That means:

  • first implementation issue should require a working native desktop run
  • first implementation issue should also require a local wasm build and local browser serve
  • do not require full browser polish, CI rollout, or mobile support in milestone 1

Why this split is the right trade:

  • Native is the faster development loop.
  • Browser support is a stated design constraint, so deferring wasm entirely invites accidental native-only choices.
  • The proposed slice is small enough that a same-milestone wasm smoke target is realistic.

For the web renderer path, prefer WebGL2-compatible Bevy usage first, not WebGPU-only features. Bevy's official examples page still treats WebGPU as separate while the main browser examples run on WASM + WebGL.

Web packaging path

Prefer Trunk.

Reasoning:

  • It matches the repo's likely future need for a small static-hostable wasm surface.
  • It handles the HTML entrypoint plus asset copying cleanly.
  • Its copy-dir / copy-file model fits a small viewer assets folder well.

Suggested future commands for the implementation issue:

ward exec test

Add repo verbs when the viewer lands, likely wrapping commands equivalent to:

cargo run -p factory_viewer
rustup target add wasm32-unknown-unknown
RUSTFLAGS='--cfg getrandom_backend="wasm_js"' trunk serve crates/factory_viewer/web/index.html

and for a production wasm build:

RUSTFLAGS='--cfg getrandom_backend="wasm_js"' trunk build crates/factory_viewer/web/index.html --release

Note: Bevy's current migration guidance for web builds calls out the getrandom/RUSTFLAGS requirement for wasm targets, so the implementation issue should account for that explicitly.

Assets strategy for wasm

For the first viewer slice, use viewer-owned local static assets or generated placeholders, not Unity-era asset reuse.

Recommended priority:

  1. generated primitives and Bevy text
  2. tiny viewer-local placeholder PNGs/SVG-derived exports if needed
  3. Unity-era art only later, after a separate asset-import decision

Why:

  • The current repo keeps Unity assets as migration reference material, not an active runtime contract.
  • Pulling viewer milestone 1 through those assets would entangle the Bevy viewer with legacy asset policy, .meta noise, and future LFS decisions.
  • A debug viewer does not need production art.

If art reuse happens later, treat it as a deliberate import/export task into a viewer-owned asset directory, not direct runtime dependence on the existing Unity Assets/ tree.

Feature gates and platform boundaries

Use cfg(target_arch = "wasm32") for platform differences first. Do not over-engineer cargo feature matrices yet.

Platform-specific behavior that should be isolated behind thin shims:

  • browser logging and panic hook setup
  • any JS interop or browser-only controls
  • persistence or download/export behavior later
  • window/canvas initialization differences

Keep shared viewer logic platform-neutral:

  • projection from TickSnapshot
  • playback controls/state machine
  • layout of labels/panels
  • scenario reset/step logic

Avoid in milestone 1:

  • filesystem assumptions
  • background threads as a requirement
  • native-only plugins
  • browser-only networking
  • WebGPU-only rendering features

Out of scope until after the first slice

Keep these explicitly out of milestone 1:

  • pathfinding visualization beyond the fixed source-factory route
  • building placement or editing tools
  • authoritative input-to-sim gameplay controls beyond tick playback
  • save/load UX
  • advanced camera controls
  • production art pass
  • Unity asset import pipeline
  • CI changes
  • multiplayer or remote sync
  • replacing factory_cli
  • any change that makes Bevy required for factory_sim

Risks and open questions

Risks

  • Snapshot shape growth: if later scenarios add more entities, TickSnapshot may become too coarse or too allocation-heavy. That is a future optimization problem, not a blocker for the first slice.
  • Wasm friction: Bevy web builds still need explicit wasm setup and care around browser-safe features.
  • UI creep: the viewer can easily drift into a game client. The milestone should stay debug-first.
  • Platform divergence: if native and web code paths grow quickly, a second crate may eventually be justified. Not yet.

Open questions worth deferring, not blocking on

  • whether long-term replay should read JSONL directly from factory_cli output or from a shared recorded format
  • whether future sim snapshots should add stable entity IDs beyond the current fixed scenario shape
  • whether later viewers want a richer event stream than Vec<String>

None of those should block the first implementation issue.

Proposed acceptance criteria for a later implementation issue

  • Add crates/factory_viewer to the workspace without adding Bevy to factory_sim.
  • factory_viewer runs the existing starter iron-bars scenario by wrapping factory_sim::GameState.
  • The viewer renders source, factory, hauler, current route, current assignment state, and inventory counts.
  • The viewer displays current tick number and factory craft progress.
  • The viewer supports pause, resume, single-step, and reset.
  • The viewer uses deterministic tick stepping separate from Bevy frame updates.
  • Native desktop run works locally.
  • wasm32-unknown-unknown build works locally and can be served through Trunk.
  • Web packaging uses viewer-owned static assets or generated placeholders only.
  • No CI changes are required for the first milestone.
  • factory_sim remains platform-neutral and Bevy-free.
  • Repo docs are updated to mention the new viewer surface and commands when implementation lands.

Source notes


Researched and posted automatically by ward agent advisor --harness codex (ward#179). This is one-shot research, not a carried change - verify before acting on it.

— Codex, via ward agent

### 🔎 ward agent advisor `ward agent advisor --harness codex` ran a one-shot **standard** research pass on this question: > Read the issue title, body, and comment thread below as the research brief. Identify the decisions, options, open questions, and risks, then give a clear advisory recommendation that can be posted back on the issue. --- ## Recommendation Add a single new workspace crate, `crates/factory_viewer`, as a **debug/observability viewer** that depends on `factory_sim` and `factory_content`, but keep all simulation authority in `factory_sim`. The repo already has the right core split for this: `factory_sim` owns deterministic stepping and emits serializable `TickSnapshot`s, `factory_content` owns starter data, and `factory_cli` is the current headless surface. The viewer should sit beside them as a projection layer, not as a rules layer. As of **July 9, 2026**, Bevy's official web examples still emphasize **WASM + WebGL** for broad browser compatibility, while WebGPU is presented separately. That makes a **2D, WebGL2-compatible, observability-first viewer** the safest first slice for both native and browser targets. Trunk is still the cleanest packaging path for a small Rust/WASM app. ## Recommended crate and workspace shape Keep the workspace shape simple first: ```text Cargo.toml crates/ factory_content/ factory_sim/ factory_cli/ factory_viewer/ Cargo.toml src/ lib.rs main.rs app.rs projection.rs sim_driver.rs controls.rs ui.rs platform/ mod.rs native.rs web.rs assets/ ... placeholder/local viewer assets only ... web/ index.html Trunk.toml # optional, only if config grows past one file ``` Recommended dependency direction: - `factory_viewer -> factory_sim` - `factory_viewer -> factory_content` - `factory_cli -> factory_sim` - `factory_sim` stays **Bevy-free** Do **not** add a separate `factory_viewer_web` crate yet. One crate with `cfg(target_arch = "wasm32")` platform shims is enough until there is real divergence. ## Boundary between sim and Bevy The viewer should consume **immutable snapshots and events**, not simulation internals as mutable ECS state. Recommended rule: - `factory_sim` owns `GameState`, deterministic tick order, dispatch rules, inventories, production rules, and authoritative IDs/state. - `factory_viewer` owns camera, scene graph, labels, colors, controls, interpolation policy, and UI state. - Bevy entities are a **projection cache** of the latest `TickSnapshot`, not the source of truth. For the first implementation, define a viewer-local driver abstraction in `factory_viewer`, not in `factory_sim` yet. For example: ```rust trait SnapshotSource { fn current(&self) -> factory_sim::TickSnapshot; fn step(&mut self) -> factory_sim::TickSnapshot; fn reset(&mut self); } ``` Initial adapter: - `SimSnapshotSource` wraps `factory_sim::GameState` Future adapters, without changing the viewer architecture: - JSONL replay source from `factory_cli` output - recorded scenario playback - remote/network source later, if ever needed That gives the viewer a clean seam without prematurely changing `factory_sim`. ## Boundary between deterministic ticks and Bevy frame scheduling Use **two clocks**: - **Simulation clock**: discrete, deterministic, integer tick steps - **Render/UI clock**: Bevy frame updates at whatever rate the platform can sustain Recommended behavior: - Maintain a viewer resource like `PlaybackState { paused, ticks_per_second, accumulated_time }`. - On each Bevy update, accumulate wall-clock time. - When enough time has accumulated, advance the sim by whole ticks through the driver. - Apply each completed `TickSnapshot` to the Bevy projection. - Render systems only read projected viewer state. Important guardrails: - No simulation rules in Bevy systems. - No frame-rate-coupled mutation of sim state. - Cap catch-up work per frame, for example `max_ticks_per_frame`, to avoid a spiral on slow browsers. - Support `pause`, `resume`, and `single-step` from day one. For the first slice, **do not interpolate gameplay state between ticks**. Stepwise movement is acceptable for a debug viewer and preserves determinism clarity. ## Minimum first viewer slice worth building The smallest slice that justifies the crate is a **2D orthographic debug viewer** for the existing `iron-bars` scenario. Include: - source node - factory node - hauler - fixed route line or lane indicator between source and factory - current hauler assignment state - inventory counts for source, hauler, and factory - current factory craft progress - current tick number - controls: `play/pause`, `single-step`, `reset`, and at least one faster speed like `4x` - event log panel showing current tick events from `TickSnapshot.events` Presentation can stay intentionally simple: - source/factory as colored boxes or circles - hauler as a moving marker or sprite - text labels for counts and dispatch state - no art fidelity requirement This is enough to validate the architecture without pretending to be the future game client. ## Build targets Recommendation: **design for both native and wasm immediately, but make native the primary dev loop**. That means: - first implementation issue should require a working native desktop run - first implementation issue should also require a local wasm build and local browser serve - do **not** require full browser polish, CI rollout, or mobile support in milestone 1 Why this split is the right trade: - Native is the faster development loop. - Browser support is a stated design constraint, so deferring wasm entirely invites accidental native-only choices. - The proposed slice is small enough that a same-milestone wasm smoke target is realistic. For the web renderer path, prefer **WebGL2-compatible Bevy usage first**, not WebGPU-only features. Bevy's official examples page still treats WebGPU as separate while the main browser examples run on WASM + WebGL. ## Web packaging path Prefer **Trunk**. Reasoning: - It matches the repo's likely future need for a small static-hostable wasm surface. - It handles the HTML entrypoint plus asset copying cleanly. - Its `copy-dir` / `copy-file` model fits a small viewer assets folder well. Suggested future commands for the implementation issue: ```bash ward exec test ``` Add repo verbs when the viewer lands, likely wrapping commands equivalent to: ```bash cargo run -p factory_viewer ``` ```bash rustup target add wasm32-unknown-unknown RUSTFLAGS='--cfg getrandom_backend="wasm_js"' trunk serve crates/factory_viewer/web/index.html ``` and for a production wasm build: ```bash RUSTFLAGS='--cfg getrandom_backend="wasm_js"' trunk build crates/factory_viewer/web/index.html --release ``` Note: Bevy's current migration guidance for web builds calls out the `getrandom`/`RUSTFLAGS` requirement for wasm targets, so the implementation issue should account for that explicitly. ## Assets strategy for wasm For the first viewer slice, use **viewer-owned local static assets or generated placeholders**, not Unity-era asset reuse. Recommended priority: 1. generated primitives and Bevy text 2. tiny viewer-local placeholder PNGs/SVG-derived exports if needed 3. Unity-era art only later, after a separate asset-import decision Why: - The current repo keeps Unity assets as migration reference material, not an active runtime contract. - Pulling viewer milestone 1 through those assets would entangle the Bevy viewer with legacy asset policy, `.meta` noise, and future LFS decisions. - A debug viewer does not need production art. If art reuse happens later, treat it as a deliberate import/export task into a viewer-owned asset directory, not direct runtime dependence on the existing Unity `Assets/` tree. ## Feature gates and platform boundaries Use `cfg(target_arch = "wasm32")` for platform differences first. Do not over-engineer cargo feature matrices yet. Platform-specific behavior that should be isolated behind thin shims: - browser logging and panic hook setup - any JS interop or browser-only controls - persistence or download/export behavior later - window/canvas initialization differences Keep shared viewer logic platform-neutral: - projection from `TickSnapshot` - playback controls/state machine - layout of labels/panels - scenario reset/step logic Avoid in milestone 1: - filesystem assumptions - background threads as a requirement - native-only plugins - browser-only networking - WebGPU-only rendering features ## Out of scope until after the first slice Keep these explicitly out of milestone 1: - pathfinding visualization beyond the fixed source-factory route - building placement or editing tools - authoritative input-to-sim gameplay controls beyond tick playback - save/load UX - advanced camera controls - production art pass - Unity asset import pipeline - CI changes - multiplayer or remote sync - replacing `factory_cli` - any change that makes Bevy required for `factory_sim` ## Risks and open questions ### Risks - **Snapshot shape growth**: if later scenarios add more entities, `TickSnapshot` may become too coarse or too allocation-heavy. That is a future optimization problem, not a blocker for the first slice. - **Wasm friction**: Bevy web builds still need explicit wasm setup and care around browser-safe features. - **UI creep**: the viewer can easily drift into a game client. The milestone should stay debug-first. - **Platform divergence**: if native and web code paths grow quickly, a second crate may eventually be justified. Not yet. ### Open questions worth deferring, not blocking on - whether long-term replay should read JSONL directly from `factory_cli` output or from a shared recorded format - whether future sim snapshots should add stable entity IDs beyond the current fixed scenario shape - whether later viewers want a richer event stream than `Vec<String>` None of those should block the first implementation issue. ## Proposed acceptance criteria for a later implementation issue - Add `crates/factory_viewer` to the workspace without adding Bevy to `factory_sim`. - `factory_viewer` runs the existing starter `iron-bars` scenario by wrapping `factory_sim::GameState`. - The viewer renders source, factory, hauler, current route, current assignment state, and inventory counts. - The viewer displays current tick number and factory craft progress. - The viewer supports `pause`, `resume`, `single-step`, and `reset`. - The viewer uses deterministic tick stepping separate from Bevy frame updates. - Native desktop run works locally. - `wasm32-unknown-unknown` build works locally and can be served through Trunk. - Web packaging uses viewer-owned static assets or generated placeholders only. - No CI changes are required for the first milestone. - `factory_sim` remains platform-neutral and Bevy-free. - Repo docs are updated to mention the new viewer surface and commands when implementation lands. ## Source notes - Repo workspace and current sim split: [Cargo.toml](Cargo.toml), [docs/factory-sim.md](docs/factory-sim.md), [crates/factory_sim/src/lib.rs](crates/factory_sim/src/lib.rs), [crates/factory_sim/src/world.rs](crates/factory_sim/src/world.rs), [crates/factory_sim/src/dispatch.rs](crates/factory_sim/src/dispatch.rs) - Bevy web examples page, showing WASM + WebGL examples and separate WebGPU page: <https://bevy.org/examples/> - Bevy 0.16 -> 0.17 migration guide, noting wasm `getrandom` / `RUSTFLAGS` requirements: <https://bevy.org/learn/migration-guides/0-16-to-0-17/> - Trunk asset guide, including `copy-dir` / `copy-file`: <https://trunk-rs.github.io/trunk/guide/assets/index.html> --- Researched and posted automatically by `ward agent advisor --harness codex` (ward#179). This is one-shot research, not a carried change - verify before acting on it. <!-- ward-agent-reply --> <!-- ward-agent-signature --> — Codex, via `ward agent`
Author
Owner

Clarification from Kai after the advisor comment: do not mix galaxy generation into factory-game-v3.

This issue should be read only as a future factory-game Bevy/Wasm debug viewer over factory_sim. Galaxy generation and any Bevy galaxy viewer are a separate app/repo concern, not a factory_game crate or milestone.

Clarification from Kai after the advisor comment: do not mix galaxy generation into `factory-game-v3`. This issue should be read only as a future **factory-game** Bevy/Wasm debug viewer over `factory_sim`. Galaxy generation and any Bevy galaxy viewer are a separate app/repo concern, not a `factory_game` crate or milestone.
Author
Owner

Bevy/Wasm viewer spec, informed by the landed #18 shell (crates/factory_shell, 7cd29b4) and bounded by docs/app-boundary.md.

Crate/workspace shape

  • New crate crates/factory_viewer, sibling to factory_shell. Depends on factory_sim + factory_content (read-only consumption) and Bevy. factory_sim stays pure and Bevy-free - the viewer is a client of its public API, never a host for its rules.
  • factory_shell stays the packaging exemplar (trunk from the crate dir, nginx Dockerfile). Recommended shape: the viewer replaces the shell's placeholder scene behind the same packaging, keeping one deployable app - the shell crate's scene was always scaffolding.

Bevy <-> sim boundary

  • The sim runs as an owned GameState inside a Bevy resource (for example SimHost { state: GameState, cadence: Timer }).
  • Bevy systems never mutate WorldState directly. One system calls state.step() on a fixed cadence (default 2 ticks/sec, pause + single-step controls). Everything rendered derives from the returned TickSnapshot, never from reaching into GameState internals.
  • That makes the deterministic-tick vs frame-rate boundary explicit. Frames interpolate nothing at first - haulers jump node to node. Interpolation over position_grid is later polish.
  • Snapshots are already Serialize and stable - the viewer consumes the same shape the CLI emits, which keeps a future load-a-JSONL-recording playback mode trivial and requires no live sim.

Minimum first slice

  • Render the hub topology from TopologySnapshot grid positions: sources, road, factory as colored quads with labels.
  • Render haulers at position_grid with cargo count and assignment phase as text.
  • Side panel (Bevy UI text): tick number, per-node inventory counts, craft progress, run metrics summary.
  • Controls: pause/resume, single-step, speed toggle. Scenario select stays a compile-time default (iron-bars) in slice one.
  • Event log: last N event strings as scrolling text.

Build targets and packaging

  • Native desktop and wasm32-unknown-unknown from day one - the shell proves both paths and the viewer rides the same trunk + Dockerfile pipeline. agentic-os:v0.255.0 carries the full toolchain in CI.
  • Ward verbs repoint (shell-* verbs serve whichever scene ships) or gain viewer-* twins - implementer's choice.

Assets

  • Slice one uses no external assets: colored quads and Bevy's default font, which sidesteps Wasm asset loading entirely. Unity-era art stays untouched as reference; if art returns it enters as trunk-copied static assets, not runtime Git LFS loads.

Out of scope until after slice one

Interpolated movement, camera controls beyond a fixed 2D view, scenario editing, live parameter tweaking, in-app scenario switching, recording/replay UI (the JSONL playback hook stays a design affordance), and anything galaxy-shaped.

Acceptance criteria for the implementation issue

  • cargo test --workspace passes, including one headless test proving SimHost stepping produces byte-identical snapshots to calling GameState::step() directly - determinism preserved under the Bevy host.
  • Native run and trunk-served Wasm both render the iron-bars scenario visibly progressing to iron bars.
  • Pause and single-step verified.
  • factory_sim / factory_content gain no Bevy dependency.
  • Docs: FEATURES entry + docs/factory-viewer.md; factory-shell.md updated for the replace-vs-twin choice.

Risks

  • Bevy version churn between spec and implementation - pin whatever factory_shell pins at the time (0.19 today).
  • Wasm bundle size once UI text rendering is in - the shell ships 76MB un-wasm-opt'ed already; the wasm-opt follow-up (upstream binaryen only, per the galaxy-gen pin note) should probably land with or before the viewer.
  • Tick-stepping inside a resource is single-threaded and fine at this scale. If scenarios grow huge, move stepping to an async task before considering ECS-ifying the sim - which stays off the table.

WARD-OUTCOME: done

Bevy/Wasm viewer spec, informed by the landed #18 shell (`crates/factory_shell`, `7cd29b4`) and bounded by [docs/app-boundary.md](https://forgejo.coilysiren.me/coilyco-gaming/factory-game-v3/src/branch/main/docs/app-boundary.md). ## Crate/workspace shape * New crate `crates/factory_viewer`, sibling to `factory_shell`. Depends on `factory_sim` + `factory_content` (read-only consumption) and Bevy. `factory_sim` stays pure and Bevy-free - the viewer is a client of its public API, never a host for its rules. * `factory_shell` stays the packaging exemplar (trunk from the crate dir, nginx Dockerfile). Recommended shape: the viewer replaces the shell's placeholder scene behind the same packaging, keeping one deployable app - the shell crate's scene was always scaffolding. ## Bevy <-> sim boundary * The sim runs as an owned `GameState` inside a Bevy resource (for example `SimHost { state: GameState, cadence: Timer }`). * Bevy systems never mutate `WorldState` directly. One system calls `state.step()` on a fixed cadence (default 2 ticks/sec, pause + single-step controls). Everything rendered derives from the returned `TickSnapshot`, never from reaching into `GameState` internals. * That makes the deterministic-tick vs frame-rate boundary explicit. Frames interpolate nothing at first - haulers jump node to node. Interpolation over `position_grid` is later polish. * Snapshots are already `Serialize` and stable - the viewer consumes the same shape the CLI emits, which keeps a future load-a-JSONL-recording playback mode trivial and requires no live sim. ## Minimum first slice * Render the hub topology from `TopologySnapshot` grid positions: sources, road, factory as colored quads with labels. * Render haulers at `position_grid` with cargo count and assignment phase as text. * Side panel (Bevy UI text): tick number, per-node inventory counts, craft progress, run metrics summary. * Controls: pause/resume, single-step, speed toggle. Scenario select stays a compile-time default (`iron-bars`) in slice one. * Event log: last N event strings as scrolling text. ## Build targets and packaging * Native desktop and `wasm32-unknown-unknown` from day one - the shell proves both paths and the viewer rides the same trunk + Dockerfile pipeline. `agentic-os:v0.255.0` carries the full toolchain in CI. * Ward verbs repoint (`shell-*` verbs serve whichever scene ships) or gain `viewer-*` twins - implementer's choice. ## Assets * Slice one uses no external assets: colored quads and Bevy's default font, which sidesteps Wasm asset loading entirely. Unity-era art stays untouched as reference; if art returns it enters as trunk-copied static assets, not runtime Git LFS loads. ## Out of scope until after slice one Interpolated movement, camera controls beyond a fixed 2D view, scenario editing, live parameter tweaking, in-app scenario switching, recording/replay UI (the JSONL playback hook stays a design affordance), and anything galaxy-shaped. ## Acceptance criteria for the implementation issue * `cargo test --workspace` passes, including one headless test proving `SimHost` stepping produces byte-identical snapshots to calling `GameState::step()` directly - determinism preserved under the Bevy host. * Native run and trunk-served Wasm both render the iron-bars scenario visibly progressing to iron bars. * Pause and single-step verified. * `factory_sim` / `factory_content` gain no Bevy dependency. * Docs: FEATURES entry + docs/factory-viewer.md; factory-shell.md updated for the replace-vs-twin choice. ## Risks * Bevy version churn between spec and implementation - pin whatever `factory_shell` pins at the time (0.19 today). * Wasm bundle size once UI text rendering is in - the shell ships 76MB un-wasm-opt'ed already; the wasm-opt follow-up (upstream binaryen only, per the galaxy-gen pin note) should probably land with or before the viewer. * Tick-stepping inside a resource is single-threaded and fine at this scale. If scenarios grow huge, move stepping to an async task before considering ECS-ifying the sim - which stays off the table. `WARD-OUTCOME: done`
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/factory-game-v3#16
No description provided.