#79 — Input.axis_i(neg,pos) -> int returns a -1/0/1 movement intent from the multi-key device set, so WASD-to-movement needs no bool->int glue and feeds an int mover directly (dx = Input.axis_i('a','d')). #84 — a Bounds config entity (rect + policy, from ludic.core) drives the engine-owned world-bounds system (LateUpdate): clamp / wrap / bounce (clamp + flip Body velocity) / kill (despawn a body fully outside). Reads the Collider size so the box stays inside; off by default, spliced only when Bounds is declared (byte-identical otherwise). Adds World.despawn(e) — the by-id reflective despawn (runs @OnDespawn + frees) via a new world_despawn intrinsic whose @fn_world_despawn helper is emitted in emit_program's tail once a use is seen (the g_uses_* prelude pattern), used by the kill policy and callable from any system. Examples input_movement + world_bounds. Full suite 112/0, goldens byte-identical, fixpoint holds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
0497029dae
commit
ad3be0c53c
17 changed files with 30435 additions and 29731 deletions
|
|
@ -224,6 +224,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
if (meth == "press") { bare = "input_press"; push(labels, "key") }
|
||||
if (meth == "release") { bare = "input_release"; push(labels, "key") }
|
||||
if (meth == "axis") { bare = "input_axis"; push(labels, "neg"); push(labels, "pos") }
|
||||
if (meth == "axis_i") { bare = "input_axis_i"; push(labels, "neg"); push(labels, "pos") } # #79 directional int (-1/0/1)
|
||||
if (meth == "vector") { bare = "input_vector"; push(labels, "left"); push(labels, "right"); push(labels, "up"); push(labels, "down") }
|
||||
if (meth == "strength") { bare = "input_strength"; push(labels, "action") }
|
||||
if (meth == "mouse_x") { bare = "input_mouse_x" }
|
||||
|
|
@ -287,6 +288,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
if (meth == "count") { bare = "world_count" }
|
||||
if (meth == "size") { bare = "world_size" }
|
||||
if (meth == "spawn") { bare = "world_spawn" }
|
||||
if (meth == "despawn") { bare = "world_despawn" } # #84 — despawn an entity by id (runs @OnDespawn + frees)
|
||||
if (meth == "save") { bare = "world_save" }
|
||||
if (meth == "load") { bare = "world_load" }
|
||||
if (meth == "prop_id") { bare = "world_prop_id" }
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ function emit_program() -> void {
|
|||
g_uses_panic = false
|
||||
g_uses_result = false
|
||||
g_uses_option = false
|
||||
g_uses_world_despawn = false # #84: set when a world_despawn call is emitted (below)
|
||||
g_cov_lines = new []int
|
||||
g_cov_active = true
|
||||
loc_name = new []pointer; loc_reg = new []pointer; loc_ty = new []pointer; loc_mut = new []int
|
||||
|
|
@ -184,6 +185,7 @@ function emit_program() -> void {
|
|||
i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_MAIN { emit_main(prog[i]) }; i = i + 1 }
|
||||
} } }
|
||||
if g_uses_world_despawn { emit_world_despawn_fn() } # #84: @fn_world_despawn, after all World.despawn / esys_bounds-kill uses are seen
|
||||
if g_uses_loopback { emit_loopback() } # built-in transport, after all net_send/net_poll uses are seen
|
||||
if g_uses_str { emit_str_prelude() } # @fn_str_eq / @fn_str_concat, after all uses are seen
|
||||
if g_uses_intstr { emit_int_str() } # @fn_int_str, for string(int) in interpolation
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ function is_intrinsic(name: pointer) -> bool {
|
|||
if (name == "arg_count") or (name == "arg") or (name == "exit") { return true }
|
||||
if (name == "file_stderr") or (name == "file_stdout") { return true }
|
||||
if (name == "run") or (name == "getenv") { return true }
|
||||
if (name == "world_despawn") { return true } # #84 — despawn an entity by id from a system
|
||||
return false
|
||||
}
|
||||
# emit " <r> = <rest>\n" and return r
|
||||
|
|
@ -56,6 +57,15 @@ function emit_intrinsic(name: pointer, e: Node) -> Val {
|
|||
emit(" call i32 @fclose(ptr "); emit(f); emit(")\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
# #84 — despawn an entity by id (runs its @OnDespawn hooks + frees the slot),
|
||||
# callable from a system (esys_bounds kill) or as World.despawn(e). The helper
|
||||
# @fn_world_despawn is emitted with the ECS defs when g_uses_world_despawn.
|
||||
if (name == "world_despawn") {
|
||||
g_uses_world_despawn = true # emit @fn_world_despawn in the tail (after all uses seen)
|
||||
let ent = arg_code(e, 0)
|
||||
emit(" call void @fn_world_despawn(i32 "); emit(ent); emit(")\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
# print_str / print_int are the polymorphic `print(x)` builtin now (emit_call).
|
||||
if (name == "arg_count") { return val(emit_bind("load i32, ptr @L_argc"), "int") }
|
||||
if (name == "arg") {
|
||||
|
|
|
|||
|
|
@ -293,6 +293,37 @@ function emit_despawn_hooks() -> void {
|
|||
emit_despawn_all_fn()
|
||||
}
|
||||
|
||||
# #84 — @fn_world_despawn(e): the runtime-callable despawn, used by the
|
||||
# world-bounds kill policy and World.despawn(e). Same per-kind @OnDespawn dispatch
|
||||
# as the `despawn` statement (emit_despawn) but reading a runtime %e, then the
|
||||
# entity-scoped-listener sweep and the slot free. Emitted from emit_program's tail
|
||||
# only when a world_despawn call was emitted (g_uses_world_despawn), after all uses
|
||||
# are seen — so a program that never despawns by id is byte-identical.
|
||||
function emit_world_despawn_fn() -> void {
|
||||
emit("define void @fn_world_despawn(i32 %e) {\nentry:\n")
|
||||
if len(g_ondespawn) > 0 {
|
||||
let me = itoa(MAX_ENT)
|
||||
emit(" %kp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n")
|
||||
emit(" %k = load i32, ptr %kp\n")
|
||||
var i = 0
|
||||
while i < len(g_ondespawn) {
|
||||
let mname = g_ondespawn[i].s
|
||||
let si = itoa(i)
|
||||
emit(" %c"); emit(si); emit(" = icmp eq i32 %k, "); emit(itoa(find_arch_id(mname))); emit("\n")
|
||||
emit(" br i1 %c"); emit(si); emit(", label %hit"); emit(si); emit(", label %next"); emit(si); emit("\n")
|
||||
emit("hit"); emit(si); emit(":\n call void @on_despawn_"); emit(mname); emit("(i32 %e, i32 0)\n") # reason = Despawned
|
||||
let dev = `model_{mname}_despawn`
|
||||
if (find_event(dev) != null) { emit(" call void @ev_"); emit(dev); emit("(i32 %e, i32 0)\n") }
|
||||
emit(" br label %next"); emit(si); emit("\n")
|
||||
emit("next"); emit(si); emit(":\n")
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
if len(g_events) > 0 { emit(" call void @ludic_sweep_entity(i32 %e)\n") } # EV5: drop entity-scoped listeners
|
||||
emit(" call void @L_free_entity(i32 %e)\n")
|
||||
emit(" ret void\n}\n\n")
|
||||
}
|
||||
|
||||
# LC1 "no silent deaths": at program shutdown every still-live entity's despawn
|
||||
# hook fires with reason Quit, so teardown that must run on exit is not skipped.
|
||||
# @L_despawn_all(reason) walks the live set and dispatches each entity by kind —
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue