wip(0.S2): migrate - a var nothing writes becomes a let; a state is keyed by its module, directory or program, so every program's plan agrees; paths normalized; program states named SceneDemoState / scene_demo_st; the LSP reads state, mut and entry/handler parameters

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 15:31:34 +03:00
parent 3eda72e8c2
commit 5ffe50ed02
463 changed files with 72045 additions and 67421 deletions

View file

@ -9,78 +9,89 @@
const COL_CELL: int = 16
const COL_CAP: int = 120000
var col_x: floats = null
var col_z: floats = null
var col_r: floats = null
# What a collider occupies VERTICALLY: y0 its base, y1 its top, metres, float bits. A circle used
# to be an infinite pillar - you could not climb a boulder, and a knee-high rock stopped you dead,
# because there was no height to compare against. col_add keeps that shape (a span from far below
# to far above) so every existing caller behaves exactly as it did; col_add_h gives a real one.
var col_y0: floats = null
var col_y1: floats = null
var col_n: int = 0
var col_side: int = 0 # cells per side
var col_start: words = null # per cell: first index into col_sorted (side*side + 1)
var col_sorted: words = null
var col_built: bool = false
var col_out: floats = null # the resolved position (x, z)
const COL_LOW: float = -16777216.0 # -16777216.0: below any ground
const COL_HIGH: float = 16777216.0 # 16777216.0: above any sky
function col_add(render3d_st: mut Render3dState, x: float, z: float, r: float) -> void { col_add_h(render3d_st, x, z, r, COL_LOW, COL_HIGH) }
function col_add(x: float, z: float, r: float) -> void { col_add_h(x, z, r, COL_LOW, COL_HIGH) }
# a collider that occupies only y0 .. y1: a body above its top walks over it, a body below its base
# passes under, and col_top_at reports it as something to stand on
function col_add_h(render3d_st: mut Render3dState, x: float, z: float, r: float, y0: float, y1: float) -> void {
if render3d_st.col_x == null {
render3d_st.col_x = floats(COL_CAP); render3d_st.col_z = floats(COL_CAP); render3d_st.col_r = floats(COL_CAP)
render3d_st.col_y0 = floats(COL_CAP); render3d_st.col_y1 = floats(COL_CAP); render3d_st.col_out = floats(2)
function col_add_h(x: float, z: float, r: float, y0: float, y1: float) -> void {
if col_x == null {
col_x = floats(COL_CAP); col_z = floats(COL_CAP); col_r = floats(COL_CAP)
col_y0 = floats(COL_CAP); col_y1 = floats(COL_CAP); col_out = floats(2)
}
if render3d_st.col_n >= COL_CAP { return }
render3d_st.col_x[render3d_st.col_n] = x; render3d_st.col_z[render3d_st.col_n] = z; render3d_st.col_r[render3d_st.col_n] = r
render3d_st.col_y0[render3d_st.col_n] = y0; render3d_st.col_y1[render3d_st.col_n] = y1
render3d_st.col_n += 1
render3d_st.col_built = false
if col_n >= COL_CAP { return }
col_x[col_n] = x; col_z[col_n] = z; col_r[col_n] = r
col_y0[col_n] = y0; col_y1[col_n] = y1
col_n += 1
col_built = false
}
function col_cell_of(render3d_st: Render3dState, v: float, origin: float) -> int {
var c = int(Math.floor((v - origin + float(render3d_st.TERRAIN_HALF)) / float(COL_CELL)))
function col_cell_of(v: float, origin: float) -> int {
var c = int(Math.floor((v - origin + float(TERRAIN_HALF)) / float(COL_CELL)))
if c < 0 { c = 0 }
if c > render3d_st.col_side - 1 { c = render3d_st.col_side - 1 }
if c > col_side - 1 { c = col_side - 1 }
return c
}
function col_build(render3d_st: mut Render3dState) -> void {
render3d_st.col_side = (render3d_st.TERRAIN_HALF * 2) / COL_CELL
let ncell = render3d_st.col_side * render3d_st.col_side
if render3d_st.col_start == null { render3d_st.col_start = words(ncell + 1); render3d_st.col_sorted = words(COL_CAP) }
for i in 0 .. ncell + 1 { render3d_st.col_start[i] = 0 }
for i in 0 .. render3d_st.col_n { render3d_st.col_start[col_cell_of(render3d_st, render3d_st.col_z[i], render3d_st.ter_oz) * render3d_st.col_side + col_cell_of(render3d_st, render3d_st.col_x[i], render3d_st.ter_ox) + 1] += 1 }
for c in 0 .. ncell { render3d_st.col_start[c + 1] += render3d_st.col_start[c] }
function col_build() -> void {
col_side = (TERRAIN_HALF * 2) / COL_CELL
let ncell = col_side * col_side
if col_start == null { col_start = words(ncell + 1); col_sorted = words(COL_CAP) }
for i in 0 .. ncell + 1 { col_start[i] = 0 }
for i in 0 .. col_n { col_start[col_cell_of(col_z[i], ter_oz) * col_side + col_cell_of(col_x[i], ter_ox) + 1] += 1 }
for c in 0 .. ncell { col_start[c + 1] += col_start[c] }
let fill = words(ncell)
for c in 0 .. ncell { fill[c] = render3d_st.col_start[c] }
for i in 0 .. render3d_st.col_n {
let c = col_cell_of(render3d_st, render3d_st.col_z[i], render3d_st.ter_oz) * render3d_st.col_side + col_cell_of(render3d_st, render3d_st.col_x[i], render3d_st.ter_ox)
render3d_st.col_sorted[fill[c]] = i
for c in 0 .. ncell { fill[c] = col_start[c] }
for i in 0 .. col_n {
let c = col_cell_of(col_z[i], ter_oz) * col_side + col_cell_of(col_x[i], ter_ox)
col_sorted[fill[c]] = i
fill[c] += 1
}
free(fill)
render3d_st.col_built = true
print(`colliders: {render3d_st.col_n}`)
col_built = true
print(`colliders: {col_n}`)
}
# push (px, pz) with radius pr out of every circle it overlaps; the result is in col_out
function col_resolve(render3d_st: mut Render3dState, px: float, pz: float, pr: float) -> bool { return col_resolve_at(render3d_st, px, pz, pr, COL_LOW, COL_HIGH) }
function col_resolve(px: float, pz: float, pr: float) -> bool { return col_resolve_at(px, pz, pr, COL_LOW, COL_HIGH) }
# the same, for a body that occupies feet .. head: a collider whose span misses that is not in the
# way at all. This is what lets a hiker stand on top of a boulder rather than inside it.
function col_resolve_at(render3d_st: mut Render3dState, px: float, pz: float, pr: float, feet: float, head: float) -> bool {
render3d_st.col_out[0] = px; render3d_st.col_out[1] = pz
if not render3d_st.col_built or render3d_st.col_n == 0 { return false }
function col_resolve_at(px: float, pz: float, pr: float, feet: float, head: float) -> bool {
col_out[0] = px; col_out[1] = pz
if not col_built or col_n == 0 { return false }
var x = px; var z = pz
var moved = false
let cx = col_cell_of(render3d_st, px, render3d_st.ter_ox); let cz = col_cell_of(render3d_st, pz, render3d_st.ter_oz)
let cx = col_cell_of(px, ter_ox); let cz = col_cell_of(pz, ter_oz)
for pass in 0 .. 2 {
for dz in 0 .. 3 {
let zc = cz + dz - 1
if zc < 0 or zc >= render3d_st.col_side { continue }
if zc < 0 or zc >= col_side { continue }
for dx in 0 .. 3 {
let xc = cx + dx - 1
if xc < 0 or xc >= render3d_st.col_side { continue }
let c = zc * render3d_st.col_side + xc
for k in render3d_st.col_start[c] .. render3d_st.col_start[c + 1] {
let i = render3d_st.col_sorted[k]
let ex = x - render3d_st.col_x[i]; let ez = z - render3d_st.col_z[i]
if xc < 0 or xc >= col_side { continue }
let c = zc * col_side + xc
for k in col_start[c] .. col_start[c + 1] {
let i = col_sorted[k]
let ex = x - col_x[i]; let ez = z - col_z[i]
let d2 = ex * ex + ez * ez
let rr = render3d_st.col_r[i] + pr
let rr = col_r[i] + pr
# nothing to push out of if the body is wholly above its top or below its base
if not (feet < render3d_st.col_y1[i]) { continue }
if not (head > render3d_st.col_y0[i]) { continue }
if not (feet < col_y1[i]) { continue }
if not (head > col_y0[i]) { continue }
if d2 < rr * rr {
let d = Math.sqrt(d2)
# The UNIT normal out of this circle. (ex, ez) / d is always unit for d > 0,
@ -105,31 +116,31 @@ function col_resolve_at(render3d_st: mut Render3dState, px: float, pz: float, pr
}
}
}
render3d_st.col_out[0] = x; render3d_st.col_out[1] = z
col_out[0] = x; col_out[1] = z
return moved
}
# The highest collider top under (px, pz) that a body at `feet` could be standing on or step up to:
# tops above `reach` are a wall, not a step. F_ZERO-safe: returns `floor` when there is nothing, so
# a caller can pass the terrain height and use the answer directly as the ground.
function col_top_at(render3d_st: Render3dState, px: float, pz: float, pr: float, feet: float, reach: float, floor: float) -> float {
function col_top_at(px: float, pz: float, pr: float, feet: float, reach: float, floor: float) -> float {
var top = floor
if not render3d_st.col_built or render3d_st.col_n == 0 { return top }
let cx = col_cell_of(render3d_st, px, render3d_st.ter_ox); let cz = col_cell_of(render3d_st, pz, render3d_st.ter_oz)
if not col_built or col_n == 0 { return top }
let cx = col_cell_of(px, ter_ox); let cz = col_cell_of(pz, ter_oz)
let limit = feet + reach
for dz in 0 .. 3 {
let zc = cz + dz - 1
if zc < 0 or zc >= render3d_st.col_side { continue }
if zc < 0 or zc >= col_side { continue }
for dx in 0 .. 3 {
let xc = cx + dx - 1
if xc < 0 or xc >= render3d_st.col_side { continue }
let c = zc * render3d_st.col_side + xc
for k in render3d_st.col_start[c] .. render3d_st.col_start[c + 1] {
let i = render3d_st.col_sorted[k]
let ex = px - render3d_st.col_x[i]; let ez = pz - render3d_st.col_z[i]
if xc < 0 or xc >= col_side { continue }
let c = zc * col_side + xc
for k in col_start[c] .. col_start[c + 1] {
let i = col_sorted[k]
let ex = px - col_x[i]; let ez = pz - col_z[i]
let d2 = ex * ex + ez * ez
let rr = render3d_st.col_r[i] + pr
let rr = col_r[i] + pr
if not (d2 < rr * rr) { continue }
let t = render3d_st.col_y1[i]
let t = col_y1[i]
if t > limit { continue } # too tall to step onto: it is a wall
if t > top { top = t }
}
@ -138,21 +149,21 @@ function col_top_at(render3d_st: Render3dState, px: float, pz: float, pr: float,
return top
}
# is the segment from (x0,z0) to (x1,z1) clear of every circle (a camera line of sight)?
function col_clear(render3d_st: mut Render3dState, x0: float, z0: float, x1: float, z1: float, r: float) -> bool {
function col_clear(x0: float, z0: float, x1: float, z1: float, r: float) -> bool {
let steps = 6
for s in 0 .. steps + 1 {
let t = float(s) / float(steps)
let x = Math.lerp(x0, x1, t); let z = Math.lerp(z0, z1, t)
if col_resolve(render3d_st, x, z, r) { return false }
if col_resolve(x, z, r) { return false }
}
return true
}
# No colliders, ready for another map's. The cell index is sized from TERRAIN_HALF on its
# first build and kept, so it is released too: a larger map would overrun the old one.
function col_reset(render3d_st: mut Render3dState) -> void {
render3d_st.col_n = 0
render3d_st.col_built = false
if render3d_st.col_start != null { free(render3d_st.col_start); render3d_st.col_start = null }
if render3d_st.col_sorted != null { free(render3d_st.col_sorted); render3d_st.col_sorted = null }
function col_reset() -> void {
col_n = 0
col_built = false
if col_start != null { free(col_start); col_start = null }
if col_sorted != null { free(col_sorted); col_sorted = null }
}