/-
  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.
