feat(lang): strict numbers in float files; render3d on float

A numbers float file adapts decimal literals to a fixed operand or slot, and refuses to
promote a computed int to a float implicitly: there it is almost always float bits. Explicit
float(x) is always allowed.

render3d's numbers are float, converted by tools/migrate/floatbits.py - a whole-program
inference of which ints carried IEEE bits (union-find over flows, calls, returns, buffers,
nested buffers and lexical scopes) and a rewriter to operators, Math.* and float literals,
with float_bits / float_from_bits left only where bits really cross (runtime scratch
buffers, mixed buffers). Seed regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-23 17:13:25 +03:00
parent 3ac0d8d5cb
commit cc89fc37a4
35 changed files with 57282 additions and 54382 deletions

View file

@ -19,14 +19,14 @@ property Chunk {
used: int = 0, # the walk that last wanted it (for eviction)
data: words, # INST_FLOATS per instance
count: int = 0,
ymin: int = 0, # height range of its instances (float bits), for the frustum test
ymax: int = 0
ymin: float = 0.0, # height range of its instances (float bits), for the frustum test
ymax: float = 0.0
}
property Stream {
layer: Layer,
size: int = 0, # chunk size (metres, float bits)
reach: int = 0, # radius (metres, float bits)
bands: words, # band outer radii (float bits), ascending; 4 of them
size: float = 0.0, # chunk size (metres, float bits)
reach: float = 0.0, # radius (metres, float bits)
bands: floats, # band outer radii (float bits), ascending; 4 of them
chunks: []Chunk,
keys: words, # parallel to chunks for lookup
n: int = 0,
@ -51,7 +51,7 @@ var stream_us_gather: long = 0 # copying cached chunks into the layer buffer
var stream_us_walk: long = 0 # the ring walk itself
var stream_walks: int = 0 # streams that walked their whole ring this frame
function stream_new(layer: Layer, size: int, reach: int, b0: int, b1: int, b2: int, b3: int) -> Stream {
function stream_new(layer: Layer, size: float, reach: float, b0: float, b1: float, b2: float, b3: float) -> Stream {
if not stream_cap_read {
stream_cap_read = true
if r3d_env_has("R3D_STREAM_CAP") { STREAM_MAX_CHUNKS = Text.to_int(r3d_env("R3D_STREAM_CAP")) }
@ -61,7 +61,7 @@ function stream_new(layer: Layer, size: int, reach: int, b0: int, b1: int, b2: i
s.layer = layer; s.size = size; s.reach = reach
layer.streamed = true
layer.grounded = true
s.bands = words(4)
s.bands = floats(4)
s.bands[0] = b0; s.bands[1] = b1; s.bands[2] = b2; s.bands[3] = b3
s.chunks = new []Chunk
s.keys = words(STREAM_MAX_CHUNKS)
@ -108,23 +108,23 @@ function stream_remember(s: Stream, key: int, idx: int) -> void {
# chunk gets an exactly-sized copy when the fill ends)
const STREAM_CHUNK_MAX: int = 262144
var stream_debug_n: int = 0
var stream_scratch: words = null
function stream_emit(s: Stream, x: int, y: int, z: int, scale: int, yaw: int, seed: int, wind: int) -> void {
var stream_scratch: floats = null
function stream_emit(s: Stream, x: float, y: float, z: float, scale: float, yaw: float, seed: float, wind: float) -> void {
let c = s.cur
if c.count >= STREAM_CHUNK_MAX { return }
if stream_scratch == null { stream_scratch = words(STREAM_CHUNK_MAX * INST_FLOATS) }
if c.count == 0 { c.ymin = y; c.ymax = y } else { c.ymin = f_min(c.ymin, y); c.ymax = f_max(c.ymax, y) }
if stream_scratch == null { stream_scratch = floats(STREAM_CHUNK_MAX * INST_FLOATS) }
if c.count == 0 { c.ymin = y; c.ymax = y } else { c.ymin = Math.min(c.ymin, y); c.ymax = Math.max(c.ymax, y) }
let o = c.count * INST_FLOATS
stream_scratch[o] = x; stream_scratch[o + 1] = y; stream_scratch[o + 2] = z; stream_scratch[o + 3] = scale
stream_scratch[o + 4] = f_sin(yaw); stream_scratch[o + 5] = f_cos(yaw); stream_scratch[o + 6] = seed; stream_scratch[o + 7] = wind
stream_scratch[o + 4] = Math.sin(yaw); stream_scratch[o + 5] = Math.cos(yaw); stream_scratch[o + 6] = seed; stream_scratch[o + 7] = wind
c.count += 1
}
function stream_band(s: Stream, d: int) -> int {
if f_ls(d, s.bands[0]) { return 0 }
if f_ls(d, s.bands[1]) { return 1 }
if f_ls(d, s.bands[2]) { return 2 }
if f_ls(d, s.bands[3]) { return 3 }
function stream_band(s: Stream, d: float) -> int {
if d < s.bands[0] { return 0 }
if d < s.bands[1] { return 1 }
if d < s.bands[2] { return 2 }
if d < s.bands[3] { return 3 }
return 4
}
@ -193,9 +193,9 @@ function stream_evict(s: Stream) -> void {
stream_evictions += 1
}
function stream_update(s: Stream, cam_x: int, cam_z: int) -> void {
let ccx = f_to_int(f_floor(f_div(cam_x, s.size)))
let ccz = f_to_int(f_floor(f_div(cam_z, s.size)))
function stream_update(s: Stream, cam_x: float, cam_z: float) -> void {
let ccx = int(Math.floor(cam_x / s.size))
let ccz = int(Math.floor(cam_z / s.size))
if ccx == s.last_cx and ccz == s.last_cz and not s.pending and s.view_gen == sc_view_gen { return }
let first = s.last_cx == 999999
s.view_gen = sc_view_gen
@ -206,7 +206,7 @@ function stream_update(s: Stream, cam_x: int, cam_z: int) -> void {
stream_walks += 1
stream_walk_no += 1
let tw = gl_now_us()
let r = f_to_int(f_div(s.reach, s.size)) + 1
let r = int(s.reach / s.size) + 1
# rings outward from the camera's cell: the nearest chunks are generated first
var ring = 0
while ring <= r {
@ -216,12 +216,12 @@ function stream_update(s: Stream, cam_x: int, cam_z: int) -> void {
while cx <= ccx + ring {
let edge = (cz == ccz - ring) or (cz == ccz + ring) or (cx == ccx - ring) or (cx == ccx + ring)
if edge {
let wx = f_mul(f_add(fi(cx), F_HALF), s.size)
let wz = f_mul(f_add(fi(cz), F_HALF), s.size)
let dx = f_sub(wx, cam_x); let dz = f_sub(wz, cam_z)
let d = f_sqrt(f_add(f_mul(dx, dx), f_mul(dz, dz)))
let wx = (float(cx) + 0.5) * s.size
let wz = (float(cz) + 0.5) * s.size
let dx = wx - cam_x; let dz = wz - cam_z
let d = Math.sqrt(dx * dx + dz * dz)
let band = stream_band(s, d)
if band < 4 and band >= s.min_band and f_ls(d, f_add(s.reach, s.size)) {
if band < 4 and band >= s.min_band and d < s.reach + s.size {
let key = stream_key(cx, cz, band)
var c = stream_find(s, key)
if c != null { c.used = stream_walk_no }
@ -283,12 +283,12 @@ function stream_update(s: Stream, cam_x: int, cam_z: int) -> void {
# height range, padded for the tallest cover and for casters just outside the frame
# whose short shadows still fall inside it.
function stream_chunk_visible(s: Stream, cx: int, cz: int, c: Chunk) -> bool {
let half = f_mul(s.size, F_HALF)
let wx = f_add(f_mul(fi(cx), s.size), half)
let wz = f_add(f_mul(fi(cz), s.size), half)
let hy = f_mul(f_sub(c.ymax, c.ymin), F_HALF)
let cy = f_add(c.ymin, hy)
let r = f_add(f_sqrt(f_add(f_mul(f_mul(half, half), F_TWO), f_mul(hy, hy))), fi(8))
let half = s.size * 0.5
let wx = float(cx) * s.size + half
let wz = float(cz) * s.size + half
let hy = (c.ymax - c.ymin) * 0.5
let cy = c.ymin + hy
let r = Math.sqrt(half * half * 2.0 + hy * hy) + 8.0
return cam_sphere_visible(wx, cy, wz, r)
}