#!/usr/bin/env python3 """Procedurally bake the Nebula welcome art into nebula_fetch_art.rs. v3: SATURN, per the user's reference image (2026-07-04): - planet ball in three latitude bands: `o/0` indigo top, `*` cyan belt, `~` magenta south — dense fill, hard round silhouette - a golden `=` ring tilted across the ball: the front arc passes IN FRONT of the planet (overwrites it), the back arc hides behind the ball and only shows outside the silhouette - sparse starfield around it: `+` `.` in white / gold / violet - 62 cols wide (welcome.rs info_col = 65 depends on this), 19 rows, cell aspect ~2.6:1 corrected via ASPECT Regenerate: `python tools/gen_fetch_art.py` (stdout shows a true-color ANSI preview; D:\\SHARE\\nebula_fetch_preview.html the same in a browser). """ import math OUT_RS = r"D:\temp_build\nebula\nebula_app\src\window_context\nebula_fetch_art.rs" OUT_HTML = r"D:\SHARE\nebula_fetch_preview.html" W, H = 62, 19 ASPECT = 2.6 # terminal cell height/width ratio # --- planet --- CX, CY = 27.0, 9.4 # ball center (cells); slightly left so the ring can sweep right R = 16.0 # ball radius in x-units BELT_TOP = -0.18 # latitude (normalized dy) where the cyan belt starts BELT_BOT = 0.30 # where the magenta south begins # --- ring --- TILT = math.radians(14.0) # ring plane tilt: lower-left -> upper-right RING_A = 31.0 # ring ellipse semi-major (x-units) RING_B = 8.6 # semi-minor before aspect correction RING_HALF = 0.20 # band half-thickness in ellipse-e units (e in [1-h, 1+h]) RING_EDGE = 0.12 # |e-1| beyond this renders `-` instead of `=` # --- starfield --- STAR_P = 0.022 # per-cell probability outside planet/ring # --- palette (sampled from the reference) --- TERMBG = (13, 15, 24) COL_TOP0 = (126, 138, 232) # indigo `0/o` north, upper rows COL_TOP1 = (104, 116, 210) # ... slightly darker toward the belt COL_BELT = (86, 202, 218) # cyan `*` belt COL_SOUTH = (198, 112, 214) # magenta `~` south COL_RING = (214, 170, 78) # golden `=` STAR_COLS = [(212, 216, 230), (214, 170, 78), (150, 128, 224)] STAR_CHARS = ["+", ".", "."] def hash01(*args): """Deterministic per-cell noise in [0,1) — bake must be reproducible.""" h = 2166136261 for a in args: h ^= int(a) & 0xFFFFFFFF h = (h * 16777619) & 0xFFFFFFFF return ((h >> 8) & 0xFFFF) / 65536.0 def planet_at(x, y): """Inside the ball? -> (char, color) by latitude band.""" dx = x - CX dy = (y - CY) * ASPECT if dx * dx + dy * dy > R * R: return None lat = dy / R # -1 (north pole) .. 1 (south pole) if lat < BELT_TOP: # North: mixed o/0 rows, indigo, slightly darker toward the belt. t = (lat + 1.0) / max(1e-6, BELT_TOP + 1.0) # 0 at pole -> 1 at belt col = tuple(round(COL_TOP0[i] + (COL_TOP1[i] - COL_TOP0[i]) * t) for i in range(3)) ch = "0" if hash01(x * 31, y * 57) < 0.55 else "o" return ch, col if lat < BELT_BOT: return "*", COL_BELT return "~", COL_SOUTH def ring_at(x, y): """On the ring band? -> (in_front, char, color). Ellipse in tilted plane.""" dx = x - CX dy = (y - CY) * ASPECT # Rotate into the ring plane (screen y points down, tilt is CCW). u = dx * math.cos(TILT) - dy * math.sin(TILT) v = dx * math.sin(TILT) + dy * math.cos(TILT) e = math.hypot(u / RING_A, v / RING_B) if abs(e - 1.0) > RING_HALF: return None # Band core reads `=`, the thin inner/outer fringes read `-`. ch = "=" if abs(e - 1.0) <= RING_EDGE else "-" # v > 0 = the arc dipping below the ring plane on screen -> in front. return v > 0.0, ch, COL_RING grid = [[None] * W for _ in range(H)] # (char, (r,g,b)) or None for y in range(H): for x in range(W): planet = planet_at(x, y) ring = ring_at(x, y) if ring is not None: in_front, ring_ch, ring_col = ring if in_front or planet is None: # Front arc always wins; back arc only outside the ball. grid[y][x] = (ring_ch, ring_col) continue if planet is not None: grid[y][x] = planet continue # Starfield. Spatial-hash mix of both coords per word, otherwise # nearby rows correlate and stars line up in fake columns. rnd = hash01(x * 73856093 ^ y * 19349663, y * 83492791 ^ x * 2971215073) if rnd < STAR_P: pick = int(hash01(x * 419 + y, y * 631 + x) * len(STAR_COLS)) % len(STAR_COLS) grid[y][x] = (STAR_CHARS[pick], STAR_COLS[pick]) # --- emit --- rs_lines, html_lines, filled = [], [], 0 for y in range(H): row_rs, row_html = [], [] for x in range(W): cell = grid[y][x] if cell is None: row_rs.append(" ") row_html.append(" ") else: filled += 1 ch, (r, g, b) = cell row_rs.append(f"\\x1b[38;2;{r};{g};{b}m{ch}") row_html.append(f'{ch}') line = "".join(row_rs).rstrip() if line: line += "\\x1b[0m" rs_lines.append(line) html_lines.append("".join(row_html).rstrip()) header = ( "//! Nebula welcome-screen fetch art: Saturn (user reference 2026-07-04).\n" "//! Latitude-banded ball (o/0 indigo, * cyan, ~ magenta), golden tilted\n" "//! `=` ring with front/back occlusion, sparse starfield.\n" "//! Regenerate with `python tools/gen_fetch_art.py`.\n\n" "#[cfg(windows)]\n" "pub const NEBULA_STAR_ART: &[&str] = &[\n" ) body = "".join(f' "{l}",\n' for l in rs_lines) with open(OUT_RS, "w", encoding="utf-8") as f: f.write(header + body + "];\n") with open(OUT_HTML, "w", encoding="utf-8") as f: f.write( "" "
"
        + "\n".join(html_lines)
        + "
" ) print(f"baked {W}x{H}, filled {filled} ({filled * 100 // (W * H)}%)") # True-color ANSI preview straight to the terminal. for y in range(H): out = [] for x in range(W): cell = grid[y][x] if cell is None: out.append(" ") else: ch, (r, g, b) = cell out.append(f"\x1b[38;2;{r};{g};{b}m{ch}") print("".join(out).rstrip() + "\x1b[0m")