Skip to content

docs(prds): PRD-561 decline contact shadows from screen depth - #480

Merged
jonit-dev merged 8 commits into
developfrom
feat/prd-561-contact-shadows
Oct 12, 2026
Merged

jonit-dev merged 8 commits into
developfrom
feat/prd-561-contact-shadows

Conversation

@jonit-dev

@jonit-dev jonit-dev commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

What is this PR about?

PRD-561 is declined, so the planned soft shadow under feet and small objects will not ship. The engine and the games do not change. The platformer keeps its flat disc under the fox.

Why does it matter?

Small objects and feet do not look grounded, because no shadow shows where they touch the ground. The plan was to add that shadow. In the judged test poses on the platformer camera, 0 of 3 showed the feet touching the ground. Low-resolution light maps also added noisy dots to the image. Keeping the plan open keeps it on the list with no path to ship.

What changed

  • PRD-561 moves to docs/PRDs/done/ with Status: DECLINED and its decision record.
  • The old copy in docs/PRDs/unreal-source-borrowing/ is removed.
  • The PRD-561 row in that folder's README.md links to the new location.

How I checked it

  • git diff --name-only origin/develop...HEAD | grep -v '^docs/' prints nothing. The branch changes only docs.
  • 8eb1159c7 deleted the mechanism code. The diff against develop has no code.
  • pnpm check:docs exits 0. It checked 3029 relative links in 1350 Markdown files.
  • Six prose-lane specs pass: check-doc-links, evidence-budget, evidence-citations, sync-agent-docs, ci-structure and ci-needs. 246 tests pass.
  • Every commit that the PRD cites exists (git cat-file -t).
  • Merged the current develop (b753d2013). The only conflict was the README row.
  • Not run: the full CI board. The diff is docs only, and the prose-lane checks above cover it.

Still open

The decline closes these items without running them:

  • Phase 1: pick one of the two shadow methods, using the judge's verdict.
  • Phase 2: the platformer drops its flat disc and uses the shadow.
  • Phase 3: the desktop native host matches the browser.
  • Acceptance checks AC-1 to AC-3: a visible shadow under the fox, and GPU cost on desktop and on a Pixel 8.

No box is ticked for work that did not run.

Where to look first

  • docs/PRDs/done/PRD-561-contact-shadows-from-screen-depth.md, lines 1 to 12: the status and the decision.
PRD progress: 25% — 0/3 phases (1/4 boxes), 0/3 acceptance (generated)

PRD: docs/PRDs/done/PRD-561-contact-shadows-from-screen-depth.md

Phase 1: Mechanism and the apply decision — 1/2

  • The dispatch-list builder writes every on-screen pixel at least once, except the pixel under the light, for a light in front of, behind, beside and far off screen, and for viewports that are not multiples of 64. Overlap stays bounded (see ## Decisions). proof: pnpm exec vitest run packages/core/__tests__/contact-shadow-dispatch.spec.ts — Evidence: 7 passed (1920x1080, 1001x577, 63x65, 1x1, 130x70, 1024x512, six light placements each; bounds case 301,203 to 700,510).
  • Arm A or Arm B is chosen with the judge's verdict, and the choice is written under ## Decisions. proof: pnpm visuals:ab --before <arm A> --after <arm B> --raters 3 on the starter and the dark-environment fixture.

Phase 2: Templates use it — 0/1

  • The platformer ships without the blob, and the template gate passes for the platformer and the starter. proof: pnpm test:templates.

Phase 3: Native — 0/1

  • The desktop native host produces the same mask as the browser within tolerance for a fixed depth fixture and light. proof: pnpm parity --target desktop --only-tests contact-shadow.

Acceptance criteria

  • AC-1 [local]: In a platformer scenario, with the blob mesh removed and the stage on, the region under the standing fox's feet has a dark-pixel ratio at least 0.3. The same scenario fails with the stage off. proof: node packages/playtest/dist/runner/cli.js playtests/contact-shadow.playtest.json --url <dev url> --browser-recipe webgpu with a region minDarkPixelRatio assertion — Evidence: open. Arm A at this camera scale did not produce it; two judge rounds REGRESSION (see ## Decisions).
  • AC-2 [local]: The stage costs at most 0.3 ms GPU at 1080p on the RTX 2080 browser WebGPU lane. proof: TN_FRAME_BUDGET p50 delta, stage on against stage off, from node packages/playtest/dist/runner/cli.js perf — Evidence: open. Informal gpuCompute 0.1 to 0.2 ms; the perf p50 delta was not run on a shipped stage.
  • AC-3 [local]: On the Pixel 8, the stage costs at most 0.5 ms GPU at the starter's mobile resolution, or the low tier refuses it by name. proof: node packages/playtest/dist/runner/cli.js perf --logcat <serial> stage on against stage off — Evidence: open. Physical device, no shipped stage to measure (see ## Blocked on).

Add the Bend screen-space-shadow port to core as mechanism: the CPU
dispatch builder (contact-shadow-dispatch.ts, Apache-2.0, with a spec that
emulates every thread) and the TSL compute kernel (contact-shadow.ts) behind
contactShadow(). The chain gains a contactShadows stage name before
ambientOcclusion. THIRD_PARTY_NOTICES.md ships in the package.

The builder rounds the light to float32 and measures bounds from
floor(light), the pixel the kernel measures from. The upstream round() left
column 1919 unwritten for a light at x = 38400 on a 1920-wide target.

The kernel has not run on a GPU yet; PRD-561 box 2 and the stage wiring
follow.
@jonit-dev jonit-dev added the prd:25% PRD phase 1 verified label Oct 10, 2026
… blob

Both the starter and the platformer gain src/render/contactShadowStage.ts, a
chain stage (minimumTier medium) that multiplies the screen-space contact
mask from @threenative/core into the frame. The look (reach, contrast,
strength per tier) lives in that generated file; the dispatch and kernel stay
in core. The platformer drops its painted blob mesh and the `drop` pose field.

The scaffold keeps the render-folder import ban with one exact-string
exemption for the contactShadow import, mirrored in the core-boundary
allowlist. The fox rig floats 0.05 to 0.2 m above its capsule bottom, which
the blob used to mask; the convention test now states that bound.

Proof: contact-shadow.playtest.json on the RTX 2080 (webgpu recipe) passes
with the stage on and fails with it off; the same scenario passes on a
scaffolded starter.
…ing and silhouettes

Final tried state of the template stage before Phase 2 is withdrawn: the view
space comes from the pass camera's projection inverse (the post quad's camera
made every rebuilt normal flat), the term applies only on sun-facing pixels
and not across silhouettes. Two fresh visual judges still returned REGRESSION.
Kept in history so the follow-up can start from it.
…o REGRESSION judge verdicts

Two fresh visual judges returned REGRESSION on the platformer (the stage did
not beat the control with the blob removed). The templates, the core-boundary
allowlist additions and the platformer playtest go back to the Phase 1 state.
The WGSL flag-index fix in contact-shadow.ts stays: it is a kernel bug found
on a GPU, independent of the template wiring.

The stage source is kept in 4afd70c and PR #480 for the follow-up.
…s blocked

The contact-shadow stage built and ran on a GPU, and failed the visual judge
twice on the platformer. The PRD keeps every box open with the reason, adds
the measured cause (4096 key-light shadow map over 44 m already resolves the
foot gap), the pass-camera projection-inverse finding and a Blocked-on list.
@jonit-dev

Copy link
Copy Markdown
Contributor Author

Visual judge: two rounds, both REGRESSION (Phase 2 not shipped)

Pose: platformer start, fox standing. Headed, --browser-recipe webgpu, RTX 2080, two runs per arm. BEFORE is the painted blob. CONTROL is the blob removed with the stage off. AFTER is the blob removed with the stage on. A fresh read-only subagent judged each round. AFTER did not beat CONTROL, and both lost to BEFORE under the feet. Cause and numbers are under ## Decisions in the PRD.

Round 1 (final3), feet crop, run 1: BEFORE · CONTROL · AFTER
Round 2 (final8), feet crop, run 1: BEFORE · CONTROL · AFTER

Full frame, round 2, run 1: BEFORE · CONTROL · AFTER

All 48 frames (feet, castle, islands, full; two runs) are on the prd-561-screenshots branch at 16d6237ac92a3c25fd5a470f01eae4f3e07cc9c5.

BEFORE
CONTROL
AFTER

…he unused mechanism

One falsifier round with the key-light map cut to 1024 over 44 m (about
4.3 cm a texel) judged REGRESSION: the feet were pixel-identical between
CONTROL and AFTER and the stage added speckle on tower rims and silhouettes.
Nothing consumes contactShadow(), so the kernel, the dispatch builder, their
spec, the capability entries, the CHARTER paragraph and the third-party
notice go back to their pre-PRD state. The code stays recoverable at
9bd6b3c, c9773d9 and 4afd70c on this branch. The PRD moves to done/
as DECLINED with the evidence URLs and the untested far-field threshold.
Resolves the batch README table: PRD-567 and PRD-561 both link to done/.
@jonit-dev

Copy link
Copy Markdown
Contributor Author

Falsifier round: 1024 key-light map (about 4.3 cm a texel), REGRESSION, PRD declined

Same platformer pose, headed --browser-recipe webgpu, adapter nvidia/turing (RTX 2080), two runs per arm. Fresh read-only judge. AFTER against CONTROL and against BEFORE: REGRESSION. The feet are pixel-identical between CONTROL and AFTER. The stage adds dotted dark dashes under tower rims, a halo on the windmill silhouette and a stair-stepped dash pattern on the bridge edge.

Feet: BEFORE · CONTROL · AFTER
Castle: BEFORE · CONTROL · AFTER

AFTER castle

The PRD moved to done/ as DECLINED. The unused kernel and dispatch builder are deleted; they stay recoverable at 9bd6b3c25, c9773d985 and 4afd70ca2. Draft, not armed.

@jonit-dev jonit-dev changed the title feat(core): contact shadows from screen depth (PRD-561) docs(prds): decline PRD-561, contact shadows from screen depth do not pay on any template Oct 10, 2026
@jonit-dev
jonit-dev marked this pull request as ready for review October 10, 2026 16:10
@jonit-dev jonit-dev added prd:0% No verified implementation phase complete and removed prd:25% PRD phase 1 verified prd:0% No verified implementation phase complete labels Oct 10, 2026
@jonit-dev
jonit-dev added this pull request to the merge queue Oct 10, 2026
@jonit-dev
jonit-dev removed this pull request from the merge queue due to a manual request Oct 10, 2026
@jonit-dev
jonit-dev marked this pull request as draft October 10, 2026 17:16
@jonit-dev

jonit-dev commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor Author

UNJUDGED artifact-fix candidate — parked; the existing decline remains.

Candidate d93319599 stays on the separate experiment branch. It reduces trace reach to 8/6 pixels, contrast to 1, and gates application by measured sun-shadow texel size. It suppresses the effect on these poses and leaves a fox ear dash; useful grounding has not been demonstrated. No candidate code was integrated into this PR.

The author’s earlier “NEUTRAL by measurement” inference is retracted: pixel differences cannot provide a visual-judge verdict. These comparisons are UNJUDGED. The requested Claude Code visual-judge skill is unavailable; no substitute judge ran and no acceptance box changed.

CONTROL = 4afd70c with the stage disabled; AFTER = candidate enabled. BODY_REST=0.377 in both; map size differs only between the 1024/4096 sets. Each sheet has CONTROL left, AFTER right, run 1 above run 2. All eight run metadata files confirm NVIDIA/Turing WebGPU, headed Chromium, 1280×720. Labels are burned into the images.

Pose 1024 shadow map 4096 shadow map
full UNJUDGED full 1024 UNJUDGED full 4096
feet UNJUDGED feet 1024 UNJUDGED feet 4096
castle UNJUDGED castle 1024 UNJUDGED castle 4096
islands UNJUDGED islands 1024 UNJUDGED islands 4096

All original labeled frames and capture metadata.

Checks: 26 dispatch/template-look tests pass; scaffold byte-stable test and stage-file Biome pass. The existing contact-shadow scenario passes with the stage and fails its stage-presence assertion without it; that proves wiring, not grounding. Root typecheck/lint/full suite, native proof and GPU cost remain unrun. No merge, ready, archive or deployment.

The normal pre-push drift hook stopped because this historical experiment checkout lacks packages/physics/dist. The supported TN_SKIP_PREPUSH=1 route uploaded only the parked experiment and screenshot branches; no CI acceptance is claimed.

Quarantine: experiment source commit d933195997fe59a04440ed79748f1f7c22cc4bbd was also uploaded with that skipped hook. Its normal pnpm ci:fast gate remains unpassed; keep it isolated from this PR and integration. Screenshot commit: c9b3b0581c9d2306aeaa9b09b3d611b58cd91ff8.

@jonit-dev jonit-dev added the prd:25% PRD phase 1 verified label Oct 11, 2026
…ct-shadows

# Conflicts:
#	docs/PRDs/unreal-source-borrowing/README.md
@jonit-dev jonit-dev removed the prd:25% PRD phase 1 verified label Oct 12, 2026
@jonit-dev jonit-dev changed the title docs(prds): decline PRD-561, contact shadows from screen depth do not pay on any template docs(prds): PRD-561 decline contact shadows from screen depth Oct 12, 2026
@jonit-dev

Copy link
Copy Markdown
Contributor Author

Pushed f56dbf2: merges the current develop (b753d20). The four generated capability files now match develop. Next: review, then merge.

@jonit-dev
jonit-dev marked this pull request as ready for review October 12, 2026 05:10
@jonit-dev
jonit-dev merged commit 5c15811 into develop Oct 12, 2026
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant