# emit_world.ludic — the generated reflection ABI (the "world table" a mod reads/writes entity state by name through), the per-frame tick helpers, and the synthesized @main. Split out of emit_game.ludic (concern: runtime world/entry synthesis, vs. emit_game.ludic's system/scene/event lowering). # EV2 — the world table: a generated reflection ABI so a mod reads and writes # entity state *by name*, without having compiled against the game. This is the # "game table" that lets a modding layer be ported in. Generated from the # compile-time schema, so it never drifts. Emitted only for an ECS program that # also declares events (a modding program), so event-free games stay byte-exact. # # i32 ludic_prop_id(name) property name -> stable id (-1 = none) # i32 ludic_field_id(prop, name) field name within a property -> index # i64 ludic_get(entity, prop, field) read a field (sign-extended to i64) # void ludic_set(entity, prop, field, i64) write a field (truncated to i32) # i32 ludic_has(entity, prop) does the entity have the property? # # First cut: integer component fields (the common case — hp, x, amount). Property # ids are assignment order in the source; field ids are declaration order. function emit_world_table() -> void { let me = itoa(MAX_ENT) # EV7 — schema opening: a mod can register a brand-new component at runtime. # Compile-time components take prop ids 0..NC-1; mod-defined ones take NC.. and # live in these parallel registries (fixed capacity 32). Storage is a flat # malloc'd [MAX_ENT x nfields x i32] with a parallel has-flag array. get/set/has # and prop_id fall through to this table for a prop id >= NC. var ncomp = 0 var ci0 = 0 while ci0 < len(prog) { if prog[ci0].kind == N_COMP { ncomp += 1 }; ci0 += 1 } let NC = itoa(ncomp) emith("@dyn_count = global i32 0\n") emith("@dynS = global [32 x ptr] zeroinitializer\n") # storage base per dyn component emith("@dynH = global [32 x ptr] zeroinitializer\n") # has-flag array per dyn component emith("@dynF = global [32 x i32] zeroinitializer\n") # field count per dyn component emith("@dynName = global [32 x ptr] zeroinitializer\n") # name per dyn component # ludic_prop_id(name): strcmp against each property's name constant emit("define i32 @ludic_prop_id(ptr %name) {\nentry:\n") var k = 0 var i = 0 while i < len(prog) { if prog[i].kind == N_COMP { let sc = emit_str_const(prog[i].s); let sk = itoa(k) emit(" %c"); emit(sk); emit(" = call i32 @strcmp(ptr %name, ptr "); emit(sc); emit(")\n") emit(" %e"); emit(sk); emit(" = icmp eq i32 %c"); emit(sk); emit(", 0\n") emit(" br i1 %e"); emit(sk); emit(", label %h"); emit(sk); emit(", label %n"); emit(sk); emit("\n") emit("h"); emit(sk); emit(":\n ret i32 "); emit(sk); emit("\n") emit("n"); emit(sk); emit(":\n") k += 1 } i += 1 } # EV7: not a compile-time component — search the dynamic (mod-registered) names emit(" %dpi = alloca i32\n store i32 0, ptr %dpi\n br label %dpl\n") emit("dpl:\n %di = load i32, ptr %dpi\n %dn = load i32, ptr @dyn_count\n %dg = icmp slt i32 %di, %dn\n br i1 %dg, label %dpb, label %dpnone\n") emit("dpb:\n %dnp = getelementptr inbounds [32 x ptr], ptr @dynName, i32 0, i32 %di\n %dname = load ptr, ptr %dnp\n %dcmp = call i32 @strcmp(ptr %name, ptr %dname)\n %deq = icmp eq i32 %dcmp, 0\n br i1 %deq, label %dphit, label %dpnext\n") emit("dphit:\n %drid = add i32 %di, "); emit(NC); emit("\n ret i32 %drid\n") emit("dpnext:\n %di1 = add i32 %di, 1\n store i32 %di1, ptr %dpi\n br label %dpl\n") emit("dpnone:\n ret i32 -1\n}\n\n") # ludic_field_id(prop, name): within the matched property, strcmp each field name emit("define i32 @ludic_field_id(i32 %p, ptr %name) {\nentry:\n") k = 0; i = 0 while i < len(prog) { if prog[i].kind == N_COMP { let c = prog[i]; let sk = itoa(k) emit(" %pm"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n") emit(" br i1 %pm"); emit(sk); emit(", label %pk"); emit(sk); emit(", label %pn"); emit(sk); emit("\n") emit("pk"); emit(sk); emit(":\n") var f = 0 while f < len(c.kids) { let fc = emit_str_const(c.kids[f].s); let fk = `{sk}_{itoa(f)}` emit(" %fc"); emit(fk); emit(" = call i32 @strcmp(ptr %name, ptr "); emit(fc); emit(")\n") emit(" %fe"); emit(fk); emit(" = icmp eq i32 %fc"); emit(fk); emit(", 0\n") emit(" br i1 %fe"); emit(fk); emit(", label %fh"); emit(fk); emit(", label %fn"); emit(fk); emit("\n") emit("fh"); emit(fk); emit(":\n ret i32 "); emit(itoa(f)); emit("\n") emit("fn"); emit(fk); emit(":\n") f += 1 } emit(" ret i32 -1\n") emit("pn"); emit(sk); emit(":\n") k += 1 } i += 1 } emit(" ret i32 -1\n}\n\n") # ludic_get / ludic_set / ludic_has dispatch prop -> @S_/@H_ storage; the field # address is slot + field*4 (integer fields). # ludic_get/ludic_set dispatch prop -> component storage, then the field id to a # constant struct GEP (so mixed layouts and ptr/byte fields are addressed # correctly, not assumed 4-byte). Values cross the ABI as i64: int/bool/fixed # sign-extend, byte zero-extends, ptr round-trips through ptrtoint/inttoptr. emit("define i64 @ludic_get(i32 %e, i32 %p, i32 %f) {\nentry:\n") k = 0; i = 0 while i < len(prog) { if prog[i].kind == N_COMP { let c = prog[i]; let cn = c.s; let sk = itoa(k) emit(" %m"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n") emit(" br i1 %m"); emit(sk); emit(", label %g"); emit(sk); emit(", label %gn"); emit(sk); emit("\n") emit("g"); emit(sk); emit(":\n") let eb1 = ecs_base(`S_{cn}`) emit(" %s"); emit(sk); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr "); emit(eb1); emit(", i32 %e\n") var fj = 0 while fj < len(c.kids) { let ft = llty(c.kids[fj].ty); let fk = `{sk}_{itoa(fj)}` emit(" %gm"); emit(fk); emit(" = icmp eq i32 %f, "); emit(itoa(fj)); emit("\n") emit(" br i1 %gm"); emit(fk); emit(", label %gf"); emit(fk); emit(", label %gk"); emit(fk); emit("\n") emit("gf"); emit(fk); emit(":\n") emit(" %ga"); emit(fk); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr %s"); emit(sk); emit(", i32 0, i32 "); emit(itoa(fj)); emit("\n") if (ft == "ptr") { emit(" %gl"); emit(fk); emit(" = load ptr, ptr %ga"); emit(fk); emit("\n") emit(" %gr"); emit(fk); emit(" = ptrtoint ptr %gl"); emit(fk); emit(" to i64\n ret i64 %gr"); emit(fk); emit("\n") } else { if (ft == "i8") { emit(" %gl"); emit(fk); emit(" = load i8, ptr %ga"); emit(fk); emit("\n") emit(" %gr"); emit(fk); emit(" = zext i8 %gl"); emit(fk); emit(" to i64\n ret i64 %gr"); emit(fk); emit("\n") } else { emit(" %gl"); emit(fk); emit(" = load i32, ptr %ga"); emit(fk); emit("\n") emit(" %gr"); emit(fk); emit(" = sext i32 %gl"); emit(fk); emit(" to i64\n ret i64 %gr"); emit(fk); emit("\n") } } emit("gk"); emit(fk); emit(":\n") fj += 1 } emit(" ret i64 0\n") emit("gn"); emit(sk); emit(":\n") k += 1 } i += 1 } # EV7: prop id >= NC -> a mod-registered component; index its flat storage emit(" %gdyn = sub i32 %p, "); emit(NC); emit("\n") emit(" %gdlo = icmp sge i32 %gdyn, 0\n %gdc = load i32, ptr @dyn_count\n %gdhi = icmp slt i32 %gdyn, %gdc\n %gdok = and i1 %gdlo, %gdhi\n br i1 %gdok, label %gdyng, label %gdnone\n") emit("gdyng:\n %gsp = getelementptr inbounds [32 x ptr], ptr @dynS, i32 0, i32 %gdyn\n %gbase = load ptr, ptr %gsp\n") emit(" %gfp = getelementptr inbounds [32 x i32], ptr @dynF, i32 0, i32 %gdyn\n %gnf = load i32, ptr %gfp\n %grow = mul i32 %e, %gnf\n %gidx = add i32 %grow, %f\n") emit(" %gaddr = getelementptr inbounds i32, ptr %gbase, i32 %gidx\n %gv = load i32, ptr %gaddr\n %gr = sext i32 %gv to i64\n ret i64 %gr\n") emit("gdnone:\n ret i64 0\n}\n\n") emit("define void @ludic_set(i32 %e, i32 %p, i32 %f, i64 %val) {\nentry:\n") k = 0; i = 0 while i < len(prog) { if prog[i].kind == N_COMP { let c = prog[i]; let cn = c.s; let sk = itoa(k) emit(" %m"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n") emit(" br i1 %m"); emit(sk); emit(", label %g"); emit(sk); emit(", label %gn"); emit(sk); emit("\n") emit("g"); emit(sk); emit(":\n") let eb2 = ecs_base(`S_{cn}`) emit(" %s"); emit(sk); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr "); emit(eb2); emit(", i32 %e\n") var fj = 0 while fj < len(c.kids) { let ft = llty(c.kids[fj].ty); let fk = `{sk}_{itoa(fj)}` emit(" %sm"); emit(fk); emit(" = icmp eq i32 %f, "); emit(itoa(fj)); emit("\n") emit(" br i1 %sm"); emit(fk); emit(", label %sf"); emit(fk); emit(", label %sk"); emit(fk); emit("\n") emit("sf"); emit(fk); emit(":\n") emit(" %sa"); emit(fk); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr %s"); emit(sk); emit(", i32 0, i32 "); emit(itoa(fj)); emit("\n") if (ft == "ptr") { emit(" %sp"); emit(fk); emit(" = inttoptr i64 %val to ptr\n") emit(" store ptr %sp"); emit(fk); emit(", ptr %sa"); emit(fk); emit("\n ret void\n") } else { if (ft == "i8") { emit(" %sb"); emit(fk); emit(" = trunc i64 %val to i8\n") emit(" store i8 %sb"); emit(fk); emit(", ptr %sa"); emit(fk); emit("\n ret void\n") } else { emit(" %sw"); emit(fk); emit(" = trunc i64 %val to i32\n") emit(" store i32 %sw"); emit(fk); emit(", ptr %sa"); emit(fk); emit("\n ret void\n") } } emit("sk"); emit(fk); emit(":\n") fj += 1 } emit(" ret void\n") emit("gn"); emit(sk); emit(":\n") k += 1 } i += 1 } # EV7: prop id >= NC -> a mod-registered component emit(" %sdyn = sub i32 %p, "); emit(NC); emit("\n") emit(" %sdlo = icmp sge i32 %sdyn, 0\n %sdc = load i32, ptr @dyn_count\n %sdhi = icmp slt i32 %sdyn, %sdc\n %sdok = and i1 %sdlo, %sdhi\n br i1 %sdok, label %sdyng, label %sdnone\n") emit("sdyng:\n %ssp = getelementptr inbounds [32 x ptr], ptr @dynS, i32 0, i32 %sdyn\n %sbase = load ptr, ptr %ssp\n") emit(" %sfp = getelementptr inbounds [32 x i32], ptr @dynF, i32 0, i32 %sdyn\n %snf = load i32, ptr %sfp\n %srow = mul i32 %e, %snf\n %sidx = add i32 %srow, %f\n") emit(" %saddr = getelementptr inbounds i32, ptr %sbase, i32 %sidx\n %sv = trunc i64 %val to i32\n store i32 %sv, ptr %saddr\n ret void\n") emit("sdnone:\n ret void\n}\n\n") emit("define i32 @ludic_has(i32 %e, i32 %p) {\nentry:\n") k = 0; i = 0 while i < len(prog) { if prog[i].kind == N_COMP { let cn = prog[i].s; let sk = itoa(k) emit(" %m"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n") emit(" br i1 %m"); emit(sk); emit(", label %g"); emit(sk); emit(", label %gn"); emit(sk); emit("\n") emit("g"); emit(sk); emit(":\n") let eb3 = ecs_base(`H_{cn}`) emit(" %hp"); emit(sk); emit(" = getelementptr inbounds i8, ptr "); emit(eb3); emit(", i32 %e\n") emit(" %hv"); emit(sk); emit(" = load i8, ptr %hp"); emit(sk); emit("\n") emit(" %hr"); emit(sk); emit(" = zext i8 %hv"); emit(sk); emit(" to i32\n ret i32 %hr"); emit(sk); emit("\n") emit("gn"); emit(sk); emit(":\n") k += 1 } i += 1 } # EV7: prop id >= NC -> a mod-registered component's has-flag array emit(" %hdyn = sub i32 %p, "); emit(NC); emit("\n") emit(" %hdlo = icmp sge i32 %hdyn, 0\n %hdc = load i32, ptr @dyn_count\n %hdhi = icmp slt i32 %hdyn, %hdc\n %hdok = and i1 %hdlo, %hdhi\n br i1 %hdok, label %hdyng, label %hdnone\n") emit("hdyng:\n %hhp = getelementptr inbounds [32 x ptr], ptr @dynH, i32 0, i32 %hdyn\n %hh = load ptr, ptr %hhp\n %hslot = getelementptr inbounds i8, ptr %hh, i32 %e\n %hval = load i8, ptr %hslot\n %hres = zext i8 %hval to i32\n ret i32 %hres\n") emit("hdnone:\n ret i32 0\n}\n\n") # ludic_register_prop(name, nfields) -> prop id — a mod declares a NEW component. # Allocates flat [MAX_ENT x nfields x i32] storage + a MAX_ENT has-flag array, # zeroed. The returned id works with get/set/has/attach exactly like a built-in. emit("define i32 @ludic_register_prop(ptr %name, i32 %nfields) {\nentry:\n") emit(" %dc = load i32, ptr @dyn_count\n %full = icmp slt i32 %dc, 32\n br i1 %full, label %do, label %rej\n") emit("do:\n %rcap = load i32, ptr @L_cap\n %nf4 = mul i32 %nfields, 4\n %sz = mul i32 %nf4, %rcap\n %szl = sext i32 %sz to i64\n") emit_site("ecs mod store") emit(" %buf = call ptr @lp_malloc(i64 %szl)\n call ptr @memset(ptr %buf, i32 0, i64 %szl)\n") emit(" %sp = getelementptr inbounds [32 x ptr], ptr @dynS, i32 0, i32 %dc\n store ptr %buf, ptr %sp\n") emit_site("ecs mod flags") emit(" %hcap = sext i32 %rcap to i64\n %hbuf = call ptr @lp_malloc(i64 %hcap)\n store i32 0, ptr @lp_site\n call ptr @memset(ptr %hbuf, i32 0, i64 %hcap)\n") emit(" %hp = getelementptr inbounds [32 x ptr], ptr @dynH, i32 0, i32 %dc\n store ptr %hbuf, ptr %hp\n") emit(" %fp = getelementptr inbounds [32 x i32], ptr @dynF, i32 0, i32 %dc\n store i32 %nfields, ptr %fp\n") emit(" %np = getelementptr inbounds [32 x ptr], ptr @dynName, i32 0, i32 %dc\n store ptr %name, ptr %np\n") emit(" %id = add i32 %dc, "); emit(NC); emit("\n %dc1 = add i32 %dc, 1\n store i32 %dc1, ptr @dyn_count\n ret i32 %id\n") emit("rej:\n ret i32 -1\n}\n\n") # ludic_attach_dyn / ludic_detach_dyn(entity, prop) — set/clear a mod-registered # component's has-flag on an entity (the dynamic analogue of attach/detach). emit("define void @ludic_attach_dyn(i32 %e, i32 %p) {\nentry:\n") emit(" %dyn = sub i32 %p, "); emit(NC); emit("\n %hp = getelementptr inbounds [32 x ptr], ptr @dynH, i32 0, i32 %dyn\n %h = load ptr, ptr %hp\n %slot = getelementptr inbounds i8, ptr %h, i32 %e\n store i8 1, ptr %slot\n ret void\n}\n\n") emit("define void @ludic_detach_dyn(i32 %e, i32 %p) {\nentry:\n") emit(" %dyn = sub i32 %p, "); emit(NC); emit("\n %hp = getelementptr inbounds [32 x ptr], ptr @dynH, i32 0, i32 %dyn\n %h = load ptr, ptr %hp\n %slot = getelementptr inbounds i8, ptr %h, i32 %e\n store i8 0, ptr %slot\n ret void\n}\n\n") # ludic_entity_count / ludic_kind / ludic_model_id — a mod scans the world and # identifies each entity's model, then reads/writes it with get/set/has above. emit("define i32 @ludic_entity_count() {\nentry:\n %n = load i32, ptr @L_entc\n ret i32 %n\n}\n\n") emit("define i32 @ludic_kind(i32 %e) {\nentry:\n") let eb4 = ecs_base("L_kind") emit(" %kp = getelementptr inbounds i32, ptr "); emit(eb4); emit(", i32 %e\n") emit(" %k = load i32, ptr %kp\n ret i32 %k\n}\n\n") emit("define i32 @ludic_model_id(ptr %name) {\nentry:\n") k = 0; i = 0 while i < len(prog) { if prog[i].kind == N_ARCH { let sc = emit_str_const(prog[i].s); let sk = itoa(k) emit(" %mdc"); emit(sk); emit(" = call i32 @strcmp(ptr %name, ptr "); emit(sc); emit(")\n") emit(" %mde"); emit(sk); emit(" = icmp eq i32 %mdc"); emit(sk); emit(", 0\n") emit(" br i1 %mde"); emit(sk); emit(", label %mdh"); emit(sk); emit(", label %mdn"); emit(sk); emit("\n") emit("mdh"); emit(sk); emit(":\n ret i32 "); emit(itoa(find_arch_id(prog[i].s))); emit("\n") emit("mdn"); emit(sk); emit(":\n") k += 1 } i += 1 } emit(" ret i32 -1\n}\n\n") # ludic_spawn(model_id) -> entity — a mod creates a new entity. Each model gets a # @ludic_spawn_ that reuses the compiler's own spawn lowering (alloc, kind, # component defaults, @OnSpawn, and the model__spawn event), so a mod-spawned # entity is indistinguishable from one born in source. A dispatcher routes the id. i = 0 while i < len(prog) { if prog[i].kind == N_ARCH { let m = prog[i].s ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0; nself = 0 ret_ty = "int" let fbody = buf_new() falloc = buf_new() let saved = code code = fbody let syn = node(S_SPAWN); syn.s = m # a defaults-only spawn of model m let se = emit_spawn(syn) emit(" ret i32 "); emit(se); emit("\n") code = saved emit("define i32 @ludic_spawn_"); emit(m); emit("() {\nentry:\n") emit(buf_str(falloc)) emit(buf_str(fbody)) emit("}\n\n") } i += 1 } emit("define i32 @ludic_spawn(i32 %m) {\nentry:\n") k = 0; i = 0 while i < len(prog) { if prog[i].kind == N_ARCH { let m = prog[i].s; let sk = itoa(k) emit(" %sm"); emit(sk); emit(" = icmp eq i32 %m, "); emit(itoa(find_arch_id(m))); emit("\n") emit(" br i1 %sm"); emit(sk); emit(", label %sh"); emit(sk); emit(", label %sn"); emit(sk); emit("\n") emit("sh"); emit(sk); emit(":\n %sr"); emit(sk); emit(" = call i32 @ludic_spawn_"); emit(m); emit("()\n ret i32 %sr"); emit(sk); emit("\n") emit("sn"); emit(sk); emit(":\n") k += 1 } i += 1 } emit(" ret i32 -1\n}\n\n") # ludic_query_next(prop_id, from) -> the next live entity (>= from) that has the # property, or -1. A mod iterates: for (e = query_next(p, 0); e >= 0; e = # query_next(p, e+1)). Reuses ludic_has for the membership test. emit("define i32 @ludic_query_next(i32 %p, i32 %from) {\nentry:\n") emit(" %n = load i32, ptr @L_entc\n br label %loop\n") emit("loop:\n %e = phi i32 [ %from, %entry ], [ %e1, %cont ]\n") emit(" %go = icmp slt i32 %e, %n\n br i1 %go, label %body, label %none\n") emit("body:\n") let eb5 = ecs_base("L_alive") emit(" %ap = getelementptr inbounds i32, ptr "); emit(eb5); emit(", i32 %e\n") emit(" %al = load i32, ptr %ap\n %isa = icmp ne i32 %al, 0\n br i1 %isa, label %chk, label %cont\n") emit("chk:\n %h = call i32 @ludic_has(i32 %e, i32 %p)\n %hit = icmp ne i32 %h, 0\n br i1 %hit, label %hitb, label %cont\n") emit("hitb:\n ret i32 %e\n") emit("cont:\n %e1 = add i32 %e, 1\n br label %loop\n") emit("none:\n ret i32 -1\n}\n\n") emit_world_reflect_enum() } # EV8 — schema enumeration: walk the property/field metadata by index, not just # by name. Powers the Reflect.* namespace (auto-serialization, debug inspectors) # so a mod can list every component and field without knowing them up front. # Compile-time props take ids 0..NC-1; ids >= NC are mod-registered (dynamic), # whose field names are unknown (i32 fields) so they report "" / "int". function emit_world_reflect_enum() -> void { let empty = emit_str_const("") let tint = emit_str_const("int") # count the compile-time components (== the first dynamic prop id) var ncomp = 0 var i = 0 while i < len(prog) { if prog[i].kind == N_COMP { ncomp += 1 }; i += 1 } let NC = itoa(ncomp) # ludic_prop_count() -> total property count (compile-time + mod-registered) emit("define i32 @ludic_prop_count() {\nentry:\n") emit(" %d = load i32, ptr @dyn_count\n %n = add i32 %d, "); emit(NC); emit("\n ret i32 %n\n}\n\n") # ludic_prop_name(i) -> the i-th property's name (or "" if out of range) emit("define ptr @ludic_prop_name(i32 %i) {\nentry:\n") var k = 0; i = 0 while i < len(prog) { if prog[i].kind == N_COMP { let sc = emit_str_const(prog[i].s); let sk = itoa(k) emit(" %pe"); emit(sk); emit(" = icmp eq i32 %i, "); emit(sk); emit("\n") emit(" br i1 %pe"); emit(sk); emit(", label %ph"); emit(sk); emit(", label %pn"); emit(sk); emit("\n") emit("ph"); emit(sk); emit(":\n ret ptr "); emit(sc); emit("\n") emit("pn"); emit(sk); emit(":\n") k += 1 } i += 1 } emit(" %dyn = sub i32 %i, "); emit(NC); emit("\n %dc = load i32, ptr @dyn_count\n") emit(" %ib = icmp uge i32 %dyn, %dc\n br i1 %ib, label %pbad, label %pdyn\n") emit("pdyn:\n %np = getelementptr inbounds [32 x ptr], ptr @dynName, i32 0, i32 %dyn\n %nm = load ptr, ptr %np\n ret ptr %nm\n") emit("pbad:\n ret ptr "); emit(empty); emit("\n}\n\n") # ludic_field_count(prop) -> the number of fields of a property emit("define i32 @ludic_field_count(i32 %p) {\nentry:\n") k = 0; i = 0 while i < len(prog) { if prog[i].kind == N_COMP { let c = prog[i]; let sk = itoa(k) emit(" %fce"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n") emit(" br i1 %fce"); emit(sk); emit(", label %fch"); emit(sk); emit(", label %fcn"); emit(sk); emit("\n") emit("fch"); emit(sk); emit(":\n ret i32 "); emit(itoa(len(c.kids))); emit("\n") emit("fcn"); emit(sk); emit(":\n") k += 1 } i += 1 } emit(" %dyn = sub i32 %p, "); emit(NC); emit("\n %dc = load i32, ptr @dyn_count\n") emit(" %ib = icmp uge i32 %dyn, %dc\n br i1 %ib, label %fcbad, label %fcdyn\n") emit("fcdyn:\n %fp = getelementptr inbounds [32 x i32], ptr @dynF, i32 0, i32 %dyn\n %fn = load i32, ptr %fp\n ret i32 %fn\n") emit("fcbad:\n ret i32 0\n}\n\n") # ludic_field_name(prop, f) -> the f-th field's name of a property (or "") emit("define ptr @ludic_field_name(i32 %p, i32 %f) {\nentry:\n") k = 0; i = 0 while i < len(prog) { if prog[i].kind == N_COMP { let c = prog[i]; let sk = itoa(k) emit(" %fne"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n") emit(" br i1 %fne"); emit(sk); emit(", label %fnk"); emit(sk); emit(", label %fnn"); emit(sk); emit("\n") emit("fnk"); emit(sk); emit(":\n") var f = 0 while f < len(c.kids) { let fc = emit_str_const(c.kids[f].s); let fk = `{sk}_{itoa(f)}` emit(" %fnfe"); emit(fk); emit(" = icmp eq i32 %f, "); emit(itoa(f)); emit("\n") emit(" br i1 %fnfe"); emit(fk); emit(", label %fnfh"); emit(fk); emit(", label %fnfn"); emit(fk); emit("\n") emit("fnfh"); emit(fk); emit(":\n ret ptr "); emit(fc); emit("\n") emit("fnfn"); emit(fk); emit(":\n") f += 1 } emit(" ret ptr "); emit(empty); emit("\n") emit("fnn"); emit(sk); emit(":\n") k += 1 } i += 1 } emit(" ret ptr "); emit(empty); emit("\n}\n\n") # ludic_field_type(prop, f) -> the f-th field's type name (or "int" fallback) emit("define ptr @ludic_field_type(i32 %p, i32 %f) {\nentry:\n") k = 0; i = 0 while i < len(prog) { if prog[i].kind == N_COMP { let c = prog[i]; let sk = itoa(k) emit(" %fte"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n") emit(" br i1 %fte"); emit(sk); emit(", label %ftk"); emit(sk); emit(", label %ftn"); emit(sk); emit("\n") emit("ftk"); emit(sk); emit(":\n") var f = 0 while f < len(c.kids) { let tc = emit_str_const(c.kids[f].ty); let fk = `{sk}_{itoa(f)}` emit(" %ftfe"); emit(fk); emit(" = icmp eq i32 %f, "); emit(itoa(f)); emit("\n") emit(" br i1 %ftfe"); emit(fk); emit(", label %ftfh"); emit(fk); emit(", label %ftfn"); emit(fk); emit("\n") emit("ftfh"); emit(fk); emit(":\n ret ptr "); emit(tc); emit("\n") emit("ftfn"); emit(fk); emit(":\n") f += 1 } emit(" ret ptr "); emit(tint); emit("\n") emit("ftn"); emit(sk); emit(":\n") k += 1 } i += 1 } emit(" ret ptr "); emit(tint); emit("\n}\n\n") } # N5 — the drivable sim (NETWORKING-DESIGN §5). The per-frame phases the auto-loop # runs are also exposed as callables, so a game that owns its `entry` loop can # drive the simulation itself (for prediction/rollback, replay, headless tests, or # AI). tick_fixed() runs the sim phases; tick_render() runs Render. function emit_tick_helpers() -> void { emit("define void @L_tick_fixed() {\nentry:\n") ll_t = 0; ll_lbl = 0 emit_calls_for_phase("Input") emit_calls_for_phase("FixedUpdate") emit_calls_for_phase("Update") emit_calls_for_phase("LateUpdate") emit(" ret void\n}\n\n") emit("define void @L_tick_render() {\nentry:\n") ll_t = 0; ll_lbl = 0 emit_calls_for_phase("Render") emit_calls_for_phase("Overlay") emit(" ret void\n}\n\n") } # system functions + lifecycle hooks + the drivable tick helpers — shared by the # auto-loop game (emit_game_main) and an entry-driven game that owns its own loop. function emit_game_defs() -> void { emit_system_registry() # #64: @ludic_register_system + the registry globals var i = 0 while i < len(prog) { if prog[i].kind == N_SYS { emit_system_fn(prog[i]) }; i += 1 } emit_despawn_hooks() emit_scene_hooks() if has_countdowns() { emit_countdown_system() } # `countdown` fields (see emit_game.ludic) if has_prefabs() { emit_prefab_fns() } # @L_prefab_ + @L_spawn_prefab(name) emit_tick_helpers() } function emit_game_main() -> void { emit_game_defs() emit("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n") emit(" store i32 %argc, ptr @L_argc\n") emit(" store ptr %argv, ptr @L_argv\n") emit(" call void @L_init_runtime()\n") if has_ecs() { emit(" call void @L_grow(i32 1)\n") } if (find_fn("rt_init") != null) { emit(" call void " + fn_sym("rt_init") + "()\n") } emit(" call void @L_init_globals()\n") emit_calls_for_phase("Start") # enter the start scene once, after boot: store its id and run its on-enter. if len(g_scenes) > 0 { emit(" store i32 "); emit(itoa(g_start_scene)); emit(", ptr @L_scene\n") var si = 0 while si < len(g_scenes) { if (g_scenes[si].ival == g_start_scene) { emit(" call void @scene_enter_"); emit(g_scenes[si].s); emit("()\n") } si += 1 } } if (find_event("program_start") != null) { emit(" call void @ev_program_start()\n") } # EV1: @Public @OnStart emit(" br label %loop\n") emit("loop:\n") let r = emit_bind("load i32, ptr @L_running") let rc = emit_bind(`icmp ne i32 {r}, 0`) if (find_fn("rt_running") != null) { let pr = emit_bind("call i32 " + fn_sym("rt_running") + "()") let pc = emit_bind(`icmp ne i32 {pr}, 0`) let go = emit_bind(`and i1 {rc}, {pc}`) emit(" br i1 "); emit(go); emit(", label %body, label %done\n") } else { emit(" br i1 "); emit(rc); emit(", label %body, label %done\n") } emit("body:\n") # #83: when the game uses the input action-map / device layer, the frame loop # commits the device layer automatically each frame by calling input_poll (which # reads the live key via rt_poll, records/replays, and rebuilds the held-key set, # mouse and gamepad state). Its return is the frame key, so Input.key still works # and the game no longer has to call Input.poll by hand. A game that uses no Input # runtime keeps the plain rt_poll path, byte-identical. if g_uses_input and (find_fn("input_drive") != null) { let k = emit_bind("call i32 " + fn_sym("input_drive") + "()") emit(" store i32 "); emit(k); emit(", ptr @L_key\n") } else { if (find_fn("rt_poll") != null) { let k = emit_bind("call i32 " + fn_sym("rt_poll") + "()") emit(" store i32 "); emit(k); emit(", ptr @L_key\n") } } # a program with `ui` blocks: navigate the open UI from this frame's key and emit # UiClicked on activation — so a scene never polls ui_tick by hand. if has_ui() and (find_fn("rt_ui_tick") != null) { let uk = emit_bind("load i32, ptr @L_key") emit(" call void " + fn_sym("rt_ui_tick") + "(i32 "); emit(uk); emit(")\n") } emit_calls_for_phase("Input") emit_calls_for_phase("FixedUpdate") emit_calls_for_phase("Update") emit_calls_for_phase("LateUpdate") # #86: @ClearColor(N) makes the Render phase auto-clear to N at the top and # auto-present after the handlers run, so a game drops the clear/show boilerplate # (the clear colour is declared, not written in the handler body). Opt-in — a game # with no @ClearColor is byte-identical (it clears/shows itself, or the light # system owns the present). if g_has_clear_color and (find_fn("rt_clear") != null) { let colour = emit_expr(g_clear_color) # a literal, a const, or a Color.Name emit(" call void " + fn_sym("rt_clear") + "(i32 "); emit(colour.code); emit(")\n") } emit_calls_for_phase("Render") # Overlay: HUD / menus drawn after every engine Render system (sprites, lights), so # game UI is never painted under an actor. Presented together with the frame. emit_calls_for_phase("Overlay") if g_has_clear_color and (find_fn("rt_present") != null) { emit(" call void " + fn_sym("rt_present") + "()\n") } emit(" call void @lp_mem_frame()\n") # 25.1: the fence judges the frame that ended let fcur = emit_bind("load i32, ptr @L_frame") # Time.frame(): count completed frames let fnext = emit_bind(`add i32 {fcur}, 1`) emit(" store i32 "); emit(fnext); emit(", ptr @L_frame\n") emit(" br label %loop\n") emit("done:\n") if len(g_ondespawn) > 0 { emit(" call void @L_despawn_all(i32 2)\n") } # LC1: every survivor's @OnDespawn fires with reason Quit emit_calls_for_phase("OnQuit") # @OnQuit shutdown hooks run once, before teardown if (find_event("program_quit") != null) { emit(" call void @ev_program_quit()\n") } # EV1: @Public @OnQuit if (find_fn("rt_shutdown") != null) { emit(" call void " + fn_sym("rt_shutdown") + "()\n") } emit(" ret i32 0\n}\n") }