# kyokkou (極光)
> 極光 — polar light. The aurora. Light created at the extremes.
**Stack:** Rust · egui · egui_graph · Blender 5.1.1 · FalkorDB
> *Intent in. Raytraced beauty out.*
---
## Sibling projects — shared hardware, independent stacks
All three projects run on the same machine (ThinkPad T14s AMD 360 · gfx1150 · 32 GB shared VRAM).
Each project manages its own ROCm stack independently — no project depends on another.
| Project | ROCm strategy |
|---|---|
| text-analysis | lemonade-sdk bundled ROCm 7 — fully self-contained |
| klient-t14s | installs ROCm + HIP system-wide — self-contained installer |
| kyokkou | ROCm + HIP inside container — fully isolated |
---
## What it is
An intent-driven visual generator. You pick what you want to create.
The tool handles ALL Blender settings — materials, lighting, sim params,
camera, render quality. One button: **Create image** or **Create animation**.
You never touch Blender directly.
---
## Origin
> *"I want great Blender creations — raytraced god rays, metal and water reflections,
> fluid animations through pipes, 3D system maps with information flowing as fluid.
> A Blender setup acting as Grafana, or a render export feeding my Rust egui engine
> with live system metrics."*
---
## Architecture
```
Rust egui UI (kyokkou)
→ validated scene_config.yaml
→ bpy scene script (one per scene type)
→ Blender headless (container)
→ PNG / EXR / MP4 / glTF
↑
egui live viewer ←┘
```
**Three layers:**
| Layer | Responsibility |
|---|---|
| Rust egui UI | Input, preview, validation, parameter rescaling, render trigger |
| scene_config.yaml | Validated contract — bpy script trusts this completely |
| bpy scene script | One hand-crafted Blender scene, parameterised from yaml |
---
## Fluid strategy
Two distinct methods. Each semantically correct for its category.
| Method | What it is | When |
|---|---|---|
| Mantaflow Liquid | Physics fluid — splashes, pours, chaos | Category 1 art |
| Mantaflow Guide | Directed flow along a curve — no walls, no leaking | Category 2 & 3 info flow |
**Guide is the key insight for information flow.**
The curve *is* the pipe. No collision geometry → no leaking.
The pipe mesh is visual-only — glass tube, conduit, laser channel — zero physics role.
The fluid is mathematically steered along the graph edge path.
No walls exist, so nothing can leak through them.
This matches the semantics: information doesn't slosh and leak. It travels a path.
It has direction and intent. It follows the graph topology.
Speed and colour of each guided flow come directly from edge data —
high-traffic connections flow faster, weighted edges glow brighter.
---
## Category 1 — Art scenes
**Fluid method:** Mantaflow Liquid (physics)
**Inputs:** Scalars + enums + asset (logo / image)
**Validator:** Geometry + quality constraints
**Preview:** Parameter checklist + logo rescale handle
**Output:** PNG / EXR / MP4
```ansi
╲ │ ╱
╲ │ ╱
─────[◈]───── ← logo
╱ │ ╲
╱ │ ╲
≋≋≋≋≋≋≋≋≋≋≋≋≋ ← fluid
```
Scenes:
- Raytraced god rays (volumetric scatter)
- Metal reflections (anisotropic BSDF — gold / steel / chrome / copper)
- Water / liquid (glass BSDF + IOR)
- Fluid through pipes (Mantaflow Liquid)
- Volumetric clouds
- Logo integration — auto-normalised to scene units
**Build this first.** One hand-crafted Blender scene. Extract 5–8 parameters.
---
## Category 2 — System topology
**Fluid method:** Mantaflow Guide (information flow)
**Inputs:** Structural graph — what connects to what
**Validator:** Graph structure validator
**Preview:** egui_graph editor — confirm layout before render
**Output:** PNG / MP4 OR glTF → egui live viewer
```ansi
●━━━━●━━━━━━━● line A
┃
●━━━━◉━━━● ← interchange
┃
●━━━━━━━● line C
```
Each graph edge becomes a bezier curve. Guide flow attaches to the curve.
The pipe mesh is purely visual — glass tube around the curve, no physics.
Information visibly travels the dependency graph, directed and leak-free.
Input sources:
- Draw in egui_graph canvas
- Import JSON / YAML / Terraform / k8s / docker-compose
- Two-way: import → edit → export back to JSON
Graph validator:
- Circular deps → warn, flag in render
- Orphaned nodes → layout decision
- Node count > 50 → readability warning
- Missing labels → fallback to node ID
---
## Category 3 — Data graphs (cinematic portrait)
**Fluid method:** Mantaflow Guide (information flow)
**Inputs:** Property graph — nodes and edges carry real values
**Validator:** Schema + value range validator
**Preview:** 2D graph + value→visual mapping config
**Output:** PNG / MP4 OR glTF → egui live viewer
```ansi
◉━━━━━━◎ ← small
│╲
│ ◉ ← mid
│ ╲
◎━━━━━◉ ← large
```
The scene:
- Nodes → glass spheres / crystal polyhedra / liquid-metal orbs
- Edges → guided fluid in glass pipes / glowing laser beams / energy arcs
- God rays through the node cluster
- Volumetric fog — edges scatter visibly
- Depth of field — foreground sharp, background bokeh
- Node size + emission = property value
- Edge flow velocity / glow = relationship weight
Value → visual mapping (configured in egui):
- Which property → node size?
- Which property → emission brightness?
- Which property → edge flow velocity / laser intensity?
- All values normalised 0–1 before bpy
Input sources:
- FalkorDB — live Cypher queries
- Neo4j dump / GraphML
- Property graph JSON
Validator concerns:
- Glass caustics > 50 nodes → render time explodes → warn
- Edge count → guide volumes expensive → cap or batch
Live path: FalkorDB → poll → egui graph viewer live.
Blender = the beautiful artifact export.
---
## Category 4 — Physical rack topology
**Inputs:** Server inventory — rack units, slots, hardware specs, cable routes
**Validator:** Rack geometry validator (U-count, slot conflicts, power budget)
**Preview:** egui rack diagram — side-on RU view, confirm layout before render
**Output:** PNG / EXR / MP4 fly-through OR glTF → egui live viewer
```ansi
╔══════════════╗
║ ▬▬ srvA ● ║ ← cool
║ ▬▬ srvB ● ║ ← normal
║ ▬▬▬ srvC ● ║ ← hot
║ ░░ [empty] ║
╚══════════════╝
```
The scene:
- Rack chassis as brushed steel enclosure — perforated front panels, cable management arms
- Servers as 1U / 2U / 4U sleds — indicator LEDs emissive, drive bays visible
- Switches → dark anodised aluminium, SFP cages glowing
- Cable runs → colour-coded fibre and copper bundles, volumetric glow on active links
- God rays through server room — dust motes, cold aisle / hot aisle separation visible
- Active ports → pulsing emission
- Load / temp values → heat maps on chassis surfaces
Value → visual mapping (configured in egui):
- CPU load → chassis surface heat map (cool blue → alert red)
- Network utilisation → cable glow intensity
- Power draw → PDU indicator brightness
- Disk activity → drive bay LED pulse rate
Validator concerns:
- U slot overlap → block render, flag conflict
- Power budget exceeded → warn per PDU rail
- Cable count > 100 → batch into trunks for render performance
- Missing U slots → show empty blanking panels
Input sources:
- Netbox REST API — live pull of rack inventory
- Static `rack.yaml` — version-controllable, import/export
- Manual egui editor — drag servers between U slots
---
## Visual style reference
Network and topology views follow a schematic transit-map aesthetic —
lines as routes, nodes as stations, edges carrying weight through
thickness and glow intensity. Orthogonal routing, interchange dots,
color-coded layers.
```ansi
●━━━━━━━━━━━●━━━━━● line A
┃
●━━━━━━━━━━━◉━━━━━● line B
┃
● ← interchange
```
In egui_graph: node size encodes property value, edge thickness encodes
relationship weight, color temperature encodes load or state.
The Blender export is the cinematic version of the same data.
---
## Scene file format
All egui settings saveable and loadable as a **kyokkou scene file** —
the full UI state: category, scene type, all parameter values, graph layout,
value→visual mappings, validator state.
**RON** — Rust-idiomatic, enums serialize cleanly, comments allowed,
readable by power users. Scene files are not machine interchange — readability wins.
```ron
// kyokkou scene — art / fluid pipe
(
category: ArtScene,
scene_type: FluidPipe,
metal: Chrome,
render_samples: 512,
output_type: Still,
logo_path: Some("/assets/logo.png"),
logo_scale: 0.42,
)
```
Scene files load back into egui state exactly. The bpy script still receives
the validated `scene_config.yaml` — the scene file is the egui side of the contract.
---
## Render backend — bpy in a container
**Runtime: Podman.** Rust speaks the Podman socket directly via bollard
(`DOCKER_HOST=unix:///run/user/$UID/podman/podman.sock`). No Docker needed.
bpy (Blender as a Python module, official PyPI package) runs inside a custom container.
No suitable off-the-shelf image exists. All custom images share the same Ubuntu base +
bpy install + Python HTTP agent. The compute layer is the only difference.
### Two deployment scenarios
**Scenario A — outside** (default): Rust egui runs on the host, agent runs in the container.
HTTP on `localhost:8080` bridges them. Rust lifecycle-manages the container via bollard.
**Scenario B — bundled**: Rust egui + agent both inside the container. One `podman run`
ships everything. Entrypoint starts the Python agent then exec's the Rust binary.
Display forwarded via Wayland socket or X11 socket at runtime.
### Images
| Image | Scenario | GPU | Device passthrough |
|---|---|---|---|
| `kyokkou-cpu` | A | CPU fallback | — |
| `kyokkou-rocm` | A | AMD ROCm/HIP | `/dev/kfd`, `/dev/dri` |
| `kyokkou-cuda` | A | NVIDIA CUDA | `--gpus all` |
| `kyokkou-bundled-cpu` | B | CPU fallback | — |
| `kyokkou-bundled-rocm` | B | AMD ROCm/HIP | `/dev/kfd`, `/dev/dri` |
| `kyokkou-bundled-cuda` | B | NVIDIA CUDA | `--gpus all` |
`kyokkou-cpu` / `kyokkou-bundled-cpu` are always the fallback.
ROCm 7.2.2 + HIP confirmed working on host (klient-t14s) — reference for the ROCm images.
**Target: Blender 5.1.1 · bpy 5.1**
**Hardware:** kyokkou is not tied to any specific GPU. Cycles runs on whatever
the host has — AMD ROCm, NVIDIA CUDA/OptiX, Apple Metal, or CPU fallback.
Current development machine is a ThinkPad T14s with Radeon 890M (ROCm),
but a 4090 or any CUDA card drops in without code changes — just pass the
right device through to the container.
bpy is a Python C extension that IS Blender internals — scene graph, materials,
node trees, render engine — loaded directly into a Python process. The only way
to drive it from Rust is via an HTTP agent in the same process as bpy.
The agent calls bpy functions directly in shared memory.
**What the container solves:**
- **CUDA conflict** — `import bpy` calls `cuInit()`. egui already holds a GPU context.
Container gives bpy exclusive CUDA ownership, full GPU Cycles on the same card.
- **Host bleed** — Python version, numpy, system libs pinned inside. Reproducible renders.
- **Python pinning** — bpy 5.1 requires exactly Python 3.13. Container owns that.
- **GPU/EGL** — Cycles full GPU, EEVEE Next via EGL. Pass `--gpus all` via bollard.
**Volume mounts** set at container creation:
- `/tmp/kyokkou/in` → `/in` — yaml, textures, assets
- `/tmp/kyokkou/out` → `/out` — PNG, EXR, MP4 output
**HTTP agent** (stdlib only, no framework) inside the container:
| Endpoint | Behaviour |
|---|---|
| `GET /health` | 200 when bpy is initialised and ready |
| `POST /render` | Accepts yaml body, calls bpy, blocks until done, returns 200 |
| `POST /upload` | Receives texture bytes, writes to `/in/textures/`, calls `bpy.data.images.load()` |
**Rust owns the container lifecycle** via `bollard` — speaks the Podman socket directly,
no CLI dependency (`DOCKER_HOST=unix:///run/user/$UID/podman/podman.sock`).
Dependencies: `bollard 0.17` · `tokio 1` (full) · `reqwest 0.12` (json)
**Startup:** inspect via bollard → running: proceed / stopped: start / missing: create then start.
Poll `GET /health` until 200, then unlock render button.
**Render:** validator green → Rust posts yaml to `POST /render` → tokio task awaits 200 →
reads from `/tmp/kyokkou/out` → displays in egui preview. egui thread never blocks.
---
## The parameter validator
Every input has a type, range, and cross-parameter constraints.
egui shows live checkbox state — green / yellow / red.
**Render button locked until all checks pass.**
Examples:
- Logo scale re-validated when FOV changes
- Volume clouds → quality auto-upgraded to min 512 samples
- Animation mode → disables still-only params
- Glass node graph > 50 nodes → warn, offer simplified material
- FalkorDB values → normalised 0–1 before hitting bpy
- Logo → normalised to scene units, rescale handle in egui preview
The bpy script trusts the yaml. egui guarantees correctness.
---
## egui_graph — first-class editor
- Draw topology from scratch
- Import JSON → auto-layout (force-directed / Sugiyama) → edit
- Export back to JSON — version-controllable
- Drives Blender scene OR live egui viewer directly
---
## What bpy sees — after validation passes
```yaml
scene_type: clouds_metal_logo
metal: chrome
cloud_type: cumulus
logo_path: /assets/logo.png
logo_scale_scene_units: 0.42
logo_position: [0.0, 0.0, 1.2]
camera_focal_mm: 35
render_samples: 512
output_type: still
output_path: /renders/out.png
```
---
## Live egui path (no Blender)
```
FalkorDB (Cypher) → poll → parse property graph
→ egui_graph live viewer → values drive visuals
→ "Export to Blender render" → fires bpy scene
```
egui = live Grafana-like viewer.
Blender = beautiful artifact on demand.
---
## Build sequence
1. One hand-crafted bpy scene — beautiful, hardcoded
2. Extract 5–8 parameters → `scene_config.yaml`
3. CLI: `kyokkou render --scene fluid_pipe --config scene.yaml`
4. Rust egui UI — writes yaml, live validation
5. Validator logic — per parameter, cross-constraints
6. Add scene types one by one
---
*Start here: one beautiful fluid pipe scene. Hand-craft it. Make it stunning.
Extract the parameters. Everything else follows.*