feat(compiler): #75 resolve the auto-spliced engine runtime from LUDIC_HOME
The compiler auto-splices runtime/native/* for any ECS game, but resolved it relative to the build CWD, then fell back to the package module root ($LUDIC_MODULES) — forcing every external project to copy/symlink the engine runtime into ludic_modules/. The runtime is part of the toolchain, not the project: do_import now resolves a runtime/... import that isn't found locally from $LUDIC_HOME (default: the compiler binary's dir — where cocoa.ll/audio.ll already come from), before the module root. So ludic_modules/ holds only third-party packages. In-repo builds are byte-identical (the runtime resolves locally there, so the $LUDIC_HOME fallback never fires; fixpoint holds). Verified by a hermetic test that builds an ECS game from an external CWD with no runtime/ or ludic_modules/ under it, resolving the runtime from LUDIC_HOME. Full suite 113/0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ad3be0c53c
commit
ab4546e365
5 changed files with 17240 additions and 17005 deletions
3
changes/runtime-ships-with-toolchain.md
Normal file
3
changes/runtime-ships-with-toolchain.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
bump: minor
|
||||
type: feat
|
||||
The engine runtime ships with the toolchain, not the project (#75). The compiler auto-splices `runtime/native/*` for any ECS game; a `runtime/...` import that is not found relative to the build is now resolved from the install root **`$LUDIC_HOME`** (default: the compiler binary's directory — where the platform `.ll` files already come from) *before* the package module root. So an external game that consumes the `ludic.*` packages no longer has to copy or symlink the engine runtime into its `ludic_modules/`; that directory holds only third-party packages, and the runtime is part of the toolchain install. In-repo builds are byte-identical (the runtime still resolves locally there, so the `$LUDIC_HOME` fallback never fires and the C-free bootstrap fixpoint is untouched).
|
||||
|
|
@ -94,7 +94,16 @@ The compiler resolves an import first relative to the importing file, then — f
|
|||
a non-absolute path that is not found — under the package module root
|
||||
(`$LUDIC_MODULES`, default `ludic_modules/`). So a fetched package's code is
|
||||
spliced into the build and its namespace becomes available exactly the way the
|
||||
built-in stdlib namespaces (Regex.\*, Grid.\*, …) are. Because Ludic compiles
|
||||
built-in stdlib namespaces (Regex.\*, Grid.\*, …) are.
|
||||
|
||||
The **engine runtime is the exception** (issue #75): the compiler auto-splices
|
||||
`runtime/native/*` for any ECS game, and that runtime ships with the *toolchain*,
|
||||
not the project. A `runtime/...` import that is not found relative to the build is
|
||||
resolved from the install root **`$LUDIC_HOME`** (default: the compiler binary's
|
||||
directory — the same place the platform `.ll` files come from), *before* the
|
||||
package module root. So an external game does not have to copy or symlink the
|
||||
engine runtime into its `ludic_modules/`; that directory holds only third-party
|
||||
packages. In-repo builds are unaffected — the runtime resolves locally there. Because Ludic compiles
|
||||
ahead-of-time, a source package is compiled *into* the consumer's binary — no
|
||||
ABI seam, and the whole-program guarantees (determinism, replay, `world_save`)
|
||||
still hold.
|
||||
|
|
|
|||
|
|
@ -662,9 +662,35 @@ function parse_test() -> Node {
|
|||
# materialised by `x get` at ludic_modules/git.workshopsoft.io/user/pkg/foo.ludic.
|
||||
# The fallback only fires when the local path does not exist, so every existing
|
||||
# import resolves exactly as before (the emitted IR is byte-identical).
|
||||
# true when `rel` is an engine-runtime splice path ("runtime/..."), as opposed to
|
||||
# a user or package import. Those ship with the toolchain, not the project (#75).
|
||||
function is_runtime_path(rel: pointer) -> bool {
|
||||
if rel[0] != 114 { return false } # r
|
||||
if rel[1] != 117 { return false } # u
|
||||
if rel[2] != 110 { return false } # n
|
||||
if rel[3] != 116 { return false } # t
|
||||
if rel[4] != 105 { return false } # i
|
||||
if rel[5] != 109 { return false } # m
|
||||
if rel[6] != 101 { return false } # e
|
||||
if rel[7] != 47 { return false } # /
|
||||
return true
|
||||
}
|
||||
|
||||
function do_import(rel: pointer) -> void {
|
||||
var full = path_join(cur_dir, rel)
|
||||
var src = read_file(full)
|
||||
# #75 — the engine runtime (runtime/native/*) ships with the toolchain, not the
|
||||
# project. When an auto-spliced runtime import is not found relative to the build
|
||||
# (an external game whose CWD is not the toolchain repo), resolve it from the
|
||||
# install root $LUDIC_HOME (default: the compiler binary's directory) — the same
|
||||
# place main.ludic finds cocoa.ll / audio.ll. This is tried before the package
|
||||
# module root, so $LUDIC_MODULES / ludic_modules holds only third-party packages.
|
||||
if (src == null) and (rel[0] != 47) and is_runtime_path(rel) {
|
||||
let home = ensure_slash(getenv_or("LUDIC_HOME", dir_of(arg(0))))
|
||||
let hc = home + rel
|
||||
let hs = read_file(hc)
|
||||
if (hs != null) { full = hc; src = hs }
|
||||
}
|
||||
if (src == null) and (rel[0] != 47) {
|
||||
let cand = ensure_slash(getenv_or("LUDIC_MODULES", "ludic_modules")) + rel
|
||||
let s2 = read_file(cand)
|
||||
|
|
|
|||
34186
selfhost/ludicc.seed.ll
34186
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
|
|
@ -41,6 +41,24 @@ function controller_case(path: pointer, keys: pointer, exp: pointer, label: poin
|
|||
if (got == exp) { ok(label) } else { bad2(label, `got [{got}] want [{exp}]`) }
|
||||
}
|
||||
|
||||
# #75 — an external project (its CWD is not the toolchain repo, with no runtime/
|
||||
# under it and no ludic_modules) builds an ECS game by resolving the engine runtime
|
||||
# from $LUDIC_HOME (the toolchain install), independent of the package module root.
|
||||
function runtime_home_case() -> void {
|
||||
let root = capture_line("pwd")
|
||||
let dir = "/tmp/x_ext75"
|
||||
shq(`rm -rf {dir} && mkdir -p {dir}`)
|
||||
write_file(`{dir}/game.ludic`, "program Ext {\n property Mob { hp: int = 0 }\n model M { Mob }\n entry {\n spawn M { Mob { hp: 7 } }\n let pm = World.prop_id(\"Mob\")\n let e = World.query_next(pm, 0)\n print(World.get(e, pm, World.field_id(pm, \"hp\")))\n }\n}\n")
|
||||
let lbl = "runtime ships with the toolchain, resolved from LUDIC_HOME (#75)"
|
||||
# compiled from the external CWD; there is deliberately no runtime/ or
|
||||
# ludic_modules/ under {dir}, so the engine runtime can only come from LUDIC_HOME.
|
||||
if not shq(`cd {dir} && LUDIC_HOME={root} {root}/bin/ludicc game.ludic -o {dir}/game 2>{dir}/err`) {
|
||||
bad2(lbl, capture_line(`grep -i 'error|cannot open' {dir}/err | head -1`)); return
|
||||
}
|
||||
let got = capture_line(`{dir}/game < /dev/null`)
|
||||
if (got == "7") { ok(lbl) } else { bad2(lbl, `got [{got}] want [7]`) }
|
||||
}
|
||||
|
||||
# a "does it still compile" smoke test (parse -> lower -> link), no run
|
||||
function qsmoke(path: pointer) -> void {
|
||||
let nm = flat(path)
|
||||
|
|
@ -241,6 +259,7 @@ function cmd_test() -> int {
|
|||
feat_case("library/light_tiers", "", "1 1 1 1 1 1 1 1 1", "light_tiers.ludic (Light spot/falloff/soft/gel/normal/time_of_day — render-quality tiers 3-4; issue #49)")
|
||||
feat_case("library/physics", "", "184 1 1 284 1 1 50 51 104 1 134 1 120 0 1 1 1 0 1 1 1", "physics.ludic (Body + Collider + esys_move: gravity/land, wall stop, sub-pixel, entity platform, one-way, triggers — entity broadphase; issue #65)")
|
||||
feat_case("library/physics_tiles", "", "112 1 80 1 48 1 60", "physics_tiles.ludic (esys_move tile-grid broadphase: tile floor/wall + one-way tile land-from-above / rise-through; issue #65)")
|
||||
runtime_home_case() # #75: engine runtime resolves from LUDIC_HOME, not the project
|
||||
# builtin gameplay controllers, consumed as in-repo packages (issues #57-#61)
|
||||
controller_case("library/core_components", "", "110 30 184 1", "core_components.ludic (ludic.core #77: canonical Position/Body/Collider imported from a package, driving esys_move, composed with a game Health component)")
|
||||
controller_case("library/sprite_render", "q", "1 0", "sprite_render.ludic (ludic.core #85: engine sprite-render system auto-draws a Sprite entity from its Position; respects hidden)")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue