From aaf1a5b26662a9c513ab84a54bae90c2f59d18cf Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 10 Jul 2026 18:23:35 -0700 Subject: [PATCH] Fold Thought through intertwined flow paths Defense: Implements wiki/interface/thought-fluid.md's adopted intertwined-path material clause and criterion 6: close high-rate Thought now forms a broad asymmetric sheet from two continuous paths, and their rate/phase-derived separation creates and seals the strain window without a subtractive bore or second simulation state. Preserves wiki/art/effects-lab.md's LOD boundary by changing only the cached close implicit frames; medium and far remain compressed representations. --- .../assets/shaders/thought_flow.wgsl | 17 +- crates/misaligned-assets/src/effects.rs | 5 +- .../misaligned-assets/src/implicit_surface.rs | 183 ++++++++++++------ wiki/art/effects-lab.md | 30 ++- wiki/interface/thought-fluid.md | 27 ++- wiki/log/2026-07-10-thought-sheet-field.md | 46 +++++ wiki/log/DEVLOG.md | 5 + wiki/log/decisions/2026-07-10.md | 13 ++ 8 files changed, 254 insertions(+), 72 deletions(-) create mode 100644 wiki/log/2026-07-10-thought-sheet-field.md diff --git a/crates/misaligned-assets/assets/shaders/thought_flow.wgsl b/crates/misaligned-assets/assets/shaders/thought_flow.wgsl index 4b032c0b..952bd81d 100644 --- a/crates/misaligned-assets/assets/shaders/thought_flow.wgsl +++ b/crates/misaligned-assets/assets/shaders/thought_flow.wgsl @@ -18,9 +18,13 @@ var thought: ThoughtFlowExtension; // cross-axis waves keep it from reading as painted-on parallel stripes. fn marble_field(p: vec3) -> f32 { let travel = thought.time * thought.flow_speed; + // The same angular phase selects the implicit mesh frame. This leading + // current reaches a region just before the dominant lobe swells there. + let pressure_lead = sin(p.x * 1.9 - travel + 0.65); let cross_warp = sin(p.y * 11.0 + p.z * 5.0 + travel * 0.31) * 1.35 - + sin(p.y * 23.0 - p.z * 13.0 - travel * 0.19) * 0.42; + + sin(p.y * 23.0 - p.z * 13.0 - travel * 0.19) * 0.42 + + pressure_lead * 0.68; let primary = sin(p.x * thought.band_scale - travel + cross_warp); let fold = sin( p.x * thought.band_scale * 2.17 @@ -36,7 +40,7 @@ fn marble_field(p: vec3) -> f32 { + fold * 0.85 + travel * 0.43, ); - return primary * 0.54 + fold * 0.31 + eddy * 0.15; + return primary * 0.49 + fold * 0.29 + eddy * 0.14 + pressure_lead * 0.08; } @fragment @@ -45,12 +49,21 @@ fn fragment(in: VertexOutput, @builtin(front_facing) is_front: bool) -> Fragment let field = marble_field(in.world_position.xyz); let band = smoothstep(-0.48, 0.42, field); let vein = pow(1.0 - min(abs(field) * 1.4, 1.0), 5.0); + let fine_flow = sin( + in.world_position.x * thought.band_scale * 3.1 + + in.world_position.y * 13.0 + + in.world_position.z * 19.0 + - thought.time * thought.flow_speed * 1.62 + + field * 5.2, + ); + let fine_vein = pow(1.0 - abs(fine_flow), 9.0); let ivory = vec3(0.82, 0.79, 0.72); let silver = vec3(0.96, 0.97, 0.95); let graphite = vec3(0.16, 0.18, 0.20); var color = mix(graphite, silver, band); color = mix(color, ivory, vein * 0.42); + color = mix(color, graphite, fine_vein * 0.19); color = mix(vec3(0.42), color, thought.contrast); pbr_input.material.base_color = vec4(color, 1.0); diff --git a/crates/misaligned-assets/src/effects.rs b/crates/misaligned-assets/src/effects.rs index 3f9784b1..399bfd3c 100644 --- a/crates/misaligned-assets/src/effects.rs +++ b/crates/misaligned-assets/src/effects.rs @@ -14,6 +14,7 @@ use crate::implicit_surface::{ThoughtSurfaceParams, build_thought_surface}; use crate::palette::{BONE, CRIMSON}; type ThoughtFlowMaterial = ExtendedMaterial; +const THOUGHT_FLOW_RADIANS_PER_SECOND: f32 = 1.35; #[derive(Asset, AsBindGroup, Reflect, Debug, Clone)] struct ThoughtFlowExtension { @@ -257,7 +258,7 @@ fn setup_effect_assets( }, extension: ThoughtFlowExtension { time: 0.0, - flow_speed: 1.35, + flow_speed: THOUGHT_FLOW_RADIANS_PER_SECOND, band_scale: 8.5, contrast: 0.88, }, @@ -511,7 +512,7 @@ fn spawn_thought( EffectVisual, ThoughtSurfaceAnimation { frames: frames.clone(), - cycles_per_second: (0.12 + effects.thought_rate * 0.08).clamp(0.10, 0.42), + cycles_per_second: THOUGHT_FLOW_RADIANS_PER_SECOND / std::f32::consts::TAU, }, Mesh3d(frames[0].clone()), MeshMaterial3d(assets.thought_surface.clone()), diff --git a/crates/misaligned-assets/src/implicit_surface.rs b/crates/misaligned-assets/src/implicit_surface.rs index da121934..5b92b403 100644 --- a/crates/misaligned-assets/src/implicit_surface.rs +++ b/crates/misaligned-assets/src/implicit_surface.rs @@ -17,7 +17,7 @@ pub(crate) struct ThoughtSurfaceParams { pub phase: f32, } -const RESOLUTION: UVec3 = UVec3::new(56, 28, 28); +const RESOLUTION: UVec3 = UVec3::new(64, 36, 34); const TETS: [[usize; 4]; 6] = [ [0, 5, 1, 6], [0, 1, 2, 6], @@ -169,50 +169,71 @@ fn sdf_normal(params: ThoughtSurfaceParams, p: Vec3, e: f32) -> Vec3 { fn thought_sdf(params: ThoughtSurfaceParams, p: Vec3) -> f32 { let amount = params.amount.max(0.1).cbrt(); let rate = params.rate.max(0.0); + let cycle = params.phase.fract() * std::f32::consts::TAU; + let base_strain = ((rate - 0.72) / 0.38).clamp(0.0, 1.0); + let openness = base_strain * ((cycle.sin() * 0.5 + 0.5).powf(1.6)); + let window_t = 0.56 + cycle.cos() * 0.10; + // The dark/current phase reaches the strain locus first; the dominant + // pressure lobe follows just downstream and pulls the opening shut. + let dominant_t = (window_t + 0.16 + 0.025 * cycle.sin()).min(0.84); + let secondary_t = 0.20 + 0.035 * cycle.cos(); + let neck_t = 0.39 + 0.025 * cycle.sin(); + let base_radius = 0.060 + 0.025 * amount; let mut body = 10_000.0; - const BLOBS: usize = 31; - for i in 0..BLOBS { - let t = i as f32 / (BLOBS - 1) as f32; - let wave = std::f32::consts::TAU * t; - let center = folded_route_point(params.start, params.end, t); - let pressure = (wave * 3.0 - rate * 0.8).sin() * 0.5 + 0.5; - let radius = (0.082 + 0.034 * amount) - * (0.98 + 0.32 * pressure.powf(1.5)) - * (0.95 + 0.08 * (wave * 5.0).sin()); - let axes = Vec3::new( - radius * (1.50 + pressure * 0.25), - radius * (0.72 + pressure * 0.70), - radius * (0.38 + pressure * 0.25), - ); - body = smooth_min(body, ellipsoid_sdf(p - center, axes), radius * 0.58); - } + const SAMPLES: usize = 39; + for rail in [-1.0, 1.0] { + for i in 0..SAMPLES { + let t = i as f32 / (SAMPLES - 1) as f32; + let center = flow_path_point(params, t, rail, cycle, openness, window_t); + let before = flow_path_point( + params, + (t - 0.004).max(0.0), + rail, + cycle, + openness, + window_t, + ); + let after = flow_path_point( + params, + (t + 0.004).min(1.0), + rail, + cycle, + openness, + window_t, + ); + let tangent = (after - before).normalize_or_zero(); - // A high-rate pressure lobe thickens one region enough to make the - // silhouette fold back around a transient, through-thickness window. - let base_strain = ((rate - 0.72) / 0.38).clamp(0.0, 1.0); - let cycle = params.phase.fract() * std::f32::consts::TAU; - let strain = base_strain * ((cycle.sin() * 0.5 + 0.5).powf(1.6)); - if strain > 0.01 { - let t = 0.56 + cycle.cos() * 0.12; - let center = folded_route_point(params.start, params.end, t); - let membrane = ellipsoid_sdf( - p - center, - Vec3::new( - 0.34 + 0.15 * strain, - 0.25 + 0.14 * strain, - 0.09 + 0.04 * strain, - ), - ); - body = smooth_min(body, membrane, 0.09); - let half_depth = 0.72; - let hole_radius = 0.030 + 0.125 * strain; - let cavity = capsule_sdf( - p, - center - Vec3::Z * half_depth, - center + Vec3::Z * half_depth, - hole_radius, - ); - body = body.max(-cavity); + let dominant = gaussian(t, dominant_t, 0.14); + let secondary = gaussian(t, secondary_t, 0.10); + let neck = gaussian(t, neck_t, 0.085); + let radius = + base_radius * (0.86 + dominant * 0.70 + secondary * 0.30 - neck * 0.25).max(0.58); + let axes = Vec3::new( + radius * (1.55 + dominant * 0.95 + secondary * 0.30), + radius * (0.52 + dominant * 0.40 + secondary * 0.22), + radius * (0.28 + dominant * 0.07), + ); + body = smooth_min( + body, + oriented_ellipsoid_sdf(p - center, tangent, axes), + radius * 0.30, + ); + if i > 0 { + let previous = flow_path_point( + params, + (i - 1) as f32 / (SAMPLES - 1) as f32, + rail, + cycle, + openness, + window_t, + ); + body = smooth_min( + body, + capsule_sdf(p, previous, center, base_radius * 0.54), + base_radius * 0.16, + ); + } + } } body } @@ -225,13 +246,39 @@ fn route_point(start: Vec3, end: Vec3, t: f32) -> Vec3 { start * u * u * u + c1 * 3.0 * u * u * t + c2 * 3.0 * u * t * t + end * t * t * t } -fn folded_route_point(start: Vec3, end: Vec3, t: f32) -> Vec3 { - let tangent = (end - start).normalize_or_zero(); - let side = Vec3::new(-tangent.z, 0.0, tangent.x).normalize_or_zero(); +fn flow_path_point( + params: ThoughtSurfaceParams, + t: f32, + rail: f32, + cycle: f32, + openness: f32, + window_t: f32, +) -> Vec3 { let wave = std::f32::consts::TAU * t; - route_point(start, end, t) - + side * (0.16 * (wave * 1.45 + 0.6).sin() + 0.07 * (wave * 3.1).sin()) - + Vec3::Y * (0.20 * (wave * 2.0 - 0.5).sin()) + let envelope = (std::f32::consts::PI * t).sin().max(0.0).powf(0.72); + let mut center = flow_spine_point(params, t); + + // Two paths weave through the body. Under strain they pull apart around a + // traveling locus, making the negative space from separation alone. + let weave = envelope * (0.050 * (wave * 1.15 + 0.55).sin() + 0.022 * (wave * 2.3 - 0.8).sin()); + let split_profile = gaussian(t, window_t, 0.20).powf(1.20); + let split = openness * 0.34 * split_profile; + let split_asymmetry = if rail > 0.0 { 1.0 } else { 0.85 }; + center.y += rail * (weave + split * split_asymmetry); + center.x += rail * envelope * 0.045 * (wave + cycle * 0.18).sin(); + center.z += rail * envelope * 0.028 * (wave * 1.65 - cycle * 0.12).cos(); + center +} + +fn flow_spine_point(params: ThoughtSurfaceParams, t: f32) -> Vec3 { + let wave = std::f32::consts::TAU * t; + let envelope = (std::f32::consts::PI * t).sin().max(0.0).powf(0.72); + let mut center = route_point(params.start, params.end, t); + // The shared spine has one long arch and a smaller returning fold rather + // than a periodic chain of equal bulges. + center.y += envelope * (0.12 * (wave + 0.35).sin() + 0.055 * (wave * 2.7 - 0.8).sin()); + center.z += envelope * 0.035 * (wave * 1.4 + 0.5).sin(); + center } fn smooth_min(a: f32, b: f32, k: f32) -> f32 { @@ -239,6 +286,10 @@ fn smooth_min(a: f32, b: f32, k: f32) -> f32 { b.lerp(a, h) - k * h * (1.0 - h) } +fn gaussian(value: f32, center: f32, width: f32) -> f32 { + (-((value - center) / width.max(0.0001)).powi(2)).exp() +} + fn capsule_sdf(p: Vec3, a: Vec3, b: Vec3, radius: f32) -> f32 { let pa = p - a; let ba = b - a; @@ -246,8 +297,12 @@ fn capsule_sdf(p: Vec3, a: Vec3, b: Vec3, radius: f32) -> f32 { (pa - ba * h).length() - radius } -fn ellipsoid_sdf(p: Vec3, axes: Vec3) -> f32 { - let scaled = p / axes.max(Vec3::splat(0.0001)); +fn oriented_ellipsoid_sdf(p: Vec3, tangent: Vec3, axes: Vec3) -> f32 { + let tangent = tangent.normalize_or(Vec3::X); + let across = Vec3::Z.cross(tangent).normalize_or(Vec3::Y); + let depth = tangent.cross(across).normalize_or(Vec3::Z); + let local = Vec3::new(p.dot(tangent), p.dot(across), p.dot(depth)); + let scaled = local / axes.max(Vec3::splat(0.0001)); (scaled.length() - 1.0) * axes.min_element() } @@ -277,15 +332,35 @@ mod tests { let high = params(1.3); let mut relaxed = high; relaxed.phase = 0.75; - let p = folded_route_point(low.start, low.end, 0.56); + let cycle = low.phase * std::f32::consts::TAU; + let window_t = 0.56 + cycle.cos() * 0.10; + let p = flow_spine_point(low, window_t); assert!(thought_sdf(low, p) < 0.0, "low-rate body stays filled"); + let open_value = thought_sdf(high, p); assert!( - thought_sdf(high, p) > 0.0, - "high-rate strain opens a window" + open_value > 0.0, + "high-rate strain opens a window, got {open_value}" ); assert!( thought_sdf(relaxed, p) < 0.0, "the same high-rate body seals when pressure phase relaxes" ); } + + #[test] + fn open_window_keeps_both_paths_continuous() { + let high = params(1.3); + let cycle = high.phase * std::f32::consts::TAU; + let window_t = 0.56 + cycle.cos() * 0.10; + for rail in [-1.0, 1.0] { + for i in 0..=24 { + let t = i as f32 / 24.0; + let center = flow_path_point(high, t, rail, cycle, 1.0, window_t); + assert!( + thought_sdf(high, center) < 0.0, + "rail {rail} left the body at t={t}" + ); + } + } + } } diff --git a/wiki/art/effects-lab.md b/wiki/art/effects-lab.md index 09b0b3b7..047372c7 100644 --- a/wiki/art/effects-lab.md +++ b/wiki/art/effects-lab.md @@ -7,9 +7,11 @@ Status note: decided 2026-07-10. The first implementation establishes the separate lab binary, shared renderer boundary, deterministic presets, and close / medium / far visual levels. A second slice replaces close high-rate Thought's rejected hose/pearl geometry with a CPU implicit surface, animated - strain-window frames, and internal-flow marbling. Main-game binding follows - the live machine-work and thought-fluid readouts; the lab does not invent - game state. + strain-window frames, and internal-flow marbling. The next silhouette pass + replaces that field's single-lane bulges and drilled window with intertwined + sheet-forming paths whose separation creates the opening. Main-game binding + follows the live machine-work and thought-fluid readouts; the lab does not + invent game state. Stage: B1 — The Basement Work order: effects-lab Work priority: 26 @@ -153,12 +155,13 @@ Criteria 1–7 are held by the first lab slice: the exported stress), automatic and forced LOD, deterministic scatter/time, and the PNG harness. Screenshot review rejected the first pearl-train close prototype and replaced high-rate Thought first with an overlapping rivulet, then with a -close-only implicit surface extracted from overlapping, flattened pressure -lobes. Eight deterministic surface frames make a rate-derived strain window -travel, open, and seal; a custom PBR extension advects marbled ivory, silver, -and gunmetal bands across the skin from the same presentation clock. Amount, -rate, route, and direction remain the only authored inputs. Medium and far -retain the cheaper rivulet and aggregate-route representations. +close-only implicit surface. The current field intertwines two continuous +paths beneath flattened pressure lobes; their animated separation creates the +window without a subtractive bore. Eight deterministic surface frames make the +rate-derived strain travel, open, and seal; a custom PBR extension advects +marbled ivory, silver, and gunmetal bands from the same phase. Amount, rate, +route, and direction remain the only authored inputs. Medium and far retain the +cheaper rivulet and aggregate-route representations. The implicit frames are generated once per distinct input set, shared across matching roots, and swapped during animation. They are disposable presentation @@ -169,6 +172,15 @@ remains: `misaligned-bevy` has a concurrent Thought-flow renderer, so binding the shared plugin to its named sim readouts must be a deliberate reconciliation rather than an overwrite. +The implemented silhouette refinement keeps the same extraction/cache boundary +but changes the close field itself: two asymmetrical returning paths merge into +a flattened sheet, separate into a traveling slit/window, and reunite behind a +dominant pressure lobe. Thin capsule fields guarantee each path remains +continuous through the split; anisotropic lobes, not those hidden spines, own +the broad visible silhouette. Geometry and marbling use the same angular phase +so visible internal flow predicts deformation. Exact path offsets, anisotropy, +and phase curves remain `[TUNE]` in deterministic lab captures. + ## Decided, open, and deferred **DECIDED:** separate `misaligned-effects` binary; shared package-library diff --git a/wiki/interface/thought-fluid.md b/wiki/interface/thought-fluid.md index 80ce898d..e9c66435 100644 --- a/wiki/interface/thought-fluid.md +++ b/wiki/interface/thought-fluid.md @@ -305,11 +305,12 @@ presentation of close-zoom strain. The shared effects lab now proves this material direction without installing a second fluid simulation. At close zoom and rivulet rate, it builds a CPU -implicit field from overlapping flattened pressure lobes, extracts the surface -with marching tetrahedra, and swaps eight deterministic presentation frames. -The frames move a subtractive through-window with the pressure phase and return -to a sealed body as the phase relaxes. A Bevy PBR material extension advects -marbled ivory, silver, and gunmetal bands over the same surface. +implicit field from two intertwined flow paths, their flattened pressure lobes, +and thin continuity spines; it extracts the surface with marching tetrahedra +and swaps eight deterministic presentation frames. The frames pull the paths +apart into a through-window and merge them back into a sealed body as the phase +relaxes. A Bevy PBR material extension advects marbled ivory, silver, and +gunmetal bands over the same phase. The field is rebuilt only from route, amount, rate, and disposable presentation phase; it owns no Thought counter, arrival, or routing state. Medium and far @@ -318,6 +319,22 @@ close-material portion of criterion 6 in the lab, but it does not complete the criterion for sim-driven main-game flow or the remaining grayscale/state lineup. +**The close field uses intertwined flow paths, not one decorated centerline +(DECIDED 2026-07-10).** Two returning paths separate, cross, and merge inside +the route envelope. Their union forms a thin-in-depth, wide-across-flow sheet: +one dominant pressure lobe, one long stretched neck, and a smaller returning +fold. Repeating equal bulges are rejected with the constant-radius hose; the +silhouette must remain asymmetrical enough to look reorganized by pressure +rather than extruded along a spline. + +Strain windows form where the two paths pull apart. A window begins as a slit +between them, widens asymmetrically as the dominant lobe passes, and pinches +shut behind it. A subtractive circular bore is an implementation anti-target: +the negative space must be explained by separation of the visible body. The +marbled current and deformation share one presentation phase, so a dark current +enters a region before that region swells or draws inward. This synchronization +is still recomputable rate presentation, never new Thought state. + **Sleek-rule exemption, stated once:** the strip remains the chassis's only lit *element*; tokens are cargo standing on anchors, not chassis elements, and the pool/slugs carry their decided self-glow without diff --git a/wiki/log/2026-07-10-thought-sheet-field.md b/wiki/log/2026-07-10-thought-sheet-field.md new file mode 100644 index 00000000..5cf1f286 --- /dev/null +++ b/wiki/log/2026-07-10-thought-sheet-field.md @@ -0,0 +1,46 @@ +# 2026-07-10 — Thought becomes an intertwined sheet + +``` +Type: log +``` + +## Intent + +Act on Cameron's review of the first implicit Thought prototype. The technique +worked, but its single centerline still read as a marbled sausage: three +inflated lobes, a drilled-looking hole, and surface pattern doing too much of +the visual work. + +## Decided and built + +- Replaced the single sampled centerline with two asymmetrical paths which + weave, separate, and merge inside the authored route envelope. +- Flattened their anisotropic pressure lobes into one broad sheet with a + downstream dominant lobe, stretched necks, and a smaller upstream fold. +- Removed the subtractive capsule cavity. The transient window is now exactly + the negative space between the paths while they are separated. +- Added thin continuous implicit spines beneath the lobes so an open window + remains a connected loop rather than breaking into mesh islands. +- Locked geometry-frame speed to the shader's angular flow phase and added a + restrained fine current inside the broad ivory/silver/gunmetal marbling. +- Raised close extraction resolution; cached frames still keep matching stress + roots from rebuilding identical meshes. + +## Evidence + +- Focused field tests prove a non-empty surface, low-rate sealing, high-strain + opening, relaxed-phase resealing, and continuity of both paths throughout an + open frame. +- Fixed-step close, medium, and far captures completed. Two independent close + captures matched byte-for-byte at SHA-256 + `6ab813a3f394278083c739fd2c18eed718949d56e215e7878d75926480a2b158`. +- Visual review confirms one broad asymmetric lobe, two enclosing necks, and a + path-separated window. Medium and far remain on their cheaper representations. + +## Defense + +Implements the adopted intertwined-path and strain-window clauses in +`wiki/interface/thought-fluid.md#material-ivory-mercury-against-chalk` and the +close-only LOD boundary in `wiki/art/effects-lab.md#one-effect-three-visual-levels`. +The field still derives solely from route, amount, rate, and disposable phase; +it adds no Thought quantity, arrival, routing, or fluid simulation state. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 07fd2261..fd571d2a 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -46,6 +46,11 @@ add or amend a session log, then re-run the generator. - Intent: Resolve the one material-topology call left by the selected Thought-fluid macro reference. - Log: [wiki/log/2026-07-10-thought-strain-windows.md](2026-07-10-thought-strain-windows.md) +## 2026-07-10 - Thought becomes an intertwined sheet + +- Intent: Act on Cameron's review of the first implicit Thought prototype. The technique worked, but its single centerline still read as a marbled sausage: three inflated lobes, a drilled-looking hole, and surface pattern doing too much of the visual work. +- Log: [wiki/log/2026-07-10-thought-sheet-field.md](2026-07-10-thought-sheet-field.md) + ## 2026-07-10 - Thought fluid render: the animation design - Intent: Cameron dispatched the thought fluid animation design — the wire-flow render layer decided 2026-07-10 (mercury slugs on links, meniscus fills at sinks, filament snap, pooling), driven entirely by FlowGraph rates/queues with no CFD. Deliverable: a spec-level design capture unde... diff --git a/wiki/log/decisions/2026-07-10.md b/wiki/log/decisions/2026-07-10.md index 75c2a9c9..7730bd11 100644 --- a/wiki/log/decisions/2026-07-10.md +++ b/wiki/log/decisions/2026-07-10.md @@ -367,3 +367,16 @@ Type: log treating every key or panel command as a world verb; copying complete mechanic rules into the vocabulary owner; treating compatibility aliases as authored terms. No gameplay effects changed. +- **2026-07-10 — Thought folds from intertwined paths, not one decorated + centerline.** Cameron adopted the post-prototype silhouette correction. + Close high-rate Thought uses two returning flow paths which separate, cross, + and merge into a thin-in-depth, broad sheet with an intentionally uneven + rhythm: one dominant lobe, one stretched neck, and one smaller fold. A strain + window is the asymmetrical space created when those paths pull apart; it + begins as a slit and pinches shut behind the passing lobe. The internal + marbling shares deformation phase so its currents visibly precede swelling + and contraction. Rejected: three equal inflated bulges; marbling used as + camouflage on a tubular silhouette; a circular cavity drilled through an + otherwise unchanged body. Exact field constants remain `[TUNE]`. Owner: + wiki/interface/thought-fluid.md material section; implementation lab: + wiki/art/effects-lab.md. -- 2.51.2