mittens-engine 0.7.0

A Vulkan and OpenXR scene engine with ECS, reactive signals, and Meow Meow scripting
// pipe-demo.mms
// Demonstrates the |> forward pipe operator.
//
// Features exercised (all require implementation):
//   |>  forward pipe operator        -- parser + eval, no type system needed
//   print(value)                     -- evaluator builtin, EvalResponse::Print
//   assert(cond, msg)                -- evaluator builtin, fails loudly on bad output
//   name = "id" in CE body           -- component registry: sets ComponentNode name
//   component.set_rgba(r,g,b,a)      -- Phase 7 mutation method on ColorComponent
//   component.set_position(x,y,z)    -- Phase 7 mutation method on TransformComponent
//   query("selector", handler)       -- Phase 6+ HostCall

// ── pure function definitions ─────────────────────────────────────────────────

fn double(x) { return x * 2 }
fn halve(x)  { return x / 2 }
fn square(x) { return x * x }
fn negate(x) { return 0 - x }

fn clamp01(x) {
    if x < 0.0 { return 0.0 }
    if x > 1.0 { return 1.0 }
    return x
}

// rough perceptual gamma curve (pow ~2.2, approximated as square)
fn gamma_encode(x) { return x * x }
fn gamma_decode(x) { return x + (x - x * x) * 0.5 }  // crude sqrt approx

// ── section 1: basic pipe chain ───────────────────────────────────────────────
//
// expr |> f  desugars to  f(expr)
// a |> f |> g  is left-associative: g(f(a))

let a = 2.0 |> double           // 4.0
print("double(2) = " + a)
assert(a == 4.0, "double(2) should be 4.0")

let b = 3.0 |> double |> double  // 12.0
print("double(double(3)) = " + b)
assert(b == 12.0, "double chain should be 12.0")

let c = 0.3 |> double |> clamp01  // 0.6, already in range
print("clamp01(double(0.3)) = " + c)
assert(c == 0.6, "should be 0.6")

let d = 5.0 |> double |> clamp01  // 10.0 clamped to 1.0
print("clamp01(double(5)) = " + d)
assert(d == 1.0, "should clamp to 1.0")

// pipe into an inline function literal
let e = 7.0 |> fn(x) { return x * 3 }   // 21.0
print("inline fn pipe: 7 * 3 = " + e)
assert(e == 21.0, "inline fn should give 21.0")

// ── section 2: pipe to build a colour gradient scene ──────────────────────────
//
// Spawn 8 cubes in a row. Each cube's brightness is computed as:
//   fraction → gamma_encode → used as greyscale colour
// All via forward pipe — no intermediate let bindings needed.

BGC.rgba(0.08, 0.08, 0.10, 1.0)
AL.rgb(0.6, 0.6, 0.6)

for i in range(8) {
    let frac = i / 7           // 0.0 .. 1.0
    let bright = frac |> gamma_encode |> clamp01
    T.position(i - 3.5, 0, -3) {
        R.cube() { C.rgba(bright, bright, bright, 1.0) }
    }
}

print("gradient strip spawned (8 cubes)")

// second strip: same but inverted — shows negate working in a pipe chain
for i in range(8) {
    let frac   = i / 7
    let bright = frac |> gamma_encode |> negate |> fn(x) { return x + 1.0 } |> clamp01
    T.position(i - 3.5, -1.2, -3) {
        R.cube() { C.rgba(bright, bright, bright, 1.0) }
    }
}

print("inverted strip spawned")

// ── section 3: pipe chain ending at a component query HostCall ───────────────
//
// Build a "target" cube, then drive its colour with a value computed via pipe.
// The final step is a query dispatch via -> — a HostCall that crosses into the engine.
//
// -> is the query/dispatch operator: "selector" -> method(args)
// desugars to: query("selector", fn(r) { r.method(args) })

T.position(0, 1.5, -3) {
    name = "target_t"
    R.sphere() {
        name = "target_r"
        C.rgba(1.0, 0.0, 0.0, 1.0)  // starts red
    }
}

// compute a colour via pipe — purely functional, no engine involvement yet
let intensity = 0.4 |> double |> gamma_encode |> clamp01  // 0.64
let tint_r = intensity
let tint_g = intensity |> halve                            // 0.32
let tint_b = intensity |> double |> clamp01               // 1.0 (clamped)

print("computed tint: r=" + tint_r + " g=" + tint_g + " b=" + tint_b)

// now apply the computed colour to the spawned component
// this is the HostCall sink — crosses the script/engine boundary
"#target_r C" -> set_rgba(tint_r, tint_g, tint_b, 1.0)

print("target cube colour set via -> query dispatch  [HostCall]")

// equivalent explicit form:
// query("#target_r C", fn(c) {
//     if !c { return }
//     c.set_rgba(tint_r, tint_g, tint_b, 1.0)
// })

// ── section 4: pipe as composition — functions returning functions ─────────────
//
// Standard forward pipe: a |> f |> g  where f and g are named functions.
// f can itself return a function — the result is piped onward.
// Demonstrates that pipe works with any callable, not just simple transforms.

fn make_adder(n) {
    return fn(x) { return x + n }
}

let add5 = make_adder(5)
let result = 10.0 |> add5 |> double   // (10 + 5) * 2 = 30
print("(10 + 5) * 2 via pipe = " + result)
assert(result == 30.0, "should be 30.0")

print("pipe-demo: all assertions passed")