Repository navigation
Conversation
……), 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>
…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
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
trigscript-4is 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 intomainwhen 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)
triggerMapsetting,python/trigscript.pylabels every trigger with the statement it came from, then logs where each one sits in the payload.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.compiler/allowlist.tschecks a run's trace against the allowlist, byte by byte under the mask, and names the address.scripts/game-check.mtsbuilds, plays and reports by line.A2: tests in the game
GameSim(compiler/gameSim.ts) gives a test the samesimas the simulator, driven through scm-sim's test handle:sim.program()reads a program's variables where the build says they are. With thevariableMapsetting the lowering logs each cell's address, andcompiler/gameVariables.tsreads them by the simulator's lookup rules.dist/gametest.jsruns the compile's JavaScript again beside the game. The plugin never loads it; it hands the text to the host.scripts/test-game.mtsprints the game and simulator results side by side.Simulator fix found by the game
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.World.gone(…, fought)), and three tests were corrected to the game's rule.Checked
probes/game-tests.tsandprobes/game-program.tspass in the game and the simulator.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