facett-graphview/src/world_still.rs
//! **The world-graph STILL** — a converged, well-distributed layout rendered once,
//! headless, on the GPU into a single emissive image (the "mega tier": one nicely-spread
//! picture of the whole universe at 100k+). Reuses the shared instanced GPU cloud
//! ([`facett_core::render::gpu::render_graph_offscreen`], the ONE emissive node/edge
//! renderer with real bloom) — no second renderer — and the world graph's own galaxy
//! layout ([`crate::world::hier_placement`]/[`crate::world::layout_world`]) + categorical
//! colour ([`crate::world::node_color`]). Behind feature `gpu` (the wgpu path).
//!
//! The chip scene ([`crate::scene`]) is for labelled dep-graph boxes; a 100k node cloud
//! wants *points*, so the still lowers each node to a bloomed disc sized by its tier and
//! coloured by its repo/crate — a galaxy of coloured clusters, not grey turds.
use facett_core::render::gpu::{
render_graph_offscreen, BloomParams, EdgeInstance, GraphRender, GraphScene, NodeInstance,
};
use crate::world::{crate_ordinals, node_color, tier_scale, WorldGraph};
/// Approx sRGB8 → linear, then a per-tier emissive gain so repos bloom brightest and the
/// hierarchy reads by brightness as well as hue/size. `gain` pushes radiance past 1.0 so
/// the bloom post-stack actually lights up.
fn emissive(c: crate::model::Color, gain: f32) -> [f32; 4] {
let lin = |v: u8| {
let s = v as f32 / 255.0;
s.powf(2.2)
};
[lin(c.r) * gain, lin(c.g) * gain, lin(c.b) * gain, 1.0]
}
/// Fit `[-1,1]²` layout positions into a `w × h` canvas (physical px, y-down), preserving
/// aspect, with a fractional `margin`. Returns the projected centres.
fn project(pos: &[[f32; 2]], w: u32, h: u32, margin: f32) -> Vec<[f32; 2]> {
let (fw, fh) = (w as f32, h as f32);
let s = ((fw.min(fh)) * (1.0 - 2.0 * margin)) * 0.5; // [-1,1] → half-extent px
let (cx, cy) = (fw * 0.5, fh * 0.5);
pos.iter().map(|p| [cx + p[0] * s, cy - p[1] * s]).collect()
}
/// Build the emissive [`GraphScene`] for a laid-out world graph: one bloomed disc per
/// node (radius by tier, colour by repo/crate), plus optional dim edges. `dark` selects
/// the palette surface + background. `edge_cap` bounds the edge batch (0 = no edges) so a
/// 10M-edge world still renders a legible web instead of a muddy fill.
#[must_use]
pub fn world_scene(
w: &WorldGraph,
pos: &[[f32; 2]],
width: u32,
height: u32,
dark: bool,
edge_cap: usize,
) -> GraphScene {
let proj = project(pos, width, height, 0.045);
let ords = crate_ordinals(w);
// Base disc radius scales gently down with node count so a dense world stays legible.
let base_r = (2.6e6 / w.n.max(1) as f32).sqrt().clamp(0.7, 4.5);
let nodes: Vec<NodeInstance> = (0..w.n)
.map(|i| {
let lvl = w.level[i];
let col = node_color(lvl, w.repo_of[i] as usize, ords[i] as usize, dark);
let gain = match lvl {
crate::world::NodeLevel::Repo => 2.3,
crate::world::NodeLevel::Crate => 1.7,
crate::world::NodeLevel::Function => 1.25,
};
NodeInstance {
center: proj[i],
radius: (base_r * tier_scale(lvl)).max(0.6),
_pad: 0.0,
color: emissive(col, gain),
}
})
.collect();
let edges: Vec<EdgeInstance> = if edge_cap == 0 {
Vec::new()
} else {
let stride = (w.edges.len() / edge_cap.max(1)).max(1);
w.edges
.iter()
.step_by(stride)
.filter_map(|&(a, b)| {
let (a, b) = (a as usize, b as usize);
if a >= w.n || b >= w.n {
return None;
}
// Edge hue: the source node's (dimmed) colour, thin, low radiance — a faint
// structural web under the bloomed nodes.
let col = node_color(w.level[a], w.repo_of[a] as usize, ords[a] as usize, dark);
Some(EdgeInstance {
a: proj[a],
b: proj[b],
width: 0.55,
_pad: 0.0,
color: emissive(col, 0.28),
})
})
.collect()
};
let background = if dark { [0.015, 0.017, 0.025, 1.0] } else { [0.96, 0.965, 0.975, 1.0] };
GraphScene { background, nodes, edges }
}
/// **Render the world-graph still**: build the emissive scene and rasterise it headless
/// on the GPU with bloom. Returns the read-back frame (straight RGBA8 + draw counts), or
/// `None` when no usable GPU adapter is present (the caller then reports the honest
/// "needs a device" fallback). Reuses the shared cloud renderer — no new pipeline.
#[must_use]
pub fn render_world_still(
w: &WorldGraph,
pos: &[[f32; 2]],
width: u32,
height: u32,
dark: bool,
edge_cap: usize,
) -> Option<GraphRender> {
let scene = world_scene(w, pos, width, height, dark, edge_cap);
let bloom = BloomParams { intensity: 1.4, sigma: 2.6, ..BloomParams::default() };
render_graph_offscreen(width, height, &scene, bloom)
}
// ── the 3D warehouse view ────────────────────────────────────────────────────
//
// The third view the owner asked for: the repo→crate→function hierarchy spread in **3D**
// and rendered on the GPU without the grey-blob look. It reuses the SAME instanced cloud
// renderer (facett-core graphcloud — the shared 3D cloud, per its own docs) and the world
// graph's categorical colour; the 3D is a nested-spherical galaxy + a CPU orthographic
// projection with depth cues (nearer = larger + brighter), exactly the approach
// `facett_graph3d` uses. No second renderer, no fabricated data.
const TAU: f32 = std::f32::consts::TAU;
// The camera + placement math (`project_azimuth`, `fib_sphere`, `galaxy_3d`)
// moved to the UNGATED `crate::camera3d` so CPU-side hosts share the one
// implementation instead of porting it past this module's `gpu` gate; re-exported
// here so every existing `world_still::*` consumer keeps compiling.
pub use crate::camera3d::{fib_sphere, galaxy_3d, project_azimuth};
/// Rotate a `[-1,1]³` point by `yaw` (about Y) then `pitch` (about X) and orthographically
/// project to a `w×h` canvas. Returns `(screen_xy, depth)` where depth in `[-1,1]` (+ =
/// nearer) drives the size/brightness cue. Camera math is [`project_azimuth`] (shared with
/// live host views); this only maps the world-space `[x, y]` into y-down canvas pixels.
fn project_3d(p: [f32; 3], yaw: f32, pitch: f32, w: u32, h: u32) -> ([f32; 2], f32) {
let [x1, y2, z2] = project_azimuth(p, yaw, pitch);
let s = (w.min(h) as f32) * 0.5 * 0.9;
([w as f32 * 0.5 + x1 * s, h as f32 * 0.5 - y2 * s], z2)
}
/// **Render the 3D warehouse still**: a nested-spherical galaxy projected with depth cues
/// and rasterised headless on the GPU with bloom (the shared cloud renderer). `yaw`/`pitch`
/// orbit the camera. `None` when no GPU adapter is present (honest fallback).
#[must_use]
#[allow(clippy::too_many_arguments)] // public signature hosts call: kept as is
pub fn render_world_still_3d(
w: &WorldGraph,
pos3: &[[f32; 3]],
width: u32,
height: u32,
dark: bool,
yaw: f32,
pitch: f32,
edge_cap: usize,
) -> Option<GraphRender> {
let ords = crate_ordinals(w);
let base_r = (2.6e6 / w.n.max(1) as f32).sqrt().clamp(0.7, 4.5);
let proj: Vec<([f32; 2], f32)> =
pos3.iter().map(|&p| project_3d(p, yaw, pitch, width, height)).collect();
let nodes: Vec<NodeInstance> = (0..w.n)
.map(|i| {
let lvl = w.level[i];
let (xy, depth) = proj[i];
// Depth cue: nearer (+depth) → larger + brighter.
let dcue = 0.6 + 0.4 * (depth * 0.5 + 0.5);
let col = node_color(lvl, w.repo_of[i] as usize, ords[i] as usize, dark);
let gain = dcue * match lvl {
crate::world::NodeLevel::Repo => 2.4,
crate::world::NodeLevel::Crate => 1.7,
crate::world::NodeLevel::Function => 1.25,
};
NodeInstance {
center: xy,
radius: (base_r * tier_scale(lvl) * dcue).max(0.6),
_pad: 0.0,
color: emissive(col, gain),
}
})
.collect();
let edges: Vec<EdgeInstance> = if edge_cap == 0 {
Vec::new()
} else {
let stride = (w.edges.len() / edge_cap.max(1)).max(1);
w.edges
.iter()
.step_by(stride)
.filter_map(|&(a, b)| {
let (a, b) = (a as usize, b as usize);
if a >= w.n || b >= w.n {
return None;
}
let col = node_color(w.level[a], w.repo_of[a] as usize, ords[a] as usize, dark);
Some(EdgeInstance { a: proj[a].0, b: proj[b].0, width: 0.5, _pad: 0.0, color: emissive(col, 0.24) })
})
.collect()
};
let background = if dark { [0.012, 0.014, 0.022, 1.0] } else { [0.95, 0.955, 0.965, 1.0] };
let scene = GraphScene { background, nodes, edges };
let bloom = BloomParams { intensity: 1.4, sigma: 2.6, ..BloomParams::default() };
let _ = TAU;
render_graph_offscreen(width, height, &scene, bloom)
}
// ── the MEGA composite still: dense RASTER + crisp SVG TEXT burned in ─────────
//
// The owner's "SVG + JPEG2000" mega tier: one gigantic composite of the whole code-universe
// graph — a dense high-res raster of the node/edge cloud with a crisp *vector* text layer
// (repo labels at cluster centroids, a title, a colour↔repo legend, node/edge counts, a
// timestamp) burned OVER it into ONE image.
//
// FORMAT — an honest, PURE-RUST note. The shared offscreen cloud renderer clamps to wgpu
// downlevel limits (max texture 2048²), so the mega canvas is rendered in overlapping
// tiles and stitched ([`render_world_still_mega`]). The text layer is a real SVG
// ([`world_text_svg`]) rasterised + composited by resvg/usvg/tiny_skia — the same pure-Rust
// stack facett-docview uses (no pdfium/inkscape/native libs). The composite is saved as
// **PNG**, NOT JPEG2000: there is **no pure-Rust JPEG2000 encoder** on crates.io — every JP2
// path (openjpeg-sys, jpeg2k, …) binds the C OpenJPEG library, which the pure-Rust law here
// forbids. PNG is the lossless, pure-Rust, offline-cacheable high-quality raster; the crisp
// text also survives as the separate scalable `.svg`. We do NOT shell out to any C imaging
// tool.
/// Escape a string for inclusion in XML text / attribute content.
fn xml_escape(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 8);
for c in s.chars() {
match c {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'"' => out.push_str("""),
'\'' => out.push_str("'"),
_ => out.push(c),
}
}
out
}
/// Tuning for the [`world_text_svg`] crisp vector text layer.
#[derive(Clone, Debug)]
pub struct MegaLabels {
/// The big title, top-left of the canvas.
pub title: String,
/// Dark surface (light text) vs light surface (dark text) — match the raster's `dark`.
pub dark: bool,
/// Node count (rendered into the meta line).
pub node_count: usize,
/// Edge count (rendered into the meta line).
pub edge_count: usize,
/// Whether the world hierarchy is synthetic (honest provenance in the meta line).
pub synthetic: bool,
/// A free-form timestamp string (e.g. an ISO date) for the meta line.
pub timestamp: String,
/// Cap on how many repo cluster labels are drawn (the largest-by-node-count repos win),
/// so a 1M-node world with thousands of repos stays legible instead of a wall of text.
pub max_repo_labels: usize,
}
impl Default for MegaLabels {
fn default() -> Self {
Self {
title: "facett — the code-universe graph".into(),
dark: true,
node_count: 0,
edge_count: 0,
synthetic: true,
timestamp: String::new(),
max_repo_labels: 80,
}
}
}
/// One repo's aggregate for the label/legend layer: its name, colour slot, projected
/// cluster centroid (mega-canvas px) and node population.
struct RepoAgg {
slot: usize,
name: String,
cx: f32,
cy: f32,
count: usize,
}
/// Aggregate per-repo centroid + population from the SAME projection the raster uses
/// (`project(.., 0.045)`), so labels land on their clusters.
fn repo_aggregates(w: &WorldGraph, pos: &[[f32; 2]], width: u32, height: u32) -> Vec<RepoAgg> {
let proj = project(pos, width, height, 0.045);
let rc = w.repo_count();
let mut sx = vec![0.0f64; rc];
let mut sy = vec![0.0f64; rc];
let mut cnt = vec![0usize; rc];
for (p, &ri) in proj.iter().zip(w.repo_of.iter()) {
let r = ri as usize;
if r < rc {
sx[r] += p[0] as f64;
sy[r] += p[1] as f64;
cnt[r] += 1;
}
}
(0..rc)
.filter(|&r| cnt[r] > 0)
.map(|r| RepoAgg {
slot: r,
name: w.repos.get(r).cloned().unwrap_or_else(|| format!("repo{r}")),
cx: (sx[r] / cnt[r] as f64) as f32,
cy: (sy[r] / cnt[r] as f64) as f32,
count: cnt[r],
})
.collect()
}
/// **Build the crisp vector TEXT layer** as a standalone SVG string sized to the mega
/// canvas (`width × height` px, transparent background). Contains: a title + meta panel
/// (node/edge counts, timestamp, provenance, the honest raster-format note), a colour↔repo
/// legend (swatch + name for the largest repos), and a repo label anchored at each repo
/// cluster's centroid with a contrasting halo (`paint-order: stroke`) so it reads over the
/// bright node cloud. Pure string building — no font/GPU dependency — so it is always
/// available (the composite burn-in that rasterises it needs `gpu`). Also kept as the
/// separate `.svg` so the text stays scalable/searchable at any zoom.
#[must_use]
pub fn world_text_svg(
w: &WorldGraph,
pos: &[[f32; 2]],
width: u32,
height: u32,
opts: &MegaLabels,
) -> String {
let mut repos = repo_aggregates(w, pos, width, height);
// Largest repos first — both the legend and (when capped) the drawn labels.
repos.sort_by(|a, b| b.count.cmp(&a.count).then(a.slot.cmp(&b.slot)));
let dark = opts.dark;
let text_col = if dark { "#e9edf6" } else { "#12151c" };
let halo_col = if dark { "#05060a" } else { "#f6f7fb" };
let panel_fill = if dark { "rgba(8,10,16,0.72)" } else { "rgba(250,251,253,0.82)" };
let panel_stroke = if dark { "rgba(150,165,200,0.35)" } else { "rgba(30,40,70,0.30)" };
let muted = if dark { "#9fb0cc" } else { "#4a5568" };
let fw = width as f32;
// Font sizes scale with the canvas so text reads at any mega resolution.
let title_fs = (fw / 30.0).round().max(14.0);
let meta_fs = (fw / 78.0).round().max(9.0);
let legend_fs = (fw / 82.0).round().max(9.0);
let label_fs = (fw / 96.0).round().max(9.0);
let pad = (fw / 90.0).round().max(8.0);
let legend_n = repos.len().min(24);
let sw = legend_fs * 1.15; // swatch size
let line_h = legend_fs * 1.55;
let title_h = title_fs * 1.2;
let meta_h = meta_fs * 1.35 * 3.0; // three meta lines
let panel_x = pad;
let panel_y = pad;
let panel_w = (fw * 0.34).max(title_fs * 12.0).min(fw - 2.0 * pad);
let panel_h = title_h + meta_h + legend_fs * 1.4 + legend_n as f32 * line_h + pad * 2.5;
let mut s = String::with_capacity(4096 + repos.len() * 160);
s.push_str(&format!(
"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{width}\" height=\"{height}\" \
viewBox=\"0 0 {width} {height}\" font-family=\"DejaVu Sans, Liberation Sans, sans-serif\">\n"
));
// Info panel backing.
s.push_str(&format!(
"<rect x=\"{:.1}\" y=\"{:.1}\" width=\"{:.1}\" height=\"{:.1}\" rx=\"{:.1}\" \
fill=\"{panel_fill}\" stroke=\"{panel_stroke}\" stroke-width=\"1.5\"/>\n",
panel_x, panel_y, panel_w, panel_h, meta_fs * 0.8
));
let tx = panel_x + pad;
let mut ty = panel_y + pad + title_fs;
// Title.
s.push_str(&format!(
"<text x=\"{:.1}\" y=\"{:.1}\" font-size=\"{:.1}\" font-weight=\"700\" fill=\"{text_col}\">{}</text>\n",
tx, ty, title_fs, xml_escape(&opts.title)
));
// Meta lines.
ty += title_fs * 0.35 + meta_fs * 1.5;
let prov = if opts.synthetic { "synthetic hierarchy" } else { "live warehouse data" };
for line in [
format!("{} nodes · {} edges · {prov}", opts.node_count, opts.edge_count),
format!("{} repos · raster: PNG (pure Rust — no pure-Rust JPEG2000 encoder)", w.repo_count()),
if opts.timestamp.is_empty() {
"text layer: crisp SVG (resvg/usvg/tiny_skia)".to_string()
} else {
format!("{} · text layer: crisp SVG", opts.timestamp)
},
] {
s.push_str(&format!(
"<text x=\"{:.1}\" y=\"{:.1}\" font-size=\"{:.1}\" fill=\"{muted}\">{}</text>\n",
tx, ty, meta_fs, xml_escape(&line)
));
ty += meta_fs * 1.35;
}
// Legend heading + rows.
ty += legend_fs * 0.8;
s.push_str(&format!(
"<text x=\"{:.1}\" y=\"{:.1}\" font-size=\"{:.1}\" font-weight=\"700\" fill=\"{text_col}\">legend — repo ↔ colour</text>\n",
tx, ty, legend_fs
));
ty += legend_fs * 0.8;
for ra in repos.iter().take(legend_n) {
let col = crate::world::repo_hue(ra.slot, dark);
ty += line_h;
s.push_str(&format!(
"<rect x=\"{:.1}\" y=\"{:.1}\" width=\"{:.1}\" height=\"{:.1}\" rx=\"{:.1}\" fill=\"#{:02x}{:02x}{:02x}\"/>\n",
tx, ty - sw * 0.85, sw, sw, sw * 0.2, col.r, col.g, col.b
));
s.push_str(&format!(
"<text x=\"{:.1}\" y=\"{:.1}\" font-size=\"{:.1}\" fill=\"{text_col}\">{} · {}</text>\n",
tx + sw * 1.5, ty, legend_fs, xml_escape(&ra.name), ra.count
));
}
// Repo cluster labels at centroids (largest repos, capped), with a halo.
for ra in repos.iter().take(opts.max_repo_labels) {
s.push_str(&format!(
"<text x=\"{:.1}\" y=\"{:.1}\" font-size=\"{:.1}\" font-weight=\"600\" \
text-anchor=\"middle\" fill=\"{text_col}\" stroke=\"{halo_col}\" stroke-width=\"{:.2}\" \
paint-order=\"stroke\" stroke-linejoin=\"round\">{}</text>\n",
ra.cx, ra.cy, label_fs, label_fs * 0.18, xml_escape(&ra.name)
));
}
s.push_str("</svg>\n");
s
}
/// A finished MEGA raster: the stitched straight-RGBA8 buffer at mega resolution plus the
/// tiling + draw counts (the render-proof asserts on these as DATA).
#[cfg(feature = "gpu")]
pub struct MegaRaster {
pub width: u32,
pub height: u32,
/// Straight RGBA8, row-major (`width*height*4`).
pub rgba: Vec<u8>,
/// Scene node instances (the full projected cloud).
pub nodes: usize,
/// Scene edge instances.
pub edges: usize,
/// Number of GPU tiles stitched.
pub tiles: u32,
}
/// Clone a scene, shift every instance by `(-ox, -oy)`, and CULL to the render region
/// `[0,rw]×[0,rh]` (plus a small margin) — so each tile only uploads the handful of
/// instances that actually touch it (a 1M cloud stays cheap per tile). The halo is what
/// keeps bloom continuous across tile seams: near-region instances outside the crop still
/// contribute their glow.
#[cfg(feature = "gpu")]
fn shift_and_cull(scene: &GraphScene, ox: f32, oy: f32, rw: f32, rh: f32) -> GraphScene {
let m = 4.0f32; // margin beyond node radius already covered by the halo
let inside = |x: f32, y: f32, r: f32| {
x >= -(r + m) && x <= rw + r + m && y >= -(r + m) && y <= rh + r + m
};
let nodes: Vec<NodeInstance> = scene
.nodes
.iter()
.filter_map(|n| {
let c = [n.center[0] - ox, n.center[1] - oy];
inside(c[0], c[1], n.radius).then_some(NodeInstance { center: c, ..*n })
})
.collect();
let edges: Vec<EdgeInstance> = scene
.edges
.iter()
.filter_map(|e| {
let a = [e.a[0] - ox, e.a[1] - oy];
let b = [e.b[0] - ox, e.b[1] - oy];
// Keep if the segment's bbox overlaps the (margined) region.
let (lo_x, hi_x) = (a[0].min(b[0]), a[0].max(b[0]));
let (lo_y, hi_y) = (a[1].min(b[1]), a[1].max(b[1]));
(hi_x >= -m && lo_x <= rw + m && hi_y >= -m && lo_y <= rh + m)
.then_some(EdgeInstance { a, b, ..*e })
})
.collect();
GraphScene { background: scene.background, nodes, edges }
}
/// **Render the MEGA world-graph raster** — the dense colored node/edge cloud at a
/// resolution far beyond the GPU's 2048² offscreen cap, by rendering overlapping tiles
/// and stitching them. `tile` is the interior tile edge and `halo` the per-side bleed
/// (both in px); `tile + 2*halo` MUST be ≤ 2048 (the downlevel max texture). Every mega
/// pixel is written by exactly one tile's cropped interior, so there are no gaps; the halo
/// keeps bloom seamless. Returns `None` when no GPU adapter is present (honest fallback).
#[cfg(feature = "gpu")]
#[must_use]
#[allow(clippy::too_many_arguments, reason = "explicit mega/tile/halo knobs read clearer than a struct here")]
pub fn render_world_still_mega(
w: &WorldGraph,
pos: &[[f32; 2]],
mega_w: u32,
mega_h: u32,
dark: bool,
edge_cap: usize,
tile: u32,
halo: u32,
) -> Option<MegaRaster> {
assert!(tile + 2 * halo <= 2048, "tile+2*halo must fit the 2048 downlevel cap");
let scene = world_scene(w, pos, mega_w, mega_h, dark, edge_cap);
let bloom = BloomParams { intensity: 1.4, sigma: 2.6, ..BloomParams::default() };
let mut out = vec![0u8; mega_w as usize * mega_h as usize * 4];
let (nx, ny) = (mega_w.div_ceil(tile), mega_h.div_ceil(tile));
let mut tiles = 0u32;
for ty in 0..ny {
for tx in 0..nx {
let (x0, y0) = (tx * tile, ty * tile);
let (tw, th) = ((mega_w - x0).min(tile), (mega_h - y0).min(tile));
let (rw, rh) = (tw + 2 * halo, th + 2 * halo);
let (ox, oy) = (x0 as f32 - halo as f32, y0 as f32 - halo as f32);
let sub = shift_and_cull(&scene, ox, oy, rw as f32, rh as f32);
let r = render_graph_offscreen(rw, rh, &sub, bloom)?;
// Copy the tile's interior (offset by halo) into the mega buffer at (x0,y0).
for row in 0..th {
let src = (((row + halo) * rw + halo) * 4) as usize;
let dst = (((y0 + row) * mega_w + x0) * 4) as usize;
let n = (tw * 4) as usize;
out[dst..dst + n].copy_from_slice(&r.rgba[src..src + n]);
}
tiles += 1;
}
}
Some(MegaRaster {
width: mega_w,
height: mega_h,
rgba: out,
nodes: scene.nodes.len(),
edges: scene.edges.len(),
tiles,
})
}
/// **Burn the crisp SVG text layer OVER an RGBA raster** (in place). Rasterises `svg` at
/// the raster's exact size with resvg/usvg/tiny_skia (pure Rust; a fontdb populated from
/// system fonts so `<text>` shapes) and alpha-composites the premultiplied result over the
/// opaque raster. Returns `false` if the SVG failed to parse or the pixmap could not be
/// allocated. `rgba` must be `width*height*4` straight RGBA8.
#[cfg(feature = "gpu")]
#[must_use]
pub fn burn_svg_over_rgba(rgba: &mut [u8], width: u32, height: u32, svg: &str) -> bool {
use resvg::{tiny_skia, usvg};
let mut db = usvg::fontdb::Database::new();
db.load_system_fonts();
if db.is_empty() {
// Airgap safety: no system fonts → usvg would drop every <text>. Report honestly.
eprintln!("burn_svg_over_rgba: no system fonts found — text layer would be empty");
}
let opts = usvg::Options { fontdb: std::sync::Arc::new(db), ..usvg::Options::default() };
let tree = match usvg::Tree::from_str(svg, &opts) {
Ok(t) => t,
Err(e) => {
eprintln!("burn_svg_over_rgba: SVG parse failed: {e}");
return false;
}
};
let Some(mut pm) = tiny_skia::Pixmap::new(width, height) else { return false };
resvg::render(&tree, tiny_skia::Transform::identity(), &mut pm.as_mut());
// tiny_skia pixmaps are premultiplied sRGB; alpha-over onto the opaque raster:
// out = src_premul + dst * (255 - a) / 255.
for (d, sc) in rgba.as_chunks_mut::<4>().0.iter_mut().zip(pm.data().as_chunks::<4>().0) {
let a = sc[3] as u32;
if a == 0 {
continue;
}
let inv = 255 - a;
d[0] = (sc[0] as u32 + d[0] as u32 * inv / 255).min(255) as u8;
d[1] = (sc[1] as u32 + d[1] as u32 * inv / 255).min(255) as u8;
d[2] = (sc[2] as u32 + d[2] as u32 * inv / 255).min(255) as u8;
d[3] = 255;
}
true
}
#[cfg(test)]
mod tests {
use super::*;
use crate::world::{layout_world, Lod, LayoutBudget};
#[test]
fn galaxy_3d_spreads_in_all_three_dims() {
let w = WorldGraph::synthetic(4_000, Lod::Functions, false);
let p = galaxy_3d(&w);
assert_eq!(p.len(), w.n);
// Real extent on every axis (a true 3D spread, not a flat sheet or a point).
for d in 0..3 {
let lo = p.iter().map(|q| q[d]).fold(f32::MAX, f32::min);
let hi = p.iter().map(|q| q[d]).fold(f32::MIN, f32::max);
assert!(hi - lo > 1.0, "axis {d} spreads (extent {:.3})", hi - lo);
}
// Distinct positions (no stacking).
let mut seen = std::collections::BTreeSet::new();
for q in &p {
seen.insert((q[0].to_bits(), q[1].to_bits(), q[2].to_bits()));
}
assert!(seen.len() as f32 > 0.99 * w.n as f32, "3D positions are distinct");
}
#[test]
fn project_azimuth_spins_about_the_vertical_axis_without_touching_the_layout() {
// A galaxy laid out ONCE (the cached layout the live spin must never rebuild).
let w = WorldGraph::synthetic(2_000, Lod::Functions, false);
let pos3 = galaxy_3d(&w);
let snapshot = pos3.clone();
// Azimuth about Y leaves each point's HEIGHT (y) invariant — it is the vertical spin
// axis — while the projected x/z sweep. Pitch = 0 so world-y == layout-y exactly.
for &p in pos3.iter().take(64) {
let a = project_azimuth(p, 0.0, 0.0);
let b = project_azimuth(p, 0.9, 0.0);
assert!((a[1] - p[1]).abs() < 1e-5, "yaw preserves the vertical axis");
assert!((b[1] - p[1]).abs() < 1e-5, "yaw preserves the vertical axis");
// The camera actually turned: the flattened x moved (unless the point is on-axis).
if p[0].hypot(p[2]) > 1e-3 {
assert!((a[0] - b[0]).abs() > 1e-4, "advancing yaw sweeps the projection");
}
}
// Rotating the CAMERA never mutated the (cached) LAYOUT.
assert_eq!(pos3, snapshot, "projection is read-only over the galaxy_3d layout");
}
#[test]
fn text_svg_carries_repo_labels_legend_and_counts() {
let w = WorldGraph::synthetic(4_000, Lod::Functions, true);
let (pos, _) = layout_world(&w, &LayoutBudget::default());
let opts = MegaLabels {
title: "facett — code universe".into(),
node_count: w.n,
edge_count: w.edges.len(),
synthetic: w.synthetic_flag(),
timestamp: "2026-07-24".into(),
..MegaLabels::default()
};
let svg = world_text_svg(&w, &pos, 2000, 2000, &opts);
// Well-formed, sized, transparent overlay.
assert!(svg.starts_with("<svg"), "is an SVG");
assert!(svg.contains("viewBox=\"0 0 2000 2000\""), "sized to the mega canvas");
// The crisp TEXT: title, the synthetic repo names, legend + honest format note.
assert!(svg.contains("facett — code universe"));
assert!(svg.contains(">repo0<"), "repo0 label present");
assert!(svg.contains(">repo1<") || svg.contains("repo1 ·"), "repo1 present (label/legend)");
assert!(svg.contains("legend — repo ↔ colour"));
assert!(svg.contains(&format!("{} nodes", w.n)));
assert!(svg.contains("no pure-Rust JPEG2000 encoder"), "honest format note burned in");
// A real label per repo cluster (capped) — at least a handful of <text> runs.
assert!(svg.matches("<text").count() > 8, "many text runs: {}", svg.matches("<text").count());
}
#[test]
fn scene_lowers_every_node_and_colours_by_tier() {
let w = WorldGraph::synthetic(4_000, Lod::Functions, true);
let (pos, _) = layout_world(&w, &LayoutBudget::default());
let scene = world_scene(&w, &pos, 1024, 1024, true, 20_000);
assert_eq!(scene.nodes.len(), w.n, "every node lowered to a disc");
// Distinct fills across tiers/repos — NOT one grey (the grey-blob red-when-broken).
let mut set = std::collections::BTreeSet::new();
for nd in &scene.nodes {
let key = (
(nd.color[0] * 255.0) as u32,
(nd.color[1] * 255.0) as u32,
(nd.color[2] * 255.0) as u32,
);
set.insert(key);
}
assert!(set.len() > 8, "world is multi-coloured (>{} distinct fills), not grey blobs", set.len());
// Nodes are spread — projected centres occupy a real 2-D extent, not one point.
let xs: Vec<f32> = scene.nodes.iter().map(|n| n.center[0]).collect();
let ys: Vec<f32> = scene.nodes.iter().map(|n| n.center[1]).collect();
let sx = xs.iter().cloned().fold(f32::MAX, f32::min)..xs.iter().cloned().fold(f32::MIN, f32::max);
let sy = ys.iter().cloned().fold(f32::MAX, f32::min)..ys.iter().cloned().fold(f32::MIN, f32::max);
assert!((sx.end - sx.start) > 400.0 && (sy.end - sy.start) > 400.0, "nodes fill the canvas");
}
}