diff --git a/crates/misaligned-assets/src/rack.rs b/crates/misaligned-assets/src/rack.rs index d0b3ce9a..3d81e9a7 100644 --- a/crates/misaligned-assets/src/rack.rs +++ b/crates/misaligned-assets/src/rack.rs @@ -162,22 +162,44 @@ pub struct RackRoot; const RACK_W: f32 = 0.68; const RACK_D: f32 = 0.44; const RACK_H: f32 = 2.45; +/// Dark recessed base the shell overhangs — a deployed appliance stands on +/// its own structure, it does not meet the floor flush. +const PLINTH_H: f32 = 0.072; +/// Capping crown and the dark line that separates it from the body. +const CROWN_H: f32 = 0.118; +const CROWN_GAP: f32 = 0.022; +/// How far the one front plate stands proud of the shell's own front face. +/// The aperture is a hole bored through this thickness, so the relief is also +/// the depth of the well the instrument sits in. +const PLATE_RELIEF: f32 = 0.078; +/// Front-face chamfers. Kept modest so the plate that carries the aperture has +/// a broad uninterrupted plane to be smooth across. +const FRONT_CHAMFER_L: f32 = 0.022; +const FRONT_CHAMFER_R: f32 = 0.058; +/// Bone between the aperture's outer edge and the plate's own edge. The shell +/// must have room to close smoothly around the opening; the 2026-08-02 +/// jagged-shoulder failure was this margin going to nothing. +const APERTURE_MARGIN: f32 = 0.060; /// Every Foundation machine carries one large, body-integrated circular -/// instrument. It is deliberately too large and too deep to read as a badge. -pub const MACHINE_INSTRUMENT_RADIUS: f32 = 0.292; -/// Slightly off-axis so the folded chassis remains clinical, not heraldic. -pub const MACHINE_INSTRUMENT_X: f32 = -0.035; +/// instrument: deep enough and wide enough that it can only be the machine's +/// heart, but not so wide that the shell around it degenerates into slivers. +pub const MACHINE_INSTRUMENT_RADIUS: f32 = 0.215; +/// Slightly off-axis so the bone margin around the opening is unequal — that +/// asymmetry is the shell's, not a decoration applied to it. +pub const MACHINE_INSTRUMENT_X: f32 = -0.030; pub const MACHINE_INSTRUMENT_Y: f32 = 1.39; -const MACHINE_INSTRUMENT_APERTURE_RADIUS: f32 = 0.214; +/// The instrument's internal proportions are fractions of its radius, so the +/// concentric assembly stays correct under any retune of the opening's size. +const MACHINE_INSTRUMENT_APERTURE_RADIUS: f32 = MACHINE_INSTRUMENT_RADIUS * 0.733; /// The black drum projects through this inner edge of the bone enclosure. -const MACHINE_INSTRUMENT_ENCLOSURE_INNER: f32 = 0.236; -const MACHINE_INSTRUMENT_DRUM_OUTER: f32 = 0.247; -const MACHINE_INSTRUMENT_RING_OUTER: f32 = 0.205; -const MACHINE_INSTRUMENT_RING_INNER: f32 = 0.169; -const MACHINE_INSTRUMENT_PIE_RADIUS: f32 = 0.147; -const MACHINE_INSTRUMENT_LIP_DEPTH: f32 = 0.104; -const MACHINE_INSTRUMENT_ENCLOSURE_DEPTH: f32 = 0.060; -const MACHINE_INSTRUMENT_FACE_RECESS: f32 = 0.044; +const MACHINE_INSTRUMENT_ENCLOSURE_INNER: f32 = MACHINE_INSTRUMENT_RADIUS * 0.808; +const MACHINE_INSTRUMENT_DRUM_OUTER: f32 = MACHINE_INSTRUMENT_RADIUS * 0.846; +const MACHINE_INSTRUMENT_RING_OUTER: f32 = MACHINE_INSTRUMENT_RADIUS * 0.702; +const MACHINE_INSTRUMENT_RING_INNER: f32 = MACHINE_INSTRUMENT_RADIUS * 0.579; +const MACHINE_INSTRUMENT_PIE_RADIUS: f32 = MACHINE_INSTRUMENT_RADIUS * 0.503; +const MACHINE_INSTRUMENT_LIP_DEPTH: f32 = MACHINE_INSTRUMENT_RADIUS * 0.356; +const MACHINE_INSTRUMENT_ENCLOSURE_DEPTH: f32 = MACHINE_INSTRUMENT_RADIUS * 0.205; +const MACHINE_INSTRUMENT_FACE_RECESS: f32 = MACHINE_INSTRUMENT_RADIUS * 0.151; /// Quantization used only at the render boundary. The source remains an exact /// sim throughput; twenty slices keep hierarchy churn bounded while reading /// as a continuous pie at the shipped camera distance. @@ -260,124 +282,167 @@ pub fn spawn_server_rack( }; let front_z = RACK_D * 0.5; - let mid_y = RACK_H * 0.5; - - // --- Foundation body: a deep structural shell carrying broad front - // plates that stop at the instrument envelope. The collar is not a badge - // on a finished box: upper, lower, and unequal shoulder plates visibly - // resolve around its aperture. - let facet_back = front_z - 0.110; - let body_d = facet_back + front_z; - spawn_box( + + // --- Foundation body: a folded shell standing clear of a dark plinth and + // capped by a crown, fronted by ONE uninterrupted plate with the aperture + // bored straight through it. The shell closes around the opening as a + // continuous curve; nothing steps, tabs, or notches into it. The bored + // thickness is the well, and the well is what stops the instrument reading + // as a badge — not a scatter of plates arranged near it. + let body_y0 = PLINTH_H; + let body_y1 = RACK_H - CROWN_H - CROWN_GAP; + // The shell's own mass stops short of the front face; the front plate + // occupies the remaining depth. + let body_d = RACK_D - PLATE_RELIEF; + let body_z = -PLATE_RELIEF * 0.5; + let body_profile = chamfered_footprint( + RACK_W, + body_d, + [FRONT_CHAMFER_L, FRONT_CHAMFER_R, 0.074, 0.046], + ); + spawn_shell( commands, meshes, tower.clone(), root, - Vec3::new(RACK_W - 0.045, RACK_H, body_d), - Vec3::new(0.010, mid_y, facet_back - body_d * 0.5), + &body_profile, + &body_profile, + body_y0, + body_y1, + Vec3::new(0.0, 0.0, body_z), ); - // One darker folded cheek keeps the appliance asymmetric and gives the - // plate stack a serviceable edge without resurrecting the old light beam. - spawn_box( + + // The tower stands on a recessed dark base rather than meeting the floor + // flush: the undercut shadow is what gives it deployed weight. + let plinth_profile = + chamfered_footprint(RACK_W - 0.14, body_d - 0.12, [0.03, 0.06, 0.04, 0.03]); + spawn_shell( commands, meshes, - fold.clone(), + channel.clone(), root, - Vec3::new(0.075, RACK_H - 0.035, RACK_D * 0.79), - Vec3::new(-RACK_W * 0.5 + 0.038, mid_y - 0.004, -0.026), + &plinth_profile, + &plinth_profile, + 0.0, + PLINTH_H + 0.006, + Vec3::new(0.0, 0.0, body_z), ); - // Upper and lower plates use different depths and horizontal offsets. - // Their boundaries pass over unequal parts of the outer annulus rather - // than framing a complete badge on a generic front panel. - let well_top = MACHINE_INSTRUMENT_Y + MACHINE_INSTRUMENT_RADIUS; - let well_bottom = MACHINE_INSTRUMENT_Y - MACHINE_INSTRUMENT_RADIUS; - let lower_h = well_bottom + MACHINE_INSTRUMENT_RADIUS * 0.10; - spawn_box( + // One dark separation line, then the crown: a slightly proud cap that + // bevels in at the top so the silhouette closes instead of stopping. + let gap_profile = chamfered_footprint(RACK_W - 0.036, body_d - 0.034, [0.04, 0.09, 0.06, 0.04]); + spawn_shell( commands, meshes, - tower.clone(), + channel.clone(), root, - Vec3::new(RACK_W - 0.090, lower_h, 0.030), - Vec3::new(0.012, lower_h * 0.5, front_z - 0.018), + &gap_profile, + &gap_profile, + body_y1, + body_y1 + CROWN_GAP + 0.004, + Vec3::new(0.0, 0.0, body_z), ); - let upper_y0 = well_top - MACHINE_INSTRUMENT_RADIUS * 0.16; - let upper_h = RACK_H - upper_y0 - 0.018; - spawn_box( + let crown_d = RACK_D - 0.044; + let crown_z = front_z - crown_d * 0.5 - 0.018; + let crown_low = chamfered_footprint(RACK_W - 0.030, crown_d, [0.058, 0.112, 0.082, 0.052]); + let crown_high = chamfered_footprint(RACK_W - 0.104, crown_d - 0.060, [0.09, 0.15, 0.11, 0.08]); + // The crown takes the fold value — one shade down. It is the only value + // break on the shell, and it reads as a capping component rather than as a + // seam drawn on a continuous surface. + spawn_shell( commands, meshes, fold.clone(), root, - Vec3::new(RACK_W - 0.115, upper_h, 0.026), - Vec3::new(0.024, upper_y0 + upper_h * 0.5, front_z - 0.010), + &crown_low, + &crown_high, + body_y1 + CROWN_GAP, + RACK_H, + Vec3::new(0.0, 0.0, crown_z), ); - let shoulder_h = MACHINE_INSTRUMENT_RADIUS * 2.0 + 0.036; - let left_edge = -RACK_W * 0.5 + 0.046; - let left_stop = MACHINE_INSTRUMENT_X - MACHINE_INSTRUMENT_RADIUS * 0.82; - let left_w = (left_stop - left_edge).max(0.025); - spawn_box( - commands, - meshes, - fold.clone(), - root, - Vec3::new(left_w, shoulder_h, 0.034), - Vec3::new( - left_edge + left_w * 0.5, - MACHINE_INSTRUMENT_Y, - front_z - 0.021, - ), + + // The one front plate. Its outline sits on the shell's own front face, so + // the chamfers behind it stay unbroken, and the aperture is bored clean + // through: a continuous circular wall, PLATE_RELIEF deep, with bone closing + // smoothly around all of it. Because the opening is off-axis, the margin it + // leaves is unequal left to right — that is the asymmetry, and it costs the + // surface nothing. + let face_left = -RACK_W * 0.5 + FRONT_CHAMFER_L; + let face_right = RACK_W * 0.5 - FRONT_CHAMFER_R; + let aperture = Vec2::new(MACHINE_INSTRUMENT_X, MACHINE_INSTRUMENT_Y); + let bore = MACHINE_INSTRUMENT_RADIUS + APERTURE_MARGIN * 0.20; + let plate_outline = chamfered_rect_xy( + face_left, + face_right, + PLINTH_H, + body_y1, + [0.026, 0.026, 0.052, 0.052], ); - let right_start = MACHINE_INSTRUMENT_X + MACHINE_INSTRUMENT_RADIUS * 0.90; - let right_edge = RACK_W * 0.5 - 0.032; - let right_w = (right_edge - right_start).max(0.025); - spawn_box( + spawn_bored_plate( commands, meshes, tower.clone(), root, - Vec3::new(right_w, shoulder_h, 0.030), - Vec3::new( - right_start + right_w * 0.5, - MACHINE_INSTRUMENT_Y, - front_z - 0.014, - ), + &plate_outline, + aperture, + bore, + front_z - PLATE_RELIEF, + front_z, ); + // The well floor: darkness behind the opening, so the bore reads as depth + // into the machine rather than a ring drawn on bone. + commands.spawn(( + Mesh3d(meshes.add(Circle::new(bore))), + MeshMaterial3d(channel.clone()), + Transform::from_translation(Vec3::new( + aperture.x, + aperture.y, + front_z - PLATE_RELIEF + 0.002, + )), + ChildOf(root), + Name::new("machine_instrument_well_floor"), + )); - // Crown, rear shoulder, and one disciplined floor trunk keep the smooth - // shell physically maintained without rack-unit clutter. + // Service structure: one quiet conduit down each flank at unequal length + // and depth, plus the disciplined rear/base trunk. Straight, dark, parallel + // to the shell, and kept to the rear half of the flanks — canted, stepped, + // or forward service structure re-breaks the surface this plate exists to + // keep smooth, and on a bone chassis a dark line near the front edge reads + // as damage rather than as structure. + let flank_x = RACK_W * 0.5 - 0.012; spawn_box( commands, meshes, - tower.clone(), + channel.clone(), root, - Vec3::new(RACK_W * 0.86, 0.030, RACK_D), - Vec3::new(0.006, RACK_H - 0.015, 0.0), + Vec3::new(0.030, RACK_H - 0.86, 0.100), + Vec3::new(-flank_x, 0.48 + (RACK_H - 0.86) * 0.5, body_z - 0.048), ); spawn_box( commands, meshes, - tower.clone(), + channel.clone(), root, - Vec3::new(RACK_W * 0.58, 0.085, 0.070), - Vec3::new(-0.045, RACK_H - 0.045, -RACK_D * 0.40), + Vec3::new(0.026, RACK_H - 1.42, 0.078), + Vec3::new(flank_x, 0.34 + (RACK_H - 1.42) * 0.5, body_z - 0.094), ); + // The heavy cooling/power connection at the base — infrastructural weight, + // disciplined into one trunk instead of loose floor cabling. spawn_box( commands, meshes, channel.clone(), root, - Vec3::new(0.20, 0.105, 0.30), - Vec3::new(0.17, 0.0525, -RACK_D * 0.78), + Vec3::new(0.21, 0.125, 0.32), + Vec3::new(0.16, 0.062, -RACK_D * 0.80), ); - // One lower service aperture is enough. It is structure, never another - // screen or lamp. spawn_box( commands, meshes, channel.clone(), root, - Vec3::new(0.13, 0.34, 0.024), - Vec3::new(-0.185, 0.31, front_z + 0.001), + Vec3::new(0.13, 0.44, 0.13), + Vec3::new(0.16, 0.24, -RACK_D * 0.62), ); let step = if state.owned() { @@ -392,7 +457,13 @@ pub fn spawn_server_rack( meshes, materials, root, - Vec3::new(MACHINE_INSTRUMENT_X, MACHINE_INSTRUMENT_Y, front_z - 0.050), + // Seated inside the bore, its drum lip stopping just short of the + // plate face: proud enough to be physical, never breaking the surface. + Vec3::new( + MACHINE_INSTRUMENT_X, + MACHINE_INSTRUMENT_Y, + front_z - PLATE_RELIEF * 0.5 - MACHINE_INSTRUMENT_LIP_DEPTH * 0.12, + ), MACHINE_INSTRUMENT_RADIUS, state, Some(mode), @@ -826,12 +897,310 @@ fn spawn_box( parent: Entity, size: Vec3, translation: Vec3, +) -> Entity { + spawn_box_rot( + commands, + meshes, + material, + parent, + size, + translation, + Quat::IDENTITY, + ) +} + +/// A box the shell may cant off-axis. Sloped service structure is what stops +/// the appliance reading as a stack of axis-aligned slabs; it carries no +/// signal of its own. +#[allow(clippy::too_many_arguments)] +fn spawn_box_rot( + commands: &mut Commands, + meshes: &mut Assets, + material: Handle, + parent: Entity, + size: Vec3, + translation: Vec3, + rotation: Quat, ) -> Entity { commands .spawn(( Mesh3d(meshes.add(Cuboid::from_size(size))), MeshMaterial3d(material), - Transform::from_translation(translation), + Transform::from_translation(translation).with_rotation(rotation), + ChildOf(parent), + )) + .id() +} + +/// A convex chassis footprint in XZ: a rectangle whose four vertical corners +/// are cut back by their own amount. Unequal chamfers are what give the shell +/// direction — a plain cuboid reads as a slab from every angle because every +/// front-facing surface receives identical light. +/// +/// Corner order is front-left, front-right, back-right, back-left; +Z is the +/// instrument face. +fn chamfered_footprint(w: f32, d: f32, chamfer: [f32; 4]) -> Vec { + let hx = w * 0.5; + let hz = d * 0.5; + let limit = hx.min(hz) * 0.98; + let [fl, fr, br, bl] = chamfer.map(|c| c.clamp(0.0, limit)); + vec![ + Vec2::new(-hx + fl, hz), + Vec2::new(hx - fr, hz), + Vec2::new(hx, hz - fr), + Vec2::new(hx, -hz + br), + Vec2::new(hx - br, -hz), + Vec2::new(-hx + bl, -hz), + Vec2::new(-hx, -hz + bl), + Vec2::new(-hx, hz - fl), + ] +} + +/// Sweep a convex footprint between two heights, with an independent top +/// footprint so a crown can taper. Faces carry flat geometric normals: the +/// chamfers must read as deliberate facets, never as a smoothed tube. +fn extruded_shell_mesh(bottom: &[Vec2], top: &[Vec2], y0: f32, y1: f32) -> Mesh { + assert_eq!( + bottom.len(), + top.len(), + "shell profiles must share a corner count" + ); + let count = bottom.len(); + let inv = 1.0 / count as f32; + let centroid_b = bottom.iter().fold(Vec2::ZERO, |a, p| a + *p) * inv; + let centroid_t = top.iter().fold(Vec2::ZERO, |a, p| a + *p) * inv; + + let mut positions: Vec<[f32; 3]> = Vec::with_capacity(count * 12); + let mut normals: Vec<[f32; 3]> = Vec::with_capacity(count * 12); + let mut uvs: Vec<[f32; 2]> = Vec::with_capacity(count * 12); + + let mut tri = |a: Vec3, b: Vec3, c: Vec3, outward: Vec3| { + let (b, c) = if (b - a).cross(c - a).dot(outward) >= 0.0 { + (b, c) + } else { + (c, b) + }; + for p in [a, b, c] { + positions.push(p.to_array()); + normals.push(outward.to_array()); + uvs.push([0.0, 0.0]); + } + }; + + let lift = |p: Vec2, y: f32| Vec3::new(p.x, y, p.y); + for i in 0..count { + let j = (i + 1) % count; + let (b0, b1) = (lift(bottom[i], y0), lift(bottom[j], y0)); + let (t0, t1) = (lift(top[i], y1), lift(top[j], y1)); + let mut outward = (b1 - b0).cross(t0 - b0).normalize_or_zero(); + if outward == Vec3::ZERO { + continue; + } + let mid = (bottom[i] + bottom[j]) * 0.5 - centroid_b; + if outward.dot(Vec3::new(mid.x, 0.0, mid.y)) < 0.0 { + outward = -outward; + } + tri(b0, b1, t1, outward); + tri(b0, t1, t0, outward); + } + for i in 0..count { + let j = (i + 1) % count; + tri( + lift(centroid_b, y0), + lift(bottom[i], y0), + lift(bottom[j], y0), + Vec3::NEG_Y, + ); + tri( + lift(centroid_t, y1), + lift(top[i], y1), + lift(top[j], y1), + Vec3::Y, + ); + } + + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetUsages::default(), + ) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) +} + +/// The front plate's outline in XY: a rectangle given by its own edges, with +/// per-corner chamfers so the plate's silhouette agrees with the folded shell +/// behind it. Corner order is bottom-left, bottom-right, top-right, top-left. +fn chamfered_rect_xy(x0: f32, x1: f32, y0: f32, y1: f32, chamfer: [f32; 4]) -> Vec { + let limit = ((x1 - x0).min(y1 - y0) * 0.49).max(0.0); + let [bl, br, tr, tl] = chamfer.map(|c| c.clamp(0.0, limit)); + vec![ + Vec2::new(x0 + bl, y0), + Vec2::new(x1 - br, y0), + Vec2::new(x1, y0 + br), + Vec2::new(x1, y1 - tr), + Vec2::new(x1 - tr, y1), + Vec2::new(x0 + tl, y1), + Vec2::new(x0, y1 - tl), + Vec2::new(x0, y0 + bl), + ] +} + +/// Where the ray from `center` at `angle` leaves a convex outline. Used to +/// resample the plate's boundary against the bore so the surface between them +/// triangulates without a seam. +fn outline_radius(outline: &[Vec2], center: Vec2, angle: f32) -> Vec2 { + let dir = Vec2::new(angle.cos(), angle.sin()); + let mut best = f32::MAX; + for i in 0..outline.len() { + let a = outline[i]; + let b = outline[(i + 1) % outline.len()]; + let edge = b - a; + let denom = dir.x * edge.y - dir.y * edge.x; + if denom.abs() < 1.0e-6 { + continue; + } + let rel = a - center; + let t = (rel.x * edge.y - rel.y * edge.x) / denom; + let s = (rel.x * dir.y - rel.y * dir.x) / denom; + if t > 0.0 && (-1.0e-4..=1.0 + 1.0e-4).contains(&s) && t < best { + best = t; + } + } + center + dir * if best == f32::MAX { 0.0 } else { best } +} + +/// A single plate with a circular bore straight through it: front and back +/// faces, the bore's own cylindrical wall, and flat outer edges taken from the +/// exact outline. This is how the shell closes around the instrument — one +/// continuous surface with an opening in it, never a set of plates arranged +/// around a gap. +fn bored_plate_mesh( + outline: &[Vec2], + center: Vec2, + bore: f32, + z0: f32, + z1: f32, + segments: usize, +) -> Mesh { + let mut positions: Vec<[f32; 3]> = Vec::new(); + let mut normals: Vec<[f32; 3]> = Vec::new(); + let mut uvs: Vec<[f32; 2]> = Vec::new(); + let mut tri = |a: Vec3, b: Vec3, c: Vec3, outward: Vec3| { + let (b, c) = if (b - a).cross(c - a).dot(outward) >= 0.0 { + (b, c) + } else { + (c, b) + }; + for p in [a, b, c] { + positions.push(p.to_array()); + normals.push(outward.to_array()); + uvs.push([0.0, 0.0]); + } + }; + + let ring: Vec<(Vec2, Vec2)> = (0..segments) + .map(|i| { + let angle = std::f32::consts::TAU * i as f32 / segments as f32; + let inner = center + Vec2::new(angle.cos(), angle.sin()) * bore; + (inner, outline_radius(outline, center, angle)) + }) + .collect(); + + for i in 0..segments { + let (i0, o0) = ring[i]; + let (i1, o1) = ring[(i + 1) % segments]; + for (z, face) in [(z1, Vec3::Z), (z0, Vec3::NEG_Z)] { + let p = |v: Vec2| Vec3::new(v.x, v.y, z); + tri(p(i0), p(o0), p(o1), face); + tri(p(i0), p(o1), p(i1), face); + } + // Bore wall — normals point back at the axis, so the opening lights as + // a real cylindrical cut rather than a flat ring. + let mid = ((i0 + i1) * 0.5 - center).normalize_or_zero(); + let inward = Vec3::new(-mid.x, -mid.y, 0.0); + let (a, b) = (Vec3::new(i0.x, i0.y, z0), Vec3::new(i1.x, i1.y, z0)); + let (c, d) = (Vec3::new(i1.x, i1.y, z1), Vec3::new(i0.x, i0.y, z1)); + tri(a, b, c, inward); + tri(a, c, d, inward); + } + + // Outer edges come from the exact outline so the chamfers stay crisp. + let centroid = outline.iter().fold(Vec2::ZERO, |a, p| a + *p) / outline.len() as f32; + for i in 0..outline.len() { + let a = outline[i]; + let b = outline[(i + 1) % outline.len()]; + let edge = b - a; + let mut out = Vec2::new(edge.y, -edge.x).normalize_or_zero(); + if out.dot((a + b) * 0.5 - centroid) < 0.0 { + out = -out; + } + let outward = Vec3::new(out.x, out.y, 0.0); + tri( + Vec3::new(a.x, a.y, z0), + Vec3::new(b.x, b.y, z0), + Vec3::new(b.x, b.y, z1), + outward, + ); + tri( + Vec3::new(a.x, a.y, z0), + Vec3::new(b.x, b.y, z1), + Vec3::new(a.x, a.y, z1), + outward, + ); + } + + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetUsages::default(), + ) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) +} + +#[allow(clippy::too_many_arguments)] +fn spawn_bored_plate( + commands: &mut Commands, + meshes: &mut Assets, + material: Handle, + parent: Entity, + outline: &[Vec2], + center: Vec2, + bore: f32, + z0: f32, + z1: f32, +) -> Entity { + commands + .spawn(( + Mesh3d(meshes.add(bored_plate_mesh(outline, center, bore, z0, z1, 96))), + MeshMaterial3d(material), + Transform::IDENTITY, + ChildOf(parent), + Name::new("machine_front_plate"), + )) + .id() +} + +/// Spawn one folded shell mass. `offset` places the swept footprint in the +/// chassis frame (the profile itself is authored around its own centre). +#[allow(clippy::too_many_arguments)] +fn spawn_shell( + commands: &mut Commands, + meshes: &mut Assets, + material: Handle, + parent: Entity, + bottom: &[Vec2], + top: &[Vec2], + y0: f32, + y1: f32, + offset: Vec3, +) -> Entity { + commands + .spawn(( + Mesh3d(meshes.add(extruded_shell_mesh(bottom, top, y0, y1))), + MeshMaterial3d(material), + Transform::from_translation(offset), ChildOf(parent), )) .id() @@ -889,13 +1258,34 @@ pub fn spawn_switch_chassis( let front_z = SW_D * 0.5; let mid_y = SW_H * 0.5; - spawn_box( + // Same folded-shell grammar as Foundation compute at a low class's scale: + // chamfered mass standing clear of a dark plinth. The chassis differs, the + // language does not. + const SW_PLINTH_H: f32 = 0.030; + let sw_profile = chamfered_footprint(SW_W, SW_D, [0.034, 0.068, 0.048, 0.030]); + let sw_top = chamfered_footprint(SW_W - 0.030, SW_D - 0.024, [0.042, 0.078, 0.056, 0.036]); + spawn_shell( commands, meshes, slab, root, - Vec3::new(SW_W, SW_H, SW_D), - Vec3::new(0.0, mid_y, 0.0), + &sw_profile, + &sw_top, + SW_PLINTH_H, + SW_H, + Vec3::ZERO, + ); + let sw_plinth = chamfered_footprint(SW_W - 0.09, SW_D - 0.06, [0.02, 0.04, 0.03, 0.02]); + spawn_shell( + commands, + meshes, + channel.clone(), + root, + &sw_plinth, + &sw_plinth, + 0.0, + SW_PLINTH_H + 0.004, + Vec3::ZERO, ); // One asymmetric black service cut keeps the relay a distinct low class. spawn_box( @@ -903,7 +1293,7 @@ pub fn spawn_switch_chassis( meshes, channel, root, - Vec3::new(0.075, SW_H * 0.76, 0.026), + Vec3::new(0.075, SW_H * 0.66, 0.026), Vec3::new(-SW_W * 0.34, mid_y, front_z + 0.002), ); spawn_machine_instrument( @@ -1181,7 +1571,12 @@ mod tests { // The outer structural annulus dominates the narrow front, exposes a // real opening, and keeps the live ring/face nested behind a deep lip. // These are broad qualitative bounds; exact multipliers remain TUNE. - const _: () = assert!(MACHINE_INSTRUMENT_RADIUS > RACK_W * 0.40); + // The lower bound keeps the opening the machine's heart. The upper + // bound (the margin assertion below) keeps the shell able to close + // smoothly around it — an aperture that eats the front leaves only + // slivers to fold with, which is what produced the 2026-08-02 jagged + // shoulders. + const _: () = assert!(MACHINE_INSTRUMENT_RADIUS > RACK_W * 0.28); const _: () = assert!(MACHINE_INSTRUMENT_APERTURE_RADIUS < MACHINE_INSTRUMENT_RADIUS * 0.80); const _: () = @@ -1317,4 +1712,140 @@ mod tests { } } } + + /// Criterion 9 (computer-visual-language.md): the B1 shell is a folded + /// mass, not a cuboid. A rectangular prism gives every front-facing plate + /// the same normal, which is exactly why the old body read as a slab with + /// a badge on it. The footprint must therefore carry unequal chamfers, and + /// the swept mesh must expose those extra facets with outward normals. + #[test] + fn the_shell_is_a_folded_mass_with_unequal_facets() { + let profile = + chamfered_footprint(RACK_W, RACK_D - PLATE_RELIEF, [0.052, 0.108, 0.074, 0.046]); + assert_eq!(profile.len(), 8, "a chamfered footprint is an octagon"); + + // Unequal cuts: a symmetric shell would read heraldic, not clinical. + let front_left = profile[0].x + RACK_W * 0.5; + let front_right = RACK_W * 0.5 - profile[1].x; + assert!( + (front_left - front_right).abs() > 0.02, + "front chamfers must be visibly unequal" + ); + + let mesh = extruded_shell_mesh(&profile, &profile, 0.0, 1.0); + let Some(VertexAttributeValues::Float32x3(normals)) = + mesh.attribute(Mesh::ATTRIBUTE_NORMAL) + else { + panic!("shell must carry Float32x3 normals"); + }; + let mut horizontal: Vec<[f32; 3]> = Vec::new(); + for n in normals { + if n[1].abs() > 0.01 { + continue; + } + if !horizontal + .iter() + .any(|k| (k[0] - n[0]).abs() < 0.001 && (k[2] - n[2]).abs() < 0.001) + { + horizontal.push(*n); + } + } + assert_eq!( + horizontal.len(), + 8, + "each chamfer must light as its own facet, not fold into a box face" + ); + + // Every side normal points away from the axis: an inverted face would + // render the shell's interior and break the matte chalk read. + let Some(VertexAttributeValues::Float32x3(positions)) = + mesh.attribute(Mesh::ATTRIBUTE_POSITION) + else { + panic!("shell must carry Float32x3 positions"); + }; + for (p, n) in positions.iter().zip(normals.iter()) { + if n[1].abs() > 0.01 { + continue; + } + assert!( + p[0] * n[0] + p[2] * n[2] > 0.0, + "side facet at {p:?} faces inward" + ); + } + } + + /// The shell must be able to close smoothly around the opening. Both sides + /// of the front face keep real bone, and the thinner side is still wide + /// enough to read as surface rather than as a leftover sliver — the + /// condition that failed when the aperture was 86% of the chassis width and + /// the "shoulders" degenerated into protruding tabs. + #[test] + fn the_aperture_leaves_bone_on_every_side() { + let face_left = -RACK_W * 0.5 + FRONT_CHAMFER_L; + let face_right = RACK_W * 0.5 - FRONT_CHAMFER_R; + let bore = MACHINE_INSTRUMENT_RADIUS + APERTURE_MARGIN * 0.20; + let left = (MACHINE_INSTRUMENT_X - bore) - face_left; + let right = face_right - (MACHINE_INSTRUMENT_X + bore); + for (side, margin) in [("left", left), ("right", right)] { + assert!( + margin >= APERTURE_MARGIN, + "{side} margin {margin} leaves no shell to close with" + ); + } + assert!( + (left - right).abs() > 0.02, + "an off-axis opening must leave unequal margins" + ); + // Vertically the plate runs the whole body, so the only way the bore + // could break the surface is by reaching past the crown or the plinth. + let body_y1 = RACK_H - CROWN_H - CROWN_GAP; + assert!(MACHINE_INSTRUMENT_Y - bore > PLINTH_H + APERTURE_MARGIN); + assert!(MACHINE_INSTRUMENT_Y + bore < body_y1 - APERTURE_MARGIN); + } + + /// The opening is bored through one plate, not left between plates: the + /// mesh carries a continuous cylindrical wall whose normals face the axis, + /// and no vertex intrudes inside the bore. + #[test] + fn the_front_plate_is_bored_not_assembled() { + let outline = chamfered_rect_xy(-0.30, 0.28, 0.10, 2.20, [0.03, 0.03, 0.05, 0.05]); + let center = Vec2::new(MACHINE_INSTRUMENT_X, MACHINE_INSTRUMENT_Y); + let bore = 0.21; + let mesh = bored_plate_mesh(&outline, center, bore, 0.0, PLATE_RELIEF, 48); + let Some(VertexAttributeValues::Float32x3(positions)) = + mesh.attribute(Mesh::ATTRIBUTE_POSITION) + else { + panic!("plate must carry Float32x3 positions"); + }; + let Some(VertexAttributeValues::Float32x3(normals)) = + mesh.attribute(Mesh::ATTRIBUTE_NORMAL) + else { + panic!("plate must carry Float32x3 normals"); + }; + for [x, y, _] in positions { + let r = (*x - center.x).hypot(*y - center.y); + assert!( + r >= bore - 1.0e-3, + "a vertex at radius {r} intrudes into the opening" + ); + } + let wall = positions + .iter() + .zip(normals.iter()) + .filter(|(p, n)| { + n[2].abs() < 0.01 && (p[0] - center.x).hypot(p[1] - center.y) < bore + 1.0e-3 + }) + .count(); + assert!(wall > 0, "the bore must have a cylindrical wall"); + for (p, n) in positions.iter().zip(normals.iter()) { + if n[2].abs() > 0.01 || (p[0] - center.x).hypot(p[1] - center.y) > bore + 1.0e-3 { + continue; + } + let radial = Vec2::new(p[0] - center.x, p[1] - center.y).normalize_or_zero(); + assert!( + radial.x * n[0] + radial.y * n[1] < 0.0, + "bore wall must face the axis" + ); + } + } } diff --git a/wiki/interface/computer-visual-language.md b/wiki/interface/computer-visual-language.md index 577df489..b2c5a261 100644 --- a/wiki/interface/computer-visual-language.md +++ b/wiki/interface/computer-visual-language.md @@ -25,7 +25,8 @@ Status note: IMPLEMENTED (ROADMAP #32). Current state: visible chassis has local sight and that a core chassis exists exactly when the core tile is Seen. - **The B1 chassis** is a tall, slender, asymmetric bone-white appliance with - thick black service structure. All 240 Foundation rack sites derive their + thick black service structure, built as a **folded shell** rather than a + stack of axis-aligned slabs (2026-08-02). All 240 Foundation rack sites derive their state (live/unpowered/dead/owned/core/empty) from one `RackSite` query across material, flat, terminal, and agent views. Future heterogeneous computers keep these signals without inheriting this exact shell. @@ -130,6 +131,70 @@ fails this contract even when its colors and state logic are correct. Exact overlap and recess remain `[TUNE]`; the enclosure/opening relationship is binding. +## The folded shell — chassis construction (2026-08-02) + +**Amendment (Cameron, screenshot feedback: the machines read as flat boxes).** +The aperture tuning above was satisfied by constants while the body that +carried it was a stack of axis-aligned cuboids, and a cuboid gives every +front-facing plate an identical normal. Under one light that collapses into a +single uniform value, so the shell read as cardboard and the instrument read as +a decal printed on it — failing criteria 8 and 9 in the render even though the +numbers passed. Chassis construction is therefore binding, not only chassis +proportion: + +1. **The shell is swept from a chamfered footprint, never a box.** Each + vertical corner is cut back by its own amount, so the mass presents several + unequal lit facets from any angle. The chamfers are what carry "large + middle-soft facets" — they must be unequal front-to-front, or the form reads + heraldic. +2. **The tower stands on a recessed dark plinth** and is closed by a **crown** + that bevels in at the top, separated from the body by one dark line. A shell + that meets the floor flush or stops at a flat top has no deployed weight. + The crown carries the fold value; it is the shell's only value break. +3. **The front is ONE plate with the aperture bored through it** (amended the + same day — see "Smooth is the constraint" below). The bore's thickness is + the well. Relief depth, not the annulus constants alone, is what makes the + aperture a well rather than a disc. +4. **The opening is off-axis**, so the bone it leaves is unequal left to right. + That inequality is the asymmetry, and it costs the surface nothing. +5. **Service structure is straight, dark, unequal, and rearward**: conduit down + each flank at different lengths and depths, kept to the rear half of the + flanks, plus one rear/base trunk. + +Exact chamfer, relief, plinth, crown, and aperture magnitudes remain `[TUNE]`. +What is binding is that the body is a folded mass with unequal facets and that +the front is a single bored plane. The same grammar scales to the switch class +at its own size. + +### Smooth is the constraint (Cameron, same day, second pass) + +The first attempt at this amendment satisfied "plates fold around the aperture" +literally — upper and lower plates, edge steps riding over unequal arcs of the +enclosure, canted flank intakes. Cameron's verdict on the render was +unambiguous: *"absolutely hideous… it's supposed to be smooth… weird and +jaggedy."* He was right, and the failure is worth stating as law because it is +the same failure in a new costume: the *aperture* was 86% of chassis width, so +every plate meant to fold around it degenerated into a sliver or a tab +protruding past the silhouette. Detail was added to compensate for a proportion +that had no room in it. + +Therefore: + +- **The shell closes around the opening as one continuous surface.** The + aperture is bored through a single plate, not left as a gap between plates. + No tab, step, notch, or rectangular recess may intrude on the opening or + break the plane around it. "Broad unequal plates stop at its aperture and + fold around it" (2026-07-28) is satisfied by the bore's own wall — the plate + terminates at the opening continuously, through every angle. +- **The aperture must leave real bone on every side.** A margin of at least + `APERTURE_MARGIN` of shell on the thin side is binding, and it bounds the + opening's size from above. The earlier lower bound (radius > 0.40 × chassis + width) is retired: it was written to prevent a badge, and depth now does that + job. The current bound is radius > 0.28 × chassis width. +- **Elaboration is not the fix for flatness.** Where the render reads flat, the + first question is whether a proportion left the surface no room, not what can + be added to it. + 1. **Chassis albedo — the ownership field (DECIDED 2026-07-09).** The chassis itself is painted by control: machines under your control are **bone-white/porcelain**; machines not under your control — @@ -282,6 +347,15 @@ the fallback. structure, restrained facets, instrument-bearing folded shoulders, and one disciplined rear/base trunk. At game distance it reads as one dense computer, not exposed rack anatomy or architecture. +9b. The shell is a folded mass and the front is one bored plane, both enforced + by tests over the geometry rather than by convention: the footprint is a + chamfered octagon with unequal front cuts and every chamfer lights as its + own facet with an outward normal; the front plate's opening is a continuous + cylindrical bore whose wall faces the axis and into which no vertex + intrudes; and the opening leaves at least `APERTURE_MARGIN` of shell on its + thin side, unequal to its thick side. A body built from axis-aligned + cuboids fails this criterion even when every proportion constant passes, + and so does an opening assembled out of plates and tabs. 10. Light / medium / hard intensity changes the owned machine's cast-pool strength in the material render and appears as L/M/H text in the terminal; neither frontend invents intensity state. diff --git a/wiki/log/2026-08-02-folded-chassis-shell.md b/wiki/log/2026-08-02-folded-chassis-shell.md new file mode 100644 index 00000000..2383b906 --- /dev/null +++ b/wiki/log/2026-08-02-folded-chassis-shell.md @@ -0,0 +1,138 @@ +# 2026-08-02 — the B1 chassis becomes a folded shell + +``` +Type: log +``` + +## Report + +Cameron, from a REAL-frame screenshot: the computers in 3D read badly, and the +suspicion was that downloaded art was involved. It was not. There is no +downloaded machine art anywhere in this repository — `assets/pixellab/` and its +load paths were deleted by the 2026-07-08 flat-materials scrub, and every +Foundation machine is procedural geometry built in +`crates/misaligned-assets/src/rack.rs` under the named clinical palette. The +render was bad on its own merits. + +## Audit + +`computer-visual-language.md` was `IMPLEMENTED` and its constants all passed: +proportion (3.6:1), aperture radius, drum/enclosure nesting, lip depth, face +recess, ownership albedo, ring counts, notch counts, twenty-step pie. The +`ownership_is_albedo_and_the_integrated_ring` test enforced every one of them. + +The body carrying those constants was eleven axis-aligned cuboids. A cuboid +gives every front-facing plate the same normal, so under one light the whole +front collapses to a single flat value. Three spec clauses were failing in the +render while passing in the numbers: + +- **Criterion 8** ("body-integrated, not a stuck-on badge") — the aperture sat + on an uninterrupted rectangular panel. The plates that were supposed to fold + around it were 0.02–0.03 deep and 0.02 wide, which is nothing at any camera. +- **Criterion 9** ("restrained facets", "large middle-soft facets") — there + were no facets. The silhouette was a rectangle. +- The selected direction reference + (`foundation-compute/selected-asymmetric-service-spine.png`) asks for a + chamfered mass, a capping crown, unequal shell masses, and canted black + service structure. None of it survived into geometry. + +This is the failure mode the corpus is built to catch and did not: a spec +enforced entirely through scalar constants, where the *construction* those +constants describe was never itself binding. + +## Repair — first pass, rejected + +`rack.rs` gained two mesh primitives — `chamfered_footprint` (a rectangle whose +four vertical corners cut back by their own amount) and `extruded_shell_mesh` +(sweeps a convex footprint between two heights with flat per-face normals and +an independent top profile, so a crown can taper). The B1 body is rebuilt from +them: + +- chamfered octagonal shell with unequal front cuts, standing on a recessed + near-black plinth, closed by a crown that bevels in at the top over one dark + separation line; +- a front plate stack standing proud of the shell's own face over a near-black + recessed wall across the instrument band — that relief is what makes the + aperture a well instead of a disc; +- two unequal masses: the upper plate sits inboard on the left, leaving the + shell face exposed as a recessed shoulder; +- one plate step per side folding past unequal arcs of the enclosure, each + clearing the projecting drum by construction (`drum_clear`), so no step can + clip the state read; +- canted flank conduit at unequal lengths and depths, one canted low intake, + and the disciplined rear/base trunk. + +Cameron rejected it on sight: *"absolutely hideous… it's supposed to be +smooth… weird and jaggedy."* Correct. The chamfered shell, plinth, and crown +were right; everything done to the front was wrong. The aperture was 86% of +chassis width, so every plate meant to fold around it had nowhere to be: the +shoulders became slivers, the steps became tabs, and one of them protruded past +the silhouette entirely. Detail had been added to compensate for a proportion +that had no room in it — the same error as the original slab, one layer up. + +## Repair — second pass + +The front is now **one plate with the aperture bored straight through it**. A +new `bored_plate_mesh` builds it: front and back faces triangulated between the +plate's outline and the bore, the bore's own cylindrical wall with normals +facing the axis, and outer edges taken from the exact outline so the chamfers +stay crisp. The shell closes around the opening as a continuous curve. Nothing +steps, tabs, or notches into it. + +For that to be possible the opening had to give back room it should never have +taken. `MACHINE_INSTRUMENT_RADIUS` drops from 0.292 to 0.215 — from 86% to 63% +of chassis width — and every internal instrument proportion is now expressed as +a fraction of that radius, so the concentric assembly stays correct under any +retune. Off-axis placement leaves unequal bone (0.061 left, 0.085 right); that +inequality is now the asymmetry, and it costs the surface nothing. Flank +conduit moved to the rear half: on a bone chassis a dark line near the front +edge reads as damage, not structure. + +Retired with the first pass: the upper/lower plate split, both edge steps, the +rectangular dark recess band, and the canted intakes. + +The switch class takes the same grammar at its own scale, keeping criterion 5 +(one kit, two classes) honest. + +No signal semantics moved. Ownership is still albedo, the instrument is still +the only lit element, the pie is still twenty steps of sim-authored throughput, +and no new emissive, color, or state channel exists. Every added surface is +`CHASSIS_OWNED`, `CHASSIS_OWNED_FOLD`, `GUNMETAL_DARK`, or `NEAR_BLACK` from +the shared palette. + +## Defense + +Three tests pin the construction the constants could not: + +- `the_shell_is_a_folded_mass_with_unequal_facets` — the footprint is an + octagon with visibly unequal front chamfers, the swept mesh exposes exactly + eight distinct horizontal facet normals, and every side normal points away + from the axis. A cuboid body fails this test. +- `the_front_plate_is_bored_not_assembled` — no vertex intrudes inside the + opening, the bore carries a real cylindrical wall, and every wall normal + faces the axis. An opening assembled from plates and tabs fails this test. +- `the_aperture_leaves_bone_on_every_side` — both margins clear + `APERTURE_MARGIN`, they are unequal, and the bore stays clear of plinth and + crown. This is the bound that would have caught the 86%-width aperture before + anything was built around it. + +Criterion 9b records the same requirements in the spec, and the retired +`radius > 0.40 × chassis width` bound is replaced by `> 0.28` plus the margin +assertion above. + +## Evidence + +Asset tester `lineup` (dead / foreign / idle / busy / core) — all five states +still separate, ownership albedo unchanged, no warm pixel on foreign or dead +hardware, core still double-collared. Game `worklight` (owned machine beside +two foreign), `hall-material` (the dense 240-site hall), and +`build-switch-real` (close range) all render the bored shell; the chamfered +crowns are what give the hall rows their form under the overhead lights. +`cargo test -p misaligned-assets` passes, `./tools/check.sh --frontend` passes. + +## Open + +Chamfer, relief, plinth, crown, and aperture magnitudes remain `[TUNE]`. The +opening is now 63% of chassis width, chosen as the largest size that still +leaves a smooth margin; if Cameron wants it larger the chassis has to get +wider, not the margin thinner. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 3b4317da..570f3709 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -51,6 +51,11 @@ add or amend a session log, then re-run the generator. - Intent: (see session log) - Log: [wiki/log/2026-08-02-knowledge-use-receipts.md](2026-08-02-knowledge-use-receipts.md) +## 2026-08-02 - the B1 chassis becomes a folded shell + +- Intent: (see session log) +- Log: [wiki/log/2026-08-02-folded-chassis-shell.md](2026-08-02-folded-chassis-shell.md) + ## 2026-08-02 - One device action, one consequence read - Intent: The focused-device panel displayed `UNTAP / TAKE` above `NO COST` and `NO ADDED ATTENTION`. Those consequences belonged to UNTAP only. TAKE is a paid, attention-producing ownership seizure, so the composition made two different actions look like one action and made the dangero...