Skip to content

TrigScript 4 - #3

Draft
jeany55 wants to merge 288 commits into
mainfrom
trigscript-4
Draft

jeany55 wants to merge 288 commits into
mainfrom
trigscript-4

Conversation

@jeany55

@jeany55 jeany55 commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

trigscript-4 is the branch for all of TrigScript 4, across plugin-trigscript, scm-sim, plugin-scm-sim and plugin-eudplib. Each slice lands on it, and it merges into main when v4 is complete. This PR stays a draft until then. So far it holds slices A0–A2:

This starts TrigScript 4: the lifted 1.16.1 game (scm-sim) checks what TrigScript builds. A script's tests run in the game as well as in the simulator, and a run's memory accesses are checked against what Remastered allows, line by line. The plan, with an "As built" section for each slice, is docs/v4-plan.md.

A1: what a run touches, by line (Node only for now)

  • The trigger map. With the triggerMap setting, python/trigscript.py labels every trigger with the statement it came from, then logs where each one sits in the payload.
  • The allowlist. data/allowlist.json (191 entries) lists what Remastered lets through. It comes from Magenta's catalogue, three failures seen in the real game, and the traces of the 15 played probes.
  • The checker. compiler/allowlist.ts checks a run's trace against the allowlist, byte by byte under the mask, and names the address. scripts/game-check.mts builds, plays and reports by line.

A2: tests in the game

  • GameSim (compiler/gameSim.ts) gives a test the same sim as the simulator, driven through scm-sim's test handle:
    • Every read is the game's memory.
    • Units are placed, killed, removed, given and moved by the game's own actions, through a scratch location that is put back afterwards.
    • A typed line is chat command 0x5C, and keys and the mouse are the local machine's memory.
  • sim.program() reads a program's variables where the build says they are. With the variableMap setting the lowering logs each cell's address, and compiler/gameVariables.ts reads them by the simulator's lookup rules.
  • dist/gametest.js runs the compile's JavaScript again beside the game. The plugin never loads it; it hands the text to the host.
  • In the editor: Run all tests in the game in the Testing view and the palette. Each test gets a second mark for the game, ringed red when the game and the simulator disagree. Test Results gets an "in the game" row. The build's machine lines are kept out of Output.
  • Under Node: scripts/test-game.mts prints the game and simulator results side by side.

Simulator fix found by the game

  • Kill Unit, Kill Unit At Location, u.kill() and hit points written to 0 counted a death in the simulator. The game counts none of them, and the editor's trigger reference agrees.
  • Fixed (World.gone(…, fought)), and three tests were corrected to the game's rule.

Checked

  • probes/game-tests.ts and probes/game-program.ts pass in the game and the simulator.
  • In a built editor, served beside scm-sim's page: "In the game: 5 of 5 tests passed." about 10 s after the click, eudplib's one-time install included.
  • The plugin suite passes (611 tests).

Depends on: jeany55/scm-sim#20 deployed to /play/, and scm-js/plugin-scm-sim#14 released as 0.10.0. Without those the button says what's missing and does nothing else. The build-fixture scripts use scm-js/plugin-eudplib#2 for the log file.

After merging: cut a release (4.0.0 per the plan, or a minor if A1's Node-only tools shouldn't count as 4.0), then pin it in scm-js.

🤖 Generated with Claude Code

@jeany55 jeany55 changed the title TrigScript 4: TrigScript 4, A0–A2: the lifted game checks the build, and tests run in it TrigScript 4 Oct 16, 2026
jeany55 and others added 28 commits October 16, 2026 17:34
……), async methods

for…of walks a generator (function*, or async function* with for await):
its body inlined, the loop's body at each yield. const [a, b] = await
Promise.all([…]) takes each item's value. .then's function may take
what the promise gives. A class may have async methods. An async
function that never awaits is a hint; a loop that can break out is no
longer called a freeze. Checked in the lifted game by
probes/game-generators.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TrigScript 4, B1 part 2: tasks (start), generators, Promise.all values, async methods
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A .tsx script writes a text the game shows as elements: <line>, <span>,
<cell> and <bar>, coloured by name or by a number of the program's.
A JSX text is put together in a buffer of its own (buildText / built),
so a board is written in place rather than copied per piece; char is a
byte from a number. ui.lines(player, () => ...) is a task that shows
the text in the player's lines, built again only when something it
reads was written (views.ts) and shown again every 24 frames.

Also: a call in a loop's condition that writes what is outside it now
counts as changing the condition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e game too

The Python lowering worked every item of an and / or out before testing
any, so a call to the right of one that settled it still ran:
keyPressed(P1, "W") && !move(0, 0, 1) turned the Tetris piece every
frame in the game, never in the simulator. An item after the first that
calls, pops or draws a random number is now worked out inside the test
of those before it. probes/game-short-circuit.ts fails on the old
lowering in the lifted game and passes now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MSQC's KeyPress is true on every frame a key is down, and a tap lasts
two or three frames: one tap of W turned the Tetris piece two or three
times, so it often looked as if it had not turned. The build asks MSQC
for KeyDown, the frame the key goes down, and the simulator treats a
key down on consecutive frames as one press, as the game does.
probes/game-key-tap.ts fails on the old build in the lifted game.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
keyHeld(player, key) is true on every frame a key is down: MSQC's
KeyPress, sent each such frame into cells of its own (tsin_held). In the
lifted game a hold of n frames is read on n - 1 of them (the first two
frames' messages arrive together); probes/game-key-held.ts checks it
there and in the simulator. probes/key-held.tsx draws each key's last 32
frames as a strip, to play in Remastered: whether a held key reaches the
game the whole time, and stops when it is let go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cation never hoisted

probes/unit-pixels.ts asks whether a board can be drawn with units:
owners as colours, vision, which kinds make a clean pixel, a pixel moved
by removing and making it, and how many a frame before the game slows.
Run in the lifted game first: Overlords land on their cells on any
ground; buildings and small items only where the ground takes them;
Player 2's (a computer's) is moved by its AI; large air units push each
other apart. The probe maps gain a 64 x 64 location, Pixel.

Tetris: held A or D slides the piece after a moment, held S keeps
dropping (keyHeld).

centerLocation() with every argument known at build time was computed
when the script was built, and threw: it is a call of the game's now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A held key is a message a frame, carried by MSQC as a game command, and a
command can go missing for a frame (the lifted game loses the second of
every hold) or arrive with the next frame's (a multiplayer game carries
commands in turns). Tetris's slide, which counts held frames in a row,
never got going. What arrives is now banked and paid out a frame at a
time, and a key stays held until two frames pass with no message; the
simulator has the same grace. The game tests run a main.tsx too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MSQC's carrier has one order, and a multiplayer game may bring two
frames' commands at once: the later replaces the earlier. A tap sent
"went down" then "is down", and when the two arrived together the
press was lost - in Tetris, a tap of A or D now and then did nothing,
in multiplayer only. With any held key in the game every key is sent as
held, and a press is held now and not before, worked out where it
arrives; the simulator does the same. Without held keys, KeyDown as
before: one command a tap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…erlords

The game writes one board and two screens show it: the text lines
(ui.lines, JSX) and a board of Overlords beside the base, one a cell in
its piece's colour, which the minimap shows as solid blocks.
probes/lib/unit-screen.ts draws any board that way and knows nothing of
Tetris: only the cells that changed are taken away and made again. The
probe scripts now take the files a script imports (scripts/script-files).

In the lifted game the Overlords follow the board piece by piece; it
makes no unit of Player 12's (the Z piece's colour), which Remastered
does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… from the camera

Players 2 to 8 own the Overlords, a player a piece, each given its
piece's colour as the text lines draw it (measured from screenshots) -
Remastered's own colours for a map's players. Player 2 is no computer on
this map, so nothing moves them. The board is in the top left corner,
away from the camera, so the minimap's view box is not over it.

A probe states such players in a line of its own (// probe-players:
{...}), which the probe-map build applies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remastered will not start a game without a computer, single player or
multiplayer, and a computer moves its own units and sends them to fight
(in the lifted game its Overlords wandered and its Scourges destroyed a
P3 Overlord). Player 2 stays the computer and owns no cell. The green S
piece is Player 1's - your units are green on the minimap anyway - and
P3 to P8 have the other six, each in its piece's colour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A class declared at the top of any of the script's files is the
program's wherever a program makes an instance of it, and an ordinary
class when the script is built. It is planned as a game function is
(hoist.ts#planClass); the emitter adds __class(C, ...) after it, handing
the run what its bodies compute at build time in its own file; in a
program a script class is never hoisted, and the lowering declares
the classes a program names (and theirs, and those of the game()
functions it calls) before its first statement. Its errors are
reported only where a program uses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TrigScript 4, S2: JSX texts and ui.lines, keyHeld, Tetris on two screens, classes from any file
Each on.… is a listener task (IR 18: start.listen) inside an async program that hands
on what happened from the next frame: a handler that is not async is inlined, an async
one is started each time. Units come from a pass once a frame that walks the game's
visible and dying lists from their heads to where the last pass left them, with a
slot table of its own; nothing of the game is written (unitLoop.source). on.chat and
on.key are unrolled over the players, so the player is known in the handler.

Idle, a listener costs about 46 conditions and actions a frame (2 for each more); a
unit made about 100, a death about 80. The build-time evaluator now takes object
literals and a record's known fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ghidra on Remastered 1.23.10: its kill routine links a dying unit in behind the
other end of the list from the one its EUD layer maps to 1.16.1's 0x62842C, so a
walk along "next" from there sees one death a frame. The events pass now walks
the dying list whole, after and then before the record at 0x62842C, which is
right in both games; the cell still hands each death on once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t and on.key in the lifted game

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Played 2026-10-17: on.key heard Q, F7 and F9 but not F2, F3, F4 or F5, and
keyPressed did not hear F2 either (F2 to F4 move the camera). They leave the Key
type with a message saying why; the examples and tests use F7. The events probe
passed every other step in Remastered, recorded in the plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jeany55 and others added 30 commits October 19, 2026 23:23
…e script is built

`print(`${first({ owner: P1 })?.hp}`)` printed "undefined", `if (first(…)?.cloaked)` compiled to nothing, and
`n = first(…)?.hp ?? 5` to `n = 5`: the evaluator worked the field out of the pick object itself. A field of a pick is
read while the game runs now, as one of `local.selected` already was. Both builds had it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1CM9pLuTuAn3mgTEkjgVg
…order

GAME_CALLS took every on.* event's name, so the library's own action `order(…)` was never worked out when the script
was built, and a program using it failed with "A call's arguments must be known". An event's name is the game's only
as `on.order(…)` or a unit's `u.order(…)`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1CM9pLuTuAn3mgTEkjgVg
…op's own

`while (true) await play(s)` showed the loop no sleep of its own, so `start(music.loop(song))`, the module's own
example, never compiled. The loop plays the song itself and waits at least a frame at the end of each turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1CM9pLuTuAn3mgTEkjgVg
… a map of our own makes of it

831 scripts in ledger/<area>/, each a feature of the language or the library, its first line the step of the way off
eudplib that makes it a map of our own's and whether one takes it today, in the words of its refusal. 250 are taken:
steps 1-3 whole, the start but for a ?: of booleans, fixed arrays but for a cell read at a shifted index.

tests/ledger.test.ts holds every script to its first line, and holds the ledger to the language: every kind of the IR
(but the six the front end takes out), every name a script has from the library, the members of on, ui, memory, local,
a unit, a set of units and a task, and every export of the standard library's modules is in some script.
scripts/ledger.mts prints the table by step, writes the first lines anew after a step (--write), and checks a few or
lists what nothing reaches (--check, --missing).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1CM9pLuTuAn3mgTEkjgVg
Its table by step; what was fixed with it (a field read off a pick, order() in a program, music.loop, a print to the
owner by slot, owners P9-P12); what is open (a function of the script that keeps a state, called in a program, worked
out once; orders.* in order(); disabled() and preserve() in a program said nothing; false errors; JavaScript a program
lacks); and the README's language reference, older than TrigScript 4, to be written again from the ledger at 4.0.
Refusals now name what they refuse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1CM9pLuTuAn3mgTEkjgVg
The ledger: every feature, its step off eudplib, and what a map of our own makes of it
…Move as move, patrol and attack

orders.Move made an Order action of modifier 6, orders.dat's number, where the action knows 0, 1 and 2; any other
of orders.* passed as a number too, orders.Die and orders.Stop taken for move and patrol. The three the action
gives are now its own numbers, every other a type error (a raw number is a second signature: in one union with
it, TypeScript took Order<0> for a number).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1CM9pLuTuAn3mgTEkjgVg
…hen the script is built

A function changing a variable declared outside it (a closure's, a let of the file's) or a field of this outside a
constructor was worked out where its call was written: a loop printing next() of `const next = counter()` showed
1, 1, 1. A call of it is now the program's (hoist.ts keepsState): a closure made in the program runs in the game;
one made when the script was built, or a method of an instance made then, is refused; a function writing the
script's own let or record is refused as the write written in the program is. A cache filled by a method stays a
build-time call. Diagnostics said again for each call of the same function are said once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1CM9pLuTuAn3mgTEkjgVg
a * b of two values, a / b and a % b of two values and by a constant, signed and u32, written where they are
used. A product tests its narrower side a bit at a time and adds the other side: its multiples as fields of
their own when the tested side is narrow (a trigger a bit), else Horner's way, the product doubling in one
action of a trigger that walks a run of bit triggers by moving its own next pointer on. A quotient by a
constant is eudplib's chain inlined, as long as the ranges allow; of two values, long division with the
remainder doubling and one shared step holding the divisor in two fields. Operands the plan copies are routed
into a routine's fields where it starts, never kept as multiples. Fills fold their scratch cell's conditions
and routes too; copies into multiples go over only the bits a range allows.

probes/own-numbers.ts, lifted game: 200 products 0.700 ms against eudplib's 0.943; by constants 0.572 / 0.730;
quotients 0.848 / 1.175; remainders 0.750 / 1.148; small numbers 0.514 / 0.764. Ledger: step 4, 17 of 59.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1CM9pLuTuAn3mgTEkjgVg
& | ^ as values, with a constant and of two values, are masked writes of a copy: the game works a masked write
on the bits under its mask alone, an addition's carry past it lost, so & and | are one write whose mask is the
other side (an action's mask is a field like any number, flipped for & by two masked additions of all ones),
and ^ two masked additions over the even and the odd bits. A shift left by a value walks a doubling trigger
through runs of empty triggers, one a bit of the count; a shift right is Horner's way over the bits that stay,
the doubling trigger counting. Counts of 32 or more leave nothing (or the sign), as TrigScript has it; the
existing << by a constant took the remainder, and >> 0 is the number itself. A scratch value read by one field
only is worked in that field. The tests' interpreters do masked writes as the game does.

probes/own-bits.ts, lifted game: masks 0.063 ms against eudplib's 0.146; bits of two values 0.086 / 0.217;
shifts by a value 0.834 / 2.399. Products and quotients moved to 1.4–1.6x with the fill into one field.
Ledger: step 4, 27 of 59.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1CM9pLuTuAn3mgTEkjgVg
Fixed (16.16) products and quotients by a constant and of two values, a
constant over a value, and a fixed number printed, each faster than the
eudplib build in the lifted game (x*y 0.52 vs 0.68 ms for 200, x/y 0.69 vs
1.09, nine printed 0.015 vs 0.17). random(n) and random() draw the game's own
random bits through Set Switch to a random state and draw again past the
bound, so every number below n is as likely; 200 draws below a value cost
0.26 ms against eudplib's 1.84. An index that is more than a sum is worked
out first, which takes pick(list) and step 3's last row, xs[a >> 1].

Ledger: step 3 54 of 54, step 4 44 of 59 (left: sqrt, sin, cos, atan2,
u8, u16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1CM9pLuTuAn3mgTEkjgVg
…d u16

Step 4 is done: the ledger's 59 rows of numbers are all taken by a map of
our own.

A square root keeps the root at the place its bits end up, so it only gains
constants, and doubles the remainder instead; a shared compare trigger
steps its own next pointer into a pair of triggers laid out for each step.
Sines, cosines and arc tangents are written as statements the emitter
already makes, and the arc tangent's quotient takes 17 steps, not 47. Stores
into u8 and u16 stop at their top. In the lifted game, 200 of each against
the eudplib build: sqrt 0.39 vs 1.35 ms, of a whole number 0.65 vs 3.67,
sin and cos 1.09 vs 2.26, atan2 1.34 vs 2.30.

Also fixed: a whole number's square root below zero had the value range 0,
so comparing it was worked out wrong when the map was built, in both
builds; and a shift whose count comes to a constant was refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1CM9pLuTuAn3mgTEkjgVg
CI timed out on #43: the random programs with functions took 130 s there and
the ones on a stack 283 s, past the 120 s allowed, once step 4 put products,
quotients, square roots and sines in them (a program of twenty of them is
thousands of triggers to lay out and walk). A program now holds four at most;
each kind still comes in thousands of programs. Here they take 23 s and 47 s
(12 s and 25 s before step 4), about three times that on CI, so the limit is
300 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1CM9pLuTuAn3mgTEkjgVg
Two wrong builds: order() with orders.Move, and functions that keep a state
Own backend, step 4: numbers
Every condition and action whose fields are known when the map is built
is laid out as the game's own record (Cond.game, Act.record); its text
and sound file go among the map's strings, the script's clips into the
archive. memory() and setMemory() at a known address come with it.

- `!` of a condition is one condition where one says the opposite
  (at least n / at most n - 1, set / cleared); else a skip past.
- `&&` whose parts fit in one trigger is one trigger, not a branch each.
- An `if` of game actions and self-only writes under such conditions is
  one trigger: no branch, no put-back.
- Bring counted again after an action that makes or moves units.
- A `?:` of two booleans is taken (the last step-0 refusal).

Ledger: step 0 158/158, step 5 118/195. probes/own-game.ts: every line
ok both ways; 200 turns of conditions and actions 0.020 ms vs eudplib's
0.028, 200 of `!exactly` 0.021 vs 0.038.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwPs2Ya2X9rB9TYHijfoom
An action's field from a value is a place of its variable, kept by its
writes, so the action reads it where it is (eudplib copies it in before
every run). Type, kind and count share a dword: the place's constant is
what is not the value.

- A unit count: 255 at a time while more are left, then the rest, each
  half only where the range reaches it; 0 or below none.
- A made text fills a template of its own; the action's text names it.
- The game's count table thrown away after making or moving units, by
  Kill Unit of P12's type 228, in a program that asks Command or Bring:
  `command()` after Create Unit read the old count in both builds.
- A Transmission is first in a trigger of its own; the next trigger puts
  its started mark and the trigger's current action back, so each one
  in a loop shows (eudplib's: every other).
- max(n, 0) around an amount dropped where the ranges know it.

Ledger: step 5 154/195. probes/own-fields.ts: every line ok ours;
200 Set Deaths of a counter 0.014 ms vs eudplib's 0.021.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwPs2Ya2X9rB9TYHijfoom
Own backend: the game's conditions and actions (step 5, parts 1 and 2)
What a condition can be asked, read as a number, and the players' race,
slot and supply.

- One player's deaths, kills, ore or gas, and memory() at a known
  address, read where the game keeps them: a masked condition a bit,
  entered at the highest group of eight bits the number has (ore below
  65 536 in 16 bits; eudplib's f_dwread_epd reads 32).
- Anything else asked of the condition a bit at a time from the top,
  its amount a place of what is found so far: one trigger a bit.
- Race and slot as a byte of the player table; supply from its race's
  table, halved as the top bar shows it.

Ledger: step 5 179/195. probes/own-reads.ts: every line the same both
ways; 200 ore reads 0.129 ms vs 0.219, 200 unit counts 0.102 vs 0.418.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwPs2Ya2X9rB9TYHijfoom
`minerals(P1) >= cost` is Accumulate at least `cost`, the condition's
amount a place of `cost`: no read. `>` / `<` and their turned-around
forms shift the amount by one where the value's range allows.

probes/own-reads.ts: 200 of `minerals(P1) >= j * 10` 0.012 ms vs
eudplib's 0.248.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwPs2Ya2X9rB9TYHijfoom
- centerLocation: four Set Deaths into MRGN, each amount a place of
  the side, the counts thrown away after; without a size the sides read
  first. Actions with fields from values share a trigger.
- A print to P1–P8 or All Players: Display Text with each the current
  player in turn (no map settings needed); forces wait for step 7.
- rose() / once(): a latch per place in the script.
- hasLeft and force prints moved to step 7; Wait refused on purpose,
  its reason says why.

Ledger: step 5 195/198 (centre prints left). probes/own-locations.ts:
every line ok both ways; 200 moves 0.036 ms vs eudplib's 0.061.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwPs2Ya2X9rB9TYHijfoom
As eudplib's f_eprintln: with each target the current player, "Cannot
create more units" raised (Create Unit with the first free unit read,
taken away, put back), and on that player's computer alone the text
written into the game's line of errors — the template is the line.

Ledger: step 5 197/198; Wait refused on purpose. probes/own-step5.ts
for Remastered (centre print, Transmissions in a loop, a print to
every player, counts after Create Unit, a moved location).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwPs2Ya2X9rB9TYHijfoom
Own backend: the game read as a value, locations, players, centre prints (step 5, parts 3–5)
stats() and memory.* cells are at addresses known when built: a read is
the cell's bits of its dword, moved down (the group entry now on masked
fields too); a write a Set Deaths into the dword, masked for a byte,
word or bit, its amount a place of the value. A name is its text's
string number; a top speed goes through the type's flingy, the writes
naming their dwords by a place of it.

Ledger: step 6 50/50 (five local.* reads of step 12 among them).
probes/own-memory.ts: every line ok both ways; 200 word reads 0.092 ms
vs 0.140, 200 writes 0.040 vs 0.188.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwPs2Ya2X9rB9TYHijfoom
Own backend: the game's tables (step 6)
Every program of a script, each as every player it runs for: a run per program, one for each of
its players' slots for a per-player program, each with variables of its own but the shared ones.
The start-up is in every player's triggers and links each list to an entry of that player's own,
alone in it; a gate lets the walk into the runs from the first player still in the game, so a
player who leaves stops nobody else's programs (the game frees a leaver's list by its next
pointers: only the entry is reachable from it). Prints to a force and All Players go to the map's
slots; hasLeft is one condition. Played in the lifted game against eudplib's build: the probes
own-programs, fog-lib and own-leaver (a game of two, either leaving) the same frame for frame.

Also fixed: a program whose first statement jumps (let done = false) lost the jump in a map of our
own, and the walk came in at the true side.

Ledger: step 7 27 of 28 (a program owned by P12 refused on purpose).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwPs2Ya2X9rB9TYHijfoom
Own backend: programs, owners and players (step 7)
…8, part 1

A text that is one of a few the script wrote (a ?: of texts, a list read at a value, a variable
only ever given such texts, a slice or a character of one at a value) is a number in a map of our
own: its place in the script's pool of texts, which the map's strings hold in order from the next
free one, sorted as texts compare. Printed alone it is Display Text of the string its number names;
in a printed text, printed once a text or written whole into a field of its own (a pick part, a case
for each text); compared as numbers; its length, a search, a character or a slice worked out when
built for each text it can be, or read from a table of the one variable it depends on, within that
variable's range. Nothing of it is copied, where eudplib's build copies its bytes every time.

Ledger: step 8 18 of 46 (15 rows flipped, 3 new). probes/own-texts.ts the same in both builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwPs2Ya2X9rB9TYHijfoom
Own backend: texts known when built, chosen while the game runs (step 8, part 1)
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