Learn · from docs/playground-tutorial.md in the eLucid8 repository

Contents

eLucid8 Playground Tutorial

The eLucid8 Playground runs the eLucid8 engine entirely in your browser: you write a program, give it data, ask for one value at one point, and see every demand the engine made to answer it. This tutorial teaches the playground itself. The language is covered in the eLucid8 Tutorial, so read that first if fby, next and @ (t) are new to you.

Each lesson takes 5–10 minutes and starts from one of the built-in examples. Nothing you do is saved: reloading the page or picking another example starts fresh, and other people with the link have their own separate copy.

This file is the source; a shared copy is kept as a Claude Docs page. The numbers and messages it quotes are checked against the engine by packages/evaluator/test/playground-tutorial.test.ts.

Tour of the screen

The page has a header and three panes: what to compute (Program), what to ask (Query), and why the answer came out as it did (Details). On a narrow screen the panes stack.

AreaWhat it holds
HeaderExamples dropdown (15 ready-made programs) and Reset caches
Program paneThe program editor; a status line ("Compiled · 3 values, 1 source…" or a compile error with a clickable line number); one data box per source and per clock; External service latency (ms); the list of external services you can call, with a folded Services reference
Query paneValue dropdown, one coordinate box per dimension, Run query, eight option checkboxes, the result line, and the Demand tree
Details paneThe selected tree node explained: how it was produced, its outcome, and any external service call or alignment record; for 2-D values, a grid view

Edits to the program or data recompile after a short pause, but they do not rerun the query. Press Run query (or Enter in a coordinate box) to run it; changing a checkbox reruns it automatically, except trace, which applies from the next run.

Lesson 1: Run your first query

You will ask for the running sum of a six-number stream and get 14.

  1. Choose Streams: first, next, fby from Examples. The program declares dimension t : int, one source x @ (t) and four values: n, running, delta and since_start.
  2. Look at source x in the Program pane. It is a table: t,value then the rows 0,3 · 1,1 · 2,4 · 3,1 · 4,5 · 5,9.
  3. In the Query pane, Value is running @ (t) and t is 4. The example has already run, so the result line reads 14 (3 + 1 + 4 + 1 + 5).
  4. Under the answer, the grey chips count the work: how many values were evaluated, how many source points were read in how many calls, and how many service calls were made.
  5. Change t to 5 and press Enter: 23. Change it to 6: Undefined, because next.t x at t = 5 asks for x at t = 6, which the table does not list.

Try the other values at t = 4: delta is 4 (9 − 5), since_start is 2 (5 − 3) and n is 4.

Lesson 2: Read the demand tree

The demand tree shows every value the engine asked for to answer your query, nested under whatever asked for it. It needs trace on, which is off by default to keep big queries fast.

  1. Stay on Streams with running at t = 4. Under Demand tree you will see "Trace is off". Press Turn on the trace and re-run.
  2. Then press Reset caches: the first run already stored the answer, and without the reset the tree would show only that stored copy.
  3. The root row is running at t = 4. Each row shows the point asked for, a coloured label saying how it was produced, and → its outcome. The first three levels open automatically; click ▸ to open deeper ones.
  4. Under the root you will find running at t = 3 (the fby step back) and x at t = 4 labelled source-read. Follow running down to t = 0 to see the whole recursion.
  5. Click any row. The Details pane names it, explains its label in one sentence and shows its outcome.
  6. Press Run query again without changing anything. The root is now labelled cache-hit with no children: the answer came from the point cache (Lesson 4).

For a value with two or more integer dimensions, Details also offers Show value over x × y: a coloured grid around your point. If the value has a third dimension, ◀ ▶ and Play step through it. Try it on Heat diffusion over time and space to watch the heat spread tick by tick.

Lesson 3: Write your own program and give it data

Every source you declare gets a data box, and you choose how it supplies values. Start from any example and replace the program with this:

dimension t : int
source temp @ (t)

change @ (t) = next.t temp - temp
warming @ (t) = change > 0
  1. The status line should read "Compiled · 2 values, 1 source". If it shows a compile error instead, click the line number in the message to jump to the problem.
  2. A source temp box appears with the header t,value. Add the rows 0,10 · 1,12 · 2,11 · 3,15, one per line.
  3. Pick warming @ (t), set t to 2, press Run query: true (15 − 11 = 4). At t = 3 it is Undefined, because there is no t = 4 row.
  4. Switch the box's mode from table (CSV) to formula and enter 10 + #t * 0.5. Now every t has a value, so change is 0.5 everywhere.

The three data modes:

ModeWhat you typePoints with no data
table (CSV)A header of the source's dimensions plus value, then one row per point. Values can be numbers, true/false, text, undefined or error: message. A quoted field ("a, b", with "" for a quote) is always text; read exactly as the elucid8 command reads a CSV fileUndefined
formulaOne eLucid8 expression using #t, #x and so onNone: every point has a value
built-in exampleNothing; offered only where an example supplies its own function (the Alps data, the Game of Life seed)Decided by that function

A program that declares a clock gets a clock box too, starting at 0, 100, 200; Lesson 5 covers it. Editing a program or its data never clears the caches. Each edit creates a new version, and stored values from the old version simply stop matching.

Lesson 4: Caches and Reset caches

The playground keeps computed values in two places. The memo table lives for one query; the point cache lives in the page and lets later queries reuse earlier work.

Memo tablePoint cache
LastsOne queryUntil Reset caches, a new example, or a page reload
Keyname + contextname + definition version + context
Checked on a hitNo: one query sees one snapshot of the dataYes: the versions of the sources and clocks it was built from must still match
Tree labelmemoizedcache-hit
Switched byAlways onthe point cache checkbox
  1. On Streams, with trace on, press Reset caches. Query running at t = 4: every row is computed or read.
  2. Query t = 5. The root is computed, but its child running at t = 4 is a cache-hit: the earlier query's work was reused.
  3. Edit source x, changing the first row to 0,4, then run t = 5 again. The answer becomes 24 and everything is recomputed: the old entries were built from a different version of x, so they no longer count.
  4. Untick point cache and run again. With no page cache, the whole recursion is computed every time.

Reset caches clears only this page in your browser: the point cache, the cluster store used by tiling, the source cache and the counts that decide when tiling starts. Then it reruns the current query. Nothing is saved anywhere else.

Lesson 5: Clocks, service calls and late

A clock gives each tick of a dimension a timestamp in milliseconds. An effect calls an external service: a program outside eLucid8 that answers on request, such as an AI model. A source is external data you read; an effect calls an external service that computes an answer. Its deadline counts from the tick's timestamp, and a call that finishes after the deadline is late, which is an error.

  1. Choose Spatial anomaly (README workload). Its program declares dimension time : int clock sensor, and the clock sensor box holds 0, 100, 200, 300: tick 0 is at 0 ms, tick 1 at 100 ms, and so on.
  2. The effect score calls the service anomaly_detector with deadline 20ms and retry overloaded up to 2 (at most 2 attempts in total). At tick 1 its deadline is 100 + 20 = 120 ms.
  3. The default query, alarm at time 1, x 7, y 6, comes back late: the detector finished at 121 ms. Turn on trace, open the score row and read the External service call table in Details: the deadline, each attempt's start and finish, and the result.
  4. Change y to 7 and run: the call finishes in time, and alarm gets an ordinary answer (false).
  5. Press Show alarm over x × y, then Play. Late cells are purple; the legend names every colour.

Two things a clock does not do. It does not require evenly spaced ticks: any strictly increasing list of whole milliseconds is accepted. And a source with no value at a tick gives Undefined, not late, because sources have no deadline.

The spatial example's detector is built in, with its own timings, so the latency box has no effect on it. The Program pane lists the services you can call, and the Services reference describes each one. For programs you write, the playground supplies a stand-in service, sum_service, which adds up its numeric inputs and takes External service latency (ms) to answer. To see late appear and disappear on demand:

dimension time : int clock sensor
source x @ (time)

effect total @ (time) =
  call sum_service(x)
  deadline 20ms

Switch source x to formula and enter #time. The clock box starts at 0, 100, 200. Query total at time 2: the answer is 2, delivered 5 ms after the 200 ms tick. Set the latency to 30 and run again: late (230 ms > deadline 220 ms).

Only services the playground supplies can be called. Misspell the name, call sum_servce(x), and the status line shows a compile error, "unknown service: sum_servce; available: sum_service", with the line to fix. (In the Spatial example the list also includes anomaly_detector.)

Lesson 6: Align two clocks

Values on different clocks can only be combined through align, which picks ticks on the other clock by timestamp. The policy decides which ticks count.

Choose Two clocks and alignment. A reading arrives on clock sensor (0, 98, 203, 300, 390, 401, 499 ms) and is multiplied by a calibration on clock calib (0, 230, 380, 401 ms). The calibration table is 1.0, 1.05, undefined, 1.1, so the tick at 380 ms is missing.

time_s (sensor ms)asofhold max 100mswindow 200ms (mean)
1 (98)111111
2 (203)12Undefined: last value is 203 ms oldUndefined: no calib tick in the window
3 (300)13.6513.6513.65
4 (390)Undefined: tick at 380 has no valueUndefined: 1.05 is 160 ms old14.7: the missing value is skipped
5 (401)16.516.516.125: mean of 1.05 and 1.1
  1. Query each of the three values at time_s 1 to 5 and compare with the table.
  2. With trace on, open the aligned row under any answer. Details lists the calib ticks examined, in order, with their timestamps, or "no tick qualified".
  3. Edit the calib clock or the calibration table and watch the answers change. Lesson 4 applies: every stored value built on the old clock version is recomputed.

The fourth policy, exact, matches only a calib tick with exactly the same timestamp: here only time_s 0 and 5.

Lesson 7: Speed options never change the answer

Five checkboxes change how the engine works, never what it computes. Choose Image pipeline: blur and edges: the query edge at x 16, y 7 is 0.444 under every combination. Watch the chips under the answer instead.

Turn the options on one at a time, pressing Reset caches after each. These are the counts for the first run, with check mode on (the default):

Options onEvaluatedSource points / callsTree labelWhat changed
None extra (point cache, check)721 / 21computedOnly what the query needs
+ tiling (size 4)128126 / 126tile-fillThe whole 4×4 tile around the point is computed and stored
+ batched reads128126 / 1tile-fillThe same reads, gathered into one call
+ tile kernels (with tiling)512190 / 65vectorisedWhole tiles run as one kernel instead of point by point

Check mode verifies each kernel by evaluating every element again point by point, and those extra evaluations and reads are counted too. Untick check mode on the last row and the counts show what the kernel itself did: 96 evaluated, 64 source points in 1 call.

  1. With tiling on, query x 17, y 6: cluster-hit, 0 evaluated, because it sat in the tile just filled. Query x 20, y 7: a new tile is filled.
  2. Untick tiling and point cache, then turn on source cache. Query x 17, then x 16: the second query reads only 5 source points instead of 21, because neighbouring reads are kept between queries. Query x 17 again: 0.
  3. On a vectorised row, Details offers Re-run this element per element, without kernels, which shows the full per-point tree for the same answer.

Tiling pays off when you will ask for neighbouring points; for one point it does extra work. Tile size (2, 4 or 8) sets the tile's width in each dimension.

The fifth option, group levels (on by default), speeds up values that recur together over a dimension, such as a network's weights and gradients over training steps. Each step is computed as a few whole-array kernels instead of point by point. Try it on Training a tiny neural network (XOR): untick check mode, then run predict with group levels on and off. The answer is the same; with it on, the query runs about 3× faster (about 7 ms against 20 ms on a laptop).

Lesson 8: Undefined, errors and check mode

An answer is a value, Undefined or an Error, and every Error names the point where it started. Choose Missing and failed readings (Undefined and Error): reading has no value at hour 5 and divides by zero at hour 9.

QueryAnswerWhy
doubled at t = 5UndefinedStrict: a missing input gives a missing result
doubled at t = 9Error via reading (t = 9): division by zeroThe Error names its source point
safe at t = 90The guard is false, so the bad reading is never asked for
window3 at t = 6UndefinedThe window t 5–7 includes the missing hour
window3 at t = 10Error via reading (t = 9): division by zeroThe window t 9–11 includes the failed hour

The playground catches three kinds of program mistake:

Check mode (on by default) makes the engine audit itself while it runs. It confirms that cache and cluster entries for the same point agree, that kernels give exactly what point-by-point evaluation gives, that a value freed from memory and computed again comes out the same, and that remembered validity matches a fresh check. A check failure is not a mistake in your program; it means the engine broke one of its own rules, and the message names the rule (R1, R3, M2r and so on). Report it with the program and query that caused it. Check mode makes queries slower, because it repeats work to compare; untick it to see the engine's real speed.

Quick reference

Options

CheckboxDefaultEffect
point cacheonKeeps computed values between queries; reused while their versions match
tiling + tile sizeoff, 4On a miss, computes the whole tile around the point
batched readsoffGathers source reads into as few calls as possible
source cacheoffKeeps source values between queries
tile kernels (with tiling)offComputes a whole tile in one kernel run
group levelsonComputes values that recur together one whole level at a time
check modeonThe engine audits its own rules while it runs (slower)
traceoffRecords the demand tree; takes effect on the next run

Demand tree labels

LabelMeaning
computedIts equation was evaluated
source-readRead from a source at the query's pinned version
source-memoA source point already read in this query
source-cacheA source point read by an earlier query (source cache)
cache-hitTaken from the point cache, still valid
cluster-hitTaken from a tile stored by tiling
tile-fillA miss under tiling: the whole tile was computed
vectorisedComputed with its tile or level by one kernel run
calledAn external service was called
memoizedThe same point or service call earlier in this query
replayedA successful service result from an earlier query, reused by policy
alignedA value on another clock, reached through align

Answer colours in the grid view: true, false, late (purple), Undefined (grey), Error (red), and a shaded scale for numbers.