Skip to content

feat(runtime-scripts): a Package writes its Runtime Scripts in TypeScript - #402

Merged
Mathih13 merged 5 commits into
mainfrom
feat/320-runtime-script-typescript-build
Sep 3, 2026
Merged

Mathih13 merged 5 commits into
mainfrom
feat/320-runtime-script-typescript-build

Conversation

@Mathih13

@Mathih13 Mathih13 commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

A Runtime Script is Lua the Module runs on a gameplay event. The Module could already run one and a Script Artifact could already carry one, but nothing produced an artifact. The only example was hand-written JSON, so in practice a Package could not ship a script.

A Package now keeps its sources in its own folder, packages/<name>/scripts/, as .ts or as .lua. The sources sit inside the Package because they are its own content; a Datascript stays outside because only artifacts belong in a Package folder.

Packages may instead ship a source-free prebuilt Script Artifact when they ship authored Lua directly. It has no local author inputs or Build Identity. packages check still uses the authoritative parser and tracer, so this path needs neither Bun nor a Module wasm build.

datascripts/runtime-scripts/ holds the Runtime Script Toolchain: the pinned typescript-to-lua, a hand-maintained .d.ts for the Host API, the emitter, and the builder that writes one Script Artifact per Package. It is a Bun workspace of the existing Datascript project, so one bun install covers both and there is no second toolchain.

Declaring a script

Every file opens with its Script Directives, // in TypeScript and -- in Lua:

// @event on_cast_resolved
// @id 100200
// @enabled false

@event and @id are required, @priority defaults to 0 and @enabled to true. The identifier is written down rather than derived. It is durable — game_script keys on it — and a sorted file index would renumber every later script the moment an author added one alphabetically earlier. The name is derived, because <package>.<file stem> is the one part with an obvious answer.

A TypeScript script declares function script(): number | void. The emitted Lua ends with return script(), because TypeScript has no top-level return and a Script Answer is the chunk's return value. Each file compiles as its own program, so two scripts in one Package may both declare script.

The piccolo call shape

piccolo 0.3.3 passes an inline table constructor's element count as an extra argument when the constructor is a call's last argument, so f({7, 8, 9}) arrives as f(table, 3). Transpiler output meets that shape constantly. The emitter passes a trailing constructor through a one-parameter local, and the Module now pins the fault, the fix, and the fact that parentheses do not help.

What checks what

lyracore-delta-check routes each file on its own kind and traces both artifact kinds in one run, so a Runtime Script collision between two Packages fails the way a Claim Conflict does. packages build supplies the event catalogue from --print-events, so an unknown @event is refused while the diagnostic can still name the file.

The Fuel Budget is now sized against real transpiler output. datascripts/runtime-scripts/fuel-workload.lua is committed, the Module runs those exact bytes, and a Bun test recompiles the source and refuses a stale copy.

packages/fire_nova/scripts/ember_echo.ts is the worked example. It ships switched off: on_cast_resolved fires for every cast on a realm.

The CLI half is LyraCoreProject/lyracore-cli#55.


Implemented by Claude (model claude-opus-5) via Claude Code subagent.

Mathih13 and others added 5 commits September 3, 2026 17:18
A Runtime Script had no authoring path. The Module ran Lua a Script Artifact
carried, but nothing produced one, and the hand-written example artifact was
the only way a script reached a Shard.

A Package now keeps its sources in `packages/<name>/scripts/`, as `.ts` or as
`.lua`. `datascripts/runtime-scripts/` holds the Runtime Script Toolchain: the
pinned `typescript-to-lua`, the hand-maintained Host API typings, and the
builder that writes one Script Artifact per Package. The sources sit inside the
Package because they are its own content; a Datascript stays outside because
only artifacts belong in a Package folder.

Each file opens with its Script Directives. `@event` and `@id` are required,
`@priority` and `@enabled` have defaults. The identifier is written down rather
than derived: it is durable, and a file index would renumber a Package's
scripts the moment an author added one. The build refuses an unknown event, an
identifier outside the Package Script Range, and two scripts claiming one
identity.

piccolo 0.3.3 leaks an inline table constructor's element count as an extra
argument when the constructor is a call's last argument, and transpiler output
meets that shape constantly. The emitter passes a trailing constructor through
a one-parameter local. The Module pins the fault, the fix, and the fact that
parentheses do not help.

`lyracore-delta-check` now routes each file on its own `kind` and traces both
artifact kinds in one run, so a Runtime Script collision between two Packages
fails the same way a Claim Conflict does. The Host's Fuel Budget is sized
against real transpiler output: `datascripts/runtime-scripts/fuel-workload.lua`
is committed, the Module runs those exact bytes, and a Bun test refuses a stale
copy.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Mathih13
Mathih13 force-pushed the feat/320-runtime-script-typescript-build branch from f2630e5 to 0653e2f Compare September 3, 2026 15:19
@Mathih13
Mathih13 merged commit 9d1f0fb into main Sep 3, 2026
1 check passed
@Mathih13
Mathih13 deleted the feat/320-runtime-script-typescript-build branch September 3, 2026 15:19
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