summaryrefslogtreecommitdiff
path: root/doc/archive
diff options
context:
space:
mode:
Diffstat (limited to 'doc/archive')
-rw-r--r--doc/archive/GEOM_BUFFER.md229
-rw-r--r--doc/archive/GPU_PROCEDURAL_PHASE4.md70
-rw-r--r--doc/archive/SHADER_REUSE_INVESTIGATION.md241
-rw-r--r--doc/archive/SPECTRAL_BRUSH_EDITOR.md195
-rw-r--r--doc/archive/WORKSPACE_SYSTEM.md576
5 files changed, 1311 insertions, 0 deletions
diff --git a/doc/archive/GEOM_BUFFER.md b/doc/archive/GEOM_BUFFER.md
new file mode 100644
index 0000000..0188125
--- /dev/null
+++ b/doc/archive/GEOM_BUFFER.md
@@ -0,0 +1,229 @@
+# Geometry Buffer Design [IN PROGRESS]
+
+**Status:** Ideation phase
+**Goal:** Efficient G-buffer for deferred rendering in 64k demo
+
+---
+
+## Overview
+
+Replace direct rendering with geometry buffer accumulation for advanced post-processing and lighting.
+
+**Target:** 8-10 bytes/pixel, 16-bit precision
+
+---
+
+## Buffer Elements
+
+### Core Attributes
+
+| Attribute | Channels | Precision | Source |
+|-----------|----------|-----------|--------|
+| Albedo (RGB) | 3 | f16 | Material/procedural |
+| Roughness | 1 | u8/u16 | PBR material property |
+| Metallic | 1 | u8/u16 | PBR material property |
+| Normal (XYZ) | 2 | f16 | Octahedral encoding |
+| Depth | 1 | f16/f32 | 1/z for precision |
+| Object/Material ID | 1 | u16 | Rasterization/SDF |
+| Transparency | 1 | u8/u16 | Alpha channel |
+
+### Optional/Derived
+
+| Attribute | Storage | Notes |
+|-----------|---------|-------|
+| Depth gradient | On-demand | Compute from depth (Sobel) |
+| Laplacian | On-demand | Second derivative of depth |
+| Motion vectors | 2×f16 | Screen-space XY |
+| AO | 1×f16 | Ambient occlusion |
+
+**Key insight:** Depth derivatives cheaper to compute than store (2-4 bytes/pixel saved).
+
+---
+
+## Packing Strategies
+
+### Traditional Multi-Render-Target (MRT)
+
+```
+RT0 (RGBA16): Albedo.rgb + Roughness (packed with metallic)
+RT1 (RG16): Octahedral normal (2 channels encode XYZ)
+RT2 (R32F): 1/z depth (or use hardware depth buffer)
+RT3 (RG16): Motion vectors XY
+RT4 (R16UI): Object/Material ID
+```
+
+**Total:** 4-5 render targets = 8-10 bytes/pixel
+
+### Compute Shader + Storage Buffer (RECOMMENDED)
+
+**Advantages:**
+- Custom bit-packing (not bound to RGBA formats)
+- Compute derivatives in-pass (depth gradient, Laplacian)
+- Cache-optimized tiling (Morton order)
+- No MRT limits (store 20+ attributes)
+
+**Tradeoffs:**
+- No hardware depth/early-Z during G-buffer generation
+- Manual atomics if pixel overdraw
+- Lose ROPs hardware optimizations
+
+**Struct Layout:**
+```cpp
+struct GBufferPixel {
+ u32 packed_normal; // Octahedral 16+16
+ u32 rgba_rough; // RGBA8 + Roughness8 + Metallic8 (26 bits used)
+ f16 inv_z; // 1/z depth
+ u16 material_id; // Object/material
+ // Total: 12 bytes/pixel
+};
+
+// Compressed variant (8 bytes):
+struct CompactGBuffer {
+ u32 normal_depth; // Oct16 normal + u16 quantized depth
+ u32 rgba_params; // RGB565 + Rough4 + Metal4 + Flags4
+};
+```
+
+**Access Pattern:**
+```wgsl
+@group(0) @binding(0) var<storage, read_write> g_buffer: array<GBufferPixel>;
+
+fn write_gbuffer(pixel_id: u32, data: SurfaceData) {
+ g_buffer[pixel_id].packed_normal = pack_octahedral(data.normal);
+ g_buffer[pixel_id].rgba_rough = pack_rgba8(data.albedo) | (u32(data.roughness * 255.0) << 24);
+ g_buffer[pixel_id].inv_z = f16(1.0 / data.depth);
+ g_buffer[pixel_id].material_id = data.id;
+}
+```
+
+---
+
+## Normal Encoding
+
+**Octahedral mapping** (most efficient for 2-channel storage):
+- Encodes unit sphere normal to 2D square
+- 16-bit per channel = good precision
+- Fast encode/decode (no trig)
+
+```cpp
+vec2 octahedral_encode(vec3 n) {
+ n /= (abs(n.x) + abs(n.y) + abs(n.z));
+ vec2 p = n.z >= 0.0 ? n.xy : (1.0 - abs(n.yx)) * sign(n.xy);
+ return p * 0.5 + 0.5; // [0, 1]
+}
+
+vec3 octahedral_decode(vec2 p) {
+ p = p * 2.0 - 1.0; // [-1, 1]
+ vec3 n = vec3(p.x, p.y, 1.0 - abs(p.x) - abs(p.y));
+ float t = max(-n.z, 0.0);
+ n.x += n.x >= 0.0 ? -t : t;
+ n.y += n.y >= 0.0 ? -t : t;
+ return normalize(n);
+}
+```
+
+---
+
+## Depth Storage
+
+**1/z (inverse depth):**
+- Better precision distribution (more bits near camera)
+- Linear in screen space
+- Matches perspective projection
+
+**Alternatives:**
+- Logarithmic depth (even better precision)
+- Hardware depth buffer (R32F, free with render targets)
+
+---
+
+## Material Properties
+
+**Roughness/Metallic are NOT geometry:**
+- **Source:** Texture lookups, procedural noise, or constants
+- **Not bump-mapping:** Bump/normal maps perturb normals (geometry)
+- **PBR properties:** Control light interaction (0=smooth/dielectric, 1=rough/metal)
+
+**Demoscene approach:** Procedural generation or baked constants (avoid textures).
+
+---
+
+## Post-Processing Derivatives
+
+**Compute on-demand** (cheaper than storing):
+
+```wgsl
+// Depth gradient (Sobel filter)
+fn depth_gradient(uv: vec2f) -> vec2f {
+ let dx = textureLoad(depth, uv + vec2(1,0)) - textureLoad(depth, uv - vec2(1,0));
+ let dy = textureLoad(depth, uv + vec2(0,1)) - textureLoad(depth, uv - vec2(0,1));
+ return vec2(dx, dy) * 0.5;
+}
+
+// Laplacian (edge detection)
+fn laplacian(uv: vec2f) -> f32 {
+ let c = textureLoad(depth, uv);
+ let n = textureLoad(depth, uv + vec2(0,1));
+ let s = textureLoad(depth, uv - vec2(0,1));
+ let e = textureLoad(depth, uv + vec2(1,0));
+ let w = textureLoad(depth, uv - vec2(1,0));
+ return (n + s + e + w) - 4.0 * c;
+}
+```
+
+---
+
+## Integration with Hybrid Renderer
+
+**Current:** Hybrid SDF raymarching + rasterized proxy geometry
+**Future:** Both write to unified G-buffer
+
+```cpp
+// Rasterization pass
+void rasterize_geometry() {
+ // Vertex shader → fragment shader
+ // Write to G-buffer (compute or MRT)
+}
+
+// SDF raymarching pass (compute)
+void raymarch_sdf() {
+ // Per-pixel ray march
+ // Write to same G-buffer at hit points
+}
+
+// Deferred lighting pass
+void deferred_lighting() {
+ // Read G-buffer
+ // Apply PBR lighting, shadows, etc.
+}
+```
+
+**Atomics handling:** Use depth test or tile-based sorting to avoid conflicts.
+
+---
+
+## Size Budget
+
+**Target:** 1920×1080 @ 8 bytes/pixel = **16 MB**
+**Compressed:** 1920×1080 @ 6 bytes/pixel = **12 MB**
+
+**Acceptable for 64k demo:** RAM usage OK, not binary size.
+
+---
+
+## Next Steps
+
+1. Prototype compute shader G-buffer writer
+2. Implement octahedral normal encoding
+3. Test SDF + raster unified writes
+4. Add deferred lighting pass
+5. Validate depth derivative quality (gradient/Laplacian)
+6. Optimize packing (aim for 6-8 bytes/pixel)
+
+---
+
+## References
+
+- Octahedral mapping: "Survey of Efficient Representations for Independent Unit Vectors" (Meyer et al.)
+- PBR theory: "Physically Based Rendering" (Pharr, Jakob, Humphreys)
+- G-buffer design: "Deferred Rendering in Killzone 2" (Valient, 2007)
diff --git a/doc/archive/GPU_PROCEDURAL_PHASE4.md b/doc/archive/GPU_PROCEDURAL_PHASE4.md
new file mode 100644
index 0000000..4cfc271
--- /dev/null
+++ b/doc/archive/GPU_PROCEDURAL_PHASE4.md
@@ -0,0 +1,70 @@
+# GPU Procedural Phase 4: Texture Composition
+
+**Status:** ✅ Complete
+
+## Implementation
+
+Multi-input composite shaders with configurable sampler support.
+
+### API
+
+```cpp
+enum class SamplerType {
+ LinearClamp, LinearRepeat, NearestClamp, NearestRepeat
+};
+
+void create_gpu_composite_texture(
+ const std::string& name,
+ const std::string& shader_func,
+ const char* shader_code,
+ const void* uniform_data,
+ size_t uniform_size,
+ int width, int height,
+ const std::vector<std::string>& input_names,
+ SamplerType sampler = SamplerType::LinearClamp);
+```
+
+### Shaders
+
+**gen_blend.wgsl** - Blend two textures with lerp factor:
+- Bindings: output (0), uniform (1), input_a (2), input_b (3), sampler (4)
+- Uniform: `{u32 width, height; f32 blend_factor, _pad0}`
+
+**gen_mask.wgsl** - Multiply textures (masking):
+- Bindings: output (0), uniform (1), input_a (2), input_b (3), sampler (4)
+- Uniform: `{u32 width, height}`
+
+### Usage
+
+```cpp
+extern const char* gen_blend_compute_wgsl;
+
+struct { uint32_t width, height; float blend_factor, _pad0; } uni = {256, 256, 0.5f, 0.0f};
+
+tex_mgr.create_gpu_composite_texture(
+ "blended", "gen_blend", gen_blend_compute_wgsl,
+ &uni, sizeof(uni), 256, 256,
+ {"noise_a", "noise_b"},
+ SamplerType::LinearClamp);
+```
+
+### Features
+
+- **Dynamic bind groups:** N input textures + 1 sampler
+- **Lazy sampler creation:** Map-based cache, 4 preset types
+- **Multi-stage composition:** Composite of composites supported
+- **Guarded with `#if !defined(STRIP_GPU_COMPOSITE)`**
+
+### Size Impact
+
+- Code: ~460 lines added
+- Compressed: ~830 bytes (2 shaders + dispatch logic)
+
+### Tests
+
+`test_gpu_composite.cc`:
+- Blend two noise textures
+- Mask noise with grid
+- Multi-stage composite (composite of composites)
+
+All 35 tests passing.
diff --git a/doc/archive/SHADER_REUSE_INVESTIGATION.md b/doc/archive/SHADER_REUSE_INVESTIGATION.md
new file mode 100644
index 0000000..e840126
--- /dev/null
+++ b/doc/archive/SHADER_REUSE_INVESTIGATION.md
@@ -0,0 +1,241 @@
+# Shader Code Reuse Investigation
+
+## ✅ Implementation Status
+
+**Date:** February 13, 2026
+**Solution:** Option 1 - Shared Common Directory
+**Status:** IMPLEMENTED
+
+**Results:**
+- Created `src/shaders/` with 20 shared shader files (moved from `common/shaders/` 2026-02-28)
+- Eliminated 36 duplicate files across workspaces
+- Asset references use `../../src/shaders/...`
+- Enhanced asset_packer with filesystem path normalization
+- Build passes, all tests pass
+
+See `doc/FILE_HIERARCHY_CLEANUP_2026-02-13.md` for full details.
+
+---
+
+## Investigation (Historical)
+
+### Current State
+
+### Duplication Analysis
+- **36 duplicate shader files** between main and test workspaces
+- Common files include: math utilities, render helpers, compute shaders, uniforms
+- Files are byte-identical duplicates (verified)
+
+### Current System Architecture
+1. **ShaderComposer**: Runtime shader composition via `#include` directives
+2. **Asset System**: Shaders loaded from workspace `assets.txt`
+3. **Registration**: `InitShaderComposer()` registers snippets with paths like:
+ - `common_uniforms`
+ - `math/sdf_shapes`, `math/noise`, `math/common_utils`
+ - `render/shadows`, `render/scene_query_bvh`, `render/lighting_utils`
+
+### Duplicate Common Shaders
+```
+Common across both workspaces:
+- common_uniforms.wgsl
+- math/{common_utils, noise, sdf_shapes, sdf_utils}.wgsl
+- render/{lighting_utils, scene_query_bvh, scene_query_linear, shadows}.wgsl
+- compute/{gen_blend, gen_grid, gen_mask, gen_noise, gen_perlin}.wgsl
+- Various effect shaders (passthrough, lighting, ray_box, etc.)
+```
+
+---
+
+## Proposed Approaches
+
+### Option 1: Shared Common Directory (Original Design)
+**Structure:**
+```
+/common/
+ /shaders/
+ /math/ # Shared math utilities
+ /render/ # Shared rendering helpers
+ /compute/ # Shared compute shaders
+ common_uniforms.wgsl
+
+/workspaces/
+ /main/
+ /shaders/ # Workspace-specific only
+ /music/
+ /weights/
+ /obj/
+```
+
+**Pros:**
+- Single source of truth for common code
+- No duplication
+- Clear separation: common vs workspace-specific
+- Matches original design doc intent
+
+**Cons:**
+- Breaks workspace isolation (workspaces depend on external directory)
+- Harder to version control per-workspace
+- Need to update asset packer to handle cross-workspace references
+- Complicates workspace portability
+
+---
+
+### Option 2: Symbolic Links
+**Structure:**
+```
+/workspaces/
+ /main/
+ /shaders/
+ /common@ -> ../../common/shaders/ # Symlink
+ custom_effect.wgsl
+```
+
+**Pros:**
+- Maintains single source
+- Works with existing asset system
+- Simple filesystem solution
+
+**Cons:**
+- Windows symlink issues (requires admin or dev mode)
+- Git symlink handling varies
+- Still breaks workspace isolation
+- Can confuse developers
+
+---
+
+### Option 3: Build-Time Copy/Sync
+**Structure:**
+Workspaces keep duplicates, but sync from canonical source at build time.
+
+```
+/common/shaders/ # Canonical source
+/workspaces/*/shaders/ # Build-synced copies
+```
+
+**Implementation:**
+- CMake script copies common/ → workspaces/ before asset packing
+- Or: Pre-commit hook validates consistency
+
+**Pros:**
+- Workspaces remain self-contained
+- No runtime dependencies
+- Works across all platforms
+- Git shows actual files
+
+**Cons:**
+- Easy to forget sync
+- Merge conflicts if edited in workspace
+- Need tooling to maintain consistency
+
+---
+
+### Option 4: Asset System Extension
+**Enhance asset packer to support cross-workspace includes:**
+
+`workspaces/main/assets.txt`:
+```
+SHADER_COMMON_UTILS, NONE, @common/shaders/math/common_utils.wgsl
+SHADER_CUSTOM, NONE, shaders/custom_effect.wgsl
+```
+
+**Pros:**
+- Clean at asset definition level
+- ShaderComposer unchanged
+- Explicit common vs local
+
+**Cons:**
+- Asset packer modification needed
+- New syntax to learn
+- Breaks workspace portability
+
+---
+
+### Option 5: Status Quo + Documentation
+**Keep duplicates, document as intentional.**
+
+Add `common/shaders/` as reference templates that workspaces copy.
+
+**Pros:**
+- Workspaces fully independent
+- Can diverge per-workspace if needed
+- No build system changes
+- Simple mental model
+
+**Cons:**
+- Ongoing duplication
+- Manual sync required
+- Disk space (minimal impact)
+- Bug fixes need multi-workspace updates
+
+---
+
+## Recommendation Matrix
+
+| Criteria | Opt 1 | Opt 2 | Opt 3 | Opt 4 | Opt 5 |
+|----------|-------|-------|-------|-------|-------|
+| No duplication | ✓ | ✓ | ✗ | ✓ | ✗ |
+| Workspace isolation | ✗ | ✗ | ✓ | ✗ | ✓ |
+| Cross-platform | ✓ | ✗ | ✓ | ✓ | ✓ |
+| Low complexity | ✗ | ✓ | ✗ | ✗ | ✓ |
+| Easy maintenance | ✓ | ✓ | ✗ | ✓ | ✗ |
+
+---
+
+## Current Implementation Details
+
+### How Shaders Are Currently Included
+
+**In WGSL files:**
+```wgsl
+#include "common_uniforms"
+#include "math/sdf_shapes"
+#include "render/shadows"
+```
+
+**In C++ (shaders.cc):**
+```cpp
+void InitShaderComposer() {
+ auto& sc = ShaderComposer::Get();
+ sc.RegisterSnippet("common_uniforms", ...);
+ sc.RegisterSnippet("math/sdf_shapes", ...);
+ sc.RegisterSnippet("render/shadows", ...);
+}
+```
+
+**Asset Registration (assets.txt):**
+```
+SHADER_MATH_SDF_SHAPES, NONE, shaders/math/sdf_shapes.wgsl
+SHADER_RENDER_SHADOWS, NONE, shaders/render/shadows.wgsl
+```
+
+### ShaderComposer Flow
+1. Assets loaded from workspace `assets.txt`
+2. Snippets registered in `InitShaderComposer()`
+3. Effects call `Compose()` with dependencies
+4. `#include` directives recursively resolved
+5. Final shader string assembled
+
+---
+
+## Next Steps
+
+1. **Decide on approach** based on project priorities:
+ - Simplicity → Option 5
+ - True reuse → Option 1 or 4
+ - Compatibility → Option 3
+
+2. **If Option 1 chosen:**
+ - Create `/common/shaders/` with canonical sources
+ - Update `workspace.cfg` to reference common
+ - Update asset packer to resolve `../common/` paths
+ - Update docs
+
+3. **If Option 3 chosen:**
+ - Create sync script in `scripts/`
+ - Add to build process
+ - Document workflow
+
+4. **If Option 5 chosen:**
+ - Create `/common/shaders/` as templates
+ - Add `doc/SHADER_COMMON.md` explaining pattern
+ - Accept duplication as intentional
diff --git a/doc/archive/SPECTRAL_BRUSH_EDITOR.md b/doc/archive/SPECTRAL_BRUSH_EDITOR.md
new file mode 100644
index 0000000..a7d0e3a
--- /dev/null
+++ b/doc/archive/SPECTRAL_BRUSH_EDITOR.md
@@ -0,0 +1,195 @@
+# Spectral Brush Editor (Task #5)
+
+## Concept
+
+Replace large `.spec` assets with procedural C++ code (50-100× compression).
+
+**Before:** 5 KB binary `.spec` file
+**After:** ~100 bytes C++ code calling `draw_bezier_curve()`
+
+**Workflow:**
+```
+.wav → Load in editor → Trace with Bezier curves → Export procedural_params.txt + C++ code
+```
+
+---
+
+## Core Primitive: "Spectral Brush"
+
+### 1. Central Curve (Bezier)
+Traces time-frequency path: `{freq_bin, amplitude} = bezier(frame_number)`
+
+**Example control points:**
+```javascript
+[
+ {frame: 0, freq_hz: 200.0, amplitude: 0.9}, // Attack
+ {frame: 20, freq_hz: 80.0, amplitude: 0.7}, // Sustain
+ {frame: 100, freq_hz: 50.0, amplitude: 0.0} // Decay
+]
+```
+
+### 2. Vertical Profile
+Shapes "brush stroke" around curve at each frame.
+
+**Profile Types:**
+- **Gaussian**: `exp(-(dist² / σ²))` - Musical tones, bass
+- **Decaying Sinusoid**: `exp(-decay * dist) * cos(ω * dist)` - Metallic sounds
+- **Noise**: `random(seed, dist) * amplitude` - Hi-hats, cymbals
+- **Composite**: Combine multiple profiles (add/subtract/multiply)
+
+---
+
+## File Formats
+
+### A. `procedural_params.txt` (Human-readable)
+```text
+METADATA dct_size=512 num_frames=100 sample_rate=32000
+
+CURVE bezier
+ CONTROL_POINT 0 200.0 0.9
+ CONTROL_POINT 20 80.0 0.7
+ CONTROL_POINT 100 50.0 0.0
+ PROFILE gaussian sigma=30.0
+END_CURVE
+```
+
+### B. C++ Code (Ready to compile)
+```cpp
+#include "audio/spectral_brush.h"
+
+void gen_kick_procedural(float* spec, int dct_size, int num_frames) {
+ const float frames[] = {0.0f, 20.0f, 100.0f};
+ const float freqs[] = {200.0f, 80.0f, 50.0f};
+ const float amps[] = {0.9f, 0.7f, 0.0f};
+
+ draw_bezier_curve(spec, dct_size, num_frames,
+ frames, freqs, amps, 3,
+ PROFILE_GAUSSIAN, 30.0f);
+}
+```
+
+---
+
+## C++ Runtime API
+
+### Files: `src/audio/spectral_brush.{h,cc}`
+
+**Key functions:**
+```cpp
+enum ProfileType {
+ PROFILE_GAUSSIAN,
+ PROFILE_DECAYING_SINUSOID,
+ PROFILE_NOISE
+};
+
+void draw_bezier_curve(float* spectrogram, int dct_size, int num_frames,
+ const float* control_frames,
+ const float* control_freqs_hz,
+ const float* control_amps,
+ int n_control_points,
+ ProfileType profile_type,
+ float profile_param1,
+ float profile_param2 = 0.0f);
+
+float evaluate_bezier_linear(const float* control_frames,
+ const float* control_values,
+ int n_points,
+ float frame);
+
+float evaluate_profile(ProfileType type, float distance,
+ float param1, float param2);
+```
+
+---
+
+## Editor UI
+
+### Technology Stack
+- HTML5 Canvas (visualization)
+- Web Audio API (playback)
+- Pure JavaScript (no dependencies)
+- Reuse `dct.js` from existing editor
+
+### Key Features
+- Dual-layer canvas (reference + procedural spectrograms)
+- Drag control points to adjust curves
+- Real-time spectrogram rendering
+- Audio playback (keys 1/2 for procedural/original)
+- Undo/Redo (Ctrl+Z, Ctrl+Shift+Z)
+- Load .wav/.spec, save params, generate C++ code
+
+### Keyboard Shortcuts
+| Key | Action |
+|-----|--------|
+| 1 | Play procedural |
+| 2 | Play original |
+| Space | Play/pause |
+| Delete | Remove control point |
+| Ctrl+Z | Undo |
+| Ctrl+S | Save params |
+
+---
+
+## Implementation Phases
+
+### Phase 1: C++ Runtime
+**Files:** `src/audio/spectral_brush.{h,cc}`, `src/tests/test_spectral_brush.cc`
+
+**Tasks:**
+- Define API (ProfileType, draw_bezier_curve, evaluate_profile)
+- Implement linear Bezier interpolation
+- Implement Gaussian profile
+- Add unit tests
+- **Deliverable:** Compiles, tests pass
+
+### Phase 2: Editor Core
+**Files:** `tools/spectral_editor/{index.html, script.js, style.css, dct.js}`
+
+**Tasks:**
+- HTML structure (canvas, controls, file input)
+- Bezier curve editor (place/drag/delete control points)
+- Dual-layer canvas rendering
+- Real-time spectrogram generation
+- Audio playback (IDCT → Web Audio API)
+- Undo/Redo system
+- **Deliverable:** Interactive editor, can trace .wav files
+
+### Phase 3: File I/O
+**Tasks:**
+- Load .wav (decode, STFT → spectrogram)
+- Load .spec (binary parser)
+- Save procedural_params.txt (text format)
+- Generate C++ code (template)
+- Load procedural_params.txt (re-editing)
+- **Deliverable:** Full save/load cycle
+
+---
+
+## Design Decisions
+
+- **Bezier:** Linear interpolation (Phase 1), cubic later
+- **Profiles:** Gaussian only (Phase 1), others later
+- **Parameters:** Soft UI limits, no enforced bounds
+- **RNG:** Home-brew deterministic (small, repeatable)
+- **Code gen:** Single function per sound (generic loader later)
+
+---
+
+## Size Impact
+
+**Example: Kick drum**
+
+**Before (Binary):**
+- 512 bins × 100 frames × 4 bytes = 200 KB uncompressed
+- ~5 KB compressed (zlib)
+
+**After (Procedural):**
+- 4 control points × 3 arrays × 4 floats = ~48 bytes data
+- Function call overhead = ~20 bytes
+- **Total: ~100 bytes** (50-100× reduction)
+
+**Trade-off:** Runtime CPU cost, acceptable for 64k demo.
+
+---
+
+*See TODO.md for detailed implementation tasks.*
diff --git a/doc/archive/WORKSPACE_SYSTEM.md b/doc/archive/WORKSPACE_SYSTEM.md
new file mode 100644
index 0000000..6b4319d
--- /dev/null
+++ b/doc/archive/WORKSPACE_SYSTEM.md
@@ -0,0 +1,576 @@
+# Workspace System (Task #77) [COMPLETED]
+
+## Status
+
+**Implemented:** Feb 9, 2026
+
+Workspace system is now active. Use `cmake -B build -DDEMO_WORKSPACE=<name>` to select workspace.
+
+## Overview
+
+Self-contained demo workspaces for parallel development and clean content separation.
+
+**Motivation:** Current structure scatters demo content across multiple locations:
+- `assets/demo.seq` - Timeline
+- `assets/music.track` - Audio
+- `assets/final/demo_assets.txt` - Asset list
+- `assets/final/music/*.spec` - Audio samples
+- `assets/shaders/*.wgsl` - Shaders (some demo-specific, some shared)
+
+This makes it hard to:
+- Work on multiple demos simultaneously
+- Understand what files belong to which demo
+- Share common resources cleanly
+- Onboard new developers
+
+## Problem Statement
+
+**Current structure (flat):**
+```
+/assets/
+ demo.seq # Main demo timeline
+ music.track # Main demo music
+ test_demo.seq # Test demo timeline
+ test_demo.track # Test demo music
+ /final/
+ demo_assets.txt # Main demo assets
+ /music/*.spec # Audio samples (mixed usage)
+ /shaders/
+ *.wgsl # Mixed: common + demo-specific
+```
+
+**Issues:**
+1. No clear ownership of files
+2. Can't easily switch between demos
+3. Assets intermingled (demo vs test)
+4. Shaders hard to categorize (shared vs demo-specific)
+5. Adding new demos requires scattered changes
+
+## Current Structure (Implemented)
+
+### Workspace Directory Layout
+
+```
+/workspaces/
+ /main/ # Main production demo
+ workspace.cfg # Workspace metadata
+ timeline.seq # Visual effects
+ music.track # Audio patterns
+ assets.txt # Asset list
+ /music/*.spec # Audio samples
+ /weights/*.bin # CNN binary weights
+ /obj/*.obj # 3D models
+ /shaders/ # Workspace-specific shaders only
+ custom_effect.wgsl
+
+ /test/ # Test/validation demo
+ workspace.cfg
+ timeline.seq
+ music.track
+ assets.txt
+ /music/
+ /weights/
+ /obj/
+ /shaders/
+
+/common/ # Shared resources
+ /shaders/
+ /math/ # Shared math utilities
+ common_utils.wgsl
+ noise.wgsl
+ sdf_shapes.wgsl
+ sdf_utils.wgsl
+ /render/ # Shared rendering helpers
+ lighting_utils.wgsl
+ scene_query_bvh.wgsl
+ scene_query_linear.wgsl
+ shadows.wgsl
+ /compute/ # Shared compute shaders
+ gen_blend.wgsl
+ gen_grid.wgsl
+ gen_mask.wgsl
+ gen_noise.wgsl
+ gen_perlin.wgsl
+ common_uniforms.wgsl
+ lighting.wgsl
+ passthrough.wgsl
+ ray_box.wgsl
+ ray_triangle.wgsl
+ sdf_primitives.wgsl
+ skybox.wgsl
+
+/tools/
+ originals/ # Source audio files (.wav, .aif)
+```
+
+### Workspace Configuration
+
+**`workspace.cfg` format:**
+```ini
+[workspace]
+name = "Main Demo"
+description = "Production 64k demo"
+version = "1.0"
+
+[build]
+target = "demo64k"
+timeline = "timeline.seq"
+music = "music.track"
+assets = "assets.txt"
+asset_dirs = ["music/", "weights/", "obj/"]
+shader_dirs = ["shaders/"]
+
+[build]
+# Output binary name
+target = "demo64k"
+
+# Include paths (relative to workspace root)
+timeline = "timeline.seq"
+music = "music.track"
+assets = "assets.txt"
+
+# Asset directories
+asset_dirs = ["assets/", "../common/audio/"]
+
+# Shader directories (order matters: workspace-specific first)
+shader_dirs = ["shaders/", "../src/shaders/"]
+
+[options]
+# Default resolution
+width = 1280
+height = 720
+
+# Duration (seconds, -1 = auto-detect)
+duration = -1
+
+# BPM for timeline
+bpm = 120
+```
+
+## Architecture
+
+### Build System Integration
+
+**CMakeLists.txt changes:**
+```cmake
+# Select active workspace (default: main)
+set(DEMO_WORKSPACE "main" CACHE STRING "Active workspace")
+
+# Parse workspace config
+set(WORKSPACE_DIR "${CMAKE_SOURCE_DIR}/workspaces/${DEMO_WORKSPACE}")
+set(WORKSPACE_CFG "${WORKSPACE_DIR}/workspace.cfg")
+
+# Read config and set variables
+parse_workspace_config(${WORKSPACE_CFG}
+ TIMELINE_PATH
+ MUSIC_PATH
+ ASSETS_PATH
+ ASSET_DIRS
+ SHADER_DIRS
+ TARGET_NAME
+)
+
+# Generate assets from workspace
+add_custom_command(
+ OUTPUT ${GEN_ASSETS_H}
+ COMMAND asset_packer ${WORKSPACE_DIR}/${ASSETS_PATH} ...
+ DEPENDS ${WORKSPACE_DIR}/${ASSETS_PATH}
+)
+
+# Generate timeline from workspace
+add_custom_command(
+ OUTPUT ${GEN_TIMELINE_CC}
+ COMMAND seq_compiler ${WORKSPACE_DIR}/${TIMELINE_PATH} ...
+ DEPENDS ${WORKSPACE_DIR}/${TIMELINE_PATH}
+)
+
+# Add shader include paths from workspace
+foreach(SHADER_DIR ${SHADER_DIRS})
+ list(APPEND SHADER_INCLUDE_DIRS "${WORKSPACE_DIR}/${SHADER_DIR}")
+endforeach()
+```
+
+**Building specific workspace:**
+```bash
+# Default (main workspace)
+cmake -S . -B build
+cmake --build build -j4
+
+# Specific workspace
+cmake -S . -B build_test -DDEMO_WORKSPACE=test
+cmake --build build_test -j4
+
+# Experimental workspace
+cmake -S . -B build_exp -DDEMO_WORKSPACE=experiments/demo2024_revision
+cmake --build build_exp -j4
+```
+
+### Asset Packer Changes
+
+**Support workspace-relative paths:**
+```cpp
+// asset_packer.cc
+void PackerContext::resolve_asset_path(const char* rel_path) {
+ // Try workspace-local first
+ std::string workspace_path = workspace_dir + "/" + rel_path;
+ if (file_exists(workspace_path)) {
+ return workspace_path;
+ }
+
+ // Try common assets
+ std::string common_path = "assets/common/" + rel_path;
+ if (file_exists(common_path)) {
+ return common_path;
+ }
+
+ error("Asset not found: %s", rel_path);
+}
+```
+
+### Shader Composer Changes
+
+**Support multiple include paths:**
+```cpp
+// shader_composer.cc
+class ShaderComposer {
+ std::vector<std::string> include_paths_;
+
+ void add_include_path(const char* path) {
+ include_paths_.push_back(path);
+ }
+
+ std::string resolve_include(const char* filename) {
+ for (const auto& base : include_paths_) {
+ std::string full = base + "/" + filename;
+ if (file_exists(full)) {
+ return full;
+ }
+ }
+ error("Shader include not found: %s", filename);
+ }
+};
+```
+
+**Shader includes with search paths:**
+```wgsl
+// workspace-specific shader
+#include "custom_uniforms.wgsl" // From workspaces/main/shaders/
+#include "math/common_utils.wgsl" // From src/shaders/
+```
+
+### CLI Tool
+
+**`scripts/workspace.sh` - Workspace management:**
+```bash
+#!/bin/bash
+# workspace.sh - Manage demo workspaces
+
+case "$1" in
+ list)
+ # List all workspaces
+ ls -1 workspaces/
+ ;;
+
+ create)
+ # Create new workspace from template
+ NAME=$2
+ mkdir -p workspaces/$NAME/{assets,shaders}
+ cp templates/workspace.cfg workspaces/$NAME/
+ echo "Created workspace: $NAME"
+ ;;
+
+ build)
+ # Build specific workspace
+ NAME=$2
+ cmake -S . -B build_$NAME -DDEMO_WORKSPACE=$NAME
+ cmake --build build_$NAME -j4
+ ;;
+
+ info)
+ # Show workspace details
+ NAME=$2
+ cat workspaces/$NAME/workspace.cfg
+ ;;
+esac
+```
+
+**Usage:**
+```bash
+./scripts/workspace.sh list
+./scripts/workspace.sh create my_demo
+./scripts/workspace.sh build main
+./scripts/workspace.sh info test
+```
+
+## Implementation Plan
+
+### Phase 1: Create Workspace Structure
+
+**1.1 Create directory tree:**
+```bash
+mkdir -p workspaces/{main,test}/assets
+mkdir -p workspaces/{main,test}/shaders
+mkdir -p assets/common/{shaders,audio}
+```
+
+**1.2 Move existing files:**
+```bash
+# Main demo
+mv assets/demo.seq workspaces/main/timeline.seq
+mv assets/music.track workspaces/main/music.track
+mv assets/final/demo_assets.txt workspaces/main/assets.txt
+mv assets/final/music workspaces/main/assets/
+
+# Test demo
+mv assets/test_demo.seq workspaces/test/timeline.seq
+mv assets/test_demo.track workspaces/test/music.track
+cp assets/final/demo_assets.txt workspaces/test/assets.txt # Or create minimal version
+
+# Common shaders
+mv assets/shaders/math assets/common/shaders/
+mv assets/shaders/common_uniforms assets/common/shaders/
+```
+
+**1.3 Create workspace configs:**
+```bash
+# Generate workspace.cfg for main and test
+# See format above
+```
+
+### Phase 2: Update Build System
+
+**2.1 Add workspace parsing:**
+- Create `cmake/ParseWorkspace.cmake`
+- Parse INI-style config
+- Set CMake variables
+
+**2.2 Update CMakeLists.txt:**
+- Add `DEMO_WORKSPACE` option
+- Use workspace paths for assets/timeline/music
+- Pass shader include paths to ShaderComposer
+
+**2.3 Update compiler tools:**
+- `asset_packer`: Accept workspace root dir
+- `seq_compiler`: Accept workspace root dir
+- `tracker_compiler`: Accept workspace root dir
+
+### Phase 3: Update Code
+
+**3.1 ShaderComposer:**
+- Add multi-path include resolution
+- Search workspace shaders first, then common
+
+**3.2 Asset loading:**
+- Update paths to match new structure
+- Ensure backward compatibility during transition
+
+**3.3 Generated code:**
+- Verify generated assets.h/timeline.cc still work
+- Update include paths if needed
+
+### Phase 4: Documentation & Tools
+
+**4.1 Update docs:**
+- README.md: Explain workspace structure
+- HOWTO.md: Document workspace build commands
+- CONTRIBUTING.md: Workspace best practices
+
+**4.2 Create helper scripts:**
+- `scripts/workspace.sh`: Workspace management CLI
+- `templates/workspace.cfg`: Template for new workspaces
+
+**4.3 Update CI:**
+- Build both main and test workspaces
+- Validate workspace isolation
+
+### Phase 5: Migration & Validation
+
+**5.1 Test builds:**
+```bash
+# Build both workspaces
+cmake -S . -B build_main -DDEMO_WORKSPACE=main
+cmake --build build_main -j4
+
+cmake -S . -B build_test -DDEMO_WORKSPACE=test
+cmake --build build_test -j4
+
+# Run tests
+cd build_main && ctest
+cd build_test && ctest
+```
+
+**5.2 Verify isolation:**
+- Modify workspace-specific shader → only that workspace affected
+- Modify common shader → both workspaces affected
+
+**5.3 Update .gitignore:**
+```
+/build_*/
+/workspaces/*/generated/
+```
+
+## Benefits
+
+### 1. Clear Ownership
+Each workspace is self-contained:
+```bash
+workspaces/main/
+├── workspace.cfg # Configuration
+├── timeline.seq # Timeline
+├── music.track # Music
+├── assets.txt # Assets
+├── assets/ # Local assets
+└── shaders/ # Local shaders
+```
+
+### 2. Parallel Development
+Multiple developers can work on different demos without conflicts:
+```bash
+# Developer A: Main demo
+cd workspaces/main
+vim timeline.seq
+
+# Developer B: Experimental demo
+cd workspaces/experiments/christmas_demo
+vim timeline.seq
+```
+
+### 3. Easy Switching
+```bash
+# Build main demo
+cmake -B build -DDEMO_WORKSPACE=main
+./build/demo64k
+
+# Build test demo
+cmake -B build -DDEMO_WORKSPACE=test
+./build/test_demo
+```
+
+### 4. Clean Sharing
+Common resources in one place:
+```
+assets/common/
+├── shaders/
+│ ├── math/ # Shared by all
+│ └── common_uniforms/
+└── audio/
+ └── standard_drums.spec
+```
+
+### 5. Scalability
+Easy to add new demos:
+```bash
+./scripts/workspace.sh create party2025
+cd workspaces/party2025
+# Start developing...
+```
+
+## Migration Path
+
+### Backward Compatibility
+
+During transition, support both structures:
+```cmake
+if(EXISTS "${CMAKE_SOURCE_DIR}/workspaces/${DEMO_WORKSPACE}")
+ # New workspace structure
+ set(USING_WORKSPACES TRUE)
+else()
+ # Legacy flat structure
+ set(USING_WORKSPACES FALSE)
+endif()
+```
+
+### Gradual Migration
+
+1. **Phase 1:** Create workspace structure alongside existing
+2. **Phase 2:** Update build system to support both
+3. **Phase 3:** Move files to workspaces
+4. **Phase 4:** Remove legacy paths
+5. **Phase 5:** Update all documentation
+
+**Timeline:** 1-2 weeks for full migration
+
+## Trade-offs
+
+### Pros
+- Clear project organization
+- Parallel demo development
+- Clean resource sharing
+- Better onboarding for new devs
+- Scales to many demos
+
+### Cons
+- Initial migration effort
+- Build system complexity increases
+- More directories to navigate
+- Learning curve for contributors
+
+### Alternative Approaches
+
+**1. Monorepo with subprojects:**
+- Separate CMake projects per demo
+- More isolated but harder to share code
+
+**2. Configuration files only:**
+- Keep flat structure, use config to select files
+- Less clear, harder to understand ownership
+
+**3. Git branches per demo:**
+- Each demo is a branch
+- Conflicts when merging shared code
+
+**Chosen:** Workspace system (best balance)
+
+## Implementation Effort
+
+**Estimated time: 12-16 hours**
+
+- Phase 1: Create structure (2-3 hours)
+- Phase 2: Build system (4-5 hours)
+- Phase 3: Code updates (3-4 hours)
+- Phase 4: Documentation (2-3 hours)
+- Phase 5: Validation (1-2 hours)
+
+**Priority:** Medium (improves workflow, not blocking)
+
+## Success Criteria
+
+1. Both `main` and `test` workspaces build successfully
+2. Switching workspaces requires only CMake flag change
+3. Workspace-specific changes don't affect other workspaces
+4. Common shader changes affect all workspaces
+5. New workspace creation takes < 5 minutes
+6. All tests pass for both workspaces
+7. Documentation clear for new contributors
+
+## Related Files
+
+**New files:**
+- `workspaces/main/workspace.cfg` - Main demo config
+- `workspaces/test/workspace.cfg` - Test demo config
+- `cmake/ParseWorkspace.cmake` - Config parser
+- `scripts/workspace.sh` - Workspace CLI tool
+- `templates/workspace.cfg` - New workspace template
+
+**Modified files:**
+- `CMakeLists.txt` - Workspace support
+- `tools/asset_packer.cc` - Multi-path resolution
+- `src/gpu/shader_composer.cc` - Multi-path includes
+- `README.md` - Workspace documentation
+- `doc/HOWTO.md` - Build commands
+- `doc/CONTRIBUTING.md` - Workspace guidelines
+
+**Moved files:**
+- `assets/demo.seq` → `workspaces/main/timeline.seq`
+- `assets/music.track` → `workspaces/main/music.track`
+- `assets/final/demo_assets.txt` → `workspaces/main/assets.txt`
+- `assets/test_demo.*` → `workspaces/test/`
+- `assets/shaders/math/` → `assets/common/shaders/math/`
+
+## Notes
+
+- This is a **structural refactor**, not a feature
+- No runtime behavior changes
+- Enables future scalability
+- Makes project more professional
+- Good foundation for demoscene releases (multiple demos per repo)