diff --git a/src/bin/assets/mod.rs b/src/bin/assets/mod.rs index 90870bc..9a2e8eb 100644 --- a/src/bin/assets/mod.rs +++ b/src/bin/assets/mod.rs @@ -12,9 +12,7 @@ mod rack; use bevy::core_pipeline::tonemapping::Tonemapping; use bevy::input::mouse::{AccumulatedMouseMotion, AccumulatedMouseScroll}; -use bevy::post_process::bloom::Bloom; use bevy::prelude::*; -use bevy::render::view::Hdr; use bevy::render::view::screenshot::{Screenshot, save_to_disk}; use bevy::window::{CursorGrabMode, CursorOptions, WindowResolution}; @@ -312,14 +310,13 @@ fn setup(mut commands: Commands, orbit: Res, harness: Option f32 { + if t < 0.08 { + return 0.0; + } + let f = (t * 61.7).sin() * (t * 23.3).sin() + (t * 7.9).sin(); + let hold = (t / 1.9).clamp(0.0, 1.0); + let threshold = 1.4 - 2.6 * hold; + if f > threshold { + 1.0 + } else { + 0.05 + 0.1 * hold + } +} + /// Which piece of a tile's 3D representation an entity is. #[derive(Clone, Copy, PartialEq, Debug)] enum TilePart { @@ -795,6 +840,36 @@ fn main() { } }); + // The wake plays on a normal launch; harness kinds freeze it at a + // choreography frame or disable it for every other shot. + let wake = match std::env::var("MISALIGNED_SHOT").ok().as_deref() { + None => WakeState { + t: 0.0, + active: true, + frozen: false, + }, + Some("wake1") => WakeState { + t: 0.98, + active: true, + frozen: true, + }, + Some("wake2") => WakeState { + t: 2.3, + active: true, + frozen: true, + }, + Some("wake3") => WakeState { + t: 4.3, + active: true, + frozen: true, + }, + Some(_) => WakeState { + t: WAKE_LEN, + active: false, + frozen: false, + }, + }; + let mut app = App::new(); app.add_plugins( DefaultPlugins @@ -820,6 +895,7 @@ fn main() { .insert_resource(mode) .insert_resource(Materials3d::default()) .insert_resource(Machines3d::default()) + .insert_resource(wake) .init_resource::() .init_gizmo_group::() .add_systems(Startup, (setup, setup_ui, setup_3d).chain()) @@ -831,6 +907,7 @@ fn main() { advance_sim, apply_render_mode, update_camera, + wake_sequence, update_camera_real, render_map, render_sensor_overlays, @@ -996,6 +1073,12 @@ fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { mode.material = true; mode.zoom = 1.6; } + // Wake choreography frames (opening.md beat 1): the sequence + // frozen at a stutter flash, the column reveal, and the + // pull-back. WakeState is staged in main from the kind. + "wake1" | "wake2" | "wake3" => { + mode.material = true; + } // Work-token anchor evidence: stage demand cubes and knowledge // mercury on the host through the sim's own queue API, then // zoom close (machine-work.md anchors, in-game). @@ -2060,15 +2143,14 @@ fn setup_3d( order: -1, ..default() }, - // HDR + LUT tonemapping + LED-scale bloom (graphics pass - // 2026-07-09, tuned in the asset tester): emissives read as - // light sources; hotter bloom hazes the lit floor. - Hdr, - Tonemapping::TonyMcMapface, - Bloom { - intensity: 0.07, - ..Bloom::NATURAL - }, + // Filmic tonemapping, deliberately NO bloom and NO HDR view: + // Bloom requires an HDR target, and on macOS the EDR surface + // can vanish when the display idles, locks, or the window is + // unfocused — an HDR camera then renders solid black (observed + // 2026-07-09 as black harness captures). Glow is carried by + // emissives and the core's linear light rig instead; the + // capture pipeline must never depend on display EDR state. + Tonemapping::AcesFitted, Projection::from(PerspectiveProjection { fov: 35.0_f32.to_radians(), ..default() @@ -2249,11 +2331,37 @@ fn setup_3d( ..default() }, Transform::from_translation(core_base + Vec3::new(0.0, y, dz)), + CoreRung { base: intensity }, + layer.clone(), + ChildOf(root), + )); + } + } + // The wake's column of light: crossed opaque emissive slabs over + // the core seam — bloom supplies the glow (an additive-transparent + // shaft broke the HDR screenshot pipeline outright, so the column + // is solid light: flat color, no texture, no alpha). Hidden until + // the wake drives it; it dissolves by scale, not transparency. + for (w, base) in [(0.085, 5.5_f32), (0.22, 2.2)] { + for rot in [0.0_f32, std::f32::consts::FRAC_PI_2] { + commands.spawn(( + Mesh3d(meshes.add(Cuboid::new(w, 1.78, 0.02))), + MeshMaterial3d(materials.add(StandardMaterial { + base_color: AMBER, + emissive: AMBER.to_linear() * base, + unlit: true, + ..default() + })), + Transform::from_translation(core_base + Vec3::new(0.0, 0.86, 0.0)) + .with_rotation(Quat::from_rotation_y(rot)), + Visibility::Hidden, + WakeColumn { base }, layer.clone(), ChildOf(root), )); } } + // No second amber lamp on foreign/known-subnet devices: blueprint is // topology, not a glowing rack farm (machine-work.md: no intel, no render). @@ -2427,6 +2535,102 @@ fn flicker_feeds( } } +/// Drive the wake: flicker envelope onto the column quads and the +/// strip's rungs, a bloom surge while the color fills the frame, and +/// the pull-back camera path from inside the light to the game tilt. +/// Any key skips. Runs only in the material render. +#[allow(clippy::too_many_arguments)] +fn wake_sequence( + time: Res