Deep understanding of K1.reinvented's compilation architecture, node system, and extension methodology. Teaches why graphs compile to C++ and how to extend the system without violating minimalism.
Scanned 2/10/2026
Install via CLI
openskills install majiayu000/claude-skill-registry---
name: k1-architecture
description: Deep understanding of K1.reinvented's compilation architecture, node system, and extension methodology. Teaches why graphs compile to C++ and how to extend the system without violating minimalism.
---
# K1.reinvented System Architecture Skill
## The Core Insight: Why This Architecture Works
For three years, every LED project architecture fell into the same trap: **flexibility OR performance**. You could have creative freedom OR execution speed, never both.
K1.reinvented proves this is a false choice. The insight is simple but profound:
**Move the creative work to the computer. Move the execution work to the device.**
Don't ask the device to interpret JSON at runtime. Don't ask it to evaluate node graphs in real-time. Don't force a tiny embedded system to be both flexible AND fast.
Instead:
- **Computer**: Visual node graphs, artistic composition, creative iteration (TypeScript codegen)
- **Device**: Compiled native C++, zero interpretation overhead, pure execution (ESP32-S3)
The node graph exists ONLY at development time. It guides code generation. Then it disappears. What runs on the device is pure C++ that looks exactly like what you'd write by hand.
**This is the revolution: compilation as creative medium, not optimization trick.**
---
## The Two-Stage Compilation Process
### Stage 1: JSON Graph → C++ Code (Development Time)
**Input:** `graphs/departure.json` - Visual node graph describing the pattern
```json
{
"name": "Departure",
"nodes": [
{"id": "position", "type": "position_gradient"},
{"id": "palette", "type": "palette_interpolate", "parameters": {"palette": "departure"}},
{"id": "output", "type": "output"}
],
"wires": [...],
"palette_data": [[0, 8, 3, 0], [32, 45, 25, 0], ...]
}
```
**Process:** TypeScript compiler (`codegen/src/index.ts`) walks the graph:
1. Reads node definitions and their parameters
2. Performs topological sort (generators → processors → output)
3. For each node, generates C++ code using Handlebars templates
4. Embeds palette data directly into generated code
5. Outputs `firmware/src/generated_effect.h`
**Output:** Pure C++ code with no runtime graph interpretation
```cpp
void draw_generated_effect() {
static float field_buffer[NUM_LEDS];
static CRGBF color_buffer[NUM_LEDS];
// position_gradient: Map LED index to 0.0-1.0
for (int i = 0; i < NUM_LEDS; i++) {
field_buffer[i] = (float)i / (NUM_LEDS - 1);
}
// palette_interpolate: Interpolate between keyframes
const uint8_t palette_keyframes[] = {0, 8, 3, 0, 32, 45, 25, 0, ...};
for (int i = 0; i < NUM_LEDS; i++) {
// ... interpolation logic ...
}
// output: Write to LED array
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = color_buffer[i];
}
}
```
### Stage 2: C++ Code → Machine Code (Compile Time)
**Process:** PlatformIO + GCC compile the generated C++:
1. C++ compiler inlines loops
2. Constant folding optimizes calculations
3. Dead code elimination removes unused paths
4. Result: Optimal assembly for ESP32-S3
**Outcome:** 450+ FPS execution with zero overhead. The graph structure has completely disappeared.
---
## The Node Type System
Every node has a **type** that determines what code it generates. Currently supported:
### Generator Nodes (Create Data from Context)
**`position_gradient`**
- Maps LED index to 0.0-1.0 range
- Used as input to other nodes
- Generated code: Simple division loop
**`gradient`** (HSV gradient)
- Creates hue values across LED range
- Parameters: start_hue, end_hue
- Generated code: Linear interpolation
### Transform Nodes (Process Data)
**`palette_interpolate`** (CRITICAL NODE)
- Maps 0.0-1.0 position to palette colors
- Reads `palette_data` from graph
- Generates keyframe array + interpolation logic
- **This is where artistic intent becomes code**
**`hsv_to_rgb`**
- Converts HSV color space to RGB
- Parameter: brightness
- Generated code: Standard HSV→RGB math
### Output Nodes (Write to LEDs)
**`output`**
- Copies color_buffer to leds array
- Always the final node in graph
- Generated code: Simple array copy
---
## How to Extend: Adding New Node Types
**The minimalist principle:** Only add node types that serve beauty. If it doesn't enable new forms of artistic expression, don't add it.
### Example: Adding a `sine_wave` Node
**1. Define the node type in graph JSON:**
```json
{"id": "wave", "type": "sine_wave", "parameters": {"frequency": 3.0, "amplitude": 0.5}}
```
**2. Add case to codegen (`codegen/src/index.ts`):**
```typescript
case 'sine_wave':
const freq = node.parameters?.frequency ?? 1.0;
const amp = node.parameters?.amplitude ?? 1.0;
return `
// Node: ${node.id} (sine_wave)
for (int i = 0; i < NUM_LEDS; i++) {
float t = field_buffer[i];
field_buffer[i] = ${amp}f * sin(t * ${freq}f * TWO_PI);
}`;
```
**3. Test with all three patterns:**
- Does it compile without errors?
- Does the generated code make sense?
- Could you debug it if it breaks?
**4. Verify it serves beauty:**
- Does this enable new artistic possibilities?
- Or is it just technical sophistication?
### Anti-Pattern: Don't Add Complexity for Completeness
**Bad reason to add a node:** "It would be technically elegant to have a full math library"
**Good reason to add a node:** "This enables expressing emotions that weren't possible before"
---
## The Codegen Architecture
### Key Files
**`codegen/src/index.ts`** (~280 lines)
- Main entry point
- Graph parsing and validation
- Topological sort (ensures correct execution order)
- Node-specific code generation
- Template compilation via Handlebars
**Critical Functions:**
```typescript
function generateNodeCode(node: Node, graph: Graph): string
// Takes a node definition, returns C++ code string
// This is where artistic intent becomes executable code
function compileGraph(graph: Graph): string
// Orchestrates entire compilation process
// Returns complete C++ file content
```
### The Template System (Handlebars)
Uses simple string templates, NOT complex C++ metaprogramming:
```typescript
const effectTemplate = `
void draw_generated_effect() {
{{#each steps}}
{{{this}}}
{{/each}}
}`;
```
**Why Handlebars instead of C++ templates?**
- Simpler to understand and debug
- Template errors are clear TypeScript errors, not cryptic C++ template errors
- Maintains minimalism (string substitution, not meta-programming)
---
## Debugging Guide
### Problem: Codegen Fails
**Symptom:** `npm run compile` throws error
**Check:**
1. Is the JSON valid? Run through JSON validator
2. Does every node have a valid type?
3. Are palette_data arrays properly formatted?
4. Are wires connecting valid node IDs?
**Fix:** Correct the JSON graph definition
### Problem: Generated Code Won't Compile
**Symptom:** PlatformIO build fails with C++ errors
**Check:**
1. Open `firmware/src/generated_effect.h`
2. Look at the actual generated code
3. Find the C++ syntax error
4. Trace back to which node generated it
**Fix:** Fix the code generation logic in `codegen/src/index.ts` for that node type
### Problem: FPS is Below 450
**Symptom:** `Serial.println` shows 200-300 FPS
**Check:**
1. Is LED transmission blocking? (Should use RMT non-blocking)
2. Are there `delay()` calls in the main loop?
3. Is audio processing interfering? (Should be on separate core)
4. Are there expensive operations in the render loop?
**Fix:** Profile where cycles are going, optimize the bottleneck
### Problem: Colors Look Wrong
**Symptom:** Pattern doesn't match expected visual
**Check:**
1. Is palette_interpolate generating correct keyframe data?
2. Are edge cases handled (first/last LED)?
3. Is the interpolation formula correct?
4. Are RGB values in 0.0-1.0 range (not 0-255)?
**Fix:** Debug the palette interpolation logic in generated C++
---
## Performance Characteristics
### Target: 450+ FPS for 180 LEDs
**Calculation:**
- 180 LEDs × 3 bytes × 8 bits = 4,320 bits per frame
- WS2812B: 800 kHz bitrate = 5.4 ms transmission time
- Remaining time per frame: 2.22ms - 5.4ms = -3.18ms? NO.
**Wait, how is 450 FPS possible?**
**Answer: Non-blocking transmission via RMT peripheral**
- Main loop renders next frame while RMT transmits current frame
- Dual-core: Core 0 does audio, Core 1 does graphics
- Zero blocking, pure parallel execution
**Breakdown per frame (180 LEDs):**
- Position calculation: ~360 cycles (2 cycles/LED)
- Palette interpolation: ~5,400 cycles (30 cycles/LED)
- Color assignment: ~360 cycles (2 cycles/LED)
- **Total: ~6,120 cycles = ~25 microseconds @ 240 MHz**
- Result: 40,000 FPS theoretical, 450 FPS actual (RMT transmission limit)
**The RMT transmission is the bottleneck, not the computation.**
---
## Extension Philosophy
### When to Add Node Types
**Ask these questions:**
1. Does this enable expressing emotions that weren't possible before?
2. Can I explain why this serves beauty, not just technical elegance?
3. Is the generated code simple enough to debug when it breaks?
4. Does this maintain the 450+ FPS target?
**If all four are YES:** Add it.
**If any are NO:** Don't.
### When to Add Features to Codegen
**Examples of GOOD additions:**
- Time-based modulation (enables animation)
- Audio-reactive nodes (enables music sync)
- Multi-layer blending (enables complex compositions)
**Examples of BAD additions:**
- "Complete" math library (complexity without purpose)
- Runtime graph switching (violates compilation principle)
- Visual debugger UI (sophistication without necessity)
---
## The Mission Connection
Every architectural decision serves the mission: **Prove flexibility and performance aren't opposites.**
**Compilation philosophy:** Flexibility at development time, performance at execution time
**Node system:** Composable primitives that maintain minimalism
**Code generation:** Transparent, debuggable, understandable
**Performance target:** Uncompromising 450+ FPS
If you find yourself adding complexity that doesn't serve this mission, stop. Delete it. Return to clarity.
---
## Phase B and Beyond
**Phase B: Expand node types** (10-15 types total)
- Time-based modulation
- Mathematical operators (add, multiply, sin, cos)
- Color blending and layering
**Phase C: Visual editor**
- Draw graphs instead of JSON
- Real-time preview
- One-click deploy
**Phase D: Audio reactivity**
- FFT nodes
- Beat detection nodes
- Audio-driven modulation
But everything must maintain:
- Zero runtime overhead
- 450+ FPS execution
- Minimalist architecture
- Service to beauty
---
## Final Truth
This architecture works because it respects both domains completely:
**Artistic domain:** Visual node graphs, intuitive composition, creative freedom
**Execution domain:** Compiled C++, native speed, zero overhead
The compilation step is the bridge. It's not a convenience—it's the insight that makes the entire system possible.
Understanding this deeply is what separates maintaining the code from owning the vision.
No comments yet. Be the first to comment!