Gunnar

mikronova/kyokkou

README.md

# 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.*