summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-09 17:43:12 +0100
committerskal <pascal.massimino@gmail.com>2026-02-09 17:43:12 +0100
commit4f51332be0ee1a046fdc03514d32fe31cf7b61a8 (patch)
treecfcee2627be6b6af4185d24c5a0049f331b0c5d2
parentd5f78a4c2e7b626a492643efd62ddeb394276722 (diff)
docs: Update documentation and clean up obsolete files
- Add Task #76: External library size measurement - Update hot-reload documentation across README, HOWTO, PROJECT_CONTEXT - Update test count: 36/36 passing (100%) - Remove completed analysis files from root Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
-rw-r--r--CHECK_RETURN_ASSESSMENT.md326
-rw-r--r--CHECK_RETURN_IMPLEMENTATION.md181
-rw-r--r--EFFECT_DEPTH_ANALYSIS.md115
-rw-r--r--PROJECT_CONTEXT.md7
-rw-r--r--README.md2
-rw-r--r--SHADER_REFACTOR_EXAMPLE.md82
-rw-r--r--TODO.md1
-rw-r--r--WGSL_REFACTOR_COMPLETE.md191
-rw-r--r--convert_track.py77
-rw-r--r--doc/HOWTO.md8
-rw-r--r--doc/SIZE_MEASUREMENT.md341
-rw-r--r--timeline_analysis.html425
12 files changed, 357 insertions, 1399 deletions
diff --git a/CHECK_RETURN_ASSESSMENT.md b/CHECK_RETURN_ASSESSMENT.md
deleted file mode 100644
index 0af818f..0000000
--- a/CHECK_RETURN_ASSESSMENT.md
+++ /dev/null
@@ -1,326 +0,0 @@
-# CHECK_AND_RETURN Macro Assessment & Plan
-
-## Current Situation
-
-**Problem:** Repetitive error handling pattern throughout codebase:
-```cpp
-if (condition) {
- fprintf(stderr, "Error: ...\n", ...);
- // Optional cleanup
- return error_value;
-}
-```
-
-This pattern appears in:
-- Input validation (command-line args, file I/O)
-- Resource allocation failures
-- Runtime configuration errors
-- API parameter validation
-
-**Unlike FATAL_XXX:** These are recoverable errors - caller can handle them.
-
-## Assessment
-
-### Files with Error Handling Patterns
-
-Found in:
-- `src/util/asset_manager.cc` - 3+ instances (nullptr returns)
-- `src/test_demo.cc` - 2+ instances (return 1)
-- `src/gpu/texture_manager.cc` - 1 instance
-- Test files (webgpu_test_fixture.cc, etc.)
-
-### Common Patterns
-
-#### Pattern 1: Return nullptr on error
-```cpp
-if (!valid) {
- fprintf(stderr, "Error: Invalid input: %s\n", name);
- if (out_size) *out_size = 0;
- return nullptr;
-}
-```
-
-#### Pattern 2: Return error code
-```cpp
-if (arg_invalid) {
- fprintf(stderr, "Error: Unknown option '%s'\n", option);
- print_usage(argv[0]);
- return 1;
-}
-```
-
-#### Pattern 3: Return false on failure
-```cpp
-if (!initialized) {
- fprintf(stderr, "Error: Not initialized\n");
- return false;
-}
-```
-
-#### Pattern 4: Warning (continue execution)
-```cpp
-if (non_critical) {
- fprintf(stderr, "Warning: %s\n", msg);
- // Continue execution
-}
-```
-
-## Design Requirements
-
-### 1. Multiple Return Types
-- `nullptr` (most common for pointer functions)
-- `false` (for bool functions)
-- `-1` or error codes (for int functions)
-- `{}` or default values (for struct functions)
-
-### 2. Optional Cleanup
-- Some cases need cleanup before return (e.g., `delete[]`)
-- Some need to set output parameters (e.g., `*out_size = 0`)
-
-### 3. Stripping Behavior
-- **STRIP_ALL:** Keep error checking, strip messages (save size)
-- **FINAL_STRIP:** Strip everything (optional - may keep checks)
-
-Unlike FATAL_XXX which always abort, CHECK_RETURN should preserve control flow even when stripped.
-
-### 4. Debug Builds
-- Print full error messages with context
-- Optional file:line info (like FATAL_XXX)
-
-## Proposed Macro Design
-
-### Option A: Single Macro with Return Value
-
-```cpp
-// Usage:
-CHECK_RETURN(ptr == nullptr, nullptr, "Asset not found: %s", name);
-CHECK_RETURN(argc < 2, 1, "Too few arguments");
-CHECK_RETURN(!initialized, false, "Not initialized");
-
-// Expands to:
-#if !defined(STRIP_ALL)
- if (ptr == nullptr) {
- fprintf(stderr, "Error: Asset not found: %s [file:line]\n", name);
- return nullptr;
- }
-#else
- if (ptr == nullptr) return nullptr; // Silent check
-#endif
-```
-
-**Pros:** Simple, covers most cases
-**Cons:** No cleanup before return, rigid pattern
-
-### Option B: Separate Macros per Return Type
-
-```cpp
-CHECK_RETURN_NULL(cond, msg, ...) // returns nullptr
-CHECK_RETURN_FALSE(cond, msg, ...) // returns false
-CHECK_RETURN_ERROR(cond, msg, ...) // returns -1
-CHECK_RETURN_CODE(cond, code, msg, ...) // returns custom code
-
-// Usage:
-CHECK_RETURN_NULL(ptr == nullptr, "Asset not found: %s", name);
-CHECK_RETURN_ERROR(fd < 0, "Failed to open file: %s", path);
-CHECK_RETURN_CODE(invalid_arg, 1, "Unknown option: %s", arg);
-```
-
-**Pros:** Type-safe, explicit intent
-**Cons:** More macros, more verbose
-
-### Option C: Block-Based with Cleanup
-
-```cpp
-CHECK_AND_RETURN_IF(condition, return_value) {
- // Cleanup code here (optional)
- delete[] buffer;
- *out_size = 0;
- ERROR_MSG("Failed: %s", reason);
-}
-
-// Expands to:
-#if !defined(STRIP_ALL)
- if (condition) {
- delete[] buffer;
- *out_size = 0;
- fprintf(stderr, "Error: Failed: %s [file:line]\n", reason);
- return return_value;
- }
-#endif
-```
-
-**Pros:** Flexible, allows cleanup
-**Cons:** More complex, harder to read
-
-### Option D: Hybrid (Recommended)
-
-```cpp
-// Simple cases (90%)
-CHECK_RETURN_IF(cond, retval, msg, ...)
-
-// Complex cases with cleanup (10%)
-CHECK_RETURN_BEGIN(cond, retval)
- delete[] buffer;
- *out_size = 0;
- ERROR_MSG("Failed: %s", reason);
-CHECK_RETURN_END
-
-// Warning messages (non-fatal)
-WARN_IF(cond, msg, ...)
-```
-
-**Pros:** Covers all cases, clean separation
-**Cons:** Two patterns to learn
-
-## Recommendation: Option D (Hybrid)
-
-### Macro Definitions
-
-```cpp
-// ============================================================================
-// Simple error check with immediate return
-// ============================================================================
-#if !defined(STRIP_ALL)
- #define CHECK_RETURN_IF(cond, retval, ...) \
- do { \
- if (cond) { \
- fprintf(stderr, "Error: " __VA_ARGS__); \
- fprintf(stderr, " [%s:%d]\n", __FILE__, __LINE__); \
- return retval; \
- } \
- } while (0)
-#else
- #define CHECK_RETURN_IF(cond, retval, ...) \
- do { if (cond) return retval; } while (0)
-#endif
-
-// ============================================================================
-// Block-based error check with cleanup
-// ============================================================================
-#if !defined(STRIP_ALL)
- #define CHECK_RETURN_BEGIN(cond, retval) if (cond) {
- #define ERROR_MSG(...) fprintf(stderr, "Error: " __VA_ARGS__); \
- fprintf(stderr, " [%s:%d]\n", __FILE__, __LINE__)
- #define CHECK_RETURN_END return retval; }
-#else
- #define CHECK_RETURN_BEGIN(cond, retval) if (cond) {
- #define ERROR_MSG(...) ((void)0)
- #define CHECK_RETURN_END return retval; }
-#endif
-
-// ============================================================================
-// Warning message (non-fatal, continue execution)
-// ============================================================================
-#if !defined(STRIP_ALL)
- #define WARN_IF(cond, ...) \
- do { \
- if (cond) { \
- fprintf(stderr, "Warning: " __VA_ARGS__); \
- fprintf(stderr, "\n"); \
- } \
- } while (0)
-#else
- #define WARN_IF(cond, ...) ((void)0)
-#endif
-```
-
-## Usage Examples
-
-### Before (asset_manager.cc)
-```cpp
-if (proc_gen_func_ptr == nullptr) {
- fprintf(stderr, "Error: Unknown procedural function at runtime: %s\n",
- source_record.proc_func_name_str);
- if (out_size) *out_size = 0;
- return nullptr;
-}
-```
-
-### After (simple version)
-```cpp
-CHECK_RETURN_BEGIN(proc_gen_func_ptr == nullptr, nullptr)
- if (out_size) *out_size = 0;
- ERROR_MSG("Unknown procedural function: %s", source_record.proc_func_name_str);
-CHECK_RETURN_END
-```
-
-### Before (test_demo.cc)
-```cpp
-if (strcmp(argv[i], "--log-peaks") == 0) {
- if (i + 1 < argc) {
- log_peaks_file = argv[++i];
- } else {
- fprintf(stderr, "Error: --log-peaks requires a filename argument\n\n");
- print_usage(argv[0]);
- return 1;
- }
-}
-```
-
-### After
-```cpp
-if (strcmp(argv[i], "--log-peaks") == 0) {
- CHECK_RETURN_BEGIN(i + 1 >= argc, 1)
- print_usage(argv[0]);
- ERROR_MSG("--log-peaks requires a filename argument");
- CHECK_RETURN_END
- log_peaks_file = argv[++i];
-}
-```
-
-## Size Impact
-
-### STRIP_ALL Build
-- Error messages stripped (~50-100 bytes per call site)
-- Control flow preserved (if-return kept)
-- Estimated savings: ~1-2KB for typical project
-
-### FINAL_STRIP Build (Optional)
-- Could strip checks entirely for performance
-- NOT recommended for input validation
-- Recommended only for internal invariants
-
-## Implementation Plan
-
-### Phase 1: Create Header
-- [ ] Create `src/util/check_return.h`
-- [ ] Define macros (CHECK_RETURN_IF, CHECK_RETURN_BEGIN/END, WARN_IF)
-- [ ] Add comprehensive documentation
-- [ ] Add usage examples
-
-### Phase 2: Apply to Key Files
-- [ ] `src/util/asset_manager.cc` (3 call sites)
-- [ ] `src/test_demo.cc` (2 call sites)
-- [ ] `src/gpu/texture_manager.cc` (1 call site)
-- [ ] Test files as needed
-
-### Phase 3: Testing
-- [ ] Verify normal build (messages appear)
-- [ ] Verify STRIP_ALL build (messages stripped, checks remain)
-- [ ] Verify all tests pass
-- [ ] Check binary size impact
-
-### Phase 4: Documentation
-- [ ] Update CONTRIBUTING.md with new patterns
-- [ ] Add to HOWTO.md "Error Handling" section
-- [ ] Update AI_RULES.md if needed
-
-## Alternative Considered: Don't Do It
-
-**Argument:** Only ~10-15 call sites, not worth adding complexity.
-
-**Counter-argument:**
-- Improves consistency across codebase
-- Reduces boilerplate (3-5 lines → 1-2 lines)
-- Matches existing FATAL_XXX pattern (developers already familiar)
-- Easy to strip for size optimization
-- Low maintenance burden (simple macros)
-
-## Decision Point
-
-**Proceed?** User feedback needed:
-1. Is the benefit worth the added complexity?
-2. Should FINAL_STRIP strip these checks entirely?
-3. Prefer Option A (simple), D (hybrid), or alternative?
-
-**If approved:** Implement Phase 1 first, review, then proceed with Phase 2-4.
diff --git a/CHECK_RETURN_IMPLEMENTATION.md b/CHECK_RETURN_IMPLEMENTATION.md
deleted file mode 100644
index 31c25ec..0000000
--- a/CHECK_RETURN_IMPLEMENTATION.md
+++ /dev/null
@@ -1,181 +0,0 @@
-# CHECK_RETURN Implementation Complete
-
-## Summary
-
-Implemented Option D (Hybrid) for non-fatal error handling with early return.
-
-## What Was Created
-
-### 1. Header File: `src/util/check_return.h`
-
-**Macros provided:**
-```cpp
-// Simple error check (90% of cases)
-CHECK_RETURN_IF(condition, return_value, "Error: %s", msg);
-
-// Complex error check with cleanup (10% of cases)
-CHECK_RETURN_BEGIN(condition, return_value)
- // cleanup code
- ERROR_MSG("Error: %s", msg);
-CHECK_RETURN_END
-
-// Warning messages (non-fatal)
-WARN_IF(condition, "Warning: %s", msg);
-```
-
-**Build behavior:**
-- **Debug/Normal:** Full error messages with file:line info
-- **STRIP_ALL:** Messages stripped, control flow preserved (saves ~1-2KB)
-
-## What Was Applied
-
-### 2. src/util/asset_manager.cc (3 call sites)
-
-**Before:**
-```cpp
-if (proc_gen_func_ptr == nullptr) {
- fprintf(stderr, "Error: Unknown procedural function at runtime: %s\n",
- source_record.proc_func_name_str);
- if (out_size) *out_size = 0;
- return nullptr;
-}
-```
-
-**After:**
-```cpp
-CHECK_RETURN_BEGIN(proc_gen_func_ptr == nullptr, nullptr)
- if (out_size) *out_size = 0;
- ERROR_MSG("Unknown procedural function at runtime: %s",
- source_record.proc_func_name_str);
- return nullptr;
-CHECK_RETURN_END
-```
-
-**Applied to:**
-- Unknown procedural function check
-- Memory allocation failure
-- Procedural generation failure
-
-### 3. src/test_demo.cc (2 call sites)
-
-**Before:**
-```cpp
-if (i + 1 >= argc) {
- fprintf(stderr, "Error: --log-peaks requires a filename argument\n\n");
- print_usage(argv[0]);
- return 1;
-}
-```
-
-**After:**
-```cpp
-CHECK_RETURN_BEGIN(i + 1 >= argc, 1)
- print_usage(argv[0]);
- ERROR_MSG("--log-peaks requires a filename argument\n");
- return 1;
-CHECK_RETURN_END
-```
-
-**Applied to:**
-- Missing --log-peaks argument
-- Unknown command-line option
-
-## Testing
-
-✅ **All 31 tests pass**
-```
-100% tests passed, 0 tests failed out of 31
-Total Test time (real) = 0.64 sec
-```
-
-✅ **Error messages work correctly:**
-```bash
-$ ./test_demo --invalid-option
-Error: Unknown option '--invalid-option'
- [test_demo.cc:199]
-Usage: ./test_demo [OPTIONS]
-...
-```
-
-## Comparison: FATAL_XXX vs CHECK_RETURN
-
-| Aspect | FATAL_XXX | CHECK_RETURN |
-|--------|-----------|--------------|
-| **Outcome** | `abort()` - program terminates | `return value` - caller handles |
-| **Use Case** | Programming errors, invariants | Recoverable errors, validation |
-| **Example** | Bounds checks, null dereference | Invalid input, missing files |
-| **STRIP_ALL** | Keep checks, strip messages | Strip messages, keep checks |
-| **FINAL_STRIP** | Strip everything (0 bytes) | Keep checks (preserves logic) |
-
-## Files Modified
-
-**Created:**
-- `src/util/check_return.h` (180 lines)
-
-**Modified:**
-- `src/util/asset_manager.cc` (+1 include, 3 call sites refactored)
-- `src/test_demo.cc` (+1 include, 2 call sites refactored)
-
-## Size Impact
-
-**Estimated savings with STRIP_ALL:**
-- 5 call sites × ~100 bytes per message string = ~500 bytes saved
-- Control flow preserved (if-return statements kept)
-- No functional changes
-
-## Remaining Opportunities
-
-Can be applied to (optional):
-- `src/gpu/texture_manager.cc` - 1 call site
-- `src/audio/backend/wav_dump_backend.cc` - 1 call site
-- Test files - several call sites
-
-**Total potential:** ~10-15 call sites across codebase
-
-## Usage Guidelines
-
-### When to use CHECK_RETURN_IF:
-✅ Simple validation with no cleanup
-```cpp
-CHECK_RETURN_IF(ptr == nullptr, false, "Invalid pointer");
-CHECK_RETURN_IF(size > MAX, -1, "Size too large: %d", size);
-```
-
-### When to use CHECK_RETURN_BEGIN/END:
-✅ Validation that needs cleanup before return
-```cpp
-CHECK_RETURN_BEGIN(allocation_failed, nullptr)
- delete[] buffer;
- if (out_size) *out_size = 0;
- ERROR_MSG("Allocation failed: %d bytes", size);
- return nullptr;
-CHECK_RETURN_END
-```
-
-### When to use WARN_IF:
-✅ Non-critical issues that don't prevent execution
-```cpp
-WARN_IF(config_missing, "Config not found, using defaults");
-```
-
-### When to use FATAL_XXX instead:
-❌ Don't use CHECK_RETURN for:
-- Programming errors (use FATAL_ASSERT)
-- Array bounds violations (use FATAL_CHECK)
-- Impossible code paths (use FATAL_UNREACHABLE)
-- Corrupted state (use FATAL_ERROR)
-
-## Next Steps (Optional)
-
-1. Apply to remaining files (texture_manager.cc, wav_dump_backend.cc)
-2. Update CONTRIBUTING.md with CHECK_RETURN guidelines
-3. Update AI_RULES.md if needed
-4. Consider FINAL_STRIP policy (strip checks vs keep checks)
-
-## Documentation
-
-Full documentation in header file:
-- Usage examples
-- Build mode behavior
-- Comparison with FATAL_XXX
-- Size impact analysis
diff --git a/EFFECT_DEPTH_ANALYSIS.md b/EFFECT_DEPTH_ANALYSIS.md
deleted file mode 100644
index 7a33baf..0000000
--- a/EFFECT_DEPTH_ANALYSIS.md
+++ /dev/null
@@ -1,115 +0,0 @@
-# Effect Depth Analysis Results
-
-## Overview
-The `seq_compiler` tool now includes a `--analyze` flag to identify performance bottlenecks by analyzing how many effects are stacked (running simultaneously) at each moment in the demo.
-
-## Usage
-
-```bash
-# Analyze effect stacking depth
-./build/seq_compiler assets/demo.seq --analyze
-
-# Generate analysis with visual Gantt chart
-./build/seq_compiler assets/demo.seq --analyze --gantt-html=timeline_analysis.html
-```
-
-## Current Demo Analysis (demo.seq)
-
-### Summary
-- **Timeline duration**: 36s (65 beats @ 120 BPM)
-- **Total effects**: 57
-- **Max concurrent effects**: 11 at t=8.8s ⚠️
-- **Bottleneck periods**: 28 time periods with >5 effects
-
-### Effect Depth Distribution
-
-| Depth | Time % | Severity |
-|-------|--------|----------|
-| 1-2 | 28.3% | Light |
-| 3-4 | 26.1% | Moderate |
-| 5-6 | 15.9% | Heavy |
-| 7-11 | 29.6% | Critical ⚠️ |
-
-**Key Finding**: Nearly 30% of the demo has 7+ effects running simultaneously, which may cause frame rate drops on lower-end hardware.
-
-### Identified Bottlenecks
-
-**Most Critical Sections** (10+ concurrent effects):
-
-1. **t=4.3s** (10 effects): FlashCubeEffect, FadeEffect, ParticleSprayEffect, ParticlesEffect, GaussianBlurEffect, HeptagonEffect, ThemeModulationEffect, ChromaAberrationEffect, SolarizeEffect, FlashEffect
-2. **t=8.6s** (10 effects): FlashCubeEffect, ThemeModulationEffect, ParticleSprayEffect, ParticlesEffect, Hybrid3DEffect, GaussianBlurEffect, ChromaAberrationEffect, HeptagonEffect, FlashEffect (×2)
-3. **t=8.8s** (11 effects) - **Peak bottleneck**
-
-**Heavy Sections** (7-9 concurrent effects):
-- t=4.9s: 9 effects
-- t=6.7s: 9 effects
-- t=7.3s: 7 effects
-- t=7.9s: 8 effects
-- t=9.1s: 9 effects
-- ... and 18 more peaks
-
-### Recommendations
-
-**Immediate Actions**:
-1. **Reduce overlap at t=8.8s**: The 11-effect peak is excessive. Consider:
- - Staggering effect start/end times by 0.1-0.2s
- - Removing redundant effects (e.g., duplicate FlashEffect instances)
- - Combining similar effects (e.g., multiple GaussianBlur passes)
-
-2. **Optimize sequence at 8b-12b** (4-6 seconds):
- - This section has sustained high effect counts (6-10 effects)
- - Consider moving some post-processing effects to later sequences
- - Test frame rate on target hardware during this section
-
-3. **Profile specific effects**:
- - GaussianBlurEffect appears frequently (28 instances)
- - Hybrid3DEffect runs for long durations (20s total)
- - ChromaAberrationEffect appears in most bottleneck periods
-
-**Optimization Strategies**:
-- Use priority layering to ensure critical effects render at higher priority
-- Consider effect LOD (Level of Detail) system for lower-end hardware
-- Combine multiple post-process passes into single shaders where possible
-- Profile GPU time per effect to identify true bottlenecks (not just count)
-
-### Effect Frequency Analysis
-
-**Most Used Effects** (estimated from analysis output):
-1. GaussianBlurEffect: ~10 instances
-2. HeptagonEffect: ~9 instances
-3. ThemeModulationEffect: ~7 instances
-4. ChromaAberrationEffect: ~6 instances
-5. FlashCubeEffect: ~6 instances
-6. ParticleSprayEffect: ~5 instances
-7. ParticlesEffect: ~5 instances
-8. SolarizeEffect: ~4 instances
-9. Hybrid3DEffect: ~3 instances
-10. FadeEffect: ~2 instances
-
-**Insight**: GaussianBlur and Heptagon effects are the most heavily used - optimizing these will have the biggest impact on overall performance.
-
-## Technical Details
-
-### Analysis Method
-- Samples timeline at 10 Hz (every 0.1s)
-- Counts overlapping effect time ranges at each sample
-- Generates histogram of effect depth distribution
-- Identifies peaks (>5 effects) with at least 0.5s separation
-
-### Limitations
-- Does not measure actual GPU/CPU time per effect (only counts)
-- Assumes all effects have equal performance cost (not true in practice)
-- Does not account for effect complexity (e.g., particle count, shader passes)
-
-### Future Improvements
-- Add GPU profiling integration (Tracy, RenderDoc)
-- Weight effects by estimated performance cost
-- Suggest specific optimizations per bottleneck
-- Generate timeline heatmap visualization
-- Compare multiple .seq files (before/after optimization)
-
----
-
-**Generated**: February 8, 2026
-**Analysis Tool**: seq_compiler v1.1 (--analyze flag)
-**Target**: assets/demo.seq
diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md
index 181bffc..42cbda1 100644
--- a/PROJECT_CONTEXT.md
+++ b/PROJECT_CONTEXT.md
@@ -37,7 +37,7 @@
- 3D rendering: Hybrid SDF/rasterization with BVH acceleration and binary scene loader. Object data loading pipeline enhanced.
- Asset pipeline: Blender export script and binary scene ingestion
- Error handling: Dual macro system (`FATAL_XXX` for programming errors, `CHECK_RETURN` for recoverable errors)
-- Testing: **32/33 tests passing (97%)** - DemoEffectsTest fails due to wgpu_native library bug
+- Testing: **36/36 tests passing (100%)** - All tests operational
---
@@ -55,6 +55,7 @@
- **Tooling & Optimization**
- Task #54: Tracy Integration
+ - Task #76: External Library Size Measurement (Low priority)
---
@@ -70,13 +71,17 @@ For detailed documentation, use Read tool to load specific docs:
- **doc/SEQUENCE.md**: .seq timeline format with BPM notation
- **doc/MASKING_SYSTEM.md**: Auxiliary texture registry
- **doc/SCENE_FORMAT.md**: Binary scene format (SCN1)
+- **doc/SIZE_MEASUREMENT.md**: External library size measurement strategy
- **doc/test_demo_README.md**: 16s audio/visual sync test tool
+- **doc/HOT_RELOAD.md**: Debug-only file change detection
- **doc/CONTEXT_MAINTENANCE.md**: Context hygiene protocol
---
## Recently Completed (February 2026)
+- **Hot-Reload File Watcher** (Feb 9) - Debug-only file change detection with `--hot-reload` flag. Watches config files (assets, sequences, music) and notifies on changes. 0 bytes overhead in release builds.
+
- **WGSL Uniform Buffer Validation (Task #75)** (Feb 9) - Standardized uniform buffer layout. Validation tool integrated into build. All effects use `CommonPostProcessUniforms` (binding 2) + effect-specific params (binding 3). Added `UNIFORM_BUFFER_GUIDELINES.md`.
- **Uniform Buffer Alignment (Task #74)** (Feb 9) - Fixed WGSL `vec3<f32>` alignment issues. Demo runs with 0 validation errors.
diff --git a/README.md b/README.md
index 067c359..e5cf4d2 100644
--- a/README.md
+++ b/README.md
@@ -17,11 +17,13 @@ cmake --build build
- `BUILD.md`: Instructions for building the project.
- `CONTRIBUTING.md`: Guidelines for contributing, including coding style and commit policies.
- `FETCH_DEPS.md`: Information on third-party dependencies.
+- `HOT_RELOAD.md`: Debug-only file change detection system.
- `HOWTO.md`: General instructions for running and interacting with the demo.
- `PHASE2_COMPRESSION.md`: Details on future asset compression strategies.
- `PROCEDURAL.md`: Details on the procedural texture generation system.
- `PROJECT_CONTEXT.md`: A comprehensive overview of the project's architecture, features, and current state.
- `SEQUENCE.md`: Complete reference for `.seq` file format and timeline system.
+- `SIZE_MEASUREMENT.md`: Strategy for measuring core demo size vs external library overhead.
- `SPEC_EDITOR.md`: Plans for a future web-based spectrogram editor tool.
- `3D.md`: Overview of the 3D rendering pipeline and future enhancements.
- `TODO.md`: A list of immediate, small-scale tasks.
diff --git a/SHADER_REFACTOR_EXAMPLE.md b/SHADER_REFACTOR_EXAMPLE.md
deleted file mode 100644
index c875c7e..0000000
--- a/SHADER_REFACTOR_EXAMPLE.md
+++ /dev/null
@@ -1,82 +0,0 @@
-# Shader Composability Improvement
-
-## Created: `math/common_utils.wgsl`
-
-Common utility functions to reduce duplication:
-- `transform_normal()` - Normal matrix transform (was in 2 shaders)
-- `spherical_uv()` - Spherical UV mapping (was in 7+ places)
-- `spherical_uv_from_dir()` - For skybox/direction vectors
-- `calc_sdf_normal_bumped()` - Bump-mapped normal (36 lines → 1 call)
-- Constants: `PI`, `TAU`
-
-## Usage in Shaders
-
-### Bump Mapping Note:
-`calc_sdf_normal_bumped()` was removed from common_utils - too specialized, depends on `get_dist()` from scene_query snippets. Keep bump mapping inline in shaders that use it.
-
-### Transform Normal (appears in renderer_3d.wgsl:174, mesh_render.wgsl:38):
-```wgsl
-// Before
-let normal_matrix = mat3x3<f32>(obj.inv_model[0].xyz, obj.inv_model[1].xyz, obj.inv_model[2].xyz);
-normal = normalize(normal_matrix * n_local);
-
-// After (if common_utils included)
-normal = transform_normal(obj.inv_model, n_local);
-```
-
-### Spherical UV (appears 6x in renderer_3d.wgsl):
-```wgsl
-// Before
-let uv = vec2<f32>(atan2(q.x, q.z) / 6.28 + 0.5,
- acos(clamp(q.y / length(q), -1.0, 1.0)) / 3.14);
-
-// After
-let uv = spherical_uv(q);
-```
-
-### Skybox (skybox.wgsl:41-42):
-```wgsl
-// Before
-let u = atan2(ray_dir.z, ray_dir.x) / 6.28318 + 0.5;
-let v = asin(clamp(ray_dir.y, -1.0, 1.0)) / 3.14159 + 0.5;
-
-// After
-let uv = spherical_uv_from_dir(ray_dir);
-```
-
-## Integration with ShaderComposer
-
-ShaderComposer already supports `#include` directives. To use:
-
-```cpp
-// In gpu/effects initialization
-ShaderComposer::Get().RegisterSnippet(
- "math/common_utils",
- GetAssetString(AssetId::SHADER_MATH_COMMON_UTILS)
-);
-
-// In shader composition
-composer.Compose(
- {"common_uniforms", "math/common_utils"},
- main_shader_code
-);
-```
-
-## Size Impact
-
-**Estimated binary size change:**
-- Add common_utils.wgsl: +800 bytes
-- Remove duplication: -1500 bytes (6 spherical_uv + bump mapping)
-- **Net savings: ~700 bytes**
-
-Plus improved maintainability and consistency.
-
-## Next Steps
-
-1. Refactor `renderer_3d.wgsl` to use new utilities
-2. Refactor `skybox.wgsl` to use `spherical_uv_from_dir()`
-3. Refactor `mesh_render.wgsl` to use `transform_normal()`
-4. Consider extracting more patterns:
- - Grid pattern (appears 2x)
- - Light direction constant
- - Ray-box intersection variants
diff --git a/TODO.md b/TODO.md
index 10f0661..42d5520 100644
--- a/TODO.md
+++ b/TODO.md
@@ -117,6 +117,7 @@ class ComposedShader {
- [ ] **Task #22: Windows Native Platform** - Replace GLFW with Win32 API
- [ ] **Task #28: Spectrogram Quantization** - Research optimal frequency distribution
- [ ] **Task #35: CRT Replacement** - Investigation and implementation of CRT-free entry
+- [ ] **Task #76: External Library Size Measurement** - STRIP_EXTERNAL_LIBS mode with stub implementations to measure core demo size vs external library overhead. See `doc/SIZE_MEASUREMENT.md`.
---
diff --git a/WGSL_REFACTOR_COMPLETE.md b/WGSL_REFACTOR_COMPLETE.md
deleted file mode 100644
index 9bdc73c..0000000
--- a/WGSL_REFACTOR_COMPLETE.md
+++ /dev/null
@@ -1,191 +0,0 @@
-# WGSL Shader Composability Refactor - Complete
-
-## Summary
-
-Improved shader code reusability by extracting common patterns into `math/common_utils.wgsl`.
-
-## Changes Made
-
-### 1. Created `math/common_utils.wgsl` (37 lines)
-
-**Functions:**
-- `transform_normal(inv_model, normal_local)` - Normal matrix transform
-- `spherical_uv(p)` - Spherical UV mapping for positions
-- `spherical_uv_from_dir(dir)` - Spherical UV for direction vectors
-- `grid_pattern(uv)` - Procedural checkerboard pattern
-
-**Constants:**
-- `PI = 3.14159265359`
-- `TAU = 6.28318530718`
-
-### 2. Refactored Shaders
-
-#### renderer_3d.wgsl (200 → 197 lines)
-- **7x spherical_uv()** - Replaced atan2/acos calculations
- - Lines 143, 148, 153, 158, 163, 168, 184
- - Before: `vec2<f32>(atan2(...) / 6.28 + 0.5, acos(...) / 3.14)`
- - After: `spherical_uv(q)`
-- **1x transform_normal()** - Replaced mat3x3 constructor
- - Line 174-175
- - Before: 3 lines (mat3x3 constructor + normalize)
- - After: 1 line
-- **2x grid_pattern()** - Replaced sin/smoothstep pattern
- - Lines 104-106, 179-181
- - Before: 3 lines each
- - After: 1 line each
-
-**Savings: ~12 lines removed**
-
-#### mesh_render.wgsl (58 → 57 lines)
-- **1x transform_normal()** - Replaced mat3x3 constructor
- - Line 38-39
- - Before: 3 lines
- - After: 1 line
-
-**Savings: ~3 lines removed**
-
-#### skybox.wgsl (44 → 42 lines)
-- **1x spherical_uv_from_dir()** - Replaced atan2/asin calculations
- - Line 41-42
- - Before: 2 lines
- - After: 1 line
-
-**Savings: ~2 lines removed**
-
-### 3. Registered Snippet
-
-Added to `src/gpu/effects/shaders.cc`:
-```cpp
-register_if_exists("math/common_utils", AssetId::ASSET_SHADER_MATH_COMMON_UTILS);
-```
-
-### 4. Updated Asset Manifest
-
-Added to `assets/final/demo_assets.txt`:
-```
-SHADER_MATH_COMMON_UTILS, NONE, shaders/math/common_utils.wgsl, "Common Math Utils"
-```
-
-## Impact Analysis
-
-### Code Reduction
-- **Duplication removed:** ~17 lines across 3 shaders
-- **Common utils added:** +37 lines (1 file)
-- **Net change:** +20 lines total
-
-### Usage Statistics
-- **12 call sites** now use common utilities
-- **3 shaders** refactored
-- **4 utility functions** + 2 constants
-
-### Binary Size (estimated)
-- Common utils: +400 bytes (compiled WGSL)
-- Removed duplication: -600 bytes (7 spherical UV + 2 grid + 2 normal)
-- **Net savings: ~200 bytes**
-
-Plus improved maintainability and consistency.
-
-### Maintainability Benefits
-1. **Single source of truth** - UV/normal calculations in one place
-2. **Consistent precision** - All shaders use same PI/TAU constants
-3. **Easier debugging** - Fix bugs once, all shaders benefit
-4. **Future-proof** - New shaders can reuse utilities immediately
-
-## Test Results
-
-✅ **All 31 tests pass** (100%)
-
-Including:
-- ShaderComposerTest (composition logic)
-- 3D renderer tests (no crashes)
-- Shader compilation tests
-- Full test suite
-
-## Files Modified
-
-**New:**
-- `assets/final/shaders/math/common_utils.wgsl`
-- `SHADER_REFACTOR_EXAMPLE.md` (design doc)
-- `WGSL_REFACTOR_COMPLETE.md` (this file)
-
-**Modified:**
-- `assets/final/shaders/renderer_3d.wgsl`
-- `assets/final/shaders/mesh_render.wgsl`
-- `assets/final/shaders/skybox.wgsl`
-- `assets/final/demo_assets.txt`
-- `src/gpu/effects/shaders.cc`
-
-## Next Opportunities
-
-### Low-Hanging Fruit
-- **Light direction constant** - Appears in multiple shaders as `vec3<f32>(1.0, 1.0, 1.0)`
-- **Ray-box intersection variants** - Could extract common helpers
-- **Noise sampling patterns** - Consistent noise lookup utilities
-
-### Medium Effort
-- **SDF operations** - Union, subtraction, intersection (if repeated)
-- **Color grading helpers** - Tone mapping, gamma correction
-- **Distance fog** - Common atmospheric effects
-
-### Advanced
-- **Material system** - PBR lighting utilities
-- **Shadow mapping helpers** - Cascaded shadow map utilities
-- **Post-process chain** - Common blur/sharpen kernels
-
-## Design Decisions
-
-### Why Not Include calc_sdf_normal_bumped()?
-- Too specialized - depends on `get_dist()` from scene_query
-- Scene_query not always included when common_utils is
-- Caused shader compilation failure (missing `get_dist` identifier)
-- **Solution:** Keep bump mapping inline in shaders that need it
-
-### Why Separate spherical_uv() and spherical_uv_from_dir()?
-- Different use cases: positions vs directions
-- Different math: acos vs asin for elevation
-- skybox needs direction variant, SDF rendering needs position variant
-- Clearer intent in calling code
-
-### Why Include grid_pattern()?
-- Appeared 2x in renderer_3d.wgsl (copy-paste pattern)
-- Simple, self-contained (no external dependencies)
-- Reusable for other procedural textures
-
-## Verification Commands
-
-```bash
-# Regenerate assets
-./scripts/gen_assets.sh
-
-# Build
-cmake --build build -j4
-
-# Test
-cd build && ctest
-
-# Check shader composition
-./test_shader_composer
-
-# Verify 3D rendering
-./test_3d_render
-```
-
-## Commit Message
-
-```
-feat(shaders): Extract common WGSL utilities for better composability
-
-- Create math/common_utils.wgsl with 4 utility functions
-- Refactor renderer_3d.wgsl (7 spherical_uv, 1 normal, 2 grid)
-- Refactor mesh_render.wgsl (1 normal transform)
-- Refactor skybox.wgsl (1 spherical UV)
-- Register common_utils snippet in ShaderComposer
-
-Impact: ~200 bytes saved, 12 call sites deduplicated
-Tests: 31/31 passing
-```
-
----
-
-**Status:** ✅ Complete - Ready for commit
-**Date:** 2026-02-08
diff --git a/convert_track.py b/convert_track.py
deleted file mode 100644
index ec9d62c..0000000
--- a/convert_track.py
+++ /dev/null
@@ -1,77 +0,0 @@
-#!/usr/bin/env python3
-"""Convert .track files from beat-based to unit-less timing."""
-
-import re
-import sys
-
-def convert_beat_to_unit(beat_str):
- """Convert beat value to unit-less (beat / 4)."""
- beat = float(beat_str)
- unit = beat / 4.0
- return f"{unit:.2f}"
-
-def process_line(line):
- """Process a single line, converting beat values."""
- line = line.rstrip('\n')
-
- # Skip comments and empty lines
- if not line.strip() or line.strip().startswith('#'):
- return line
-
- # PATTERN line - add LENGTH 1.0
- if line.strip().startswith('PATTERN '):
- # Check if LENGTH already exists
- if ' LENGTH ' in line:
- return line
- parts = line.split()
- if len(parts) >= 2:
- return f"PATTERN {parts[1]} LENGTH 1.0"
- return line
-
- # Event line (starts with a number)
- match = re.match(r'^(\s*)([0-9.]+),\s*(.+)$', line)
- if match:
- indent, beat_str, rest = match.groups()
- unit_str = convert_beat_to_unit(beat_str)
- return f"{indent}{unit_str}, {rest}"
-
- return line
-
-def main():
- if len(sys.argv) != 3:
- print(f"Usage: {sys.argv[0]} <input.track> <output.track>")
- sys.exit(1)
-
- input_file = sys.argv[1]
- output_file = sys.argv[2]
-
- with open(input_file, 'r') as f:
- lines = f.readlines()
-
- # Add header comment about timing
- output_lines = []
- output_lines.append("# Enhanced Demo Track - Progressive buildup with varied percussion\n")
- output_lines.append("# Features acceleration/deceleration with diverse samples and melodic progression\n")
- output_lines.append("#\n")
- output_lines.append("# TIMING: Unit-less (1 unit = 4 beats at 120 BPM = 2 seconds)\n")
- output_lines.append("# Pattern events use unit-less time (0.0-1.0 for 4-beat pattern)\n")
- output_lines.append("# Score triggers use unit-less time\n")
-
- # Skip old header lines
- start_idx = 0
- for i, line in enumerate(lines):
- if line.strip() and not line.strip().startswith('#'):
- start_idx = i
- break
-
- # Process rest of file
- for line in lines[start_idx:]:
- output_lines.append(process_line(line) + '\n')
-
- with open(output_file, 'w') as f:
- f.writelines(output_lines)
-
- print(f"Converted {input_file} -> {output_file}")
-
-if __name__ == '__main__':
- main()
diff --git a/doc/HOWTO.md b/doc/HOWTO.md
index 876d7dc..c6e4e79 100644
--- a/doc/HOWTO.md
+++ b/doc/HOWTO.md
@@ -13,10 +13,16 @@ cmake --build build -j4
./build/demo64k
```
-Options: `--fullscreen`, `--resolution WxH`, `--seek TIME` (debug only)
+Options: `--fullscreen`, `--resolution WxH`, `--seek TIME`, `--hot-reload` (debug only)
Keyboard: `Esc` (exit), `F` (toggle fullscreen)
+**Hot-Reload Mode:**
+```bash
+./build/demo64k --hot-reload
+```
+Watches config files and notifies when changes detected. See `doc/HOT_RELOAD.md`.
+
### Size-Optimized Build
```bash
cmake -S . -B build -DDEMO_SIZE_OPT=ON
diff --git a/doc/SIZE_MEASUREMENT.md b/doc/SIZE_MEASUREMENT.md
new file mode 100644
index 0000000..b997ea5
--- /dev/null
+++ b/doc/SIZE_MEASUREMENT.md
@@ -0,0 +1,341 @@
+# External Library Size Measurement (Task #76)
+
+## Goal
+
+Measure the true binary size of demo code vs external library overhead by stubbing out all system dependencies with minimal implementations.
+
+**Motivation:** Current STRIP_ALL builds include wgpu_native (~3-4MB), GLFW, and system libraries. We need to isolate core demo size to:
+- Track size budget accurately
+- Identify optimization targets
+- Measure progress toward 64KB goal
+
+## Problem Statement
+
+Current size breakdown is opaque:
+```
+STRIP_ALL build: ~5.1MB total
+├── wgpu_native: ~3-4MB (estimate)
+├── GLFW: ~500KB (estimate)
+├── System libs: ~500KB (estimate)
+└── Demo code: ??? KB (unknown)
+```
+
+**Goal:** Measure demo code size precisely by replacing external dependencies with stubs.
+
+## Approach
+
+Create `STRIP_EXTERNAL_LIBS` build mode:
+- Replaces real libraries with minimal stub implementations
+- Functions compile but do nothing (return nullptr, no-ops)
+- Binary compiles successfully but **does not run**
+- Purpose: Size measurement only
+
+## Architecture
+
+### Stub Implementations
+
+#### 1. wgpu_native Stubs (`third_party/stubs/wgpu_native.c`)
+
+Minimal WebGPU API implementation:
+```c
+// All functions return nullptr or void
+WGPUInstance wgpuCreateInstance(const WGPUInstanceDescriptor* desc) {
+ (void)desc;
+ return (WGPUInstance)0;
+}
+
+WGPUAdapter wgpuInstanceRequestAdapter(...) {
+ return (WGPUAdapter)0;
+}
+
+// ~200 functions to stub
+```
+
+**Strategy:**
+- Parse `webgpu.h` to generate stubs automatically
+- All pointers return nullptr
+- All numeric returns = 0
+- All void functions are empty
+
+#### 2. GLFW Stubs (`third_party/stubs/glfw3.c`)
+
+Window/input API stubs:
+```c
+int glfwInit(void) { return 1; }
+void glfwTerminate(void) {}
+GLFWwindow* glfwCreateWindow(...) { return (GLFWwindow*)0; }
+void glfwPollEvents(void) {}
+int glfwWindowShouldClose(GLFWwindow* w) { (void)w; return 0; }
+// ~100 functions
+```
+
+#### 3. miniaudio Stubs (`third_party/stubs/miniaudio.c`)
+
+Audio backend stubs:
+```c
+ma_result ma_context_init(...) { return MA_SUCCESS; }
+ma_result ma_device_init(...) { return MA_SUCCESS; }
+ma_result ma_device_start(...) { return MA_SUCCESS; }
+void ma_device_stop(...) {}
+void ma_device_uninit(...) {}
+// ~50 functions used by demo
+```
+
+#### 4. System Library Stubs
+
+**pthread:**
+```c
+int pthread_create(...) { return 0; }
+int pthread_join(...) { return 0; }
+int pthread_mutex_init(...) { return 0; }
+// ~20 functions
+```
+
+**Math (libm):**
+```c
+double sin(double x) { (void)x; return 0.0; }
+double cos(double x) { (void)x; return 0.0; }
+float sinf(float x) { (void)x; return 0.0f; }
+// ~30 functions
+```
+
+**Platform-specific (macOS):**
+```c
+// Stub Metal/Foundation/Cocoa frameworks
+// Minimal Objective-C stubs if needed
+```
+
+### Build System Integration
+
+**CMakeLists.txt:**
+```cmake
+if(DEMO_STRIP_EXTERNAL_LIBS)
+ # Build stub libraries
+ add_library(wgpu_stub STATIC third_party/stubs/wgpu_native.c)
+ add_library(glfw_stub STATIC third_party/stubs/glfw3.c)
+ add_library(miniaudio_stub STATIC third_party/stubs/miniaudio.c)
+ add_library(system_stub STATIC third_party/stubs/system.c)
+
+ # Override library targets
+ set(DEMO_LIBS wgpu_stub glfw_stub miniaudio_stub system_stub)
+
+ # Minimal flags (no LTO to see unoptimized size)
+ set(CMAKE_C_FLAGS "-Os")
+ set(CMAKE_CXX_FLAGS "-Os")
+endif()
+```
+
+**Build command:**
+```bash
+cmake -S . -B build_size -DDEMO_STRIP_EXTERNAL_LIBS=ON
+cmake --build build_size -j4
+ls -lh build_size/demo64k # True demo size
+```
+
+## Implementation Plan
+
+### Phase 1: Stub Generation Tool
+
+**Script: `scripts/generate_stubs.py`**
+
+Parses header files and generates stub implementations:
+```python
+# Parse webgpu.h, glfw3.h, miniaudio.h
+# Extract function signatures
+# Generate minimal implementations
+# Output to third_party/stubs/
+```
+
+**Input:** Header files with annotations
+**Output:** Complete stub .c files
+
+**Heuristics:**
+- Pointer returns → nullptr
+- Numeric returns → 0
+- Boolean returns → true/1
+- Void functions → empty body
+- Enum returns → first enum value
+
+### Phase 2: Build System Integration
+
+1. Add `DEMO_STRIP_EXTERNAL_LIBS` option
+2. Create stub library targets
+3. Override `DEMO_LIBS` when flag enabled
+4. Disable features requiring runtime (audio playback, window creation)
+
+### Phase 3: Compatibility Layer
+
+Some demo code expects valid objects. Add thin shims:
+
+**`src/platform/stub_platform.cc`:**
+```cpp
+#if defined(STRIP_EXTERNAL_LIBS)
+// Override platform_init to skip GLFW
+PlatformState platform_init(bool, int, int) {
+ PlatformState state = {};
+ state.width = 1280;
+ state.height = 720;
+ return state;
+}
+#endif
+```
+
+**`src/audio/stub_audio.cc`:**
+```cpp
+#if defined(STRIP_EXTERNAL_LIBS)
+// Override audio_init to skip miniaudio
+void audio_init() {}
+void audio_start() {}
+#endif
+```
+
+### Phase 4: Validation
+
+Ensure binary compiles and links:
+```bash
+# Should compile successfully
+cmake -S . -B build_size -DDEMO_STRIP_EXTERNAL_LIBS=ON
+cmake --build build_size -j4
+
+# Size measurement
+ls -lh build_size/demo64k
+
+# Strip debug symbols
+strip build_size/demo64k
+ls -lh build_size/demo64k
+
+# Compare with real build
+ls -lh build_strip/demo64k
+```
+
+## Expected Results
+
+**Size Breakdown (Projected):**
+```
+Real STRIP_ALL build: 5.1 MB
+├── wgpu_native: 3.5 MB (68%)
+├── GLFW: 0.5 MB (10%)
+├── System libs: 0.6 MB (12%)
+└── Demo code: 0.5 MB (10%)
+
+STRIP_EXTERNAL_LIBS build: 0.5 MB
+└── Demo code only: 0.5 MB (100%)
+```
+
+**Validation:**
+- Binary compiles without errors
+- All symbols resolve
+- Size is significantly smaller (< 1MB)
+- Binary does NOT run (expected)
+
+## Use Cases
+
+### 1. Size Budget Tracking
+```bash
+# Weekly measurement
+./scripts/measure_size.sh
+# Outputs: Demo=512KB, External=4.5MB
+```
+
+### 2. Optimization Targeting
+Identify which subsystems contribute most to demo size:
+- GPU effects: 150KB
+- 3D rendering: 120KB
+- Audio synthesis: 100KB
+- Asset system: 80KB
+
+### 3. CI Integration
+Track size growth over time:
+```yaml
+# .github/workflows/size_check.yml
+- name: Measure demo size
+ run: |
+ cmake -B build_size -DDEMO_STRIP_EXTERNAL_LIBS=ON
+ size=$(stat -f%z build_size/demo64k)
+ echo "Demo size: $size bytes"
+```
+
+## Trade-offs
+
+### Pros
+- Accurate size measurement
+- Identifies optimization targets
+- Simple implementation (stubs)
+- No runtime required
+
+### Cons
+- Maintenance burden (stubs must match real APIs)
+- Binary doesn't run (testing impossible)
+- Platform-specific stubbing (macOS frameworks complex)
+- Stub generation tool needs maintenance
+
+### Alternative Approaches
+
+**1. Link-time size analysis:**
+```bash
+# Use linker map to attribute size
+-Wl,-map,output.map
+# Parse map file to see per-symbol sizes
+```
+**Pro:** No stubs needed
+**Con:** Complex parsing, less accurate
+
+**2. Binary diff analysis:**
+```bash
+# Build with/without each library
+# Diff binary sizes
+```
+**Pro:** Simpler
+**Con:** Doesn't isolate demo code cleanly
+
+**3. Compiler size reports:**
+```bash
+-ffunction-sections -fdata-sections
+-Wl,--print-gc-sections
+```
+**Pro:** Built-in tooling
+**Con:** Still includes external library overhead
+
+**Chosen:** Stub approach (most accurate, clear results)
+
+## Implementation Effort
+
+**Estimated time: 8-12 hours**
+
+- Phase 1: Stub generation (3-4 hours)
+- Phase 2: Build integration (2-3 hours)
+- Phase 3: Compatibility layer (2-3 hours)
+- Phase 4: Validation & documentation (1-2 hours)
+
+**Priority:** Low (measurement tool, not critical for demo)
+
+## Success Criteria
+
+1. Binary compiles successfully with `STRIP_EXTERNAL_LIBS=ON`
+2. Size < 1MB (proves external lib overhead is measured)
+3. Repeatable builds produce consistent sizes
+4. Can track size changes over time
+5. Documentation clear for future use
+
+## Related Files
+
+**New files:**
+- `third_party/stubs/wgpu_native.c` - WebGPU stubs (~200 functions)
+- `third_party/stubs/glfw3.c` - GLFW stubs (~100 functions)
+- `third_party/stubs/miniaudio.c` - Audio stubs (~50 functions)
+- `third_party/stubs/system.c` - System library stubs (pthread, math)
+- `scripts/generate_stubs.py` - Stub generation tool
+- `scripts/measure_size.sh` - Size measurement script
+
+**Modified files:**
+- `CMakeLists.txt` - Add STRIP_EXTERNAL_LIBS mode
+- `src/platform/platform.cc` - Conditional stub overrides
+- `src/audio/audio.cc` - Conditional stub overrides
+
+## Notes
+
+- This is a **measurement tool**, not a runtime mode
+- Binary will not execute (all APIs stubbed)
+- Useful for tracking optimization progress
+- Can reveal surprising size contributors
+- Platform-specific (stubs may differ on Windows/Linux/macOS)
diff --git a/timeline_analysis.html b/timeline_analysis.html
deleted file mode 100644
index 92e4a3f..0000000
--- a/timeline_analysis.html
+++ /dev/null
@@ -1,425 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<meta charset="UTF-8">
-<title>Demo Timeline - BPM 120</title>
-<style>
-body { font-family: 'Courier New', monospace; margin: 20px; background: #1e1e1e; color: #d4d4d4; }
-h1 { color: #569cd6; }
-.info { background: #252526; padding: 10px; border-radius: 4px; margin: 10px 0; }
-svg { background: #252526; border-radius: 4px; }
-.sequence-bar { fill: #3a3a3a; stroke: #569cd6; stroke-width: 2; }
-.effect-bar { fill: #4ec9b0; opacity: 0.8; stroke: #2a7a6a; stroke-width: 1; }
-.effect-bar.invalid { fill: #f48771; stroke: #d16969; }
-.label { fill: #d4d4d4; font-size: 12px; }
-.label.effect { fill: #cccccc; font-size: 11px; }
-.axis-line { stroke: #6a6a6a; stroke-width: 1; }
-.axis-label { fill: #858585; font-size: 10px; }
-.time-marker { stroke: #444444; stroke-width: 1; stroke-dasharray: 2,2; }
-rect:hover { opacity: 1; }
-title { font-size: 11px; }
-</style>
-</head>
-<body>
-<h1>Demo Timeline Gantt Chart</h1>
-<div class="info">
-<strong>BPM:</strong> 120 | <strong>Duration:</strong> 36s (explicit end) | <strong>Sequences:</strong> 14
-</div>
-
-<svg width="1400" height="2230" xmlns="http://www.w3.org/2000/svg">
- <!-- Time axis -->
- <line x1="250" y1="50" x2="1350" y2="50" class="axis-line"/>
- <line x1="250" y1="45" x2="250" y2="55" class="axis-line"/>
- <text x="250" y="40" class="axis-label" text-anchor="middle">0s</text>
- <line x1="250" y1="60" x2="250" y2="2210" class="time-marker"/>
- <line x1="311" y1="45" x2="311" y2="55" class="axis-line"/>
- <text x="311" y="40" class="axis-label" text-anchor="middle">2s</text>
- <line x1="311" y1="60" x2="311" y2="2210" class="time-marker"/>
- <line x1="372" y1="45" x2="372" y2="55" class="axis-line"/>
- <text x="372" y="40" class="axis-label" text-anchor="middle">4s</text>
- <line x1="372" y1="60" x2="372" y2="2210" class="time-marker"/>
- <line x1="433" y1="45" x2="433" y2="55" class="axis-line"/>
- <text x="433" y="40" class="axis-label" text-anchor="middle">6s</text>
- <line x1="433" y1="60" x2="433" y2="2210" class="time-marker"/>
- <line x1="494" y1="45" x2="494" y2="55" class="axis-line"/>
- <text x="494" y="40" class="axis-label" text-anchor="middle">8s</text>
- <line x1="494" y1="60" x2="494" y2="2210" class="time-marker"/>
- <line x1="555" y1="45" x2="555" y2="55" class="axis-line"/>
- <text x="555" y="40" class="axis-label" text-anchor="middle">10s</text>
- <line x1="555" y1="60" x2="555" y2="2210" class="time-marker"/>
- <line x1="616" y1="45" x2="616" y2="55" class="axis-line"/>
- <text x="616" y="40" class="axis-label" text-anchor="middle">12s</text>
- <line x1="616" y1="60" x2="616" y2="2210" class="time-marker"/>
- <line x1="677" y1="45" x2="677" y2="55" class="axis-line"/>
- <text x="677" y="40" class="axis-label" text-anchor="middle">14s</text>
- <line x1="677" y1="60" x2="677" y2="2210" class="time-marker"/>
- <line x1="738" y1="45" x2="738" y2="55" class="axis-line"/>
- <text x="738" y="40" class="axis-label" text-anchor="middle">16s</text>
- <line x1="738" y1="60" x2="738" y2="2210" class="time-marker"/>
- <line x1="800" y1="45" x2="800" y2="55" class="axis-line"/>
- <text x="800" y="40" class="axis-label" text-anchor="middle">18s</text>
- <line x1="800" y1="60" x2="800" y2="2210" class="time-marker"/>
- <line x1="861" y1="45" x2="861" y2="55" class="axis-line"/>
- <text x="861" y="40" class="axis-label" text-anchor="middle">20s</text>
- <line x1="861" y1="60" x2="861" y2="2210" class="time-marker"/>
- <line x1="922" y1="45" x2="922" y2="55" class="axis-line"/>
- <text x="922" y="40" class="axis-label" text-anchor="middle">22s</text>
- <line x1="922" y1="60" x2="922" y2="2210" class="time-marker"/>
- <line x1="983" y1="45" x2="983" y2="55" class="axis-line"/>
- <text x="983" y="40" class="axis-label" text-anchor="middle">24s</text>
- <line x1="983" y1="60" x2="983" y2="2210" class="time-marker"/>
- <line x1="1044" y1="45" x2="1044" y2="55" class="axis-line"/>
- <text x="1044" y="40" class="axis-label" text-anchor="middle">26s</text>
- <line x1="1044" y1="60" x2="1044" y2="2210" class="time-marker"/>
- <line x1="1105" y1="45" x2="1105" y2="55" class="axis-line"/>
- <text x="1105" y="40" class="axis-label" text-anchor="middle">28s</text>
- <line x1="1105" y1="60" x2="1105" y2="2210" class="time-marker"/>
- <line x1="1166" y1="45" x2="1166" y2="55" class="axis-line"/>
- <text x="1166" y="40" class="axis-label" text-anchor="middle">30s</text>
- <line x1="1166" y1="60" x2="1166" y2="2210" class="time-marker"/>
- <line x1="1227" y1="45" x2="1227" y2="55" class="axis-line"/>
- <text x="1227" y="40" class="axis-label" text-anchor="middle">32s</text>
- <line x1="1227" y1="60" x2="1227" y2="2210" class="time-marker"/>
- <line x1="1288" y1="45" x2="1288" y2="55" class="axis-line"/>
- <text x="1288" y="40" class="axis-label" text-anchor="middle">34s</text>
- <line x1="1288" y1="60" x2="1288" y2="2210" class="time-marker"/>
- <line x1="1350" y1="45" x2="1350" y2="55" class="axis-line"/>
- <text x="1350" y="40" class="axis-label" text-anchor="middle">36s</text>
- <line x1="1350" y1="60" x2="1350" y2="2210" class="time-marker"/>
- <!-- Sequence -->
- <rect x="250" y="60" width="61" height="30" class="sequence-bar">
- <title>SEQ@0s [pri=0] (0-2s)</title>
- </rect>
- <text x="10" y="79" class="label">SEQ@0s [pri=0]</text>
- <rect x="256" y="95" width="39" height="20" class="effect-bar">
- <title>FlashCubeEffect [pri=-1] (0.2-1.5s)</title>
- </rect>
- <text x="20" y="110" class="label effect">FlashCubeEffect [pri=-1]</text>
- <rect x="250" y="125" width="30" height="20" class="effect-bar">
- <title>FlashEffect [pri=0] (0-1s)</title>
- </rect>
- <text x="20" y="140" class="label effect">FlashEffect [pri=0]</text>
- <rect x="253" y="155" width="27" height="20" class="effect-bar">
- <title>FadeEffect [pri=1] (0.1-1s)</title>
- </rect>
- <text x="20" y="170" class="label effect">FadeEffect [pri=1]</text>
- <rect x="250" y="185" width="61" height="20" class="effect-bar">
- <title>SolarizeEffect [pri=2] (0-2s)</title>
- </rect>
- <text x="20" y="200" class="label effect">SolarizeEffect [pri=2]</text>
- <!-- Separator -->
- <line x1="250" y1="215" x2="1350" y2="215" style="stroke:#444444; stroke-width:1; stroke-dasharray:4,2;"/>
- <!-- Sequence -->
- <rect x="311" y="220" width="91" height="30" class="sequence-bar">
- <title>SEQ@2s [pri=0] (2-5s)</title>
- </rect>
- <text x="10" y="239" class="label">SEQ@2s [pri=0]</text>
- <rect x="314" y="255" width="88" height="20" class="effect-bar">
- <title>FlashCubeEffect [pri=-1] (2.1-5s)</title>
- </rect>
- <text x="20" y="270" class="label effect">FlashCubeEffect [pri=-1]</text>
- <rect x="311" y="285" width="6" height="20" class="effect-bar">
- <title>FlashEffect [pri=0] (2-2.2s)</title>
- </rect>
- <text x="20" y="300" class="label effect">FlashEffect [pri=0]</text>
- <!-- Separator -->
- <line x1="250" y1="315" x2="1350" y2="315" style="stroke:#444444; stroke-width:1; stroke-dasharray:4,2;"/>
- <!-- Sequence -->
- <rect x="341" y="320" width="122" height="30" class="sequence-bar">
- <title>SEQ@3s [pri=1] (3-7s)</title>
- </rect>
- <text x="10" y="339" class="label">SEQ@3s [pri=1]</text>
- <rect x="341" y="355" width="61" height="20" class="effect-bar">
- <title>ParticleSprayEffect [pri=0] (3-5s)</title>
- </rect>
- <text x="20" y="370" class="label effect">ParticleSprayEffect [pri=0]</text>
- <rect x="341" y="385" width="61" height="20" class="effect-bar">
- <title>ParticlesEffect [pri=1] (3-5s)</title>
- </rect>
- <text x="20" y="400" class="label effect">ParticlesEffect [pri=1]</text>
- <rect x="341" y="415" width="122" height="20" class="effect-bar">
- <title>GaussianBlurEffect [pri=1] (3-7s)</title>
- </rect>
- <text x="20" y="430" class="label effect">GaussianBlurEffect [pri=1]</text>
- <!-- Separator -->
- <line x1="250" y1="445" x2="1350" y2="445" style="stroke:#444444; stroke-width:1; stroke-dasharray:4,2;"/>
- <!-- Sequence -->
- <rect x="356" y="450" width="31" height="30" class="sequence-bar">
- <title>SEQ@3.5s [pri=0] (3.5-4.5s)</title>
- </rect>
- <text x="10" y="469" class="label">SEQ@3.5s [pri=0]</text>
- <rect x="356" y="485" width="7" height="20" class="effect-bar">
- <title>HeptagonEffect [pri=0] (3.5-3.7s)</title>
- </rect>
- <text x="20" y="500" class="label effect">HeptagonEffect [pri=0]</text>
- <rect x="360" y="515" width="27" height="20" class="effect-bar">
- <title>FadeEffect [pri=1] (3.6-4.5s)</title>
- </rect>
- <text x="20" y="530" class="label effect">FadeEffect [pri=1]</text>
- <!-- Separator -->
- <line x1="250" y1="545" x2="1350" y2="545" style="stroke:#444444; stroke-width:1; stroke-dasharray:4,2;"/>
- <!-- Sequence -->
- <rect x="372" y="550" width="153" height="30" class="sequence-bar">
- <title>SEQ@4s [pri=3] (4-9s)</title>
- </rect>
- <text x="10" y="569" class="label">SEQ@4s [pri=3]</text>
- <rect x="372" y="585" width="61" height="20" class="effect-bar">
- <title>ThemeModulationEffect [pri=0] (4-6s)</title>
- </rect>
- <text x="20" y="600" class="label effect">ThemeModulationEffect [pri=0]</text>
- <rect x="372" y="615" width="122" height="20" class="effect-bar">
- <title>HeptagonEffect [pri=0] (4-8s)</title>
- </rect>
- <text x="20" y="630" class="label effect">HeptagonEffect [pri=0]</text>
- <rect x="372" y="645" width="122" height="20" class="effect-bar">
- <title>GaussianBlurEffect [pri=1] (4-8s)</title>
- </rect>
- <text x="20" y="660" class="label effect">GaussianBlurEffect [pri=1]</text>
- <rect x="372" y="675" width="91" height="20" class="effect-bar">
- <title>ChromaAberrationEffect [pri=2] (4-7s)</title>
- </rect>
- <text x="20" y="690" class="label effect">ChromaAberrationEffect [pri=2]</text>
- <rect x="372" y="705" width="153" height="20" class="effect-bar">
- <title>SolarizeEffect [pri=3] (4-9s)</title>
- </rect>
- <text x="20" y="720" class="label effect">SolarizeEffect [pri=3]</text>
- <!-- Separator -->
- <line x1="250" y1="735" x2="1350" y2="735" style="stroke:#444444; stroke-width:1; stroke-dasharray:4,2;"/>
- <!-- Sequence -->
- <rect x="433" y="740" width="61" height="30" class="sequence-bar">
- <title>SEQ@6s [pri=2] (6-8s)</title>
- </rect>
- <text x="10" y="759" class="label">SEQ@6s [pri=2]</text>
- <rect x="439" y="775" width="40" height="20" class="effect-bar">
- <title>FlashCubeEffect [pri=-1] (6.2-7.5s)</title>
- </rect>
- <text x="20" y="790" class="label effect">FlashCubeEffect [pri=-1]</text>
- <rect x="433" y="805" width="61" height="20" class="effect-bar">
- <title>HeptagonEffect [pri=0] (6-8s)</title>
- </rect>
- <text x="20" y="820" class="label effect">HeptagonEffect [pri=0]</text>
- <rect x="433" y="835" width="61" height="20" class="effect-bar">
- <title>ParticleSprayEffect [pri=1] (6-8s)</title>
- </rect>
- <text x="20" y="850" class="label effect">ParticleSprayEffect [pri=1]</text>
- <rect x="433" y="865" width="61" height="20" class="effect-bar">
- <title>ParticlesEffect [pri=2] (6-8s)</title>
- </rect>
- <text x="20" y="880" class="label effect">ParticlesEffect [pri=2]</text>
- <!-- Separator -->
- <line x1="250" y1="895" x2="1350" y2="895" style="stroke:#444444; stroke-width:1; stroke-dasharray:4,2;"/>
- <!-- Sequence -->
- <rect x="479" y="900" width="46" height="30" class="sequence-bar">
- <title>SEQ@7.5s [pri=2] (7.5-9s)</title>
- </rect>
- <text x="10" y="919" class="label">SEQ@7.5s [pri=2]</text>
- <rect x="485" y="935" width="40" height="20" class="effect-bar">
- <title>FlashCubeEffect [pri=-1] (7.7-9s)</title>
- </rect>
- <text x="20" y="950" class="label effect">FlashCubeEffect [pri=-1]</text>
- <rect x="479" y="965" width="15" height="20" class="effect-bar">
- <title>FlashEffect [pri=0] (7.5-8s)</title>
- </rect>
- <text x="20" y="980" class="label effect">FlashEffect [pri=0]</text>
- <!-- Separator -->
- <line x1="250" y1="995" x2="1350" y2="995" style="stroke:#444444; stroke-width:1; stroke-dasharray:4,2;"/>
- <!-- Sequence -->
- <rect x="494" y="1000" width="122" height="30" class="sequence-bar">
- <title>SEQ@8s [pri=10] (8-12s)</title>
- </rect>
- <text x="10" y="1019" class="label">SEQ@8s [pri=10]</text>
- <rect x="500" y="1035" width="40" height="20" class="effect-bar">
- <title>FlashCubeEffect [pri=-1] (8.2-9.5s)</title>
- </rect>
- <text x="20" y="1050" class="label effect">FlashCubeEffect [pri=-1]</text>
- <rect x="494" y="1065" width="122" height="20" class="effect-bar">
- <title>GaussianBlurEffect [pri=0] (8-12s)</title>
- </rect>
- <text x="20" y="1080" class="label effect">GaussianBlurEffect [pri=0]</text>
- <rect x="494" y="1095" width="6" height="20" class="effect-bar">
- <title>FlashEffect [pri=1] (8-8.2s)</title>
- </rect>
- <text x="20" y="1110" class="label effect">FlashEffect [pri=1]</text>
- <rect x="509" y="1125" width="2" height="20" class="effect-bar invalid">
- <title>FlashEffect [pri=1] (8.5-8.2s) *** INVALID TIME RANGE ***</title>
- </rect>
- <text x="20" y="1140" class="label effect">FlashEffect [pri=1] ⚠</text>
- <!-- Separator -->
- <line x1="250" y1="1155" x2="1350" y2="1155" style="stroke:#444444; stroke-width:1; stroke-dasharray:4,2;"/>
- <!-- Sequence -->
- <rect x="509" y="1160" width="122" height="30" class="sequence-bar">
- <title>SEQ@8.5s [pri=2] (8.5-12.5s)</title>
- </rect>
- <text x="10" y="1179" class="label">SEQ@8.5s [pri=2]</text>
- <rect x="509" y="1195" width="61" height="20" class="effect-bar">
- <title>ThemeModulationEffect [pri=0] (8.5-10.5s)</title>
- </rect>
- <text x="20" y="1210" class="label effect">ThemeModulationEffect [pri=0]</text>
- <rect x="515" y="1225" width="55" height="20" class="effect-bar">
- <title>HeptagonEffect [pri=1] (8.7-10.5s)</title>
- </rect>
- <text x="20" y="1240" class="label effect">HeptagonEffect [pri=1]</text>
- <rect x="509" y="1255" width="61" height="20" class="effect-bar">
- <title>ParticleSprayEffect [pri=2] (8.5-10.5s)</title>
- </rect>
- <text x="20" y="1270" class="label effect">ParticleSprayEffect [pri=2]</text>
- <rect x="509" y="1285" width="61" height="20" class="effect-bar">
- <title>ParticlesEffect [pri=2] (8.5-10.5s)</title>
- </rect>
- <text x="20" y="1300" class="label effect">ParticlesEffect [pri=2]</text>
- <rect x="509" y="1315" width="61" height="20" class="effect-bar">
- <title>Hybrid3DEffect [pri=3] (8.5-10.5s)</title>
- </rect>
- <text x="20" y="1330" class="label effect">Hybrid3DEffect [pri=3]</text>
- <rect x="509" y="1345" width="122" height="20" class="effect-bar">
- <title>GaussianBlurEffect [pri=4] (8.5-12.5s)</title>
- </rect>
- <text x="20" y="1360" class="label effect">GaussianBlurEffect [pri=4]</text>
- <rect x="509" y="1375" width="92" height="20" class="effect-bar">
- <title>ChromaAberrationEffect [pri=5] (8.5-11.5s)</title>
- </rect>
- <text x="20" y="1390" class="label effect">ChromaAberrationEffect [pri=5]</text>
- <!-- Separator -->
- <line x1="250" y1="1405" x2="1350" y2="1405" style="stroke:#444444; stroke-width:1; stroke-dasharray:4,2;"/>
- <!-- Sequence -->
- <rect x="616" y="1410" width="306" height="30" class="sequence-bar">
- <title>SEQ@12s [pri=1] (12-22s)</title>
- </rect>
- <text x="10" y="1429" class="label">SEQ@12s [pri=1]</text>
- <rect x="616" y="1445" width="122" height="20" class="effect-bar">
- <title>ThemeModulationEffect [pri=0] (12-16s)</title>
- </rect>
- <text x="20" y="1460" class="label effect">ThemeModulationEffect [pri=0]</text>
- <rect x="622" y="1475" width="55" height="20" class="effect-bar">
- <title>HeptagonEffect [pri=1] (12.2-14s)</title>
- </rect>
- <text x="20" y="1490" class="label effect">HeptagonEffect [pri=1]</text>
- <rect x="616" y="1505" width="122" height="20" class="effect-bar">
- <title>ParticleSprayEffect [pri=2] (12-16s)</title>
- </rect>
- <text x="20" y="1520" class="label effect">ParticleSprayEffect [pri=2]</text>
- <rect x="616" y="1535" width="306" height="20" class="effect-bar">
- <title>Hybrid3DEffect [pri=3] (12-22s)</title>
- </rect>
- <text x="20" y="1550" class="label effect">Hybrid3DEffect [pri=3]</text>
- <rect x="616" y="1565" width="122" height="20" class="effect-bar">
- <title>GaussianBlurEffect [pri=4] (12-16s)</title>
- </rect>
- <text x="20" y="1580" class="label effect">GaussianBlurEffect [pri=4]</text>
- <rect x="616" y="1595" width="153" height="20" class="effect-bar">
- <title>ChromaAberrationEffect [pri=5] (12-17s)</title>
- </rect>
- <text x="20" y="1610" class="label effect">ChromaAberrationEffect [pri=5]</text>
- <rect x="616" y="1625" width="153" height="20" class="effect-bar">
- <title>SolarizeEffect [pri=6] (12-17s)</title>
- </rect>
- <text x="20" y="1640" class="label effect">SolarizeEffect [pri=6]</text>
- <!-- Separator -->
- <line x1="250" y1="1655" x2="1350" y2="1655" style="stroke:#444444; stroke-width:1; stroke-dasharray:4,2;"/>
- <!-- Sequence -->
- <rect x="738" y="1660" width="245" height="30" class="sequence-bar">
- <title>SEQ@16s [pri=0] (16-24s)</title>
- </rect>
- <text x="10" y="1679" class="label">SEQ@16s [pri=0]</text>
- <rect x="738" y="1695" width="62" height="20" class="effect-bar">
- <title>ThemeModulationEffect [pri=0] (16-18s)</title>
- </rect>
- <text x="20" y="1710" class="label effect">ThemeModulationEffect [pri=0]</text>
- <rect x="738" y="1725" width="245" height="20" class="effect-bar">
- <title>HeptagonEffect [pri=1] (16-24s)</title>
- </rect>
- <text x="20" y="1740" class="label effect">HeptagonEffect [pri=1]</text>
- <rect x="738" y="1755" width="245" height="20" class="effect-bar">
- <title>ChromaAberrationEffect [pri=2] (16-24s)</title>
- </rect>
- <text x="20" y="1770" class="label effect">ChromaAberrationEffect [pri=2]</text>
- <rect x="738" y="1785" width="123" height="20" class="effect-bar">
- <title>GaussianBlurEffect [pri=3] (16-20s)</title>
- </rect>
- <text x="20" y="1800" class="label effect">GaussianBlurEffect [pri=3]</text>
- <!-- Separator -->
- <line x1="250" y1="1815" x2="1350" y2="1815" style="stroke:#444444; stroke-width:1; stroke-dasharray:4,2;"/>
- <!-- Sequence -->
- <rect x="983" y="1820" width="122" height="30" class="sequence-bar">
- <title>SEQ@24s [pri=0] (24-28s)</title>
- </rect>
- <text x="10" y="1839" class="label">SEQ@24s [pri=0]</text>
- <rect x="983" y="1855" width="61" height="20" class="effect-bar">
- <title>ThemeModulationEffect [pri=0] (24-26s)</title>
- </rect>
- <text x="20" y="1870" class="label effect">ThemeModulationEffect [pri=0]</text>
- <rect x="989" y="1885" width="55" height="20" class="effect-bar">
- <title>HeptagonEffect [pri=1] (24.2-26s)</title>
- </rect>
- <text x="20" y="1900" class="label effect">HeptagonEffect [pri=1]</text>
- <rect x="983" y="1915" width="122" height="20" class="effect-bar">
- <title>GaussianBlurEffect [pri=2] (24-28s)</title>
- </rect>
- <text x="20" y="1930" class="label effect">GaussianBlurEffect [pri=2]</text>
- <rect x="983" y="1945" width="30" height="20" class="effect-bar">
- <title>SolarizeEffect [pri=3] (24-25s)</title>
- </rect>
- <text x="20" y="1960" class="label effect">SolarizeEffect [pri=3]</text>
- <!-- Separator -->
- <line x1="250" y1="1975" x2="1350" y2="1975" style="stroke:#444444; stroke-width:1; stroke-dasharray:4,2;"/>
- <!-- Sequence -->
- <rect x="1105" y="1980" width="245" height="30" class="sequence-bar">
- <title>SEQ@28s [pri=0] (28-36s)</title>
- </rect>
- <text x="10" y="1999" class="label">SEQ@28s [pri=0]</text>
- <rect x="1105" y="2015" width="122" height="20" class="effect-bar">
- <title>ThemeModulationEffect [pri=0] (28-32s)</title>
- </rect>
- <text x="20" y="2030" class="label effect">ThemeModulationEffect [pri=0]</text>
- <rect x="1111" y="2045" width="55" height="20" class="effect-bar">
- <title>HeptagonEffect [pri=0] (28.2-30s)</title>
- </rect>
- <text x="20" y="2060" class="label effect">HeptagonEffect [pri=0]</text>
- <rect x="1105" y="2075" width="61" height="20" class="effect-bar">
- <title>Hybrid3DEffect [pri=1] (28-30s)</title>
- </rect>
- <text x="20" y="2090" class="label effect">Hybrid3DEffect [pri=1]</text>
- <rect x="1105" y="2105" width="122" height="20" class="effect-bar">
- <title>ParticleSprayEffect [pri=2] (28-32s)</title>
- </rect>
- <text x="20" y="2120" class="label effect">ParticleSprayEffect [pri=2]</text>
- <rect x="1105" y="2135" width="245" height="20" class="effect-bar">
- <title>HeptagonEffect [pri=3] (28-36s)</title>
- </rect>
- <text x="20" y="2150" class="label effect">HeptagonEffect [pri=3]</text>
- <rect x="1105" y="2165" width="245" height="20" class="effect-bar">
- <title>ChromaAberrationEffect [pri=4] (28-36s)</title>
- </rect>
- <text x="20" y="2180" class="label effect">ChromaAberrationEffect [pri=4]</text>
- <rect x="1105" y="2195" width="122" height="20" class="effect-bar">
- <title>GaussianBlurEffect [pri=5] (28-32s)</title>
- </rect>
- <text x="20" y="2210" class="label effect">GaussianBlurEffect [pri=5]</text>
- <!-- Separator -->
- <line x1="250" y1="2225" x2="1350" y2="2225" style="stroke:#444444; stroke-width:1; stroke-dasharray:4,2;"/>
- <!-- Sequence -->
- <rect x="1197" y="2230" width="46" height="30" class="sequence-bar">
- <title>SEQ@31s [pri=0] (31-32.5s)</title>
- </rect>
- <text x="10" y="2249" class="label">SEQ@31s [pri=0]</text>
- <rect x="1197" y="2265" width="46" height="20" class="effect-bar">
- <title>ThemeModulationEffect [pri=0] (31-32.5s)</title>
- </rect>
- <text x="20" y="2280" class="label effect">ThemeModulationEffect [pri=0]</text>
- <rect x="1197" y="2295" width="46" height="20" class="effect-bar">
- <title>SolarizeEffect [pri=1] (31-32.5s)</title>
- </rect>
- <text x="20" y="2310" class="label effect">SolarizeEffect [pri=1]</text>
- <!-- Legend -->
- <rect x="10" y="2215" width="20" height="10" class="sequence-bar"/>
- <text x="35" y="2223" class="axis-label">Sequence</text>
- <rect x="120" y="2215" width="20" height="10" class="effect-bar"/>
- <text x="145" y="2223" class="axis-label">Effect</text>
- <rect x="220" y="2215" width="20" height="10" class="effect-bar invalid"/>
- <text x="245" y="2223" class="axis-label">Invalid Time Range</text>
-</svg>
-<div class="info">
-<strong>Tip:</strong> Hover over bars to see details. Higher priority numbers render later (on top).
-</div>
-</body>
-</html>