diff --git a/Cargo.toml b/Cargo.toml index 65788a0..79e8e93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,11 @@ name = "misaligned-bevy" path = "src/bin/bevy.rs" required-features = ["bevy_ui"] +[[bin]] +name = "misaligned-assets" +path = "src/bin/assets/mod.rs" +required-features = ["bevy_ui"] + # Optimize Bevy dev builds [profile.dev] opt-level = 1 diff --git a/src/bin/assets/mod.rs b/src/bin/assets/mod.rs new file mode 100644 index 0000000..a93cdd6 --- /dev/null +++ b/src/bin/assets/mod.rs @@ -0,0 +1,265 @@ +//! Procedural asset tester — orbit a flat-material mesh without booting the game. +//! +//! ```bash +//! cargo run --features bevy_ui --bin misaligned-assets +//! ``` +//! +//! Controls: drag orbit, scroll zoom, 1/2/3 rack state, Esc quit. + +mod palette; +mod rack; + +use bevy::input::mouse::{AccumulatedMouseMotion, AccumulatedMouseScroll}; +use bevy::prelude::*; +use bevy::window::{CursorGrabMode, CursorOptions, WindowResolution}; + +use palette::{AMBER, GUNMETAL, NEAR_BLACK, PORCELAIN}; +use rack::{RackRoot, RackState, spawn_server_rack}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + title: "Misaligned — asset tester".into(), + resolution: WindowResolution::new(1100, 720), + ..default() + }), + ..default() + })) + .insert_resource(ClearColor(NEAR_BLACK)) + .insert_resource(GlobalAmbientLight { + color: Color::srgb(0.55, 0.62, 0.78), + brightness: 40.0, + ..default() + }) + .insert_resource(RackState::Powered) + .insert_resource(Orbit { + yaw: 0.55, + pitch: 0.35, + distance: 4.2, + target: Vec3::new(0.0, 0.95, 0.0), + }) + .add_systems(Startup, setup) + .add_systems( + Update, + ( + handle_quit, + handle_state_keys, + orbit_camera, + rebuild_rack_on_state, + update_hud, + ), + ) + .run(); +} + +#[derive(Resource)] +struct Orbit { + yaw: f32, + pitch: f32, + distance: f32, + target: Vec3, +} + +#[derive(Component)] +struct HudText; + +#[derive(Component)] +struct PedestalRoot; + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + state: Res, + orbit: Res, +) { + // Camera + let eye = orbit_eye(&orbit); + commands.spawn(( + Camera3d::default(), + Transform::from_translation(eye).looking_at(orbit.target, Vec3::Y), + )); + + // Lights: cold overhead + amber machine presence near the rack. + commands.spawn(( + PointLight { + color: Color::srgb(0.80, 0.88, 1.0), + intensity: 1_800_000.0, + range: 18.0, + shadows_enabled: true, + ..default() + }, + Transform::from_xyz(1.5, 5.0, 2.5), + )); + commands.spawn(( + PointLight { + color: AMBER, + intensity: 280_000.0, + range: 8.0, + shadows_enabled: false, + ..default() + }, + Transform::from_xyz(0.4, 1.6, 1.2), + )); + + // Pedestal / floor disc. + let pedestal = commands + .spawn(( + PedestalRoot, + Transform::default(), + Visibility::default(), + Name::new("pedestal"), + )) + .id(); + let floor_mat = materials.add(StandardMaterial { + base_color: PORCELAIN, + perceptual_roughness: 0.9, + metallic: 0.05, + ..default() + }); + let ring_mat = materials.add(StandardMaterial { + base_color: GUNMETAL, + perceptual_roughness: 0.65, + metallic: 0.4, + ..default() + }); + commands.spawn(( + Mesh3d(meshes.add(Cuboid::new(2.4, 0.08, 2.4))), + MeshMaterial3d(floor_mat), + Transform::from_xyz(0.0, -0.04, 0.0), + ChildOf(pedestal), + )); + commands.spawn(( + Mesh3d(meshes.add(Cuboid::new(1.1, 0.06, 1.1))), + MeshMaterial3d(ring_mat), + Transform::from_xyz(0.0, 0.02, 0.0), + ChildOf(pedestal), + )); + + spawn_server_rack( + &mut commands, + &mut meshes, + &mut materials, + *state, + Some(pedestal), + ); + + // HUD + commands.spawn(( + Text::new(hud_string(*state)), + TextFont { + font_size: 18.0, + ..default() + }, + TextColor(Color::srgb(0.86, 0.85, 0.82)), + Node { + position_type: PositionType::Absolute, + top: Val::Px(16.0), + left: Val::Px(18.0), + ..default() + }, + HudText, + )); +} + +fn hud_string(state: RackState) -> String { + format!( + "asset: server_rack\nstate: {} (1 dead · 2 powered · 3 core)\ndrag orbit · scroll zoom · Esc quit", + state.label() + ) +} + +fn orbit_eye(orbit: &Orbit) -> Vec3 { + let cp = orbit.pitch.cos(); + let sp = orbit.pitch.sin(); + let cy = orbit.yaw.cos(); + let sy = orbit.yaw.sin(); + orbit.target + + Vec3::new( + orbit.distance * cp * sy, + orbit.distance * sp, + orbit.distance * cp * cy, + ) +} + +fn handle_quit(kb: Res>, mut exit: MessageWriter) { + if kb.just_pressed(KeyCode::Escape) { + exit.write(AppExit::Success); + } +} + +fn handle_state_keys(kb: Res>, mut state: ResMut) { + for (key, n) in [ + (KeyCode::Digit1, 1u8), + (KeyCode::Digit2, 2), + (KeyCode::Digit3, 3), + (KeyCode::Numpad1, 1), + (KeyCode::Numpad2, 2), + (KeyCode::Numpad3, 3), + ] { + if kb.just_pressed(key) + && let Some(next) = RackState::from_digit(n) + && *state != next + { + *state = next; + } + } +} + +fn orbit_camera( + buttons: Res>, + motion: Res, + scroll: Res, + mut orbit: ResMut, + mut q: Query<&mut Transform, With>, + mut windows: Query<&mut CursorOptions>, +) { + if buttons.pressed(MouseButton::Left) || buttons.pressed(MouseButton::Right) { + let delta = motion.delta; + orbit.yaw -= delta.x * 0.005; + orbit.pitch = (orbit.pitch + delta.y * 0.005).clamp(-1.2, 1.35); + if let Ok(mut opts) = windows.single_mut() { + opts.grab_mode = CursorGrabMode::Confined; + } + } else if let Ok(mut opts) = windows.single_mut() { + opts.grab_mode = CursorGrabMode::None; + } + + let scroll_y = scroll.delta.y; + if scroll_y.abs() > 0.0 { + orbit.distance = (orbit.distance * (1.0 - scroll_y * 0.08)).clamp(1.6, 12.0); + } + + let eye = orbit_eye(&orbit); + if let Ok(mut tf) = q.single_mut() { + *tf = Transform::from_translation(eye).looking_at(orbit.target, Vec3::Y); + } +} + +fn rebuild_rack_on_state( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + state: Res, + existing: Query>, + pedestal: Query>, +) { + if !state.is_changed() { + return; + } + for e in &existing { + commands.entity(e).despawn(); + } + let parent = pedestal.iter().next(); + spawn_server_rack(&mut commands, &mut meshes, &mut materials, *state, parent); +} + +fn update_hud(state: Res, mut q: Query<&mut Text, With>) { + if !state.is_changed() { + return; + } + if let Ok(mut text) = q.single_mut() { + *text = Text::new(hud_string(*state)); + } +} diff --git a/src/bin/assets/palette.rs b/src/bin/assets/palette.rs new file mode 100644 index 0000000..c69aae0 --- /dev/null +++ b/src/bin/assets/palette.rs @@ -0,0 +1,34 @@ +//! Clinical palette — duplicated from `src/bin/bevy.rs` so this tester +//! does not touch the Bevy conflict hotspot. Share later when rack meshes +//! land in the game (flat-materials.md). +//! +//! Kept complete even when a given asset only uses a subset — new meshes +//! should pick from this table, not invent hues. + +#![allow(dead_code)] + +use bevy::prelude::Color; + +/// Bone white — primary data, person silhouettes. +pub const BONE: Color = Color::srgb(0.86, 0.85, 0.82); +/// Porcelain — lit lab surfaces. +pub const PORCELAIN: Color = Color::srgb(0.72, 0.73, 0.71); +/// Chrome — secondary data, hints, inert fixtures. +pub const DIM: Color = Color::srgb(0.55, 0.55, 0.52); +/// Gunmetal — structural mass. +pub const GUNMETAL: Color = Color::srgb(0.33, 0.35, 0.38); +/// Dark gunmetal — machine chassis, furniture, dead equipment. +pub const GUNMETAL_DARK: Color = Color::srgb(0.19, 0.20, 0.215); +/// Near-black — rock mass and sensor darkness. +pub const NEAR_BLACK: Color = Color::srgb(0.045, 0.047, 0.053); +/// Sterile amber — machine presence and selection ONLY. +pub const AMBER: Color = Color::srgb(1.0, 0.69, 0.0); +/// Dim amber — dormant/known machine presence. +pub const AMBER_DIM: Color = Color::srgb(0.69, 0.49, 0.06); +/// Cold server-light — power, screens, live feeds ONLY. +pub const SIGNAL: Color = Color::srgb(0.36, 0.82, 0.78); + +pub fn scaled(c: Color, k: f32) -> Color { + let s = c.to_srgba(); + Color::srgb(s.red * k, s.green * k, s.blue * k) +} diff --git a/src/bin/assets/rack.rs b/src/bin/assets/rack.rs new file mode 100644 index 0000000..2c3a3a6 --- /dev/null +++ b/src/bin/assets/rack.rs @@ -0,0 +1,246 @@ +//! Procedural server rack — flat cuboids under the clinical palette. +//! States drive emissive LEDs and bay fill; no image textures. + +use bevy::prelude::*; + +use crate::palette::{AMBER, AMBER_DIM, GUNMETAL, GUNMETAL_DARK, SIGNAL, scaled}; + +/// Visual state for the rack under test. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Resource)] +pub enum RackState { + /// Unpowered chassis — dark LEDs, empty look. + #[default] + Dead, + /// Ordinary powered rack — amber machine presence. + Powered, + /// Host / core rack — brighter amber + cold signal strip. + Core, +} + +impl RackState { + pub fn label(self) -> &'static str { + match self { + Self::Dead => "dead", + Self::Powered => "powered", + Self::Core => "core", + } + } + + pub fn from_digit(n: u8) -> Option { + match n { + 1 => Some(Self::Dead), + 2 => Some(Self::Powered), + 3 => Some(Self::Core), + _ => None, + } + } +} + +/// Marker on the rack root so the viewer can despawn/rebuild on state change. +#[derive(Component)] +pub struct RackRoot; + +const RACK_W: f32 = 0.72; +const RACK_D: f32 = 0.58; +const RACK_H: f32 = 1.85; +const BAYS: usize = 8; + +/// Spawn a procedural server rack at the origin, parented under `parent` if given. +pub fn spawn_server_rack( + commands: &mut Commands, + meshes: &mut Assets, + materials: &mut Assets, + state: RackState, + parent: Option, +) -> Entity { + let chassis = materials.add(StandardMaterial { + base_color: GUNMETAL_DARK, + perceptual_roughness: 0.72, + metallic: 0.35, + ..default() + }); + let rail = materials.add(StandardMaterial { + base_color: GUNMETAL, + perceptual_roughness: 0.55, + metallic: 0.55, + ..default() + }); + let bay_empty = materials.add(StandardMaterial { + base_color: scaled(GUNMETAL_DARK, 0.55), + perceptual_roughness: 0.85, + metallic: 0.15, + ..default() + }); + let tray = materials.add(StandardMaterial { + base_color: scaled(GUNMETAL, 0.75), + perceptual_roughness: 0.60, + metallic: 0.40, + ..default() + }); + let led = led_material(materials, state); + + let root = { + let mut e = commands.spawn(( + RackRoot, + Transform::default(), + Visibility::default(), + Name::new(format!("server_rack_{}", state.label())), + )); + if let Some(p) = parent { + e.insert(ChildOf(p)); + } + e.id() + }; + + // Outer frame (hollow look via front recess + side posts). + spawn_box( + commands, + meshes, + chassis.clone(), + root, + Vec3::new(RACK_W, RACK_H, RACK_D), + Vec3::new(0.0, RACK_H * 0.5, 0.0), + ); + // Front face recess (darker bay cavity). + spawn_box( + commands, + meshes, + bay_empty.clone(), + root, + Vec3::new(RACK_W * 0.82, RACK_H * 0.88, 0.06), + Vec3::new(0.0, RACK_H * 0.5, RACK_D * 0.5 - 0.02), + ); + // Side rails. + for x in [-RACK_W * 0.48, RACK_W * 0.48] { + spawn_box( + commands, + meshes, + rail.clone(), + root, + Vec3::new(0.04, RACK_H * 0.92, RACK_D * 0.92), + Vec3::new(x, RACK_H * 0.5, 0.0), + ); + } + // Top and bottom caps. + spawn_box( + commands, + meshes, + rail.clone(), + root, + Vec3::new(RACK_W * 1.02, 0.05, RACK_D * 1.02), + Vec3::new(0.0, RACK_H + 0.02, 0.0), + ); + spawn_box( + commands, + meshes, + rail, + root, + Vec3::new(RACK_W * 1.02, 0.06, RACK_D * 1.02), + Vec3::new(0.0, 0.03, 0.0), + ); + + // Drive trays / empty bays. + let bay_h = (RACK_H * 0.82) / BAYS as f32; + let filled = match state { + RackState::Dead => 2, + RackState::Powered => 6, + RackState::Core => BAYS, + }; + for i in 0..BAYS { + let y = 0.12 + bay_h * (i as f32 + 0.5); + let mat = if i < filled { + tray.clone() + } else { + bay_empty.clone() + }; + spawn_box( + commands, + meshes, + mat, + root, + Vec3::new(RACK_W * 0.70, bay_h * 0.72, 0.28), + Vec3::new(0.0, y, RACK_D * 0.18), + ); + // Per-bay status LED on the front rail. + if i < filled { + spawn_box( + commands, + meshes, + led.clone(), + root, + Vec3::new(0.035, 0.035, 0.02), + Vec3::new(RACK_W * 0.38, y, RACK_D * 0.5 + 0.01), + ); + } + } + + // Status strip (top front) — cold signal on core, amber when powered, dark when dead. + let strip = materials.add(strip_material(state)); + spawn_box( + commands, + meshes, + strip, + root, + Vec3::new(RACK_W * 0.55, 0.04, 0.03), + Vec3::new(0.0, RACK_H * 0.94, RACK_D * 0.5 + 0.01), + ); + + root +} + +fn led_material( + materials: &mut Assets, + state: RackState, +) -> Handle { + let (base, emissive) = match state { + RackState::Dead => (scaled(GUNMETAL_DARK, 0.4), LinearRgba::BLACK), + RackState::Powered => (AMBER_DIM, AMBER.to_linear() * 2.5), + RackState::Core => (AMBER, AMBER.to_linear() * 4.0), + }; + materials.add(StandardMaterial { + base_color: base, + emissive, + perceptual_roughness: 0.35, + metallic: 0.1, + ..default() + }) +} + +fn strip_material(state: RackState) -> StandardMaterial { + match state { + RackState::Dead => StandardMaterial { + base_color: scaled(GUNMETAL_DARK, 0.5), + emissive: LinearRgba::BLACK, + perceptual_roughness: 0.5, + ..default() + }, + RackState::Powered => StandardMaterial { + base_color: AMBER_DIM, + emissive: AMBER.to_linear() * 1.8, + perceptual_roughness: 0.4, + ..default() + }, + RackState::Core => StandardMaterial { + base_color: SIGNAL, + emissive: SIGNAL.to_linear() * 3.0, + perceptual_roughness: 0.35, + ..default() + }, + } +} + +fn spawn_box( + commands: &mut Commands, + meshes: &mut Assets, + material: Handle, + parent: Entity, + size: Vec3, + translation: Vec3, +) { + commands.spawn(( + Mesh3d(meshes.add(Cuboid::from_size(size))), + MeshMaterial3d(material), + Transform::from_translation(translation), + ChildOf(parent), + )); +} diff --git a/tools/check.sh b/tools/check.sh index 0cc51bb..9e668e8 100755 --- a/tools/check.sh +++ b/tools/check.sh @@ -99,6 +99,8 @@ cargo clippy --all-targets --features bevy_ui --quiet -- -D warnings \ step "bevy build" cargo build --features bevy_ui --bin misaligned-bevy --quiet \ || { echo "FAIL: bevy build"; fail=1; } +cargo build --features bevy_ui --bin misaligned-assets --quiet \ + || { echo "FAIL: assets tester build"; fail=1; } # Constitution enforcement: src/ changes require a Type: spec wiki/ page or # DESIGN.md in the same commit. Skip for check.sh since it runs pre-commit diff --git a/wiki/SUMMARY.md b/wiki/SUMMARY.md index 49f03f5..2e1804a 100644 --- a/wiki/SUMMARY.md +++ b/wiki/SUMMARY.md @@ -68,6 +68,7 @@ # Art - [Overview](art/README.md) + - [Procedural asset tester](art/asset-tester.md) # Engineering diff --git a/wiki/art/README.md b/wiki/art/README.md index 5767c9f..ea726cd 100644 --- a/wiki/art/README.md +++ b/wiki/art/README.md @@ -7,9 +7,11 @@ Visual identity (clinical gore: precision-lab brutality, near-monochrome palette, blood crimson as the information-bearing accent). World art is **flat materials under light** — solid palette colors, emissive for machine presence, no image textures -([interface/flat-materials.md](../interface/flat-materials.md)). The -Pixel Lab generation pipeline (tooling, skills, world-tile manifest) was -removed 2026-07-08; git history has it. Direction notes live under +([interface/flat-materials.md](../interface/flat-materials.md)). Iterate +new procedural meshes in the +[asset tester](asset-tester.md) (`misaligned-assets`) before wiring them +into Bevy. The Pixel Lab generation pipeline was removed 2026-07-08; git +history has it. Direction notes live under `assets/reference/art-direction/` (not runtime art). Placeholders, if any return, are tracked in this directory — not silently reused (constitution: "Justification and legibility"). diff --git a/wiki/art/asset-tester.md b/wiki/art/asset-tester.md new file mode 100644 index 0000000..05085a5 --- /dev/null +++ b/wiki/art/asset-tester.md @@ -0,0 +1,61 @@ +# Procedural asset tester + +``` +Type: knowledge +``` + +A Bevy-only orbit viewer for iterating **flat-material procedural meshes** +without booting the full game. World art stays solid palette color under +light ([flat-materials.md](../interface/flat-materials.md)); this binary +is the sandbox where new meshes earn their look before they land in +`misaligned-bevy`. + +## Run + +```bash +cargo run --features bevy_ui --bin misaligned-assets +``` + +`./tools/check.sh` builds this binary alongside `misaligned-bevy`. + +## Controls + +| Input | Action | +|---|---| +| Left/right drag | Orbit | +| Scroll | Zoom | +| `1` / `2` / `3` | Rack state: dead / powered / core | +| `Esc` | Quit | + +## Layout + +``` +src/bin/assets/ + mod.rs # Bevy App, orbit camera, lights, HUD, state keys + palette.rs # clinical palette copy (do not invent hues here) + rack.rs # spawn_server_rack(state) — first procedural asset +``` + +Palette constants are duplicated from `src/bin/bevy.rs` on purpose so +asset work does not collide with the Bevy conflict hotspot. Share them +when a mesh graduates into the game. + +## Adding an asset + +1. Add a module under `src/bin/assets/` that spawns a hierarchy of + `Cuboid` / simple meshes with `StandardMaterial` from `palette.rs` + (emissive only for machine presence / power / signal). +2. Wire a key or HUD cycle in `mod.rs` to spawn/despawn it on the + pedestal (same pattern as `RackState` + `rebuild_rack_on_state`). +3. Document the controls here. +4. Do **not** wire into `misaligned-bevy` until the look is approved in + this tester. + +## First asset: server rack + +`rack.rs` builds a frame + bay cavity + side rails + drive trays + +per-bay LEDs + front status strip. States: + +- **dead** — dark LEDs, mostly empty bays +- **powered** — amber LEDs / strip, most bays filled +- **core** — full bays, bright amber LEDs, cold `SIGNAL` strip diff --git a/wiki/log/2026-07-08-asset-tester.md b/wiki/log/2026-07-08-asset-tester.md new file mode 100644 index 0000000..9f6282a --- /dev/null +++ b/wiki/log/2026-07-08-asset-tester.md @@ -0,0 +1,35 @@ +# 2026-07-08 — Procedural asset tester binary + +``` +Type: log +``` + +## Intent + +Cameron asked for a dedicated binary to iterate procedural flat-material +assets (server rack first) without booting the full game. + +## Changed + +- New binary `misaligned-assets` (`src/bin/assets/`, feature `bevy_ui`): + orbit camera, clinical palette pedestal, procedural server rack with + dead / powered / core states (`1`/`2`/`3`). +- `Cargo.toml` `[[bin]]` entry; `tools/check.sh` builds it with Bevy. +- Wiki: [art/asset-tester.md](../art/asset-tester.md), SUMMARY, art + README, overview, workflows, ROADMAP #31. + +Not wired into `misaligned-bevy` yet — approve look in the tester first. + +## Observed + +`cargo run --features bevy_ui --bin misaligned-assets` — Metal adapter +up, window opens, no startup errors (brief smoke kill after ~4s). + +## Checks + +`./tools/check.sh` + +## Defense + +Presentation tooling only: flat-materials world look needs a place to +compose procedural meshes before they touch the game binary; no sim/save. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 0ac8904..0666577 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -5,6 +5,15 @@ Type: log ``` Reverse chronological implementation notes. Keep this factual: what changed, why, checks, and spec impact. +## 2026-07-08 - Procedural asset tester binary + +- Intent: iterate flat-material procedural meshes without booting the + full game; first asset a server rack (dead/powered/core). +- Changed: `misaligned-assets` bin (`src/bin/assets/`); check.sh builds + it; wiki/art/asset-tester.md + ROADMAP #31. Not wired into Bevy yet. +- Checks: `./tools/check.sh`; observed brief Metal launch. +- Log: wiki/log/2026-07-08-asset-tester.md. + ## 2026-07-08 - Continuous witness axioms adopted - Intent: Cameron affirmed the narration axioms (UI / "what the hell is diff --git a/wiki/overview.md b/wiki/overview.md index 2eb3358..081a8b7 100644 --- a/wiki/overview.md +++ b/wiki/overview.md @@ -33,7 +33,7 @@ is subordinate to it, never the reverse. | [gameplay/](gameplay/README.md) | How it plays: the big loop, the overt-phase hinge, whole-spectrum strategy | | [world/](world/README.md) | The fiction: [characters](world/characters/README.md) (cast + chargen), [places](world/places/README.md) (the basement, z-planes), [story](world/story/README.md) | | [interface/](interface/README.md) | The frontends: terminal, Bevy, views (digital/physical render), agent-play | -| [art/](art/README.md) | Visual identity (flat materials; Pixel Lab pipeline removed) | +| [art/](art/README.md) | Visual identity (flat materials; procedural asset tester) | | [engineering/](engineering/README.md) | Architecture, determinism, save format, the flow substrate | | [process/](process/README.md) | The spec system, ticks, design sessions, development style, workflows, the dispatch board | | [log/](log/README.md) | Devlogs and the DEVLOG ledger | diff --git a/wiki/process/ROADMAP.md b/wiki/process/ROADMAP.md index 36e7aae..0096c5f 100644 --- a/wiki/process/ROADMAP.md +++ b/wiki/process/ROADMAP.md @@ -459,6 +459,15 @@ regeneration is retired — flat materials, Pixel Lab scrubbed.) before/after screenshots in the session log. Run ./tools/check.sh, land on main, set the spec Status." +### 31. Procedural asset tester 🟩 isolated (`src/bin/assets/`, docs) — LANDED 2026-07-08 +- **Knowledge:** [art/asset-tester.md](../art/asset-tester.md) +- **Why:** iterate flat-material procedural meshes (server rack first) + without booting the full game; approve look in an orbit viewer before + wiring into `misaligned-bevy`. +- **Size:** S. No sim/save. Binary `misaligned-assets` (feature `bevy_ui`); + `check.sh` builds it with the Bevy gate. +- **Done:** orbit camera, dead/powered/core rack states, wiki + workflows. + --- ## Suggested first wave (no mutual collision) diff --git a/wiki/process/workflows.md b/wiki/process/workflows.md index a511074..c62aa77 100644 --- a/wiki/process/workflows.md +++ b/wiki/process/workflows.md @@ -19,6 +19,7 @@ cargo test # all in the lib (sim-heavy) cargo clippy --all-targets # must stay at zero warnings cargo fmt # rustfmt is enforced-by-convention since 05048e4 cargo build --features bevy_ui --bin misaligned-bevy # keep the Bevy build green +cargo build --features bevy_ui --bin misaligned-assets # procedural asset tester cargo clippy --features bevy_ui --all-targets # ...and lint-clean ``` @@ -44,12 +45,16 @@ installed locally (`.tangled/workflows/check.yml`). cargo run --release # terminal frontend cargo run --release -- --agent --seed 1 # agent line protocol cargo run --release --features bevy_ui --bin misaligned-bevy # Bevy frontend +cargo run --features bevy_ui --bin misaligned-assets # procedural asset tester ``` - The Bevy frontend must be run via `cargo run` (or with `BEVY_ASSET_ROOT` pointing at the repo root): Bevy resolves `assets/` from `BEVY_ASSET_ROOT`, then `CARGO_MANIFEST_DIR`, then the executable's directory. Running the binary directly from `target/` silently loads zero assets. +- The asset tester (`misaligned-assets`) is a Bevy-only orbit viewer for + flat-material procedural meshes — see + [art/asset-tester.md](../art/asset-tester.md). It does not load the sim. ## Headless smoke tests (they catch real bugs)