diff --git a/src/bin/bevy.rs b/src/bin/bevy.rs index 37cc0d6e..946c9c7a 100644 --- a/src/bin/bevy.rs +++ b/src/bin/bevy.rs @@ -64,6 +64,17 @@ const DOOR_CUT_HEIGHT: f32 = 0.62; /// Camera pitch below the horizon for the material render (35-45 degrees is /// the Octopath band; 40 keeps tile footprints readable). const CAMERA_TILT_DEG: f32 = 40.0; +/// Default material-camera distance in world units (material-dark-frame.md: +/// the opening frame is the attention close-up — the rack dominates, the +/// rest is dark; fit-to-known-content is retired here). Tuned so the +/// default reads like a room security camera on the focused machine, not +/// a god-view of the whole basement. [TUNE]. +const CLOSE_DIST: f32 = 2.6; +/// Zoom multipliers around the close default (material-dark-frame.md +/// criterion 5): min stays a tight rack close-up, max can still survey +/// lit pools without the plan driving the frame. [TUNE]. +const MIN_ZOOM: f32 = 0.5; +const MAX_ZOOM: f32 = 6.0; // ─── Palette ───────────────────────────────────────────────────────────────── @@ -1048,7 +1059,7 @@ fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { // unscouted foreign racks are absent. No scan, no splice. if kind == "intel" { mode.material = true; - mode.zoom = 0.45; + mode.zoom = 2.2; // Anchor on the known-unnetworked switch: the undetailed shell // is the tier the other kinds never show (they splice // everything). The telemetry core reads in the same frame's @@ -1091,7 +1102,7 @@ fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { } game.drain(); mode.material = true; - mode.zoom = 0.55; + mode.zoom = 1.8; if let Some(env) = game.sim.reach.device_named("environmental monitor") { game.set_cursor(env.x, env.y); } @@ -1125,11 +1136,13 @@ fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { match kind { "wide" => { mode.material = true; - mode.zoom = 1.0; + // Survey framing on the new close scale (still attention- + // anchored; the plan does not drive distance). + mode.zoom = 3.0; } "close" => { mode.material = true; - mode.zoom = 0.32; + mode.zoom = 1.0; if let Some((x, y)) = person { game.set_cursor(x, y); } @@ -1140,7 +1153,7 @@ fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { // states are read in the dark, not under the fluorescents. "dark" => { mode.material = true; - mode.zoom = 0.4; + mode.zoom = 1.6; let core = game.sim.core_position(); let far = game .sim @@ -1153,15 +1166,15 @@ fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { game.set_cursor(x, y); } } - // Framing-floor checks at the zoom bounds (material-render.md + // Framing-floor checks at the zoom bounds (material-dark-frame.md // criterion 5): min and max of update_camera_real's zoom clamp. "zoomin" => { mode.material = true; - mode.zoom = 0.25; + mode.zoom = MIN_ZOOM; } "zoomout" => { mode.material = true; - mode.zoom = 1.6; + mode.zoom = MAX_ZOOM; } // Wake choreography frames (opening.md beat 1): the sequence // frozen at a stutter flash, the column reveal, and the @@ -1185,7 +1198,7 @@ fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { ); game.drain(); mode.material = true; - mode.zoom = 0.3; + mode.zoom = 1.0; } _ => mode.material = false, } @@ -2228,9 +2241,10 @@ fn setup_3d( .id(); // The material camera: same anchor (the cursor tile) as the flat camera, - // pitched down into the room. Renders before the 2D camera, which stops + // pitched down into the room at the attention-close distance + // (material-dark-frame.md). Renders before the 2D camera, which stops // clearing in material mode so the UI composites on top. - let target = grid_to_world_3d(game.cursor_x, game.cursor_y, 0.0); + let target = grid_to_world_3d(game.cursor_x, game.cursor_y, 0.45); let tilt = CAMERA_TILT_DEG.to_radians(); commands.spawn(( Camera3d::default(), @@ -2251,7 +2265,7 @@ fn setup_3d( fov: 35.0_f32.to_radians(), ..default() }), - Transform::from_translation(target + Vec3::new(0.0, tilt.sin(), tilt.cos()) * 24.0) + Transform::from_translation(target + Vec3::new(0.0, tilt.sin(), tilt.cos()) * CLOSE_DIST) .looking_at(target, Vec3::Y), layer.clone(), RealCamera, @@ -2718,8 +2732,9 @@ fn wake_sequence( if done { wake.active = false; - // Hand the camera off near where the wake left it. - mode.zoom = (dist / 24.0).clamp(0.25, 1.6); + // Hand the camera off near where the wake left it, on the new + // attention-close zoom scale (material-dark-frame.md). + mode.zoom = (dist / CLOSE_DIST).clamp(MIN_ZOOM, MAX_ZOOM); mode.dirty = true; } } @@ -2727,9 +2742,13 @@ fn wake_sequence( /// Total wake length in seconds ([TUNE]). const WAKE_LEN: f32 = 6.2; -/// The material camera: anchored to the cursor tile like the flat camera, -/// framed to the known-content extent (bevy-visual-floor.md: no -/// postage-stamp map), zoomable with [ ]. +/// The material camera: anchored to attention (the cursor, which opens on +/// the core) at a close distance (material-dark-frame.md: the opening frame +/// is the attention close-up — the rack dominates, the rest is dark). +/// Fit-to-known-content is retired as this camera's framing driver: the +/// known extent is a plan fact, and the plan no longer drives the camera +/// (that fit-to-content default now governs the flat sensorium only). +/// Zoomable with [ ]. fn update_camera_real( time: Res