Skip to content
View source

Scenes & .esn

An .esn file is the graph: it uses libraries, declares inputs and outputs, and places nodes. A node references a published Kernel by id or carries its own Kernel inline, so one .esn can be complete on its own.

use "kernels/"; // every .ent directly in the directory
use "../shared/tonemap.ent"; // one library file
input photo: Image; // supplied by the caller
param amount: Float = 1.0; // a graph parameter, overridable by the caller
require gentle: amount <= 4; // what the parameters must hold to run
node gray: image.grayscale@1 { // reference a published Kernel
source <- photo; // wire: this evaluation's value
amount = amount; // value: a literal, or a graph param by name
}
node trail: kernel { // an anonymous Kernel, placed here
requires { field.read; field.write; }
input current: Image <- gray.result; // wired where declared
input previous: Image <~ trail.result from gray.result; // previous evaluation
param decay: Float = 0.9; // the default is this node's value
output result: Image;
compute { return sample(current) + sample(previous) * decay; }
}
node glow: kernel fx.glow@1 { ... } // a named inline Kernel: placed here and publishable by id
node display: graph "../shared/display.esn" { // another graph placed as a node
source <- glow.result; // wires its `input`s
exposure = 1.2; // sets its `param`s
}
output result <- display.result; // a named output; sub-graph outputs read as node.output
output kept <- state.result to "saves/state.field"; // also written to a file

A node’s name may be any word, a keyword too (node scan: …); a Kernel’s own names (ports, params, values) cannot be keywords.

  1. Wiring is written only at the consumer. port <- source reads this evaluation’s value, a dependency; port <~ source from initial reads what the previous successful evaluation committed, not a dependency, and initial until something is committed.
  2. A source is a graph input by name or node.port; inputs and nodes share one namespace.
  3. Every declared input port must be wired, and wiring an undeclared port is an error — both reported before anything runs.
  4. Values use =: numbers, true/false, negation, vec2/vec3/vec4. A value is written, never computed: gain = 0.5 * 2.0; and gain = n + 1; are refused.
  5. domain sets a node’s extent and may be an expression of structure (size(port), window()…).
  6. A Kernel id is unique across everything a graph loads; an anonymous inline Kernel is named <document>.<node>@1.
  7. A use path is relative to the document; a trailing / means the directory’s .ent files.
  8. A node may bind a backend’s native operation: node raster: native.rasterize@1 { … } (native.rasterize@1/@3, native.cover@1), published by the runtime, never republished from a document, never fused.
  9. Sub-graphs: node name: graph "path.esn" { … } flattens on placement, its nodes joining the parent under name..
  10. Repetition: times = N; with carry port <- output;.
  11. Fill nodes: node zeros: Rows<Float> { domain = 65; value = 0.5; } — a Field rather than a Kernel.
  12. async = true; runs off the frame.
  13. awake = value(source, row); sleeps a node while that row is 0.
  14. require name: a <= b; says what the graph’s params must hold, checked at load and before every evaluation.

Each rule in full is in docs/DOCUMENTS.md, Graph · Rules; every form at a glance in Class reference · Scene syntax.

A graph may declare several outputs. The window shows the first picture given no device; an output goes to a Host device with to:

Device Takes
to sound rows of one or two floats (mono/stereo), a block of samples each frame
to pointer a picture of at most 128 by 128, the system’s pointer
to pointer_tip the pixel of that picture it points with
to clipboard a Rows<Int> of characters, put on the clipboard (the Player needs --allow clipboard.write)
to "file.ext", to bytes "f.bin", to files "dir/" files, with --allow file.write (removal and rename also file.delete)

A device name the Host lacks is refused before anything runs, with the nearest it has.

  • node g: font "x.ttf" { codes <- wanted.result; } reads a font into g.glyphs, g.outlines, g.metrics.
  • node d: files "dir/" { paths <- wanted.result; } lists a folder as an editor does (d.listing), reading only what is opened (d.opened), honoring .gitignore.
  • node near: Rows<Spot> fields "chunks/" { paths <- want.result; } brings .field files in on demand — how a world larger than memory keeps only what its frames are near.

The session parses the graph, publishes its inline Kernels, follows its use items, packages included, and rebuilds the Graph model. It publishes the Kernels of the documents the scene’s use items name; of documents those use in turn, the declarations only. A changed document republishes only the Kernels whose text changed; a rejected replacement keeps the running version; a Kernel that leaves its document retires.