Skip to content

TrigScript 4.0.0 - #3

Merged
jeany55 merged 363 commits into
mainfrom
trigscript-4
Oct 26, 2026
Merged

jeany55 merged 363 commits into
mainfrom
trigscript-4

Conversation

@jeany55

@jeany55 jeany55 commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

TrigScript 4: the whole of trigscript-4 into main, released as v4.0.0. The plan with an As built section for every slice is docs/v4-plan.md; the README's Coming from 3.12 says what changed for a script author.

The build is TrigScript's own. Programs are laid out into the saved map by the plugin itself (steps 1–17 of "the way off eudplib"), in under a second, with nothing to install; what the build does not take is a Problems warning and a failed step that saves the rest. The Python lowering and the eudplib hand-off are gone. Every row of the ledger (ledger/, 845 scripts) is taken, and every probe of the way was played in StarCraft: Remastered.

The game as the checker. Tests run in the lifted game beside the simulator (GameSim), each run's memory accesses are held against data/allowlist.json line by line, a frame's cost is weighed in Remastered's time, several seated copies are hashed for desyncs, and the debugger reads a program's variables from the game's memory.

The language. async/await, tasks and Promise.*; on.* events; functions as values and closures; try/catch/throw; Local<T> and sync(); fixed and Math; the game's tables and memory; per-player programs; sounds, pictures and videos imported into the map; the standard library (math, random, timers, signals, ui, cutscene, fog, screen, music); the build's own input (keys, clicks, the mouse, typed lines); bounds(location) and the unit fields Magenta's rows need.

For other plugins: the trigscript.build service; Magenta 0.12 builds its rows through it.

After the merge: CI commits dist/ on main, v4.0.0 is tagged on that commit, scm-js pins it (and drops the eudplib plugin from its defaults), Magenta 0.12.0 is tagged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5

@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 17, 2026 21:37
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>
TrigScript 4, B5: Local values, sync() and select()
…hat give records

A function kept as a value may now use variables of the function or block it was
written in: they live in an environment (a row of a pool of arrays), one for each
run of the block, reference counted and given back at the program's next wait.
The let of a for is one for each turn, as in JavaScript. IR 24 adds VarDecl.held.

Also: ++n, n--, n = v and n += v used as values; a function may give a record it
makes (return { inc, get }); xs[i] += n lowers to the game's Add action.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…any file, trigscript/ui and trigscript/console

Texts: trim, replace, replaceAll, toUpperCase, toLowerCase, lastIndexOf, split,
parseInt / Number / isNaN. Arrays of every kind: shift, unshift, splice, at,
lastIndexOf, join. A function declared (or a const arrow) at the top of any
file is callable from a program with its own values, and usable as a JSX
component; components get props as a record and write their lines into the
text they stand in. A const array or record that a function it is handed
writes is the program's. A function declared in a block sees that block.

Standard library: trigscript/ui (Meter, Panel, MenuList, DialogBox, LogLines,
choose, say, typewriter, log, clock, thousands) and trigscript/console (Screen:
clear, set, get, rect, line, blit, number, present on the minimap; ScreenView
in the text lines; sprite). The library may hold .tsx modules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It holds a Screen and nothing else; in JavaScript a console is console.log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generic classes and functions: a type parameter stands for what it is in the
instance or at the call (the checker wrapped to say so), a copy of a generic
function for each set of type arguments. A function handed to a task keeps
what it uses of the block it was written in, held by the program for the
task's slot. A script function that uses the game never runs when the script
is built. A field of what a call gives (v.rotate(a).x).

timers: every, after, Cooldown, Countdown, Stopwatch. signals: Signal<T>
with on, once, off, emit. random: chance, range, dice, pick, weighted,
shuffle, and a seeded Random (xorshift). math: lerp, remap, approach, wrap,
distances, angles, easing, Vec.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Played: Snake's board did not show. Its pixels are other players' units, and
the fog of war hid them; Tetris made Map Revealers over its board, the library
did not. Screen.reveal(player) does, and Snake calls it.

The view showed twice: each showing is printed, and the one before stayed
above until it faded. ui.lines now prints its view and then lines of a
space, eleven in all, so the one before goes out of sight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…; Space, not Enter

Played (round 2): it works. The board was a corner of a 96 x 96 minimap beside
minerals and terrain: probes/lib/screen-map.mts makes a 64 x 64 map of Space,
platform only along the bottom, and Snake's board (30 x 24) fills most of its
minimap. The screen's owners were in another order than TEXT_COLORS and the
probe's player colours (the snake showed red): now P3, P4, P5, P1, P6, P7, P8.
A screen's arrays are a fixed 2,048 cells, not pushed one at a time (the first
frame was 712k conditions and actions, now 434k). ui's choose and say take
Space (Enter opens the chat in a game of several players) and W / S beside
Up / Down.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The minimap draws an Overlord as a dot, and Overlords a tile apart do not fit
(the lifted game would not place most of them). It draws a building as its
footprint: new Screen(x, y, w, h, "solid") makes each pixel a Pylon, two tiles
square, so neighbours join into one block. Needs buildable ground:
screen-map.mts --ground. 320 Pylons on the grid in the lifted game, none moved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TrigScript 4: closures over locals, generics, and the ui, screen, timers, signals, random and math libraries
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A class the program needs to keep by reference - a variable or a field
that may be none, an array or a variable of a type several classes fit,
a variable given another instance, an array of instances inside an
instance, a Map or Set of them - is found by walking the program, and
the next walk keeps its instances as rows of the closures' pool, a
column for each field (shared by name and kind), the class in the pool's
function column. A reference is the row; calls through one that may be
of several classes ask the row's class. Only variables a wait may come
between their declaration and a use are counted now.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…umns; a new object's constructor under one test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e given whole; a number or nothing

p = p.add(q), r = { ... }, r = c ? a : b copy the fields; const q = p of a p
given whole is a copy, refused when a field of either is written. A class
whose fields only its constructors write is a value; one changed in place
and given whole goes by reference. number | undefined is a value and a flag:
=== undefined, ??, !, narrowing and truthiness read them; find() and a
Map's get() give one; a field, a row's or an object's, may be one.

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 for...of walks a generator at the top of a file, a generator method, and
an instance's *[Symbol.iterator](); yield* walks another generator or a
list there. A generator or an async function at the top of a file is the
script's own function now: called and started with the program's values.
sleep(frames(n)) and sleep(seconds(n)) of a number the game holds count
down a frame at a time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
stdlib/cutscene.ts: Camera (look, pan with easing, follow, shake), walk,
charge, walkAll, line (a talking portrait and the words in the middle of
the screen), play (a scene started as a task that a key skips).

Found on the way: a class from another file used only inside a function
at the top of a file was never declared for the program (its methods were
walked in the wrong file); a parameter's default naming a constant of its
file was not worked out for those functions; a unit variable the checker
narrowed to null was tested as a boolean.

probes/game-cutscene.ts passes in the lifted game and the simulator;
probes/cutscene.ts is a played probe: a scene, then a list of effects of
two classes.

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

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

A function narrowing its argument with typeof was inlined with both sides of a
c ? a : b, and the side its argument does not take read a number variable as a
text: text() and textOf() called each other until the stack ran out. The
ternary now folds a condition known when the script is built, as if did, and
a variable of the program is read as its own kind.

Date.now(), new Date(), Math.random() and performance.now() were worked out
when the map was built, silently. Inside a program, or a function of the
script's a program calls, they are refused with elapsed(),
memory.game.frame or random(n) named; at the top of a file they stay the
build's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TrigScript 4, B9: references and values, generators finished, and trigscript/cutscene
jeany55 and others added 24 commits October 24, 2026 17:28
…d, not 16

Since step 14 the build wrote the state flag two bytes late, so every map built our
way made its input carriers in the open and no key, click, sync or typed line reached
a program. The first play of Tetris found it; the step 12 probe answers every key
again, and game-key-tap and game-events-input pass in the game on the own build.
The test checks byte 14 set and byte 16 clear. The plan says what was found.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
…layers too

A run after the first had every @in. array renamed as its own, so only the first
program that read keys, clicks or typed lines ever heard them. The prefix rule the
events pass uses, a test holding a second program's reads to the first one's cells,
and the plan's account of the input probe's play.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
…e owner while the program sleeps

A unit handed to a died handler has the order Die, which the guard every field read
had asked to be otherwise: every field read as 0. A read asks now only that the place
holds the record. And the tasks' turn comes while the program sleeps, from the last
player's list with the current player theirs: a run with tasks sets it to its owner
before its wake, every frame, so that what a task shows, the owner sees. Two tests,
and the plan's account of the events map's play.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
The gate before the Python goes (step 16)
README: the language reference against the ledger, and a plainer read
… 4, step 17)

Deleted: python/trigscript.py (7,113 lines) and its embedding (scripts/embed-python.mts is
scripts/embed-stdlib.mts, the standard library's half), the IR's hand-off to it (serializeIr,
packTable, PACK_FROM, IR_VERSION and Program.version, the `graph` statement), the emitter's
hybrid mode (emitLoop, emitGraphs, islands, fixed-point calls, the `native` flag — emitBody is
the one way through), vendor/eudplib.ts and the service's contribution to the library's build,
the MSQC/chatEvent plugin sections of the input plan (and the location it reserved for them,
which the own build never needed), the `requires` of plugin-eudplib, tests/python.test.ts,
tests/eud-build.test.ts, scripts/drive-eud.mjs, the EUDPLIB=1 path of build-fixture.mts and
--eudplib of random-scripts.mts and gate-sweep.mts.

The build step now applies to every script with programs: one the emitter does not take is a
Problems warning while typing and a failed step on save, with the reason; a map with Magenta's
member is refused with the reason too (Magenta's eudplib build rewrites the strings section
the payload sits behind; Magenta on TrigScript's layout is the next slice).

Found on the way: the editor's compile never passed `emitter: false` (only the tests did), so
the own build was handed loops already turned into `graph` statements and refused every script
with a plain numeric loop — such maps had gone through eudplib with a warning since step 14.
Gone with the hybrid. A list read a bit at a time is laid out a dword a value (stride 1, not
the EUDVArray's 18).

The debugger, cost and allowlist wording says "the build's own triggers" where it said
eudplib's; the Korean follows. README and docs/ir.md updated; the IR carries no version.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
… plan's account, three tests

A line of 24 one-digit numbers (game-lowering's C line) was 290 bytes of template, which the game's 218-byte
text line cut short in the lifted game: a number's field is now 4, 8 or 12 bytes by what the value ranges know
of it (`fieldWords`), the digits routine's last dwords copied into it; the probe's eight tests pass in the game.

The plan's section "The Python goes (2026-10-25)": what went, what the build does now, the editor-compile
finding, and the game-*.ts tests gone through — game-tests, key-tap, events-input, local-players (with a guard
in scm-sim's differences(), 8852b7c on scm-sim-eud's branch) and lowering pass; game-events' per-player
listener (and a refusal in the events pass found with it), game-events-2's on.killed/on.order in the lifted game
and game-key-held's late frame are recorded open with what was learned. Three tests that pinned the IR version,
the serialised text or eudplib's name follow the deletion.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
The Python goes: TrigScript's own build is the only build (step 17)
… contribute their programs through

Magenta on our backend, part A (decided 2026-10-25: TrigScript is the library). `contract.d.ts` publishes the
service: `contribute({ id, label, applies, collect })` brings TrigScript source under a folder of the plugin's own
(`magenta/main.ts`) and names which files run as entries; `check(files, entries)` compiles it against the open map
without building, for the contributor's preflight; `onBuild` is the editor's events. The build step applies when
the script has programs or a contribution applies; the script and the contributions compile together
(`CompileOptions.entries`: files run as entries after the script's, before the test files) and every program goes
into one build. A contribution holds programs only; a failure in one is said in its name. The step 17 refusal of
Magenta's member stays for a Magenta that has not contributed and asks for the update. Plan section "Magenta on our
backend (2026-10-25)": the design, what Magenta renders, what the language still lacks, the parts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
…ADME test compiles

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
TrigScript as the library: the trigscript.build service (Magenta on our backend, part A)
… our backend, part C)

bounds(location) is a record of the four edges of a location as cells of the game's location table, read where the
location is now and written one edge at a time; the hoisting plan knows them as game cells in the call's, a const's
and a destructured form, and the simulator keeps them in its locations beside centerLocation(). A unit gains
buildTime, rank, matrix (written: 250 points for the frames), speed (read only), speedUpgrade, hasTarget and
inTransport, and hallucinated takes a write. Fourteen ledger rows, all taken; the own payload walked beside the
simulator for each.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
bounds(location) and the unit fields Magenta's rows need (Magenta on our backend, part C)
Remastered, Magenta probe 6 (2026-10-25): marines bitten on screen for ten seconds never read as under attack. The
attack-notify timer at +0x87 runs only when the "under attack" alert sounds; the last-event bytes (+0x8A timer, +0x8B
174 = was hit) are what BWAPI reads, true for about a second after a hit. The lifted-game view reads the same bytes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
…an one under a third burns

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
…mastered ends the game on the last-event bytes

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
…ten from them (Magenta's put-back row)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
Plan: Magenta on our backend, parts B and C merged, D built
underAttack said for what it is, buildTime's early finish noted
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
@jeany55 jeany55 mentioned this pull request Oct 26, 2026
jeany55 and others added 2 commits October 25, 2026 18:58
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01En4SHqafb1QFJ4xgbUkAe5
@jeany55
jeany55 marked this pull request as ready for review October 26, 2026 01:14
@jeany55 jeany55 changed the title TrigScript 4 TrigScript 4.0.0 Oct 26, 2026
@jeany55
jeany55 merged commit 729c7d0 into main Oct 26, 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