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:
parent
3ac0d8d5cb
commit
cc89fc37a4
35 changed files with 57282 additions and 54382 deletions
|
|
@ -14,7 +14,7 @@ const GRASS_CELL: int = 16
|
|||
var grass_prog: int = 0
|
||||
var grass_mesh: Mesh = null
|
||||
var grass_on: bool = true
|
||||
var grass_wind: int = 0
|
||||
var grass_wind: float = 0.0
|
||||
# A photographed blade, as an atlas of straightened blades side by side (the game sets
|
||||
# this; the renderer does not name a game asset). 0 = the procedural gradient, which is
|
||||
# what this was for a year: a two-tone ramp with a hard edge, and every blade in the
|
||||
|
|
@ -27,12 +27,12 @@ var grass_blade_cols: int = 8
|
|||
# in it: you walked through a meadow and every blade ignored you, which is the single most
|
||||
# noticeable thing missing from every step the game asks you to take. The game sets this each
|
||||
# frame; radius 0 means nobody is there.
|
||||
var grass_push_x: int = 0
|
||||
var grass_push_z: int = 0
|
||||
var grass_push_r: int = 0
|
||||
var grass_s0: int = 0 # float bits: blade spacing at the camera (m)
|
||||
var grass_d0: int = 0 # the distance at which the spacing has doubled (m)
|
||||
var grass_radius: int = 0 # no blades past this (m)
|
||||
var grass_push_x: float = 0.0
|
||||
var grass_push_z: float = 0.0
|
||||
var grass_push_r: float = 0.0
|
||||
var grass_s0: float = 0.0 # float bits: blade spacing at the camera (m)
|
||||
var grass_d0: float = 0.0 # the distance at which the spacing has doubled (m)
|
||||
var grass_radius: float = 0.0 # no blades past this (m)
|
||||
var grass_draws: int = 0
|
||||
var grass_dbg: int = 0
|
||||
# Vulkan with multi-draw indirect: every visible tile is a record in one buffer, uploaded once a
|
||||
|
|
@ -43,8 +43,8 @@ const GRASS_CHUNK: int = 256
|
|||
const GRASS_RECS: int = 8192
|
||||
var grass_merge: bool = false
|
||||
var grass_rec: words = null # VkDrawIndexedIndirectCommand records, 5 words each
|
||||
var grass_tv: words = null # per record: corner x, corner z, indices per cell, 0 (float bits)
|
||||
var grass_chunk_tv: words = null
|
||||
var grass_tv: floats = null # per record: corner x, corner z, indices per cell, 0 (float bits)
|
||||
var grass_chunk_tv: floats = null
|
||||
var grass_n: int = 0
|
||||
var grass_cmds: int = 0
|
||||
var grass_band_start: words = null # per band: its first record
|
||||
|
|
@ -81,16 +81,16 @@ function grass_blade_mesh(rows: int) -> Mesh {
|
|||
# grass.mesh carries A COPY of these two lines for the mesh-shader path; change both or
|
||||
# the Windows blades stop matching the ones everywhere else.
|
||||
for r in 0 .. rows {
|
||||
let t = fr(r, rows - 1)
|
||||
let grow = f_min(f_div(t, fl(0.22)), F_ONE)
|
||||
let wide = f_mul(f_add(fl(0.50), f_mul(fl(0.33), grow)), f_sub(F_ONE, f_mul(t, f_mul(t, t))))
|
||||
let taper = f_max(wide, fl(0.05))
|
||||
let bend = f_mul(f_mul(t, t), fl(0.52))
|
||||
let t = float(r) / float(rows - 1)
|
||||
let grow = Math.min(t / 0.22, 1.0)
|
||||
let wide = (0.50 + 0.33 * grow) * (1.0 - t * (t * t))
|
||||
let taper = Math.max(wide, 0.05)
|
||||
let bend = t * t * 0.52
|
||||
for sd in 0 .. 2 {
|
||||
var x = f_neg(F_HALF)
|
||||
if sd == 1 { x = F_HALF }
|
||||
gl_put_bits(v, k, f_mul(x, taper)); gl_put_bits(v, k + 1, t); gl_put_bits(v, k + 2, bend)
|
||||
gl_put_bits(v, k + 3, fi(sd)); gl_put_bits(v, k + 4, t)
|
||||
var x = -0.5
|
||||
if sd == 1 { x = 0.5 }
|
||||
gl_put_bits(v, k, float_bits(x * taper)); gl_put_bits(v, k + 1, float_bits(t)); gl_put_bits(v, k + 2, float_bits(bend))
|
||||
gl_put_bits(v, k + 3, float_bits(float(sd))); gl_put_bits(v, k + 4, float_bits(t))
|
||||
k += 5
|
||||
}
|
||||
}
|
||||
|
|
@ -117,7 +117,7 @@ function grass_init() -> void {
|
|||
var defs = "#define FOLIAGE\n#define BLADE\n"
|
||||
if grass_merge {
|
||||
defs = defs + "#define TILES\n"
|
||||
grass_rec = words(GRASS_RECS * 5); grass_tv = words(GRASS_RECS * 4); grass_chunk_tv = words(GRASS_CHUNK * 4)
|
||||
grass_rec = words(GRASS_RECS * 5); grass_tv = floats(GRASS_RECS * 4); grass_chunk_tv = floats(GRASS_CHUNK * 4)
|
||||
grass_band_start = words(4); grass_band_cells = words(4)
|
||||
grass_cmds = gpu_buffer_new()
|
||||
}
|
||||
|
|
@ -126,30 +126,30 @@ function grass_init() -> void {
|
|||
# five rows, four quads: the arch above needs somewhere to bend, and at four rows a
|
||||
# blade that leans over is three straight segments and shows every join
|
||||
grass_mesh = grass_blade_mesh(5)
|
||||
grass_wind = fl(2.4)
|
||||
grass_wind = 2.4
|
||||
# Matched to the blade's width: a 1 cm blade at 0.11 m spacing covers a third of what a
|
||||
# 2.8 cm blade did, and the meadow goes bare. The game's graphics settings override this
|
||||
# (gfx_grass_spacing), but only once game_init has run - a plain headless render never
|
||||
# gets there, so the two have to agree or a shot shows something no player will see.
|
||||
# That is exactly how the last change measured as "no effect": the render was identical
|
||||
# because this line, not the settings, was deciding.
|
||||
grass_s0 = fl(0.066)
|
||||
grass_d0 = fi(45)
|
||||
grass_radius = fi(1600)
|
||||
grass_s0 = 0.066
|
||||
grass_d0 = 45.0
|
||||
grass_radius = 1600.0
|
||||
if r3d_env_has("R3D_NOBLADES") { grass_on = false }
|
||||
if r3d_env_has("R3D_GRASS_R") { grass_radius = fi(Text.to_int(r3d_env("R3D_GRASS_R"))) }
|
||||
if r3d_env_has("R3D_GRASS_R") { grass_radius = float(Text.to_int(r3d_env("R3D_GRASS_R"))) }
|
||||
if r3d_env_has("R3D_GRASS_DBG") { grass_dbg = Text.to_int(r3d_env("R3D_GRASS_DBG")) }
|
||||
}
|
||||
|
||||
# indices per 16 m cell that could exist at distance d (the count the shader computes)
|
||||
function grass_count_at(d: int) -> int {
|
||||
let spacing = f_mul(grass_s0, f_add(F_ONE, f_div(d, grass_d0)))
|
||||
let n = f_div(fi(GRASS_CELL * GRASS_CELL), f_mul(spacing, spacing))
|
||||
return f_to_int(n) + 1
|
||||
function grass_count_at(d: float) -> int {
|
||||
let spacing = grass_s0 * (1.0 + d / grass_d0)
|
||||
let n = float(GRASS_CELL * GRASS_CELL) / (spacing * spacing)
|
||||
return int(n) + 1
|
||||
}
|
||||
|
||||
# one tile size over one distance band
|
||||
function grass_tiles(size: int, d_min: int, d_max: int) -> void {
|
||||
function grass_tiles(size: int, d_min: float, d_max: float) -> void {
|
||||
let p = grass_prog
|
||||
let cells = size / GRASS_CELL
|
||||
if grass_merge {
|
||||
|
|
@ -157,27 +157,27 @@ function grass_tiles(size: int, d_min: int, d_max: int) -> void {
|
|||
} else {
|
||||
u_i(gpu_uniform(p, "u_tile_cells"), cells)
|
||||
}
|
||||
let sz = fi(size)
|
||||
let half = f_mul(sz, F_HALF)
|
||||
let reach = f_add(d_max, f_mul(half, fl(1.5)))
|
||||
let tx0 = f_to_int(f_floor(f_div(f_sub(cam_pos[0], reach), sz)))
|
||||
let tx1 = f_to_int(f_floor(f_div(f_add(cam_pos[0], reach), sz)))
|
||||
let tz0 = f_to_int(f_floor(f_div(f_sub(cam_pos[2], reach), sz)))
|
||||
let tz1 = f_to_int(f_floor(f_div(f_add(cam_pos[2], reach), sz)))
|
||||
let corner_r = f_mul(half, fl(1.42))
|
||||
let sz = float(size)
|
||||
let half = sz * 0.5
|
||||
let reach = d_max + half * 1.5
|
||||
let tx0 = int(Math.floor((cam_pos[0] - reach) / sz))
|
||||
let tx1 = int(Math.floor((cam_pos[0] + reach) / sz))
|
||||
let tz0 = int(Math.floor((cam_pos[2] - reach) / sz))
|
||||
let tz1 = int(Math.floor((cam_pos[2] + reach) / sz))
|
||||
let corner_r = half * 1.42
|
||||
var tz = tz0
|
||||
while tz <= tz1 {
|
||||
var tx = tx0
|
||||
while tx <= tx1 {
|
||||
let ox = f_mul(fi(tx), sz); let oz = f_mul(fi(tz), sz)
|
||||
let cx = f_add(ox, half); let cz = f_add(oz, half)
|
||||
let dx = f_sub(cx, cam_pos[0]); let dz = f_sub(cz, cam_pos[2])
|
||||
let dc = f_sqrt(f_add(f_mul(dx, dx), f_mul(dz, dz)))
|
||||
let ox = float(tx) * sz; let oz = float(tz) * sz
|
||||
let cx = ox + half; let cz = oz + half
|
||||
let dx = cx - cam_pos[0]; let dz = cz - cam_pos[2]
|
||||
let dc = Math.sqrt(dx * dx + dz * dz)
|
||||
# the tile's nearest and farthest points decide which band it belongs to
|
||||
let dnear = f_max(f_sub(dc, corner_r), F_ZERO)
|
||||
if f_ls(dc, d_min) or not f_ls(dnear, d_max) { tx += 1; continue }
|
||||
let dnear = Math.max(dc - corner_r, 0.0)
|
||||
if dc < d_min or not (dnear < d_max) { tx += 1; continue }
|
||||
let cy = terrain_height(cx, cz)
|
||||
if cam_sphere_visible(cx, cy, cz, f_add(corner_r, fi(6))) {
|
||||
if cam_sphere_visible(cx, cy, cz, corner_r + 6.0) {
|
||||
let per = grass_count_at(dnear)
|
||||
if per > 0 and grass_merge {
|
||||
if grass_n < GRASS_RECS {
|
||||
|
|
@ -187,7 +187,7 @@ function grass_tiles(size: int, d_min: int, d_max: int) -> void {
|
|||
grass_rec[r] = grass_mesh.count; grass_rec[r + 1] = inst; grass_rec[r + 2] = 0; grass_rec[r + 3] = 0
|
||||
grass_rec[r + 4] = ((grass_n - grass_band_start[grass_band_n - 1]) % GRASS_CHUNK) * 65536
|
||||
let t = grass_n * 4
|
||||
grass_tv[t] = ox; grass_tv[t + 1] = oz; grass_tv[t + 2] = fi(per); grass_tv[t + 3] = F_ZERO
|
||||
grass_tv[t] = ox; grass_tv[t + 1] = oz; grass_tv[t + 2] = float(per); grass_tv[t + 3] = 0.0
|
||||
grass_n += 1
|
||||
}
|
||||
} else if per > 0 {
|
||||
|
|
@ -216,28 +216,28 @@ function grass_draw() -> void {
|
|||
# CLAUDE.md records against u_wade and u_flutter, and it costs a day every time
|
||||
let btex = grass_blade_tex
|
||||
let bcols = grass_blade_cols
|
||||
u_f(gpu_uniform(p, "u_blade_cols"), fi(bcols))
|
||||
var bon = F_ZERO
|
||||
if btex != 0 { bon = F_ONE; r3d_bind_2d(p, "u_blade_tex", 12, btex) }
|
||||
u_f(gpu_uniform(p, "u_blade_cols"), float(bcols))
|
||||
var bon = 0.0
|
||||
if btex != 0 { bon = 1.0; r3d_bind_2d(p, "u_blade_tex", 12, btex) }
|
||||
u_f(gpu_uniform(p, "u_blade_tex_on"), bon)
|
||||
u_f3(gpu_uniform(p, "u_push"), grass_push_x, grass_push_z, grass_push_r)
|
||||
u_f(gpu_uniform(p, "u_rough_scale"), F_ONE)
|
||||
u_f(gpu_uniform(p, "u_rough_scale"), 1.0)
|
||||
u_v3(gpu_uniform(p, "u_tint"), sc_blade_tint)
|
||||
u_v3(gpu_uniform(p, "u_blade_base"), sc_blade_base)
|
||||
u_v3(gpu_uniform(p, "u_blade_tip"), sc_blade_tip)
|
||||
u_f(gpu_uniform(p, "u_cull"), grass_radius)
|
||||
u_f(gpu_uniform(p, "u_model_h"), F_ZERO)
|
||||
u_f(gpu_uniform(p, "u_model_h"), 0.0)
|
||||
u_f(gpu_uniform(p, "u_s0"), grass_s0)
|
||||
u_f(gpu_uniform(p, "u_d0"), grass_d0)
|
||||
u_f(gpu_uniform(p, "u_radius"), grass_radius)
|
||||
u_i(gpu_uniform(p, "u_dbg"), grass_dbg)
|
||||
var orthotex = ter_ortho_tex
|
||||
var oon = F_ONE
|
||||
if orthotex == 0 { orthotex = ter_height_tex; oon = F_ZERO }
|
||||
var oon = 1.0
|
||||
if orthotex == 0 { orthotex = ter_height_tex; oon = 0.0 }
|
||||
r3d_bind_2d(p, "u_ortho", 4, orthotex)
|
||||
u_f(gpu_uniform(p, "u_ortho_on"), oon)
|
||||
var lake = fl(-100000.0)
|
||||
if ter_lake_ex != 0 { lake = ter_lake_level }
|
||||
var lake = -100000.0
|
||||
if ter_lake_ex != 0.0 { lake = ter_lake_level }
|
||||
u_f(gpu_uniform(p, "u_lake_level"), lake)
|
||||
var sea = lake
|
||||
if ter_sea_set { sea = ter_sea_level }
|
||||
|
|
@ -252,15 +252,15 @@ function grass_draw() -> void {
|
|||
# at 0.15, 0.70% of a near-ground frame was over 210 of 255; at 0.0 it is 0.05%. A blade
|
||||
# does have a faint sheen, so this is small rather than nothing - the foliage layers have
|
||||
# used 0.05 all along and never showed the fault.
|
||||
u_f(gpu_uniform(p, "u_spec_scale"), fl(0.008))
|
||||
u_f(gpu_uniform(p, "u_spec_scale"), 0.008)
|
||||
gpu_cull(false)
|
||||
grass_draws = 0
|
||||
grass_n = 0
|
||||
grass_band_n = 0
|
||||
gpu_mesh_bind(grass_mesh)
|
||||
grass_tiles(16, F_ZERO, fi(300))
|
||||
grass_tiles(64, fi(300), fi(1200))
|
||||
grass_tiles(256, fi(1200), grass_radius)
|
||||
grass_tiles(16, 0.0, 300.0)
|
||||
grass_tiles(64, 300.0, 1200.0)
|
||||
grass_tiles(256, 1200.0, grass_radius)
|
||||
if grass_merge { grass_flush() }
|
||||
gpu_cull(true)
|
||||
}
|
||||
|
|
@ -287,7 +287,7 @@ function grass_flush() -> void {
|
|||
# chunked path's 2 at 4K on an RTX 3070 Ti)
|
||||
let cells = grass_band_cells[b]
|
||||
for q in 0 .. m {
|
||||
var total = f_to_int(grass_tv[(k + q) * 4 + 2]) * cells * cells
|
||||
var total = int(grass_tv[(k + q) * 4 + 2]) * cells * cells
|
||||
if total > 65535 { total = 65535 }
|
||||
if total > 0 {
|
||||
for c in 0 .. 4 { grass_chunk_tv[c] = grass_tv[(k + q) * 4 + c] }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue