wip(0.S3): the runtime migrated - ludic migrate state --runtime <every program>: 331 vars into 25 states (RtInputState, RtGlState, ...), 2 lets; its states are made before it boots; no module-level var is let through outside --globals

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 16:02:12 +03:00
parent 7b17b4a1b9
commit 02448e176c
38 changed files with 53877 additions and 53251 deletions

View file

@ -60,35 +60,37 @@
const PHYS_ONE: int = 65536 # Q16.16 one whole pixel
# ---- per-frame config + reflected ids (filled at the top of esys_move) -------
var pm_pos: int = -1 # Position prop id
var pm_pos_x: int = -1
var pm_pos_y: int = -1
var pm_col: int = -1 # Collider prop id
var pm_c_w: int = -1
var pm_c_h: int = -1
var pm_c_offx: int = -1
var pm_c_offy: int = -1
var pm_c_trig: int = -1
var pm_c_oneway: int = -1
var pm_c_layer: int = -1
var pm_c_mask: int = -1
var phys_ts: int = 0 # tile size px (0 = grid off)
var phys_wall: int = 0 # solid glyph
var phys_wall2: int = 0 # optional second solid glyph (Solids.solid2), e.g. a closed door
var phys_oneway: int = 0 # one-way platform glyph (0 = none)
export state RtSystemsMoveState {
pm_pos: int = -1 # Position prop id
pm_pos_x: int = -1
pm_pos_y: int = -1
pm_col: int = -1 # Collider prop id
pm_c_w: int = -1
pm_c_h: int = -1
pm_c_offx: int = -1
pm_c_offy: int = -1
pm_c_trig: int = -1
pm_c_oneway: int = -1
pm_c_layer: int = -1
pm_c_mask: int = -1
phys_ts: int = 0 # tile size px (0 = grid off)
phys_wall: int = 0 # solid glyph
phys_wall2: int = 0 # optional second solid glyph (Solids.solid2), e.g. a closed door
phys_oneway: int = 0 # one-way platform glyph (0 = none)
phys_cur_layer: int = 0
phys_cur_mask: int = 0
phys_hit_x: int = 0 # X sweep blocked
phys_hit_up: int = 0 # Y sweep blocked moving up
phys_hit_down: int = 0 # Y sweep blocked moving down
phys_trig_prev: words = null
}
# the moving body's own layer/mask, so the solid scan can filter by them
var phys_cur_layer: int = 0
var phys_cur_mask: int = 0
# sweep outputs (Ludic returns one value, so axis clampers report contact here)
var phys_hit_x: int = 0 # X sweep blocked
var phys_hit_up: int = 0 # Y sweep blocked moving up
var phys_hit_down: int = 0 # Y sweep blocked moving down
# trigger edge tracking: the body a trigger overlapped last frame, stored as
# (entity id + 1) so a zero-filled slot reads as "none" (entity 0 is valid).
var phys_trig_prev: words = null
# ---- small integer helpers --------------------------------------------------
# floor division toward negative infinity (positions can go slightly negative);
@ -102,42 +104,42 @@ function phys_floordiv(a: int, b: int) -> int {
# a solid (fully blocking) tile: out of bounds counts as a wall, so the map edge
# is a free arena boundary; otherwise the glyph equals the configured wall.
function phys_tile_solid(c: int, r: int) -> bool {
function phys_tile_solid(rt_core_st: RtCoreState, rt_systems_move_st: RtSystemsMoveState, c: int, r: int) -> bool {
if c < 0 { return true }
if r < 0 { return true }
if c >= rt_mapw { return true }
if r >= rt_maph { return true }
let g = rt_tile(c, r)
if g == phys_wall { return true }
return (phys_wall2 != 0) and (g == phys_wall2)
if c >= rt_core_st.rt_mapw { return true }
if r >= rt_core_st.rt_maph { return true }
let g = rt_tile(rt_core_st, c, r)
if g == rt_systems_move_st.phys_wall { return true }
return (rt_systems_move_st.phys_wall2 != 0) and (g == rt_systems_move_st.phys_wall2)
}
# a one-way platform tile (in bounds only — the arena edge is a full wall above).
function phys_tile_oneway(c: int, r: int) -> bool {
if phys_oneway == 0 { return false }
function phys_tile_oneway(rt_core_st: RtCoreState, rt_systems_move_st: RtSystemsMoveState, c: int, r: int) -> bool {
if rt_systems_move_st.phys_oneway == 0 { return false }
if c < 0 { return false }
if r < 0 { return false }
if c >= rt_mapw { return false }
if r >= rt_maph { return false }
return rt_tile(c, r) == phys_oneway
if c >= rt_core_st.rt_mapw { return false }
if r >= rt_core_st.rt_maph { return false }
return rt_tile(rt_core_st, c, r) == rt_systems_move_st.phys_oneway
}
# is any full-solid tile present in column c over the row span [r0, r1]?
function phys_col_solid(c: int, r0: int, r1: int) -> bool {
function phys_col_solid(rt_core_st: RtCoreState, rt_systems_move_st: RtSystemsMoveState, c: int, r0: int, r1: int) -> bool {
var r = r0
while r <= r1 { if phys_tile_solid(c, r) { return true }; r += 1 }
while r <= r1 { if phys_tile_solid(rt_core_st, rt_systems_move_st, c, r) { return true }; r += 1 }
return false
}
# is any full-solid tile present in row r over the column span [c0, c1]?
function phys_row_solid(r: int, c0: int, c1: int) -> bool {
function phys_row_solid(rt_core_st: RtCoreState, rt_systems_move_st: RtSystemsMoveState, r: int, c0: int, c1: int) -> bool {
var c = c0
while c <= c1 { if phys_tile_solid(c, r) { return true }; c += 1 }
while c <= c1 { if phys_tile_solid(rt_core_st, rt_systems_move_st, c, r) { return true }; c += 1 }
return false
}
# is any full-OR-one-way tile present in row r over [c0, c1]? (downward landings)
function phys_row_land(r: int, c0: int, c1: int) -> bool {
function phys_row_land(rt_core_st: RtCoreState, rt_systems_move_st: RtSystemsMoveState, r: int, c0: int, c1: int) -> bool {
var c = c0
while c <= c1 {
if phys_tile_solid(c, r) { return true }
if phys_tile_oneway(c, r) { return true }
if phys_tile_solid(rt_core_st, rt_systems_move_st, c, r) { return true }
if phys_tile_oneway(rt_core_st, rt_systems_move_st, c, r) { return true }
c += 1
}
return false
@ -157,37 +159,37 @@ function phys_match(la: int, ma: int, lb: int, mb: int) -> bool {
}
# ---- reflected accessors ----------------------------------------------------
function phys_pos_x(e: int) -> int { return World.get(e, pm_pos, pm_pos_x) }
function phys_pos_y(e: int) -> int { return World.get(e, pm_pos, pm_pos_y) }
function phys_set_x(e: int, v: int) -> void { World.set(e, pm_pos, pm_pos_x, v) }
function phys_set_y(e: int, v: int) -> void { World.set(e, pm_pos, pm_pos_y, v) }
function phys_pos_x(rt_systems_move_st: RtSystemsMoveState, e: int) -> int { return World.get(e, rt_systems_move_st.pm_pos, rt_systems_move_st.pm_pos_x) }
function phys_pos_y(rt_systems_move_st: RtSystemsMoveState, e: int) -> int { return World.get(e, rt_systems_move_st.pm_pos, rt_systems_move_st.pm_pos_y) }
function phys_set_x(rt_systems_move_st: RtSystemsMoveState, e: int, v: int) -> void { World.set(e, rt_systems_move_st.pm_pos, rt_systems_move_st.pm_pos_x, v) }
function phys_set_y(rt_systems_move_st: RtSystemsMoveState, e: int, v: int) -> void { World.set(e, rt_systems_move_st.pm_pos, rt_systems_move_st.pm_pos_y, v) }
function phys_c_int(e: int, f: int) -> int {
function phys_c_int(rt_systems_move_st: RtSystemsMoveState, e: int, f: int) -> int {
if f < 0 { return 0 }
return World.get(e, pm_col, f)
return World.get(e, rt_systems_move_st.pm_col, f)
}
# a collider's AABB left / top edge (Position anchor + offset).
function phys_aabb_x(e: int) -> int { return phys_pos_x(e) + phys_c_int(e, pm_c_offx) }
function phys_aabb_y(e: int) -> int { return phys_pos_y(e) + phys_c_int(e, pm_c_offy) }
function phys_aabb_x(rt_systems_move_st: RtSystemsMoveState, e: int) -> int { return phys_pos_x(rt_systems_move_st, e) + phys_c_int(rt_systems_move_st, e, rt_systems_move_st.pm_c_offx) }
function phys_aabb_y(rt_systems_move_st: RtSystemsMoveState, e: int) -> int { return phys_pos_y(rt_systems_move_st, e) + phys_c_int(rt_systems_move_st, e, rt_systems_move_st.pm_c_offy) }
# ---- X sweep ----------------------------------------------------------------
# Clamp a body's tentative dx so its AABB (px,py,w,h) stops at the first solid it
# would cross this frame — entity colliders and (optionally) the tile grid — and
# flag phys_hit_x on contact. One-way platforms never block horizontal motion.
function phys_clamp_x(self_e: int, px: int, py: int, w: int, h: int, dx: int) -> int {
function phys_clamp_x(rt_core_st: RtCoreState, rt_systems_move_st: mut RtSystemsMoveState, self_e: int, px: int, py: int, w: int, h: int, dx: int) -> int {
if dx == 0 { return 0 }
var best = dx
# --- entity solids: every other non-trigger collider we match against
if pm_col >= 0 {
var s = World.query_next(pm_col, 0)
if rt_systems_move_st.pm_col >= 0 {
var s = World.query_next(rt_systems_move_st.pm_col, 0)
while s >= 0 {
if s != self_e {
if phys_c_int(s, pm_c_trig) == 0 {
if phys_match(phys_cur_layer, phys_cur_mask, phys_c_int(s, pm_c_layer), phys_c_int(s, pm_c_mask)) {
if phys_c_int(s, pm_c_oneway) == 0 { # one-way: pass horizontally
let sx = phys_aabb_x(s); let sy = phys_aabb_y(s)
let sw = phys_c_int(s, pm_c_w); let sh = phys_c_int(s, pm_c_h)
if phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_trig) == 0 {
if phys_match(rt_systems_move_st.phys_cur_layer, rt_systems_move_st.phys_cur_mask, phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_layer), phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_mask)) {
if phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_oneway) == 0 { # one-way: pass horizontally
let sx = phys_aabb_x(rt_systems_move_st, s); let sy = phys_aabb_y(rt_systems_move_st, s)
let sw = phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_w); let sh = phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_h)
if (py < sy + sh) and (sy < py + h) { # vertical spans overlap
if dx > 0 {
if px + w <= sx { # currently left of s
@ -205,13 +207,13 @@ function phys_clamp_x(self_e: int, px: int, py: int, w: int, h: int, dx: int) ->
}
}
}
s = World.query_next(pm_col, s + 1)
s = World.query_next(rt_systems_move_st.pm_col, s + 1)
}
}
# --- tile grid: nearest solid column in the swept path over the row span
if phys_ts > 0 {
let ts = phys_ts
if rt_systems_move_st.phys_ts > 0 {
let ts = rt_systems_move_st.phys_ts
let r0 = phys_floordiv(py, ts)
let r1 = phys_floordiv(py + h - 1, ts)
if dx > 0 {
@ -219,7 +221,7 @@ function phys_clamp_x(self_e: int, px: int, py: int, w: int, h: int, dx: int) ->
let endc = phys_floordiv(px + w - 1 + best, ts)
var c = startc + 1
while c <= endc {
if phys_col_solid(c, r0, r1) {
if phys_col_solid(rt_core_st, rt_systems_move_st, c, r0, r1) {
let allow = c * ts - (px + w)
if allow < best { best = allow }
c = endc + 1 # nearest wins; stop
@ -230,7 +232,7 @@ function phys_clamp_x(self_e: int, px: int, py: int, w: int, h: int, dx: int) ->
let endc = phys_floordiv(px + best, ts)
var c = startc - 1
while c >= endc {
if phys_col_solid(c, r0, r1) {
if phys_col_solid(rt_core_st, rt_systems_move_st, c, r0, r1) {
let allow = (c + 1) * ts - px
if allow > best { best = allow }
c = endc - 1 # nearest wins; stop
@ -239,7 +241,7 @@ function phys_clamp_x(self_e: int, px: int, py: int, w: int, h: int, dx: int) ->
}
}
if best != dx { phys_hit_x = 1 }
if best != dx { rt_systems_move_st.phys_hit_x = 1 }
return best
}
@ -247,19 +249,19 @@ function phys_clamp_x(self_e: int, px: int, py: int, w: int, h: int, dx: int) ->
# Clamp a body's tentative dy against solids (and one-way platforms on a downward
# landing), flagging phys_hit_down / phys_hit_up. Must run after the X sweep so
# px is already resolved for this frame.
function phys_clamp_y(self_e: int, px: int, py: int, w: int, h: int, dy: int) -> int {
function phys_clamp_y(rt_core_st: RtCoreState, rt_systems_move_st: mut RtSystemsMoveState, self_e: int, px: int, py: int, w: int, h: int, dy: int) -> int {
if dy == 0 { return 0 }
var best = dy
if pm_col >= 0 {
var s = World.query_next(pm_col, 0)
if rt_systems_move_st.pm_col >= 0 {
var s = World.query_next(rt_systems_move_st.pm_col, 0)
while s >= 0 {
if s != self_e {
if phys_c_int(s, pm_c_trig) == 0 {
if phys_match(phys_cur_layer, phys_cur_mask, phys_c_int(s, pm_c_layer), phys_c_int(s, pm_c_mask)) {
let sx = phys_aabb_x(s); let sy = phys_aabb_y(s)
let sw = phys_c_int(s, pm_c_w); let sh = phys_c_int(s, pm_c_h)
let oneway = phys_c_int(s, pm_c_oneway)
if phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_trig) == 0 {
if phys_match(rt_systems_move_st.phys_cur_layer, rt_systems_move_st.phys_cur_mask, phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_layer), phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_mask)) {
let sx = phys_aabb_x(rt_systems_move_st, s); let sy = phys_aabb_y(rt_systems_move_st, s)
let sw = phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_w); let sh = phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_h)
let oneway = phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_oneway)
if (px < sx + sw) and (sx < px + w) { # horizontal spans overlap
if dy > 0 {
# a one-way platform blocks only when the body starts on/above it
@ -283,12 +285,12 @@ function phys_clamp_y(self_e: int, px: int, py: int, w: int, h: int, dy: int) ->
}
}
}
s = World.query_next(pm_col, s + 1)
s = World.query_next(rt_systems_move_st.pm_col, s + 1)
}
}
if phys_ts > 0 {
let ts = phys_ts
if rt_systems_move_st.phys_ts > 0 {
let ts = rt_systems_move_st.phys_ts
let c0 = phys_floordiv(px, ts)
let c1 = phys_floordiv(px + w - 1, ts)
if dy > 0 {
@ -296,10 +298,10 @@ function phys_clamp_y(self_e: int, px: int, py: int, w: int, h: int, dy: int) ->
let endr = phys_floordiv(py + h - 1 + best, ts)
var r = startr + 1
while r <= endr {
var block = phys_row_solid(r, c0, c1)
var block = phys_row_solid(rt_core_st, rt_systems_move_st, r, c0, c1)
# one-way tiles only catch a body whose feet start at/above the tile top
if not block {
if phys_row_land(r, c0, c1) { if py + h <= r * ts { block = true } }
if phys_row_land(rt_core_st, rt_systems_move_st, r, c0, c1) { if py + h <= r * ts { block = true } }
}
if block {
let allow = r * ts - (py + h)
@ -312,7 +314,7 @@ function phys_clamp_y(self_e: int, px: int, py: int, w: int, h: int, dy: int) ->
let endr = phys_floordiv(py + best, ts)
var r = startr - 1
while r >= endr {
if phys_row_solid(r, c0, c1) { # one-way never blocks upward
if phys_row_solid(rt_core_st, rt_systems_move_st, r, c0, c1) { # one-way never blocks upward
let allow = (r + 1) * ts - py
if allow > best { best = allow }
r = endr - 1
@ -322,7 +324,7 @@ function phys_clamp_y(self_e: int, px: int, py: int, w: int, h: int, dy: int) ->
}
if best != dy {
if dy > 0 { phys_hit_down = 1 } else { phys_hit_up = 1 }
if dy > 0 { rt_systems_move_st.phys_hit_down = 1 } else { rt_systems_move_st.phys_hit_up = 1 }
}
return best
}
@ -330,40 +332,40 @@ function phys_clamp_y(self_e: int, px: int, py: int, w: int, h: int, dy: int) ->
# ---- ground probe -----------------------------------------------------------
# on_ground is a 1px downward overlap test, independent of this frame's velocity,
# so a body resting under sub-pixel gravity still reads grounded.
function phys_grounded(self_e: int, px: int, py: int, w: int, h: int) -> bool {
function phys_grounded(rt_core_st: RtCoreState, rt_systems_move_st: RtSystemsMoveState, self_e: int, px: int, py: int, w: int, h: int) -> bool {
# entity solids one pixel below
if pm_col >= 0 {
var s = World.query_next(pm_col, 0)
if rt_systems_move_st.pm_col >= 0 {
var s = World.query_next(rt_systems_move_st.pm_col, 0)
while s >= 0 {
if s != self_e {
if phys_c_int(s, pm_c_trig) == 0 {
if phys_match(phys_cur_layer, phys_cur_mask, phys_c_int(s, pm_c_layer), phys_c_int(s, pm_c_mask)) {
let sx = phys_aabb_x(s); let sy = phys_aabb_y(s)
let sw = phys_c_int(s, pm_c_w); let sh = phys_c_int(s, pm_c_h)
if phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_trig) == 0 {
if phys_match(rt_systems_move_st.phys_cur_layer, rt_systems_move_st.phys_cur_mask, phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_layer), phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_mask)) {
let sx = phys_aabb_x(rt_systems_move_st, s); let sy = phys_aabb_y(rt_systems_move_st, s)
let sw = phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_w); let sh = phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_h)
if (px < sx + sw) and (sx < px + w) {
if (py + h <= sy) and (py + h + 1 > sy) { return true }
}
}
}
}
s = World.query_next(pm_col, s + 1)
s = World.query_next(rt_systems_move_st.pm_col, s + 1)
}
}
if phys_ts > 0 {
let ts = phys_ts
if rt_systems_move_st.phys_ts > 0 {
let ts = rt_systems_move_st.phys_ts
let c0 = phys_floordiv(px, ts)
let c1 = phys_floordiv(px + w - 1, ts)
let footr = phys_floordiv(py + h, ts) # tile row just below the feet
if (py + h) == footr * ts { # feet flush on a tile boundary
if phys_row_solid(footr, c0, c1) { return true }
if phys_row_land(footr, c0, c1) { return true }
if phys_row_solid(rt_core_st, rt_systems_move_st, footr, c0, c1) { return true }
if phys_row_land(rt_core_st, rt_systems_move_st, footr, c0, c1) { return true }
}
}
return false
}
# ---- the system ------------------------------------------------------------
function esys_move() -> void {
function esys_move(rt_core_st: mut RtCoreState, rt_systems_move_st: mut RtSystemsMoveState) -> void {
let pb = World.prop_id("Body")
if pb < 0 { return }
@ -382,23 +384,23 @@ function esys_move() -> void {
let f_ny = World.field_id(pb, "hit_ny")
# shared / config props (into module globals the helpers read)
pm_pos = World.prop_id("Position")
if pm_pos >= 0 {
pm_pos_x = World.field_id(pm_pos, "x")
pm_pos_y = World.field_id(pm_pos, "y")
rt_systems_move_st.pm_pos = World.prop_id("Position")
if rt_systems_move_st.pm_pos >= 0 {
rt_systems_move_st.pm_pos_x = World.field_id(rt_systems_move_st.pm_pos, "x")
rt_systems_move_st.pm_pos_y = World.field_id(rt_systems_move_st.pm_pos, "y")
}
pm_col = World.prop_id("Collider")
if pm_col >= 0 {
pm_c_w = World.field_id(pm_col, "w")
pm_c_h = World.field_id(pm_col, "h")
pm_c_offx = World.field_id(pm_col, "offx")
pm_c_offy = World.field_id(pm_col, "offy")
pm_c_trig = World.field_id(pm_col, "is_trigger")
pm_c_oneway = World.field_id(pm_col, "one_way")
pm_c_layer = World.field_id(pm_col, "layer")
pm_c_mask = World.field_id(pm_col, "mask")
rt_systems_move_st.pm_col = World.prop_id("Collider")
if rt_systems_move_st.pm_col >= 0 {
rt_systems_move_st.pm_c_w = World.field_id(rt_systems_move_st.pm_col, "w")
rt_systems_move_st.pm_c_h = World.field_id(rt_systems_move_st.pm_col, "h")
rt_systems_move_st.pm_c_offx = World.field_id(rt_systems_move_st.pm_col, "offx")
rt_systems_move_st.pm_c_offy = World.field_id(rt_systems_move_st.pm_col, "offy")
rt_systems_move_st.pm_c_trig = World.field_id(rt_systems_move_st.pm_col, "is_trigger")
rt_systems_move_st.pm_c_oneway = World.field_id(rt_systems_move_st.pm_col, "one_way")
rt_systems_move_st.pm_c_layer = World.field_id(rt_systems_move_st.pm_col, "layer")
rt_systems_move_st.pm_c_mask = World.field_id(rt_systems_move_st.pm_col, "mask")
}
phys_ts = 0; phys_wall = 0; phys_wall2 = 0; phys_oneway = 0
rt_systems_move_st.phys_ts = 0; rt_systems_move_st.phys_wall = 0; rt_systems_move_st.phys_wall2 = 0; rt_systems_move_st.phys_oneway = 0
let ps = World.prop_id("Solids")
if ps >= 0 {
let se = World.query_next(ps, 0)
@ -406,24 +408,24 @@ function esys_move() -> void {
let f_tile = World.field_id(ps, "tile")
let f_wall = World.field_id(ps, "wall")
let f_ow = World.field_id(ps, "oneway")
if f_tile >= 0 { phys_ts = World.get(se, ps, f_tile) }
if f_wall >= 0 { phys_wall = World.get(se, ps, f_wall) }
if f_ow >= 0 { phys_oneway = World.get(se, ps, f_ow) }
if f_tile >= 0 { rt_systems_move_st.phys_ts = World.get(se, ps, f_tile) }
if f_wall >= 0 { rt_systems_move_st.phys_wall = World.get(se, ps, f_wall) }
if f_ow >= 0 { rt_systems_move_st.phys_oneway = World.get(se, ps, f_ow) }
let f_w2 = World.field_id(ps, "solid2")
if f_w2 >= 0 { phys_wall2 = World.get(se, ps, f_w2) }
rt_map_solid1 = phys_wall; rt_map_solid2 = phys_wall2 # Map.is_solid reads the same config
if phys_ts > 0 { rt_map_tile_px = phys_ts }
if f_w2 >= 0 { rt_systems_move_st.phys_wall2 = World.get(se, ps, f_w2) }
rt_core_st.rt_map_solid1 = rt_systems_move_st.phys_wall; rt_core_st.rt_map_solid2 = rt_systems_move_st.phys_wall2 # Map.is_solid reads the same config
if rt_systems_move_st.phys_ts > 0 { rt_core_st.rt_map_tile_px = rt_systems_move_st.phys_ts }
}
}
# a Position is required to move a body; a Body with none is inert.
if pm_pos < 0 { return }
if pm_pos_x < 0 { return }
if pm_pos_y < 0 { return }
if rt_systems_move_st.pm_pos < 0 { return }
if rt_systems_move_st.pm_pos_x < 0 { return }
if rt_systems_move_st.pm_pos_y < 0 { return }
var e = World.query_next(pb, 0)
while e >= 0 {
if World.has(e, pm_pos) != 0 {
if World.has(e, rt_systems_move_st.pm_pos) != 0 {
# 1. integrate velocity (+ gravity for a platformer policy)
var vx = 0; if f_vx >= 0 { vx = World.get(e, pb, f_vx) }
var vy = 0; if f_vy >= 0 { vy = World.get(e, pb, f_vy) }
@ -452,42 +454,42 @@ function esys_move() -> void {
# 3. does this body have a solid shape? (a trigger-only body moves freely)
var has_shape = false
if pm_col >= 0 {
if World.has(e, pm_col) != 0 { if phys_c_int(e, pm_c_trig) == 0 { has_shape = true } }
if rt_systems_move_st.pm_col >= 0 {
if World.has(e, rt_systems_move_st.pm_col) != 0 { if phys_c_int(rt_systems_move_st, e, rt_systems_move_st.pm_c_trig) == 0 { has_shape = true } }
}
if has_shape {
phys_cur_layer = phys_c_int(e, pm_c_layer)
phys_cur_mask = phys_c_int(e, pm_c_mask)
let w = phys_c_int(e, pm_c_w)
let h = phys_c_int(e, pm_c_h)
var px = phys_aabb_x(e)
let py0 = phys_aabb_y(e)
rt_systems_move_st.phys_cur_layer = phys_c_int(rt_systems_move_st, e, rt_systems_move_st.pm_c_layer)
rt_systems_move_st.phys_cur_mask = phys_c_int(rt_systems_move_st, e, rt_systems_move_st.pm_c_mask)
let w = phys_c_int(rt_systems_move_st, e, rt_systems_move_st.pm_c_w)
let h = phys_c_int(rt_systems_move_st, e, rt_systems_move_st.pm_c_h)
var px = phys_aabb_x(rt_systems_move_st, e)
let py0 = phys_aabb_y(rt_systems_move_st, e)
# X sweep, commit, then Y sweep from the resolved x
phys_hit_x = 0
let cdx = phys_clamp_x(e, px, py0, w, h, dx)
phys_set_x(e, phys_pos_x(e) + cdx)
px = phys_aabb_x(e)
rt_systems_move_st.phys_hit_x = 0
let cdx = phys_clamp_x(rt_core_st, rt_systems_move_st, e, px, py0, w, h, dx)
phys_set_x(rt_systems_move_st, e, phys_pos_x(rt_systems_move_st, e) + cdx)
px = phys_aabb_x(rt_systems_move_st, e)
phys_hit_up = 0; phys_hit_down = 0
let py = phys_aabb_y(e)
let cdy = phys_clamp_y(e, px, py, w, h, dy)
phys_set_y(e, phys_pos_y(e) + cdy)
rt_systems_move_st.phys_hit_up = 0; rt_systems_move_st.phys_hit_down = 0
let py = phys_aabb_y(rt_systems_move_st, e)
let cdy = phys_clamp_y(rt_core_st, rt_systems_move_st, e, px, py, w, h, dy)
phys_set_y(rt_systems_move_st, e, phys_pos_y(rt_systems_move_st, e) + cdy)
# derived flags + contact normal
if phys_hit_x == 1 { hit_wall = 1; vx = 0; if dx > 0 { nx = -1 } else { nx = 1 } }
if phys_hit_up == 1 { hit_ceiling = 1; vy = 0; ny = 1 }
if phys_hit_down == 1 { vy = 0; ny = -1 }
let gy = phys_aabb_y(e)
if phys_grounded(e, px, gy, w, h) { on_ground = 1 }
if rt_systems_move_st.phys_hit_x == 1 { hit_wall = 1; vx = 0; if dx > 0 { nx = -1 } else { nx = 1 } }
if rt_systems_move_st.phys_hit_up == 1 { hit_ceiling = 1; vy = 0; ny = 1 }
if rt_systems_move_st.phys_hit_down == 1 { vy = 0; ny = -1 }
let gy = phys_aabb_y(rt_systems_move_st, e)
if phys_grounded(rt_core_st, rt_systems_move_st, e, px, gy, w, h) { on_ground = 1 }
# a body pressed onto the ground carries no downward velocity, whatever
# the gravity step was this frame (so on_ground implies a settled vy).
if (on_ground == 1) and (vy > 0) { vy = 0 }
} else {
# free integration: apply the whole-pixel step straight to Position
phys_set_x(e, phys_pos_x(e) + dx)
phys_set_y(e, phys_pos_y(e) + dy)
phys_set_x(rt_systems_move_st, e, phys_pos_x(rt_systems_move_st, e) + dx)
phys_set_y(rt_systems_move_st, e, phys_pos_y(rt_systems_move_st, e) + dy)
}
# 4. write velocity + subpixel remainder + derived outputs back
@ -511,35 +513,35 @@ function esys_move() -> void {
# 5. triggers/sensors — overlap that reports but never resolves. For each
# trigger collider, find the lowest-id body-shaped collider it overlaps and
# edge-detect enter/exit against last frame (SpriteAnim.event_fired shape).
esys_triggers()
esys_triggers(rt_systems_move_st)
}
function esys_triggers() -> void {
if pm_col < 0 { return }
if pm_c_trig < 0 { return }
let f_hit = World.field_id(pm_col, "hit")
let f_ent = World.field_id(pm_col, "entered")
let f_ext = World.field_id(pm_col, "exited")
function esys_triggers(rt_systems_move_st: mut RtSystemsMoveState) -> void {
if rt_systems_move_st.pm_col < 0 { return }
if rt_systems_move_st.pm_c_trig < 0 { return }
let f_hit = World.field_id(rt_systems_move_st.pm_col, "hit")
let f_ent = World.field_id(rt_systems_move_st.pm_col, "entered")
let f_ext = World.field_id(rt_systems_move_st.pm_col, "exited")
if (f_hit < 0) and (f_ent < 0) and (f_ext < 0) { return } # no trigger outputs declared
if phys_trig_prev == null { phys_trig_prev = words(1024) }
if rt_systems_move_st.phys_trig_prev == null { rt_systems_move_st.phys_trig_prev = words(1024) }
var t = World.query_next(pm_col, 0)
var t = World.query_next(rt_systems_move_st.pm_col, 0)
while t >= 0 {
if phys_c_int(t, pm_c_trig) != 0 {
if World.has(t, pm_pos) != 0 {
let tx = phys_aabb_x(t); let ty = phys_aabb_y(t)
let tw = phys_c_int(t, pm_c_w); let th = phys_c_int(t, pm_c_h)
let tl = phys_c_int(t, pm_c_layer); let tm = phys_c_int(t, pm_c_mask)
if phys_c_int(rt_systems_move_st, t, rt_systems_move_st.pm_c_trig) != 0 {
if World.has(t, rt_systems_move_st.pm_pos) != 0 {
let tx = phys_aabb_x(rt_systems_move_st, t); let ty = phys_aabb_y(rt_systems_move_st, t)
let tw = phys_c_int(rt_systems_move_st, t, rt_systems_move_st.pm_c_w); let th = phys_c_int(rt_systems_move_st, t, rt_systems_move_st.pm_c_h)
let tl = phys_c_int(rt_systems_move_st, t, rt_systems_move_st.pm_c_layer); let tm = phys_c_int(rt_systems_move_st, t, rt_systems_move_st.pm_c_mask)
var cur = -1
var b = World.query_next(pm_col, 0)
var b = World.query_next(rt_systems_move_st.pm_col, 0)
while b >= 0 {
if b != t {
if phys_c_int(b, pm_c_trig) == 0 {
if World.has(b, pm_pos) != 0 {
if phys_match(tl, tm, phys_c_int(b, pm_c_layer), phys_c_int(b, pm_c_mask)) {
let bx = phys_aabb_x(b); let by = phys_aabb_y(b)
let bw = phys_c_int(b, pm_c_w); let bh = phys_c_int(b, pm_c_h)
if phys_c_int(rt_systems_move_st, b, rt_systems_move_st.pm_c_trig) == 0 {
if World.has(b, rt_systems_move_st.pm_pos) != 0 {
if phys_match(tl, tm, phys_c_int(rt_systems_move_st, b, rt_systems_move_st.pm_c_layer), phys_c_int(rt_systems_move_st, b, rt_systems_move_st.pm_c_mask)) {
let bx = phys_aabb_x(rt_systems_move_st, b); let by = phys_aabb_y(rt_systems_move_st, b)
let bw = phys_c_int(rt_systems_move_st, b, rt_systems_move_st.pm_c_w); let bh = phys_c_int(rt_systems_move_st, b, rt_systems_move_st.pm_c_h)
if (tx < bx + bw) and (bx < tx + tw) and (ty < by + bh) and (by < ty + th) {
if cur < 0 { cur = b } # lowest id wins (deterministic)
}
@ -547,19 +549,19 @@ function esys_triggers() -> void {
}
}
}
b = World.query_next(pm_col, b + 1)
b = World.query_next(rt_systems_move_st.pm_col, b + 1)
}
let prev = phys_trig_prev[t] - 1 # stored as id+1, 0 = none
let prev = rt_systems_move_st.phys_trig_prev[t] - 1 # stored as id+1, 0 = none
var entered = 0
var exited = 0
if (prev < 0) and (cur >= 0) { entered = 1 }
if (prev >= 0) and (cur < 0) { exited = 1 }
phys_trig_prev[t] = cur + 1
if f_hit >= 0 { World.set(t, pm_col, f_hit, cur) }
if f_ent >= 0 { World.set(t, pm_col, f_ent, entered) }
if f_ext >= 0 { World.set(t, pm_col, f_ext, exited) }
rt_systems_move_st.phys_trig_prev[t] = cur + 1
if f_hit >= 0 { World.set(t, rt_systems_move_st.pm_col, f_hit, cur) }
if f_ent >= 0 { World.set(t, rt_systems_move_st.pm_col, f_ent, entered) }
if f_ext >= 0 { World.set(t, rt_systems_move_st.pm_col, f_ext, exited) }
}
}
t = World.query_next(pm_col, t + 1)
t = World.query_next(rt_systems_move_st.pm_col, t + 1)
}
}