feat(lang): L4 type checker between parse and emit

selfhost/check/ walks every function, the entry, tests, globals' initializers and
@On listeners with real scopes, and refuses mixed number kinds, text and numbers,
two record types, mismatched slices and fn types, wrong argument counts, wrong
returns and wrong push elements - every mix-up at once, each at its line.
LUDIC_CHECK_REPORT=1 lists them by category. pointer stays untyped (L7's).

What it found is fixed: render3d's HDR scan calling the float-bits extern f_lt
with floats; ludic.shooter's right-stick aim overflowing past half a push;
prof.ludic storing longs in []int; extern arguments now coerced to their
parameters. Text-returning runtime functions say string; Assets.ready says bool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-24 01:34:49 +03:00
parent 0677aee93f
commit 57b66bdf47
52 changed files with 27005 additions and 11844 deletions

View file

@ -417,7 +417,7 @@ function sh_half_w(e: int) -> int {
if mode == AimMode.RightStick {
let ax = Input.pad_axis(0, STICK_RIGHT_X)
let ay = Input.pad_axis(0, STICK_RIGHT_Y)
if (ax != 0) or (ay != 0) { target = sh_dir_angle(ax, ay); have = 1 }
if (ax != 0) or (ay != 0) { target = sh_dir_angle_fx(ax, ay); have = 1 }
}
if mode == AimMode.MoveDirection { # from the movement intent
let wx = td_get(e, "want_x")
@ -473,6 +473,12 @@ function sh_dir_angle(dx: int, dy: int) -> int {
let r = Math.atan2(fixed(dy), fixed(dx))
return sh_wrap_deg(floor(Math.rad_to_deg(r)))
}
# a stick's deflection is already fixed: fixed(dy) on it would scale it by 65536 again and
# overflow past half a push, turning the aim round
function sh_dir_angle_fx(dx: fixed, dy: fixed) -> int {
let r = Math.atan2(dy, dx)
return sh_wrap_deg(floor(Math.rad_to_deg(r)))
}
function sh_wrap_deg(d: int) -> int {
var a = d
while a < 0 { a += 360 }
@ -494,8 +500,9 @@ function sh_spawn_bullet(owner: int, fac: int, px: int, py: int, deg: int, speed
pos_set(b, "x", px)
pos_set(b, "y", py)
let rad = Angle.from_degrees(fixed(deg))
pr_set(b, "vx", Angle.cos(rad) * fixed(speed))
pr_set(b, "vy", Angle.sin(rad) * fixed(speed))
# World.set stores a field's raw word: a fixed field is handed its bits by name
pr_set(b, "vx", as_int(Angle.cos(rad) * fixed(speed)))
pr_set(b, "vy", as_int(Angle.sin(rad) * fixed(speed)))
pr_set(b, "dir", deg)
pr_set(b, "speed", speed)
pr_set(b, "damage", dmg)
@ -621,8 +628,8 @@ function sh_fire(owner: int, id: int) -> void {
let rad = Angle.from_degrees(fixed(na))
let nvx = Angle.cos(rad) * fixed(sp)
let nvy = Angle.sin(rad) * fixed(sp)
pr_set(e, "vx", nvx)
pr_set(e, "vy", nvy)
pr_set(e, "vx", as_int(nvx))
pr_set(e, "vy", as_int(nvy))
}
}
# integrate (Q16.16 with subpixel remainder)