Robo System Plan#
Date: 2025.09.16
Purpose: Create an automated brush system that can robotically execute brushes in non-interactive mode
Overview#
The robo command will create an automated drawing system that can import and execute existing brush files (like box.mjs) in a programmatic, non-interactive mode. This creates a "plotter-like" system that can generate procedural art using the existing brush ecosystem.
Core Concept#
robo box # Load box.mjs brush and run automatically
robo box fade:red-blue # Load box.mjs with color parameters
robo circle count:50 # Load circle.mjs and draw 50 circles
Architecture#
1. Robo Piece Structure (robo.mjs)#
// robo.mjs - Main robo system piece
function boot({ params, colon }) {
const brushName = params[0]; // e.g., "box"
const brushParams = params.slice(1); // e.g., ["fade:red-blue"]
const robotSettings = parseColon(colon); // e.g., speed:2, count:10
}
async function paint({ frame }) {
// Execute frame-by-frame robotic drawing
if (loadedBrush && robotState.active) {
executeRoboticFrame(frame);
}
}
2. Dynamic Brush Loading#
Based on research of disk.mjs loading mechanisms:
// Inside robo.mjs boot function
async function loadBrush(brushName) {
try {
const brushModule = await import(`../disks/${brushName}.mjs`);
return {
overlay: brushModule.overlay,
lift: brushModule.lift,
brush: brushModule.brush, // Legacy support
boot: brushModule.boot
};
} catch (err) {
console.error(`Failed to load brush: ${brushName}`, err);
return null;
}
}
3. Path Generation System#
The robo system needs to generate synthetic path data that mimics user interaction:
class RoboPathGenerator {
constructor() {
this.currentPath = [];
this.pathQueue = [];
}
// Generate geometric patterns
generateGrid(rows, cols, spacing) {
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
this.pathQueue.push(this.createBoxPath(
col * spacing,
row * spacing,
spacing * 0.8,
spacing * 0.8
));
}
}
}
generateSpiral(centerX, centerY, radius, steps) {
// Create spiral path
}
generateRandom(count, bounds) {
// Create random placement paths
}
createBoxPath(x, y, w, h) {
return {
type: 'drag',
startPoint: { x, y },
endPoint: { x: x + w, y: y + h },
duration: 60 // frames
};
}
}
4. Brush Execution Engine#
Mock the nopaint system's brush execution:
class RoboBrushExecutor {
constructor(brushModule, screen) {
this.brush = brushModule;
this.screen = screen;
this.mockSystem = this.createMockNopaintSystem();
}
createMockNopaintSystem() {
return {
nopaint: {
color: [255, 0, 0, 255], // Default red
brush: null,
finalDragBox: null,
buffer: null // Will be created
}
};
}
executePath(path, frame) {
const progress = frame / path.duration;
const currentBox = this.interpolateDragBox(path, progress);
// Mock the overlay call during drawing
if (progress < 1.0) {
this.mockSystem.nopaint.brush = { dragBox: currentBox };
this.callBrushFunction('overlay', {
mark: currentBox,
color: this.mockSystem.nopaint.color,
ink: this.createMockInk()
});
}
// Mock the lift call at completion
if (progress >= 1.0) {
this.mockSystem.nopaint.finalDragBox = currentBox;
this.callBrushFunction('lift', {
mark: currentBox,
color: this.mockSystem.nopaint.color,
ink: this.createMockInk()
});
return true; // Path complete
}
return false; // Path in progress
}
interpolateDragBox(path, progress) {
const { startPoint, endPoint } = path;
const currentEnd = {
x: startPoint.x + (endPoint.x - startPoint.x) * progress,
y: startPoint.y + (endPoint.y - startPoint.y) * progress
};
return {
x: startPoint.x,
y: startPoint.y,
w: currentEnd.x - startPoint.x,
h: currentEnd.y - startPoint.y
};
}
callBrushFunction(functionName, api) {
if (this.brush[functionName]) {
this.brush[functionName](api);
}
}
createMockInk() {
// Return ink function that interfaces with the robo rendering system
return (color) => ({
box: (mark, mode = 'fill') => {
this.renderBox(mark, color, mode);
return this; // Chainable
}
});
}
}
5. Robo Timing System with sim Function#
Based on AC disk API research, use the sim function for precise timing control:
// In robo.mjs - Main timing controller
let robotState = {
active: false,
currentPath: null,
pathQueue: [],
completedPaths: [],
frameCounter: 0,
speed: 1.0,
pattern: 'grid',
brushName: null
};
// 🧮 Sim (Runs once per logic frame at 120fps locked)
function sim({ simCount, needsPaint }) {
if (!robotState.active) return;
// Speed control: advance robot logic every N sim frames
const speedFrames = Math.max(1, Math.round(120 / (60 * robotState.speed)));
if (simCount % BigInt(speedFrames) === 0n) {
advanceRobotLogic();
needsPaint(); // Request paint update when robot state changes
}
// Update debug panel state
debugPanel.update(robotState);
}
function advanceRobotLogic() {
if (!robotState.currentPath && robotState.pathQueue.length > 0) {
// Start next path
robotState.currentPath = robotState.pathQueue.shift();
robotState.frameCounter = 0;
}
if (robotState.currentPath) {
robotState.frameCounter++;
// Calculate progress through current path
const progress = robotState.frameCounter / robotState.currentPath.duration;
if (progress >= 1.0) {
// Path complete - trigger lift
executePathCompletion(robotState.currentPath);
robotState.completedPaths.push(robotState.currentPath);
robotState.currentPath = null;
robotState.frameCounter = 0;
} else {
// Path in progress - trigger overlay
executePathProgress(robotState.currentPath, progress);
}
}
// Check if all paths complete
if (!robotState.currentPath && robotState.pathQueue.length === 0) {
robotState.active = false;
console.log("🤖 Robot drawing complete!");
}
}
Key sim Function Benefits:
- 120fps locked timing - Consistent logic updates regardless of display refresh rate
simCount- BigInt counter for precise frame trackingneedsPaint()- Request paint updates only when robot state changes (performance)- Speed control - Use modulo with simCount to control robot execution speed
- Frame precision - Perfect for smooth path interpolation and timing control
Support various robotic drawing modes:
const ROBO_MODES = {
// Grid patterns
'grid': { rows: 10, cols: 10, spacing: 50 },
'grid:5x3': { rows: 5, cols: 3, spacing: 80 },
// Organic patterns
'random': { count: 20, bounds: 'screen' },
'spiral': { center: 'screen', radius: 200, steps: 50 },
// Animation patterns
'wave': { amplitude: 100, frequency: 0.1 },
'orbit': { center: 'screen', radius: 150 }
};
// Example: robo box grid:5x3 fade:red-blue speed:2
6. Time Control#
class RoboTimeController {
constructor() {
this.speed = 1.0; // 1.0 = normal speed
this.pauseRequested = false;
this.stepMode = false;
}
// Speed control: robo box speed:0.5 (half speed)
setSpeed(multiplier) {
this.speed = Math.max(0.1, Math.min(10.0, multiplier));
}
// Step through frame by frame: robo box step
enableStepMode() {
this.stepMode = true;
this.speed = 0;
}
shouldAdvanceFrame() {
return !this.pauseRequested && (this.speed > 0 || this.stepMode);
}
}
7. Virtual Orange Robot Cursor#
Add a CSS virtual cursor that shows where the robot is currently drawing:
// Virtual cursor management
function updateVirtualCursor(x, y, isDrawing = false) {
let cursor = document.getElementById('robo-virtual-cursor');
if (!cursor) {
cursor = document.createElement('div');
cursor.id = 'robo-virtual-cursor';
cursor.style.cssText = `
position: absolute;
width: 12px;
height: 12px;
background: orange;
border: 2px solid white;
border-radius: 50%;
pointer-events: none;
z-index: 10000;
box-shadow: 0 0 4px rgba(255, 165, 0, 0.6);
transition: all 0.1s ease;
${isDrawing ? 'transform: scale(1.2);' : ''}
`;
document.body.appendChild(cursor);
}
// Position cursor at robot drawing location
cursor.style.left = `${x - 6}px`;
cursor.style.top = `${y - 6}px`;
cursor.style.opacity = robotState.active ? '1' : '0';
// Scale effect when drawing
cursor.style.transform = isDrawing ? 'scale(1.2)' : 'scale(1.0)';
}
function removeVirtualCursor() {
const cursor = document.getElementById('robo-virtual-cursor');
if (cursor) cursor.remove();
}
Virtual Cursor Features:
- Orange robot dot - Distinct from regular UI cursors
- Real-time position - Shows exact robot drawing location
- Drawing state feedback - Scales up when actively drawing
- Always visible - Appears even when browser cursor is elsewhere
- Smooth movement - CSS transitions for fluid robot motion
Similar to the nopaint performance overlay, create a visual debug panel:
class RoboDebugPanel {
constructor() {
this.visible = true;
this.position = { x: 10, y: 10 };
this.size = { w: 300, h: 200 };
}
render({ ink, write, screen }) {
if (!this.visible) return;
const { x, y } = this.position;
const { w, h } = this.size;
// Semi-transparent background panel
ink(0, 0, 0, 180).box(x, y, w, h);
ink(255, 255, 255, 80).box(x, y, w, h, "outline");
// Title
ink("cyan").write("🤖 ROBO DEBUG", x + 8, y + 16);
// Current state info
let lineY = y + 32;
const lineHeight = 12;
ink("white").write(`Brush: ${this.currentBrush || 'none'}`, x + 8, lineY);
lineY += lineHeight;
ink("yellow").write(`Pattern: ${this.currentPattern || 'none'}`, x + 8, lineY);
lineY += lineHeight;
ink("lime").write(`Speed: ${this.speed.toFixed(1)}x`, x + 8, lineY);
lineY += lineHeight;
ink("orange").write(`Frame: ${this.currentFrame}/${this.totalFrames}`, x + 8, lineY);
lineY += lineHeight;
// Progress bar
const progressW = w - 16;
const progress = this.totalFrames > 0 ? this.currentFrame / this.totalFrames : 0;
ink(50, 50, 50).box(x + 8, lineY, progressW, 8);
ink("cyan").box(x + 8, lineY, progressW * progress, 8);
lineY += 16;
// Current path info
if (this.currentPath) {
ink("magenta").write(`Path: ${this.currentPath.type}`, x + 8, lineY);
lineY += lineHeight;
ink("gray").write(`Start: ${this.currentPath.startPoint.x}, ${this.currentPath.startPoint.y}`, x + 8, lineY);
lineY += lineHeight;
ink("gray").write(`End: ${this.currentPath.endPoint.x}, ${this.currentPath.endPoint.y}`, x + 8, lineY);
lineY += lineHeight;
}
// Queue status
ink("white").write(`Queue: ${this.pathQueue.length} paths`, x + 8, lineY);
lineY += lineHeight;
// Mini grid visualization
this.renderMiniGrid(ink, x + 8, lineY, 100, 60);
}
renderMiniGrid(ink, x, y, w, h) {
// Background
ink(30, 30, 30).box(x, y, w, h);
ink(80, 80, 80).box(x, y, w, h, "outline");
// Grid lines
const gridSize = 10;
ink(60, 60, 60);
for (let gx = gridSize; gx < w; gx += gridSize) {
ink().line(x + gx, y, x + gx, y + h);
}
for (let gy = gridSize; gy < h; gy += gridSize) {
ink().line(x, y + gy, x + w, y + gy);
}
// Show completed paths as green dots
this.completedPaths?.forEach(path => {
const miniX = x + (path.startPoint.x / this.screenW) * w;
const miniY = y + (path.startPoint.y / this.screenH) * h;
ink("lime").circle(miniX, miniY, 2, true);
});
// Show current path as yellow
if (this.currentPath) {
const miniX = x + (this.currentPath.startPoint.x / this.screenW) * w;
const miniY = y + (this.currentPath.startPoint.y / this.screenH) * h;
ink("yellow").circle(miniX, miniY, 3, true);
}
// Show queued paths as gray dots
this.pathQueue?.forEach(path => {
const miniX = x + (path.startPoint.x / this.screenW) * w;
const miniY = y + (path.startPoint.y / this.screenH) * h;
ink("gray").circle(miniX, miniY, 1, true);
});
}
update(roboState) {
this.currentBrush = roboState.brushName;
this.currentPattern = roboState.pattern;
this.speed = roboState.speed;
this.currentFrame = roboState.frame;
this.totalFrames = roboState.totalFrames;
this.currentPath = roboState.currentPath;
this.pathQueue = roboState.pathQueue;
this.completedPaths = roboState.completedPaths;
this.screenW = roboState.screenWidth;
this.screenH = roboState.screenHeight;
}
toggle() {
this.visible = !this.visible;
}
}
Integration in main robo piece:
// In robo.mjs
let debugPanel = new RoboDebugPanel();
function paint({ ink, write, screen, keyboard }) {
// Toggle debug panel with 'd' key
if (keyboard.pressed.d) {
debugPanel.toggle();
}
// Execute robotic drawing
if (robotState.active) {
executeRoboticFrame();
}
// Update and render debug panel
debugPanel.update(robotState);
debugPanel.render({ ink, write, screen });
}
8. Visual Pattern Preview#
Show upcoming pattern visually:
function renderPatternPreview(ink, x, y, w, h) {
ink(0, 0, 0, 100).box(x, y, w, h);
// Show the complete pattern as wireframe
pathGenerator.pathQueue.forEach((path, index) => {
const alpha = Math.max(50, 255 - index * 10); // Fade distant paths
const previewX = x + (path.startPoint.x / screen.width) * w;
const previewY = y + (path.startPoint.y / screen.height) * h;
const previewW = (path.endPoint.x - path.startPoint.x) / screen.width * w;
const previewH = (path.endPoint.y - path.startPoint.y) / screen.height * h;
ink(100, 100, 255, alpha).box(previewX, previewY, previewW, previewH, "outline");
});
}
## Implementation TODO
1. **Create basic robo.mjs piece** (Start here!)
- Core structure with sim() function for 120fps logic timing
- Simple grid pattern generator (3x3 box grid)
- Robot state management with speed control
- Command parsing: `robo grid 3 box 0.5` (pattern, size, brush, speed)
- needsPaint() integration for performance
- Basic console logging for debug feedback
### Phase 2: Path Generation
1. Implement `RoboPathGenerator` class
2. Add basic patterns: grid, random, spiral
3. Create path interpolation system
4. **Add `RoboDebugPanel` with mini-grid visualization**
5. Test automated box drawing in patterns
### Phase 3: Advanced Features
1. Add time controls (speed, pause, step mode)
2. Implement complex patterns (waves, orbits)
3. Add parameter parsing for brush configuration
4. Support fade colors and brush-specific parameters
5. **Enhance debug panel with pattern preview and state visualization**
### Phase 4: Integration & Polish
1. Integrate with existing color system
2. Add recording/playback capabilities
3. Create export functionality for generated art
4. Add real-time control interface
## Technical Research Findings
### Brush Loading Pattern
- Use `await import()` for dynamic loading (found in `disk.mjs:4776`)
- Brushes export `overlay`, `lift`, and optionally `brush` functions
- Need to mock the nopaint system structure for brush execution
### Nopaint System Structure
- Brushes expect `mark` (dragBox coordinates) and `color` parameters
- `overlay` function shows preview during drawing
- `lift` function executes final drawing when stroke completes
- System provides `ink()` function for actual rendering
### Drawing Data Flow
- User interaction creates `dragBox` coordinates
- `dragBox` contains `{x, y, w, h}` rectangle data
- Brushes use this data to determine what/where to draw
- For robo mode, we synthesize this data programmatically
## Example Usage
```bash
# Basic automated box drawing
robo box
# Grid of red boxes with debug panel
robo box grid fade:red debug
# Spiral pattern with debug visualization
robo box spiral fade:blue-green speed:0.5 debug
# Random placement with custom count and mini-grid view
robo box random count:50 fade:rainbow debug
# Step-through mode for debugging (press 'd' to toggle panel)
robo box grid:3x3 step
Debug Panel Features:
- 🤖 Real-time robo state display
- 📊 Progress bar and frame counter
- 🗺️ Mini-grid showing completed/current/queued paths
- ⏱️ Speed and timing information
- 🎯 Current path coordinates and type
- 👀 Pattern preview wireframe overlay
Future Extensions#
- Brush Composition: Combine multiple brushes in sequence
- Physics Simulation: Add gravity, collision for realistic movement
- AI Integration: Use ML to generate interesting patterns
- Live Coding: Real-time pattern modification while drawing
- Network Sync: Coordinate multiple robo instances across devices
Benefits#
- Automated Art Generation: Create complex artworks without manual drawing
- Brush Testing: Systematically test brushes across different scenarios
- Pattern Libraries: Build reusable pattern generation systems
- Educational Tool: Demonstrate brush behavior and geometric concepts
- Performance Art: Live automated drawing performances
This system would essentially create a "turtle graphics" style plotter that can use any existing brush in the Aesthetic Computer ecosystem, opening up powerful possibilities for procedural art generation and automated drawing systems.