Polydera

THE LANGUAGE

Luna language.

A dynamic language that's static.

Luna feels dynamic because the compiler is the interpreter: it lives inside the runtime, compiling and checking each definition the moment it is written. You get the freedom of a scripting language — define, call, and reshape while the application runs — and nothing unchecked ever executes.

Compile onceExact typesDeterministic lifetimes
01 / WRITE IT. CHECK IT. KEEP IT.

Interactive source. Retained bytecode.

Write source in the running application and it becomes retained bytecode the moment it is defined: the whole body is checked once — types, calls, effects, ownership — then lowered into the instructions the executor will run. Every later call is checked against the retained signature and links that same artifact. Nothing is parsed or inferred again.

Interactive goes deeper than typing. The compiler is present at run time, so programs can compute with it: read a signature, build a type, declare a checked function. More on that below, where code works on its own structure.

01 / 05 · INTERACTIVE SOURCE, RETAINED BYTECODERun on the Luna client
fn worst_kept(
    // one quality per face, where 1.0 is equilateral
    qualities: list<float>,
    // slivers below the floor are ignored
    floor: float = 0.05) -> float {
  let kept = luna.sort(value: luna.filter(domain: qualities,
                                          keep: fn (q: float) => q >= floor))
  return if luna.at(value: kept, index: 0) is float worst { worst } else { 1.0 }
}
> fn.worst_kept(qualities: [0.9, 0.01, 0.4])
0.4
> luna.signature(of: fn.worst_kept)
fn.worst_kept(qualities: list<float>, floor: float = 0.05) -> float

Checked once and retained as bytecode; every later call links it. The omitted floor is supplied by compilation, and luna.at answers float | missing, so all-sliver input answers 1.0 instead of faulting.

Five sessions, each run as shown: the definitions are one cell, every > line a later cell, and the lines beneath it what that cell printed. Arguments are named at the call, and parameter comments remain discoverable documentation.

02 / SELF-DESCRIBING

The language explains itself.

An agent's first act is asking. luna.help() answers with the installed language: every construct, tool, and type, each carrying its signature, its one-line documentation, and its tags. Built-in controls like luna.fold present exactly as any function you write would: a listed set of overloads, one per kind of domain, every kind declared in the face. Nothing is read from a manual — the catalog is rendered from the same contracts the compiler checks, so what you discover is never stale. A fresh agent with no prior knowledge can be dropped into the console and learn the language in minutes — it asks, and the application answers.

It goes to any depth: luna.signature(of:) renders any callable honestly — liveness included — and luna.parameters(of:) answers a function's parameters with their documentation and defaults, as data a program can compute with. luna.catalog(search:) finds a row by name, signature, prose, or tag; luna.doc(of:) answers its full document with examples; luna.source(of:) returns the source a function was written as. Twelve topics group the vocabulary; the language is its own reference.

01 / 06 · OPEN A SESSIONLive client output
$ luna
Luna 0.1.0 — luna.help() to get started
mounted: luna.cli, luna.pose, luna.session — :catalog lists them, :help the session
imports: ~/work/project, then the shipped modules

The banner says what is mounted and where an import will resolve from: the directory the terminal opened in, then the modules Luna ships.

Six questions, asked in a running session and answered from the installed language. Built-in controls list as any function you write would: luna.fold is an overload set with its kinds declared (T1: type, Ws: ..window), one face per domain family — list, range over int, index, array — and a second set of four for its parallel merge: form. The catalog is executable truth — the same test suite that gates the compiler pins this text.

03 / MODULES

A module is one file. Ship it as source or as an image.

A module is one source file that declares module <id>; its imports declare its dependencies. The dependency closure is checked before anything compiles, so independent modules compile in parallel and a cycle is refused by construction.

Compile once holds at this scale too. A module's functions are compiled to retained artifacts when it loads, and every later call links the retained artifact: nothing recompiles at use.

Loading, not compiling.

luna --compile-module writes the compiled image of a module. A session that finds <id>.lmdi loads it without compiling, so startup is loading. Source .luna and image .lmdi sit side by side; where both exist, the source wins.

A session resolves modules from where its source stands: the directory the terminal opened in, or a script's own directory. An explicit --modules list replaces that. Every module Luna ships remains readable source.

Private modules.

--private omits the source from the image, so you ship and load modules whose implementation stays yours. The language keeps its whole self-description — signatures, documentation, and catalog rows — and only the source text is withheld: luna.source answers missing. Protecting your implementation is a flag, not an architecture.

A module filevault.luna
module vault
import units

// A full turn is 360 degrees; this answers the fraction of one.
fn turns(of: units.degrees) -> float {
  return of.value / 360.0
}
Compile it, then load itClient session
# compile once, ahead of time; --private leaves the source text out
luna --compile-module vault vault.luna -o vault.lmdi --private

# a session that resolves vault.lmdi loads it without compiling
> import vault
> vault.turns(of: units.degrees(value: 90.0))
0.25
> luna.source(of: vault.turns)
missing
> luna.doc(of: vault.turns)
vault.turns(of: units.degrees) -> float [library, cost 2]
A full turn is 360 degrees; this answers the fraction of one.

An image holds one module. Its dependencies are not bundled: compile them as images too, or keep them where the loading session resolves imports. A private image withholds only the source text; names and documentation remain part of the language's self-description.

04 / THE PROOFS

How the compiler reasons about correctness.

EXACT TYPESA literal's spelling is its type

There is no promotion ladder and no coercion at a call site. Implicit conversion only forgets type facts or adds a union tag; a conversion that changes a value is an explicit construction the author writes.

TWO FAILURE TIERSA fault is not a refusal

A fault ends the invocation with its exact source position: an invalid index, an integer overflow. A refusal is an ordinary union value, such as int | error, handled with is. Nothing silently converts between the tiers.

COMPUTATION ENDSFinite computation is proven, not hoped

Recursion is refused by construction, including through callbacks. Iteration uses bounded sequence controls, so the compiler proves every Luna computation finite. Only work that leaves Luna — a native call, an outside future — depends on the outside.

MEMORY HAS AN ENDINGDeterministic release, without a garbage collector

An immutable value can share storage safely; when the final owner ends, the storage is released immediately, without a tracing garbage collector. Cyclic ownership is impossible, so nothing needs a cycle collector. Success and fault paths both release their obligations. A capture, retained snapshot, or history entry can deliberately keep storage alive; release follows the final owner.

CONCURRENCY IS CHECKEDparallel: true changes time, nothing else

An ordinary Boolean argument: same meaning, identical values, only time changes. The compiler follows the callback's entire call graph and refuses a world write on workers — "a worker performs no world step — no effect and no wait — and enters only natives their own host declared reentrant". The artifact's cost estimate is advice for scheduling that changes no value anywhere.

ONLY VERIFIED PROGRAMS RUNOnly compiler-issued artifacts execute

Every program that runs was issued by the compiler and passed the verifier. A structural lookalike is data, not executable authority.

A VALUE'S STORAGE LIFETIME
Two holders, one buffer.

A local list and a retained snapshot refer to the same immutable storage.

What each line establishesLanguage vocabulary
// the spelling is the type: this literal is list<float>[3]
let weights = [1.5, 2.0, 2.5]

// a map keeps its domain's count: list<float>[3] again
let scaled = luna.map(step: fn (w: float) => w * 2.0, domain: weights)

// a read that can miss answers float | missing; is decides the union
let floor = if luna.at(value: weights, index: 0) is float w { w } else { 0.0 }

// the domain is bounded, so the compiler proves this fold ends
let total = luna.fold(step: fn (a: float, w: float) => a + w,
                      domain: scaled, seed: floor)

// parallel is admission-checked; the values are identical either way
luna.map(step: fn (w: float) => w / total, domain: scaled, parallel: true)

The reasoning reaches everything.

The same compile-time judgment that proves the big properties also settles the language's everyday questions.

ONE NAME, MANY SIGNATURES

A name can answer a set of signatures, and every call picks exactly one while compiling: by the argument names it spells, then by their solved types. Execution performs no overload search; an ambiguous call refuses, listing what exists. The language's own controls follow the same law: map, filter and fold are ordinary overload sets, no different from a function you write.

FUNCTIONS AND METAFUNCTIONS

The language has two callable citizens. A metafunction is an instruction for making a function — compiled once per exact shape, on first use — and it overloads under the same law: names first, then the kinds of what the call hands over. T: type and T: field are simply two different metafunctions. There are five comptime kinds — type, integer, symbol, boolean and window — and a pack such as Ws: ..window is a product of them.

IDENTITY WITHOUT COST

type gives a value a distinct identity with no allocation and no tag; alias names a transparent one. A color is not an int, and the compiler holds that line for free.

TWO ABSENCES

none is absence held as a value; missing is the absence of an answer. A list of optional values can distinguish an empty element from nothing there — most languages cannot say the difference.

ENUMS ARE UNIONS

enum demo.side = left | right declares a union of named units; the member test is the same is every union uses. No second enum machinery exists.

SIGNATURES NEVER LIE

Everything a body receives is in its signature: defaults, unions, even liveness — watch.attach(..live to: Ts, …) says its seats are observed live entries. If the signature does not say it, it does not happen.

Concepts name what a kind may be.

concept scalar = int | float | bool declares a set of types, read only while compiling. T: scalar is inferred exactly as T: type is, then refused where the instantiation falls outside the set, in the concept's own sentence.

Two family forms build on a concept: ..orderable is any positional product of its members, ..|index_type any union of them, and either at width one is the member itself — so one sort key admits x and (x, y) alike. The language ships five: luna.scalar, what an array holds; luna.orderable_type and luna.orderable, the keys a grouping reads and a sort orders by; luna.index_type and luna.index_key, what an index is keyed by.

Declare a concept, constrain a kindRun on the Luna client
// a concept names the types a kind may be, read only while compiling
concept demo.scalar = int | float | bool

// ..C is any positional product of members; ..|C any union of them
concept demo.key = ..demo.scalar
concept demo.tag = ..|demo.scalar

fn first(K: demo.key, keys: list<K>) -> K { return keys[0] }
---
fn.first(keys: [3, 1])                   // 3: width one is the member
---
fn.first(keys: [(3, true), (1, false)])  // (3, true)
---
fn.first(keys: [{at: 1}])                // refused: outside demo.key

05 / PROGRAMMING AT TWO TIMES

Code can work on its own structure.

Types and fields are values the compiler can compute with. At compile time, a product and its list of fields are two spellings of one structure. Those fields can declare a function's parameters, carrying their names, types, documentation and defaults.

Ordinary luna.map, luna.filter and luna.fold can transform that structure. Feed the answer into a declaration and the compiler checks the resulting function exactly as it checks one written by hand. The same source language does both jobs, with no separate macro syntax.

READTypes and fields

The signature is data.

COMPUTEMap · filter · fold

Ordinary Luna transforms it.

DECLAREA checked function

The answer shapes its parameters.

Reuse a function's parameter fieldsSyntax guide
fn area(
    // across
    width: int,
    // down
    height: int = 1) -> int {
  return width * height
}
---
fn scaled(factor: int, ..size: luna.parameters(of: fn.area)) -> int {
  return fn.area(..size) * factor
}
fn.scaled(factor: 2, width: 3, height: 4)

A signature you can reuse.

  1. Read the fields. luna.parameters returns area's width and height, including their docs and the height default.
  2. Declare the parameters. ..size introduces those fields into scaled. Inside its body, size holds their values as a product.
  3. Make the checked call. The argument spread passes those values to area. The final call answers 24; omitting height would use the inherited default.

At compile time, field lists describe structure. At runtime, a product holds its own exactly typed fields. No list of boxed values carries the call.

Call a function from partial answersExecutable test corpus
fn resize(width: int, label: str = "north", scale: int = 2) -> str {
  return luna.concat(a: label, b: str(value: width * scale))
}
---
fn paired(V: type, values: V) -> str {
  let wanted = luna.sort(value: luna.parameters(of: fn.resize), by: fn (p) => p.name)
  let given  = luna.sort(value: compiler.members(of: V),      by: fn (f) => f.name)
  let usable = luna.filter(domain: luna.zip(a: wanted, b: given),
                           keep: fn (f) => f.type != (int, missing) && f.type != (str, missing))
  return fn.resize(..compiler.arguments(
    domain: usable,
    step: fn (f) => compiler.member(of: values, name: f.name)))
}

fn.paired(values: {width: 7, label: "east", scale: missing})

The press. It runs entirely in the compiler.

  1. Read both structures. The function's parameters and the given values' members are both field lists — the compiler's working posture of a named product.
  2. Align and pair. Sort both by name; zip pairs each parameter with the member of its name. Mismatched name sets refuse, naming both sides.
  3. Keep what was given. The pair's second type says whether a value is present, so the filter is a compile-time decision: the shape of the call is settled before the program runs.
  4. Make the checked call. The argument spread writes one named argument per kept pair; the missing label takes its declared default. The answer is "east14".

This is the shape of every form, tool, and workflow door in a Luna application: a function called from whatever answers a person has given so far. Everything above the final call runs in the compiler; what executes is the call a hand would write. This function is an executable test in Luna's repository and answers east14 on every run of the suite.

ONE FACT, TWO POSTURES

A list of fields and a named product are the same fact in two postures: the list is what compile-time code manipulates, the product is what programs hold. Two doors cross between them.

THE TWINS

Every compile-time fact has a runtime twin: type(of: v) reads a declared type as a value the stratum computes with; T.value carries a type down as data; f.parameters and f.result read a function's signature.

TYPES ARE COMPUTED WHERE THEY ARE USED

A body can compute a type and stand it in its own annotations — type row = luna.product(of: …) then let copy: row = values — compiled byte-identical to the literal spelling.

The next compilation can learn from this run.

Read a dataset's column names and commit them. The next submission can turn those names into fields and declare a function for that schema. The compiler pins the committed revision it reads. A function built this way can be published for later calls.

See a computation that runs entirely in the compiler
Filter a list of typesLanguage report
fn numeric_columns() -> int {
  let columns = [int, str, float, bool]
  let numeric = luna.filter(domain: columns, keep: fn (t) => t == int || t == float)
  return luna.length(array: numeric)
}

The same filter used for face qualities can select numeric types. This body becomes return 2; the type list and its traversal never exist at runtime.

Compile-time parameters make a function a metafunction. Each exact instantiation compiles once and is retained. A mixed computation can resolve its structure now and leave its numeric work for execution.

Fields, products and live parameters

A field list retains declaration order, defaults and docs. Named product type identity uses only names and types. Declared product aliases retain their defaults for construction; positional products retain their order.

A live parameter names a binding. Its delivered value has the binding's type or missing. Liveness belongs to the parameter name.

An entry-watch signatureInstalled signature
watch.attach(Ts: ..type, ..live to: Ts, body: (..Ts | missing) -> none) -> watch.id

06 / NATIVE ARRAYS

The shape is in the language.

Dense multidimensional array is a Luna type, with a window per axis: array<float> is the flat rank-one array, array<float>[any, 3] has one dynamic axis and a fixed three, [2..5, 3] bounds an axis, and array<float>[..any] erases the rank. Broadcasting, reductions, selection, reshape and indexed gathers operate on native typed buffers. Shapes known at compile time travel with the type; what stays dynamic is checked at the operation.

array<float>[2, 3]2 axes · 6 contiguous values
3.01.04.01.05.09.0
meanaxis: 1 →
2.675.0
One typed array operation reduces each row. The result is another native array.
Reduce, then normalize by broadcastArray vocabulary
fn normalized(m: array<float>[any, any]) -> array<float>[any, any] {
  let low = luna.min(value: m, axis: 0)
  let span = luna.subtract(a: luna.max(value: m, axis: 0), b: low)
  return luna.divide(a: luna.subtract(a: m, b: low), b: span)
}

The input declares two dynamic axes. Each reduction, spelled with axis: on the same min and max names, answers one value per column; broadcasting aligns shapes at the last axis, so those values combine with every row. Every step runs on native typed buffers.

The controls work at any rank.

map, filter, fold, each, any, all and zip iterate an array of any rank. The callback receives the scalar at rank one, or a zero-copy rank N−1 view: a scalar body over an [n, 3] array publishes [n], a [3] body publishes [n, 3]. transpose and permute are views too.

Selection has one vocabulary. luna.compress(condition:, value:, axis:) keeps whole lanes by a mask; take(value:, indices:, axis:) is one name that gathers rows for rank-one indices and reads lanes for matching-rank ones; luna.sort is one name for a list, an array's first-axis rows, or rows by a key, and luna.sort(value:, axis:) orders every lane along an axis; nonzero answers flat positions at every rank.

Controls and selection over rowsArray vocabulary
// the callback gets the rank N-1 view of each row: a scalar body publishes [n]
fn squared_lengths(points: array<float>[any, 3]) -> array<float> {
  return luna.map(step: fn (p: array<float>[3]) => luna.sum(value: luna.multiply(a: p, b: p)),
                  domain: points)
}

// one name each: compress keeps rows by a mask, sort orders every lane along an axis
fn kept_sorted(points: array<float>[any, 3], keep: array<bool>) -> array<float>[any, any] {
  return luna.sort(value: luna.compress(condition: keep, value: points, axis: 0), axis: 0)
}
jagged<int>2 blocks · 7 values · one flat buffer
offsets
037
data
0122134
blocks
block 0 · 3block 1 · 4
Each block is a zero-copy window of the data, of 3 values and of 4: a triangle and a quad share one buffer, and nothing is copied per block.

Blocks of any length, in one buffer.

A jagged is blocks of one element, stored as {offsets, data}: one flat data array, and an offsets array that says where each block starts. Faces of any arity fit, with no per-block allocation, because each block is a zero-copy window of the data.

luna.generate publishes one block per element of its domain, in order. The parallel run publishes the same jagged: parallel: true changes time and nothing else.

07 / ASYNCHRONOUS CALLS

Start a call. Continue with other work.

luna.spawn hands a checked call to the host's dispatcher and returns future<T>. The caller can continue before asking for its answer. The task owns copies of its inputs; nothing borrows the caller's memory.

luna.await answers T | error. Failed or abandoned work settles as an error value, handled by the same is test as any union. With no dispatcher, the call runs inline and returns an already settled future.

A host can admit blocking waits, refuse an actual pending wait, or let a retained invocation yield and resume when ready. Yielding leaves the application free to process its next turn. Parallel sequence work is part of the correctness story: the compiler checks it like everything else.

Follow a retained invocation
Continue, then consume the futureParallelism topic
fn work(x: int) -> int { return x + 7 }
fn both(a: int, b: int) -> int {
  let pending = luna.spawn(call: work(x: a))
  let here = work(x: b)
  return if luna.await(value: pending) is int there { here + there } else { here }
}

The caller computes here, then consumes the spawned answer. This example uses here alone if the other call failed.

08 / THE LIVE TIER

The instant is typed.

An application's moving state — the selection, a slider mid-drag, a tool's working values — lives in the same environment as everything committed, under the same names and the same exact types. That is the live tier, and it is the language's own: typed bindings at interaction rate, with checked bodies standing on them.

Declare it live. live name: type = value — the same environment, the same exact types as every committed binding. The one difference is history: a live entry has none.

Write where it stands. := performs the write at interaction rate — no act, no trace. A mention of the name reads a fresh snapshot that cannot tear.

Stand a body on it. A watch names the live entries it observes, and the compiler checks its body as it checks any function — types, effects, termination. When an input changes, the body runs with the newest values.

Keeping is the deliberate step — see how a recording run lands it as a numbered act
Declare, write, and stand a body on itRun on the Luna client
import watch
---
// live bindings: a declared type, a first value, no history
live controls.angle: int = 0
live controls.turns: int = 0

// a standing body observes live entries and runs when one changes
watch.attach(to: controls.angle, body: fn (angle: int | missing) {
  controls.turns := controls.turns + 1
})

// := writes the entry where it stands — interaction rate, no act
controls.angle := controls.angle + 15

// a mention reads a fresh snapshot that cannot tear
io.print(controls.angle * 2)

This script prints 30: the write performed where it stood, and the read after it saw the fresh snapshot. The standing body runs on every later change.

09 / MEASURED PERFORMANCE

Real algorithms, in four languages.

Each one is the same algorithm in Luna, C++, Lua and Python, over the same input, with every answer identical to the last bit. Pick one, read the four sources, and see where each language lands against hand-written C++.

Luna
fn face_membership(faces: array<int>[any, 3]) -> jagged<int> {
  let count = luna.shape(value: faces)[0] * 3
  let corners = luna.reshape(value: faces, shape: [count])
  let order = luna.argsort(value: corners)
  let sorted = luna.take(value: corners, indices: order, axis: 0)
  let opens = luna.nonzero(value: luna.not_equal(a: luna.slice(value: sorted, axis: 0, start: 1, stop: count), b: luna.slice(value: sorted, axis: 0, start: 0, stop: count - 1)))
  let offsets = luna.concat(values: [array(values: [0]), opens + 1, array(values: [count])], axis: 0)
  return luna.jagged(offsets: offsets, data: order / 3)
}
Vertex-to-face membership

the corners put in vertex order by a stable `argsort`, a block opening wherever the vertex changes, each corner answering its face

Luna1 thread3.94 ms0.6×
C++1 thread6.26 ms1×
Lua 5.5PUC178 ms28×
PythonCPython31.1 ms5.0×

Bars are multiples of the C++ time, which is pinned at 1×. Lower is better.

Same algorithm, same loops, same data order, in every language. Every answer is hashed; a single differing bit fails the run.

Apple M4 Max. Wall-clock milliseconds, the best of several calls after a warming call (Luna and C++ best of five, Lua best of ten, Python best of three) over an input of 100,352 faces built untimed; Luna on one thread, C++ -O3 with LTO on one thread, PUC-Lua 5.4.7 and PUC-Lua 5.5.1, CPython 3.14.7 with plain lists; every answer checked bit for bit against the C++ twin's. Lua here is standard Lua (PUC-Lua), not LuaJIT. A selection from the Luna benchmark harness; methods and the full set ship with it. Every implementation answers the same bits: the FNV-1a digest of the answer's integers and float bits is b9953bcec8deb097 in Luna (one thread), C++, Lua 5.4, Lua 5.5 and Python.

Safety gives the compiler room to simplify. Exact types and an acyclic call graph tell it where values live, which functions can run, and when cleanup is needed. It can inline small calls and sequence callbacks, turn a fold inside a map into nested counted loops, and reuse scratch storage where lifetimes do not overlap.

Those decisions shape retained bytecode: direct typed operations, linked calls, and only the control flow the program needs. The executor runs it without parsing source or resolving types again, which is why the same loops, written in Luna, run far closer to hand-written C++ than the scripting runtimes do.

Native calls stay cheap, too.

Lower is better
Luna — typed native call21.79 cyc
PUC-Lua — lua_CFunction49.14 cyc

One installed string-length call through each language's native-function boundary — cycles per call, one interleaved session on Apple M4 Max. K = 1 versus K = 16 at one million calls, three repetitions. This measures Luna’s typed native door against PUC-Lua’s C function door. Lower is better.

Native arrays at work.

Luna has native multidimensional arrays for typed data work. Here, five common workflows run on Luna arrays, NumPy arrays, Python lists, and Lua tables at 1,000 and 100,000 elements. The chart shows how each approach performs the same work.

Input size
Luna = 1× NumPy 2.4 CPython 3.13 PUC-Lua 5.5Higher means more cycles than Luna

Elementwise

Double a dense block, then read a result.

Luna 1.37 cycles / element
NATIVE ARRAY LIBRARY
NumPy2.27×
LANGUAGE CONTAINERS
CPython list25.70×
PUC-Lua table26.68×

Axis reduction

Sum each 100-element row.

Luna 1.01 cycles / element
NATIVE ARRAY LIBRARY
NumPy3.06×
LANGUAGE CONTAINERS
CPython list11.22×
PUC-Lua table30.55×

Index gather

Gather alternating positions, then sum.

Luna 2.26 cycles / element
NATIVE ARRAY LIBRARY
NumPy3.71×
LANGUAGE CONTAINERS
CPython list11.27×
PUC-Lua table13.97×

Build → map → sum

Build the input, double it, then sum; construction is timed.

Luna 2.49 cycles / element
NATIVE ARRAY LIBRARY
NumPy2.74×
LANGUAGE CONTAINERS
CPython list35.71×
PUC-Lua table29.13×

Mask → map → sum

Select by a boolean mask, double the selected values, then sum.

Luna 3.40 cycles / element
NATIVE ARRAY LIBRARY
NumPy3.00×
LANGUAGE CONTAINERS
CPython list35.22×
PUC-Lua table32.47×

Ratios use warm cycles per original input element; Luna is 1× in every row. Each labeled group has its own scale, so compare bar lengths within that group and read the ratios across groups. A ratio below 1× means that competitor is faster.

Measurement details

Apple M4 Max; Release, serial execution with TBB disabled. CPython 3.13.11, NumPy 2.4.2 and PUC-Lua 5.5.1. Luna and NumPy were remeasured together after the serial array optimizations; the unchanged CPython-list and PUC-Lua-table programs come from earlier lanes of the same suite. Complete outputs and checksums were checked. Input setup is excluded except in the build workflow. The large gather row was repeated with a longer five-sample budget after its first Luna slope exceeded the 12% drift limit. Results recorded 29 September 2026 in the Luna array benchmark suite.

These are the named workflows on Apple M4 Max, not a claim about every array shape or workload distribution. All four lanes report warm cycles per original input element; methodology and samples are recorded with the Luna benchmark harness.

LUNA RUNTIME

The language stays inside the application.

See how the running product holds live state, records deliberate changes, and delivers standing programs.