Some checks are pending
Lean Action CI / build (push) Waiting to run
Implements the cells-spec vision: a computation space that preserves auditability, correctness, interactivity. Phase 1 (Lean kernel + naga-IR Rust backend) is closed; foundation hypothesis stack (Selection H1+H2, Subobject H3, Trace H5, Obs.Ctx C2, Cubical.Trace) landed. Highlights: - Cubical-HoTT syntax + value/eval/readback in Lean - naga-IR pipeline (no GLSL string crosses FFI; 17/17 probes pass) - Honesty audit: every non-transport (sealed cells, vertex shader, Y-flip, presentation conventions) is documented as such - Polymorphic Trace α as free monoid; Cubical.Trace gives CTerm → Trace CTerm by structural fold (homomorphism = definition) - Selection as Huet zipper; Subobject as Boolean algebra over WCell - All theorems proven; the proof IS the implementation See STATUS.md for the resume guide. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
448 lines
21 KiB
Text
448 lines
21 KiB
Text
/-
|
||
Topolei.GPU.Spec
|
||
================
|
||
Axiomatic specification of the GPU layer.
|
||
|
||
Everything declared `axiom` here is a CLAIM about what the Rust/GPU
|
||
implementation must satisfy. The Lean math is proved correct *assuming*
|
||
these axioms. Before writing Rust, we verify the math is consistent with
|
||
the specs. When the Rust ships, we verify it against them.
|
||
|
||
Proof obligations:
|
||
- Lean side: proved below (no axioms needed)
|
||
- Rust/GPU side: declared as axioms (discharged by `native/canvas-rs/`)
|
||
|
||
## Honesty audit — non-transports the rendering pipeline depends on
|
||
|
||
Every continuous function reaching the GPU should ideally be a
|
||
cubical transport. In practice the pipeline relies on a finite
|
||
set of non-transports. This block enumerates them. They split
|
||
into three classes:
|
||
|
||
### A. Sealed cells (cells-spec §1.7)
|
||
GPU intrinsics whose interiors are sealed at the IEEE/spec level.
|
||
We consume their boundaries (the IEEE 754 contract for `exp`,
|
||
`log`, `cos`, `+`, `-`, `*`) and trust the hardware to satisfy
|
||
them. Discharge obligation = the IEEE 754 spec on the chip.
|
||
· `Float.exp`, `Float.log` (for `eml(l, r) = exp(l) - log(r)`)
|
||
· IEEE 754 `+`, `-`, `*` on `Float`
|
||
· Rasterizer interpolation of `uv` (perspective barycentric
|
||
across the fullscreen triangle)
|
||
· WGSL vertex shader's coordinate algebra
|
||
(`pos = vec2(select(-1, 3, …)…); uv = pos * 0.5 + 0.5`)
|
||
|
||
### B. Visualization adapter (frozen by `compileEMLPath_correct`)
|
||
The cosine cycle in `EMLExpr.toColor` (`r,g,b = 0.5 +
|
||
0.5·cos(2π·v + φ)`). NOT a cubical transport — see the docstring
|
||
on `EMLExpr.toColor` below. Removing it requires re-axiomatizing
|
||
the spec or modeling color spaces as `CType` cells.
|
||
|
||
### C. Presentation conventions
|
||
Window-side choices the renderer makes that aren't part of any
|
||
semantic claim:
|
||
· Window dimensions (caller's choice)
|
||
· Aspect ratio (uv²ⁿ → window's W×H rectangle; bodies that use
|
||
`py` will be visibly stretched if W ≠ H)
|
||
· Y-flip in `pixelUV` (`uv.y = 1 - (y+0.5)/h`) to match Vulkan's
|
||
rasterizer Y-flip
|
||
· Floating-point tolerance `5e-3` in the probe (CPU vs GPU
|
||
IEEE 754 disagreement)
|
||
· The two-panel viewport split
|
||
|
||
## What is NOT in this list (by design)
|
||
|
||
Things that *are* transports in our calculus:
|
||
· `EMLExpr.eml(l, r) = exp(l) - log(r)` — generates elementary
|
||
functions per the EML primitive (Odrzywolek 2026)
|
||
· The `EMLPath` body's value as `pathParam` varies — this is
|
||
genuinely the transport we're rendering
|
||
-/
|
||
|
||
import Topolei.EML
|
||
import Topolei.EML.Path
|
||
|
||
-- ── Opaque GPU types ──────────────────────────────────────────────────────────
|
||
-- C++ objects; Lean sees them only through their axioms.
|
||
|
||
axiom ShaderHandle : Type
|
||
axiom GPUContext : Type
|
||
|
||
-- ── Semantic domain (pure Lean) ───────────────────────────────────────────────
|
||
|
||
structure PixelCoord where
|
||
x : Float
|
||
y : Float
|
||
|
||
structure FrameUniforms where
|
||
time : Float
|
||
resWidth : Float
|
||
resHeight : Float
|
||
/-- The path parameter — a fixed value chosen per render. The shader
|
||
reads this uniform and binds its cubical-path dim variable to it;
|
||
see `shaderVarWithDim` below. Earlier the canvas-rs runtime
|
||
animated this from `u.time` via a sine sweep; that was removed
|
||
because a host-side `Float → Float` is not a transport. Default
|
||
`0.0` keeps existing construction sites (record literals and
|
||
`sorry`/`default`-derived values) well-typed. -/
|
||
pathParam : Float := 0.0
|
||
|
||
structure PixelColor where
|
||
r : Float
|
||
g : Float
|
||
b : Float
|
||
a : Float
|
||
|
||
/-- The semantic type of a shader: pure function from pixel + frame state to color. -/
|
||
def ShaderSemantic := PixelCoord → FrameUniforms → PixelColor
|
||
|
||
-- ── EML reference evaluator (pure Lean) ──────────────────────────────────────
|
||
-- Defines what the GPU *should* compute. The axioms below say it does.
|
||
|
||
/-- Look up the value of a free variable name in shader context. -/
|
||
def shaderVar (name : String) (p : PixelCoord) (u : FrameUniforms) : Float :=
|
||
match name with
|
||
| "px" => p.x
|
||
| "py" => p.y
|
||
| "u_time" => u.time
|
||
| "u_resWidth" => u.resWidth
|
||
| "u_resHeight" => u.resHeight
|
||
| _ => 0.0
|
||
|
||
/-- Dim-aware variable resolver. Routes the path's distinguished
|
||
`dimName` to `u.pathParam`; every other name passes through to
|
||
`shaderVar`. This is the semantic-side analogue of the GPU shader
|
||
referencing the dim variable via `u_pathParam` rather than a
|
||
GLSL-local computation.
|
||
|
||
Named so the rewrite `shaderVarWithDim d d p u = u.pathParam` is
|
||
`rfl` on the dim-hit case; the miss case delegates cleanly. -/
|
||
def shaderVarWithDim (dimName : String) (name : String)
|
||
(p : PixelCoord) (u : FrameUniforms) : Float :=
|
||
if name = dimName then u.pathParam
|
||
else shaderVar name p u
|
||
|
||
@[simp] theorem shaderVarWithDim_dim
|
||
(dimName : String) (p : PixelCoord) (u : FrameUniforms) :
|
||
shaderVarWithDim dimName dimName p u = u.pathParam := by
|
||
simp [shaderVarWithDim]
|
||
|
||
@[simp] theorem shaderVarWithDim_other
|
||
(dimName name : String) (p : PixelCoord) (u : FrameUniforms)
|
||
(h : name ≠ dimName) :
|
||
shaderVarWithDim dimName name p u = shaderVar name p u := by
|
||
simp [shaderVarWithDim, h]
|
||
|
||
/-- Evaluate an EML expression to a Float at a given pixel and frame state.
|
||
This is the reference semantics — the ground truth for what the shader computes. -/
|
||
def EMLExpr.evalAt (p : PixelCoord) (u : FrameUniforms) : EMLExpr → Float
|
||
| .one => 1.0
|
||
| .var name => shaderVar name p u
|
||
| .eml l r =>
|
||
let lv := l.evalAt p u
|
||
let rv := r.evalAt p u
|
||
-- exp(l) − ln(r), clamping rv > 0 for real-valued rendering
|
||
Float.exp lv - Float.log (max rv 1e-9)
|
||
|
||
/-- Direct greyscale projection: write the EML value into all three
|
||
color channels.
|
||
|
||
This is the minimal-honest `Float → Color` mapping — it's the
|
||
identity injection of the Float-valued transport's image into
|
||
the framebuffer's three channels. No `cos`, no normalization,
|
||
no period-wrapping. Negative `v` displays as black after the
|
||
sealed-cell sRGB clamp at the display; `v > 1` displays as
|
||
white.
|
||
|
||
A previous version applied a cosine cycle
|
||
(`r = 0.5 + 0.5·cos(2π·v + φ)` etc.) here to make any value
|
||
visible, but the cycle has period 1 in `v` and so *aliased*
|
||
fibers that differ by 1 (e.g. `plotTransp.at0` vs
|
||
`plotTransp.at1` differ by exactly 1) into pixel-identical
|
||
output. That made it visually impossible to distinguish two
|
||
genuinely-distinct transports. The cycle is removed.
|
||
|
||
Greyscale is still not a "transport" in the cubical sense —
|
||
the framebuffer-clamp on overflow / underflow is a sealed-cell
|
||
behavior at the display, not a derived cell. Building a real
|
||
`Float ↔ Color` transport (cells-spec §8: color spaces as
|
||
`CType`, conversions as cells via `ua` of an equivalence) is
|
||
on the roadmap. -/
|
||
def EMLExpr.toColor (p : PixelCoord) (u : FrameUniforms) (expr : EMLExpr) : PixelColor :=
|
||
let v := expr.evalAt p u
|
||
{ r := v, g := v, b := v, a := 1.0 }
|
||
|
||
-- ── GPU axioms ────────────────────────────────────────────────────────────────
|
||
|
||
/-- Axiom G1: Every ShaderHandle carries a semantic — the function it computes. -/
|
||
axiom ShaderHandle.semantic : ShaderHandle → ShaderSemantic
|
||
|
||
/-- Axiom G2: An abstract EML-to-shader compiler. The C++ side is obliged to
|
||
implement something that matches this spec. -/
|
||
axiom compileEML : EMLExpr → ShaderHandle
|
||
|
||
/-- Axiom G3: `compileEML` is correct — the compiled shader's semantic
|
||
function agrees with the Lean reference evaluator `toColor`.
|
||
|
||
Scoped to the handle produced by `compileEML expr`; this avoids the
|
||
earlier unsound formulation `∀ expr h, h.semantic = expr.toColor`, which
|
||
forced `expr₁.toColor = expr₂.toColor` for any pair of exprs via a
|
||
shared `h`. -/
|
||
axiom compileEML_correct (expr : EMLExpr) :
|
||
(compileEML expr).semantic = expr.toColor
|
||
|
||
/-- Axiom G4: The render loop is faithful. For a compiled shader `h`
|
||
running under uniforms `u`, the pixel written at screen coord `p`
|
||
equals `h.semantic p u` (within IEEE 754 float tolerance — bit-
|
||
exact only on driver+arch combinations that expose it).
|
||
|
||
The axiom body is `True` because Lean cannot evaluate an IO action
|
||
in a pure proof. The **empirical discharge** happens in
|
||
`Topolei.Render.Probe` via `renderProbePixel` — a Rust FFI that
|
||
renders a shader offscreen into an `Rgba32Float` texture and reads
|
||
one pixel back. See `ProbeTest.lean` + the `probe-test` lake exe. -/
|
||
axiom render_faithful (ctx : GPUContext) (h : ShaderHandle) (u : FrameUniforms) :
|
||
True -- empirical discharge via Topolei.Render.Probe + probe-test exe
|
||
|
||
-- ── Bridge theorems (proved in Lean, assuming the axioms) ─────────────────────
|
||
|
||
/-- The compiled-shader semantic agrees with the Lean reference evaluator at
|
||
every pixel and frame, for the handle `compileEML` produced. -/
|
||
theorem compileEML_semantic_eq_toColor
|
||
(expr : EMLExpr) (p : PixelCoord) (u : FrameUniforms) :
|
||
(compileEML expr).semantic p u = expr.toColor p u := by
|
||
rw [compileEML_correct]
|
||
|
||
/-- Legacy hypothesis-carrying form: given a per-handle correctness witness,
|
||
`semantic = toColor` pointwise. Useful for handles produced by code
|
||
paths other than `compileEML` (e.g. externally provided shaders). -/
|
||
theorem compiled_semantic_eq_eval
|
||
(expr : EMLExpr) (h : ShaderHandle)
|
||
(hc : h.semantic = expr.toColor)
|
||
(p : PixelCoord) (u : FrameUniforms) :
|
||
h.semantic p u = expr.toColor p u := by
|
||
rw [hc]
|
||
|
||
-- ── Evaluator correctness (pure Lean proofs) ──────────────────────────────────
|
||
|
||
-- ── IEEE 754 Float-arithmetic obligations ────────────────────────────────────
|
||
-- Lean's `Float` is axiomatic; it has no DecidableEq matching propositional
|
||
-- equality, so `native_decide` cannot discharge even simple numeric identities.
|
||
-- The three axioms below are required for `evalAt_expOf` and are uncontroversial
|
||
-- IEEE 754 facts. They become verification obligations on the C++ runtime.
|
||
|
||
/-- `log 1 = 0` in IEEE 754 double precision. -/
|
||
axiom Float.log_one : Float.log 1.0 = 0.0
|
||
|
||
/-- `max 1.0 1e-9 = 1.0` (1.0 > 1e-9 in IEEE 754). -/
|
||
axiom Float.max_one_ge_eps : max (1.0 : Float) 1e-9 = 1.0
|
||
|
||
/-- IEEE 754 subtraction by zero is identity (for non-NaN operands;
|
||
the `evalAt` call site always feeds a finite `Float.exp` result,
|
||
which is non-NaN except at `Float.exp +∞`). -/
|
||
axiom Float.sub_zero (a : Float) : a - 0.0 = a
|
||
|
||
-- The earlier `PathDriver` / `sineSweep` / `canonicalDriver` /
|
||
-- `Float.sin_range` / `sineSweep_range01_of` block was removed
|
||
-- because no part of it was a transport in the cells-spec sense:
|
||
-- it described a host-side `Float → Float` function used to
|
||
-- animate `u.pathParam` from `u.time`, which is not derived from
|
||
-- any cubical path or type family. The `canvas-rs` runtime no
|
||
-- longer animates `pathParam`; each window renders one fiber of
|
||
-- the 1-cell at a fixed `pathParam` chosen by the caller.
|
||
--
|
||
-- Animated rendering — a continuous deformation of fibers over
|
||
-- time — is properly a 2-cell (a homotopy of 1-cells parameterised
|
||
-- by a second interval). When 2-cell infrastructure lands, the
|
||
-- spec for it goes here, derived from the cubical calculus, not as
|
||
-- a free-standing `Float → Float`.
|
||
|
||
/-- exp(x) = eml(x, 1): evaluates to Float.exp p.x.
|
||
|
||
Proof chain:
|
||
evalAt p u (eml (var "px") one)
|
||
= Float.exp p.x − Float.log (max 1.0 1e-9) [unfold evalAt, shaderVar]
|
||
= Float.exp p.x − Float.log 1.0 [Float.max_one_ge_eps]
|
||
= Float.exp p.x − 0.0 [Float.log_one]
|
||
= Float.exp p.x [Float.sub_zero]. -/
|
||
theorem evalAt_expOf (p : PixelCoord) (u : FrameUniforms) :
|
||
EMLExpr.evalAt p u (EMLExpr.expOf (.var "px")) = Float.exp p.x := by
|
||
simp only [EMLExpr.expOf, EMLExpr.evalAt, shaderVar,
|
||
Float.max_one_ge_eps, Float.log_one, Float.sub_zero]
|
||
|
||
/-- The depth-1 EML tree is the exponential — H1 first concrete instance. -/
|
||
theorem h1_exp_instance (p : PixelCoord) (u : FrameUniforms)
|
||
(h : ShaderHandle)
|
||
(hc : h.semantic = fun p u => EMLExpr.toColor p u (EMLExpr.expOf (.var "px"))) :
|
||
h.semantic p u = EMLExpr.toColor p u (EMLExpr.expOf (.var "px")) := by
|
||
rw [hc]
|
||
|
||
-- ── EMLPath–GPU bridge ────────────────────────────────────────────────────────
|
||
/-
|
||
An EMLPath with dimName = "t" represents a shader that varies along a
|
||
dimension variable t ∈ {0, 1}. The GPU evaluates it at the 1-end (t = 1.0).
|
||
The baseEnv here is (shaderVar · p u): the standard pixel+frame variable map.
|
||
|
||
This is the link between:
|
||
· EMLPath.at1 (cubical/EML: what the 1-end value should be)
|
||
· EMLExpr.evalAt (GPU/Spec: what the reference evaluator computes)
|
||
|
||
The condition "dimName variable in baseEnv" is handled by the override in
|
||
atBool; the rest of the variables use shaderVar as usual.
|
||
-/
|
||
|
||
/-- evalAt agrees with evalWithEnv when using shaderVar as the resolver. -/
|
||
theorem EMLExpr.evalAt_eq_evalWithEnv (p : PixelCoord) (u : FrameUniforms)
|
||
(e : EMLExpr) :
|
||
e.evalAt p u = e.evalWithEnv (fun name => shaderVar name p u) := by
|
||
induction e with
|
||
| one => rfl
|
||
| var name => simp [evalAt, evalWithEnv, shaderVar]
|
||
| eml l r ihl ihr =>
|
||
simp only [evalAt, evalWithEnv, ihl, ihr]
|
||
|
||
/-- An EMLPath evaluated at the 1-end (b = true) agrees with the standard
|
||
evaluator when the dimension variable is overridden to 1.0 in the env. -/
|
||
theorem EMLPath.at1_eq_evalAt_override
|
||
(path : EMLPath) (p : PixelCoord) (u : FrameUniforms) :
|
||
path.at1 (fun name => shaderVar name p u) =
|
||
path.body.evalWithEnv (fun name =>
|
||
if name = path.dimName then 1.0
|
||
else shaderVar name p u) :=
|
||
rfl
|
||
|
||
/-- If the dimension variable is not `px` and not in shaderVar's domain,
|
||
and the body of the path does not use the dim variable,
|
||
then EMLPath.at1 = EMLExpr.evalAt for the body. -/
|
||
theorem EMLPath.at1_of_absent_eq_evalAt
|
||
(path : EMLPath) (p : PixelCoord) (u : FrameUniforms)
|
||
(habs : path.body.varAbsent path.dimName = true) :
|
||
path.at1 (fun name => shaderVar name p u) =
|
||
path.body.evalAt p u := by
|
||
rw [EMLExpr.evalAt_eq_evalWithEnv]
|
||
apply EMLExpr.evalWithEnv_congr _ _ habs
|
||
intro n hn
|
||
simp [if_neg hn]
|
||
|
||
-- ── Rendering bridge: dim uniform overridden to 1.0 ⇒ shader = EMLPath.at1 ──
|
||
|
||
/-- Scalar bridge: when `shaderVar` already resolves the path's dim variable
|
||
to 1.0, the GPU evaluator on the path body equals `EMLPath.at1` computed
|
||
against the standard `shaderVar`-based env. Proof: rewrite `evalAt` via
|
||
`evalAt_eq_evalWithEnv`, then use `evalWithEnv_congr` on the two envs
|
||
which agree everywhere (the override matches the baseline at dimName). -/
|
||
theorem EMLPath.evalAt_body_eq_at1
|
||
(path : EMLPath) (p : PixelCoord) (u : FrameUniforms)
|
||
(hu : shaderVar path.dimName p u = 1.0) :
|
||
path.body.evalAt p u =
|
||
path.at1 (fun name => shaderVar name p u) := by
|
||
rw [EMLExpr.evalAt_eq_evalWithEnv, EMLPath.at1_eq_evalAt_override]
|
||
-- Both sides are evalWithEnv against envs that agree pointwise:
|
||
-- env1 n = shaderVar n p u
|
||
-- env2 n = if n = dimName then 1.0 else shaderVar n p u
|
||
-- At n = dimName: env1 = 1.0 (by hu), env2 = 1.0. Else: equal by construction.
|
||
congr 1
|
||
funext n
|
||
by_cases h : n = path.dimName
|
||
· subst h; simp [hu]
|
||
· simp [if_neg h]
|
||
|
||
/-- Color bridge: under the same dim-uniform assumption, `toColor` of the
|
||
path body equals `toColor` of the `EMLPath.at1` value. This is the
|
||
formal "rendering at t=1 equals EMLPath.at1" statement promised in
|
||
`EML/Path.lean`. -/
|
||
theorem EMLPath.toColor_body_eq_at1_toColor
|
||
(path : EMLPath) (p : PixelCoord) (u : FrameUniforms)
|
||
(hu : shaderVar path.dimName p u = 1.0) :
|
||
EMLExpr.toColor p u path.body =
|
||
let v := path.at1 (fun name => shaderVar name p u)
|
||
({ r := v, g := v, b := v, a := 1.0 } : PixelColor) := by
|
||
simp only [EMLExpr.toColor, EMLPath.evalAt_body_eq_at1 path p u hu]
|
||
|
||
/-- End-to-end rendering bridge: the compiled shader for `path.body`,
|
||
under a frame whose `shaderVar` resolves the path's dim variable to 1.0,
|
||
produces the `toColor` of `EMLPath.at1`.
|
||
|
||
This closes the cubical-to-pixel loop:
|
||
DimLine.at1 ↔ EMLPath.at1 ↔ compileEML(body) @ {dim uniform = 1.0}.
|
||
|
||
**Note (P3+P5 pass).** The hypothesis `shaderVar path.dimName p u = 1.0`
|
||
is *uninhabited* for path dims that aren't in `shaderVar`'s match
|
||
(e.g. "t") — `shaderVar` returns `0.0` as fallback, never `1.0`.
|
||
The theorem therefore says nothing about parametric shaders that run
|
||
in the `canvas-rs` runtime. The inhabited successor is
|
||
`render_eq_at_pathParam` below, built on `compileEMLPath` +
|
||
`shaderVarWithDim`. -/
|
||
theorem render_eq_at1
|
||
(path : EMLPath) (p : PixelCoord) (u : FrameUniforms)
|
||
(hu : shaderVar path.dimName p u = 1.0) :
|
||
(compileEML path.body).semantic p u =
|
||
let v := path.at1 (fun name => shaderVar name p u)
|
||
({ r := v, g := v, b := v, a := 1.0 } : PixelColor) := by
|
||
rw [compileEML_correct]
|
||
exact EMLPath.toColor_body_eq_at1_toColor path p u hu
|
||
|
||
-- ── Dim-aware shader semantic: inhabited rendering theorems (P3+P5) ─────────
|
||
/-
|
||
The semantic of a compiled *path* shader. Uses `shaderVarWithDim` so
|
||
the path's distinguished `dimName` is resolved via `u.pathParam` —
|
||
exactly how the `canvas-rs` runtime binds the uniform after its host-
|
||
side `PathDriver` computes the parameter from `u.time`.
|
||
-/
|
||
|
||
/-- Rendering color of an `EMLPath` at a pixel + frame. Uses
|
||
`shaderVarWithDim` so the path's dim name resolves to `u.pathParam`.
|
||
|
||
Greyscale projection: the EML body's Float value goes into all
|
||
three channels. See `EMLExpr.toColor` for the rationale (the
|
||
cosine cycle was removed because it aliased fibers differing by
|
||
integer multiples of 1 into pixel-identical output). -/
|
||
def EMLPath.toColor (path : EMLPath) (p : PixelCoord) (u : FrameUniforms) : PixelColor :=
|
||
let v := path.body.evalWithEnv (fun n => shaderVarWithDim path.dimName n p u)
|
||
{ r := v, g := v, b := v, a := 1.0 }
|
||
|
||
/-- **Axiom G2′**: compile an `EMLPath` to a shader handle. The path's
|
||
`dimName` determines which scalar uniform the shader references for
|
||
the path parameter; the caller binds that uniform to a fixed
|
||
`pathParam` value (no host-side animation curve — see the audit
|
||
block at the top of this file). -/
|
||
axiom compileEMLPath : EMLPath → ShaderHandle
|
||
|
||
/-- **Axiom G3′**: `compileEMLPath` is correct — the compiled shader's
|
||
semantic agrees with `EMLPath.toColor`. Like `compileEML_correct`
|
||
but dim-aware: the shader's semantic function uses `u.pathParam` in
|
||
place of `0.0` at `path.dimName`. -/
|
||
axiom compileEMLPath_correct (path : EMLPath) :
|
||
(compileEMLPath path).semantic = EMLPath.toColor path
|
||
|
||
/-- The compiled-path semantic equals `EMLPath.toColor path` pointwise —
|
||
the inhabited successor of `compileEML_semantic_eq_toColor` for
|
||
parametric shaders. -/
|
||
theorem compileEMLPath_semantic_eq_toColor
|
||
(path : EMLPath) (p : PixelCoord) (u : FrameUniforms) :
|
||
(compileEMLPath path).semantic p u = EMLPath.toColor path p u := by
|
||
rw [compileEMLPath_correct]
|
||
|
||
/-- **Inhabited rendering theorem.** The compiled path shader at
|
||
`(p, u)` equals the path body evaluated with the dim name resolved
|
||
to `u.pathParam` via `shaderVarWithDim`. No uninhabited hypotheses.
|
||
|
||
For a fixed-point instance: when `u.pathParam = 1.0`, the RHS
|
||
agrees with the earlier `render_eq_at1` form (modulo
|
||
`evalWithEnv_congr` between `shaderVarWithDim` and the ad-hoc env
|
||
used by `EMLPath.at1`). For a sweep: `u.pathParam` varies
|
||
continuously and the RHS traces the path's image. -/
|
||
theorem render_eq_at_pathParam
|
||
(path : EMLPath) (p : PixelCoord) (u : FrameUniforms) :
|
||
(compileEMLPath path).semantic p u = EMLPath.toColor path p u :=
|
||
compileEMLPath_semantic_eq_toColor path p u
|
||
|
||
-- The earlier `shaderVarWithDim_eq_driverEnv`, `EMLPath.toColor_of_driver`,
|
||
-- and `render_eq_at_driver` theorems were removed alongside the
|
||
-- `PathDriver` scaffolding above: they all said "if the host computes
|
||
-- pathParam by some `Float → Float` function, the rendering equals
|
||
-- the path body evaluated at that function's output". That's a
|
||
-- trivial restatement of `compileEMLPath_correct` once the function
|
||
-- is fixed — and the fixed function was a sine sweep, which is not a
|
||
-- transport. Use `render_eq_at_pathParam` directly (see above) for
|
||
-- the abstract form, and `EMLPath.toColor_body_eq_at1_toColor` for
|
||
-- the `pathParam = 1` reduction.
|