Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
321 lines
11 KiB
Python
321 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
GOD Frame iOS splash generator (ADR-0027).
|
|
|
|
Native pixel art at 240x240 in 16Neo palette, Tor sprite from docs/16neo/sprites.
|
|
Outputs:
|
|
- splash_native.png 240x240 (true pixel art)
|
|
- splash.png 1920x1920 (8x nearest upscale, source for flutter_native_splash)
|
|
"""
|
|
from PIL import Image
|
|
import os, random
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
SPRITE_DIR = os.path.join(ROOT, "docs/16neo/sprites")
|
|
OUT_DIR = os.path.join(ROOT, "god_frame/assets/splash")
|
|
os.makedirs(OUT_DIR, exist_ok=True)
|
|
|
|
# 16Neo palette
|
|
P = {
|
|
"bg": (26, 26, 46), # #1a1a2e deep night
|
|
"bg2": (45, 45, 68), # #2d2d44 dark wall
|
|
"shadow": (74, 74, 104), # #4a4a68
|
|
"midpurple": (107, 107, 141), # #6b6b8d
|
|
"lightgrey": (152, 152, 181), # #9898b5
|
|
"pale": (200, 200, 216), # #c8c8d8
|
|
"white": (245, 245, 250), # #f5f5fa
|
|
"darkblue": (29, 53, 87), # #1d3557
|
|
"ocean": (69, 123, 157), # #457b9d
|
|
"sky": (114, 180, 212), # #72b4d4
|
|
"lightsky": (168, 218, 220), # #a8dadc
|
|
"darkforest": (27, 67, 50), # #1b4332
|
|
"forest": (45, 106, 79), # #2d6a4f
|
|
"fresh": (82, 183, 136), # #52b788
|
|
"lightgreen": (149, 213, 178), # #95d5b2
|
|
"red": (192, 57, 43), # #c0392b
|
|
"lightred": (231, 76, 60), # #e74c3c
|
|
"gold": (212, 160, 23), # #d4a017
|
|
"amber": (243, 156, 18), # #f39c12
|
|
"orange": (230, 126, 34), # #e67e22
|
|
"purple": (192, 132, 252), # #c084fc
|
|
"pink": (255, 107, 157), # #ff6b9d
|
|
"yellow": (251, 191, 36), # #fbbf24
|
|
}
|
|
|
|
W, H = 240, 240
|
|
img = Image.new("RGB", (W, H), P["bg"])
|
|
px = img.load()
|
|
|
|
|
|
def setpx(x, y, c):
|
|
if 0 <= x < W and 0 <= y < H:
|
|
px[x, y] = c
|
|
|
|
|
|
def hline(x1, x2, y, c):
|
|
for x in range(x1, x2 + 1):
|
|
setpx(x, y, c)
|
|
|
|
|
|
# ---------- Pointy-top hex helper (clean pixel-art) ----------
|
|
def pointy_hex(cx, cy, hw, body_h, color):
|
|
"""
|
|
Pointy-top hex with half-width hw (max), and body_h rows at full width.
|
|
Tapering uses 2-pixel-per-row steps for clean diagonal look.
|
|
Returns the list of (x_left, x_right, y) scanlines used.
|
|
"""
|
|
rows = []
|
|
# Top tapering: hw_step from 1, 3, 5, ..., (2k-1) until reaches hw
|
|
# Each row: half-width = 1 + 2*i (clamped to hw)
|
|
cur = 1
|
|
top_taper = []
|
|
while cur < hw:
|
|
top_taper.append(cur)
|
|
cur += 2
|
|
# Add the row that hits exactly hw if cur != hw (clamp)
|
|
if not top_taper or top_taper[-1] != hw:
|
|
# Need to land on hw
|
|
pass
|
|
n_taper = len(top_taper)
|
|
|
|
# Top tapering rows
|
|
for i, half in enumerate(top_taper):
|
|
y = cy - body_h // 2 - (n_taper - i)
|
|
rows.append((cx - half, cx + half, y))
|
|
# Middle body rows at full hw
|
|
for i in range(body_h):
|
|
y = cy - body_h // 2 + i
|
|
rows.append((cx - hw, cx + hw, y))
|
|
# Bottom tapering (mirror)
|
|
for i, half in enumerate(reversed(top_taper)):
|
|
y = cy + body_h // 2 + (i + 1)
|
|
rows.append((cx - half, cx + half, y))
|
|
|
|
if color is not None:
|
|
for x_l, x_r, y in rows:
|
|
hline(x_l, x_r, y, color)
|
|
return rows
|
|
|
|
|
|
def hex_outline(rows, color, thickness=1):
|
|
"""Draw outline along the perimeter rows of a hex."""
|
|
if not rows:
|
|
return
|
|
# Top edge of first row
|
|
x_l, x_r, y = rows[0]
|
|
hline(x_l, x_r, y, color)
|
|
# Bottom edge of last row
|
|
x_l, x_r, y = rows[-1]
|
|
hline(x_l, x_r, y, color)
|
|
# Left/right edges
|
|
for i, (x_l, x_r, y) in enumerate(rows):
|
|
# Left edge segment
|
|
if i == 0:
|
|
continue
|
|
prev_l, prev_r, prev_y = rows[i - 1]
|
|
# Connect prev_l..x_l on this row's y if shrinking, on prev's y if growing
|
|
if x_l < prev_l:
|
|
# diagonal expanding left
|
|
hline(x_l, prev_l - 1, y, color)
|
|
elif x_l > prev_l:
|
|
hline(prev_l, x_l - 1, prev_y, color)
|
|
else:
|
|
setpx(x_l, y, color)
|
|
# Right edge
|
|
if x_r > prev_r:
|
|
hline(prev_r + 1, x_r, y, color)
|
|
elif x_r < prev_r:
|
|
hline(x_r + 1, prev_r, prev_y, color)
|
|
else:
|
|
setpx(x_r, y, color)
|
|
|
|
|
|
# ---------- Background: starfield ----------
|
|
random.seed(7)
|
|
star_pos = []
|
|
for _ in range(80):
|
|
x = random.randint(0, W - 1)
|
|
y = random.randint(0, H - 1)
|
|
star_pos.append((x, y))
|
|
for x, y in star_pos:
|
|
setpx(x, y, P["midpurple"])
|
|
for x, y in random.sample(star_pos, 16):
|
|
setpx(x, y, P["lightsky"])
|
|
# A few sparkle stars (3px cross)
|
|
sparkles = random.sample(star_pos, 4)
|
|
for x, y in sparkles:
|
|
setpx(x, y, P["white"])
|
|
setpx(x - 1, y, P["lightsky"])
|
|
setpx(x + 1, y, P["lightsky"])
|
|
setpx(x, y - 1, P["lightsky"])
|
|
setpx(x, y + 1, P["lightsky"])
|
|
|
|
|
|
# ---------- Phone hex case ----------
|
|
case_cx, case_cy = 120, 130
|
|
# Outer dark frame (large hex)
|
|
outer = pointy_hex(case_cx, case_cy, hw=58, body_h=64, color=P["bg2"])
|
|
# Frame highlight (top-left ridge)
|
|
for x_l, x_r, y in outer[:30]:
|
|
if y < case_cy - 24:
|
|
hline(x_l, min(x_r, x_l + 2), y, P["midpurple"])
|
|
# Inner case (smaller hex)
|
|
inner = pointy_hex(case_cx, case_cy, hw=53, body_h=58, color=P["shadow"])
|
|
# Inner-inner body (yet smaller, as raised plate)
|
|
plate = pointy_hex(case_cx, case_cy, hw=49, body_h=54, color=P["midpurple"])
|
|
|
|
# Subtle plate gradient: top half lighter
|
|
for x_l, x_r, y in plate:
|
|
if y < case_cy - 12:
|
|
hline(x_l, x_r, y, P["lightgrey"])
|
|
elif y > case_cy + 18:
|
|
hline(x_l, x_r, y, P["shadow"])
|
|
|
|
# Outer outline (very dark)
|
|
hex_outline(outer, P["bg"])
|
|
|
|
|
|
# ---------- LCD hex screen (upper half of plate) ----------
|
|
lcd_cx, lcd_cy = case_cx, case_cy - 18
|
|
lcd_outer = pointy_hex(lcd_cx, lcd_cy, hw=32, body_h=20, color=P["darkforest"])
|
|
# Inner LCD glow
|
|
lcd_inner = pointy_hex(lcd_cx, lcd_cy, hw=29, body_h=18, color=P["forest"])
|
|
# Brighter band at top
|
|
for x_l, x_r, y in lcd_inner:
|
|
if y < lcd_cy - 6:
|
|
hline(x_l, x_r, y, P["fresh"])
|
|
elif y < lcd_cy - 2:
|
|
# blend
|
|
for x in range(x_l, x_r + 1):
|
|
if (x + y) % 2 == 0:
|
|
setpx(x, y, P["fresh"])
|
|
|
|
# LCD bezel outline
|
|
hex_outline(lcd_outer, P["bg"])
|
|
|
|
# Scanline effect inside LCD (every 3rd row darker)
|
|
for x_l, x_r, y in lcd_inner:
|
|
if (y - lcd_cy) % 3 == 0:
|
|
for x in range(x_l, x_r + 1):
|
|
r, g, b = px[x, y]
|
|
px[x, y] = (max(0, r - 12), max(0, g - 12), max(0, b - 12))
|
|
|
|
|
|
# ---------- Place Tor sprite inside LCD ----------
|
|
tor_path = os.path.join(SPRITE_DIR, "pes-tor-happy.png")
|
|
tor = Image.open(tor_path).convert("RGBA")
|
|
# Tor is 64x64. The sprite has lots of padding; resize to fit LCD interior comfortably.
|
|
tor_size = 36
|
|
tor_small = tor.resize((tor_size, tor_size), Image.NEAREST)
|
|
tx = lcd_cx - tor_size // 2
|
|
ty = lcd_cy - tor_size // 2 + 1
|
|
tor_px = tor_small.load()
|
|
for yy in range(tor_size):
|
|
for xx in range(tor_size):
|
|
r, g, b, a = tor_px[xx, yy]
|
|
if a > 30:
|
|
setpx(tx + xx, ty + yy, (r, g, b))
|
|
|
|
|
|
# ---------- 3 hex buttons under LCD ----------
|
|
btn_y = case_cy + 18
|
|
btn_spacing = 28
|
|
btn_colors = [P["red"], P["amber"], P["fresh"]]
|
|
btn_hilights = [P["lightred"], P["yellow"], P["lightgreen"]]
|
|
for i, (color, hl) in enumerate(zip(btn_colors, btn_hilights)):
|
|
bx = case_cx - btn_spacing + i * btn_spacing
|
|
# Bezel (dark)
|
|
pointy_hex(bx, btn_y, hw=10, body_h=6, color=P["bg"])
|
|
# Button face
|
|
btn_rows = pointy_hex(bx, btn_y, hw=8, body_h=5, color=color)
|
|
# Highlight on top edge
|
|
for x_l, x_r, y in btn_rows[:3]:
|
|
hline(x_l + 1, x_r - 1, y, hl)
|
|
# Tiny shine pixel
|
|
setpx(bx - 2, btn_y - 3, P["white"])
|
|
|
|
|
|
# ---------- Antenna nub at top of case ----------
|
|
nub_x = case_cx + 38
|
|
nub_top_y = case_cy - 70
|
|
hline(nub_x - 1, nub_x + 1, nub_top_y, P["bg2"])
|
|
hline(nub_x - 1, nub_x + 1, nub_top_y + 1, P["bg2"])
|
|
setpx(nub_x, nub_top_y - 1, P["red"]) # antenna tip light
|
|
|
|
|
|
# ---------- HLTRN logo + tagline ----------
|
|
FONT = {
|
|
"H": ["X...X", "X...X", "X...X", "XXXXX", "X...X", "X...X", "X...X"],
|
|
"L": ["X....", "X....", "X....", "X....", "X....", "X....", "XXXXX"],
|
|
"T": ["XXXXX", "..X..", "..X..", "..X..", "..X..", "..X..", "..X.."],
|
|
"R": ["XXXX.", "X...X", "X...X", "XXXX.", "X.X..", "X..X.", "X...X"],
|
|
"N": ["X...X", "XX..X", "XX..X", "X.X.X", "X..XX", "X..XX", "X...X"],
|
|
"G": ["XXXXX", "X....", "X....", "X.XXX", "X...X", "X...X", "XXXXX"],
|
|
"O": [".XXX.", "X...X", "X...X", "X...X", "X...X", "X...X", ".XXX."],
|
|
"D": ["XXXX.", "X...X", "X...X", "X...X", "X...X", "X...X", "XXXX."],
|
|
"F": ["XXXXX", "X....", "X....", "XXXX.", "X....", "X....", "X...."],
|
|
"A": [".XXX.", "X...X", "X...X", "XXXXX", "X...X", "X...X", "X...X"],
|
|
"M": ["X...X", "XX.XX", "X.X.X", "X.X.X", "X...X", "X...X", "X...X"],
|
|
"E": ["XXXXX", "X....", "X....", "XXXX.", "X....", "X....", "XXXXX"],
|
|
" ": ["....."] * 7,
|
|
}
|
|
|
|
|
|
def draw_text(text, x0, y0, color, scale=1):
|
|
cur_x = x0
|
|
for ch in text:
|
|
glyph = FONT.get(ch.upper(), FONT[" "])
|
|
for gy, row in enumerate(glyph):
|
|
for gx, c in enumerate(row):
|
|
if c == "X":
|
|
for sy in range(scale):
|
|
for sx in range(scale):
|
|
setpx(cur_x + gx * scale + sx, y0 + gy * scale + sy, color)
|
|
cur_x += (len(glyph[0]) + 1) * scale
|
|
|
|
|
|
def text_width(text, scale=1):
|
|
return ((5 + 1) * len(text) - 1) * scale
|
|
|
|
|
|
# HLTRN top — gold, scale 2 (big)
|
|
hltrn = "HLTRN"
|
|
tw = text_width(hltrn, scale=2)
|
|
draw_text(hltrn, (W - tw) // 2, 16, P["gold"], scale=2)
|
|
# Underline
|
|
ul_y = 16 + 7 * 2 + 2
|
|
hline((W - tw) // 2, (W - tw) // 2 + tw - 1, ul_y, P["amber"])
|
|
|
|
# GOD FRAME tagline — bottom
|
|
tag = "GOD FRAME"
|
|
tw2 = text_width(tag, scale=1)
|
|
draw_text(tag, (W - tw2) // 2, 218, P["lightsky"], scale=1)
|
|
|
|
# Decorative dots on either side of tagline
|
|
dots_y = 218 + 3
|
|
for x in range(20, (W - tw2) // 2 - 4, 6):
|
|
setpx(x, dots_y, P["sky"])
|
|
for x in range((W + tw2) // 2 + 4, W - 20, 6):
|
|
setpx(x, dots_y, P["sky"])
|
|
|
|
|
|
# ---------- Save outputs ----------
|
|
native_path = os.path.join(OUT_DIR, "splash_native.png")
|
|
img.save(native_path)
|
|
print(f"native saved: {native_path} size={img.size}")
|
|
|
|
big = img.resize((W * 8, H * 8), Image.NEAREST)
|
|
big_path = os.path.join(OUT_DIR, "splash.png")
|
|
big.save(big_path)
|
|
print(f"upscaled saved: {big_path} size={big.size}")
|
|
|
|
# ---------- iOS LaunchImage assets (Runner/Assets.xcassets/LaunchImage.imageset) ----------
|
|
IOS_LAUNCH_DIR = os.path.join(ROOT, "god_frame/ios/Runner/Assets.xcassets/LaunchImage.imageset")
|
|
os.makedirs(IOS_LAUNCH_DIR, exist_ok=True)
|
|
|
|
# 1x=320, 2x=640, 3x=960 — nearest upscale preserves pixel-art crispness
|
|
for scale, suffix in [(320, ""), (640, "@2x"), (960, "@3x")]:
|
|
out = img.resize((scale, scale), Image.NEAREST)
|
|
out_path = os.path.join(IOS_LAUNCH_DIR, f"LaunchImage{suffix}.png")
|
|
out.save(out_path)
|
|
print(f"ios launch: {out_path} size={out.size}")
|