Back to catalog

PRISM.node-API

Complete API specification for PRISM.node K1 Light Lab editor, including node system architecture, type definitions, color space transforms, WebSocket TLV protocol, and engine configuration for 320-LED addressable light patterns.

  • Category: discovered
  • Tags: github, community
  • Version: 1.0.0
  • Author: walid
  • Downloads: 0
  • Updated: 2025-11-22

Download the latest version (v1.0.0) from this page.

SKILL.md

---
title: PRISM Node K1 Light Lab API
slug: prism-node-api
description: Complete API specification for PRISM.node K1 Light Lab editor, including node system architecture, type definitions, color space transforms, WebSocket TLV protocol, and engine configuration for 320-LED addressable light patterns.
category: web
created: 2025-10-21
last_updated: 2025-10-21
source: apps/PRISM.node/src/components/k1 (shipping Phase A code)
---

# PRISM Node K1 Light Lab API

**Authoritative Source:** TypeScript source in apps/PRISM.node/src/components/k1
**Shipping Status:** Phase A (production)
**Updated:** 2025-10-21
**Technology:** Vite + React 18 (SWC compiler)

---

## 1. Project Overview

### Purpose
K1 Light Lab is a **pro-grade graph-based animation editor** for 320-LED dual-strip WS2812B RGB controllers. It runs in the browser as a visual node editor, allows real-time parameter tuning, exports patterns via WebSocket TLV, and maintains a live preview of LED output.

### Target Device
- **Pixels:** 320 RGB8 LEDs (WS2812B compatible)
- **Mapping:** Concat-2x160 (dual GPIO parallel output)
  - GPIO 9: Pixels 0–159
  - GPIO 10: Pixels 160–319
- **Preview:** Real-time canvas rendering with selectable FPS (30/60/120)
- **Export:** Binary TLV payload (max 256KB per pattern)

### Tech Stack
- **Frontend:** React 18 + TypeScript 5
- **Build:** Vite 6
- **Compiler:** SWC (super-fast Rust-based TS/JS compilation)
- **UI Framework:** Radix UI primitives + Tailwind CSS
- **Color Space:** OKLCH (perceptual color space)
- **Canvas:** 2D WebGL-backed rendering

---

## 2. Node System Architecture

### Node Types

A **node** is a graph computation unit that transforms inputs → parameters → outputs.

#### Port Types
```typescript
type PortType = 'scalar' | 'field' | 'color' | 'output';

// scalar: Single numeric value (0–1 range, normalized)
// field:  Spatial 2D field (e.g., gradient, noise)
// color:  RGB8 color value [r, g, b]
// output: Final LED frame buffer (read-only)
```

#### Node Categories
```typescript
type NodeCategory =
  | 'generator'  // Produces fields/colors (no inputs)
  | 'spatial'    // Transforms fields spatially (grid, repeat)
  | 'modifier'   // Modifies scalars/fields (ramp, smooth)
  | 'color'      // Color space transforms (hue-shift, saturate)
  | 'combine'    // Blends/mixes inputs (blend, add, multiply)
  | 'output';    // Renders to LED frame buffer
```

#### Port Color Scheme (Visual UI)
```typescript
const PORT_COLORS: Record<PortType, string> = {
  scalar: '#F59E0B',    // Amber
  field:  '#22D3EE',    // Cyan
  color:  '#F472B6',    // Pink
  output: '#34D399',    // Emerald
};

const CATEGORY_COLORS: Record<NodeCategory, string> = {
  generator: 'bg-purple-500/10 text-purple-400',
  spatial:   'bg-cyan-500/10 text-cyan-400',
  modifier:  'bg-amber-500/10 text-amber-400',
  color:     'bg-pink-500/10 text-pink-400',
  combine:   'bg-blue-500/10 text-blue-400',
  output:    'bg-green-500/10 text-green-400',
};
```

### Node Interface Definition

```typescript
interface Port {
  id: string;           // Unique within node
  label: string;        // Human-readable name
  type: PortType;       // scalar | field | color | output
}

interface NodeParameter {
  id: string;           // Unique within node
  label: string;        // Display name
  type: 'slider' | 'select' | 'number' | 'toggle';
  value: number | string | boolean;
  min?: number;         // For slider/number
  max?: number;
  step?: number;
  options?: string[];   // For select
}

interface NodeData {
  id: string;                           // Unique node ID
  title: string;                        // Node name (e.g., "Gradient")
  category: NodeCategory;
  icon: string;                         // Emoji icon
  inputs: Port[];                       // Incoming connections
  outputs: Port[];                      // Outgoing connections
  parameters?: NodeParameter[];         // Internal state
  position: { x: number; y: number };  // Canvas position
  compact?: boolean;                    // UI hint: minimize display
}
```

### Wire (Connection) Definition

```typescript
interface Wire {
  id: string;                    // Unique wire ID
  from: {
    nodeId: string;
    portId: string;
  };
  to: {
    nodeId: string;
    portId: string;
  };
  type: PortType;                // scalar | field | color | output
}
```

---

## 3. Built-in Node Library

### Generator Nodes (No Inputs)

#### Gradient
- **Purpose:** Create linear color gradients
- **Outputs:** `field` (0–1 range)
- **Parameters:**
  - `start` (slider, 0–100): Starting position
  - `end` (slider, 0–100): Ending position
  - `startColor` (select): Color at start
  - `endColor` (select): Color at end

#### Noise
- **Purpose:** Perlin/Simplex noise for organic patterns
- **Outputs:** `field` (0–1 range)
- **Parameters:**
  - `scale` (slider, 1–100): Noise frequency
  - `speed` (slider, 0–10): Animation speed
  - `octaves` (number, 1–8): Noise complexity

### Spatial Nodes

#### Grid
- **Purpose:** Convert linear position to 2D grid
- **Inputs:** `field`
- **Outputs:** `field`
- **Parameters:**
  - `cols` (number): Grid width
  - `rows` (number): Grid height

#### Repeat
- **Purpose:** Repeat pattern N times
- **Inputs:** `field`
- **Outputs:** `field`
- **Parameters:**
  - `count` (number, 1–50): Repetitions
  - `offset` (slider): Phase offset between repeats

### Modifier Nodes

#### Ramp
- **Purpose:** Smooth transitions between values
- **Inputs:** `scalar`
- **Outputs:** `scalar`
- **Parameters:**
  - `easing` (select): Ease function (linear/ease-in/ease-out/ease-in-out)
  - `sharpness` (slider, 0–100): Curve steepness

#### Smooth
- **Purpose:** Gaussian blur on field
- **Inputs:** `field`
- **Outputs:** `field`
- **Parameters:**
  - `radius` (slider, 0.5–50): Blur radius in pixels
  - `iterations` (number, 1–5): Blur passes

### Color Nodes

#### Hue Shift
- **Purpose:** Rotate color around hue wheel
- **Inputs:** `color`, `scalar` (amount)
- **Outputs:** `color`
- **Parameters:**
  - `hue` (slider, -180–180): Hue rotation in degrees

#### Saturate
- **Purpose:** Increase/decrease color saturation
- **Inputs:** `color`, `scalar` (amount)
- **Outputs:** `color`
- **Parameters:**
  - `saturation` (slider, 0–200): Saturation multiplier

### Combine Nodes

#### Blend
- **Purpose:** Mix two colors
- **Inputs:** `color` (A), `color` (B), `scalar` (mix amount)
- **Outputs:** `color`
- **Parameters:**
  - `blend` (slider, 0–100): Mix ratio
  - `mode` (select): Blend mode (mix/add/multiply/screen)

#### Add
- **Purpose:** Additive color mixing
- **Inputs:** `color` (A), `color` (B)
- **Outputs:** `color`

### Output Node

#### K1 Output
- **Purpose:** Render computed color field to LED frame buffer
- **Inputs:** `color` (primary), `scalar` (brightness, optional)
- **Outputs:** None (terminal node)
- **Parameters:** None
- **Behavior:** Produces 320 × 3 bytes RGB8 frame

---

## 4. Engine Configuration

### Preview Specification
```typescript
const PREVIEW_SPEC = {
  length: 320,      // 320 pixels
  fps: 120 as Fps,  // Default FPS
};

type Fps = 120 | 60 | 30;  // Selectable options
```

### Engine Configuration
```typescript
const ENGINE_CONFIG = {
  pixelCount: 320,
  colorFormat: 'RGB8' as const,
  mapping: 'concat-2x160',
  channels: [
    { gpio: 9, count: 160, start: 0, end: 159 },
    { gpio: 10, count: 160, start: 160, end: 319 },
  ],
  map: Array.from({ length: 320 }, (_, i) => i),  // Identity map
};
```

### Frame Data Structure
```typescript
type RGB8 = [number, number, number];  // 8-bit R, G, B
type Frame = RGB8[];                   // 320 pixels
type FrameBuffer = Uint8Array;         // 960 bytes (320 × 3)
```

---

## 5. Color Space Transforms

### sRGB ↔ Linear (Gamma Correction)
```typescript
export function srgb8ToLinear(c: number): number {
  const v = c / 255;
  return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
}

export function linearToSrgb8(v: number): number {
  const s = v <= 0.0031308 ? 12.92 * v : 1.055 * Math.pow(v, 1 / 2.4) - 0.055;
  return Math.round(clamp01(s) * 255);
}
```

### Linear ↔ OKLab (Perceptual)
```typescript
// OKLab: Perceptually uniform color space
// Better for animation because distances = perceived differences

interface OKLab {
  L: number;  // Lightness (0–1)
  a: number;  // Green-Red axis
  b: number;  // Blue-Yellow axis
}

function oklabFromLinear([r, g, b]): [number, number, number]
function linearFromOklab([L, a, b]): [number, number, number]
```

### OKLab ↔ OKLCH (Hue-Saturation-Lightness)
```typescript
// OKLCH: Cylindrical representation of OKLab
// Separates hue from saturation, easier for rotations

interface OKLCH {
  L: number;       // Lightness (0–1)
  C: number;       // Chroma (saturation-like, 0–0.4)
  H: number;       // Hue (0–360 degrees)
}
```

### Usage Pattern
```typescript
// Example: Rotate hue while preserving brightness
const rgb8: RGB8 = [255, 128, 0];  // Orange

// 1. Convert to linear
const linear = rgb8ToLinear(rgb8);  // [1, 0.5, 0]

// 2. Convert to OKLCH
const oklch = rgbToOklch(linear);   // { L: 0.6, C: 0.2, H: 45 }

// 3. Modify hue
oklch.H = (oklch.H + 120) % 360;   // +120° → green

// 4. Convert back to RGB8
const result = oklchToRgb8(oklch);  // [0, 255, 128]
```

---

## 6. WebSocket Transport (TLV Protocol)

### Connection Setup
```typescript
const ws = new WebSocket('ws://device-ip:8080');
ws.binaryType = 'arraybuffer';
```

### TLV Message Format

**Type-Length-Value serialization:**
- **Type:** 1 byte (0x01/0x02/0x03)
- **Length:** 2 bytes (little-endian uint16)
- **Value:** N bytes (raw payload)

```typescript
enum TLVType {
  PUT_BEGIN = 0x01,  // Payload size header
  PUT_DATA = 0x02,   // Data chunk
  PUT_END = 0x03,    // CRC32 checksum
}

interface TLV {
  type: TLVType;
  length: number;
  value: Uint8Array;
}
```

### TLV Message Types

#### PUT_BEGIN (0x01)
- **Value:** 4 bytes (uint32-LE total payload size)
- **Purpose:** Signal incoming pattern, total size
- **Example:** Pattern 256KB → value = 262144 (0x0004_0000 in little-endian)

#### PUT_DATA (0x02)
- **Value:** 4089 bytes max (4 byte offset LE + 4085 bytes data)
- **Structure:**
  - Bytes 0–3: Offset in payload (uint32-LE)
  - Bytes 4–4088: Pattern data chunk
- **Count:** Total payload / 4085, rounded up
- **Example:** 256KB payload = 63 PUT_DATA messages

#### PUT_END (0x03)
- **Value:** 4 bytes (uint32-LE CRC32 checksum)
- **Purpose:** Validate pattern integrity
- **CRC32 Polynomial:** 0xEDB88320 (standard)

### Transaction Example

```
Client → Device PUT_BEGIN(262144)     // 256KB pattern
Client → Device PUT_DATA(0, 4085B)    // Bytes 0–4084
Client → Device PUT_DATA(4085, 4085B) // Bytes 4085–8169
... (61 more PUT_DATA chunks)
Client → Device PUT_END(0x12345678)   // CRC32 checksum

Device: Validates CRC32
Device: Stores pattern to /littlefs
Device: Sends ACK (firmware-dependent)
```

### Helper Functions

```typescript
export const PAYLOAD_MAX = 262_144;     // 256KB max pattern
export const PUT_DATA_MAX = 4_089;      // Per chunk

export function makePutPlan(bytes: Uint8Array): {
  tlvs: TLV[];
  total: number;
  crc: number;
}

export function serializePlan(plan): Uint8Array {
  // Combines all TLV messages into single byte array
}

export function dryRunReport(plan): string {
  // Returns debug info: "TLV plan: totalBytes=262144 | chunks=63 | CRC32=0x..."
}

export async function sendPlanOverWs(url: string, plan): Promise<void> {
  // Opens WebSocket, sends all TLV messages, closes
}
```

---

## 7. App Component Structure

### Main Entry Point
- **File:** `src/main.tsx`
- **Bootstrap:** React 18 root render

### Core Components

#### LightLab (`LightLab.tsx`)
**Purpose:** Master workspace controller, state management
- **Responsibilities:**
  - Manage nodes & wires state
  - Export payload generation
  - Real-time preview update loop
  - WebSocket connection management
- **State:** `nodes[]`, `wires[]`, `selectedNode`, `previewFps`

#### K1Toolbar (`K1Toolbar.tsx`)
**Purpose:** Top toolbar, export & FPS controls
- **Features:**
  - Export button (downloads binary or sends via WS)
  - FPS selector dropdown (120/60/30)
  - Undo/redo
  - File load/save

#### NodeCanvas (`NodeCanvas.tsx`)
**Purpose:** Visual graph editor with node connections
- **Features:**
  - Drag nodes to reposition
  - Click & drag to create wires
  - Right-click context menu (delete, inspect)
  - Pan & zoom
  - Minimap

#### Node (`Node.tsx`)
**Purpose:** Individual node card UI
- **Features:**
  - Title & category color
  - Input/output port circles
  - Parameter sliders/selects
  - Compactness toggle

#### NodeInspector (`NodeInspector.tsx`)
**Purpose:** Right-side detail panel
- **Shows:** Selected node properties, parameters, connections
- **Allows:** Edit parameters, view data flow

#### NodeLibrary (`NodeLibrary.tsx`)
**Purpose:** Left sidebar, node catalog
- **Features:**
  - Filterable by category
  - Drag-to-canvas node creation
  - Search by name

### Engine (`engine.ts`)
**Purpose:** Color space math & frame rendering
- **Exports:**
  - `stubTick()` - Debug frame generator
  - `applyFrameCapRGB8()` - Brightness capping
  - Color transform functions
  - OKLCH conversion utilities

### Transport (`transport/wsTlv.ts`)
**Purpose:** WebSocket TLV serialization & sending
- **Exports:**
  - `makePutPlan()` - Create TLV message plan
  - `sendPlanOverWs()` - Send to device
  - `dryRunReport()` - Debug info

---

## 8. Data Flow & Export Pipeline

### Editor → Device Export Pipeline

```
User clicks "Export" in toolbar
       ↓
LightLab.tsx collects:
  - All nodes & wires
  - Parameter values
  - Preview settings (FPS)
       ↓
engine.ts runs graph:
  - Evaluates all nodes
  - Computes 320 RGB8 colors
  - Returns Uint8Array(960)
       ↓
transport/wsTlv.ts packages:
  - makePutPlan(960 bytes)
  - Creates TLV messages
  - Calculates CRC32
       ↓
sendPlanOverWs() transmits:
  - Opens WebSocket
  - Sends PUT_BEGIN
  - Chunks into PUT_DATA (4089 bytes each)
  - Sends PUT_END with CRC32
  - Closes WebSocket
       ↓
Firmware receives:
  - Validates CRC32
  - Stores pattern to /littlefs
  - Playback task reads next frame
  - LEDs render 320-pixel pattern at 120 FPS
```

---

## 9. Real-Time Preview

### Canvas Preview Rendering
- **Canvas size:** 320 pixels wide × variable height
- **Pixel format:** RGB8 (3 bytes per pixel)
- **Rendering:** Immediate (no debounce)
- **FPS options:** 30, 60, 120 (matches device capability)

### Frame Update Loop
```typescript
// Pseudocode from LightLab.tsx
setInterval(() => {
  const frame = engine.evaluate(nodes, wires);  // 960 bytes
  setPreview(frame);
  canvas.drawPixels(frame);
}, 1000 / previewFps);
```

---

## 10. Quick Integration Examples

### Creating a Custom Node

```typescript
const customNode: NodeData = {
  id: "mynode-1",
  title: "My Custom Effect",
  category: "modifier",
  icon: "✨",
  inputs: [
    { id: "field", label: "Input Field", type: "field" },
  ],
  outputs: [
    { id: "output", label: "Output", type: "field" },
  ],
  parameters: [
    { id: "intensity", label: "Intensity", type: "slider", value: 50, min: 0, max: 100, step: 1 },
  ],
  position: { x: 100, y: 100 },
};
```

### Sending Pattern to Device

```typescript
import { makePutPlan, sendPlanOverWs } from './transport/wsTlv';

const patternBytes = new Uint8Array(/* 960 bytes of RGB8 data */);
const plan = makePutPlan(patternBytes);
console.log(dryRunReport(plan));  // Debug: "TLV plan: ..."
await sendPlanOverWs('ws://192.168.1.100:8080', plan);
```

### Accessing Engine Config in Code

```typescript
import { ENGINE_CONFIG, PREVIEW_SPEC } from './engine';

console.log(ENGINE_CONFIG.pixelCount);   // 320
console.log(ENGINE_CONFIG.mapping);      // "concat-2x160"
console.log(ENGINE_CONFIG.channels);     // GPIO 9 & 10 info
console.log(PREVIEW_SPEC.fps);           // 120 (default)
```

---

## 11. Type Reference

### All Exported Types

```typescript
// Core types
export type PortType = 'scalar' | 'field' | 'color' | 'output';
export type NodeCategory = 'generator' | 'spatial' | 'modifier' | 'color' | 'combine' | 'output';
export type Fps = 120 | 60 | 30;
export type RGB8 = [number, number, number];

// Interfaces
export interface Port { id: string; label: string; type: PortType; }
export interface NodeParameter { id: string; label: string; type: 'slider' | 'select' | 'number' | 'toggle'; value: any; min?: number; max?: number; step?: number; options?: string[]; }
export interface NodeData { id: string; title: string; category: NodeCategory; icon: string; inputs: Port[]; outputs: Port[]; parameters?: NodeParameter[]; position: { x: number; y: number }; compact?: boolean; }
export interface Wire { id: string; from: { nodeId: string; portId: string }; to: { nodeId: string; portId: string }; type: PortType; }

// TLV types
export enum TLVType { PUT_BEGIN = 0x01, PUT_DATA = 0x02, PUT_END = 0x03 }
export interface TLV { type: TLVType; length: number; value: Uint8Array; }
```

---

## 12. Configuration & Constants

### Device Configuration
- **Pixel count:** 320 (from CANON.md)
- **Color format:** RGB8 (3 bytes per pixel)
- **Mapping:** Concat-2x160 (GPIO 9+10 parallel)
- **Max pattern size:** 256KB (262,144 bytes)
- **WebSocket timeout:** 5 seconds
- **WebSocket buffer:** 4096 bytes

### UI Colors
- **Scalar port:** Amber (#F59E0B)
- **Field port:** Cyan (#22D3EE)
- **Color port:** Pink (#F472B6)
- **Output port:** Emerald (#34D399)

---

## 13. Performance Optimization Tips

### Tips for Smooth Editing
1. **Preview FPS:** Set to 30 if graph has 20+ nodes
2. **Node count:** Keep under 50 for real-time feedback
3. **WebSocket chunks:** Max 4089 bytes per message (auto-handled)
4. **CRC32:** Calculated client-side, doesn't block export

### Debugging
- **Browser DevTools:** F12 → Network tab → WS messages
- **Firmware logs:** UART 115200 baud (see PRISM.k1 Firmware Skill)
- **Export dry-run:** Call `dryRunReport(plan)` before sending

---

## 14. External API Contracts

### Firmware Expects (from PRISM.k1 Firmware Skill)
- **WebSocket at:** `ws://device:8080` (default, configurable)
- **TLV format:** Exactly as specified (PUT_BEGIN → PUT_DATA* → PUT_END)
- **CRC32:** Must validate; firmware rejects mismatches
- **Pattern max:** 256KB (enforced)

### Device Provides (to PRISM.node)
- **WebSocket endpoint:** Opens after boot
- **Pattern storage:** LittleFS at `/littlefs`
- **LED output:** 120 FPS refresh (CANON.md ADR-008)
- **Acknowledgment:** Optional (depends on firmware implementation)

---

## 15. Testing Checklist

- [ ] Canvas renders 320 pixels?
- [ ] Nodes connect with colored wires (port type matches)?
- [ ] Parameters update preview in real-time?
- [ ] Export button generates TLV messages?
- [ ] CRC32 calculates correctly?
- [ ] WebSocket connects to device?
- [ ] Pattern appears on LEDs after send?
- [ ] FPS selector updates preview rate?
- [ ] Undo/redo works?

---

**This specification reflects shipping Phase A code. Updates to types.ts, engine.ts, or transport/wsTlv.ts require version bump and regeneration of this skill.**

*Last updated: 2025-10-21 by Claude Code Captain*