# PanoPainter Modernization Roadmap

Status: live
Last updated: 2026-06-02

This is the living roadmap for modernizing PanoPainter into independently
testable C++23 components while retaining all existing functionality. Keep this
file current as phases are implemented. Do not let shortcuts, skipped platforms,
or temporary adapters live only in chat history.

## How To Keep This Roadmap Live

- Update the phase status before and after each implementation pass.
- When a shortcut is introduced, add it to the debt log section in this file
  until `docs/modernization/debt.md` exists, then move debt entries there.
- When a major architectural decision is made, add an ADR under `docs/adr/`
  once that directory exists.
- Every phase must preserve old behavior unless the roadmap explicitly says
  otherwise.
- Each phase must leave the repo in a buildable and testable state.
- Do not add stubs without a debt entry, validation command, and removal
  condition.

## Locked Decisions

- Graphics path: keep OpenGL working first; add Vulkan and Metal after the
  renderer boundary exists.
- Required platforms at phase gates: Windows desktop/AppX, macOS, iOS,
  Android standard, Quest, Focus/Wave, Linux, and WebGL.
- Dependency policy: use vcpkg where reliable; keep SDK, patched, or
  vendor-only dependencies with documented reasons.
- Test stack: Catch2, golden/approval tests, and fuzz/property tests where
  useful.
- Automation: local reproducible matrix first; hosted CI can be added later.
- Documentation: ADRs, debt log, and this living roadmap.
- "vkpkg" in older notes means `vcpkg`.
- Target C++ standard: C++23.
- Initial Windows CMake generator target: Visual Studio 2026 when available.

## Phase Status

| Phase | Name | Status | Gate |
| --- | --- | --- | --- |
| 0 | Inventory, Safety Rails, And Memory | Complete | No behavior changes; old builds still work |
| 1 | Unified CMake Skeleton | In progress | Root CMake builds the Windows app and owns the source list |
| 2 | Toolchain, Diagnostics, And Dependencies | In progress | Strict desktop library builds compile cleanly |
| 3 | Test Harness And Agent-Ready Automation | In progress | `ctest --preset desktop-fast` runs headlessly |
| 4 | Component Split Without Behavior Change | Started | Each extracted target builds and tests |
| 5 | Renderer Boundary And OpenGL Parity | Started | OpenGL output matches golden readbacks |
| 6 | Platform Alignment | Not started | Every supported platform has named validation |
| 7 | Hardening, Coverage, And Breaking-Point Tests | Not started | Each component has edge/failure tests |
| 8 | Future Backend Readiness | Not started | Vulkan/Metal lab targets remain non-default |

## Target Component Architecture

The refactor should move toward one-way dependencies:

```text
pp_foundation
  -> pp_assets
  -> pp_paint
  -> pp_document
  -> pp_renderer_api
  -> pp_renderer_gl
  -> pp_paint_renderer
  -> pp_ui_core
  -> pp_panopainter_ui
  -> pp_platform_*
  -> panopainter_app
```

Intended responsibilities:

- `pp_foundation`: logging facade, math/util helpers, events, task queues,
  binary streams.
- `pp_assets`: `Asset`, `Image`, `Settings`, serialization, ABR, PPBR, and PPI
  helpers.
- `pp_paint`: pure `Brush`, `Stroke`, stroke sampling, and CPU reference blend
  math.
- `pp_document`: canvas document model, layers, animation frames, and undo/redo
  model.
- `pp_renderer_api`: renderer-neutral interfaces for textures, render targets,
  shaders, meshes, readback, frame capture, and tracing.
- `pp_renderer_gl`: current OpenGL implementation behind renderer interfaces.
- `pp_paint_renderer`: stroke rasterization, layer compositing, cube/equirect
  export using `pp_renderer_api`.
- `pp_ui_core`: `Node`, layout, generic controls, text/image primitives.
- `pp_panopainter_ui`: panels, dialogs, `NodeCanvas`, and app-specific
  workflows.
- `pp_platform_*`: Windows, macOS/iOS, Android, Linux, and WebGL shells.
- `panopainter_app`: composition root only.

Rules:

- Component headers must not include platform SDK or graphics API headers unless
  the component name includes that backend or platform.
- Pure libraries must build and test without a window, GL context, network,
  tablet, VR headset, or filesystem outside test temp directories.
- Public APIs should return explicit status/result objects. PanoPainter app
  code keeps exceptions disabled unless isolated SDK wrappers require them.
- Singleton access should be replaced at component boundaries with context or
  service objects. Temporary facade shims require debt entries.

## Phase 0: Inventory, Safety Rails, And Memory

Status: complete on 2026-05-31. Created this roadmap,
`docs/modernization/debt.md`, `docs/modernization/capability-map.md`,
`docs/modernization/build-inventory.md`, and ADR 0001.

Goal: create durable project memory and prevent silent shortcuts before large
refactors begin.

Implementation tasks:

- Add `docs/modernization/roadmap.md`, `docs/modernization/debt.md`, and
  `docs/adr/`.
- Add a shortcut rule: every temporary adapter, fallback, skipped platform, or
  retained vendored dependency must have owner, reason, validation command, and
  removal condition.
- Generate a current capability map covering:
  - project open/save and PPI compatibility
  - image import/export and thumbnails
  - brush presets, ABR import, PPBR export/import
  - layers, blend modes, alpha lock, selection mask
  - animation frames and MP4/timelapse recording
  - VR, tablet, touch, mouse, keyboard, gestures
  - cloud upload/download/browse
  - UI dialogs, panels, layout XML, settings
  - Windows/AppX, macOS, iOS, Android standard, Quest, Focus/Wave, Linux, WebGL
- Record current build commands and known platform prerequisites.

Gate:

- No behavior changes.
- Existing Visual Studio, platform CMake, Gradle, Apple, Linux, and WebGL paths
  are not removed.

## Phase 1: Unified CMake Skeleton

Goal: make CMake the canonical source list without breaking existing projects.

Status: in progress. Root `CMakeLists.txt`, `CMakePresets.json`, and project
option targets exist. The Windows desktop app builds through CMake as
`PanoPainter`; the raw Visual Studio solution/project files were removed on
2026-05-31 by user decision. Android arm64 now configures and builds headless
foundation/tool targets through the root CMake/NDK path. Non-Windows platform
app/package files remain during Phase 6 alignment.

Implementation tasks:

- Add root `CMakeLists.txt` and shared CMake modules under `cmake/`.
- Add `CMakePresets.json` with at least:
  - `windows-vs2026-x64`
  - `windows-clangcl-asan`
  - `linux-clang`
  - `android-arm64`
  - `android-x64`
  - `emscripten`
  - `macos`
  - `ios-device`
  - `ios-simulator`
- Keep Android CMake, Linux CMake, WebGL CMake, Apple project files, and AppX
  packaging during the transition until each consumes shared component targets.
- Move version generation into a CMake custom command using
  `scripts/pre-build.py`.
- Fix `scripts/pre-build.py` only if required to avoid unnecessary rewrites or
  missing-tag failures.
- Add CMake options:
  - `PP_BUILD_APP`
  - `PP_BUILD_TESTS`
  - `PP_BUILD_TOOLS`
  - `PP_ENABLE_OPENGL`
  - `PP_ENABLE_VULKAN_EXPERIMENTAL=OFF`
  - `PP_ENABLE_VR`
  - `PP_ENABLE_CLOUD`
  - `PP_ENABLE_VIDEO`
- Define source-list helper targets so per-platform source duplication can be
  reduced incrementally.

Gate:

- Windows desktop app builds through CMake.
- New CMake can configure on Windows.
- Source list differences are understood and documented.
- Non-Windows platform migration is debt-tracked until Phase 6.

## Phase 2: Toolchain, Diagnostics, And Dependencies

Goal: turn the build into an error-finding system before deep refactors.

Status: in progress. Initial warning/sanitizer option targets, `vcpkg.json`,
a validated Windows headless vcpkg preset, `pp_ui_core` support for vcpkg
tinyxml2 on that preset, and a headless `panopainter_validate_shaders` target
exist. `windows-clangcl-asan` now configures as a headless Ninja/clang-cl ASan
preset and uses the release MSVC runtime required by clang-cl ASan, but local
ASan builds are blocked by DEBT-0014 until Clang and the selected MSVC STL are
compatible. Dependency migration is not complete until remaining component
dependencies and mobile/Apple triplets are validated.

Implementation tasks:

- Set C++23 through target features, not raw compiler flags.
- Add warning profiles:
  - MSVC: `/W4 /permissive- /Zc:__cplusplus /Zc:preprocessor`.
  - Optional MSVC analysis preset: `/analyze`.
  - Clang/GCC: `-Wall -Wextra -Wpedantic -Wconversion -Wshadow
    -Wnull-dereference`.
- Keep exceptions disabled for PanoPainter targets, except isolated SDK wrapper
  targets when unavoidable.
- Add sanitizer presets:
  - Clang/GCC ASan and UBSan for headless libraries.
  - MSVC ASan where supported.
  - TSan only for pure/headless targets.
- Add tooling hooks:
  - `clang-tidy`
  - `cppcheck`
  - shader validation or compile checks
  - CTest dashboard output
- Add `vcpkg.json`.
- Move reliable dependencies to vcpkg first:
  - `fmt`
  - `glm`
  - `tinyxml2`
  - `stb`
  - `curl`
  - `sqlite3`
  - `glad`
  - `Catch2`
- Keep vendored until proven:
  - OpenVR
  - OVR/Wave SDKs
  - Wacom WinTab
  - AppCenter
  - openh264
  - mp4v2
  - libyuv
  - patched or SDK-specific libraries

Gate:

- Desktop library targets compile with strict diagnostics.
- New warnings caused by refactor are fixed or locally justified.
- No global blanket warning suppression for project code.

## Phase 3: Test Harness And Agent-Ready Automation

Goal: make each component reachable by automated tools and future agents.

Status: in progress. `tests/` exists, `desktop-fast`, `fuzz`, and `stress`
CTest presets run headlessly, and
PowerShell/bash wrappers exist for
configure/build/test/analyze/platform-build/package-smoke. `pano_cli` exists
with JSON automation commands for creating a `pp_document` model, metadata-only
PPI project loading, and inspecting image signatures, PPI headers, and layout
XML; full document/app integration is debt-tracked as DEBT-0010 and full PPI
body parsing is debt-tracked as DEBT-0013.

Implementation tasks:

- Add `tests/` with one executable per component.
- Register CTest labels:
  - `foundation`
  - `assets`
  - `paint`
  - `document`
  - `renderer`
  - `ui`
  - `platform`
  - `integration`
  - `fuzz`
  - `slow`
  - `gpu`
- Add `tools/pano_cli` for headless automation.
- `pano_cli` should support:
  - create document
  - load project
  - save project
  - apply scripted strokes
  - import/export images
  - inspect layers
  - run layout parse
  - emit JSON results
- Add local automation wrappers under `scripts/automation/`:
  - configure
  - build
  - test
  - analyze
  - package smoke
- All wrappers must return machine-readable logs or summaries.
- Establish `tests/data/` fixtures:
  - tiny PPI files
  - corrupt/truncated PPI cases
  - PNG/JPEG fixtures
  - ABR/PPBR samples
  - layout XML
  - shader snippets
  - brush stroke scripts

Gate:

- `ctest --preset desktop-fast --build-config Debug` runs without a GL
  context.
- Non-render components can be tested on a headless machine.

## Phase 4: Component Split Without Behavior Change

Goal: split libraries while keeping current app behavior.

Status: started. `pp_foundation` exists with binary stream utilities and
boundary/overread/overlapping-write tests. It also owns strict decimal `uint32` parsing used by
`pano_cli`, with rejection tests for empty, signed, mixed, and overflowing
input. A synchronous event dispatcher, structured logging facade, bounded FIFO
task queue, and deterministic `TraceRecorder` now record
component/name/thread/frame/stroke metadata with filtering, capacity, and
invalid-end tests. `pp_assets` has started with PNG/JPEG signature detection,
PNG IHDR metadata parsing, PPI header/project byte-layout/body-summary
recognition, layer/frame indexing, dirty-face PNG payload metadata validation,
asset-level RGBA PNG payload decoding, and a pure typed settings document
model, with
corrupt/truncated/unsupported, non-finite opacity, unsupported blend-mode,
extreme-dimension, and key/value limit tests.
`pp_paint` has started with pure brush parameter validation/stamp evaluation,
CPU reference math for the five current final RGBA shader blend modes plus the
shader-style stroke-alpha blend modes used by pattern/dual-brush mixing, and deterministic
stroke spacing/interpolation plus duplicate-segment, non-finite, sample-limit,
and 1001-sample stress coverage, plus a pure text stroke-script parser.
`pp_document` has
started with a pure canvas/layer/frame model, alpha-lock metadata, snapshot
construction, per-layer frame metadata, layer metadata operations, frame
move/duration queries, renderer-free RGBA8 cube-face payload storage,
renderer-free alpha8 selection-mask storage, PPI image import/export, and
layer/frame/undo-redo history invariant tests. Snapshot construction validates
embedded face-pixel payload bounds, byte counts, duplicate face payloads, and
duplicate selection masks.
`pp_renderer_api` has started with renderer-neutral
texture/readback descriptors and validation tests. `pp_paint_renderer` has
started with deterministic CPU layer compositing over renderer extents using
the paint blend reference. `pp_ui_core` has started with XML-layout-facing
length parsing, color parsing, tinyxml-backed layout XML parsing, and invalid
input tests.
`pano_cli inspect-image` exposes PNG IHDR metadata as JSON,
`pano_cli import-image` accepts a PNG path and imports decoded RGBA8 pixels
into a new pure `pp_document` face payload,
with checked-in decodable PNG and truncated PNG automation coverage,
`pano_cli export-image` writes a deterministic RGBA8 PNG through `pp_assets`
and round-trips it back through file import automation,
`pano_cli inspect-project` reports validated PPI thumbnail/body byte layout,
body summary, layer/frame descriptors, dirty-face PNG payload metadata, and
asset-level decode coverage, and
`pano_cli load-project` creates a `pp_document` projection with per-layer frame
counts, durations, and decoded face-pixel payload attachment when PPI image
payloads are present.
`pp_assets` can write generated PPI projects with explicit per-layer names,
visibility, opacity, blend mode, alpha lock, per-layer frame durations, and
dirty-face payloads targeted to layer/frame/face slots. `pano_cli save-project`
exposes the generated writer for metadata-only and test dirty-face-payload
round-trips through `load-project` and rejects non-finite automation float
inputs before writing files.
`pp_document::export_ppi_project_document` converts pure documents into PPI
bytes using that writer, including PNG-encoded layer/frame face payloads.
`pano_cli simulate-document-export` exercises that pure document export path,
decodes the generated PPI bytes, reimports them, and emits JSON round-trip
metadata.
`pano_cli save-document-project` writes the same pure document export to a PPI
file for inspect/load round-trip automation.
`pano_cli create-document` can create simple animation documents with explicit
frame count/duration. `pano_cli simulate-document-edits` exercises pure
layer metadata, frame reordering, active-index preservation, tiny face-payload
attachment, and selection-mask attachment. `pano_cli simulate-document-history` exercises the
pure `pp_document::DocumentHistory` apply/undo/redo path and emits JSON state
summaries. `pano_cli simulate-image-import` decodes an embedded tiny PNG
through `pp_assets` and attaches the resulting RGBA8 payload to `pp_document`.
`pano_cli simulate-document-export` exercises pure document-to-PPI export,
asset-level PPI image decode, and document reimport in one automation command.
`pano_cli save-document-project` writes a deterministic pure document export
PPI and verifies it through inspect/load smoke coverage.
`pano_cli simulate-blend` exposes deterministic final RGBA and stroke-alpha
blend reference vectors through JSON automation.
`pano_cli simulate-stroke` exercises the pure stroke sampler for
scripted-stroke automation. `pano_cli simulate-stroke-script`
loads stroke script fixtures, parses them through `pp_paint`, and samples every
stroke. `pano_cli apply-stroke-script` maps sampled script points into a
bounded `pp_document` RGBA8 face payload, writes a PPI file, and verifies that
the applied stroke payload survives inspect/load round-trip automation, with a
rejection smoke test for unsafe tiny canvas dimensions.
`pano_cli parse-layout` exercises the XML layout path. Continue expanding
document behavior toward legacy Canvas parity and then port OpenGL classes
behind the renderer boundary.

Implementation tasks:

- Extract components in this order:
  1. `pp_foundation`
  2. `pp_assets`
  3. `pp_paint`
  4. `pp_document`
  5. `pp_renderer_api`
  6. `pp_renderer_gl`
  7. `pp_paint_renderer`
  8. `pp_ui_core`
  9. `pp_panopainter_ui`
  10. `pp_platform_*`
  11. `panopainter_app`
- Remove renderer/platform dependencies from pure headers first, especially:
  - `Brush`
  - document/layer model
  - serializer
  - UI core headers
- Keep facade shims where needed, but debt-track every shim.
- Avoid large behavioral rewrites during extraction.
- Each extracted component gets a focused test suite before moving to the next.

Gate:

- Old app still launches.
- Component tests pass after every extraction.
- No undocumented stubs or shortcuts.

## Phase 5: Renderer Boundary And OpenGL Parity

Goal: make OpenGL an implementation detail and establish parity tests before
adding new backends.

Status: started. `pp_renderer_api` exists as a headless renderer-neutral target
with renderer feature flags, explicit texture usage flags, texture descriptor, byte-size, viewport,
mesh, readback bounds, command context, render device, shader program
descriptor, mesh, render target, readback byte-size helpers,
texture mip-level validation, resource debug-label validation,
texture-upload/readback command validation,
mipmap-generation command validation, texture-state transition validation, frame-capture byte-size helpers,
readback/copy/frame-capture/blit descriptor validation, frame-capture command validation, render-target blit validation, texture-slot
binding validation, blend-state validation, scissor-state validation,
depth-state validation, trace marker/scope validation, sampler-state validation,
texture/mesh/shader resource-label validation,
recording-device reuse/reset validation, and the canonical PanoPainter shader
catalog now consumed by the legacy OpenGL app initialization path.
`pp_renderer_gl` now exists as the first OpenGL backend library and owns pure
OpenGL capability detection for framebuffer fetch, map-buffer alignment, and
float texture support. It also owns the OpenGL texture upload-type mapping used
by legacy `Texture2D` and `RTT` creation, RGBA pixel-format mapping used by
`RTT` texture allocation, plus image channel-count to texture
format mapping for `Texture2D` image uploads and framebuffer status naming for
`RTT` and `Texture2D` diagnostics. It also owns renderer API texture-format to
OpenGL internal/pixel/component token mapping, including depth-stencil formats,
for future backend texture objects. `Texture2D` 2D texture binding, upload,
mipmap generation, framebuffer readback setup, and update component-type tokens
now delegate to `pp_renderer_gl`. `TextureCube` cube-map binding, allocation
face targets, RGBA allocation format, and unsigned-byte component type also
delegate to `pp_renderer_gl`. RGBA8/RGBA32F readback formats, checked byte-count math, and PBO
pixel-buffer target/usage/access tokens used by `RTT` and `PBO` readbacks now
live in `pp_renderer_gl`. The framebuffer blit color mask and linear/nearest
filter tokens used by `RTT::resize` and `RTT::copy`, renderer API blit-filter
to OpenGL token mapping, plus the default
render-target texture parameters, texture/renderbuffer targets, depth format,
framebuffer targets, binding queries, attachment points, and completion status
used by `RTT::create` and framebuffer bind/restore paths, also live in
`pp_renderer_gl`. RTT clear color/depth masks, renderer API render-pass
color/depth/stencil clear-mask and clear-value mapping, and color-write-mask query tokens also
live in `pp_renderer_gl`. `RTT` no longer spells GL enum names directly.
Renderer API primitive-topology to OpenGL draw-mode mapping, mesh index-type
and primitive-mode decisions used by legacy `Shape` drawing, plus Shape buffer
targets, static upload usage, and vertex attribute component/normalization
tokens, also live in `pp_renderer_gl`. The PanoPainter cube-face to
OpenGL texture-target mapping used by `TextureCube` also lives in
`pp_renderer_gl`. The legacy app delegates extension, upload-format,
framebuffer diagnostic, framebuffer blit, render-target setup, clear-state,
2D/cube texture setup, mesh draw-mode, and cube-face texture-target
interpretation to that backend library. Sampler wrap, min/mag filter, and
desktop border-color parameter mapping for legacy `Sampler` also lives in
`pp_renderer_gl`. Renderer API sampler filter/address-mode to OpenGL token
mapping, including mirrored-repeat, and aggregate renderer API sampler-state to
OpenGL min/mag/wrap mapping are also tested there. The PanoPainter shader attribute
binding catalog, shader stage tokens, compile/link status queries, active-uniform
count query, and matrix-uniform transpose token also live in `pp_renderer_gl`
and are consumed by legacy `Shader` creation. Renderer API blend factor/op to
OpenGL token mapping also lives in `pp_renderer_gl`, with explicit support flags
so `GL_ZERO` remains distinguishable from unsupported enum values. Aggregate
renderer API blend-state to OpenGL enable/factor/equation/color-mask mapping,
depth compare-op to OpenGL depth-function mapping, and aggregate renderer API
depth-state to OpenGL enable/write/compare mapping also live in
`pp_renderer_gl`. Shader uniform hashing, catalog
validation, active-uniform mapping, and the legacy uniform uniqueness check now
delegate to `pp_renderer_gl` as well. `Shader` no longer spells GL enum names
directly. App OpenGL initialization debug severity, debug output, GL info
string, renderer API viewport/scissor rect conversion, default
depth/program-point/line-smooth state, blend factor/equation, and UI
render-target RGBA8 format tokens are now also cataloged and tested in
`pp_renderer_gl`; the legacy convert command and resize path consume the same
backend-owned mapping. App clear color-buffer masks, default framebuffer
binding, scissor state, and sampler filter/wrap tokens now share that backend
mapping too. OpenGL extension enumeration query tokens used before runtime
capability detection also live in `pp_renderer_gl`. Legacy font atlas texture
formats, text mesh buffer targets, attribute component/normalization, draw
primitive/index type, upload usage, and active texture unit selection also
delegate to `pp_renderer_gl`; `Font` no longer spells GL enum names directly.
Canvas undo/redo dirty-region texture updates and readbacks now also delegate
their 2D texture target, RGBA pixel format, and unsigned-byte component type
mapping to `pp_renderer_gl`.
`NodeViewport` preview rendering now also delegates viewport query,
clear-color query, color-buffer clear mask, and blend-state tokens to
`pp_renderer_gl`.
`NodeImageTexture` preview drawing now delegates its fallback 2D texture bind
target and blend-state tokens to `pp_renderer_gl`.
`NodeImage` drawing and remote-image texture creation now delegate mipmapped
sampler filters, blend-state tokens, and RGBA8/RGBA texture format mapping to
`pp_renderer_gl`.
`NodeColorWheel` triangle-buffer setup and draw-state handling now delegate
array-buffer, static-upload, vertex-attribute, primitive-mode, and blend-state
tokens to `pp_renderer_gl`.
Simple UI text, text-input, border, scroll, and animation timeline draw paths
now also delegate blend-state tokens to `pp_renderer_gl`.
Canvas layer cube/equirect generation, clear, restore, and snapshot paths now
also delegate cube/2D texture targets, active texture units, blend/clear state,
and RGBA8 read/write pixel mapping to `pp_renderer_gl`.
`NodePanelGrid` heightmap preview and lightmap baking now delegate texture
readback formats, sampler filters, depth/blend state, depth clears, viewport
queries, color-mask booleans, active texture units, and float render-target
formats to `pp_renderer_gl`.
Legacy `util.cpp` OpenGL error naming and `gl_state` save/restore now delegate
error codes, state queries, framebuffer targets, texture binding targets, and
active texture units to `pp_renderer_gl`.
`NodeStrokePreview` brush preview rendering now delegates depth/scissor/blend
state, viewport/clear-color queries, active texture units, 2D texture targets,
copy targets, and sampler filters/wraps to `pp_renderer_gl`.
Legacy `Texture2D`, `TextureManager`, `Sampler`, and `RTT` public headers no
longer expose raw OpenGL enum defaults; default texture formats, sampler
filters/wraps, and render-target formats are resolved through backend-owned
overloads.
The Windows entrypoint now delegates generic OpenGL error-code/info-string
tokens and WGL core-context/pixel-format attribute catalogs to `pp_renderer_gl`.
The headless OpenGL command planner now consumes `pp_renderer_api` recorded
commands and maps render-pass clear masks/values, viewport/scissor state,
blend/depth/sampler state, texture formats, primitive modes, draw counts, and
blit filters into GL-facing planned command data with explicit unsupported-token
rejection before a runtime GL context is needed. It also plans whole recorded
command streams, preserving per-command planned data while counting render
passes, draws, shader binds, shader uniforms, texture uploads, mipmap
generation, texture transitions, texture copies, texture readbacks, frame
captures, passthrough commands, trace commands, unsupported commands, and
render-pass ordering errors such as state changes outside a pass, nested
passes, and unclosed passes.
The existing renderer classes are not yet fully
behind the renderer interfaces.

Implementation tasks:

- Introduce renderer interfaces:
  - `IRenderDevice`
  - `ITexture2D`
  - `IRenderTarget`
  - `IShaderProgram`
  - `IMesh`
  - `ICommandContext`
  - `IReadbackBuffer`
  - `IRenderTrace`
- Port current renderer classes behind OpenGL backend types:
  - `RTT`
  - `Texture2D`
  - `Sampler`
  - `ShaderManager`
  - `Shape`
- Keep OpenGL runtime capability decisions in `pp_renderer_gl` with headless
  tests before moving GL object lifetimes behind backend types.
- Preserve current shader behavior and asset paths.
- Add deterministic GPU tests:
  - clear
  - blit
  - texture upload/download
  - stroke composite
  - erase
  - layer blend
  - equirect export
  - readback bounds
- Add CPU reference tests for final RGBA and stroke-alpha blend modes.
- Compare GPU output to golden/reference data with explicit tolerances.

Gate:

- OpenGL readbacks match golden data on Windows and Linux.
- Mobile/WebGL compile gates remain green.

## Phase 6: Platform Alignment

Goal: every supported platform consumes the same component targets.

Implementation tasks:

- Convert these builds to shared component targets:
  - Windows desktop
  - Windows AppX
  - macOS
  - iOS
  - Android standard
  - Android Quest
  - Android Focus/Wave
  - Linux
  - WebGL/Emscripten
- Keep platform entrypoints thin:
  - window lifecycle
  - input dispatch
  - clipboard
  - file picker/share
  - GL context creation
  - VR SDK bridge
  - packaging only
- Add or refine CMake toolchain/preset support for:
  - Android NDK ABIs
  - iOS device
  - iOS simulator
  - macOS
  - Emscripten
- Keep SDK-only imported libraries documented until vcpkg triplets are proven.

Gate:

- Every platform has a named configure/build command.
- Missing local prerequisites are documented.
- Each platform has at least compile or package validation.

## Phase 7: Hardening, Coverage, And Breaking-Point Tests

Goal: tests should try to break components, not only confirm current happy
paths.

Implementation tasks:

- Add property/fuzz tests for:
  - binary streams
  - serializers
  - PPI parsing
  - ABR parsing
  - layout XML parsing
  - image metadata parsing
  - brush parameter extremes
  - layer/frame operations
  - undo/redo invariants
- Add stress tests for:
  - thousands of stroke samples
  - extreme resolutions guarded by memory limits
  - rapid layer/frame edits
  - corrupt assets
  - cancellation during export
  - concurrent render/UI task scheduling
- Add coverage for headless libraries on Clang/GCC.
- Require coverage reports for changed components first; do not set a global
  threshold until the baseline is meaningful.
- Add tracing spans around:
  - project load/save
  - render passes
  - stroke commit
  - readback
  - export
  - UI layout
  - platform I/O
- Logs must include component, thread, frame/stroke id, and timing.

Gate:

- No shortcut remains undocumented.
- Every component has unit tests and at least one failure or edge test.

## Phase 8: Future Backend Readiness

Goal: prepare Vulkan and Metal without destabilizing the OpenGL parity path.

Implementation tasks:

- Create non-default targets only after OpenGL backend parity:
  - `pp_renderer_vulkan_lab`
  - `pp_renderer_metal_lab`
- Use `D:\Dev\vkpaint` as reference material for Vulkan painting experiments,
  not as direct production code.
- Before integration, prove:
  - ping-pong compositing path
  - input-attachment/subpass path where applicable
  - feedback-loop or framebuffer-fetch-style path where supported
  - synchronization and layout correctness under validation layers
- Keep WebGPU as an optional future portability backend, not the core renderer
  contract.

Gate:

- Vulkan/Metal lab targets are opt-in.
- OpenGL production backend remains stable.

## Test Matrix

| Preset/Label | Purpose | Requires |
| --- | --- | --- |
| `desktop-fast` | Pure component unit tests | No GPU/window |
| `desktop-gpu` | OpenGL backend golden/readback tests | GPU/GL context |
| `fuzz` | Deterministic parser/serializer edge corpus; future libFuzzer entrypoint | No GPU/window today |
| `stress` | Large and adversarial scenarios | Longer runtime |
| `platform-build` | Configure/build each supported platform | Local toolchains |
| `package-smoke` | AppX/APK/Apple/WebGL package smoke | Platform SDKs |

Acceptance for each phase:

- Previous phase tests still pass.
- New component has its own tests.
- No undocumented stubs.
- No skipped platform without a debt entry.
- Automation command is recorded in this roadmap or linked docs.

## Verified Commands

Last verified on 2026-06-02:

```powershell
cmake --preset windows-msvc-default
cmake --build --preset windows-msvc-default --config Debug --target pp_foundation_binary_stream_tests pp_foundation_event_tests pp_foundation_log_tests pp_foundation_parse_tests pp_foundation_task_queue_tests pp_foundation_trace_tests pp_assets_image_format_tests pp_assets_image_metadata_tests pp_assets_image_pixels_tests pp_assets_ppi_header_tests pp_assets_settings_document_tests pp_paint_brush_tests pp_paint_blend_tests pp_paint_stroke_tests pp_document_tests pp_document_ppi_import_tests pp_document_ppi_export_tests pp_renderer_api_tests pp_paint_renderer_compositor_tests pp_ui_core_color_tests pp_ui_core_layout_value_tests pp_ui_core_layout_xml_tests pano_cli PanoPainter
ctest --preset desktop-fast --build-config Debug
ctest --preset fuzz --build-config Debug
ctest --preset stress --build-config Debug
powershell -ExecutionPolicy Bypass -File scripts\automation\test.ps1 -Preset desktop-fast -Configuration Debug
powershell -ExecutionPolicy Bypass -File scripts\automation\build.ps1 -Preset windows-msvc-default -Configuration Debug -Target pano_cli
cmake --build --preset windows-msvc-default --target panopainter_validate_shaders
powershell -ExecutionPolicy Bypass -File scripts\automation\analyze.ps1 -Preset windows-msvc-default -NoApp
set VCPKG_ROOT=C:\Program Files\Microsoft Visual Studio\2022\Community\VC\vcpkg
cmake --preset windows-msvc-vcpkg-headless
powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets windows-msvc-vcpkg-headless
ctest --preset desktop-fast-vcpkg --build-config Debug
cmake --preset android-arm64
powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets android-arm64
powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug
cmake --fresh --preset windows-clangcl-asan
```

Results:

- `pp_foundation_binary_stream_tests` passed.
- `pp_foundation_event_tests` passed.
- `pp_foundation_log_tests` passed.
- `pp_foundation_parse_tests` passed.
- `pp_foundation_task_queue_tests` passed.
- `pp_foundation_trace_tests` passed.
- `pp_assets_image_format_tests` passed.
- `pp_assets_image_metadata_tests` passed.
- `pp_assets_image_pixels_tests` passed, including RGBA8 PNG decode and corrupt
  payload rejection.
- `pp_assets_ppi_header_tests` passed, including PPI thumbnail/body layout,
  body summary validation, layer/frame indexing, explicit per-layer metadata
  and per-layer frame duration writing, dirty-face PNG payload metadata
  validation, targeted layer/frame dirty-face writing, and decoded dirty-face
  payload coverage.
- `pp_assets_settings_document_tests` passed.
- `pp_paint_brush_tests` passed.
- `pp_paint_blend_tests` passed.
- `pp_paint_stroke_tests` passed.
- `pp_paint_stroke_script_tests` passed.
- `pp_document_tests` passed, including snapshot construction, alpha-lock
  metadata, per-layer frame metadata, frame move, duration, face-pixel payload
  storage/replacement/rejection, snapshot-embedded face-payload rejection, and
  history invariants.
- `pp_document_ppi_import_tests` passed, including decoded PPI dirty-face
  payload attachment to `pp_document` layer/frame storage and out-of-range
  payload rejection.
- `pp_document_ppi_export_tests` passed, including pure document metadata,
  per-layer frame duration, and PNG-encoded face-payload export to PPI bytes,
  plus malformed payload rejection at the export boundary.
- `pp_renderer_api_tests` passed, including shader descriptor validation,
  PanoPainter shader catalog validation, explicit texture usage validation,
  texture mip-level validation, resource debug-label validation, readback
  byte-size and command-order validation, texture-upload byte-count validation,
  mipmap-generation command validation, trace marker/scope validation,
  frame-capture byte-size and command-order validation,
  render-target blit validation, texture-slot binding validation, blend-state
  validation, scissor-state validation, render-pass color/depth/stencil clear
  validation, shader-uniform write validation, draw descriptor/range
  validation, backend-neutral resource factory validation, texture-copy
  validation, recording
  render-pass clear/scissor/depth/blend/shader-uniform/texture/sampler-bind/
  upload/texture-copy/readback/frame-capture/blit command capture, draw
  mesh-input capture, explicit draw-range capture, and invalid catalog
  rejection.
- `pp_paint_renderer_compositor_tests` passed.
- `pp_ui_core_color_tests` passed.
- `pp_ui_core_layout_value_tests` passed.
- `pp_ui_core_layout_xml_tests` passed.
- `pano_cli_create_document_smoke` passed.
- `pano_cli_create_animation_document_smoke` passed and reports animation
  duration JSON.
- `pano_cli_simulate_document_edits_smoke` passed and reports pure
  `pp_document` layer metadata, frame order, active indices, and face-payload
  state as JSON.
- `pano_cli_simulate_document_history_smoke` passed and reports real
  `pp_document::DocumentHistory` apply/undo/redo state as JSON.
- `pano_cli_simulate_document_export_smoke` passed and reports pure
  `pp_document` export to PPI bytes, asset-level decode, and document reimport
  round-trip state as JSON.
- `pano_cli_simulate_image_import_smoke` passed and reports embedded PNG decode
  plus `pp_document` face-payload attachment state as JSON.
- `pano_cli_inspect_image_rejects_unsupported` passed as an expected failure
  test.
- `pano_cli_inspect_png_metadata_smoke` passed and reports PNG metadata JSON
  for the tiny IHDR fixture.
- `pano_cli_import_image_rejects_truncated_png` passed as an expected failure
  test, proving the file-driven image import command rejects a metadata-valid
  but undecodable PNG payload.
- `pano_cli_inspect_project_layout_smoke` passed and reports PPI
  thumbnail/body byte layout, body summary, layer/frame descriptors, and
  dirty-face PNG payload metadata JSON.
- `pano_cli_load_project_metadata_smoke` passed and reports a `pp_document`
  projection with per-layer frame counts, durations, and zero loaded face
  payloads for the minimal PPI fixture.
- `pano_cli_save_project_roundtrip_smoke` passed and proves the metadata-only
  `pp_assets` PPI writer can save a generated multi-frame PPI and reload it
  through `pano_cli load-project`.
- `pano_cli_save_project_payload_roundtrip_smoke` passed and proves the
  `pp_assets` PPI writer can save a compressed RGBA PNG dirty-face payload to
  an explicit layer/frame slot, inspect the serialized descriptor, and reload
  it as decoded `pp_document` face-pixel data.
- `pano_cli_save_document_project_roundtrip_smoke` passed and proves a pure
  `pp_document` export can be written to a PPI file, inspected for layer/frame
  dirty-face descriptors, and loaded back through the PPI import path.
- `pano_cli_apply_stroke_script_roundtrip_smoke` passed and proves a checked-in
  stroke script can be parsed, sampled, applied to a pure `pp_document` face
  payload, written to PPI, inspected for the expected dirty-face box, and loaded
  back as decoded document pixel data.
- `pano_cli_apply_stroke_script_rejects_tiny_canvas` passed as an expected
  failure test, proving the stroke-script document command rejects dimensions
  outside its bounded automation range before payload allocation.
- `pano_cli_parse_layout_smoke` passed.
- `pano_cli_simulate_stroke_smoke` passed and reports deterministic stroke
  sample counts/distances.
- `pano_cli_simulate_stroke_script_smoke` passed and reports deterministic
  aggregate stroke-script counts/distances.
- `panopainter_validate_shaders` passed, validating 25 shader programs and 7
  shader includes for stage markers and include graph integrity.
- `pp_renderer_gl_capabilities_tests` passed on default MSVC, vcpkg-headless,
  and Android arm64 configure/build, covering framebuffer fetch, map-buffer
  alignment, desktop GL core float support, GLES float/half-float extensions,
  WebGL exclusion behavior, upload types for RGBA8/RGBA16F/RGBA32F internal
  formats, image channel-count format mapping including invalid counts, and
  RGBA8/RGBA32F readback format and byte-count mapping, PBO pixel-buffer
  target/usage/access mapping, framebuffer status names, framebuffer blit color
  mask and linear/nearest filters, plus Shape index-type and fill/stroke primitive mode mapping,
  PanoPainter cube-face texture-target order, and the linear clamp-to-edge
  render-target texture parameter set used by `RTT::create`.
  Sampler parameter validation covers wrap S/T/R plus min/mag filter ordering
  used by legacy `Sampler::set` and `Sampler::set_filter`, plus the desktop
  border-color parameter name used by `Sampler::set_border`.
  Shader attribute binding catalog validation covers the current `pos`, `uvs`,
  `uvs2`, `col`, and `nor` bindings and rejects empty, unnamed, null-name, and
  duplicate-name catalogs while preserving legacy shared locations. Shader
  uniform catalog validation covers the 43 legacy uniform
  names used by `Shader`, preserves the legacy hash ids, and rejects empty,
  unnamed, null-name, mismatched-hash, and duplicate-name catalogs.
- `pp_renderer_gl_command_plan_tests` covers the headless OpenGL command
  planner for recorded render-pass clear masks/values, viewport/scissor state,
  blend/depth/sampler state, texture format mapping, mesh/draw primitive modes,
  draw counts, shader bind/uniform names and byte counts, texture
  upload/mipmap/transition/copy/readback/capture metadata, blit filters and
  byte totals, planned command names, unsupported enum/state rejection, whole
  recorded stream planning, valid trace/render/shader/draw/blit ordering, typed
  texture-command counts, and broken render-pass order detection.
- PowerShell analyze automation returns JSON summaries and includes the shader
  validation target and renderer-boundary guard.
- `windows-msvc-vcpkg-headless` configured through the Visual Studio bundled
  vcpkg root, installed the manifest dependencies, built the headless component
  matrix, and passed `desktop-fast-vcpkg`.
- `pp_ui_core` built and tested against vcpkg tinyxml2 on
  `windows-msvc-vcpkg-headless` and against the vendored fallback on
  `windows-msvc-default` and `android-arm64`.
- `windows-clangcl-asan` configures headlessly with clang-cl 18.1.8 and
  release MSVC runtime selection; build remains blocked and debt-tracked in
  DEBT-0014 because the selected VS 2026-preview STL requires Clang 20 or
  newer.
- `PanoPainter.exe` built through CMake at
  `out/build/windows-msvc-default/Debug/PanoPainter.exe`.
- PowerShell build/test automation wrappers return JSON summaries and passed
  local smoke checks.
- Renderer-boundary automation fails if active non-backend source code
  reintroduces raw `GL_*`/`WGL_*` constants outside the allowed legacy OpenGL
  implementation files.
- `pp_renderer_api` now includes a headless `RecordingRenderDevice` with strict
  renderer feature flags, renderer-owned resource factory and
  command-order/render-pass-clear/scissor-state/depth-state/blend-state/
  texture-usage/texture-bind/sampler-bind/shader-uniform/texture-upload/
  mipmap-generation/texture-transition/readback/frame-capture/blit validation plus explicit draw
  descriptor and texture-copy validation; it creates validated textures,
  render targets, shaders, meshes, and readback buffers with validated debug
  labels, then records commands, trace markers/scopes, render-pass
  color/depth/stencil clear intent, scissor state, depth state, blend state,
  shader uniform writes, texture/sampler binds, draw mesh inputs, explicit draw
  ranges, texture uploads/mipmap generations/state transitions/copies/readbacks, frame captures,
  and render-target blits, giving automation a backend-neutral render path that
  does not require a window or GL context. Clearing the recording device now
  resets active render-pass and trace-scope state so interrupted automation can
  reuse a recorder without carrying stale frame state forward.
- `pano_cli record-render` exercises that headless recording renderer and emits
  JSON command counts, backend feature flags, resource creation counts, target dimensions, backend
  name, trace marker/scope and draw summary, labeled descriptor counts,
  render-pass/depth-clear counts, and draw
  descriptor vertex/index totals, scissor/depth/blend-state plus
  shader-uniform/texture/sampler-bind/upload/mipmap-generation/texture-transition/texture-copy/readback/
  frame-capture/blit command/byte totals for agent automation. When
  `pp_renderer_gl` is available, it also emits an `openGlPlan` JSON object with
  planned command count, support status, render-pass/draw/shader-bind/uniform/
  texture-upload/mipmap/transition/copy/readback/capture/passthrough/trace
  counts, unsupported command count, render-pass order error count, and
  unclosed-pass state. The
  `--exercise-clear` mode deliberately clears an interrupted trace/render pass,
  verifies stale trace-scope state is rejected, verifies the render context can
  be reused, and then emits that reset status in JSON. It also has an
  expected-failure smoke for oversized render/readback targets.
- `pano_cli simulate-document-history` exercises pure document history
  apply/undo/redo behavior and emits JSON layer/frame/history state for agent
  automation.
- `pano_cli simulate-document-edits` exercises pure document layer/frame edit
  operations and emits JSON metadata, frame order, face-payload state, and
  selection-mask state for agent automation.
- `pano_cli simulate-image-import` exercises embedded PNG decode through
  `pp_assets` and `pp_document` face-payload attachment through JSON
  automation.
- `pano_cli import-image` accepts a PNG file path, decodes RGBA8 pixels through
  `pp_assets`, attaches them to a pure `pp_document` face payload, and has
  checked-in decodable-PNG plus truncated-PNG rejection smoke tests.
- `pano_cli export-image` writes deterministic RGBA8 PNGs through `pp_assets`
  and has a save/import round-trip smoke test. Full legacy canvas export
  remains a future `pano_cli` task.
- `pano_cli save-project` exposes generated multi-layer, multi-frame PPI
  writing with layer metadata and targeted dirty-face layer/frame payloads
  through JSON automation and is covered by metadata-only and
  dirty-face-payload save/load round-trip smoke tests. Full legacy canvas save
  parity remains tracked by DEBT-0013.
- `pp_assets::create_ppi_project` exposes the underlying generated PPI writer
  for non-uniform layer metadata and frame-duration extraction work.
- `pp_document::export_ppi_project_document` exposes pure document-to-PPI byte
  export through CTest coverage; legacy Canvas save integration remains tracked
  by DEBT-0010/DEBT-0013.
- `pano_cli simulate-document-export` exposes the same export path through JSON
  automation for agents.
- `pano_cli save-document-project` exposes file-writing document export
  automation for inspect/load round trips.
- `pano_cli apply-stroke-script` exposes file-driven stroke-script application
  to a pure document face payload and writes a PPI artifact for inspect/load
  round-trip automation.
- Snapshot creation now rejects invalid embedded RGBA8 face payloads before
  document export or history can persist malformed state.
- PowerShell package-smoke wrapper validates the Windows CMake app executable
  and runtime `data/` copy.
- Android arm64 configured with NDK 29.0.14206865 through the platform-build
  wrapper and compiled headless foundation/tool/test targets.
- Desktop VR drawing now routes generic OpenGL scissor/depth/blend state,
  depth clears, active texture units, and fallback 2D texture unbinds through
  the renderer GL backend mapping; platform VR SDK bridges remain isolated for
  later platform-shell extraction.
- Canvas mode overlay, mask, and transform paths now route generic OpenGL
  blend/depth state, active texture units, 2D copy targets, and RGBA8
  readback formats through the renderer GL backend mapping.
- `NodeCanvas` panorama UI rendering now routes sampler defaults, saved
  viewport/clear/blend/depth/scissor state, color clears, active texture units,
  fallback 2D texture unbinds, copy targets, and RGBA8 render-target formats
  through the renderer GL backend mapping.
- Canvas resource setup now routes stroke-buffer RGBA8/RGBA16F/RGBA32F
  formats, flood-fill texture upload format/type, brush/stencil/mix sampler
  filters and wraps, and cube-strip import channel formats through the renderer
  GL backend mapping. The clamp-to-border sampler wrap is now cataloged and
  tested in `pp_renderer_gl`.
- Early canvas draw helpers now route pick readbacks, stroke mixer depth/scissor
  and blend state, saved viewport/clear-state queries, active texture units,
  fallback 2D texture unbinds, and stroke background copy targets through the
  renderer GL backend mapping.
- Canvas stroke commit now routes saved viewport/clear/blend state, history
  readbacks, active texture units, fallback 2D texture unbinds, and layer
  compositing copy targets through the renderer GL backend mapping.
- Canvas layer merge rendering and explicit layer-merge compositing now route
  depth/blend state, active texture units, fallback 2D texture unbinds, and
  merge framebuffer copy targets through the renderer GL backend mapping.
- Canvas equirectangular import drawing and depth export rendering now route
  depth/blend state and active texture units through the renderer GL backend
  mapping.
- Canvas thumbnail generation and object-drawing helpers now route saved
  viewport/clear/blend state, active texture units, readback format/type,
  framebuffer copy targets, and renderbuffer/depth attachment parameters through
  the renderer GL backend mapping; `src/canvas.cpp` no longer contains raw
  `GL_*` constants.
- Windows desktop OpenGL context creation now consumes a tested
  `windows_wgl_core_context_3_3_config()` catalog from `pp_renderer_gl`, moving
  the active WGL context/pixel-format attribute literals out of the platform
  entrypoint.
- Known remaining warnings: legacy project/vendor diagnostics, Visual Studio
  vcpkg-manifest warning, `LNK4099` missing libyuv PDBs, and `LNK4098` runtime
  library conflict from retained vendor binaries.

## Current Debt Log

The canonical debt log is now `docs/modernization/debt.md`. Keep this section
as a reminder only; do not add new debt entries here.

| ID | Status | Owner | Item | Reason | Validation | Removal Condition |
| --- | --- | --- | --- | --- | --- | --- |
| DEBT-0001 | Open | TBD | Existing platform build files remain alongside new CMake | Required for incremental migration | Existing platform builds plus new CMake configure | Remove after all platform builds consume shared CMake targets |
| DEBT-0002 | Open | TBD | Vendored SDK and patched libraries retained initially | Some dependencies are SDK-only or have platform-specific binaries | Dependency inventory and platform build smoke tests | Replace or document permanent vendored status after vcpkg triplet evaluation |
| DEBT-0003 | Open | TBD | Existing singletons remain during initial split | Avoid behavior changes while introducing boundaries | App launch and component tests | Replace singleton reaches with context/service injection at component boundaries |

## Current Capability Map Seed

Use this as the starting checklist for Phase 0 inventory.

- Project I/O: PPI open/save, thumbnails, version metadata, autosave/save-as
  flows.
- Image I/O: JPEG/PNG import/export, cube faces, equirectangular export,
  depth export.
- Brush system: ABR import, PPBR import/export, presets, tip/pattern/dual brush,
  pressure, jitter, blend modes.
- Painting: six cube faces, temporary stroke buffers, erase, flood fill, masks,
  alpha lock, layer compositing.
- Layers and animation: layer add/remove/move/merge, blend/opacity/visibility,
  frame add/remove/duplicate/duration, MP4/timelapse export.
- UI: XML layout, Yoga layout, panels, dialogs, color tools, brush tools,
  layers, animation timeline, settings, shortcuts, manual/changelog/about.
- Input: mouse, keyboard, touch, gestures, Wacom tablet, stylus pressure,
  VR controllers.
- Platform services: clipboard, file picker, save picker, directory picker,
  share/display file, keyboard show/hide.
- VR/platform variants: OpenVR desktop, Quest, Focus/Wave, Android standard,
  iOS/macOS, Linux, WebGL.
- Cloud/network: upload, download, browse, license/check flows.
- Recording/export: PBO readbacks, MP4 encoder, timelapse frames.
