Skip to content

Own backend: errors and tasks (step 11, parts 1 and 2) - #56

Merged
jeany55 merged 3 commits into
trigscript-4from
errors
Oct 23, 2026
Merged

jeany55 merged 3 commits into
trigscript-4from
errors

Conversation

@jeany55

@jeany55 jeany55 commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Step 11 of the way off eudplib, parts 1 and 2: errors and tasks on a map of our own. The ledger's step 11 is 35 of
50 (it was 0): the 12 rows of errors (six added) and the 23 of tasks and Promise.all / race / any. The 15 left
are the on.* events, part 3. No row of another step changed but three of step 12's, which now name what they are
refused for (keys) instead of "a task started".

Part 1: errors (compiler/backend/errors.ts, the emitter's try, throw and handler)

  • The front end already puts the error in flight before a throw, copies finally and wraps each program in
    try { … } catch { uncaught }. errors.ts adds what needs texts: the line each error is thrown at, uncaught as the
    Python side's red print, and the RangeError of a stack that runs out (it stopped the program before).
  • A throw is a jump to the nearest catch. Out of a function that never calls itself it leaves through a trigger of
    its own, which each call points at where its error goes: nothing is looked at on the way back, where eudplib looks
    after every call. A function that calls itself goes out from its end, and the call looks once its frame is back.
  • What only an error goes through (statements that end in a throw, a catch, never a loop) reads its values from
    their cells, so they are not places every write keeps. The errors probe's payload went from 1.88 MB to 1.21 MB.

Part 2: tasks (the emitter's planTasks, coroutines, start, cancel, parallel, the tasks' turn)

  • The program, a task's slot and a branch of a Promise.all or race are each a coroutine: a trigger that wakes it
    once the map's own count of frames reaches the frame its sleep wrote. A free slot is passed by as done.
  • A sleep is now one copy of that count (@now, one more each pass) where it read the game's tick a bit at a time:
    about 70 ns instead of 0.5 µs, for every program.
  • The tasks have their turn after the program's, in the simulator's order. A start that runs once has one slot; one
    that can run while its tasks do has its max of slots, each with its own copy of the task's variables.
  • The front end's catch around every task's body is laid out once, as a function each task calls with its number. An
    uncaught error is said through one function. Before that, the probe's two 16-slot starts made a 19.8 MB map.
  • Found on the way: a loop's test was partly tested once on the way in, but while a loop sleeps a task may change it
    (await t is while (t.running) sleep(1)). A loop that sleeps now tests its whole condition each turn. An array of
    texts longer than 64 cells takes its blocks from the pool.

Weighed (lifted game, Remastered's weights; ours / eudplib)

probes/own-errors.ts, ms a frame Ours eudplib
throws caught, classes, messages made while the game runs 0.211 1.492
up through recursion; a stack run out at 1,024 calls 0.611 2.412
300 calls of a function that may throw 0.183 0.208
100 throws caught 0.876 2.846
an error up through 40 frames, four times 0.096 0.364
the map (CHK) 1.52 MB 1.05 MB
probes/own-tasks.ts, µs a frame Ours eudplib
sixteen tasks asleep, none waking 0.7 40.2
sixteen tasks awake every frame 3.9 90.4
sixteen tasks started in one frame 10.0 141.5
four branches of a Promise.all 1.0 41.0
a task's error caught where it is awaited 20.7 55.2
the map (CHK) 1.37 MB 2.05 MB

Not yet good enough, listed in the plan's Open list

  • A call in a loop of a function that may throw is about eudplib's (1.14×). The error costs one action; the rest is how
    a call copies its argument and result, which no longer keeps step 1's lead.
  • Most of a throw is making its message (7.3 of 8.7 µs): it could be made only where a catch reads it.
  • j = 1 - j is copied a bit at a time (about 0.5 µs).
  • A start of many slots lays its task's body out once a slot: fine small, large for a large body. Such a task's arrays,
    and a start inside it, are refused for now.
  • Found in the front end (both builds): try { check(9) } catch {} with a constant argument is worked out when the
    script is built, so the throw is a compile error instead of being caught.

Checked

  • tests/emitter.test.ts, "a map of our own, errors" and "a map of our own, tasks": the payload walked from its bytes
    against the simulator. Tasks are walked frame after frame, every text at its frame. Covered: every row of
    ledger/tasks/ and the errors rows, music.loop, ui.lines, a program for every player with tasks, cancelling,
    slots and their numbers, a task started from a task, branches inside a task, errors in tasks.
  • The 30 scripts of tests/errors.test.ts a map of our own takes print the same (checked once, not kept as a test).
  • Both probes built both ways in the lifted game: every line the same at the same frame, and "ok".
  • The 26 other probes/own-* build exactly the same graphs as on trigscript-4. The emitter's random-program tests
    take as long as before.
  • Full suite: 1337 passed (72 files).

Played in Remastered: own-step11-errors.scx (recording 20.07.15): all 10 lines in order, the uncaught error in
red, the frames as in the lifted game. To play: Maps/TrigScript/own-step11-tasks.scx with its answer sheet.

🤖 Generated with Claude Code

https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5

jeany55 and others added 3 commits October 22, 2026 20:09
…un out

Step 11, part 1 of the way off eudplib. The ledger's rows of errors are taken, 12 of 12
(six added). A throw is a jump to the catch of the nearest try; out of a called function
that never calls itself it goes through a trigger of the function's own that each call
points at where its error goes, so nothing is looked at on the way back (eudplib looks
after every call). A function that calls itself goes out from its end and the call looks
at the error once its frame is back. A stack that runs out throws a RangeError. What only
an error goes through (statements ending in a throw, a catch; never a loop) reads its
values from their cells where it reads them, never as places every write keeps.

probes/own-errors.ts, lifted game, Remastered's weights (ours / eudplib): A-B 0.211 /
1.492 ms, E-F 0.611 / 2.412, 100 throws caught 0.876 / 2.846, 300 calls of a function
that may throw 0.183 / 0.208.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
Step 11, part 2 of the way off eudplib: 23 more of the ledger's rows (35 of step 11's 50;
the on.* events are part 3). The program, each task's slot and each branch of a
Promise.all or race is a coroutine: a trigger that wakes it once the map's own count of
frames reaches the frame its sleep wrote, passed by as done while nothing runs in it. A
sleep is one copy of that count now, where it read the game's tick a bit at a time. The
tasks have their turn after the program's, in the simulator's order. A start that runs
once has one slot; one that can run while its tasks do has its max of slots, each with
its own copy of the task's variables. The front end's catch around every task's body is
laid out once, a function each task calls, and an uncaught error is said through one.

Also: a loop that sleeps tests its whole condition every turn (a task may change it while
the loop sleeps); an array of texts longer than 64 cells takes its blocks from the pool.

probes/own-tasks.ts, lifted game, Remastered's weights, µs a frame (ours / eudplib):
sixteen tasks asleep 0.7 / 40.2, sixteen awake 3.9 / 90.4, sixteen started 10.0 / 141.5,
Promise.all of four 1.0 / 41.0; the map 1.37 / 2.05 MB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
@jeany55
jeany55 merged commit 3934934 into trigscript-4 Oct 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant