From 340a7b9605d299cdedfd95d45cc9f939d9714205 Mon Sep 17 00:00:00 2001 From: Orual Date: Fri, 7 Aug 2026 15:41:51 -0400 Subject: [PATCH] PM-86: continue planning remaining visual tasks Epic: PM-86 --- .current-epic/dependencies.dot | 3 + .current-epic/manifest.txt | 1 + .current-epic/plan-L.md | 302 +++++++++++---------------------- .current-epic/task-state.json | 38 +++-- 4 files changed, 126 insertions(+), 218 deletions(-) diff --git a/.current-epic/dependencies.dot b/.current-epic/dependencies.dot index 0dc5c03..6e88278 100644 --- a/.current-epic/dependencies.dot +++ b/.current-epic/dependencies.dot @@ -15,6 +15,7 @@ digraph epic_dependencies { K [label="K: PM-84\nInteroperability, fuzz,\nperformance, visual baselines"]; L [label="L: PM-90\nCorrect LDraw face lighting\nand top/bottom brightness"]; M [label="M: PM-91\nClose-range depth corruption\nand camera clipping precision"]; + N [label="N: PM-92\nCached part-edge\nselection highlight"]; // A (PM-87) blocks: F, G, J A -> F; @@ -67,6 +68,8 @@ digraph epic_dependencies { // PM-90 and PM-91 are operator-approved dynamic tasks discovered during PM-79 visual validation. F -> L; F -> M; + L -> M; + F -> N; // K (PM-84) blocks: none // L (PM-90) blocks: none diff --git a/.current-epic/manifest.txt b/.current-epic/manifest.txt index cf7979e..1ea5d3b 100644 --- a/.current-epic/manifest.txt +++ b/.current-epic/manifest.txt @@ -19,3 +19,4 @@ J PM-83 https://radiant-industries.atlassian.net/browse/PM-83 Format registry an K PM-84 https://radiant-industries.atlassian.net/browse/PM-84 Interoperability, fuzzing, performance, and visual baselines L PM-90 https://radiant-industries.atlassian.net/browse/PM-90 Correct LDraw face lighting and top/bottom brightness M PM-91 https://radiant-industries.atlassian.net/browse/PM-91 Fix close-range renderer depth corruption and camera clipping precision +N PM-92 https://radiant-industries.atlassian.net/browse/PM-92 Use cached part edges for logical-part selection highlight diff --git a/.current-epic/plan-L.md b/.current-epic/plan-L.md index 0efb2c0..639f54e 100644 --- a/.current-epic/plan-L.md +++ b/.current-epic/plan-L.md @@ -1,261 +1,151 @@ # PM-90 / Task L — Correct LDraw face lighting and top/bottom brightness -## Task identity and source of truth - -- **Epic:** PM-86 — LDraw/LEGO model support and flexible renderer -- **Epic URL:** https://radiant-industries.atlassian.net/browse/PM-86 -- **Task:** PM-90 — Correct LDraw face lighting and top/bottom brightness -- **Task URL:** https://radiant-industries.atlassian.net/browse/PM-90 -- **Task prefix:** L -- **Authoritative task cache:** `.current-epic/task-L.md` -- **Epic workspace:** `/home/orual/Projects/epic-86` (runtime manifest records `../epic-86`) -- **Epic bookmark:** `epic-86` +## Metadata and cold-start context + +- **Epic:** PM-86 — [Jira](https://radiant-industries.atlassian.net/browse/PM-86) +- **Task:** PM-90 — [Jira](https://radiant-industries.atlassian.net/browse/PM-90) +- **Prefix:** L +- **Task cache:** `.current-epic/task-L.md` (authoritative and read-only) +- **Planning inputs:** `.current-epic/manifest.txt`, `.current-epic/dependencies.dot`, `.current-epic/task-state.json` +- **Epic workspace/bookmark:** `/home/orual/Projects/epic-86` / `epic-86` - **Base bookmark:** `main` -- **Expected implementation workspace:** `../epic-86-task-L` -- **Expected implementation bookmark:** `epic-86-task-L` -- **Dependency:** F / PM-79, already integrated on the current `epic-86` line; G / PM-80 is also integrated and its current worker protocol/resolver paths are part of the live surface. +- **Expected task workspace/bookmark:** `/home/orual/Projects/epic-86-task-L` / `epic-86-task-L` +- **Dependency:** F / PM-79 only; consume the integrated F parent tip. G / PM-80 is not required. +- **Implementor skills:** load `polytoken:investigating-a-codebase`, `jira-epic-task-execute`, and `reviewing-code-for-shipping-quality`. -This plan is rewritten from the PM-90 task cache and the current integrated F/G renderer code; the previous draft is not a source of truth. +PM-90 is a diagnosis-first, native-only renderer task. The reported top/bottom brightness reversal must be reproduced, the first incorrect production boundary identified, and the approved lighting/material direction applied without changing scene architecture, culling, depth, alpha, colour, edges, picker behaviour, or ordinary-format materials. The operator is the visual authority; native production readback is the visual evidence path. -## Safety and implementation boundary +## Safety and scope -The implementor must work only in `../epic-86-task-L`, verify the task bookmark before editing, and start a fresh jj change. Run this before reading task-local files or changing source: +The implementor starts only in `/home/orual/Projects/epic-86-task-L`, never in `/home/orual/Projects/epic-86` or `main`. Before reading task-local material or editing source, run: ```sh jj workspace root jj bookmark list jj status --no-pager +jj log -r @ -T 'change_id ++ " " ++ bookmarks ++ "\\n"' ``` -Stop immediately if the workspace is the default workspace on `main`, the workspace root is not `../epic-86-task-L` (resolved from the repository parent), or the checked-out bookmark/change is not `epic-86-task-L`. The implementor must not create or move workspaces/bookmarks and must not edit `.current-epic/manifest.txt`, `.current-epic/dependencies.dot`, `.current-epic/task-state.json`, `.current-epic/merge-gate.json`, or any other Epic runtime file. The parent owns task state, integration, and merge operations. - -Create a fresh change with jj, for example: +Stop on a wrong workspace root, the default/Epic workspace, `@` on `main`, or a bookmark other than `epic-86-task-L`. Then create a fresh task change: ```sh -jj new -m "PM-90: correct LDraw face lighting" +jj new -m "PM-90: add neutral lighting diagnosis captures" ``` -Load `polytoken:investigating-a-codebase` before implementation and `reviewing-code-for-shipping-quality` before the final diff pass. No frontend-design skill is needed: this task changes renderer behavior/evidence, not a Dioxus surface. - -## Context and current live evidence - -The PM-90 symptom is visual: known-colour LDraw parts can show top and bottom faces with the opposite brightness/colour from the expected result. Existing numeric checks already show that material colour is not simply swapped and that geometry is not absent. They do **not** establish whether the first defect is authored BFC state, generated index order, normal generation, reflection composition, culling/front-face state, three-d directional-light convention, or a native/static/web divergence. +Do not edit `.current-epic/manifest.txt`, `.current-epic/dependencies.dot`, `.current-epic/task-state.json`, task caches, source branches, or parent coordination state. Do not use browser/Playwright visual tests, routes, Preview plumbing, wasm browser runners, canvas hooks, `TestReadback`, hidden readback APIs, a second renderer/context, or pixel/luminance/region thresholds as visual truth. Existing native production readback is required. -The current live renderer surface that must be grounded before coding is: +## Grounded implementation surface -- `crates/polymodel-renderer-worker/src/lib.rs` adapts LDraw primitives into `SceneGeometry`/occurrences. It carries `bfc_cull`, `bfc_ccw`, `bfc_inverted`, effective colour/material IDs, and the LDraw-to-world basis `(x,y,z) -> (x,z,-y)` with determinant `+1`. -- `scene_oriented_mesh_topology_for_view_kind` currently reverses triangle indices from `bfc_inverted ^ !bfc_ccw` and deliberately ignores the separate `reflection` argument because traversal is intended to fold transform parity into `bfc_inverted`. `scene_instance_batches_for_mode` repeats that parity in the upload batch key and stores instance transforms. -- `build_scene_model_from_batches` creates `PhysicalMaterial`, chooses `Cull::Back` only for BFC clipping, reverses upload indices again from the batch parity, and calls `CpuMesh::compute_normals()` after that reversal. This is the critical generated-topology/local-normal/instance-transform boundary. -- Native persistent preview uses `NativeLights`; the WASM worker and static/offscreen preview use `standard_lights`. These currently have similar but not identical intensities/colours and are separate constructors. The production web worker renders LDraw static/edge models with `PhysicalMaterial` and the same three-d light interface; ordinary STL/OBJ/glTF/3MF uses its existing `build_scene`/PBR path. -- Native readback already exists in `render_scene_window_options_traced` and `ldraw_preview_harness`; the existing `just test-renderer-native` recipe runs Xvfb, writes a 1280x720 PNG, and checks stage/readback evidence. `ldraw_preview` supports explicit output dimensions and `--readback-prefix` frame artifacts. Web preview/offscreen readback uses the production `OffscreenCanvas` → PNG path in `worker.rs`; browser E2E currently proves compound loading/transport but does not prove lighting sign or top/bottom relative appearance. -- Existing tests include topology/index assertions, reflected-batch outward-normal reasoning, deterministic scene/material identity/count checks, native readback nonuniformity/occupancy checks, and ordinary-format checks. They do not yet form the required matched visual identity/reflection/front/back matrix. +Re-check these seams at the integrated F parent tip before editing: `crates/polymodel-renderer-worker/src/lib.rs` camera functions around 923–985, `NativeRenderOptions` around 1406–1425, topology around 1457–1513, scene batching/material construction around 2429–2514 and 3196–3285, and native lights around 2517–2568; `crates/polymodel-renderer-worker/src/worker.rs` `standard_lights` around 1014–1036 and compound Preview camera construction around 1796–1852; native CLI/readback in `crates/polymodel-renderer-worker/src/bin/ldraw_preview.rs` around 107–180, 203–210, 327–349, and 692–860; native harness in `crates/polymodel-renderer-worker/src/bin/ldraw_preview_harness.rs`; and existing Just owners in `justfile` (`test-renderer-worker-native`, `test-renderer-native`, `test-all`, `verify-renderer-split`, `preview-ldraw`). Confirm pinned `three-d` 0.19.0 and its checked-in directional-light implementation, which passes `-direction` to `calculate_light`; analytic light signs must use that exact vector. -Treat every item above as an observation to verify against the checked-out integrated code and pinned three-d API/source. Do not assume lighting is the cause until the evidence workflow identifies the first incorrect boundary. +## Replace-versus-edit decisions -## Replace-vs-edit assessment - -| Area | Decision | Rationale and boundary | +| Area | Decision | Boundary | |---|---|---| -| `crates/polymodel-renderer-worker/src/lib.rs` LDraw topology, batch planning, and GPU upload | **Focused edit; replace a helper if diagnosis shows the current split duplicates parity** | Preserve `Scene`, geometry counts, occurrence identity, picker ranges, depth, and ordinary-format paths. The existing topology/batch/upload code is coherent, but parity is represented in several places; if more than roughly 60% of a helper or module would change, replace that helper with one named orientation/normal policy rather than adding another branch to the existing complex logic. | -| `crates/polymodel-renderer-worker/src/lib.rs` native `NativeLights` and shared material policy | **Replace duplicate policy constructors with one shared production policy** | The current native and worker light constructors are separate and numerically divergent. Replace duplication with a shared, target-safe policy/configuration consumed by native persistent, static/offscreen, and web worker paths. Keep `PhysicalMaterial`, lighting, and culling enabled; do not replace affected materials with unlit materials. | -| `crates/polymodel-renderer-worker/src/worker.rs` `standard_lights`, LDraw render loop, and ordinary mesh render path | **Edit the LDraw policy seam only** | Wire the shared policy into the worker and static preview while leaving lifecycle, reservations, camera/projection, depth clear, ordinary mesh/PBR construction, and frame ordering intact. Do not rewrite `WorkerState`, load state, or the G/G protocol. | -| `crates/polymodel-renderer-worker/src/bin/ldraw_preview.rs` and `ldraw_preview_harness.rs` | **Edit/extend existing production evidence path** | Add deterministic PM-90 fixture/evidence arguments or a focused evidence harness only where needed. Reuse persistent readback and `--readback-prefix`; do not create a parallel renderer or a browser TestReadback hook. | -| `crates/polymodel-renderer-worker/tests/ldraw_preview.rs` and worker unit tests | **Extend existing suites; add a focused fixture module only if it keeps numeric and image contracts legible** | Preserve current geometry/picker/count and ordinary-format tests. Add exact numeric boundary tests and native/browser production-path evidence assertions; do not replace lifecycle coverage. | -| `justfile` and evidence tooling | **Focused edit** | Add one recipe for repeatable PM-90 before/after high-resolution evidence if the existing recipes cannot express the required matrix. Keep all commands behind the Just surface and preserve `test-all`/renderer-split gates. | -| `crates/polymodel-renderer-protocol` and `crates/polymodel-mesh` | **Do not edit unless first-wrong-boundary evidence proves the contract is missing** | PM-90 is a renderer-policy diagnosis. Do not change protocol discriminants, generated API, or ordinary mesh parsing merely to make the symptom disappear. | - -## Required reproducible fixture and visual evidence workflow - -The implementor must capture a baseline **before** changing source and an after image using the same fixture bytes, camera, viewport, clear colour, light vectors/intensities, material colour, depth state, cull state, and output decoding. Record the current jj change ID, fixture SHA-256, dimensions, camera fit/pose, near/far, light policy values, and relevant environment (`LDRAWDIR`, Xvfb/software GL) beside each artifact in `target/pm90/`. - -### Authored diagnostic fixture - -Add or place the smallest deterministic fixture in an existing test-owned fixture location, rather than relying only on the large OMR image. It must contain: - -1. A known explicit LDraw colour (use code 4 and a second contrasting code such as 14) on two parallel visible faces with hand-computable outward normals `+Z` and `-Z`. -2. One certified CCW primitive, one certified CW primitive, and an `INVERTNEXT`/reflected occurrence path where the source winding and transform determinant are independently known. -3. Identity transform and a single-axis reflected transform with determinant exactly `-1` (for example `diag(-1,1,1)`), plus a non-reflected control transform. -4. A closed or two-sided arrangement that makes front/back visibility and culling observable without disabling culling. The expected authored BFC state, direct/effective colour, source span, vertex order, and transform are committed as fixture metadata or test constants. -5. A known official part (the existing `parts/3021.dat` path is already used by the native integration test) and the pinned `omr-10030-1.mpd` as production regressions after the minimal fixture is proven. - -### Identity/reflected × front/back matrix - -The matrix is reproducible and must be asserted numerically before image comparisons. For each row, log and check source path/cache identity, primitive/occurrence ID, direct and effective colour, material ID, authored BFC winding, `bfc_inverted`, transform determinant/reflection, generated indices, local normal, world normal, and light-facing sign: - -| Case | Authored/source orientation | Occurrence transform | Camera/view side | Required boundary assertion | -|---|---|---|---|---| -| I-F | certified CCW, no inversion | identity, det `+1` | face with outward `+Z` toward camera | generated triangle cross product and transformed normal are `+Z`; material remains the exact expected colour/material ID; `dot(world_normal, light_vector)` has the expected positive/negative sign under the controlled light | -| I-B | same identity occurrence | identity, det `+1` | opposite/back side | back-face/cull result is the expected one for the fixture; if the opposite outward face is visible, its normal/material are independently identified rather than inferred from pixel brightness | -| R-F | same authored primitive | single-axis reflection, det `-1` | reflected outward face toward camera | exactly one parity compensation occurs: generated/local winding and reflected instance transform produce the expected world-facing normal, not a double reversal | -| R-B | same reflected occurrence | single-axis reflection, det `-1` | opposite/back side | cull/front-face and world-normal sign match the reflected fixture expectation; no material identity change | -| W-CW | certified CW control | identity, det `+1` | front | `bfc_ccw=false` produces the documented index order and expected world normal without changing material | -| BFC-I | certified CCW plus inversion | identity or include inversion, det `+1` | front/back pair | inversion is consumed once and is distinguishable from transform reflection | -| R×BFC | BFC inversion plus reflected occurrence | det `-1` | front/back pair | XOR/parity composition is explicit and tested; no independent `reflection` argument is silently applied twice | -| OMR/3021 | official production source | real closure transforms | fixed camera front and yaw/back control | scene counts, picker identity, colour mapping, and relative top/bottom appearance remain valid in the production-sized path | - -Use a controlled directional light first. Verify three-d’s directional-light direction convention from the pinned dependency source and encode that convention in the fixture, rather than guessing whether the vector means light-to-surface or surface-to-light. Use light vectors that make the expected signs unambiguous (`+Z`, `-Z`, and an oblique vector), then use the production policy values for the final visual run. - -### First-wrong-boundary instrumentation - -Add a temporary-but-production-gated diagnostic seam, retained only if it is useful and shipping-quality after review, controlled by an explicit environment/feature flag such as `POLYMODEL_LDRAW_LIGHTING_DIAGNOSTICS=1`. It must stop or cap after the first mismatch for each fixture occurrence and must identify the first boundary where the observed value diverges from the fixture expectation. It must report structured fields, not a vague “lighting issue” message: - -- source path/cache key, source span, definition/geometry/primitive ID, canonical occurrence ID, and instance index; -- direct colour, inherited/effective colour, material ID, resolved albedo, and whether code 16/24 was flattened; -- authored `bfc_state`, `bfc_ccw`, source inversion, accumulated transform determinant/reflection, and final parity bit; -- source vertices, semantic forward/back indices, generated topology indices, local cross-product normal, transformed/world normal, camera/view vector, actual light vector, and checked dot/sign; -- cull mode, front-face/winding state as actually submitted to three-d, depth-test/depth-write state, and render path (`native-persistent`, `native-static-readback`, `web-worker-interactive`, or `web-worker-offscreen-preview`). - -The instrumentation must execute at the boundary under diagnosis (semantic adaptation, topology generation, upload normal generation, material creation, and render policy as applicable), not reconstruct values after pixels are produced. It must not alter indices, normals, material values, culling, depth, or lighting. Remove or retain only a bounded, documented diagnostic path after the implementation review; do not leave noisy per-frame logging in ordinary operation. - -### Exact matched render workflow - -1. **Baseline capture:** on the unmodified integrated PM-86 parent, run the focused high-resolution evidence recipe described below for the authored fixture, official 3021, and OMR. Save `before-native-persistent`, `before-native-static`, and `before-web-worker` PNGs plus JSON/log metadata under `target/pm90/before/`. Run the diagnostic flag and capture the first-wrong-boundary report before changing code. -2. **Numeric boundary pass:** run the pure scene/topology/material tests against the same fixture. A failing numeric boundary prevents interpreting a visual image as a lighting-only defect. -3. **Native persistent path:** use the existing `ldraw_preview`/harness production window/readback, Xvfb, software GL, and `NativeLights`/shared policy. Render at 2560x1440 and at the existing 1280x720 compatibility size. Capture a fixed camera plus the required front/back and identity/reflected poses; use `--readback-prefix` for frame 1/2 artifacts where applicable. -4. **Native static/offscreen readback path:** exercise the existing static model/offscreen preview render that produces PNG bytes from the production renderer, not a hand-built test mesh. Use the same fixture/camera/lights/material policy and record PNG dimensions/hash and pixel-region measurements. -5. **Web worker path:** build the real worker, load the compound fixture through the worker protocol, and capture the visible production canvas/preview image through the existing app/browser route or `PreviewImageRendered` PNG contract. Use Playwright only for the visible production surface and network/readiness evidence. Do **not** add or restore `TestReadback`, `renderer-test-readback`, `RendererReadback`, window globals, hidden pixel APIs, or e2e-only renderer hooks. -6. **Pixel-region comparison:** define fixed normalized regions for the known `+Z`/`-Z` faces and background. Decode the PNGs and report median/mean linearized luminance and RGB medians per region, occupied-pixel count, and material-colour identity. Do not compare antialiased edge pixels as face colour. The expected relative result is a controlled-light inequality with an explicit tolerance, not an arbitrary screenshot hash; hashes remain useful to detect accidental drift. -7. **After capture and parity:** repeat the exact commands, dimensions, poses, metadata, and region measurements after the fix. The after artifacts must show the expected top/bottom relationship in all three production paths, while the matrix logs show the same semantic/material/cull/depth values. +| `crates/polymodel-renderer-worker/src/lib.rs` camera/topology/material/native-light code | Focused edit; add a small shared camera/policy seam only where it removes duplication | Keep camera fit, topology, model-build, and readback lifecycle. Replace a helper only when more than 60% changes or it cannot express the frozen contract; never replace all of `lib.rs`. | +| `crates/polymodel-renderer-worker/src/worker.rs` | Focused edit | Route worker LDraw lighting/material construction through the shared policy; preserve lifecycle/loading/cancellation and ordinary mesh paths. | +| `crates/polymodel-renderer-worker/src/bin/ldraw_preview.rs` and `crates/polymodel-renderer-worker/src/bin/ldraw_preview_harness.rs` | Focused edit | Add case/variant selection, metadata, and explicit dimensions; retain native readback, picker, persistent event loop, and one harness. | +| `crates/polymodel-renderer-worker/tests/ldraw_preview.rs` and existing unit tests | Focused extension | Add the minimal camera/variant/policy/index/material assertions beside official 3021/OMR coverage; do not expand ordinary-format parser coverage. | +| `justfile` / `tools/in-dev-shell` | Focused edit to existing owners only | Pass capture arguments and enforce the 2560×1440 display contract; no PM-90 public recipe or aggregate restructuring. | +| Protocol/app/routes/generated code | No change | A protocol change requires a first-wrong-boundary report naming the changed contract; web visual scope remains excluded. | -## Numbered implementation steps +## Frozen shared camera contract owned by L -### 1. Verify the live parent, safety boundary, and renderer contracts +L owns exactly one generic, versioned `CameraCase`; M may consume it but may not add task-specific scene, control, depth, clipping, or diagnostic fields. Schema identity is `pm-camera-case-v1`. Fields are only `case_id`, `schema_version`, yaw/azimuth, pitch/elevation, roll, target/center `[f64;3]`, fit or explicit finite distance, FOV, viewport width/height, and `FitBounds` near/far policy. -Read `.current-task/task.md` and `.current-task/plan.md` in the task workspace after the safety checks. Inspect the current PM-79/PM-80 symbols named above, the pinned three-d material/light/normal/culling source, and the current Just recipes. Confirm whether static preview means the native persistent static model readback, the web offscreen preview, or both; cover both in the evidence plan rather than silently choosing one. +Serialization is deterministic with stable field order. Reject unknown schema, zero dimensions, non-finite angles/target/distance/FOV, invalid distance/FOV, and conflicting CLI sources. Defaults preserve the current camera: fitted `scene_camera_fit` center, `normalize([1.8,-2.4,1.5])`, 45° FOV, fitted distance, zero yaw/pitch/roll, and 1280×720 when no case is supplied. Apply right-handed yaw, then pitch, then roll around target, using the same fit and clip policy on both native paths. -**Done when:** the task change records the live parent/change ID, exact renderer entry points, three-d light/vector convention, current cull/front-face/depth semantics, and the three production evidence routes; implementation is grounded in the checked-out integrated code. +The six named cases remain `identity_front`, `identity_back`, `reflected_front`, `reflected_back`, `top`, and `bottom`, with the existing yaw/pitch values and 2560×1440 capture viewport. Do not encode reflected-vs-identity in `CameraCase`: add a generic `SceneVariantBinding` selected by the case manifest/CLI. It binds each reflected case to a specific fixture occurrence/scene variant with canonical occurrence ID, definition/source span, occurrence index, transform determinant/parity, and expected topology/material metadata. Identity cases bind the identity occurrence; reflected cases bind the determinant−1 occurrence; top/bottom bind explicit control occurrences. Fixture bytes do not change between cases. -### 2. Capture before artifacts and identify the first wrong boundary +Analytic tests must assert that the selected occurrence and determinant differ for identity versus reflected cases, that front/back camera transforms are distinct, and that the capture metadata reports the selected binding. Legacy `--yaw-degrees` is accepted only without `--camera-case`; both sources are a hard parse error. Native persistent and static readback consume the same case plus binding. No web/browser visual path is added. -Create the deterministic minimal fixture and metadata. Run the gated diagnostics and the full identity/reflected/front/back matrix through semantic adaptation, topology, GPU upload preparation, and native production render. Capture baseline high-resolution artifacts before source edits and classify the first divergence as one of: authored BFC/source data, scene adaptation, generated index order, local normal, instance/world normal, cull/front-face, material identity/albedo, light direction/intensity, or path-specific policy. +## Fixture and diagnosis contract -If the baseline image symptom cannot be reproduced with the minimal fixture, use the official 3021 and OMR path with the same controlled camera/light configuration and retain the evidence showing which fixture reproduces it. Do not “fix” a non-reproduced symptom by changing light values. +Before any corrective edit, add bounded environment-gated diagnostics such as `POLYMODEL_RENDERER_LIGHTING_DIAGNOSTICS=1`. Diagnostics do not mutate render state and record one first-mismatch row per occurrence/case: case/revision, fixture/scene hash, native path, source span/definition, transform determinant, direct/effective/primitive colour and `MaterialId`, authored BFC state, source/generated/upload indices, local/world normals, camera position/target/up, actual light-facing vector `-DirectionalLight.direction` and normal dot/sign, cull, depth test/write, alpha/blend, and `PhysicalMaterial` policy. -**Done when:** a reviewer can inspect one report and map each case to the first incorrect boundary, with before PNGs/logs and exact fixture/camera/light metadata; a lighting-only hypothesis is either numerically supported or rejected. +Add the smallest test-owned fixture under existing renderer ownership before changing shared policy. It contains colour 4 and contrasting colour 14 on parallel visible `+Z`/`−Z` faces; certified CCW and CW primitives; `INVERTNEXT`/source inversion; identity and `diag(-1,1,1)` determinant −1 occurrences; and a closed/two-sided arrangement with culling enabled. Record the fixture hash and the limited metadata needed by the camera/variant binding and analytic tests: source span, occurrence ID/index, determinant/parity, expected topology indices/normals, direct/effective colour, `MaterialId`, cull state, and light-facing sign. Official `parts/3021.dat` and `crates/polymodel-ldraw-testkit/corpus/omr-10030-1.mpd` remain production regressions after the minimal fixture. -### 3. Establish one shared production orientation/normal/material policy +The focused matrix remains named `I-F`, `I-B`, `R-F`, `R-B`, `W-CW`, `BFC-I`, `R×BFC`, `OMR`, and `3021`. Assert the first-wrong-boundary candidates analytically—authored/generated/upload topology, normals, occurrence parity, material identity, culling, and light-facing sign—without requiring a giant per-case freeze table. Reflection remains consumed at one named boundary only; do not apply determinant parity twice. -Implement the smallest policy change supported by Step 2. Ensure authored BFC winding, traversal inversion, transform determinant, generated index order, local normal generation, and world/instance normal semantics have one explicit parity contract. If the existing `reflection` argument is intentionally redundant, keep it out of the computation and document/test that fact; if evidence proves it is needed at a different boundary, move the parity operation to that boundary rather than XORing it again. +## Numbered execution plan: two sequential changes -Use one shared light/material policy for native persistent, native static/offscreen, and web worker LDraw renders. Keep `PhysicalMaterial` with normal lighting, preserve the known-colour albedo and roughness/metallic semantics, retain depth testing/writes and BFC culling, and verify front-face state rather than disabling culling. Do not use an unlit material, ambient-only workaround, arbitrary colour swap, polygon-offset mask, or camera change to hide the error. +1. **Safety and grounding.** Verify workspace/bookmark safety, integrated F parent revision, `epic-86@`, listed seams, native owner, official library, software-GL/Xvfb environment, and `three-d` light convention. Start the fresh task change named above. **Done when:** the evidence header records exact parent revision and environment, and no source or coordination file changed before the fresh change. -**Done when:** the exact matrix passes numeric orientation/material/light-sign assertions; the shared policy is used by all three production paths; ordinary STL/OBJ/glTF/3MF construction and rendering are unchanged. +2. **Change 1 — behaviour-neutral camera/variant/capture tooling and fixture only.** Add `pm-camera-case-v1`, the six cases, shared camera construction, exclusive CLI parsing, generic `SceneVariantBinding`, native static/persistent capture metadata, first-mismatch diagnostics, and the minimal fixture. Preserve current camera defaults, topology, BFC/parity behaviour, materials, light constants, culling, depth, alpha, edges, picker, lifecycle, and ordinary formats. Add only sufficient Tier 1 tests for CameraCase serde/validation/CLI conflicts, variant selection/metadata, fixture hash/required occurrence metadata, and unchanged production capture trace. This change must not correct lighting/material policy or topology. **Done when:** the new plumbing is tested, the reflected cases select and report the reflected occurrence, both native paths accept explicit 2560×1440 dimensions, Gate-A artifacts contain command/case/variant/fixture metadata plus valid dimension-checked PNGs, and `jj diff` shows only camera/variant/capture/fixture/diagnostic plumbing. -### 4. Add numeric regression tests at every relevant boundary +3. **Interactive stop gate A — unchanged appearance.** Commit Change 1 with exactly `Epic: PM-86` and `Task: PM-90` trailers. Report the change ID and staged native capture command, then stop. The operator inspects the staged native production case and confirms live that current appearance is unchanged from before the tooling change. **Done when:** the parent records explicit live confirmation; no confirmation means the second change cannot begin. -Add Tier 1 pure tests for the authored fixture and helper functions. Each test must assert exact or tightly bounded values, not only counts: +4. **Hard reproduction gate.** After gate A, run the staged native case over the minimal fixture and official 3021/OMR matrix. Use `/home/orual/Projects/epic-86/lighting-shading-error.png` as the canonical historical failure-class reference. The operator must confirm live that the staged native output exhibits the same top/bottom brightness/face-lighting error class. Record control cases that show no defect as evidence. A failed reproduction blocks implementation and returns to fixture/camera/environment diagnosis; it is not a completion state. **Done when:** the defect class is explicitly reproduced and operator-confirmed, or the task is blocked with the diagnosis evidence. -- semantic source indices and BFC/inversion bits; -- generated triangle indices before and after the single parity operation; -- local cross-product normals and transformed/world normals for det `+1` and det `-1` transforms; -- material identity, effective colour, exact albedo mapping, and no code-16/24 accidental substitution; -- light-facing sign at dot products `-1.0`, `-1e-6`, `0.0`, `+1e-6`, `+1.0`, plus the chosen production tolerance boundary immediately below/at/above it; -- identity/reflected × front/back cull/front-face outcomes and the no-double-reflection invariant; -- invalid/non-finite/degenerate vectors and foreign index domains remain rejected as before. +5. **Pre-fix matched capture.** Only after reproduction, capture `identity_front`, `identity_back`, `reflected_front`, `reflected_back`, `top`, and `bottom` through native persistent and native static production readback. Use identical fixture bytes, case serialization, dimensions, library revision/hash, light/material/cull/depth environment, and command shape reserved for the post-fix capture. Store separate pre-fix PNGs, metadata, and contact sheet for human inspection. **Done when:** every case has valid nonempty PNGs and exact dimensions, and the operator can inspect the complete pre-fix set; no pixel/luminance/region threshold is used. -Keep geometry conservation, index bounds, quad triangulation, picker ranges, and occurrence/material identity assertions. If a shared policy type is introduced, test its configuration equality across native/static/web target builds. +6. **Change 2 — diagnose and correct the shared production boundary.** Start a fresh change only after reproduction and pre-fix artifacts exist. Identify the first mismatch across authored BFC winding, generated/upload indices, local/world normals, determinant/reflection composition, culling, effective colour/`MaterialId`, `PhysicalMaterial`, and the `three-d` light convention. Add one small, behaviour-neutral `LdrawLightingMaterialPolicy` descriptor in `crates/polymodel-renderer-worker/src/lib.rs` for the approved LDraw ambient/key/fill energies, colours, direction vectors, albedo/roughness/metallic, and render-state expectations. Make native `NativeLights::new` and `crates/polymodel-renderer-worker/src/worker.rs` `standard_lights` consume that same descriptor; there must be no duplicated native/worker constants or different policy. Apply the correction only at the diagnosed boundary. Preserve `PhysicalMaterial`, culling, depth test/write, alpha/colour/edges, picker, ordinary-format materials, and no-double-reflection. Do not disable lighting/culling/depth or substitute unlit materials. **Done when:** the report names the first wrong boundary and ruled-out hypotheses, native and worker LDraw paths use the same testable policy seam, and the focused diff preserves the surrounding lifecycle. -**Done when:** a numeric test fails for each injected wrong-boundary mutation (one extra reversal, missing reversal, inverted normal, swapped material, reversed light vector, or altered cull policy), and all existing scene/picker/count tests remain green. +7. **Worker/native analytic proof and matched post-fix capture.** Add focused host assertions for exact indices, normals, determinant parity, `-DirectionalLight.direction` signs, material IDs/albedo, cull/depth/alpha, CameraCase errors, variant binding metadata, and only the negative mutations needed to distinguish the diagnosed boundary. Add an explicit worker-target analytic test for the shared policy descriptor, compiled through the worker wasm test path; assert policy version/values, light-facing vector convention, material mapping, and render-state expectations without browser/canvas visual truth. Capture the identical six cases and both native production paths with unchanged fixture/case/variant/dimension/library/environment metadata. Store separate post-fix artifacts. **Done when:** the worker-target test fails if native/worker policy values or sign convention diverge, reflected metadata remains bound to the reflected occurrence, PNGs are valid/nonempty/dimension-checked, LDraw geometry/picker/official regressions remain green, and the operator explicitly accepts the matched visual result at stop gate B. -### 5. Add production-path visual/evidence assertions without prohibited browser hooks +8. **Review, repository gates, and handoff.** Load `reviewing-code-for-shipping-quality`, inspect `jj diff`, and remove noisy diagnostics/process residue. Run the required public `just fix` and record its status. Run the repository-required `just test-all` and record its status; do not claim that its internal `_fix` is the explicit `just fix` invocation. Run `just verify-renderer-split` explicitly; it is absent from the current `test-all` DAG. Keep the persistent CLI capture invocation separate from the static harness proof and require operator inspection/approval of its output; do not create an automated persistent visual oracle. Do not add a PM-90 public recipe or restructure the validation DAG. **Done when:** no critical/high review finding remains, both implementation changes have exact trailers, all evidence and command statuses are recorded, and the parent receives a merge-ready handoff. -Extend the existing native Xvfb/readback integration to assert known face-region luminance/colour relationships for the minimal fixture and an official regression. Extend the static/offscreen PNG test to use the same scene policy and region measurement. Add a browser-backed worker assertion through the visible production canvas/preview image or existing PNG response contract; assert render readiness, image dimensions, non-background face regions, and the same relative appearance tolerance. Keep browser tests transport/readiness-oriented where pixels are not exposed by the production surface; do not add TestReadback or a test-only renderer API. +## Test strategy and executable capture policy -The native evidence test must retain the current 1280x720 contract and add a high-resolution 2560x1440 artifact run. The high-resolution comparison must be deterministic enough to catch the top/bottom reversal while allowing software-GL antialiasing noise; use region medians/inequalities and metadata, not a brittle whole-image byte equality. +| Behaviour | Concrete proof and tier | +|---|---| +| Camera schema, defaults, rotation order, fit/clip, finite/degenerate inputs, stable serialization, CLI conflicts | Focused Tier 1 tests in the camera seam and native parser; exact serialized bytes and error variants sufficient for the new plumbing. | +| Scene variants and reflected binding | Focused Tier 1 tests select identity versus determinant−1 occurrence bindings, assert metadata/occurrence IDs/determinants, and prove front/back camera transforms are distinct; no task-specific fields enter `CameraCase`. | +| BFC, source/generated/upload indices, normals, inversion, determinant parity, no-double-reflection | Existing focused native worker tests plus the minimal fixture matrix; exact indices/vector components/signs, never pixels. | +| Shared native/worker lighting/material policy | Host analytic tests compare native and worker descriptors; an explicit worker-target test compiled through `just test-renderer` asserts exact energies, colours, directions, `-direction` convention, material mapping, and render-state expectations. No browser visual test. | +| Native static and persistent production readback | Static evidence uses `ldraw_preview_harness` and `render_scene_window_options_traced`; persistent evidence uses the real `ldraw_preview` binary/event loop with the command below. Assert command success, selected case/variant metadata, requested viewport, PNG validity/dimensions, and path-specific logs. These are evidence/trace checks, not image or luminance oracles. | +| Defect reproduction and visual acceptance | Operator inspects native staged output against `/home/orual/Projects/epic-86/lighting-shading-error.png`, confirms reproduction, confirms Gate A unchanged appearance, and approves matched pre/post contact sheets. No automated visual threshold. | +| LDraw regressions and renderer split | Existing official 3021/OMR, geometry-count, picker, and aggregate coverage remain green; run `just verify-renderer-split` explicitly if absent from `just test-all`. Ordinary-format parser/material expansion is not part of this focused LDraw correction. | -**Done when:** the same fixture and controlled policy produce expected top/bottom relative appearance in native persistent, static/offscreen, and web worker production paths; before/after artifacts are available at 2560x1440 and the existing 1280x720 gate remains green; no prohibited browser readback surface exists. - -### 6. Preserve ordinary formats, depth/culling/lighting, and renderer split - -Run ordinary STL/OBJ/glTF/3MF worker tests and the existing viewer route. Verify the policy extraction does not pull `three-d`/`three-d-asset`/mesh dependencies into the app bundle, does not change protocol discriminants, and does not alter ordinary PBR material/normal behaviour. Verify LDraw geometry counts, picker selection/subtree ranges, BFC culling, depth ordering, edge/conditional rendering, and scene lifecycle remain intact. - -**Done when:** ordinary formats, LDraw counts/picker behavior, culling/depth/lighting, and `just verify-renderer-split` all pass, with no unrelated protocol/UI/generated-file changes. - -### 7. Validate, review, and commit - -Use Just recipes exclusively. Run the focused evidence gate at baseline and after implementation, then run the repository gates in this order: +The persistent command is a concrete invocation through the real binary and is inspected by the operator, not replaced by a new automated event-loop visual oracle: ```sh -just check -just fix -just test-renderer-worker-native -just test-renderer-native -just test-renderer -just test-ldraw-core -just ldraw-corpus -just build-renderer-worker -just verify-renderer-split -just e2e -just lint -just test-all +Xvfb :99 -screen 0 2560x1440x24 -ac +extension GLX +render -noreset >/tmp/pm90-xvfb.log 2>&1 & +DISPLAY=:99 WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY= XDG_RUNTIME_DIR= LIBGL_ALWAYS_SOFTWARE=1 \\ + LDRAWDIR="$LDRAWDIR" timeout --signal=TERM --kill-after=5s 180s \\ + just preview-ldraw "$FIXTURE" --library-root "$LDRAWDIR" \\ + --width 2560 --height 1440 --camera-case "$CASE" --scene-variant "$VARIANT" \\ + --readback-prefix "$OUT/persistent" --proof-ms 1500 ``` -Add and use a focused recipe named `test-renderer-lighting-evidence` that runs the PM-90 fixture matrix and emits both `target/pm90/before/` and `target/pm90/after/` high-resolution artifacts at **2560x1440**, plus the existing **1280x720** compatibility render. The recipe must own Xvfb/software-GL/environment setup and must print fixture hash, camera, light/material policy, output paths, and region metrics. `just test-renderer-native` remains mandatory; it is not replaced by the evidence recipe. +The static path uses the existing `just test-renderer-native` owner/harness with the same `--width 2560 --height 1440`, case, variant, fixture, and display. The implementor must thread those arguments through the existing binaries/owner and fail rather than silently falling back to 1280×720; logs must show the requested and actual viewport. Use Just recipes for repository build/test work; keep explicit capture commands separate from `just test-all`. Run public `just fix` as required, then public `just test-all`, and explicitly run `just verify-renderer-split` because the current DAG does not include it. Record each status honestly; do not optimize or restructure the repository DAG. -If a focused browser worker visual test needs a distinct recipe, add it behind Just and make it use the real worker/app route; do not invoke raw cargo/dx/npm commands from the plan or implementation handoff. Run `just build-renderer-worker` before web rendering and let `just e2e` own the browser command surface. +## Review expectations -Before commit, load `reviewing-code-for-shipping-quality`, inspect `jj diff`, changed files, and `jj status --no-pager`. Remove diagnostic/process residue unless the retained diagnostic path is bounded, documented, and explicitly reviewed. Commit on `epic-86-task-L` with a concise message and exactly these trailers: +Reviewers verify the two-change ordering and hard gates: Change 1 contains only behaviour-neutral camera/variant/capture/fixture/diagnostic work; the operator confirmed unchanged appearance; reproduction was operator-confirmed against the canonical failure class; pre-fix cases were captured before Change 2; and post-fix cases are identical in case, fixture, variant, dimensions, and environment. They verify the first-wrong-boundary report, shared native/worker policy seam, independent worker-target analytic proof, reflected occurrence bindings/metadata, executable 2560×1440 Xvfb/display plumbing, native static versus real persistent CLI invocation, preserved cull/depth/alpha/colour/edges/material semantics, official LDraw regressions, no browser/hidden readback/pixel oracle, no generated churn, and no recipe proliferation. Persistent output is operator evidence, not an automated visual oracle. + +## Ready-for-parent-merge handoff + +Report `/home/orual/Projects/epic-86-task-L`, bookmark `epic-86-task-L`, both change IDs, consumed F parent revision, `jj status --no-pager`, `jj diff --stat`, and complete diff review. Include grouped changed files and replace-versus-edit decisions; fixture/library/case hashes; exact native capture commands and separate pre/post artifact paths; canonical reproduction and operator decisions at gates A and B; first-wrong-boundary diagnosis and ruled-out hypotheses; analytic mutation outcomes; exact command exit statuses; and concrete environment blockers. Both implementation commits contain exactly: ```text Epic: PM-86 Task: PM-90 ``` -**Done when:** all focused numeric/visual gates and the complete command sequence pass; the diff is limited to the planned renderer/tests/evidence/Just files; the commit has both required trailers; no browser TestReadback or unlit/culling/depth workaround is present. - -## Test strategy matrix - -| Behavior | Tier/infrastructure | Concrete proof | -|---|---|---| -| Authored BFC/winding/inversion reaches scene IR | Tier 1 pure scene/adaptation tests | Minimal fixture asserts source vertices, BFC bits, inversion, colour, indices, and stable identity/span. | -| Generated topology parity | Tier 1 worker unit tests | Identity, BFC inversion, reflected det `-1`, and composed parity vectors assert exact index streams and one-reversal-only behavior. | -| Local/world normals | Tier 1 numeric tests plus pinned three-d contract review | Cross products and transformed normals assert signs/components for identity, reflection, front, and back. | -| Light-facing sign | Tier 1 scalar/vector tests | Dot products test `-1`, `-1e-6`, `0`, `+1e-6`, `+1`, and immediately below/at/above the selected tolerance. | -| Material identity/effective colour | Tier 1 material-policy tests | Direct/effective code, MaterialId, and `Srgba` albedo are exact; code 16/24 flattening remains intentional and stable. | -| Culling/front-face/depth preservation | Native production integration plus Tier 1 policy assertions | Native GL fixture proves expected front/back visibility with cull/depth enabled; no `Cull::None` for certified face batches and no depth disable. | -| Native persistent render | Existing Xvfb native integration (`just test-renderer-native`) | Production `ldraw_preview`/harness reads back PNG; current 1280x720 contract and PM-90 region metrics pass. | -| Native high-resolution evidence | Focused Just evidence recipe | Same scene at 2560x1440 before/after, fixed pose/lights, region medians/inequalities and metadata recorded under `target/pm90`. | -| Static/offscreen readback | Existing production offscreen PNG path and focused integration test | `render_preview_png`/static model path produces the same expected face-region relation and dimensions without a test-only renderer. | -| Web worker parity | Existing wasm worker tests plus browser E2E through visible production surface/PNG contract | Real worker build/load/render uses the shared policy; browser asserts readiness/dimensions/visible face-region relationship and no TestReadback hook. | -| Ordinary STL/OBJ/glTF/3MF behavior | Existing worker unit/wasm tests and `just e2e` viewer coverage | PBR materials, source normals/derived normals, textures, bounds, and rendering remain unchanged. | -| LDraw geometry/picker/lifecycle | Existing `crates/polymodel-renderer-worker` native tests, `tests/ldraw_preview.rs`, `just test-renderer-native`, `just test-all` | Counts, index domains, occurrences, picker hits/subtrees, edges, cancellation/readiness, and cleanup remain green. | -| First-wrong-boundary diagnosis | Gated production diagnostics + fixture expectation checker | The first mismatching source/scene/topology/normal/material/light/cull field is named; diagnostics do not mutate render state. | -| Renderer split | Repository gate | `just build-renderer-worker` and `just verify-renderer-split` confirm app remains protocol-only and worker artifacts are valid. | -| Full repository safety | Exact Just sequence above | `just check`, `just fix`, focused tests/evidence, `just lint`, and `just test-all` results are recorded exactly. | - -This task uses no provider, daemon, TUI `TestBackend`, or browser TestReadback tier: the behavior is renderer-native/WASM and must be proven through the existing native GL/readback, production offscreen PNG, worker WASM, and visible browser surfaces. - -## Implementation review expectations - -Reviewers must inspect the complete diff and verify: - -1. The baseline and after evidence use the same fixture bytes, camera, dimensions, clear colour, light/material policy, and software-GL environment; high-resolution artifacts are 2560x1440 and the existing 1280x720 gate remains. -2. The first-wrong-boundary report is concrete and distinguishes authored BFC, generated indices, local/world normals, reflection parity, cull/front-face, material identity, and light convention; the implementation does not assume lighting caused the defect without evidence. -3. Exactly one authoritative parity policy exists from authored winding through generated indices, normal computation, instance transforms, and world-facing normals. The separate reflection argument is not silently double-applied. -4. Numeric tests cover exact indices, normal vectors, dot/sign boundaries, material IDs/albedo, and identity/reflected/front/back fixture outcomes, including injected wrong-boundary mutations. -5. Native persistent, native static/offscreen, and web worker LDraw paths use equivalent light/material semantics; policy values are not accidentally divergent by target. -6. `PhysicalMaterial`, lighting, depth testing/writing, and BFC culling remain enabled and intentional. No unlit fallback, arbitrary colour swap, depth disable, cull disable for certified faces, or camera/polygon-offset masking is used. -7. Ordinary STL/OBJ/glTF/3MF rendering, LDraw geometry counts, picker behavior, edge/conditional paths, lifecycle, and renderer split remain green. -8. Existing PM-79/G protocol/load/resolver behavior is not rewritten as part of a visual symptom fix; no generated API/assets or browser TestReadback surface is added. -9. The focused Just evidence recipe is reproducible, prints metadata/metrics, and writes before/after artifacts outside source control; all exact Just gates and observed failures are recorded. -10. The final diff is shipping-quality: no stale-plan material, speculative comments, uncontrolled diagnostics, process residue, or unrelated refactor. - -## Ready-for-parent-merge handoff - -Before returning to the parent, report: - -- task workspace `../epic-86-task-L`, bookmark `epic-86-task-L`, final jj change/commit ID, and the consumed `epic-86` parent change ID; -- complete changed-file list grouped as renderer policy, tests/fixtures, evidence tooling, and Just surface; -- exact pass/fail output for `just check`, `just fix`, `just test-renderer-worker-native`, `just test-renderer-native`, `just test-renderer`, `just test-ldraw-core`, `just ldraw-corpus`, `just build-renderer-worker`, `just verify-renderer-split`, `just e2e`, `just lint`, and `just test-all`; -- baseline/after artifact paths, dimensions, fixture SHA-256, camera/light/material metadata, and numeric region metrics for native persistent, static/offscreen, and web worker paths; -- first-wrong-boundary diagnosis and the exact production policy correction; -- confirmation that identity/reflected and front/back matrix tests pass, including indices, normals, light-facing signs, material identity, culling, and depth; -- confirmation that ordinary formats, geometry counts, picker behavior, worker protocol, and renderer split remain green; -- confirmation that no browser TestReadback/TestReadback-like hook, unlit workaround, disabled depth, or disabled certified-face culling was added; -- `jj diff`/changed-file review result and every observed blocker or environment limitation. - -The parent may merge only after the implementation review accepts the full evidence and the task change is independently merge-ready. Parent integration must rebase the task change onto the current `epic-86`/Epic tip, run the parent-owned merge gates, and preserve the PM-90 trailers and evidence summary in the Jira handoff. - -## Prior finding dispositions - -No prior reviewer findings were supplied for planner-L1. The stale plan was replaced in full because it described a different Jira task and was not a valid basis for PM-90. +The parent owns Jira workflow, rebasing onto the current PM-86 Epic tip, merge gates, and final integration. The parent verifies analytic/state evidence, native production readback, both trailers, and both operator visual checkpoints before merging. + +## Prior-review and coverage-finding dispositions + +- **L6 two-change sequence / circular baseline — fixed.** Change 1 is behaviour-neutral, Gate A is hard and operator-confirmed, reproduction is hard, pre-fix evidence precedes Change 2, and post-fix evidence is command/case/variant/fixture/display matched. +- **L6 correction without reproduction — fixed.** The canonical `/home/orual/Projects/epic-86/lighting-shading-error.png` is retained as the historical reference and the operator must confirm the reproduced failure class before correction. +- **Coverage 1 shared native-worker policy seam — fixed.** A single testable descriptor feeds native `NativeLights::new` and `crates/polymodel-renderer-worker/src/worker.rs` `standard_lights`; an explicit worker-target analytic test protects equivalence without browser visual truth. +- **Coverage 3 persistent CLI evidence — partially fixed per operator direction.** The plan names an executable `just preview-ldraw` invocation through the real `crates/polymodel-renderer-worker/src/bin/ldraw_preview.rs` binary, with operator inspection/approval. It deliberately does not invent an automated persistent event-loop visual oracle; static harness evidence remains distinct. +- **Coverage 4 reflected/front-back identity — fixed.** A generic `SceneVariantBinding` selects concrete identity/reflected occurrences, records occurrence/determinant/source metadata, and is asserted independently of generic `CameraCase` fields. +- **Coverage 5 2560×1440 execution — fixed.** The exact Xvfb screen, `preview-ldraw` arguments, static-path dimensions, requested/actual viewport logging, and fail-on-fallback contract are explicit. +- **Coverage 2 ordinary-format production expansion — rebutted-accepted by explicit operator decision.** PM-90 is a focused LDraw lighting correction; expanding STL/OBJ/glTF/GLB/3MF parser/material production assertions is disproportionate once the shared policy is analytically protected. Existing ordinary-format behavior remains in the repository’s existing regression scope and is not narrowed by this plan. +- **Coverage 6 giant freeze table — rebutted-accepted by explicit operator decision.** The focused case matrix, minimal fixture metadata, analytic indices/normals/material/sign assertions, hard reproduction gate, matched evidence, and operator authority provide sufficient PM-90 control; a giant per-case source/generated/upload freeze table is disproportionate. +- **Coverage 7 command-DAG formatting semantics — rebutted-accepted by explicit operator decision.** Redundant formatter ownership is harmless to plan correctness. The plan preserves required public `just fix`, `just test-all`, and explicit `just verify-renderer-split` statuses without restructuring the repository DAG. +- **Coverage 8 maximal independent Change-1 taxonomy — rebutted-accepted by explicit operator decision.** Change 1 adds sufficient tests for the actual CameraCase/variant/capture plumbing and Gate A; an independent maximal host-test taxonomy is disproportionate and not required for acceptance. +- **Earlier architecture screenshot-path MEDIUM — rebutted-accepted.** Both paths exist; `/home/orual/Projects/epic-86/lighting-shading-error.png` is the stable project-root canonical evidence copy. The harmless LOW prefix inconsistency is corrected everywhere to `crates/polymodel-renderer-worker/src/worker.rs` and `crates/polymodel-renderer-worker/src/bin/ldraw_preview.rs`. + +## Definition of done + +- The reported defect is reproduced and operator-confirmed before correction, and the first wrong production boundary is identified. +- Change 1 is behaviour-neutral camera/variant/capture/fixture/diagnostic tooling, with live unchanged-appearance confirmation and pre-fix captures. +- Change 2 applies the diagnosed approved lighting/material correction and produces identical post-fix native cases. +- One generic `pm-camera-case-v1` controls all six native poses with frozen serialization, defaults, equations, handedness, and CLI conflict behaviour; M can consume it without M-specific fields. +- Native persistent/static paths share LDraw camera/material/light/cull/depth semantics; numeric tests cover indices, normals, light signs, and material identity. +- Existing LDraw counts, picker behaviour, native readback, renderer split, and final aggregate remain green; ordinary-format behavior is not narrowed, but no new ordinary-format production test expansion is required for this focused task. +- Human operator accepts the matched native visual result; automation validates state, metadata, hashes, PNG validity/dimensions, and regressions only. +- No browser/Playwright/route/Preview plumbing, pixel threshold, unlit material, disabled culling/depth, hidden readback hook, second renderer, generated churn, unrelated refactor, or duplicate validation recipe exists. diff --git a/.current-epic/task-state.json b/.current-epic/task-state.json index 7624ca0..25b9e06 100644 --- a/.current-epic/task-state.json +++ b/.current-epic/task-state.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "updated_at": "2026-08-06T18:35:00Z", + "updated_at": "2026-08-07T20:47:00Z", "updated_by": "polytoken", "epic": { "jira_key": "PM-86", @@ -114,7 +114,7 @@ "last_error": null }, "H": { - "phase": "plan-authored-halted", + "phase": "completed", "jira_key": "PM-81", "jira_url": "https://radiant-industries.atlassian.net/browse/PM-81", "title": "LDraw points, surfaces, explicit edges, and conditional lines", @@ -124,11 +124,11 @@ "plan_path": ".current-epic/plan-H.md", "task_context_dir": "../epic-86-task-H/.current-task", "job_id": null, - "change_id": null, - "last_error": "Operator questioned whether PM-81 is superseded by integrated F/G. H2 was cancelled. Existing H1 plan is not accepted; await decision to narrow H to conditional-line classifier correction or supersede it with a smaller task." + "change_id": "sqxozxwrkrzokkmpslnwkvtpvnzkosox", + "last_error": null }, "I": { - "phase": "pending", + "phase": "planner-spawned", "jira_key": "PM-82", "jira_url": "https://radiant-industries.atlassian.net/browse/PM-82", "title": "LDraw TEXMAP and embedded texture pipeline", @@ -137,7 +137,7 @@ "task_cache": ".current-epic/task-I.md", "plan_path": ".current-epic/plan-I.md", "task_context_dir": "../epic-86-task-I/.current-task", - "job_id": null, + "job_id": "general-purpose:planner-i1", "change_id": null, "last_error": null }, @@ -170,7 +170,7 @@ "last_error": null }, "L": { - "phase": "planner-spawned", + "phase": "plan-authored", "jira_key": "PM-90", "jira_url": "https://radiant-industries.atlassian.net/browse/PM-90", "title": "Correct LDraw face lighting and top/bottom brightness", @@ -179,12 +179,12 @@ "task_cache": ".current-epic/task-L.md", "plan_path": ".current-epic/plan-L.md", "task_context_dir": "../epic-86-task-L/.current-task", - "job_id": "general-purpose:planner-l1", + "job_id": null, "change_id": null, - "last_error": null + "last_error": "L7 accepted by explicit operator disposition. Skeptic finding L-plan-7-S1 rebutted-accepted: canonical reference images are the established visual baseline; planner-L8 cancelled." }, "M": { - "phase": "planner-spawned", + "phase": "plan-authored", "jira_key": "PM-91", "jira_url": "https://radiant-industries.atlassian.net/browse/PM-91", "title": "Fix close-range renderer depth corruption and camera clipping precision", @@ -193,9 +193,23 @@ "task_cache": ".current-epic/task-M.md", "plan_path": ".current-epic/plan-M.md", "task_context_dir": "../epic-86-task-M/.current-task", - "job_id": "general-purpose:planner-m2", + "job_id": null, "change_id": null, - "last_error": "Planner-M1 exceeded provider context after successfully inspecting both screenshots; planner-M2 restarted with recovered visual facts and tightly bounded code inspection." + "last_error": "M4 remains unchanged and accepted by explicit operator decision. All M skeptic objections are recorded as rebutted-accepted in .current-epic/findings-M.jsonl; implementation eligibility is blocked only by dependencies.dot L -> M until L / PM-90 completes." + }, + "N": { + "phase": "planner-spawned", + "jira_key": "PM-92", + "jira_url": "https://radiant-industries.atlassian.net/browse/PM-92", + "title": "Use cached part edges for logical-part selection highlight", + "workspace": "../epic-86-task-N", + "bookmark": "epic-86-task-N", + "task_cache": ".current-epic/task-N.md", + "plan_path": ".current-epic/plan-N.md", + "task_context_dir": "../epic-86-task-N/.current-task", + "job_id": "general-purpose:planner-n1", + "change_id": null, + "last_error": null } } } -- 2.51.2