Skip to main content

ling/runtime/
mod.rs

1// src/runtime/mod.rs — tree-walking interpreter with graphics support
2use std::cell::RefCell;
3use std::collections::HashMap;
4use crate::parser::ast::*;
5use crate::gfx::{GfxState, Light};
6#[cfg(not(target_arch = "wasm32"))]
7use crate::gfx::raster::{fill_triangle, draw_line};
8#[cfg(not(target_arch = "wasm32"))]
9use ling_audio::{AudioEngine, ToneParams, Wave};
10
11#[cfg(not(target_arch = "wasm32"))]
12use ling_audio::FftAnalyzer;
13
14#[cfg(not(target_arch = "wasm32"))]
15use ling_mic;
16
17// ─── Values ──────────────────────────────────────────────────────────────────
18
19#[derive(Debug, Clone)]
20pub enum Value {
21    Str(String),
22    Number(f64),
23    Bool(bool),
24    Unit,
25    List(Vec<Value>),
26    Ok(Box<Value>),
27    Err(Box<Value>),
28    Fn(Vec<String>, Vec<Stmt>, Env),
29}
30
31type Env = HashMap<String, Value>;
32
33impl std::fmt::Display for Value {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match self {
36            Value::Str(s)    => write!(f, "{s}"),
37            Value::Number(n) => {
38                if n.fract() == 0.0 && n.abs() < 1e15 { write!(f, "{}", *n as i64) }
39                else { write!(f, "{n}") }
40            }
41            Value::Bool(b)   => write!(f, "{b}"),
42            Value::Unit      => write!(f, "()"),
43            Value::List(v)   => {
44                write!(f, "[")?;
45                for (i, x) in v.iter().enumerate() {
46                    if i > 0 { write!(f, ", ")?; }
47                    write!(f, "{x}")?;
48                }
49                write!(f, "]")
50            }
51            Value::Ok(v)     => write!(f, "Ok({v})"),
52            Value::Err(v)    => write!(f, "Err({v})"),
53            Value::Fn(_, _, _) => write!(f, "<fn>"),
54        }
55    }
56}
57
58// ─── Control flow ────────────────────────────────────────────────────────────
59
60#[derive(Debug)]
61enum EvalErr {
62    Runtime(String),
63    Return(Value),
64    #[allow(dead_code)] // reserved for future `break` statement support
65    Break,
66}
67
68impl From<String> for EvalErr {
69    fn from(s: String) -> Self { EvalErr::Runtime(s) }
70}
71
72type EvalResult = Result<Value, EvalErr>;
73
74// GfxState is now defined in crate::gfx — see src/gfx/mod.rs.
75
76// ─── SVG writer ───────────────────────────────────────────────────────────────
77
78struct SvgWriter {
79    path:     String,
80    width:    f64,
81    height:   f64,
82    elements: Vec<String>,
83}
84
85impl SvgWriter {
86    fn new(path: String, width: f64, height: f64) -> Self {
87        let bg = format!(
88            "<rect width=\"{width}\" height=\"{height}\" fill=\"#0a0a0a\"/>"
89        );
90        Self { path, width, height, elements: vec![bg] }
91    }
92
93    fn save(&self) -> std::io::Result<()> {
94        let w = self.width;
95        let h = self.height;
96        let mut out = format!(
97            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
98             <svg xmlns=\"http://www.w3.org/2000/svg\" \
99             width=\"{w}\" height=\"{h}\" viewBox=\"0 0 {w} {h}\">\n"
100        );
101        for elem in &self.elements {
102            out.push_str("  ");
103            out.push_str(elem);
104            out.push('\n');
105        }
106        out.push_str("</svg>\n");
107        // Create parent directory if it doesn't exist.
108        if let Some(parent) = std::path::Path::new(&self.path).parent() {
109            if !parent.as_os_str().is_empty() {
110                let _ = std::fs::create_dir_all(parent);
111            }
112        }
113        std::fs::write(&self.path, out.as_bytes())
114    }
115}
116
117fn hsl_to_hex(h: f64, s: f64, l: f64) -> String {
118    let s = s / 100.0;
119    let l = l / 100.0;
120    let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
121    let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
122    let m = l - c / 2.0;
123    let (r1, g1, b1) = if h < 60.0       { (c, x, 0.0) }
124                       else if h < 120.0  { (x, c, 0.0) }
125                       else if h < 180.0  { (0.0, c, x) }
126                       else if h < 240.0  { (0.0, x, c) }
127                       else if h < 300.0  { (x, 0.0, c) }
128                       else               { (c, 0.0, x) };
129    let r = ((r1 + m) * 255.0).round() as u8;
130    let g = ((g1 + m) * 255.0).round() as u8;
131    let b = ((b1 + m) * 255.0).round() as u8;
132    format!("#{r:02x}{g:02x}{b:02x}")
133}
134
135// ─── Procedural texture helpers ───────────────────────────────────────────────
136
137fn tex_hash(x: i32, y: i32, seed: u32) -> f32 {
138    let mut h = (x as u32).wrapping_add((y as u32).wrapping_mul(2654435769)).wrapping_add(seed.wrapping_mul(1234567891));
139    h ^= h >> 16; h = h.wrapping_mul(0x45d9f3b); h ^= h >> 16;
140    h as f32 / u32::MAX as f32
141}
142
143fn tex_vnoise(x: f32, y: f32, seed: u32) -> f32 {
144    let xi = x.floor() as i32; let yi = y.floor() as i32;
145    let sm = |t: f32| t * t * (3.0 - 2.0 * t);
146    let xf = sm(x - xi as f32); let yf = sm(y - yi as f32);
147    let a = tex_hash(xi, yi, seed); let b = tex_hash(xi+1, yi, seed);
148    let c = tex_hash(xi, yi+1, seed); let d = tex_hash(xi+1, yi+1, seed);
149    a + (b-a)*xf + (c-a)*yf + (a-b-c+d)*xf*yf
150}
151
152fn tex_fbm(x: f32, y: f32, octaves: u32, seed: u32) -> f32 {
153    let mut v = 0.0f32; let mut amp = 0.5f32; let mut f = 1.0f32;
154    for i in 0..octaves {
155        v += tex_vnoise(x * f, y * f, seed.wrapping_add(i * 7919)) * amp;
156        amp *= 0.5; f *= 2.0;
157    }
158    v
159}
160
161fn tex_palette(name: &str, t: f32) -> [f32; 3] {
162    let (a, b, c, d): ([f32;3],[f32;3],[f32;3],[f32;3]) = match name {
163        "fire"        => ([0.8,0.4,0.1],[0.7,0.3,0.1],[1.0,0.5,0.3],[0.0,0.5,0.8]),
164        "ocean"       => ([0.1,0.4,0.7],[0.3,0.3,0.4],[0.8,1.0,0.5],[0.3,0.0,0.6]),
165        "psychedelic" => ([0.5,0.5,0.5],[0.8,0.8,0.8],[1.0,1.3,0.7],[0.0,0.15,0.3]),
166        "neon"        => ([0.5,0.5,0.5],[0.5,0.5,0.5],[2.0,1.0,0.0],[0.5,0.2,0.25]),
167        "forest"      => ([0.3,0.5,0.2],[0.2,0.3,0.1],[1.0,0.5,0.8],[0.1,0.3,0.6]),
168        _             => ([0.5,0.5,0.5],[0.5,0.5,0.5],[1.0,1.0,1.0],[0.0,0.333,0.667]),
169    };
170    [0,1,2].map(|i| (a[i] + b[i] * (std::f32::consts::TAU * (c[i] * t + d[i])).cos()).clamp(0.0, 1.0))
171}
172
173/// Map a physical key to a typed character for ling-ui text input (lowercase).
174#[cfg(not(target_arch = "wasm32"))]
175fn key_char(k: minifb::Key) -> Option<char> {
176    use minifb::Key::*;
177    Some(match k {
178        A=>'a',B=>'b',C=>'c',D=>'d',E=>'e',F=>'f',G=>'g',H=>'h',I=>'i',J=>'j',K=>'k',L=>'l',M=>'m',
179        N=>'n',O=>'o',P=>'p',Q=>'q',R=>'r',S=>'s',T=>'t',U=>'u',V=>'v',W=>'w',X=>'x',Y=>'y',Z=>'z',
180        Key0=>'0',Key1=>'1',Key2=>'2',Key3=>'3',Key4=>'4',Key5=>'5',Key6=>'6',Key7=>'7',Key8=>'8',Key9=>'9',
181        Space=>' ', Minus=>'-', Period=>'.',
182        _ => return None,
183    })
184}
185
186/// Lowercase-hex encode bytes (the wire format for crypto values in Ling).
187fn hex_encode(bytes: &[u8]) -> String {
188    let mut s = String::with_capacity(bytes.len() * 2);
189    for b in bytes { s.push_str(&format!("{b:02x}")); }
190    s
191}
192
193/// Decode a lowercase/uppercase hex string to bytes (ignores malformed tail).
194fn hex_decode(s: &str) -> Vec<u8> {
195    let s = s.trim();
196    (0..s.len() / 2)
197        .filter_map(|i| u8::from_str_radix(s.get(i * 2..i * 2 + 2)?, 16).ok())
198        .collect()
199}
200
201/// Decode a hex string into a fixed 32-byte key (zero-padded / truncated).
202fn hex_to_32(s: &str) -> [u8; 32] {
203    let v = hex_decode(s);
204    let mut out = [0u8; 32];
205    let n = v.len().min(32);
206    out[..n].copy_from_slice(&v[..n]);
207    out
208}
209
210fn tex_rgb(r: f32, g: f32, b: f32) -> u32 {
211    ((r * 255.0) as u32) << 16 | ((g * 255.0) as u32) << 8 | (b * 255.0) as u32
212}
213
214// ─── 3D Perlin Noise (Improved Perlin 2002) ───────────────────────────────────
215
216const PERM: [u8; 512] = [
217    151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,
218    140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,
219    247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,
220    57,177,33,88,237,149,56,87,174,35,63,189,114,56,42,123,
221    165,38,72,93,69,139,138,78,149,159,56,89,152,78,61,140,
222    63,26,142,76,124,132,72,11,90,44,82,59,96,41,148,126,
223    157,13,49,27,176,33,47,14,97,78,71,40,87,183,4,122,
224    92,7,72,3,246,17,225,87,91,106,203,190,57,74,76,88,
225    207,208,239,170,251,67,77,51,133,69,249,2,127,80,60,159,
226    168,81,163,64,143,146,157,56,245,188,182,218,33,16,255,243,
227    210,205,12,19,236,95,151,68,23,196,167,126,61,100,93,25,
228    115,96,129,79,220,34,42,144,136,70,238,184,20,222,94,11,
229    219,224,50,58,10,73,6,36,92,194,211,172,98,145,149,228,
230    121,231,200,55,109,141,213,78,169,108,86,244,234,101,122,174,
231    8,186,120,37,46,28,166,180,198,232,221,116,31,75,189,139,
232    138,112,62,181,102,72,3,246,14,97,53,87,185,134,193,29,
233    158,225,248,152,17,105,217,142,148,155,30,135,233,206,85,40,
234    223,140,161,137,13,191,230,66,104,153,199,167,147,99,179,92,
235    // Duplicate for wrap-around indexing
236    151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,
237    140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,
238    247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,
239    57,177,33,88,237,149,56,87,174,35,63,189,114,56,42,123,
240    165,38,72,93,69,139,138,78,149,159,56,89,152,78,61,140,
241    63,26,142,76,124,132,72,11,90,44,82,59,96,41,148,126,
242    157,13,49,27,176,33,47,14,97,78,71,40,87,183,4,122,
243    92,7,72,3,246,17,225,87,91,106,203,190,57,74,76,88,
244    207,208,239,170,251,67,77,51,133,69,249,2,127,80,60,159,
245    168,81,163,64,143,146,157,56,245,188,182,218,33,16,255,243,
246    210,205,12,19,236,95,151,68,23,196,167,126,61,100,93,25,
247    115,96,129,79,220,34,42,144,136,70,238,184,20,222,94,11,
248    219,224,50,58,10,73,6,36,92,194,211,172,98,145,149,228,
249    121,231,200,55,109,141,213,78,169,108,86,244,234,101,122,174,
250];
251
252fn fade(t: f32) -> f32 {
253    t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
254}
255
256fn grad(hash: u8, x: f32, y: f32, z: f32) -> f32 {
257    let h = hash & 15;
258    let u = if h < 8 { x } else { y };
259    let v = if h < 8 { y } else { z };
260    (if (h & 1) == 0 { u } else { -u }) + (if (h & 2) == 0 { v } else { -v })
261}
262
263fn perlin3(x: f32, y: f32, z: f32) -> f32 {
264    let xi = (x.floor() as i32) & 255;
265    let yi = (y.floor() as i32) & 255;
266    let zi = (z.floor() as i32) & 255;
267
268    let xf = x - x.floor();
269    let yf = y - y.floor();
270    let zf = z - z.floor();
271
272    let u = fade(xf);
273    let v = fade(yf);
274    let w = fade(zf);
275
276    let p0 = PERM[xi as usize] as usize;
277    let p1 = PERM[((xi + 1) & 255) as usize] as usize;
278    let pa = PERM[(p0 + yi as usize) & 255] as usize;
279    let pb = PERM[(p0 + ((yi + 1) & 255) as usize) & 255] as usize;
280    let pc = PERM[(p1 + yi as usize) & 255] as usize;
281    let pd = PERM[(p1 + ((yi + 1) & 255) as usize) & 255] as usize;
282
283    let g000 = grad(PERM[(pa + zi as usize) & 255], xf, yf, zf);
284    let g001 = grad(PERM[(pa + ((zi + 1) & 255) as usize) & 255], xf, yf, zf - 1.0);
285    let g010 = grad(PERM[(pb + zi as usize) & 255], xf, yf - 1.0, zf);
286    let g011 = grad(PERM[(pb + ((zi + 1) & 255) as usize) & 255], xf, yf - 1.0, zf - 1.0);
287    let g100 = grad(PERM[(pc + zi as usize) & 255], xf - 1.0, yf, zf);
288    let g101 = grad(PERM[(pc + ((zi + 1) & 255) as usize) & 255], xf - 1.0, yf, zf - 1.0);
289    let g110 = grad(PERM[(pd + zi as usize) & 255], xf - 1.0, yf - 1.0, zf);
290    let g111 = grad(PERM[(pd + ((zi + 1) & 255) as usize) & 255], xf - 1.0, yf - 1.0, zf - 1.0);
291
292    let l00 = g000 + u * (g100 - g000);
293    let l01 = g001 + u * (g101 - g001);
294    let l10 = g010 + u * (g110 - g010);
295    let l11 = g011 + u * (g111 - g011);
296
297    let l0 = l00 + v * (l10 - l00);
298    let l1 = l01 + v * (l11 - l01);
299
300    l0 + w * (l1 - l0)
301}
302
303fn fast_rand_f64(state: &mut u64) -> f64 {
304    *state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
305    ((*state >> 32) as u32) as f64 / 4294967296.0
306}
307
308// ─── Circle Drawing Primitives ────────────────────────────────────────────────
309
310/// Write one pixel into the framebuffer (normal or additive blend).
311#[inline]
312fn put_px(buf: &mut [u32], idx: usize, color: u32, blend: u8) {
313    if idx >= buf.len() { return; }
314    if blend == 0 {
315        buf[idx] = color;
316    } else {
317        let old = buf[idx];
318        let r = (((old >> 16) & 255) + ((color >> 16) & 255)).min(255);
319        let g = (((old >> 8) & 255) + ((color >> 8) & 255)).min(255);
320        let b = ((old & 255) + (color & 255)).min(255);
321        buf[idx] = (r << 16) | (g << 8) | b;
322    }
323}
324
325fn draw_circle_outline(buf: &mut [u32], w: i32, h: i32, cx: i32, cy: i32, r: i32, color: u32, blend: u8) {
326    let mut x = 0;
327    let mut y = r;
328    let mut d = 3 - 2 * r;
329    while x <= y {
330        plot_circle_points(buf, w, h, cx, cy, x, y, color, blend);
331        if d < 0 {
332            d += 4 * x + 6;
333        } else {
334            d += 4 * (x - y) + 10;
335            y -= 1;
336        }
337        x += 1;
338    }
339}
340
341fn plot_circle_points(buf: &mut [u32], w: i32, h: i32, cx: i32, cy: i32, x: i32, y: i32, color: u32, blend: u8) {
342    let points = [(cx+x, cy+y), (cx-x, cy+y), (cx+x, cy-y), (cx-x, cy-y),
343                  (cx+y, cy+x), (cx-y, cy+x), (cx+y, cy-x), (cx-y, cy-x)];
344    for &(px, py) in &points {
345        if px >= 0 && px < w && py >= 0 && py < h {
346            put_px(buf, (py * w + px) as usize, color, blend);
347        }
348    }
349}
350
351fn draw_circle_filled(buf: &mut [u32], w: i32, h: i32, cx: i32, cy: i32, r: i32, color: u32, blend: u8) {
352    if r <= 0 { return; }
353    for dy in -r..=r {
354        let dx_max = ((r*r - dy*dy) as f64).sqrt() as i32;
355        let py = cy + dy;
356        if py < 0 || py >= h { continue; }
357        for dx in -dx_max..=dx_max {
358            let px = cx + dx;
359            if px >= 0 && px < w {
360                put_px(buf, (py * w + px) as usize, color, blend);
361            }
362        }
363    }
364}
365
366#[cfg(test)]
367mod draw_tests {
368    use super::*;
369
370    #[test]
371    fn filled_circle_actually_writes_pixels() {
372        let mut buf = vec![0u32; 100 * 100];
373        draw_circle_filled(&mut buf, 100, 100, 50, 50, 10, 0xFF00FF, 0);
374        assert_eq!(buf[50 * 100 + 50], 0xFF00FF, "centre pixel must be filled");
375        assert_eq!(buf[0], 0, "far corner must stay clear");
376        let n = buf.iter().filter(|&&p| p != 0).count();
377        assert!(n > 200 && n < 500, "r=10 disc area ≈ 314, got {n}");
378    }
379
380    #[test]
381    fn circle_outline_writes_a_ring() {
382        let mut buf = vec![0u32; 100 * 100];
383        draw_circle_outline(&mut buf, 100, 100, 50, 50, 20, 0x00FF00, 0);
384        assert_eq!(buf[50 * 100 + 50], 0, "outline must NOT fill the centre");
385        assert!(buf.iter().any(|&p| p == 0x00FF00), "outline must draw a ring");
386    }
387
388    #[test]
389    fn additive_blend_accumulates_channels() {
390        let mut buf = vec![0x202020u32; 1];
391        put_px(&mut buf, 0, 0x404040, 1);
392        assert_eq!(buf[0], 0x606060);
393    }
394}
395
396// ─── Interpreter ─────────────────────────────────────────────────────────────
397
398/// Customizable colour palette for the vector UI toolkit (packed 0x00RRGGBB).
399/// `ui_theme(...)` sets it; every widget falls back to these and accepts a
400/// trailing `r,g,b` override.
401#[derive(Clone, Copy)]
402pub struct UiTheme {
403    pub primary: u32,
404    pub accent:  u32,
405    pub track:   u32,
406    pub warn:    u32,
407    pub text:    u32,
408    pub bg:      u32,
409}
410
411impl Default for UiTheme {
412    fn default() -> Self {
413        Self {
414            primary: 0x00D2FF, // holographic cyan
415            accent:  0x28FFB4, // mint
416            track:   0x2C3E64, // dim slate
417            warn:    0xFF5A5A, // alert red
418            text:    0xBEEBFF, // pale cyan
419            bg:      0x0A1018, // near-black panel
420        }
421    }
422}
423
424pub struct Interpreter {
425    globals:   HashMap<String, Expr>,
426    functions: HashMap<String, FnDef>,
427    _modules:  HashMap<String, Vec<FnDef>>,
428    gfx:       RefCell<GfxState>,
429    svg:       RefCell<Option<SvgWriter>>,
430    /// Directory of the primary source file, for relative `use` resolution.
431    pub source_dir: Option<std::path::PathBuf>,
432    /// Files already loaded — prevents circular imports.
433    loaded_files: std::collections::HashSet<String>,
434    /// Optional audio engine — `None` if no audio device is available.
435    #[cfg(not(target_arch = "wasm32"))]
436    audio:     Option<AudioEngine>,
437    #[cfg(not(target_arch = "wasm32"))]
438    fft:       RefCell<FftAnalyzer>,
439    fft_bands_cache: RefCell<Vec<f32>>,
440    /// Real-time clock — initialized at startup
441    start_time: std::time::Instant,
442    /// Frame counter — incremented at each present()
443    frame_num: u64,
444    /// Random state for rand() builtin (xorshift)
445    rand_state: u64,
446    /// Microphone input (Phase 1 audio reactivity)
447    #[cfg(not(target_arch = "wasm32"))]
448    mic: Option<ling_mic::MicInput>,
449    /// Persistent KEM keypairs (knot / hybrid identities), referenced by handle.
450    #[cfg(not(target_arch = "wasm32"))]
451    crypto_ids: Vec<ling_crypto::KnotIdentity>,
452    /// Editable text-input buffer (ling-ui text fields).
453    text_buffer: String,
454    /// Frame counter for record_frame().
455    record_n: u32,
456    /// Accumulated microphone samples (for turning sound into crypto donuts).
457    #[cfg(not(target_arch = "wasm32"))]
458    mic_buffer: Vec<f32>,
459    /// Loaded vector UI fonts, referenced by handle (index) from `font_load`.
460    #[cfg(not(target_arch = "wasm32"))]
461    fonts: Vec<ling_graphics::VectorFont>,
462    /// Customizable UI colour palette (set via `ui_theme`).
463    ui_theme: UiTheme,
464    /// Left-mouse state on the previous frame — for widget click-edge detection.
465    mouse_was_down: bool,
466    /// Live music engine (decode playback + GM synth) — lazily initialised.
467    #[cfg(not(target_arch = "wasm32"))]
468    music: Option<ling_music::MusicEngine>,
469    #[cfg(not(target_arch = "wasm32"))]
470    music_init: bool,
471    /// Decoded tracks (for analysis + playback), by `music_load` handle.
472    #[cfg(not(target_arch = "wasm32"))]
473    tracks: Vec<ling_music::DecodedAudio>,
474    /// Parsed `.lrc` lyrics, by `music_lrc` handle.
475    #[cfg(not(target_arch = "wasm32"))]
476    lyrics: Vec<ling_music::Lyrics>,
477    /// Parsed MIDI songs, by `music_midi_load` handle.
478    #[cfg(not(target_arch = "wasm32"))]
479    midis: Vec<ling_music::MidiSong>,
480    /// Soft bodies (deformable balls), by `soft_ball` handle.
481    soft_bodies: Vec<ling_physics::soft::SoftBody>,
482    /// Rigid-body world (angular dynamics), shared by `rb_*`.
483    rigid_world: ling_physics::rigid::PhysicsWorld,
484    /// Liquid grids (water/oil), by `liquid_new` handle.
485    liquids: Vec<ling_physics::liquid::LiquidGrid>,
486}
487
488impl Interpreter {
489    pub fn new() -> Self {
490        #[cfg(not(target_arch = "wasm32"))]
491        let audio = AudioEngine::new()
492            .map_err(|e| eprintln!("audio init failed (no sound): {e}"))
493            .ok();
494        Self {
495            globals:   HashMap::new(),
496            functions: HashMap::new(),
497            _modules:  HashMap::new(),
498            gfx:       RefCell::new(GfxState::new()),
499            svg:       RefCell::new(None),
500            source_dir: None,
501            loaded_files: std::collections::HashSet::new(),
502            #[cfg(not(target_arch = "wasm32"))]
503            audio,
504            #[cfg(not(target_arch = "wasm32"))]
505            fft: RefCell::new(FftAnalyzer::new(2048, 44100)),
506            fft_bands_cache: RefCell::new(vec![]),
507            start_time: std::time::Instant::now(),
508            frame_num: 0,
509            rand_state: 0x123456789ABCDEF,
510            #[cfg(not(target_arch = "wasm32"))]
511            mic: None,
512            #[cfg(not(target_arch = "wasm32"))]
513            crypto_ids: Vec::new(),
514            text_buffer: String::new(),
515            record_n: 0,
516            #[cfg(not(target_arch = "wasm32"))]
517            mic_buffer: Vec::new(),
518            #[cfg(not(target_arch = "wasm32"))]
519            fonts: Vec::new(),
520            ui_theme: UiTheme::default(),
521            mouse_was_down: false,
522            #[cfg(not(target_arch = "wasm32"))]
523            music: None,
524            #[cfg(not(target_arch = "wasm32"))]
525            music_init: false,
526            #[cfg(not(target_arch = "wasm32"))]
527            tracks: Vec::new(),
528            #[cfg(not(target_arch = "wasm32"))]
529            lyrics: Vec::new(),
530            #[cfg(not(target_arch = "wasm32"))]
531            midis: Vec::new(),
532            soft_bodies: Vec::new(),
533            rigid_world: ling_physics::rigid::PhysicsWorld::new(),
534            liquids: Vec::new(),
535        }
536    }
537
538    /// Lazily start the music engine on first use (playback/synth need a device;
539    /// analysis/decoding do not). Returns `false` if no audio device is available.
540    #[cfg(not(target_arch = "wasm32"))]
541    fn ensure_music(&mut self) -> bool {
542        if self.music.is_some() { return true; }
543        if self.music_init { return false; }
544        self.music_init = true;
545        match ling_music::MusicEngine::new() {
546            Ok(m) => { self.music = Some(m); true }
547            Err(e) => { eprintln!("music engine init failed (no music playback): {e}"); false }
548        }
549    }
550
551    /// Lay out `text` for font `id` at size `px`, returning every glyph contour as
552    /// a screen-space polyline (x→right, y→down). `(x, y)` is the text box top-left;
553    /// the baseline is placed `ascent*px` below it. Curves are flattened to 0.3 px.
554    #[cfg(not(target_arch = "wasm32"))]
555    fn font_layout_2d(&mut self, id: usize, x: f32, y: f32, px: f32, text: &str) -> Vec<Vec<[f32; 2]>> {
556        let mut out = Vec::new();
557        for g in self.font_layout_2d_glyphs(id, x, y, px, text) { out.extend(g); }
558        out
559    }
560
561    /// Same as [`font_layout_2d`] but grouped per glyph (so a fill can apply the
562    /// non-zero winding rule within each glyph, preserving interior holes).
563    #[cfg(not(target_arch = "wasm32"))]
564    fn font_layout_2d_glyphs(&mut self, id: usize, x: f32, y: f32, px: f32, text: &str) -> Vec<Vec<Vec<[f32; 2]>>> {
565        let font = &mut self.fonts[id];
566        let asc = font.ascent();
567        let tol = 0.3 / px;
568        let mut pen = 0.0f32;
569        let mut glyphs = Vec::new();
570        for ch in text.chars() {
571            let go = font.glyph_outline(ch, tol);
572            let mut contours = Vec::with_capacity(go.polylines.len());
573            for pl in &go.polylines {
574                let mapped: Vec<[f32; 2]> = pl.iter()
575                    .map(|p| [x + (pen + p[0]) * px, y + (asc - p[1]) * px])
576                    .collect();
577                contours.push(mapped);
578            }
579            glyphs.push(contours);
580            pen += go.advance;
581        }
582        glyphs
583    }
584
585    pub fn run_program(&mut self, program: &Program) -> Result<(), String> {
586        for item in &program.items {
587            self.register_item("", item)?;
588        }
589        let entry = self.find_entry()
590            .ok_or("no entry point — need `bind start = do {...}` or `ผูก เริ่ม = ทำ {...}`")?;
591        // Seed the entry env with non-Do globals so top-level `令` bindings
592        // are visible in the main Do block (same two-pass logic as call_named).
593        let mut env = Env::new();
594        let non_do: Vec<_> = self.globals.iter()
595            .filter(|(_, e)| !matches!(e, Expr::Do(_)))
596            .map(|(k, e)| (k.clone(), e.clone()))
597            .collect();
598        let mut pending: Vec<(String, Expr)> = Vec::new();
599        for (k, expr) in &non_do {
600            let mut tmp = Env::new();
601            if let Ok(v) = self.eval_expr(expr, &mut tmp) {
602                env.insert(k.clone(), v);
603            } else {
604                pending.push((k.clone(), expr.clone()));
605            }
606        }
607        for (k, expr) in &pending {
608            let mut tmp = env.clone();
609            if let Ok(v) = self.eval_expr(expr, &mut tmp) {
610                env.insert(k.clone(), v);
611            }
612        }
613        self.eval_expr(&entry, &mut env).map(|_| ()).map_err(|e| match e {
614            EvalErr::Runtime(s) => s,
615            EvalErr::Return(_)  => "unexpected top-level return".to_string(),
616            EvalErr::Break      => "unexpected break at top level".to_string(),
617        })
618    }
619
620    fn register_item(&mut self, ns: &str, item: &Item) -> Result<(), String> {
621        match item {
622            Item::Bind(name, expr) => {
623                let key = if ns.is_empty() { name.clone() } else { format!("{ns}::{name}") };
624                self.globals.insert(key, expr.clone());
625            }
626            Item::Fn(def) => {
627                let key = if ns.is_empty() { def.name.clone() } else { format!("{ns}::{}", def.name) };
628                self.functions.insert(key, def.clone());
629            }
630            Item::Mod(name, body) => {
631                let child_ns = if ns.is_empty() { name.clone() } else { format!("{ns}::{name}") };
632                for child in body {
633                    self.register_item(&child_ns, child)?;
634                }
635            }
636            Item::TypeAlias(_, _) => {}
637            Item::Use { path, alias } => {
638                self.load_module(path, alias.as_deref(), ns)?;
639            }
640        }
641        Ok(())
642    }
643
644    /// Resolve `path` relative to `source_dir`, load and parse it, then
645    /// register all its definitions.  If `alias` is given, every name is
646    /// prefixed with `<parent_ns>::<alias>`.  Circular imports are silently
647    /// skipped.
648    fn load_module(&mut self, path: &str, alias: Option<&str>, parent_ns: &str) -> Result<(), String> {
649        // Build candidate file paths (.ling extension variants)
650        let base_dir = self.source_dir.clone().unwrap_or_else(|| std::path::PathBuf::from("."));
651        let raw = std::path::Path::new(path);
652        let candidates: Vec<std::path::PathBuf> = vec![
653            base_dir.join(format!("{}.ling", path)),
654            base_dir.join(format!("{}.灵", path)),
655            base_dir.join(format!("{}.령", path)),
656            base_dir.join(format!("{}.霊", path)),
657            base_dir.join(format!("{}.ลิง", path)),
658            // exact path if already has extension
659            base_dir.join(raw),
660            std::path::PathBuf::from(format!("{}.ling", path)),
661            std::path::PathBuf::from(path),
662        ];
663
664        let resolved = candidates.into_iter().find(|p| p.exists())
665            .ok_or_else(|| format!("use: cannot find module '{path}'"))?;
666
667        let canonical = resolved.canonicalize()
668            .unwrap_or_else(|_| resolved.clone())
669            .to_string_lossy()
670            .to_string();
671
672        // Skip if already loaded (circular import guard)
673        if self.loaded_files.contains(&canonical) {
674            return Ok(());
675        }
676        self.loaded_files.insert(canonical.clone());
677
678        let source = std::fs::read_to_string(&resolved)
679            .map_err(|e| format!("use: failed to read '{path}': {e}"))?;
680
681        // Save/restore source_dir for nested relative imports
682        let prev_dir = self.source_dir.clone();
683        self.source_dir = resolved.parent().map(|p| p.to_path_buf());
684
685        let program = crate::parser::parse(&source)
686            .map_err(|e| format!("use: parse error in '{path}': {e}"))?;
687
688        // Compute target namespace: parent_ns :: alias (or just alias, or just parent_ns)
689        let target_ns = match (parent_ns.is_empty(), alias) {
690            (_, Some(a)) if !parent_ns.is_empty() => format!("{parent_ns}::{a}"),
691            (_, Some(a)) => a.to_string(),
692            (false, None) => parent_ns.to_string(),
693            (true,  None) => String::new(),
694        };
695
696        for item in &program.items {
697            self.register_item(&target_ns, item)?;
698        }
699
700        self.source_dir = prev_dir;
701        Ok(())
702    }
703
704    fn find_entry(&self) -> Option<Expr> {
705        // Try all known entry-point names in multiple human languages
706        for key in &[
707            "start", "main",
708            "启",
709            "เริ่ม",           // Thai
710            "시작",
711            "начать", "начало",
712            "inicio", "comenzar",
713            "début", "commencer",
714            "anfang", "starten",
715            "início",
716            "शुरू",
717            "ابدأ",
718        ] {
719            if let Some(e) = self.globals.get(*key) { return Some(e.clone()); }
720        }
721        self.globals.values().find(|e| matches!(e, Expr::Do(_))).cloned()
722    }
723
724    // ─── Expression evaluation ────────────────────────────────────────────────
725
726    fn eval_expr(&mut self, expr: &Expr, env: &mut Env) -> EvalResult {
727        match expr {
728            Expr::Str(s)    => Ok(Value::Str(s.clone())),
729            Expr::Number(n) => Ok(Value::Number(*n)),
730            Expr::Bool(b)   => Ok(Value::Bool(*b)),
731            Expr::Unit      => Ok(Value::Unit),
732            Expr::Array(elems) => {
733                let vs: Vec<_> = elems.iter()
734                    .map(|e| self.eval_expr(e, env))
735                    .collect::<Result<_,_>>()?;
736                Ok(Value::List(vs))
737            }
738
739            Expr::Ident(name) => self.lookup(name, env),
740
741            Expr::Path(segs) => {
742                if segs.len() == 1 { return self.lookup(&segs[0], env); }
743                Ok(Value::Str(segs.join("::")))
744            }
745
746            Expr::Ref(inner) => self.eval_expr(inner, env),
747            Expr::Await(inner) => self.eval_expr(inner, env),
748
749            Expr::Do(stmts) => {
750                let mut local = env.clone();
751                Ok(self.exec_block(stmts, &mut local)?.unwrap_or(Value::Unit))
752            }
753
754            Expr::BinOp(op, lhs, rhs) => {
755                let l = self.eval_expr(lhs, env)?;
756                let r = self.eval_expr(rhs, env)?;
757                self.apply_binop(op, l, r)
758            }
759
760            Expr::If { cond, then, elseifs, else_body } => {
761                let cond_val = self.eval_expr(cond, env)?;
762                if self.is_truthy(&cond_val) {
763                    return Ok(self.exec_block(then, env)?.unwrap_or(Value::Unit));
764                }
765                for (ei_cond, ei_body) in elseifs {
766                    let ei_cond_val = self.eval_expr(ei_cond, env)?;
767                    if self.is_truthy(&ei_cond_val) {
768                        return Ok(self.exec_block(ei_body, env)?.unwrap_or(Value::Unit));
769                    }
770                }
771                if let Some(eb) = else_body {
772                    return Ok(self.exec_block(eb, env)?.unwrap_or(Value::Unit));
773                }
774                Ok(Value::Unit)
775            }
776
777            Expr::While { cond, body } => {
778                // Run the body directly in the *outer* env so that
779                // `bind counter = counter + 1` persists across iterations,
780                // which is the expected behaviour in a scripting language.
781                loop {
782                    let cv = self.eval_expr(cond, env)?;
783                    if !self.is_truthy(&cv) { break; }
784                    match self.exec_block(body, env) {
785                        Ok(_) => {}
786                        Err(EvalErr::Break) => break,
787                        Err(e) => return Err(e),
788                    }
789                }
790                Ok(Value::Unit)
791            }
792
793            Expr::For { var, iter, body } => {
794                let iter_val = self.eval_expr(iter, env)?;
795                let items = self.value_to_iter(iter_val)?;
796                for item in items {
797                    let mut local = env.clone();
798                    local.insert(var.clone(), item);
799                    match self.exec_block(body, &mut local) {
800                        Ok(_) => {}
801                        Err(EvalErr::Break) => break,
802                        Err(e) => return Err(e),
803                    }
804                }
805                Ok(Value::Unit)
806            }
807
808            Expr::Match(subject, arms) => {
809                let subj = self.eval_expr(subject, env)?;
810                for arm in arms {
811                    if let Some(bindings) = self.match_pattern(&arm.pattern, &subj) {
812                        let mut local = env.clone();
813                        local.extend(bindings);
814                        return self.eval_expr(&arm.body, &mut local);
815                    }
816                }
817                Ok(Value::Unit)
818            }
819
820            Expr::Range(lo, hi) => {
821                let lo_v = self.eval_expr(lo, env)?;
822                let hi_v = self.eval_expr(hi, env)?;
823                let lo_n = self.to_number(&lo_v)? as i64;
824                let hi_n = self.to_number(&hi_v)? as i64;
825                Ok(Value::List((lo_n..hi_n).map(|i| Value::Number(i as f64)).collect()))
826            }
827
828            Expr::Index(base, idx) => {
829                let b = self.eval_expr(base, env)?;
830                let i = self.eval_expr(idx, env)?;
831                let n = self.to_number(&i)? as usize;
832                match b {
833                    Value::List(v) => v.get(n).cloned()
834                        .ok_or_else(|| EvalErr::from(format!("index {n} out of bounds"))),
835                    Value::Str(s)  => s.chars().nth(n)
836                        .map(|c| Value::Str(c.to_string()))
837                        .ok_or_else(|| EvalErr::from(format!("index {n} out of bounds"))),
838                    other => Err(EvalErr::from(format!("cannot index {:?}", other))),
839                }
840            }
841
842            Expr::Call(callee, args) => {
843                let arg_vals: Vec<Value> = args.iter()
844                    .map(|a| self.eval_expr(a, env))
845                    .collect::<Result<_,_>>()?;
846                match callee.as_ref() {
847                    Expr::Ident(name) => self.call_named(name, arg_vals, env),
848                    Expr::Path(segs)  => self.call_named(&segs.join("::"), arg_vals, env),
849                    _ => {
850                        let v = self.eval_expr(callee, env)?;
851                        self.call_value(v, arg_vals)
852                    }
853                }
854            }
855
856            Expr::MethodCall { receiver, method, args } => {
857                let recv = self.eval_expr(receiver, env)?;
858                let arg_vals: Vec<Value> = args.iter()
859                    .map(|a| self.eval_expr(a, env))
860                    .collect::<Result<_,_>>()?;
861                self.call_method(recv, method, arg_vals)
862            }
863
864            Expr::Closure(params, body) => {
865                Ok(Value::Fn(params.clone(), vec![Stmt::Expr(*body.clone())], env.clone()))
866            }
867        }
868    }
869
870    // ─── Block execution ─────────────────────────────────────────────────────
871
872    fn exec_block(&mut self, stmts: &[Stmt], env: &mut Env) -> Result<Option<Value>, EvalErr> {
873        let mut last: Option<Value> = None;
874        for stmt in stmts {
875            match stmt {
876                Stmt::Bind(name, expr) => {
877                    let v = self.eval_expr(expr, env)?;
878                    env.insert(name.clone(), v);
879                    last = None;
880                }
881                Stmt::Return(expr) => {
882                    let v = self.eval_expr(expr, env)?;
883                    return Err(EvalErr::Return(v));
884                }
885                Stmt::Expr(expr) => {
886                    last = Some(self.eval_expr(expr, env)?);
887                }
888            }
889        }
890        Ok(last)
891    }
892
893    // ─── Dispatch helpers ─────────────────────────────────────────────────────
894
895    fn lookup(&self, name: &str, env: &Env) -> EvalResult {
896        if let Some(v) = env.get(name) { return Ok(v.clone()); }
897        if self.functions.contains_key(name) {
898            let def = &self.functions[name];
899            return Ok(Value::Fn(def.params.clone(), def.body.clone(), Env::new()));
900        }
901        // Math constants usable as plain identifiers (e.g. `sin(pi)`)
902        match name {
903            "pi" | "π" | "พาย" | "圆周率" | "円周率" | "파이" => return Ok(Value::Number(std::f64::consts::PI)),
904            "tau" | "τ" | "双周率" | "タウ" | "타우" | "ทาว"        => return Ok(Value::Number(std::f64::consts::TAU)),
905            _ => {}
906        }
907        Err(EvalErr::from(format!("undefined: '{name}'")))
908    }
909
910    fn call_named(&mut self, name: &str, args: Vec<Value>, env: &Env) -> EvalResult {
911        match name {
912            // ── Print ──
913            "print" | "println" | "印" | "打印" | "印刷" | "พิมพ์" | "출력" | "вывести" | "imprimir" | "afficher" => {
914                let s = args.iter().map(|v| v.to_string()).collect::<Vec<_>>().join("");
915                println!("{s}");
916                return Ok(Value::Unit);
917            }
918            // ── Format ──
919            "format" | "格式" | "フォーマット" | "서식" | "รูปแบบ" | "форматировать" | "formatear" | "formater" => {
920                return Ok(Value::Str(self.builtin_format(&args)?));
921            }
922            // ── String join / concatenation ──
923            "格式::拼接" | "format::join" => {
924                match args.first() {
925                    Some(Value::List(items)) => {
926                        return Ok(Value::Str(items.iter().map(|v| v.to_string()).collect()));
927                    }
928                    _ => return Ok(Value::Str(self.builtin_format(&args)?)),
929                }
930            }
931            // ── Result constructors ──
932            "ok" | "好" | "良し" | "좋아" | "โอเค" => {
933                let val = args.into_iter().next().unwrap_or(Value::Unit);
934                return Ok(Value::Ok(Box::new(val)));
935            }
936            "bad" | "坏" | "err" | "悪い" | "나쁨" | "ผิด" => {
937                let val = args.into_iter().next().unwrap_or(Value::Unit);
938                return Ok(Value::Err(Box::new(val)));
939            }
940            // ── Vec constructors ──
941            "向量::从" | "Vec::from" => {
942                if let Some(Value::List(v)) = args.first() {
943                    return Ok(Value::List(v.clone()));
944                }
945                return Ok(Value::List(args));
946            }
947            "向量::有容量" | "Vec::with_capacity" => return Ok(Value::List(Vec::new())),
948            // ── Timer stubs ──
949            "计时::获取当前小时" | "Timer::hour" => return Ok(Value::Number(14.0)),
950            "计时::现在" | "Timer::now"          => return Ok(Value::Number(1000.0)),
951            // ── Sleep ──
952            "sleep" | "หยุด" | "นอน" | "sleep_ms" | "睡眠" | "眠る" | "スリープ" | "잠자기" | "잠" | "流水::睡眠" | "Flow::sleep" => {
953                if let Some(ms_val) = args.first() {
954                    if let Ok(ms) = self.to_number(ms_val) {
955                        std::thread::sleep(std::time::Duration::from_millis(ms as u64));
956                    }
957                }
958                return Ok(Value::Unit);
959            }
960            // ── Flow::parallel stub ──
961            "流水::并行" | "Flow::parallel" => {
962                if let Some(Value::Fn(params, body, mut cap)) = args.first().cloned() {
963                    let _ = params;
964                    match self.exec_block(&body, &mut cap) {
965                        Ok(Some(v)) => return Ok(v),
966                        Ok(None) => return Ok(Value::Unit),
967                        Err(EvalErr::Return(v)) => return Ok(v),
968                        Err(e) => return Err(e),
969                    }
970                }
971                return Ok(Value::Unit);
972            }
973
974            // ══════════════════════════════════════════════════════════════════
975            // MATH BUILTINS  (all args and results are f64)
976            // Thai aliases: ไซน์ โคไซน์ แทนเจนต์ รากที่สอง ค่าสัมบูรณ์
977            //               ปัดลง ปัดขึ้น ปัดเศษ ตัดทศนิยม ต่ำสุด สูงสุด
978            //               จำกัด ยกกำลัง ลอการิทึม พาย
979            // ══════════════════════════════════════════════════════════════════
980
981            // ── Trigonometry (input in radians) ──
982            "sin" | "ไซน์" | "正弦" | "サイン" | "사인" => {
983                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.sin()));
984            }
985            "cos" | "โคไซน์" | "余弦" | "コサイン" | "코사인" => {
986                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.cos()));
987            }
988
989            // ── Hyperbolic functions ──
990            // Hyperbolic tangent
991            "tanh" | "tanhf" | "双曲正切" | "双曲線正接" | "쌍곡탄젠트" => {
992                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.tanh()));
993            }
994
995
996            "tan" | "แทนเจนต์" | "正切" | "タンジェント" | "탄젠트" => {
997                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.tan()));
998            }
999            "asin" | "arcsin" | "反正弦" | "アークサイン" | "아크사인" | "อาร์กไซน์" => {
1000                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.asin()));
1001            }
1002            "acos" | "arccos" | "反余弦" | "アークコサイン" | "아크코사인" | "อาร์กโคไซน์" => {
1003                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.acos()));
1004            }
1005            "atan" | "arctan" | "反正切" | "アークタンジェント" | "아크탄젠트" | "อาร์กแทนเจนต์" => {
1006                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.atan()));
1007            }
1008            "atan2" | "arctan2" | "反正切2" | "アークタンジェント2" | "아크탄젠트2" => {
1009                let y = self.arg_num(&args, 0, 0.0)?;
1010                let x = self.arg_num(&args, 1, 1.0)?;
1011                return Ok(Value::Number(y.atan2(x)));
1012            }
1013
1014            // ── Roots / powers ──
1015            "sqrt" | "รากที่สอง" | "平方根" | "根" | "제곱근" => {
1016                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.sqrt()));
1017            }
1018            "cbrt" | "立方根" | "세제곱근" | "รากที่สาม" => {
1019                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.cbrt()));
1020            }
1021            "pow" | "ยกกำลัง" | "幂" | "べき乗" | "거듭제곱" => {
1022                let base = self.arg_num(&args, 0, 0.0)?;
1023                let exp  = self.arg_num(&args, 1, 1.0)?;
1024                return Ok(Value::Number(base.powf(exp)));
1025            }
1026            "exp" | "指数" | "指数関数" | "지수" => {
1027                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.exp()));
1028            }
1029            "hypot" | "斜边" | "斜辺" | "빗변" => {
1030                let x = self.arg_num(&args, 0, 0.0)?;
1031                let y = self.arg_num(&args, 1, 0.0)?;
1032                return Ok(Value::Number(x.hypot(y)));
1033            }
1034
1035            // ── Logarithms ──
1036            "ln" | "log" | "ลอการิทึม" | "对数" | "対数" | "로그" => {
1037                return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.ln()));
1038            }
1039            "log2" | "对数2" | "対数2" | "로그2" => {
1040                return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.log2()));
1041            }
1042            "log10" | "对数10" | "対数10" | "로그10" => {
1043                return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.log10()));
1044            }
1045
1046            // ── Rounding / truncation ──
1047            "abs" | "ค่าสัมบูรณ์" | "绝对值" | "绝对" | "絶対値" | "절댓값" | "절대값" => {
1048                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.abs()));
1049            }
1050            "floor" | "ปัดลง" | "向下取整" | "下整" | "床関数" | "내림" => {
1051                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.floor()));
1052            }
1053            "ceil" | "ปัดขึ้น" | "向上取整" | "上整" | "天井関数" | "올림" => {
1054                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.ceil()));
1055            }
1056            "round" | "ปัดเศษ" | "四舍五入" | "四舍" | "四捨五入" | "반올림" => {
1057                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.round()));
1058            }
1059            "trunc" | "int" | "ตัดทศนิยม" | "取整" | "整数化" | "整数" | "截整"
1060                    | "정수화" | "정수" | "切り捨て" | "버림" => {
1061                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.trunc()));
1062            }
1063            "fract" | "小数部分" | "小数部" | "소수부" => {
1064                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.fract()));
1065            }
1066
1067            // ── min / max / clamp ──
1068            "min" | "ต่ำสุด" | "最小" | "최솟값" => {
1069                let a = self.arg_num(&args, 0, 0.0)?;
1070                let b = self.arg_num(&args, 1, 0.0)?;
1071                return Ok(Value::Number(a.min(b)));
1072            }
1073            "max" | "สูงสุด" | "最大" | "최댓값" => {
1074                let a = self.arg_num(&args, 0, 0.0)?;
1075                let b = self.arg_num(&args, 1, 0.0)?;
1076                return Ok(Value::Number(a.max(b)));
1077            }
1078            "clamp" | "จำกัด" | "截取" | "範囲制限" | "범위제한" => {
1079                let x  = self.arg_num(&args, 0, 0.0)?;
1080                let lo = self.arg_num(&args, 1, 0.0)?;
1081                let hi = self.arg_num(&args, 2, 1.0)?;
1082                return Ok(Value::Number(x.clamp(lo, hi)));
1083            }
1084
1085            // ── Constants (also accessible as plain identifiers via lookup) ──
1086            "pi" | "π" | "พาย" | "圆周率" | "円周率" | "파이" => return Ok(Value::Number(std::f64::consts::PI)),
1087            "tau" | "τ" | "双周率" | "タウ" | "타우" | "ทาว"        => return Ok(Value::Number(std::f64::consts::TAU)),
1088
1089            // ══════════════════════════════════════════════════════════════════
1090            // PHASE 1: DMT TRIP CODER FEATURES
1091            // ══════════════════════════════════════════════════════════════════
1092
1093            // ── Step 1: Noise Functions ──
1094            "vnoise" | "noise2" | "นอยส์2ดี" | "柏林噪声2D" | "バリューノイズ2D" | "값노이즈2D" => {
1095                let x = self.arg_num(&args, 0, 0.0)? as f32;
1096                let y = self.arg_num(&args, 1, 0.0)? as f32;
1097                let seed = self.arg_num(&args, 2, 0.0)? as u32;
1098                return Ok(Value::Number(tex_vnoise(x, y, seed) as f64));
1099            }
1100
1101            "fbm" | "นอยส์ออร์แกนิก" | "分形噪声" | "フラクタルノイズ" | "프랙탈노이즈" => {
1102                let x = self.arg_num(&args, 0, 0.0)? as f32;
1103                let y = self.arg_num(&args, 1, 0.0)? as f32;
1104                let octaves = self.arg_num(&args, 2, 4.0)? as u32;
1105                let seed = self.arg_num(&args, 3, 0.0)? as u32;
1106                return Ok(Value::Number(tex_fbm(x, y, octaves, seed) as f64));
1107            }
1108
1109            "perlin" | "perlin3" | "เพอร์ลิน3ดี" | "柏林噪声3D" | "パーリンノイズ3D" | "펄린노이즈3D" => {
1110                let x = self.arg_num(&args, 0, 0.0)? as f32;
1111                let y = self.arg_num(&args, 1, 0.0)? as f32;
1112                let z = self.arg_num(&args, 2, 0.0)? as f32;
1113                return Ok(Value::Number(perlin3(x, y, z) as f64));
1114            }
1115
1116            // ── Step 2: Math Ergonomics ──
1117            "lerp" | "ค่าระหว่าง" | "线性插值" | "線形補間" | "선형보간" => {
1118                let a = self.arg_num(&args, 0, 0.0)?;
1119                let b = self.arg_num(&args, 1, 1.0)?;
1120                let t = self.arg_num(&args, 2, 0.0)?;
1121                return Ok(Value::Number(a + (b - a) * t));
1122            }
1123
1124            "smoothstep" | "เปลี่ยนแบบนุ่ม" | "平滑步进" | "スムーズステップ" | "스무스스텝" => {
1125                let lo = self.arg_num(&args, 0, 0.0)?;
1126                let hi = self.arg_num(&args, 1, 1.0)?;
1127                let x = self.arg_num(&args, 2, 0.5)?;
1128                let t = ((x - lo) / (hi - lo)).clamp(0.0, 1.0);
1129                return Ok(Value::Number(t * t * (3.0 - 2.0 * t)));
1130            }
1131
1132            "rand" | "สุ่ม" | "随机" | "乱数" | "난수" => {
1133                let val = fast_rand_f64(&mut self.rand_state);
1134                return Ok(Value::Number(val));
1135            }
1136
1137            "sign" | "เครื่องหมาย" | "符号" | "符号関数" | "부호" => {
1138                let x = self.arg_num(&args, 0, 0.0)?;
1139                return Ok(Value::Number(x.signum()));
1140            }
1141
1142            "hsv_to_rgb" | "เอชเอสวีเป็นRGB" | "HSV转RGB" | "HSV変換RGB" | "HSV변환RGB" => {
1143                let h = self.arg_num(&args, 0, 0.0)?; // 0-360
1144                let s = self.arg_num(&args, 1, 1.0)?; // 0-1
1145                let v = self.arg_num(&args, 2, 1.0)?; // 0-1
1146                let c = v * s;
1147                let x = c * (1.0 - (((h / 60.0) % 2.0) - 1.0).abs());
1148                let m = v - c;
1149                let (r1, g1, b1) = if h < 60.0       { (c, x, 0.0) }
1150                                    else if h < 120.0  { (x, c, 0.0) }
1151                                    else if h < 180.0  { (0.0, c, x) }
1152                                    else if h < 240.0  { (0.0, x, c) }
1153                                    else if h < 300.0  { (x, 0.0, c) }
1154                                    else               { (c, 0.0, x) };
1155                let r = ((r1 + m) * 255.0).round();
1156                let g = ((g1 + m) * 255.0).round();
1157                let b = ((b1 + m) * 255.0).round();
1158                return Ok(Value::List(vec![Value::Number(r), Value::Number(g), Value::Number(b)]));
1159            }
1160
1161            "lerp_color" | "ไล่สี" | "颜色插值" | "色補間" | "색보간" => {
1162                let r1 = self.arg_num(&args, 0, 0.0)?;
1163                let g1 = self.arg_num(&args, 1, 0.0)?;
1164                let b1 = self.arg_num(&args, 2, 0.0)?;
1165                let r2 = self.arg_num(&args, 3, 255.0)?;
1166                let g2 = self.arg_num(&args, 4, 255.0)?;
1167                let b2 = self.arg_num(&args, 5, 255.0)?;
1168                let t = self.arg_num(&args, 6, 0.0)?;
1169                let r = r1 + (r2 - r1) * t;
1170                let g = g1 + (g2 - g1) * t;
1171                let b = b1 + (b2 - b1) * t;
1172                let c = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
1173                self.gfx.borrow_mut().color = c;
1174                return Ok(Value::Unit);
1175            }
1176
1177            // ── Step 3: Real-Time Clock ──
1178            "time_now" | "เวลาปัจจุบัน" | "当前时间" | "経過時間" | "현재시간" => {
1179                return Ok(Value::Number(self.start_time.elapsed().as_secs_f64()));
1180            }
1181
1182            "frame_count" | "เฟรม" | "帧数" | "フレーム数" | "프레임수" => {
1183                return Ok(Value::Number(self.frame_num as f64));
1184            }
1185
1186            // ── Step 4: Microphone Input ──
1187            "mic_open" | "เปิดไมค์" | "开麦克风" | "マイク開く" | "마이크열기" => {
1188                #[cfg(not(target_arch = "wasm32"))]
1189                {
1190                    match ling_mic::MicInput::open(Default::default()) {
1191                        Ok(mic) => {
1192                            let _ = mic.start(|_samples: &[f32]| {});  // No-op callback
1193                            self.mic = Some(mic);
1194                            return Ok(Value::Unit);
1195                        }
1196                        Err(e) => return Err(EvalErr::from(format!("mic_open failed: {e}"))),
1197                    }
1198                }
1199                #[cfg(target_arch = "wasm32")]
1200                return Ok(Value::Unit);
1201            }
1202
1203            "mic_rms" | "เสียงRMS" | "麦克风音量" | "マイクRMS" | "마이크RMS" => {
1204                #[cfg(not(target_arch = "wasm32"))]
1205                {
1206                    let rms = self.mic.as_ref().map(|m: &ling_mic::MicInput| m.rms()).unwrap_or(0.0);
1207                    return Ok(Value::Number(rms as f64));
1208                }
1209                #[cfg(target_arch = "wasm32")]
1210                return Ok(Value::Number(0.0));
1211            }
1212
1213            "mic_peak" | "เสียงพีค" | "麦克风峰值" | "マイクピーク" | "마이크피크" => {
1214                #[cfg(not(target_arch = "wasm32"))]
1215                {
1216                    let peak = self.mic.as_ref().map(|m: &ling_mic::MicInput| m.peak()).unwrap_or(0.0);
1217                    return Ok(Value::Number(peak as f64));
1218                }
1219                #[cfg(target_arch = "wasm32")]
1220                return Ok(Value::Number(0.0));
1221            }
1222
1223            "mic_fft" | "วิเคราะห์เสียงสด" | "实时频谱" | "リアルタイムFFT" | "실시간FFT" => {
1224                #[cfg(not(target_arch = "wasm32"))]
1225                {
1226                    let n = self.arg_num(&args, 0, 8.0)? as usize;
1227                    if let Some(mic) = self.mic.as_ref() {
1228                        let samples = mic.latest_samples();
1229                        self.fft.borrow_mut().push_samples(&samples);
1230                    }
1231                    let bands = self.fft.borrow().freq_bands(n);
1232                    let result = bands.iter().map(|&v| Value::Number(v as f64)).collect();
1233                    return Ok(Value::List(result));
1234                }
1235                #[cfg(target_arch = "wasm32")]
1236                return Ok(Value::List(vec![]));
1237            }
1238
1239            // ── Step 5: Additive Blend Mode ──
1240            "set_blend" | "โหมดผสม" | "混合模式" | "ブレンドモード" | "블렌드모드" => {
1241                let mode = self.arg_num(&args, 0, 0.0)? as u8;
1242                self.gfx.borrow_mut().blend = mode;
1243                return Ok(Value::Unit);
1244            }
1245
1246            // ── Step 6: Circle Primitives ──
1247            "draw_circle" | "วาดวงกลม" | "画圆" | "円描画" | "원그리기" => {
1248                let cx = self.arg_num(&args, 0, 0.0)? as i32;
1249                let cy = self.arg_num(&args, 1, 0.0)? as i32;
1250                let r = self.arg_num(&args, 2, 10.0)? as i32;
1251                let mut gfx = self.gfx.borrow_mut();
1252                let (w, h, color, blend) = (gfx.width as i32, gfx.height as i32, gfx.color, gfx.blend);
1253                draw_circle_outline(&mut gfx.buffer, w, h, cx, cy, r, color, blend);
1254                return Ok(Value::Unit);
1255            }
1256
1257            "draw_filled_circle" | "draw_disc" | "วาดวงกลมทึบ" | "画实心圆" | "塗りつぶし円" | "원채우기" => {
1258                let cx = self.arg_num(&args, 0, 0.0)? as i32;
1259                let cy = self.arg_num(&args, 1, 0.0)? as i32;
1260                let r = self.arg_num(&args, 2, 10.0)? as i32;
1261                let mut gfx = self.gfx.borrow_mut();
1262                let (w, h, color, blend) = (gfx.width as i32, gfx.height as i32, gfx.color, gfx.blend);
1263                draw_circle_filled(&mut gfx.buffer, w, h, cx, cy, r, color, blend);
1264                return Ok(Value::Unit);
1265            }
1266
1267            // ══════════════════════════════════════════════════════════════════
1268            // GRAPHICS BUILTINS
1269            // Thai names first, then English aliases.
1270            // ══════════════════════════════════════════════════════════════════
1271
1272            // ── เปิดหน้าต่าง(width, height, title) — open_window ──
1273            "เปิดหน้าต่าง" | "open_window" | "gfx_window" | "开窗" | "ウィンドウ開く" | "창열기" => {
1274                let w = self.arg_num(&args, 0, 800.0)? as usize;
1275                let h = self.arg_num(&args, 1, 600.0)? as usize;
1276                #[cfg(not(target_arch = "wasm32"))]
1277                {
1278                    let title = args.get(2).map(|v| v.to_string()).unwrap_or_else(|| "Ling".into());
1279                    let mut gfx = self.gfx.borrow_mut();
1280                    let mut win = minifb::Window::new(
1281                        &title, w, h,
1282                        minifb::WindowOptions {
1283                            resize: false,
1284                            scale: minifb::Scale::X1,
1285                            ..Default::default()
1286                        },
1287                    ).map_err(|e| EvalErr::from(format!("cannot open window: {e}")))?;
1288                    #[allow(deprecated)]
1289                    win.limit_update_rate(Some(std::time::Duration::from_millis(8)));
1290                    gfx.buffer = vec![0u32; w * h];
1291                    gfx.width  = w;
1292                    gfx.height = h;
1293                    gfx.window = Some(win);
1294                    gfx.sync_projection();
1295                    hide_console_window();
1296                }
1297                #[cfg(target_arch = "wasm32")]
1298                {
1299                    let mut gfx = self.gfx.borrow_mut();
1300                    gfx.width  = w;
1301                    gfx.height = h;
1302                    gfx.sync_projection();
1303                    crate::gfx::webgl::resize(w as u32, h as u32);
1304                }
1305                return Ok(Value::Unit);
1306            }
1307
1308            // ── เติม(r, g, b) — fill / clear screen with colour ──
1309            "เติม" | "fill" | "gfx_fill" | "clear" | "填" | "塗り潰し" | "채우기" | "清" | "消去" | "지우기" => {
1310                let r = self.arg_num(&args, 0, 0.0)? as u32;
1311                let g = self.arg_num(&args, 1, 0.0)? as u32;
1312                let b = self.arg_num(&args, 2, 0.0)? as u32;
1313                #[cfg(not(target_arch = "wasm32"))]
1314                {
1315                    let c = (r << 16) | (g << 8) | b;
1316                    self.gfx.borrow_mut().buffer.fill(c);
1317                }
1318                #[cfg(target_arch = "wasm32")]
1319                {
1320                    let mut gfx = self.gfx.borrow_mut();
1321                    gfx.fill_r = r as f32 / 255.0;
1322                    gfx.fill_g = g as f32 / 255.0;
1323                    gfx.fill_b = b as f32 / 255.0;
1324                }
1325                return Ok(Value::Unit);
1326            }
1327
1328            // ── set_color_hsl(h, s, l) — set drawing colour from HSL ──
1329            // h: 0–360 degrees, s: 0–100 saturation, l: 0–100 lightness
1330            "set_color_hsl" | "颜色HSL" | "色相" | "HSL色" | "HSL색설정" | "สีHSLวาด" => {
1331                let h = self.arg_num(&args, 0, 0.0)?;
1332                let s = self.arg_num(&args, 1, 70.0)?;
1333                let l = self.arg_num(&args, 2, 50.0)?;
1334                let hex = hsl_to_hex(h, s, l);
1335                let r = u32::from_str_radix(&hex[1..3], 16).unwrap_or(255);
1336                let g = u32::from_str_radix(&hex[3..5], 16).unwrap_or(255);
1337                let b = u32::from_str_radix(&hex[5..7], 16).unwrap_or(255);
1338                self.gfx.borrow_mut().color = (r << 16) | (g << 8) | b;
1339                return Ok(Value::Unit);
1340            }
1341
1342            // ── สีดินสอ(r, g, b) — set drawing colour ──
1343            "สีดินสอ" | "set_color" | "gfx_color" | "color" | "设色" | "色設定" | "색설정" => {
1344                let r = self.arg_num(&args, 0, 255.0)? as u32;
1345                let g = self.arg_num(&args, 1, 255.0)? as u32;
1346                let b = self.arg_num(&args, 2, 255.0)? as u32;
1347                self.gfx.borrow_mut().color = (r << 16) | (g << 8) | b;
1348                return Ok(Value::Unit);
1349            }
1350
1351            // ── วาดสามเหลี่ยม(x1,y1, x2,y2, x3,y3) — draw filled triangle ──
1352            "วาดสามเหลี่ยม" | "draw_triangle" | "gfx_triangle" | "triangle" | "画三角" | "三角形描画" | "삼각형그리기" => {
1353                let x0 = self.arg_num(&args, 0, 0.0)? as f32;
1354                let y0 = self.arg_num(&args, 1, 0.0)? as f32;
1355                let x1 = self.arg_num(&args, 2, 0.0)? as f32;
1356                let y1 = self.arg_num(&args, 3, 0.0)? as f32;
1357                let x2 = self.arg_num(&args, 4, 0.0)? as f32;
1358                let y2 = self.arg_num(&args, 5, 0.0)? as f32;
1359                let mut gfx = self.gfx.borrow_mut();
1360                let color = gfx.color;
1361                #[cfg(not(target_arch = "wasm32"))]
1362                {
1363                    let w = gfx.width;
1364                    let h = gfx.height;
1365                    fill_triangle(&mut gfx.buffer, w, h, color, x0, y0, x1, y1, x2, y2);
1366                }
1367                #[cfg(target_arch = "wasm32")]
1368                gfx.depth_queue.push_triangle(0.0, color, x0, y0, x1, y1, x2, y2);
1369                return Ok(Value::Unit);
1370            }
1371
1372            // ── วาดเส้น(x1,y1, x2,y2) — draw line ──
1373            "วาดเส้น" | "draw_line" | "gfx_line" | "line" | "画线" | "線描く" | "선그리기" => {
1374                let x0 = self.arg_num(&args, 0, 0.0)? as f32;
1375                let y0 = self.arg_num(&args, 1, 0.0)? as f32;
1376                let x1 = self.arg_num(&args, 2, 0.0)? as f32;
1377                let y1 = self.arg_num(&args, 3, 0.0)? as f32;
1378                let mut gfx = self.gfx.borrow_mut();
1379                let color = gfx.color;
1380                #[cfg(not(target_arch = "wasm32"))]
1381                {
1382                    let w = gfx.width;
1383                    let h = gfx.height;
1384                    draw_line(&mut gfx.buffer, w, h, color, x0, y0, x1, y1);
1385                }
1386                #[cfg(target_arch = "wasm32")]
1387                gfx.depth_queue.push_line(0.0, color, x0, y0, x1, y1);
1388                return Ok(Value::Unit);
1389            }
1390
1391            // ── วาดจุด(x, y) — plot a single pixel ──
1392            "วาดจุด" | "draw_pixel" | "gfx_pixel" | "pixel" | "画点" | "点描く" | "점그리기" => {
1393                let px = self.arg_num(&args, 0, 0.0)? as i32;
1394                let py = self.arg_num(&args, 1, 0.0)? as i32;
1395                #[cfg(not(target_arch = "wasm32"))]
1396                {
1397                    let mut gfx = self.gfx.borrow_mut();
1398                    let color = gfx.color;
1399                    let w = gfx.width;
1400                    let h = gfx.height;
1401                    if px >= 0 && py >= 0 && (px as usize) < w && (py as usize) < h {
1402                        gfx.buffer[py as usize * w + px as usize] = color;
1403                    }
1404                }
1405                #[cfg(target_arch = "wasm32")]
1406                {
1407                    // Render pixel as a 1×1 square via two triangles.
1408                    let mut gfx = self.gfx.borrow_mut();
1409                    let color = gfx.color;
1410                    let x = px as f32; let y = py as f32;
1411                    gfx.depth_queue.push_triangle(0.0, color, x, y, x+1.0, y, x+1.0, y+1.0);
1412                    gfx.depth_queue.push_triangle(0.0, color, x, y, x+1.0, y+1.0, x, y+1.0);
1413                }
1414                return Ok(Value::Unit);
1415            }
1416
1417            // ── แสดงผล() — flush depth queue, then present frame to screen ──
1418            "แสดงผล" | "present" | "gfx_present" | "show" | "显" | "呈现" | "表示" | "표시" => {
1419                #[cfg(not(target_arch = "wasm32"))]
1420                {
1421                    // Flush depth queue and present — release borrow before reading mouse.
1422                    {
1423                        let mut gfx = self.gfx.borrow_mut();
1424                        if !gfx.depth_queue.is_empty() {
1425                            let w = gfx.width;
1426                            let h = gfx.height;
1427                            let queue = std::mem::take(&mut gfx.depth_queue);
1428                            queue.flush(&mut gfx.buffer, w, h);
1429                        }
1430                        let buf = gfx.buffer.clone();
1431                        let w   = gfx.width;
1432                        let h   = gfx.height;
1433                        if let Some(win) = gfx.window.as_mut() {
1434                            win.update_with_buffer(&buf, w, h)
1435                                .map_err(|e| EvalErr::from(format!("present error: {e}")))?;
1436                        }
1437                    }
1438                    // Read mouse AFTER update_with_buffer so events are processed.
1439                    let mouse_pos = {
1440                        let gfx = self.gfx.borrow();
1441                        gfx.window.as_ref()
1442                            .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
1443                    };
1444                    let mut gfx = self.gfx.borrow_mut();
1445                    if gfx.mouse_captured {
1446                        if let Some((mx, my)) = mouse_pos {
1447                            if gfx.last_mx.is_nan() {
1448                                gfx.mouse_dx = 0.0; gfx.mouse_dy = 0.0;
1449                            } else {
1450                                gfx.mouse_dx = mx - gfx.last_mx;
1451                                gfx.mouse_dy = my - gfx.last_my;
1452                            }
1453                            gfx.last_mx = mx; gfx.last_my = my;
1454                        } else {
1455                            gfx.mouse_dx = 0.0; gfx.mouse_dy = 0.0;
1456                        }
1457                        // Clip cursor to window bounds so it can't escape.
1458                        #[cfg(windows)]
1459                        unsafe {
1460                            #[repr(C)]
1461                            struct RECT { left: i32, top: i32, right: i32, bottom: i32 }
1462                            extern "system" {
1463                                fn ClipCursor(lpRect: *const std::ffi::c_void) -> i32;
1464                                fn GetForegroundWindow() -> isize;
1465                                fn GetWindowRect(hwnd: isize, lpRect: *mut RECT) -> i32;
1466                            }
1467                            let hwnd = GetForegroundWindow();
1468                            let mut rect = RECT { left: 0, top: 0, right: 0, bottom: 0 };
1469                            if GetWindowRect(hwnd, &mut rect) != 0 {
1470                                ClipCursor(&rect as *const RECT as *const std::ffi::c_void);
1471                            }
1472                        }
1473                    } else if let Some((mx, my)) = mouse_pos {
1474                        if gfx.last_mx.is_nan() {
1475                            gfx.mouse_dx = 0.0; gfx.mouse_dy = 0.0;
1476                        } else {
1477                            gfx.mouse_dx = mx - gfx.last_mx;
1478                            gfx.mouse_dy = my - gfx.last_my;
1479                        }
1480                        gfx.last_mx = mx; gfx.last_my = my;
1481                    } else {
1482                        gfx.mouse_dx = 0.0; gfx.mouse_dy = 0.0;
1483                    }
1484                }
1485                #[cfg(target_arch = "wasm32")]
1486                {
1487                    let mut gfx = self.gfx.borrow_mut();
1488                    let w  = gfx.width;
1489                    let h  = gfx.height;
1490                    let fr = gfx.fill_r;
1491                    let fg = gfx.fill_g;
1492                    let fb = gfx.fill_b;
1493                    let queue = std::mem::take(&mut gfx.depth_queue);
1494                    queue.flush_to_webgl(fr, fg, fb, w, h);
1495                }
1496                // Update the click-edge latch for interactive UI widgets.
1497                #[cfg(not(target_arch = "wasm32"))]
1498                {
1499                    let (_, _, down) = self.mouse_now();
1500                    self.mouse_was_down = down;
1501                }
1502                // Increment frame counter
1503                self.frame_num += 1;
1504                return Ok(Value::Unit);
1505            }
1506
1507            // ── เปิดหน้าต่างเต็มจอ(title) — true native-res fullscreen window ──
1508            "เปิดหน้าต่างเต็มจอ" | "open_fullscreen" | "fullscreen" | "全屏" | "全画面" | "전체화면" => {
1509                // In WASM the canvas defines the viewport; use its current size
1510                // as the default so the projection matches what's actually visible.
1511                #[cfg(target_arch = "wasm32")]
1512                let (default_w, default_h) = {
1513                    let (cw, ch) = crate::gfx::webgl::canvas_size();
1514                    (cw as f64, ch as f64)
1515                };
1516                // On native: query the actual primary monitor resolution.
1517                #[cfg(all(not(target_arch = "wasm32"), windows))]
1518                let (default_w, default_h) = unsafe {
1519                    extern "system" { fn GetSystemMetrics(nIndex: i32) -> i32; }
1520                    (GetSystemMetrics(0) as f64, GetSystemMetrics(1) as f64)
1521                };
1522                #[cfg(all(not(target_arch = "wasm32"), not(windows)))]
1523                let (default_w, default_h) = native_screen_size();
1524
1525                let w = args.get(1).map(|v| self.to_number(v).unwrap_or(default_w) as usize).unwrap_or(default_w as usize);
1526                let h = args.get(2).map(|v| self.to_number(v).unwrap_or(default_h) as usize).unwrap_or(default_h as usize);
1527                #[cfg(not(target_arch = "wasm32"))]
1528                {
1529                    let title = args.get(0).map(|v| v.to_string()).unwrap_or_else(|| "Ling".into());
1530                    let mut gfx = self.gfx.borrow_mut();
1531                    let mut win = minifb::Window::new(
1532                        &title, w, h,
1533                        minifb::WindowOptions {
1534                            borderless: true,
1535                            title:      false,
1536                            resize:     false,
1537                            scale:      minifb::Scale::X1,
1538                            ..Default::default()
1539                        },
1540                    ).map_err(|e| EvalErr::from(format!("cannot open fullscreen: {e}")))?;
1541                    #[allow(deprecated)]
1542                    win.limit_update_rate(Some(std::time::Duration::from_millis(8)));
1543                    gfx.buffer = vec![0u32; w * h];
1544                    gfx.width  = w;
1545                    gfx.height = h;
1546                    gfx.window = Some(win);
1547                    gfx.sync_projection();
1548                    // Snap to top-left, cover full screen, bring above taskbar.
1549                    #[cfg(windows)]
1550                    reposition_fullscreen(w as i32, h as i32);
1551                    hide_console_window();
1552                }
1553                #[cfg(target_arch = "wasm32")]
1554                {
1555                    let mut gfx = self.gfx.borrow_mut();
1556                    gfx.width  = w;
1557                    gfx.height = h;
1558                    gfx.sync_projection();
1559                    crate::gfx::webgl::resize(w as u32, h as u32);
1560                }
1561                return Ok(Value::Unit);
1562            }
1563
1564            // ── ความกว้าง() / ความสูง() — current framebuffer size ──
1565            "get_width" | "ความกว้าง" | "宽" | "幅取得" | "너비" => {
1566                return Ok(Value::Number(self.gfx.borrow().width as f64));
1567            }
1568            "get_height" | "ความสูง" | "高" | "高取得" | "높이" => {
1569                return Ok(Value::Number(self.gfx.borrow().height as f64));
1570            }
1571
1572            // ── หน้าต่างเปิดอยู่() → bool — is the window still open? ──
1573            "หน้าต่างเปิดอยู่" | "window_is_open" | "gfx_is_open" | "is_open" | "窗开" | "開いている" | "창열림" => {
1574                #[cfg(not(target_arch = "wasm32"))]
1575                {
1576                    let gfx = self.gfx.borrow();
1577                    let open = gfx.window.as_ref()
1578                        .map(|w| w.is_open() && !w.is_key_down(minifb::Key::Escape))
1579                        .unwrap_or(false);
1580                    return Ok(Value::Bool(open));
1581                }
1582                #[cfg(target_arch = "wasm32")]
1583                return Ok(Value::Bool(true));
1584            }
1585
1586            // ── key_down(name) → bool — is a key held? ──
1587            "key_down" | "กดค้าง" | "按键" | "キー押す" | "키누름" => {
1588                #[cfg(not(target_arch = "wasm32"))]
1589                {
1590                    let name = self.arg_str(&args, 0, "");
1591                    let gfx  = self.gfx.borrow();
1592                    let down = gfx.window.as_ref()
1593                        .and_then(|w| str_to_minifb_key(&name).map(|k| w.is_key_down(k)))
1594                        .unwrap_or(false);
1595                    return Ok(Value::Bool(down));
1596                }
1597                #[cfg(target_arch = "wasm32")]
1598                return Ok(Value::Bool(false));
1599            }
1600
1601            // ── key_pressed(name) → bool — was a key pressed this frame? ──
1602            "key_pressed" | "กดปุ่ม" | "键按" | "キー押した" | "키눌림" => {
1603                #[cfg(not(target_arch = "wasm32"))]
1604                {
1605                    let name = self.arg_str(&args, 0, "");
1606                    let gfx  = self.gfx.borrow();
1607                    let pressed = gfx.window.as_ref()
1608                        .and_then(|w| str_to_minifb_key(&name)
1609                            .map(|k| w.is_key_pressed(k, minifb::KeyRepeat::No)))
1610                        .unwrap_or(false);
1611                    return Ok(Value::Bool(pressed));
1612                }
1613                #[cfg(target_arch = "wasm32")]
1614                return Ok(Value::Bool(false));
1615            }
1616
1617            // ── mouse_dx() / mouse_dy() → f64 — delta since last frame ──
1618            "mouse_dx" | "เมาส์X" | "鼠ΔX" | "マウスΔX" | "마우스ΔX" => {
1619                #[cfg(not(target_arch = "wasm32"))]
1620                return Ok(Value::Number(self.gfx.borrow().mouse_dx as f64));
1621                #[cfg(target_arch = "wasm32")]
1622                return Ok(Value::Number(0.0));
1623            }
1624            "mouse_dy" | "เมาส์Y" | "鼠ΔY" | "マウスΔY" | "마우스ΔY" => {
1625                #[cfg(not(target_arch = "wasm32"))]
1626                return Ok(Value::Number(self.gfx.borrow().mouse_dy as f64));
1627                #[cfg(target_arch = "wasm32")]
1628                return Ok(Value::Number(0.0));
1629            }
1630
1631            // ── set_camera_pos(x, y, z) — move camera to world position ──
1632            "set_camera_pos" | "ตั้งตำแหน่งกล้อง" | "镜坐标" | "カメラ座標" | "카메라좌표" => {
1633                let x = self.arg_num(&args, 0, 0.0)? as f32;
1634                let y = self.arg_num(&args, 1, 0.0)? as f32;
1635                let z = self.arg_num(&args, 2, 0.0)? as f32;
1636                let mut gfx = self.gfx.borrow_mut();
1637                gfx.camera.tx = x; gfx.camera.ty = y; gfx.camera.tz = z;
1638                return Ok(Value::Unit);
1639            }
1640
1641            // ── move_camera(dx, dy, dz) — translate camera by delta ──
1642            "move_camera" => {
1643                let dx = self.arg_num(&args, 0, 0.0)? as f32;
1644                let dy = self.arg_num(&args, 1, 0.0)? as f32;
1645                let dz = self.arg_num(&args, 2, 0.0)? as f32;
1646                let mut gfx = self.gfx.borrow_mut();
1647                gfx.camera.tx += dx; gfx.camera.ty += dy; gfx.camera.tz += dz;
1648                return Ok(Value::Unit);
1649            }
1650
1651            // ── set_zdist(d) — set perspective z-offset (field-of-view taper) ──
1652            "set_zdist" | "ตั้งระยะห่าง" | "镜距" | "Z距離設定" | "Z거리설정" => {
1653                let d = self.arg_num(&args, 0, 5.0)? as f32;
1654                self.gfx.borrow_mut().camera.zdist = d;
1655                return Ok(Value::Unit);
1656            }
1657
1658            // ── capture_mouse() — hide cursor and warp to centre each frame ──
1659            "capture_mouse" | "จับเมาส์" | "捕鼠" | "マウス捕捉" | "마우스잡기" => {
1660                #[cfg(not(target_arch = "wasm32"))]
1661                {
1662                    let mut gfx = self.gfx.borrow_mut();
1663                    gfx.mouse_captured = true;
1664                    gfx.last_mx = f32::NAN;
1665                    if let Some(win) = gfx.window.as_mut() {
1666                        win.set_cursor_visibility(false);
1667                    }
1668                }
1669                return Ok(Value::Unit);
1670            }
1671
1672            // ── release_mouse() — restore cursor and remove clip region ──
1673            "release_mouse" => {
1674                #[cfg(not(target_arch = "wasm32"))]
1675                {
1676                    let mut gfx = self.gfx.borrow_mut();
1677                    gfx.mouse_captured = false;
1678                    gfx.last_mx = f32::NAN;
1679                    if let Some(win) = gfx.window.as_mut() {
1680                        win.set_cursor_visibility(true);
1681                    }
1682                    #[cfg(windows)]
1683                    unsafe {
1684                        // Null releases the clip; reuse the RECT-typed declaration above.
1685                        extern "system" { fn ClipCursor(lpRect: *const std::ffi::c_void) -> i32; }
1686                        ClipCursor(std::ptr::null());
1687                    }
1688                }
1689                return Ok(Value::Unit);
1690            }
1691
1692            // ══════════════════════════════════════════════════════════════════
1693            // 3-D / 4-D DRAWING — camera, lights, depth-sorted geometry
1694            // ══════════════════════════════════════════════════════════════════
1695
1696            // ── set_camera(cry, sry, crx, srx) — store precomputed camera trig ──
1697            // Call once per frame after computing cos/sin of your rotation angles.
1698            "set_camera" | "ตั้งกล้อง" | "设镜" | "设置摄像机" | "カメラ設定" | "카메라설정" => {
1699                let cry = self.arg_num(&args, 0, 1.0)? as f32;
1700                let sry = self.arg_num(&args, 1, 0.0)? as f32;
1701                let crx = self.arg_num(&args, 2, 1.0)? as f32;
1702                let srx = self.arg_num(&args, 3, 0.0)? as f32;
1703                let mut gfx = self.gfx.borrow_mut();
1704                gfx.camera.cry = cry; gfx.camera.sry = sry;
1705                gfx.camera.crx = crx; gfx.camera.srx = srx;
1706                return Ok(Value::Unit);
1707            }
1708
1709            // ── set_projection(cx, cy, focal, zdist) — override projection params ──
1710            // Automatically set when the window opens; override only if needed.
1711            "set_projection" | "ตั้งโปรเจกชัน" | "投影" | "投影設定" | "투영설정" => {
1712                let cx    = self.arg_num(&args, 0, 960.0)? as f32;
1713                let cy    = self.arg_num(&args, 1, 540.0)? as f32;
1714                let focal = self.arg_num(&args, 2, 1080.0)? as f32;
1715                let zdist = self.arg_num(&args, 3, 5.0)? as f32;
1716                let mut gfx = self.gfx.borrow_mut();
1717                gfx.camera.cx    = cx;
1718                gfx.camera.cy    = cy;
1719                gfx.camera.focal = focal;
1720                gfx.camera.zdist = zdist;
1721                return Ok(Value::Unit);
1722            }
1723
1724            // ── add_light(x, y, z, r, g, b, intensity, radius) ──
1725            // Adds a point light in world space.  r/g/b in [0..1].
1726            // radius == 0 → no distance falloff.
1727            "add_light" | "เพิ่มแสง" | "加灯" | "ライト追加" | "조명추가" => {
1728                let x   = self.arg_num(&args, 0, 0.0)? as f32;
1729                let y   = self.arg_num(&args, 1, -3.0)? as f32;
1730                let z   = self.arg_num(&args, 2, 3.0)? as f32;
1731                let mut r   = self.arg_num(&args, 3, 1.0)? as f32;
1732                let mut g   = self.arg_num(&args, 4, 1.0)? as f32;
1733                let mut b   = self.arg_num(&args, 5, 1.0)? as f32;
1734                // Forgive 0-255 colour values: if any channel is clearly > 1,
1735                // treat the triple as 0-255 and normalise. Keeps 0-1 callers exact.
1736                if r > 1.5 || g > 1.5 || b > 1.5 { r/=255.0; g/=255.0; b/=255.0; }
1737                let intensity = self.arg_num(&args, 6, 1.0)? as f32;
1738                let radius    = self.arg_num(&args, 7, 0.0)? as f32;
1739                self.gfx.borrow_mut().lights.push(Light { x, y, z, r, g, b, intensity, radius });
1740                return Ok(Value::Unit);
1741            }
1742
1743            // ── clear_lights() — remove all lights ──
1744            "clear_lights" | "ล้างแสง" | "清灯" | "ライト消去" | "조명초기화" => {
1745                self.gfx.borrow_mut().lights.clear();
1746                return Ok(Value::Unit);
1747            }
1748
1749            // ── set_ambient(v) — ambient light level [0..1] ──
1750            "set_ambient" | "ตั้งแสงรอบข้าง" | "环境光" | "環境光設定" | "환경광설정" => {
1751                let v = self.arg_num(&args, 0, 0.15)? as f32;
1752                self.gfx.borrow_mut().ambient = v;
1753                return Ok(Value::Unit);
1754            }
1755
1756            // ── วาดสามเหลี่ยม3มิติ(ax,ay,az, bx,by,bz, cx,cy,cz) ──
1757            // Computes lighting from world-space normal + active lights (cel shading),
1758            // projects via the stored camera, and pushes to the depth queue.
1759            "วาดสามเหลี่ยม3มิติ" | "draw_triangle_3d" | "triangle3d" => {
1760                let ax = self.arg_num(&args, 0, 0.0)? as f32;
1761                let ay = self.arg_num(&args, 1, 0.0)? as f32;
1762                let az = self.arg_num(&args, 2, 0.0)? as f32;
1763                let bx = self.arg_num(&args, 3, 0.0)? as f32;
1764                let by = self.arg_num(&args, 4, 0.0)? as f32;
1765                let bz = self.arg_num(&args, 5, 0.0)? as f32;
1766                let cx = self.arg_num(&args, 6, 0.0)? as f32;
1767                let cy = self.arg_num(&args, 7, 0.0)? as f32;
1768                let cz = self.arg_num(&args, 8, 0.0)? as f32;
1769
1770                let mut gfx = self.gfx.borrow_mut();
1771
1772                // World-space face normal  N = (B−A) × (C−A)
1773                let ux = bx-ax; let uy = by-ay; let uz = bz-az;
1774                let vx = cx-ax; let vy = cy-ay; let vz = cz-az;
1775                let normal = [
1776                    uy*vz - uz*vy,
1777                    uz*vx - ux*vz,
1778                    ux*vy - uy*vx,
1779                ];
1780                // World-space centroid
1781                let centroid = [
1782                    (ax+bx+cx)/3.0,
1783                    (ay+by+cy)/3.0,
1784                    (az+bz+cz)/3.0,
1785                ];
1786
1787                // Cel-shaded colour
1788                let lit_color = crate::gfx::light::compute_lit_color(
1789                    gfx.color, normal, centroid, &gfx.lights, gfx.ambient,
1790                );
1791
1792                // Near-plane cull — skip any triangle that has a vertex
1793                // behind or at the camera near plane (avoids projected-to-infinity blowup).
1794                let near = -gfx.camera.zdist + 0.05;
1795                let da_raw = gfx.camera.depth(ax, ay, az);
1796                let db_raw = gfx.camera.depth(bx, by, bz);
1797                let dc_raw = gfx.camera.depth(cx, cy, cz);
1798                if da_raw <= near || db_raw <= near || dc_raw <= near {
1799                    return Ok(Value::Unit);
1800                }
1801
1802                // Project to screen
1803                let (sax, say, da) = gfx.camera.project(ax, ay, az);
1804                let (sbx, sby, db) = gfx.camera.project(bx, by, bz);
1805                let (scx, scy, dc) = gfx.camera.project(cx, cy, cz);
1806
1807                // Average camera depth (used for painter's sort)
1808                let depth = (da + db + dc) / 3.0;
1809
1810                gfx.depth_queue.push_triangle(
1811                    depth, lit_color,
1812                    sax, say, sbx, sby, scx, scy,
1813                );
1814                return Ok(Value::Unit);
1815            }
1816
1817            // ── วาดเส้น3มิติ(ax,ay,az, bx,by,bz) ──
1818            // Projects two world-space points via the stored camera and pushes
1819            // a line to the depth queue.
1820            "วาดเส้น3มิติ" | "draw_line_3d" | "line3d" | "画3D线" | "3D線描く" | "3D선그리기" => {
1821                let ax = self.arg_num(&args, 0, 0.0)? as f32;
1822                let ay = self.arg_num(&args, 1, 0.0)? as f32;
1823                let az = self.arg_num(&args, 2, 0.0)? as f32;
1824                let bx = self.arg_num(&args, 3, 0.0)? as f32;
1825                let by = self.arg_num(&args, 4, 0.0)? as f32;
1826                let bz = self.arg_num(&args, 5, 0.0)? as f32;
1827
1828                let mut gfx = self.gfx.borrow_mut();
1829                let color = gfx.color;
1830                // Near-plane clip in 3-D before perspective divide
1831                let near = -gfx.camera.zdist + 0.05;
1832                let mut lax = ax; let mut lay = ay; let mut laz = az;
1833                let mut lbx = bx; let mut lby = by; let mut lbz = bz;
1834                let da_raw = gfx.camera.depth(lax, lay, laz);
1835                let db_raw = gfx.camera.depth(lbx, lby, lbz);
1836                if da_raw <= near && db_raw <= near {
1837                    return Ok(Value::Unit);
1838                }
1839                if da_raw <= near {
1840                    let t = (near - da_raw) / (db_raw - da_raw);
1841                    lax += t * (lbx - lax);
1842                    lay += t * (lby - lay);
1843                    laz += t * (lbz - laz);
1844                } else if db_raw <= near {
1845                    let t = (near - da_raw) / (db_raw - da_raw);
1846                    lbx = lax + t * (lbx - lax);
1847                    lby = lay + t * (lby - lay);
1848                    lbz = laz + t * (lbz - laz);
1849                }
1850                let (sax, say, da) = gfx.camera.project(lax, lay, laz);
1851                let (sbx, sby, db) = gfx.camera.project(lbx, lby, lbz);
1852                let depth = (da + db) / 2.0;
1853                gfx.depth_queue.push_line(depth, color, sax, say, sbx, sby);
1854                return Ok(Value::Unit);
1855            }
1856
1857            // project_3d(x,y,z) -> [screen_x, screen_y, depth]; behind the camera
1858            // returns a sentinel ([-99999,-99999, depth]) so scripts can skip it.
1859            // Lets scripts place 2-D overlays (e.g. filled teardrop flames) onto 3-D points.
1860            "project_3d" | "投影3D" | "3D投影" | "3D투영" | "ฉาย3มิติ" => {
1861                let x = self.arg_num(&args,0,0.0)? as f32;
1862                let y = self.arg_num(&args,1,0.0)? as f32;
1863                let z = self.arg_num(&args,2,0.0)? as f32;
1864                let gfx = self.gfx.borrow();
1865                let near = -gfx.camera.zdist + 0.05;
1866                let d = gfx.camera.depth(x, y, z);
1867                if d <= near {
1868                    return Ok(Value::List(vec![Value::Number(-99999.0), Value::Number(-99999.0), Value::Number(d as f64)]));
1869                }
1870                let (sx, sy, depth) = gfx.camera.project(x, y, z);
1871                return Ok(Value::List(vec![Value::Number(sx as f64), Value::Number(sy as f64), Value::Number(depth as f64)]));
1872            }
1873            // draw_poly([x0,y0,x1,y1,…]) — filled 2-D polygon in the current colour,
1874            // honouring the blend mode (additive → translucent glow). Auto-closes.
1875            #[cfg(not(target_arch = "wasm32"))]
1876            "draw_poly" | "填充多边形" | "ポリゴン塗り" | "다각형채우기" | "เติมรูปหลายเหลี่ยม" => {
1877                let mut pts: Vec<[f32; 2]> = Vec::new();
1878                if let Some(Value::List(v)) = args.first() {
1879                    let mut i = 0;
1880                    while i + 1 < v.len() {
1881                        let x = self.to_number(&v[i]).unwrap_or(0.0) as f32;
1882                        let y = self.to_number(&v[i + 1]).unwrap_or(0.0) as f32;
1883                        pts.push([x, y]);
1884                        i += 2;
1885                    }
1886                }
1887                if pts.len() >= 3 {
1888                    if pts[0] != pts[pts.len() - 1] { let p0 = pts[0]; pts.push(p0); } // close
1889                    let mut gfx = self.gfx.borrow_mut();
1890                    let (w, h, color, add) = (gfx.width, gfx.height, gfx.color, gfx.blend == 1);
1891                    crate::gfx::raster::fill_contours_aa(&mut gfx.buffer, w, h, color, add, std::slice::from_ref(&pts));
1892                }
1893                return Ok(Value::Unit);
1894            }
1895
1896            // ══════════════════════════════════════════════════════════════════
1897            // VECTOR TEXTURE BUILTINS  (src/gfx/vtex.rs)
1898            // All patterns are depth-biased so they appear on top of surfaces.
1899            // Plane defined by: centre (cx,cy,cz) + U tangent + V tangent.
1900            // Last two args always: fr (frame f32), hue (phase offset f32).
1901            // ══════════════════════════════════════════════════════════════════
1902
1903            // vtex_grid(cx,cy,cz, ux,uy,uz, vx,vy,vz, cols,rows, cw,ch, fr,hue)
1904            "vtex_grid" | "ลายตาราง" | "纹格" | "格子模様" | "격자무늬" => {
1905                let cx=self.arg_num(&args,0,0.)?as f32; let cy=self.arg_num(&args,1,0.)?as f32; let cz=self.arg_num(&args,2,0.)?as f32;
1906                let ux=self.arg_num(&args,3,1.)?as f32; let uy=self.arg_num(&args,4,0.)?as f32; let uz=self.arg_num(&args,5,0.)?as f32;
1907                let vx=self.arg_num(&args,6,0.)?as f32; let vy=self.arg_num(&args,7,0.)?as f32; let vz=self.arg_num(&args,8,1.)?as f32;
1908                let cols=self.arg_num(&args,9,10.)?as usize; let rows=self.arg_num(&args,10,10.)?as usize;
1909                let cw=self.arg_num(&args,11,1.)?as f32;  let ch=self.arg_num(&args,12,1.)?as f32;
1910                let fr=self.arg_num(&args,13,0.)?as f32;  let hue=self.arg_num(&args,14,0.)?as f32;
1911                let mut gfx = self.gfx.borrow_mut();
1912                let cam = gfx.camera.clone();
1913                crate::gfx::vtex::draw_grid(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, cols,rows,cw,ch, fr,hue);
1914                return Ok(Value::Unit);
1915            }
1916
1917            // vtex_rings(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_rings,n_sides, max_r,twist, fr,hue)
1918            "vtex_rings" | "ลายวงซ้อน" | "纹环" | "同心円" | "동심원" => {
1919                let cx=self.arg_num(&args,0,0.)?as f32; let cy=self.arg_num(&args,1,0.)?as f32; let cz=self.arg_num(&args,2,0.)?as f32;
1920                let ux=self.arg_num(&args,3,1.)?as f32; let uy=self.arg_num(&args,4,0.)?as f32; let uz=self.arg_num(&args,5,0.)?as f32;
1921                let vx=self.arg_num(&args,6,0.)?as f32; let vy=self.arg_num(&args,7,0.)?as f32; let vz=self.arg_num(&args,8,1.)?as f32;
1922                let nr=self.arg_num(&args,9,6.)?as usize; let ns=self.arg_num(&args,10,6.)?as usize;
1923                let mr=self.arg_num(&args,11,3.)?as f32;  let tw=self.arg_num(&args,12,0.)?as f32;
1924                let fr=self.arg_num(&args,13,0.)?as f32;  let hue=self.arg_num(&args,14,0.)?as f32;
1925                let mut gfx = self.gfx.borrow_mut();
1926                let cam = gfx.camera.clone();
1927                crate::gfx::vtex::draw_rings(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, nr,ns,mr,tw, fr,hue);
1928                return Ok(Value::Unit);
1929            }
1930
1931            // vtex_star(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_pts,r_out,r_in, rot_speed, fr,hue)
1932            "vtex_star" | "ลายดาว" | "纹星" | "星模様" | "별무늬" => {
1933                let cx=self.arg_num(&args,0,0.)?as f32; let cy=self.arg_num(&args,1,0.)?as f32; let cz=self.arg_num(&args,2,0.)?as f32;
1934                let ux=self.arg_num(&args,3,1.)?as f32; let uy=self.arg_num(&args,4,0.)?as f32; let uz=self.arg_num(&args,5,0.)?as f32;
1935                let vx=self.arg_num(&args,6,0.)?as f32; let vy=self.arg_num(&args,7,0.)?as f32; let vz=self.arg_num(&args,8,1.)?as f32;
1936                let np=self.arg_num(&args,9,6.)?as usize;
1937                let ro=self.arg_num(&args,10,2.)?as f32; let ri=self.arg_num(&args,11,1.)?as f32;
1938                let rs=self.arg_num(&args,12,0.01)?as f32;
1939                let fr=self.arg_num(&args,13,0.)?as f32; let hue=self.arg_num(&args,14,0.)?as f32;
1940                let mut gfx = self.gfx.borrow_mut();
1941                let cam = gfx.camera.clone();
1942                crate::gfx::vtex::draw_star(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, np,ro,ri,rs, fr,hue);
1943                return Ok(Value::Unit);
1944            }
1945
1946            // vtex_spiral(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_turns,max_r,steps, fr,hue)
1947            "vtex_spiral" | "ลายเกลียว" | "纹螺" | "螺旋" | "나선" => {
1948                let cx=self.arg_num(&args,0,0.)?as f32; let cy=self.arg_num(&args,1,0.)?as f32; let cz=self.arg_num(&args,2,0.)?as f32;
1949                let ux=self.arg_num(&args,3,1.)?as f32; let uy=self.arg_num(&args,4,0.)?as f32; let uz=self.arg_num(&args,5,0.)?as f32;
1950                let vx=self.arg_num(&args,6,0.)?as f32; let vy=self.arg_num(&args,7,0.)?as f32; let vz=self.arg_num(&args,8,1.)?as f32;
1951                let nt=self.arg_num(&args,9,3.)?as f32; let mr=self.arg_num(&args,10,3.)?as f32;
1952                let st=self.arg_num(&args,11,120.)?as usize;
1953                let fr=self.arg_num(&args,12,0.)?as f32; let hue=self.arg_num(&args,13,0.)?as f32;
1954                let mut gfx = self.gfx.borrow_mut();
1955                let cam = gfx.camera.clone();
1956                crate::gfx::vtex::draw_spiral(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, nt,mr,st, fr,hue);
1957                return Ok(Value::Unit);
1958            }
1959
1960            // vtex_flower(cx,cy,cz, ux,uy,uz, vx,vy,vz, radius,n_sides, fr,hue)
1961            "vtex_flower" | "ลายดอก" | "纹花" | "花模様" | "꽃무늬" => {
1962                let cx=self.arg_num(&args,0,0.)?as f32; let cy=self.arg_num(&args,1,0.)?as f32; let cz=self.arg_num(&args,2,0.)?as f32;
1963                let ux=self.arg_num(&args,3,1.)?as f32; let uy=self.arg_num(&args,4,0.)?as f32; let uz=self.arg_num(&args,5,0.)?as f32;
1964                let vx=self.arg_num(&args,6,0.)?as f32; let vy=self.arg_num(&args,7,0.)?as f32; let vz=self.arg_num(&args,8,1.)?as f32;
1965                let r=self.arg_num(&args,9,1.)?as f32; let ns=self.arg_num(&args,10,24.)?as usize;
1966                let fr=self.arg_num(&args,11,0.)?as f32; let hue=self.arg_num(&args,12,0.)?as f32;
1967                let mut gfx = self.gfx.borrow_mut();
1968                let cam = gfx.camera.clone();
1969                crate::gfx::vtex::draw_flower(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, r,ns, fr,hue);
1970                return Ok(Value::Unit);
1971            }
1972
1973            // vtex_letter_rain(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_cols,n_vis, col_w,row_h, speed, fr,hue)
1974            "vtex_letter_rain" | "ลายอักษรไหล" | "纹字雨" | "文字雨" | "글자비" => {
1975                let cx=self.arg_num(&args,0,0.)?as f32; let cy=self.arg_num(&args,1,0.)?as f32; let cz=self.arg_num(&args,2,0.)?as f32;
1976                let ux=self.arg_num(&args,3,1.)?as f32; let uy=self.arg_num(&args,4,0.)?as f32; let uz=self.arg_num(&args,5,0.)?as f32;
1977                let vx=self.arg_num(&args,6,0.)?as f32; let vy=self.arg_num(&args,7,0.)?as f32; let vz=self.arg_num(&args,8,1.)?as f32;
1978                let nc=self.arg_num(&args,9,16.)?as usize; let nv=self.arg_num(&args,10,14.)?as usize;
1979                let cw=self.arg_num(&args,11,0.65)?as f32; let rh=self.arg_num(&args,12,0.60)?as f32;
1980                let sp=self.arg_num(&args,13,0.025)?as f32;
1981                let fr=self.arg_num(&args,14,0.)?as f32; let hue=self.arg_num(&args,15,0.)?as f32;
1982                let mut gfx = self.gfx.borrow_mut();
1983                let cam = gfx.camera.clone();
1984                crate::gfx::vtex::draw_letter_rain(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, nc,nv,cw,rh,sp, fr,hue);
1985                return Ok(Value::Unit);
1986            }
1987
1988            // vtex_hyperbolic_uv(cx,cy,cz, ux,uy,uz, vx,vy,vz, max_r,n_circles,n_rays, fr,hue)
1989            "vtex_hyperbolic_uv" | "ลายไฮเพอร์โบลิก" | "纹曲面" | "双曲線" | "쌍곡선" => {
1990                let cx=self.arg_num(&args,0,0.)?as f32; let cy=self.arg_num(&args,1,0.)?as f32; let cz=self.arg_num(&args,2,0.)?as f32;
1991                let ux=self.arg_num(&args,3,1.)?as f32; let uy=self.arg_num(&args,4,0.)?as f32; let uz=self.arg_num(&args,5,0.)?as f32;
1992                let vx=self.arg_num(&args,6,0.)?as f32; let vy=self.arg_num(&args,7,0.)?as f32; let vz=self.arg_num(&args,8,1.)?as f32;
1993                let mr=self.arg_num(&args,9,5.)?as f32;
1994                let nc=self.arg_num(&args,10,12.)?as usize; let nr=self.arg_num(&args,11,18.)?as usize;
1995                let fr=self.arg_num(&args,12,0.)?as f32; let hue=self.arg_num(&args,13,0.)?as f32;
1996                let mut gfx = self.gfx.borrow_mut();
1997                let cam = gfx.camera.clone();
1998                crate::gfx::vtex::draw_hyperbolic_uv(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, mr,nc,nr, fr,hue);
1999                return Ok(Value::Unit);
2000            }
2001
2002            // vtex_halftone(cx,cy,cz, ux,uy,uz, vx,vy,vz, cols,rows, cell_w,cell_h, density, fr,hue)
2003            "vtex_halftone" | "ลายจุด" | "纹半调" | "網点模様" | "망점" => {
2004                let cx=self.arg_num(&args,0,0.)?as f32; let cy=self.arg_num(&args,1,0.)?as f32; let cz=self.arg_num(&args,2,0.)?as f32;
2005                let ux=self.arg_num(&args,3,1.)?as f32; let uy=self.arg_num(&args,4,0.)?as f32; let uz=self.arg_num(&args,5,0.)?as f32;
2006                let vx=self.arg_num(&args,6,0.)?as f32; let vy=self.arg_num(&args,7,0.)?as f32; let vz=self.arg_num(&args,8,1.)?as f32;
2007                let cols=self.arg_num(&args,9,16.)?as usize; let rows=self.arg_num(&args,10,12.)?as usize;
2008                let cw=self.arg_num(&args,11,0.5)?as f32; let ch=self.arg_num(&args,12,0.5)?as f32;
2009                let dens=self.arg_num(&args,13,0.4)?as f32;
2010                let fr=self.arg_num(&args,14,0.)?as f32; let hue=self.arg_num(&args,15,0.)?as f32;
2011                let mut gfx = self.gfx.borrow_mut();
2012                let cam = gfx.camera.clone();
2013                crate::gfx::vtex::draw_halftone(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, cols,rows,cw,ch,dens, fr,hue);
2014                return Ok(Value::Unit);
2015            }
2016
2017            // vtex_tessellated(cx,cy,cz, ux,uy,uz, vx,vy,vz, cols,rows, cell, amplitude,freq, fr,hue)
2018            "vtex_tessellated" | "ลายตาข่าย" | "纹镶嵌" | "網目模様" | "격자망" => {
2019                let cx=self.arg_num(&args,0,0.)?as f32; let cy=self.arg_num(&args,1,0.)?as f32; let cz=self.arg_num(&args,2,0.)?as f32;
2020                let ux=self.arg_num(&args,3,1.)?as f32; let uy=self.arg_num(&args,4,0.)?as f32; let uz=self.arg_num(&args,5,0.)?as f32;
2021                let vx=self.arg_num(&args,6,0.)?as f32; let vy=self.arg_num(&args,7,0.)?as f32; let vz=self.arg_num(&args,8,1.)?as f32;
2022                let cols=self.arg_num(&args,9,14.)?as usize; let rows=self.arg_num(&args,10,10.)?as usize;
2023                let cell=self.arg_num(&args,11,0.6)?as f32;
2024                let amp=self.arg_num(&args,12,0.25)?as f32; let freq=self.arg_num(&args,13,4.)?as f32;
2025                let fr=self.arg_num(&args,14,0.)?as f32; let hue=self.arg_num(&args,15,0.)?as f32;
2026                let mut gfx = self.gfx.borrow_mut();
2027                let cam = gfx.camera.clone();
2028                crate::gfx::vtex::draw_tessellated(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, cols,rows,cell,amp,freq, fr,hue);
2029                return Ok(Value::Unit);
2030            }
2031
2032            // vtex_lotus(cx,cy,cz, ux,uy,uz, vx,vy,vz, r_inner,r_outer,n_petals, fr,hue)
2033            "vtex_lotus" | "ลายดอกบัว" | "纹莲" | "蓮模様" | "연꽃무늬" => {
2034                let cx=self.arg_num(&args,0,0.)?as f32; let cy=self.arg_num(&args,1,0.)?as f32; let cz=self.arg_num(&args,2,0.)?as f32;
2035                let ux=self.arg_num(&args,3,1.)?as f32; let uy=self.arg_num(&args,4,0.)?as f32; let uz=self.arg_num(&args,5,0.)?as f32;
2036                let vx=self.arg_num(&args,6,0.)?as f32; let vy=self.arg_num(&args,7,0.)?as f32; let vz=self.arg_num(&args,8,1.)?as f32;
2037                let ri=self.arg_num(&args,9,1.)?as f32; let ro=self.arg_num(&args,10,2.)?as f32;
2038                let np=self.arg_num(&args,11,12.)?as usize;
2039                let fr=self.arg_num(&args,12,0.)?as f32; let hue=self.arg_num(&args,13,0.)?as f32;
2040                let mut gfx = self.gfx.borrow_mut();
2041                let cam = gfx.camera.clone();
2042                crate::gfx::vtex::draw_lotus(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, ri,ro,np, fr,hue);
2043                return Ok(Value::Unit);
2044            }
2045
2046            // vtex_chakra(cx,cy,cz, ux,uy,uz, vx,vy,vz, r,n_spokes, fr,hue)
2047            "vtex_chakra" | "ลายจักร" | "纹轮" | "輪模様" | "바퀴무늬" => {
2048                let cx=self.arg_num(&args,0,0.)?as f32; let cy=self.arg_num(&args,1,0.)?as f32; let cz=self.arg_num(&args,2,0.)?as f32;
2049                let ux=self.arg_num(&args,3,1.)?as f32; let uy=self.arg_num(&args,4,0.)?as f32; let uz=self.arg_num(&args,5,0.)?as f32;
2050                let vx=self.arg_num(&args,6,0.)?as f32; let vy=self.arg_num(&args,7,0.)?as f32; let vz=self.arg_num(&args,8,1.)?as f32;
2051                let r=self.arg_num(&args,9,2.)?as f32; let ns=self.arg_num(&args,10,8.)?as usize;
2052                let fr=self.arg_num(&args,11,0.)?as f32; let hue=self.arg_num(&args,12,0.)?as f32;
2053                let mut gfx = self.gfx.borrow_mut();
2054                let cam = gfx.camera.clone();
2055                crate::gfx::vtex::draw_chakra(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, r,ns, fr,hue);
2056                return Ok(Value::Unit);
2057            }
2058
2059            // vtex_yantra(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_layers,max_r, fr,hue)
2060            "vtex_yantra" | "ลายยันต์" | "纹咒" | "護符模様" | "부적무늬" => {
2061                let cx=self.arg_num(&args,0,0.)?as f32; let cy=self.arg_num(&args,1,0.)?as f32; let cz=self.arg_num(&args,2,0.)?as f32;
2062                let ux=self.arg_num(&args,3,1.)?as f32; let uy=self.arg_num(&args,4,0.)?as f32; let uz=self.arg_num(&args,5,0.)?as f32;
2063                let vx=self.arg_num(&args,6,0.)?as f32; let vy=self.arg_num(&args,7,0.)?as f32; let vz=self.arg_num(&args,8,1.)?as f32;
2064                let nl=self.arg_num(&args,9,4.)?as usize; let mr=self.arg_num(&args,10,3.)?as f32;
2065                let fr=self.arg_num(&args,11,0.)?as f32; let hue=self.arg_num(&args,12,0.)?as f32;
2066                let mut gfx = self.gfx.borrow_mut();
2067                let cam = gfx.camera.clone();
2068                crate::gfx::vtex::draw_yantra(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, nl,mr, fr,hue);
2069                return Ok(Value::Unit);
2070            }
2071
2072            // vtex_spiked_cog(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_teeth,r_body,r_spike,r_hub,n_spokes, fr,hue)
2073            "vtex_spiked_cog" | "ฟันเฟืองหนาม" | "纹棘轮" | "歯車模様" | "톱니바퀴" => {
2074                let cx=self.arg_num(&args,0,0.)?as f32; let cy=self.arg_num(&args,1,0.)?as f32; let cz=self.arg_num(&args,2,0.)?as f32;
2075                let ux=self.arg_num(&args,3,1.)?as f32; let uy=self.arg_num(&args,4,0.)?as f32; let uz=self.arg_num(&args,5,0.)?as f32;
2076                let vx=self.arg_num(&args,6,0.)?as f32; let vy=self.arg_num(&args,7,0.)?as f32; let vz=self.arg_num(&args,8,1.)?as f32;
2077                let nt=self.arg_num(&args,9,12.)?as usize; let rb=self.arg_num(&args,10,1.)?as f32;
2078                let rs=self.arg_num(&args,11,1.3)?as f32; let rh=self.arg_num(&args,12,0.2)?as f32;
2079                let ns=self.arg_num(&args,13,6.)?as usize;
2080                let fr=self.arg_num(&args,14,0.)?as f32; let hue=self.arg_num(&args,15,0.)?as f32;
2081                let mut gfx = self.gfx.borrow_mut();
2082                let cam = gfx.camera.clone();
2083                crate::gfx::vtex::draw_spiked_cog(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, nt,rb,rs,rh,ns, fr,hue);
2084                return Ok(Value::Unit);
2085            }
2086
2087            // vtex_torii(cx,cy,cz, ux,uy,uz, vx,vy,vz, width,height, fr,hue)
2088            "vtex_torii" | "ประตูโทริอิ" | "纹鸟居" | "鳥居" | "도리이" => {
2089                let cx=self.arg_num(&args,0,0.)?as f32; let cy=self.arg_num(&args,1,0.)?as f32; let cz=self.arg_num(&args,2,0.)?as f32;
2090                let ux=self.arg_num(&args,3,1.)?as f32; let uy=self.arg_num(&args,4,0.)?as f32; let uz=self.arg_num(&args,5,0.)?as f32;
2091                let vx=self.arg_num(&args,6,0.)?as f32; let vy=self.arg_num(&args,7,0.)?as f32; let vz=self.arg_num(&args,8,1.)?as f32;
2092                let w=self.arg_num(&args,9,4.)?as f32; let h=self.arg_num(&args,10,5.)?as f32;
2093                let fr=self.arg_num(&args,11,0.)?as f32; let hue=self.arg_num(&args,12,0.)?as f32;
2094                let mut gfx = self.gfx.borrow_mut();
2095                let cam = gfx.camera.clone();
2096                crate::gfx::vtex::draw_torii(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, w,h, fr,hue);
2097                return Ok(Value::Unit);
2098            }
2099
2100            // vtex_pagoda(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_tiers,base_w,tier_h,taper,eave_out, fr,hue)
2101            "vtex_pagoda" | "เจดีย์" | "纹塔" | "塔" | "탑" => {
2102                let cx=self.arg_num(&args,0,0.)?as f32; let cy=self.arg_num(&args,1,0.)?as f32; let cz=self.arg_num(&args,2,0.)?as f32;
2103                let ux=self.arg_num(&args,3,1.)?as f32; let uy=self.arg_num(&args,4,0.)?as f32; let uz=self.arg_num(&args,5,0.)?as f32;
2104                let vx=self.arg_num(&args,6,0.)?as f32; let vy=self.arg_num(&args,7,0.)?as f32; let vz=self.arg_num(&args,8,1.)?as f32;
2105                let nt=self.arg_num(&args,9,5.)?as usize; let bw=self.arg_num(&args,10,2.)?as f32;
2106                let th=self.arg_num(&args,11,1.)?as f32; let tp=self.arg_num(&args,12,0.72)?as f32;
2107                let eo=self.arg_num(&args,13,0.28)?as f32;
2108                let fr=self.arg_num(&args,14,0.)?as f32; let hue=self.arg_num(&args,15,0.)?as f32;
2109                let mut gfx = self.gfx.borrow_mut();
2110                let cam = gfx.camera.clone();
2111                crate::gfx::vtex::draw_pagoda(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, nt,bw,th,tp,eo, fr,hue);
2112                return Ok(Value::Unit);
2113            }
2114
2115            // ══════════════════════════════════════════════════════════════════
2116            // AUDIO BUILTINS
2117            // ══════════════════════════════════════════════════════════════════
2118
2119            // audio_tone(idx, x, y, z, w, freq, amp, lfo_rate, lfo_depth)
2120            #[cfg(not(target_arch = "wasm32"))]
2121            "audio_tone" | "เสียงโทน" | "音调" | "音調" | "음조" | "空间音" | "空間音" | "공간음" => {
2122                let idx  = self.arg_num(&args, 0, 0.0)? as usize;
2123                let x    = self.arg_num(&args, 1, 0.0)? as f32;
2124                let y    = self.arg_num(&args, 2, 0.0)? as f32;
2125                let z    = self.arg_num(&args, 3, 0.0)? as f32;
2126                let w    = self.arg_num(&args, 4, 1.0)? as f32;
2127                let freq = self.arg_num(&args, 5, 220.0)? as f32;
2128                let amp  = self.arg_num(&args, 6, 0.15)? as f32;
2129                let lfo_rate  = self.arg_num(&args, 7, 0.5)? as f32;
2130                let lfo_depth = self.arg_num(&args, 8, 0.02)? as f32;
2131                if let Some(audio) = &self.audio {
2132                    audio.set_tone(idx, ToneParams { x, y, z, w, freq, amp, lfo_rate, lfo_depth });
2133                }
2134                return Ok(Value::Unit);
2135            }
2136
2137            #[cfg(not(target_arch = "wasm32"))]
2138            "audio_listener" | "ผู้ฟัง" | "音频监听" | "音声リスナー" | "오디오리스너" => {
2139                let cry = self.arg_num(&args, 0, 1.0)? as f32;
2140                let sry = self.arg_num(&args, 1, 0.0)? as f32;
2141                let crx = self.arg_num(&args, 2, 1.0)? as f32;
2142                let srx = self.arg_num(&args, 3, 0.0)? as f32;
2143                if let Some(audio) = &self.audio {
2144                    audio.set_listener(cry, sry, crx, srx);
2145                }
2146                return Ok(Value::Unit);
2147            }
2148
2149            #[cfg(not(target_arch = "wasm32"))]
2150            "audio_bgm" | "เพลงพื้นหลัง" | "เพลงประกอบ" | "背景乐" | "BGM" | "배경음악" => {
2151                let path = match args.first() {
2152                    Some(Value::Str(s)) => s.clone(),
2153                    _ => return Ok(Value::Unit),
2154                };
2155                let vol = self.arg_num(&args, 1, 0.5)? as f32;
2156                if let Some(audio) = &self.audio {
2157                    audio.load_bgm(&path, vol);
2158                }
2159                return Ok(Value::Unit);
2160            }
2161
2162            #[cfg(not(target_arch = "wasm32"))]
2163            "audio_bgm_volume" | "ระดับเสียงพื้นหลัง" | "ระดับเพลงประกอบ" | "背景乐音量" | "BGM音量" | "배경음악음량" => {
2164                let vol = self.arg_num(&args, 0, 0.5)? as f32;
2165                if let Some(audio) = &self.audio {
2166                    audio.set_bgm_volume(vol);
2167                }
2168                return Ok(Value::Unit);
2169            }
2170
2171            #[cfg(not(target_arch = "wasm32"))]
2172            "audio_volume" | "ระดับเสียง" | "音量" | "음량" => {
2173                let vol = self.arg_num(&args, 0, 0.7)? as f32;
2174                if let Some(audio) = &self.audio {
2175                    audio.set_master_volume(vol);
2176                }
2177                return Ok(Value::Unit);
2178            }
2179
2180            // WASM audio builtins — delegate to Web Audio API
2181            #[cfg(target_arch = "wasm32")]
2182            "audio_tone" | "เสียงโทน" | "音调" | "音調" | "음조" | "空间音" | "空間音" | "공간음" => {
2183                let idx  = self.arg_num(&args, 0, 0.0)? as usize;
2184                let x    = self.arg_num(&args, 1, 0.0)? as f32;
2185                let y    = self.arg_num(&args, 2, 0.0)? as f32;
2186                let z    = self.arg_num(&args, 3, 0.0)? as f32;
2187                let w    = self.arg_num(&args, 4, 1.0)? as f32;
2188                let freq = self.arg_num(&args, 5, 220.0)? as f32;
2189                let amp  = self.arg_num(&args, 6, 0.15)? as f32;
2190                let lfo_rate  = self.arg_num(&args, 7, 0.5)? as f32;
2191                let lfo_depth = self.arg_num(&args, 8, 0.02)? as f32;
2192                crate::gfx::audio_web::set_tone(idx, x, y, z, w, freq, amp, lfo_rate, lfo_depth);
2193                return Ok(Value::Unit);
2194            }
2195
2196            #[cfg(target_arch = "wasm32")]
2197            "audio_listener" | "ผู้ฟัง" | "音频监听" | "音声リスナー" | "오디오리스너" => {
2198                let cry = self.arg_num(&args, 0, 1.0)? as f32;
2199                let sry = self.arg_num(&args, 1, 0.0)? as f32;
2200                let crx = self.arg_num(&args, 2, 1.0)? as f32;
2201                let srx = self.arg_num(&args, 3, 0.0)? as f32;
2202                crate::gfx::audio_web::set_listener(cry, sry, crx, srx);
2203                return Ok(Value::Unit);
2204            }
2205
2206            #[cfg(target_arch = "wasm32")]
2207            "audio_bgm" | "เพลงพื้นหลัง" | "เพลงประกอบ" | "背景乐" | "BGM" | "배경음악" => {
2208                let path = self.arg_str(&args, 0, "");
2209                let vol  = self.arg_num(&args, 1, 0.5)? as f32;
2210                crate::gfx::audio_web::load_bgm(&path, vol);
2211                return Ok(Value::Unit);
2212            }
2213
2214            #[cfg(target_arch = "wasm32")]
2215            "audio_bgm_volume" | "ระดับเสียงพื้นหลัง" | "ระดับเพลงประกอบ" | "背景乐音量" | "BGM音量" | "배경음악음량" => {
2216                let vol = self.arg_num(&args, 0, 0.5)? as f32;
2217                crate::gfx::audio_web::set_bgm_volume(vol);
2218                return Ok(Value::Unit);
2219            }
2220
2221            #[cfg(target_arch = "wasm32")]
2222            "audio_volume" | "ระดับเสียง" | "音量" | "음량" => {
2223                let vol = self.arg_num(&args, 0, 0.7)? as f32;
2224                crate::gfx::audio_web::set_master_volume(vol);
2225                return Ok(Value::Unit);
2226            }
2227
2228            // ── รอหน้าต่าง() — block until window closed / Escape ──
2229            "รอหน้าต่าง" | "wait_window" | "gfx_wait" => {
2230                #[cfg(not(target_arch = "wasm32"))]
2231                loop {
2232                    let still_open = {
2233                        let gfx = self.gfx.borrow();
2234                        gfx.window.as_ref()
2235                            .map(|w| w.is_open() && !w.is_key_down(minifb::Key::Escape))
2236                            .unwrap_or(false)
2237                    };
2238                    if !still_open { break; }
2239                    let (buf, w, h) = {
2240                        let gfx = self.gfx.borrow();
2241                        (gfx.buffer.clone(), gfx.width, gfx.height)
2242                    };
2243                    let mut gfx = self.gfx.borrow_mut();
2244                    if let Some(win) = gfx.window.as_mut() {
2245                        if win.update_with_buffer(&buf, w, h).is_err() { break; }
2246                    }
2247                }
2248                return Ok(Value::Unit);
2249            }
2250
2251            // ── File I/O ──────────────────────────────────────────────────────
2252            "read_file" | "อ่านไฟล์" => {
2253                let path = self.arg_str(&args, 0, "");
2254                return std::fs::read_to_string(&path)
2255                    .map(Value::Str)
2256                    .map_err(|e| EvalErr::from(format!("read_file '{path}': {e}")));
2257            }
2258            "write_file" | "เขียนไฟล์" => {
2259                let path    = self.arg_str(&args, 0, "");
2260                let content = self.arg_str(&args, 1, "");
2261                std::fs::write(&path, content.as_bytes())
2262                    .map_err(|e| EvalErr::from(format!("write_file '{path}': {e}")))?;
2263                return Ok(Value::Unit);
2264            }
2265            "print_file" | "พิมพ์ไฟล์" => {
2266                let content = self.arg_str(&args, 0, "");
2267                print!("{content}");
2268                return Ok(Value::Unit);
2269            }
2270
2271            // ── CLI arguments ─────────────────────────────────────────────────
2272            "get_args" | "รับอาร์กิวเมนต์" => {
2273                let v: Vec<Value> = std::env::args().map(Value::Str).collect();
2274                return Ok(Value::List(v));
2275            }
2276
2277            // ── String utilities ──────────────────────────────────────────────
2278            "split" | "str_split" | "แยก" => {
2279                let s   = self.arg_str(&args, 0, "");
2280                let sep = self.arg_str(&args, 1, "\n");
2281                let sep = if sep.is_empty() { "\n".into() } else { sep };
2282                let parts: Vec<Value> = s.split(sep.as_str())
2283                    .map(|p| Value::Str(p.to_string())).collect();
2284                return Ok(Value::List(parts));
2285            }
2286            "trim" | "str_trim" | "ตัดช่องว่าง" => {
2287                let s = self.arg_str(&args, 0, "");
2288                return Ok(Value::Str(s.trim().to_string()));
2289            }
2290            "starts_with" | "str_starts_with" | "เริ่มด้วย" => {
2291                let s      = self.arg_str(&args, 0, "");
2292                let prefix = self.arg_str(&args, 1, "");
2293                return Ok(Value::Bool(s.starts_with(prefix.as_str())));
2294            }
2295            "ends_with" | "str_ends_with" | "ลงท้ายด้วย" => {
2296                let s      = self.arg_str(&args, 0, "");
2297                let suffix = self.arg_str(&args, 1, "");
2298                return Ok(Value::Bool(s.ends_with(suffix.as_str())));
2299            }
2300            "str_replace" | "แทนสตริง" => {
2301                let s    = self.arg_str(&args, 0, "");
2302                let from = self.arg_str(&args, 1, "");
2303                let to   = self.arg_str(&args, 2, "");
2304                return Ok(Value::Str(s.replace(from.as_str(), to.as_str())));
2305            }
2306            "str_find" | "หาในสตริง" => {
2307                let s      = self.arg_str(&args, 0, "");
2308                let needle = self.arg_str(&args, 1, "");
2309                // Return char index (not byte index) for consistency with substr
2310                let pos = s.find(needle.as_str())
2311                    .map(|byte_i| s[..byte_i].chars().count() as f64)
2312                    .unwrap_or(-1.0);
2313                return Ok(Value::Number(pos));
2314            }
2315            "substr" | "str_slice" | "ส่วนสตริง" => {
2316                let s     = self.arg_str(&args, 0, "");
2317                let start = self.arg_num(&args, 1, 0.0)? as usize;
2318                let len   = args.get(2)
2319                    .map(|v| self.to_number(v).unwrap_or(999999.0) as usize)
2320                    .unwrap_or_else(|| s.chars().count().saturating_sub(start));
2321                let chars: Vec<char> = s.chars().collect();
2322                let end   = (start + len).min(chars.len());
2323                let slice: String = chars.get(start..end).unwrap_or(&[]).iter().collect();
2324                return Ok(Value::Str(slice));
2325            }
2326            "to_str" | "str" | "num_str" | "แปลงสตริง" => {
2327                let v = args.into_iter().next().unwrap_or(Value::Unit);
2328                return Ok(Value::Str(v.to_string()));
2329            }
2330            "str_repeat" | "ทำซ้ำสตริง" => {
2331                let s = self.arg_str(&args, 0, "");
2332                let n = self.arg_num(&args, 1, 1.0)? as usize;
2333                return Ok(Value::Str(s.repeat(n)));
2334            }
2335            "str_upper" => {
2336                let s = self.arg_str(&args, 0, "");
2337                return Ok(Value::Str(s.to_uppercase()));
2338            }
2339            "str_lower" => {
2340                let s = self.arg_str(&args, 0, "");
2341                return Ok(Value::Str(s.to_lowercase()));
2342            }
2343            "str_len" | "len" | "ความยาว" | "长度" | "長さ" | "길이" => {
2344                match args.first() {
2345                    Some(Value::Str(s))  => return Ok(Value::Number(s.chars().count() as f64)),
2346                    Some(Value::List(v)) => return Ok(Value::Number(v.len() as f64)),
2347                    _ => return Ok(Value::Number(0.0)),
2348                }
2349            }
2350
2351            // ── FNV-1a hash (deterministic, normalized 0.0–1.0) ──────────────
2352            "hash_str" | "แฮช" => {
2353                let s = self.arg_str(&args, 0, "");
2354                let mut h: u64 = 14695981039346656037_u64;
2355                for b in s.bytes() { h ^= b as u64; h = h.wrapping_mul(1099511628211); }
2356                return Ok(Value::Number((h & 0xFFFFFF) as f64 / 16777215.0));
2357            }
2358            "hash_int" | "แฮชจำนวน" => {
2359                let s = self.arg_str(&args, 0, "");
2360                let n = self.arg_num(&args, 1, 100.0)? as u64;
2361                let mut h: u64 = 14695981039346656037_u64;
2362                for b in s.bytes() { h ^= b as u64; h = h.wrapping_mul(1099511628211); }
2363                return Ok(Value::Number((h % n.max(1)) as f64));
2364            }
2365
2366            // ── List utilities ────────────────────────────────────────────────
2367            "list_new" | "รายการใหม่" | "新建列表" | "新規リスト" | "새목록" => {
2368                return Ok(Value::List(Vec::new()));
2369            }
2370            "list_push" | "เพิ่มรายการ" | "列表添加" | "リスト追加" | "목록추가" => {
2371                let lst = args.first().cloned().unwrap_or(Value::List(vec![]));
2372                let val = args.get(1).cloned().unwrap_or(Value::Unit);
2373                if let Value::List(mut v) = lst { v.push(val); return Ok(Value::List(v)); }
2374                return Ok(Value::List(vec![val]));
2375            }
2376            "list_get" | "รับรายการ" | "取元素" | "要素取得" | "요소가져오기" => {
2377                let lst = args.first().cloned().unwrap_or(Value::List(vec![]));
2378                let i   = self.arg_num(&args, 1, 0.0)? as usize;
2379                if let Value::List(v) = lst {
2380                    return Ok(v.get(i).cloned().unwrap_or(Value::Str(String::new())));
2381                }
2382                return Ok(Value::Str(String::new()));
2383            }
2384            "list_join" | "join" | "รวมรายการ" | "连接" | "連結" | "연결" => {
2385                let lst = args.first().cloned().unwrap_or(Value::List(vec![]));
2386                let sep = args.get(1).map(|v| v.to_string()).unwrap_or_default();
2387                if let Value::List(v) = lst {
2388                    return Ok(Value::Str(v.iter().map(|x| x.to_string())
2389                        .collect::<Vec<_>>().join(&sep)));
2390                }
2391                return Ok(Value::Str(String::new()));
2392            }
2393
2394            // ══════════════════════════════════════════════════════════════════
2395            // SVG EXPORT  (svg_begin / svg_rect / svg_circle / svg_line /
2396            //              svg_polyline / svg_text / svg_end / hsl_color)
2397            // Chinese aliases: 开始SVG 结束SVG SVG矩形 SVG圆形 SVG线段 SVG折线 SVG文本 HSL颜色
2398            // Thai aliases:    เริ่มSVG จบSVG SVGสี่เหลี่ยม SVGวงกลม SVGเส้น SVGเส้นหัก SVGข้อความ สีHSL
2399            // ══════════════════════════════════════════════════════════════════
2400
2401            "svg_begin" | "开始SVG" | "เริ่มSVG" => {
2402                let path   = self.arg_str(&args, 0, "output.svg");
2403                let width  = self.arg_num(&args, 1, 800.0)?;
2404                let height = self.arg_num(&args, 2, 600.0)?;
2405                *self.svg.borrow_mut() = Some(SvgWriter::new(path, width, height));
2406                return Ok(Value::Unit);
2407            }
2408
2409            "svg_rect" | "SVG矩形" | "SVGสี่เหลี่ยม" => {
2410                let x    = self.arg_num(&args, 0, 0.0)?;
2411                let y    = self.arg_num(&args, 1, 0.0)?;
2412                let w    = self.arg_num(&args, 2, 10.0)?;
2413                let h    = self.arg_num(&args, 3, 10.0)?;
2414                let fill = self.arg_str(&args, 4, "#ffffff");
2415                if let Some(svg) = self.svg.borrow_mut().as_mut() {
2416                    svg.elements.push(format!(
2417                        "<rect x=\"{x:.1}\" y=\"{y:.1}\" width=\"{w:.1}\" \
2418                         height=\"{h:.1}\" fill=\"{fill}\"/>"));
2419                }
2420                return Ok(Value::Unit);
2421            }
2422
2423            "svg_circle" | "SVG圆形" | "SVGวงกลม" => {
2424                let cx   = self.arg_num(&args, 0, 0.0)?;
2425                let cy   = self.arg_num(&args, 1, 0.0)?;
2426                let r    = self.arg_num(&args, 2, 5.0)?;
2427                let fill = self.arg_str(&args, 3, "#ffffff");
2428                if let Some(svg) = self.svg.borrow_mut().as_mut() {
2429                    svg.elements.push(format!(
2430                        "<circle cx=\"{cx:.1}\" cy=\"{cy:.1}\" r=\"{r:.1}\" fill=\"{fill}\"/>"));
2431                }
2432                return Ok(Value::Unit);
2433            }
2434
2435            "svg_line" | "SVG线段" | "SVGเส้น" => {
2436                let x1     = self.arg_num(&args, 0, 0.0)?;
2437                let y1     = self.arg_num(&args, 1, 0.0)?;
2438                let x2     = self.arg_num(&args, 2, 0.0)?;
2439                let y2     = self.arg_num(&args, 3, 0.0)?;
2440                let stroke = self.arg_str(&args, 4, "#ffffff");
2441                let sw     = self.arg_num(&args, 5, 1.0)?;
2442                if let Some(svg) = self.svg.borrow_mut().as_mut() {
2443                    svg.elements.push(format!(
2444                        "<line x1=\"{x1:.1}\" y1=\"{y1:.1}\" x2=\"{x2:.1}\" y2=\"{y2:.1}\" \
2445                         stroke=\"{stroke}\" stroke-width=\"{sw:.1}\"/>"));
2446                }
2447                return Ok(Value::Unit);
2448            }
2449
2450            "svg_polyline" | "SVG折线" | "SVGเส้นหัก" => {
2451                let pts    = self.arg_str(&args, 0, "");
2452                let stroke = self.arg_str(&args, 1, "#ffffff");
2453                let sw     = self.arg_num(&args, 2, 1.0)?;
2454                if let Some(svg) = self.svg.borrow_mut().as_mut() {
2455                    svg.elements.push(format!(
2456                        "<polyline points=\"{pts}\" fill=\"none\" \
2457                         stroke=\"{stroke}\" stroke-width=\"{sw:.1}\"/>"));
2458                }
2459                return Ok(Value::Unit);
2460            }
2461
2462            "svg_text" | "SVG文本" | "SVGข้อความ" => {
2463                let x    = self.arg_num(&args, 0, 0.0)?;
2464                let y    = self.arg_num(&args, 1, 0.0)?;
2465                let text = self.arg_str(&args, 2, "");
2466                let fill = self.arg_str(&args, 3, "#ffffff");
2467                let size = self.arg_num(&args, 4, 12.0)?;
2468                if let Some(svg) = self.svg.borrow_mut().as_mut() {
2469                    let safe = text.replace('&', "&amp;").replace('<', "&lt;").replace('>', "&gt;");
2470                    svg.elements.push(format!(
2471                        "<text x=\"{x:.1}\" y=\"{y:.1}\" fill=\"{fill}\" \
2472                         font-family=\"monospace\" font-size=\"{size:.0}\">{safe}</text>"));
2473                }
2474                return Ok(Value::Unit);
2475            }
2476
2477            "svg_end" | "结束SVG" | "จบSVG" => {
2478                {
2479                    let borrow = self.svg.borrow();
2480                    if let Some(svg) = borrow.as_ref() {
2481                        svg.save().map_err(|e| EvalErr::from(format!("svg_end: {e}")))?;
2482                    }
2483                }
2484                *self.svg.borrow_mut() = None;
2485                return Ok(Value::Unit);
2486            }
2487
2488            "hsl_color" | "HSL颜色" | "สีHSL" => {
2489                let h = self.arg_num(&args, 0, 0.0)?;
2490                let s = self.arg_num(&args, 1, 70.0)?;
2491                let l = self.arg_num(&args, 2, 50.0)?;
2492                return Ok(Value::Str(hsl_to_hex(h, s, l)));
2493            }
2494
2495            // ══════════════════════════════════════════════════════════════════
2496            // FFT / AUDIO ANALYSIS BUILTINS  (native only)
2497            // ══════════════════════════════════════════════════════════════════
2498
2499            // fft_push(samples_list) — feed raw audio samples and run FFT
2500            #[cfg(not(target_arch = "wasm32"))]
2501            "fft_push" | "วิเคราะห์เสียง" | "频谱输入" | "FFT入力" | "FFT입력" => {
2502                if let Some(Value::List(v)) = args.first() {
2503                    let samples: Vec<f32> = v.iter()
2504                        .filter_map(|x| if let Value::Number(n) = x { Some(*n as f32) } else { None })
2505                        .collect();
2506                    self.fft.borrow_mut().push_samples(&samples);
2507                }
2508                return Ok(Value::Unit);
2509            }
2510
2511            // fft_bands(n) → list of n log-spaced magnitude bands (0..1)
2512            #[cfg(not(target_arch = "wasm32"))]
2513            "fft_bands" | "แถบความถี่" | "频段" | "周波数帯" | "주파수대" => {
2514                let n = self.arg_num(&args, 0, 32.0)? as usize;
2515                let bands = self.fft.borrow().freq_bands(n);
2516                *self.fft_bands_cache.borrow_mut() = bands.clone();
2517                return Ok(Value::List(bands.into_iter().map(|v| Value::Number(v as f64)).collect()));
2518            }
2519
2520            // fft_beat() → bool
2521            #[cfg(not(target_arch = "wasm32"))]
2522            "fft_beat" | "จังหวะเสียง" | "节拍检测" | "ビート検出" | "비트" => {
2523                return Ok(Value::Bool(self.fft.borrow().is_beat()));
2524            }
2525
2526            // fft_beat_ratio() → f64  (1.0 = at threshold, >1 = strong beat)
2527            #[cfg(not(target_arch = "wasm32"))]
2528            "fft_beat_ratio" | "อัตราจังหวะ" | "节拍比" | "ビート比" | "비트비율" => {
2529                return Ok(Value::Number(self.fft.borrow().beat_ratio() as f64));
2530            }
2531
2532            // fft_rms() → f64
2533            #[cfg(not(target_arch = "wasm32"))]
2534            "fft_rms" | "ระดับRMS" | "均方根" | "二乗平均" | "RMS레벨" => {
2535                return Ok(Value::Number(self.fft.borrow().rms() as f64));
2536            }
2537
2538            // fft_dominant_freq() → f64  in Hz
2539            #[cfg(not(target_arch = "wasm32"))]
2540            "fft_dominant_freq" | "ความถี่หลัก" | "主频" | "主要周波数" | "주파수" => {
2541                return Ok(Value::Number(self.fft.borrow().dominant_freq() as f64));
2542            }
2543
2544            // ── wasm32 stubs: fft builtins are no-ops on web ───────────────
2545            #[cfg(target_arch = "wasm32")]
2546            "fft_push" | "วิเคราะห์เสียง" | "频谱输入" | "FFT入力" | "FFT입력" => { return Ok(Value::Unit); }
2547            #[cfg(target_arch = "wasm32")]
2548            "fft_bands" | "แถบความถี่" | "频段" | "周波数帯" | "주파수대" => {
2549                let n = self.arg_num(&args, 0, 32.0)? as usize;
2550                return Ok(Value::List(vec![Value::Number(0.0); n]));
2551            }
2552            #[cfg(target_arch = "wasm32")]
2553            "fft_beat" | "จังหวะเสียง" | "节拍检测" | "ビート検出" | "비트" => { return Ok(Value::Bool(false)); }
2554            #[cfg(target_arch = "wasm32")]
2555            "fft_beat_ratio" | "อัตราจังหวะ" | "节拍比" | "ビート比" | "비트비율" => { return Ok(Value::Number(1.0)); }
2556            #[cfg(target_arch = "wasm32")]
2557            "fft_rms" | "ระดับRMS" | "均方根" | "二乗平均" | "RMS레벨" => { return Ok(Value::Number(0.0)); }
2558            #[cfg(target_arch = "wasm32")]
2559            "fft_dominant_freq" | "ความถี่หลัก" | "主频" | "主要周波数" | "주파수" => { return Ok(Value::Number(0.0)); }
2560
2561            // ══════════════════════════════════════════════════════════════════
2562            // PROCEDURAL TEXTURE BLIT BUILTINS  (screen-space)
2563            // All: name(dst_x, dst_y, width, height, ...params, palette)
2564            // palette: "rainbow" | "fire" | "ocean" | "psychedelic" | "neon" | "forest"
2565            // ══════════════════════════════════════════════════════════════════
2566
2567            // tex_checkerboard(x, y, w, h, tiles, r1,g1,b1, r2,g2,b2)
2568            "tex_checkerboard" | "ลายตารางหมากรุก" => {
2569                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2570                let tiles = self.arg_num(&args, 4, 8.0)? as u32;
2571                let (r1,g1,b1) = (self.arg_num(&args,5,255.)? as u32, self.arg_num(&args,6,255.)? as u32, self.arg_num(&args,7,255.)? as u32);
2572                let (r2,g2,b2) = (self.arg_num(&args,8,0.)? as u32,   self.arg_num(&args,9,0.)? as u32,   self.arg_num(&args,10,0.)? as u32);
2573                let c1 = (r1<<16)|(g1<<8)|b1; let c2 = (r2<<16)|(g2<<8)|b2;
2574                let mut gfx = self.gfx.borrow_mut();
2575                let (bw, bh) = (gfx.width, gfx.height);
2576                for row in 0..th { for col in 0..tw {
2577                    let cx = col as u32 * tiles / tw as u32;
2578                    let cy = row as u32 * tiles / th as u32;
2579                    let (dx, dy) = (tx+col, ty+row);
2580                    if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = if (cx+cy)%2==0 { c1 } else { c2 }; }
2581                }}
2582                return Ok(Value::Unit);
2583            }
2584
2585            // tex_gradient(x, y, w, h, angle_deg, r1,g1,b1, r2,g2,b2)
2586            "tex_gradient" | "ลายไล่สี" => {
2587                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2588                let angle = self.arg_num(&args, 4, 0.0)? as f32;
2589                let (r1,g1,b1) = (self.arg_num(&args,5,0.)? as f32/255., self.arg_num(&args,6,0.)? as f32/255., self.arg_num(&args,7,0.)? as f32/255.);
2590                let (r2,g2,b2) = (self.arg_num(&args,8,255.)? as f32/255., self.arg_num(&args,9,255.)? as f32/255., self.arg_num(&args,10,255.)? as f32/255.);
2591                let (ca, sa) = (angle.to_radians().cos(), angle.to_radians().sin());
2592                let mut gfx = self.gfx.borrow_mut();
2593                let (bw, bh) = (gfx.width, gfx.height);
2594                for row in 0..th { for col in 0..tw {
2595                    let nx = col as f32/tw as f32 - 0.5; let ny = row as f32/th as f32 - 0.5;
2596                    let t = ((nx*ca + ny*sa + 0.707)/1.414).clamp(0.,1.);
2597                    let (dx, dy) = (tx+col, ty+row);
2598                    if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(r1+(r2-r1)*t, g1+(g2-g1)*t, b1+(b2-b1)*t); }
2599                }}
2600                return Ok(Value::Unit);
2601            }
2602
2603            // tex_noise(x, y, w, h, scale, octaves, seed, palette)
2604            "tex_noise" | "ลายนอยส์" => {
2605                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2606                let scale   = self.arg_num(&args, 4, 4.0)? as f32;
2607                let octaves = self.arg_num(&args, 5, 4.0)? as u32;
2608                let seed    = self.arg_num(&args, 6, 0.0)? as u32;
2609                let palette = self.arg_str(&args, 7, "rainbow");
2610                let mut gfx = self.gfx.borrow_mut();
2611                let (bw, bh) = (gfx.width, gfx.height);
2612                for row in 0..th { for col in 0..tw {
2613                    let v = tex_fbm(col as f32*scale/tw as f32, row as f32*scale/th as f32, octaves, seed);
2614                    let [r,g,b] = tex_palette(&palette, v);
2615                    let (dx, dy) = (tx+col, ty+row);
2616                    if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(r, g, b); }
2617                }}
2618                return Ok(Value::Unit);
2619            }
2620
2621            // tex_freq_map(x, y, w, h, time, speed, palette)
2622            // Uses bands written by the last fft_bands() call.
2623            "tex_freq_map" | "ลายความถี่" => {
2624                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2625                let time    = self.arg_num(&args, 4, 0.0)? as f32;
2626                let speed   = self.arg_num(&args, 5, 0.3)? as f32;
2627                let palette = self.arg_str(&args, 6, "rainbow");
2628                let bands: Vec<f32> = {
2629                    let c = self.fft_bands_cache.borrow();
2630                    if c.is_empty() { vec![0.0; 32] } else { c.clone() }
2631                };
2632                let n = bands.len().max(1);
2633                let mut gfx = self.gfx.borrow_mut();
2634                let (bw, bh) = (gfx.width, gfx.height);
2635                for row in 0..th { for col in 0..tw {
2636                    let band_idx = (col * n / tw.max(1)).min(n-1);
2637                    let mag = bands[band_idx].clamp(0.,1.);
2638                    let fill_y = (mag * th as f32) as usize;
2639                    if row >= th.saturating_sub(fill_y) {
2640                        let t = (col as f32/tw as f32 + time*speed) % 1.0;
2641                        let [r,g,b] = tex_palette(&palette, t);
2642                        let bright = mag * (1.0 - row as f32/th as f32 * 0.5);
2643                        let (dx, dy) = (tx+col, ty+row);
2644                        if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(r*bright, g*bright, b*bright); }
2645                    }
2646                }}
2647                return Ok(Value::Unit);
2648            }
2649
2650            // tex_spiral(x, y, w, h, freq, bands, time, palette)
2651            "tex_spiral" | "ลายเกลียวหมุน" => {
2652                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2653                let freq    = self.arg_num(&args, 4, 5.0)? as f32;
2654                let n_bands = self.arg_num(&args, 5, 8.0)? as f32;
2655                let time    = self.arg_num(&args, 6, 0.0)? as f32;
2656                let palette = self.arg_str(&args, 7, "rainbow");
2657                let mut gfx = self.gfx.borrow_mut();
2658                let (bw, bh) = (gfx.width, gfx.height);
2659                for row in 0..th { for col in 0..tw {
2660                    let nx = col as f32/tw as f32 - 0.5; let ny = row as f32/th as f32 - 0.5;
2661                    let r  = (nx*nx + ny*ny).sqrt();
2662                    let theta = ny.atan2(nx);
2663                    let t = ((r*freq - theta/std::f32::consts::TAU + time*0.5) * n_bands % 1.0).abs();
2664                    let [cr,cg,cb] = tex_palette(&palette, t);
2665                    let (dx, dy) = (tx+col, ty+row);
2666                    if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(cr, cg, cb); }
2667                }}
2668                return Ok(Value::Unit);
2669            }
2670
2671            // tex_ripple(x, y, w, h, freq, cx, cy, time, palette)
2672            "tex_ripple" | "ลายระลอก" => {
2673                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2674                let freq    = self.arg_num(&args, 4, 10.0)? as f32;
2675                let rcx     = self.arg_num(&args, 5, 0.5)? as f32;
2676                let rcy     = self.arg_num(&args, 6, 0.5)? as f32;
2677                let time    = self.arg_num(&args, 7, 0.0)? as f32;
2678                let palette = self.arg_str(&args, 8, "ocean");
2679                let mut gfx = self.gfx.borrow_mut();
2680                let (bw, bh) = (gfx.width, gfx.height);
2681                for row in 0..th { for col in 0..tw {
2682                    let nx = col as f32/tw as f32 - rcx; let ny = row as f32/th as f32 - rcy;
2683                    let r = (nx*nx + ny*ny).sqrt();
2684                    let t = ((r*freq - time) % 1.0).abs();
2685                    let [cr,cg,cb] = tex_palette(&palette, t);
2686                    let (dx, dy) = (tx+col, ty+row);
2687                    if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(cr, cg, cb); }
2688                }}
2689                return Ok(Value::Unit);
2690            }
2691
2692            // tex_mandelbrot(x, y, w, h, zoom, cx, cy, max_iter, palette)
2693            "tex_mandelbrot" | "ลายแมนเดลบรอต" => {
2694                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2695                let zoom     = self.arg_num(&args, 4, 1.0)?;
2696                let mcx      = self.arg_num(&args, 5, -0.5)?;
2697                let mcy      = self.arg_num(&args, 6, 0.0)?;
2698                let max_iter = self.arg_num(&args, 7, 64.0)? as u32;
2699                let palette  = self.arg_str(&args, 8, "psychedelic");
2700                let mut gfx = self.gfx.borrow_mut();
2701                let (bw, bh) = (gfx.width, gfx.height);
2702                for row in 0..th { for col in 0..tw {
2703                    let zx0 = (col as f64/tw as f64 - 0.5)/zoom + mcx;
2704                    let zy0 = (row as f64/th as f64 - 0.5)/zoom + mcy;
2705                    let mut x = 0.0f64; let mut y = 0.0f64; let mut i = 0u32;
2706                    while i < max_iter && x*x+y*y < 4.0 { let t=x*x-y*y+zx0; y=2.0*x*y+zy0; x=t; i+=1; }
2707                    let t = if i==max_iter { 0.0f32 } else {
2708                        (i as f32 - (x as f32*x as f32+y as f32*y as f32).ln().ln()/2.0f32.ln()) / max_iter as f32
2709                    };
2710                    let [cr,cg,cb] = tex_palette(&palette, t.clamp(0.,1.));
2711                    let (dx, dy) = (tx+col, ty+row);
2712                    if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(cr, cg, cb); }
2713                }}
2714                return Ok(Value::Unit);
2715            }
2716
2717            // tex_julia(x, y, w, h, c_re, c_im, max_iter, palette)
2718            "tex_julia" | "ลายจูเลีย" => {
2719                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2720                let c_re     = self.arg_num(&args, 4, -0.7)?;
2721                let c_im     = self.arg_num(&args, 5, 0.27)?;
2722                let max_iter = self.arg_num(&args, 6, 64.0)? as u32;
2723                let palette  = self.arg_str(&args, 7, "neon");
2724                let mut gfx = self.gfx.borrow_mut();
2725                let (bw, bh) = (gfx.width, gfx.height);
2726                for row in 0..th { for col in 0..tw {
2727                    let mut zx = (col as f64/tw as f64 - 0.5)*3.5;
2728                    let mut zy = (row as f64/th as f64 - 0.5)*3.5;
2729                    let mut i = 0u32;
2730                    while i < max_iter && zx*zx+zy*zy < 4.0 { let t=zx*zx-zy*zy+c_re; zy=2.0*zx*zy+c_im; zx=t; i+=1; }
2731                    let t = i as f32 / max_iter as f32;
2732                    let [cr,cg,cb] = tex_palette(&palette, t);
2733                    let (dx, dy) = (tx+col, ty+row);
2734                    if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(cr, cg, cb); }
2735                }}
2736                return Ok(Value::Unit);
2737            }
2738
2739            // tex_voronoi(x, y, w, h, cells, seed, palette)
2740            "tex_voronoi" | "ลายโวโรนอย" => {
2741                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2742                let cells   = self.arg_num(&args, 4, 16.0)? as u32;
2743                let seed    = self.arg_num(&args, 5, 42.0)? as u32;
2744                let palette = self.arg_str(&args, 6, "rainbow");
2745                let pts: Vec<[f32;2]> = (0..cells).map(|i| [tex_hash(i as i32,0,seed), tex_hash(i as i32,1,seed+999)]).collect();
2746                let mut gfx = self.gfx.borrow_mut();
2747                let (bw, bh) = (gfx.width, gfx.height);
2748                for row in 0..th { for col in 0..tw {
2749                    let (fx, fy) = (col as f32/tw as f32, row as f32/th as f32);
2750                    let (min_d, nearest) = pts.iter().enumerate().fold((f32::MAX,0usize), |(d,idx),(i,&[cx,cy])| {
2751                        let dd = (fx-cx).powi(2)+(fy-cy).powi(2);
2752                        if dd < d { (dd,i) } else { (d,idx) }
2753                    });
2754                    let t = (nearest as f32/cells as f32 + min_d*4.0) % 1.0;
2755                    let [cr,cg,cb] = tex_palette(&palette, t);
2756                    let (dx, dy) = (tx+col, ty+row);
2757                    if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(cr, cg, cb); }
2758                }}
2759                return Ok(Value::Unit);
2760            }
2761
2762            // tex_halftone(x, y, w, h, dot_size, time, palette)
2763            "tex_halftone" | "ลายฮาล์ฟโทน" => {
2764                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2765                let dot_size = self.arg_num(&args, 4, 0.05)? as f32;
2766                let time     = self.arg_num(&args, 5, 0.0)? as f32;
2767                let palette  = self.arg_str(&args, 6, "rainbow");
2768                let mut gfx = self.gfx.borrow_mut();
2769                let (bw, bh) = (gfx.width, gfx.height);
2770                for row in 0..th { for col in 0..tw {
2771                    let (fx, fy) = (col as f32/tw as f32, row as f32/th as f32);
2772                    let gx = (fx/dot_size).floor(); let gy = (fy/dot_size).floor();
2773                    let lx = (fx/dot_size - gx - 0.5)*2.0; let ly = (fy/dot_size - gy - 0.5)*2.0;
2774                    let r = (lx*lx + ly*ly).sqrt();
2775                    let t = (gx/(1.0/dot_size) + time*0.1) % 1.0;
2776                    let a = if r < 0.7 { ((0.7-r)/0.7).clamp(0.,1.) } else { 0.0 };
2777                    if a > 0.0 {
2778                        let [cr,cg,cb] = tex_palette(&palette, t);
2779                        let (dx, dy) = (tx+col, ty+row);
2780                        if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(cr, cg, cb); }
2781                    }
2782                }}
2783                return Ok(Value::Unit);
2784            }
2785
2786            // ══════════════════════════════════════════════════════════════════
2787            // RENDER / LIGHTING MODES  (holographic cel shading)
2788            // ══════════════════════════════════════════════════════════════════
2789            // set_shade_mode(m) — 0 flat · 1 cel · 2 holo (default)
2790            "set_shade_mode" | "设置着色" | "シェード設定" | "셰이드모드" | "ตั้งการแรเงา" => {
2791                let m = self.arg_num(&args, 0, 2.0)? as u8;
2792                self.gfx.borrow_mut().shade_mode = m;
2793                return Ok(Value::Unit);
2794            }
2795            // set_cel_bands(n) — number of posterisation bands (>=2)
2796            "set_cel_bands" | "设置色阶" | "セル段数" | "셀밴드" | "ตั้งระดับสี" => {
2797                let n = (self.arg_num(&args, 0, 4.0)? as u32).max(2);
2798                self.gfx.borrow_mut().shade.bands = n;
2799                return Ok(Value::Unit);
2800            }
2801            // set_shadow_color(r,g,b) — coloured-shadow tint, 0-255
2802            "set_shadow_color" | "设置阴影色" | "影の色" | "그림자색" | "ตั้งสีเงา" => {
2803                let r=self.arg_num(&args,0,26.)? as f32/255.0;
2804                let g=self.arg_num(&args,1,33.)? as f32/255.0;
2805                let b=self.arg_num(&args,2,77.)? as f32/255.0;
2806                self.gfx.borrow_mut().shade.shadow = [r,g,b];
2807                return Ok(Value::Unit);
2808            }
2809            // set_rim(strength, r,g,b) — holographic fresnel edge glow
2810            // ══════════════════════════════════════════════════════════════════
2811            // CRYPTOGRAPHY (ling-crypto) — geo suite, hybrid PQ KEM, holographic
2812            // Bytes cross the language boundary as lowercase hex strings.
2813            // ══════════════════════════════════════════════════════════════════
2814            #[cfg(not(target_arch = "wasm32"))]
2815            "crypto_hash" | "แฮชเข้ารหัส" | "几何哈希" | "幾何ハッシュ" | "기하해시" => {
2816                let s = self.arg_str(&args, 0, "");
2817                return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(s.as_bytes()))));
2818            }
2819            // 3-D torus-knot fingerprint of any text/key → flat [x,y,z, x,y,z, …]
2820            #[cfg(not(target_arch = "wasm32"))]
2821            "knot_points" | "จุดปม" | "结点坐标" | "結び目点" | "매듭점" => {
2822                let s = self.arg_str(&args, 0, "");
2823                let shape = ling_crypto::geo::KnotShape::from_bytes(s.as_bytes());
2824                let mut out = Vec::with_capacity(shape.points.len() * 3);
2825                for p in &shape.points {
2826                    out.push(Value::Number(p[0] as f64));
2827                    out.push(Value::Number(p[1] as f64));
2828                    out.push(Value::Number(p[2] as f64));
2829                }
2830                return Ok(Value::List(out));
2831            }
2832            #[cfg(not(target_arch = "wasm32"))]
2833            "knot_label" | "ป้ายปม" | "结点标签" | "結び目ラベル" | "매듭라벨" => {
2834                let s = self.arg_str(&args, 0, "");
2835                return Ok(Value::Str(ling_crypto::geo::KnotShape::from_bytes(s.as_bytes()).label()));
2836            }
2837            // KEM keypair (hybrid X25519+ML-KEM-768) → integer handle
2838            #[cfg(not(target_arch = "wasm32"))]
2839            "knot_keygen" | "hybrid_keygen" | "สร้างกุญแจปม" | "生成密钥" | "鍵生成" | "키생성" => {
2840                self.crypto_ids.push(ling_crypto::KnotIdentity::generate());
2841                return Ok(Value::Number((self.crypto_ids.len() - 1) as f64));
2842            }
2843            #[cfg(not(target_arch = "wasm32"))]
2844            "knot_public" | "hybrid_public" | "กุญแจสาธารณะปม" | "公钥" | "公開鍵" | "공개키" => {
2845                let h = self.arg_num(&args, 0, 0.0)? as usize;
2846                let pk = self.crypto_ids.get(h).map(|id| hex_encode(id.public_key())).unwrap_or_default();
2847                return Ok(Value::Str(pk));
2848            }
2849            // encapsulate(pubkey_hex) → [ciphertext_hex, shared_secret_hex]
2850            #[cfg(not(target_arch = "wasm32"))]
2851            "knot_encapsulate" | "hybrid_encapsulate" | "ห่อกุญแจปม" | "封装密钥" | "カプセル化" | "캡슐화" => {
2852                let pk = hex_decode(&self.arg_str(&args, 0, ""));
2853                match ling_crypto::geo::knot_encapsulate(&pk) {
2854                    Ok((ct, ss)) => return Ok(Value::List(vec![Value::Str(hex_encode(&ct)), Value::Str(hex_encode(&ss))])),
2855                    Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
2856                }
2857            }
2858            // decapsulate(handle, ciphertext_hex) → shared_secret_hex
2859            #[cfg(not(target_arch = "wasm32"))]
2860            "knot_decapsulate" | "hybrid_decapsulate" | "แกะกุญแจปม" | "解封装密钥" | "カプセル解除" | "캡슐해제" => {
2861                let h = self.arg_num(&args, 0, 0.0)? as usize;
2862                let ct = hex_decode(&self.arg_str(&args, 1, ""));
2863                let ss = self.crypto_ids.get(h)
2864                    .and_then(|id| id.decapsulate(&ct).ok())
2865                    .map(|s| hex_encode(&s)).unwrap_or_default();
2866                return Ok(Value::Str(ss));
2867            }
2868            // Authenticated encryption (XChaCha20-Poly1305) — seal(key_hex, text) → ct_hex
2869            #[cfg(not(target_arch = "wasm32"))]
2870            "crypto_seal" | "ผนึก" | "封印" | "封印する" | "봉인" => {
2871                let key = hex_to_32(&self.arg_str(&args, 0, ""));
2872                let pt = self.arg_str(&args, 1, "");
2873                match ling_crypto::geo::holo_seal(key, pt.as_bytes()) {
2874                    Ok(ct) => return Ok(Value::Str(hex_encode(&ct))),
2875                    Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
2876                }
2877            }
2878            #[cfg(not(target_arch = "wasm32"))]
2879            "crypto_open" | "เปิดผนึก" | "解封" | "封印解除" | "봉인해제" => {
2880                let key = hex_to_32(&self.arg_str(&args, 0, ""));
2881                let ct = hex_decode(&self.arg_str(&args, 1, ""));
2882                match ling_crypto::geo::holo_open(key, &ct) {
2883                    Ok(pt) => return Ok(Value::Str(String::from_utf8_lossy(&pt).into_owned())),
2884                    Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
2885                }
2886            }
2887            // Holographic all-or-nothing transform — 4-D fragment coords [a,b,c,d, …]
2888            #[cfg(not(target_arch = "wasm32"))]
2889            "holo_points" | "จุดโฮโลแกรม" | "全息点" | "ホログラム点" | "홀로그램점" => {
2890                let s = self.arg_str(&args, 0, "");
2891                let frags = ling_crypto::geo::scatter(s.as_bytes());
2892                let mut out = Vec::with_capacity(frags.len() * 4);
2893                for f in &frags { for c in f.coord { out.push(Value::Number(c as f64)); } }
2894                return Ok(Value::List(out));
2895            }
2896            #[cfg(not(target_arch = "wasm32"))]
2897            "holo_fragment_count" | "จำนวนชิ้นโฮโลแกรม" | "全息碎片数" | "ホログラム断片数" | "홀로그램조각수" => {
2898                let s = self.arg_str(&args, 0, "");
2899                return Ok(Value::Number(ling_crypto::geo::scatter(s.as_bytes()).len() as f64));
2900            }
2901
2902            // ══════════════════════════════════════════════════════════════════
2903            // ling-ui — animation easings + holographic vector widgets + text I/O
2904            // ══════════════════════════════════════════════════════════════════
2905            "ease" => {
2906                let name = self.arg_str(&args, 0, "ease");
2907                let t = self.arg_num(&args, 1, 0.0)? as f32;
2908                return Ok(Value::Number(ling_ui::Easing::from_name(&name).apply(t) as f64));
2909            }
2910            #[cfg(not(target_arch = "wasm32"))]
2911            "mouse_x" => {
2912                let gfx = self.gfx.borrow();
2913                let v = gfx.window.as_ref().and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp)).map(|p| p.0 as f64).unwrap_or(0.0);
2914                return Ok(Value::Number(v));
2915            }
2916            #[cfg(not(target_arch = "wasm32"))]
2917            "mouse_y" => {
2918                let gfx = self.gfx.borrow();
2919                let v = gfx.window.as_ref().and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp)).map(|p| p.1 as f64).unwrap_or(0.0);
2920                return Ok(Value::Number(v));
2921            }
2922            #[cfg(not(target_arch = "wasm32"))]
2923            "mouse_down" => {
2924                let gfx = self.gfx.borrow();
2925                let d = gfx.window.as_ref().map(|w| w.get_mouse_down(minifb::MouseButton::Left)).unwrap_or(false);
2926                return Ok(Value::Bool(d));
2927            }
2928            #[cfg(not(target_arch = "wasm32"))]
2929            "ui_hot" | "热区" | "ホットエリア" | "핫존" | "พื้นที่สัมผัส" => {
2930                let x = self.arg_num(&args,0,0.0)? as f32;
2931                let y = self.arg_num(&args,1,0.0)? as f32;
2932                let w = self.arg_num(&args,2,0.0)? as f32;
2933                let h = self.arg_num(&args,3,0.0)? as f32;
2934                let gfx = self.gfx.borrow();
2935                let (mx,my) = gfx.window.as_ref().and_then(|win| win.get_mouse_pos(minifb::MouseMode::Clamp)).unwrap_or((0.0,0.0));
2936                return Ok(Value::Bool(ling_ui::holo::hit_rect(mx,my,x,y,w,h)));
2937            }
2938            // ui_text(x, y, scale, "string") — holographic vector text
2939            "ui_text" | "界面文字" | "UI文字" | "UI텍스트" | "ข้อความหน้าจอ" => {
2940                let x = self.arg_num(&args,0,0.0)? as f32;
2941                let y = self.arg_num(&args,1,0.0)? as f32;
2942                let scale = self.arg_num(&args,2,16.0)? as f32;
2943                let s = self.arg_str(&args,3,"");
2944                let segs = ling_ui::holo::text_lines(&s, x, y, scale*0.62, scale, scale*0.24);
2945                let mut gfx = self.gfx.borrow_mut();
2946                let (w,h,color) = (gfx.width, gfx.height, gfx.color);
2947                for sg in segs { draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]); }
2948                return Ok(Value::Unit);
2949            }
2950            // font_load("path.ttf") — load a vector font (outlines cached lazily as
2951            // cache/fonts/<stem>/<codepoint>.ling). Returns a handle, or -1 on failure.
2952            #[cfg(not(target_arch = "wasm32"))]
2953            "font_load" | "โหลดฟอนต์" | "加载字体" | "フォント読込" | "글꼴로드" => {
2954                let path = self.arg_str(&args, 0, "");
2955                // Optional 2nd arg: variable-font weight (e.g. 600 for a solid, bold UI).
2956                let weight = match self.arg_num(&args, 1, 0.0)? {
2957                    w if w > 0.0 => Some(w as f32),
2958                    _ => None,
2959                };
2960                // Try the path as given, then relative to the script's directory.
2961                let mut loaded = ling_graphics::VectorFont::from_path_weight(&path, weight);
2962                if loaded.is_err() {
2963                    if let Some(dir) = &self.source_dir {
2964                        let joined = dir.join(&path);
2965                        loaded = ling_graphics::VectorFont::from_path_weight(&joined.to_string_lossy(), weight);
2966                    }
2967                }
2968                match loaded {
2969                    Ok(f) => {
2970                        let id = self.fonts.len();
2971                        self.fonts.push(f);
2972                        return Ok(Value::Number(id as f64));
2973                    }
2974                    Err(e) => {
2975                        eprintln!("font_load failed ({path}): {e}");
2976                        return Ok(Value::Number(-1.0));
2977                    }
2978                }
2979            }
2980            // font_text(handle, x, y, px, "string") — anti-aliased *stroked* vector outline
2981            // in the current set_color / set_blend. (x,y) is the text box top-left.
2982            #[cfg(not(target_arch = "wasm32"))]
2983            "font_text" | "ข้อความฟอนต์" | "字体文本" | "フォント文字" | "글꼴텍스트" => {
2984                let id = self.arg_num(&args, 0, 0.0)? as i64;
2985                let x  = self.arg_num(&args, 1, 0.0)? as f32;
2986                let y  = self.arg_num(&args, 2, 0.0)? as f32;
2987                let px = self.arg_num(&args, 3, 16.0)? as f32;
2988                let s  = self.arg_str(&args, 4, "");
2989                if id >= 0 && (id as usize) < self.fonts.len() && px > 0.0 {
2990                    let strokes = self.font_layout_2d(id as usize, x, y, px, &s);
2991                    let mut gfx = self.gfx.borrow_mut();
2992                    let (w, h, color, add) = (gfx.width, gfx.height, gfx.color, gfx.blend == 1);
2993                    for pl in &strokes {
2994                        for seg in pl.windows(2) {
2995                            crate::gfx::raster::draw_line_aa(&mut gfx.buffer, w, h, color, add,
2996                                seg[0][0], seg[0][1], seg[1][0], seg[1][1]);
2997                        }
2998                    }
2999                }
3000                return Ok(Value::Unit);
3001            }
3002            // font_text_fill(handle, x, y, px, "string") — anti-aliased *filled* vector glyphs.
3003            #[cfg(not(target_arch = "wasm32"))]
3004            "font_text_fill" | "เติมฟอนต์" | "填充字体" | "フォント塗り" | "글꼴채움" => {
3005                let id = self.arg_num(&args, 0, 0.0)? as i64;
3006                let x  = self.arg_num(&args, 1, 0.0)? as f32;
3007                let y  = self.arg_num(&args, 2, 0.0)? as f32;
3008                let px = self.arg_num(&args, 3, 16.0)? as f32;
3009                let s  = self.arg_str(&args, 4, "");
3010                if id >= 0 && (id as usize) < self.fonts.len() && px > 0.0 {
3011                    // fill each glyph independently so interior holes (winding) stay correct
3012                    let glyphs = self.font_layout_2d_glyphs(id as usize, x, y, px, &s);
3013                    let mut gfx = self.gfx.borrow_mut();
3014                    let (w, h, color, add) = (gfx.width, gfx.height, gfx.color, gfx.blend == 1);
3015                    for contours in &glyphs {
3016                        crate::gfx::raster::fill_contours_aa(&mut gfx.buffer, w, h, color, add, contours);
3017                    }
3018                }
3019                return Ok(Value::Unit);
3020            }
3021            // font_text_3d(handle, cx,cy,cz, ux,uy,uz, vx,vy,vz, size, "string")
3022            // — stroked vector text on a 3D plane: u = advance dir, v = up dir, size = world/em.
3023            //   Flows through the depth-sorted line pipeline, so it rotates with the camera (and 4D).
3024            #[cfg(not(target_arch = "wasm32"))]
3025            "font_text_3d" | "ข้อความฟอนต์3มิติ" | "字体3D" | "フォント3D" | "글꼴3D" => {
3026                let id = self.arg_num(&args, 0, 0.0)? as i64;
3027                let cx=self.arg_num(&args,1,0.0)? as f32; let cy=self.arg_num(&args,2,0.0)? as f32; let cz=self.arg_num(&args,3,0.0)? as f32;
3028                let ux=self.arg_num(&args,4,1.0)? as f32; let uy=self.arg_num(&args,5,0.0)? as f32; let uz=self.arg_num(&args,6,0.0)? as f32;
3029                let vx=self.arg_num(&args,7,0.0)? as f32; let vy=self.arg_num(&args,8,1.0)? as f32; let vz=self.arg_num(&args,9,0.0)? as f32;
3030                let size=self.arg_num(&args,10,1.0)? as f32;
3031                let s = self.arg_str(&args,11,"");
3032                if id >= 0 && (id as usize) < self.fonts.len() && size > 0.0 {
3033                    // Build world-space polylines: world = C + (pen+ex)*size*U + ey*size*V
3034                    let font = &mut self.fonts[id as usize];
3035                    let asc = font.ascent();
3036                    let mut pen = 0.0f32;
3037                    let mut lines: Vec<[f32; 6]> = Vec::new();
3038                    for ch in s.chars() {
3039                        let go = font.glyph_outline(ch, 0.01);
3040                        for pl in &go.polylines {
3041                            for seg in pl.windows(2) {
3042                                let map = |p: [f32; 2]| {
3043                                    let a = pen + p[0];
3044                                    let b = p[1] - asc; // shift so the top of the cap sits near C
3045                                    [cx + a*size*ux + b*size*vx,
3046                                     cy + a*size*uy + b*size*vy,
3047                                     cz + a*size*uz + b*size*vz]
3048                                };
3049                                let p0 = map(seg[0]); let p1 = map(seg[1]);
3050                                lines.push([p0[0],p0[1],p0[2], p1[0],p1[1],p1[2]]);
3051                            }
3052                        }
3053                        pen += go.advance;
3054                    }
3055                    let mut gfx = self.gfx.borrow_mut();
3056                    let color = gfx.color;
3057                    let near = -gfx.camera.zdist + 0.05;
3058                    for l in &lines {
3059                        let (mut ax, mut ay, mut az) = (l[0], l[1], l[2]);
3060                        let (mut bx, mut by, mut bz) = (l[3], l[4], l[5]);
3061                        let da = gfx.camera.depth(ax, ay, az);
3062                        let db = gfx.camera.depth(bx, by, bz);
3063                        if da <= near && db <= near { continue; }
3064                        if da <= near {
3065                            let t = (near - da) / (db - da);
3066                            ax += t*(bx-ax); ay += t*(by-ay); az += t*(bz-az);
3067                        } else if db <= near {
3068                            let t = (near - da) / (db - da);
3069                            bx = ax + t*(bx-ax); by = ay + t*(by-ay); bz = az + t*(bz-az);
3070                        }
3071                        let (sax, say, da2) = gfx.camera.project(ax, ay, az);
3072                        let (sbx, sby, db2) = gfx.camera.project(bx, by, bz);
3073                        let depth = (da2 + db2) / 2.0;
3074                        gfx.depth_queue.push_line(depth, color, sax, say, sbx, sby);
3075                    }
3076                }
3077                return Ok(Value::Unit);
3078            }
3079            // font_width(handle, px, "string") — pixel width of a string in a loaded font.
3080            #[cfg(not(target_arch = "wasm32"))]
3081            "font_width" | "ความกว้างฟอนต์" | "字体宽度" | "フォント幅" | "글꼴너비" => {
3082                let id = self.arg_num(&args, 0, 0.0)? as i64;
3083                let px = self.arg_num(&args, 1, 16.0)? as f32;
3084                let s  = self.arg_str(&args, 2, "");
3085                if id >= 0 && (id as usize) < self.fonts.len() {
3086                    return Ok(Value::Number(self.fonts[id as usize].measure(&s, px) as f64));
3087                }
3088                return Ok(Value::Number(0.0));
3089            }
3090            // ui_frame(x,y,w,h, bracketLen) — sci-fi corner brackets
3091            "ui_frame" | "边框" | "フレーム枠" | "프레임틀" | "กรอบ" => {
3092                let x=self.arg_num(&args,0,0.0)? as f32; let y=self.arg_num(&args,1,0.0)? as f32;
3093                let w0=self.arg_num(&args,2,0.0)? as f32; let h0=self.arg_num(&args,3,0.0)? as f32;
3094                let l=self.arg_num(&args,4,14.0)? as f32;
3095                let segs = ling_ui::holo::corner_brackets(x,y,w0,h0,l);
3096                let mut gfx = self.gfx.borrow_mut();
3097                let (w,h,color)=(gfx.width,gfx.height,gfx.color);
3098                for sg in segs { draw_line(&mut gfx.buffer, w,h,color, sg[0],sg[1],sg[2],sg[3]); }
3099                return Ok(Value::Unit);
3100            }
3101            // ui_bevel(x,y,w,h, bevel) — beveled holographic panel outline
3102            "ui_bevel" | "斜角框" | "ベベル枠" | "베벨틀" | "กรอบเฉียง" => {
3103                let x=self.arg_num(&args,0,0.0)? as f32; let y=self.arg_num(&args,1,0.0)? as f32;
3104                let w0=self.arg_num(&args,2,0.0)? as f32; let h0=self.arg_num(&args,3,0.0)? as f32;
3105                let bv=self.arg_num(&args,4,10.0)? as f32;
3106                let segs = ling_ui::holo::beveled_rect(x,y,w0,h0,bv);
3107                let mut gfx = self.gfx.borrow_mut();
3108                let (w,h,color)=(gfx.width,gfx.height,gfx.color);
3109                for sg in segs { draw_line(&mut gfx.buffer, w,h,color, sg[0],sg[1],sg[2],sg[3]); }
3110                return Ok(Value::Unit);
3111            }
3112
3113            // ══════════════════════════════════════════════════════════════════
3114            // VECTOR UI TOOLKIT  (crates/ling-ui/src/widgets.rs)
3115            // All widgets are vector + theme-coloured with an optional trailing
3116            // r,g,b override; interactive ones read the mouse and return state.
3117            // ══════════════════════════════════════════════════════════════════
3118            #[cfg(not(target_arch = "wasm32"))]
3119            "ui_theme" | "界面主题" | "UIテーマ" | "인터페이스테마" | "ธีมส่วนติดต่อ" => {
3120                let cur = self.ui_theme;
3121                let primary = self.color_at(&args, 0,  cur.primary);
3122                let accent  = self.color_at(&args, 3,  cur.accent);
3123                let track   = self.color_at(&args, 6,  cur.track);
3124                let warn    = self.color_at(&args, 9,  cur.warn);
3125                let text    = self.color_at(&args, 12, cur.text);
3126                let bg      = self.color_at(&args, 15, cur.bg);
3127                self.ui_theme = UiTheme { primary, accent, track, warn, text, bg };
3128                return Ok(Value::Unit);
3129            }
3130
3131            // ── HUD ──────────────────────────────────────────────────────────
3132            #[cfg(not(target_arch = "wasm32"))]
3133            "ui_radar" | "雷达" | "レーダー" | "레이더" | "เรดาร์" => {
3134                let cx=self.arg_num(&args,0,0.)? as f32; let cy=self.arg_num(&args,1,0.)? as f32;
3135                let r=self.arg_num(&args,2,60.)? as f32; let sweep=self.arg_num(&args,3,0.)? as f32;
3136                let th=self.ui_theme;
3137                let prim=self.color_at(&args,4,th.primary);
3138                self.draw_ui(&ling_ui::widgets::radar(cx,cy,r,sweep, prim, th.accent, th.track));
3139                return Ok(Value::Unit);
3140            }
3141            #[cfg(not(target_arch = "wasm32"))]
3142            "ui_compass" | "罗盘" | "コンパス" | "나침반" | "เข็มทิศ" => {
3143                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3144                let w0=self.arg_num(&args,2,300.)? as f32; let h0=self.arg_num(&args,3,24.)? as f32;
3145                let head=self.arg_num(&args,4,0.)? as f32;
3146                let th=self.ui_theme; let prim=self.color_at(&args,5,th.primary);
3147                self.draw_ui(&ling_ui::widgets::compass(x,y,w0,h0,head, prim, th.track));
3148                return Ok(Value::Unit);
3149            }
3150            #[cfg(not(target_arch = "wasm32"))]
3151            "ui_reticle" | "准星" | "照準" | "조준선" | "เป้าเล็ง" => {
3152                let cx=self.arg_num(&args,0,0.)? as f32; let cy=self.arg_num(&args,1,0.)? as f32;
3153                let r=self.arg_num(&args,2,30.)? as f32; let spread=self.arg_num(&args,3,0.)? as f32;
3154                let th=self.ui_theme; let prim=self.color_at(&args,4,th.primary);
3155                self.draw_ui(&ling_ui::widgets::reticle(cx,cy,r,spread, prim));
3156                return Ok(Value::Unit);
3157            }
3158            #[cfg(not(target_arch = "wasm32"))]
3159            "ui_target" | "锁定框" | "ターゲット" | "표적" | "กรอบเป้า" => {
3160                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3161                let w0=self.arg_num(&args,2,80.)? as f32; let h0=self.arg_num(&args,3,80.)? as f32;
3162                let lock=self.arg_num(&args,4,0.)? as f32;
3163                let th=self.ui_theme; let prim=self.color_at(&args,5,th.primary);
3164                self.draw_ui(&ling_ui::widgets::target(x,y,w0,h0,lock, prim, th.accent));
3165                return Ok(Value::Unit);
3166            }
3167            #[cfg(not(target_arch = "wasm32"))]
3168            "ui_panel" | "面板" | "パネル" | "패널" | "แผง" => {
3169                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3170                let w0=self.arg_num(&args,2,200.)? as f32; let h0=self.arg_num(&args,3,120.)? as f32;
3171                let bv=self.arg_num(&args,4,12.)? as f32;
3172                let th=self.ui_theme; let prim=self.color_at(&args,5,th.primary);
3173                self.draw_ui(&ling_ui::widgets::panel(x,y,w0,h0,bv, prim, th.bg));
3174                return Ok(Value::Unit);
3175            }
3176            #[cfg(not(target_arch = "wasm32"))]
3177            "ui_scanlines" | "扫描线" | "走査線" | "스캔라인" | "เส้นสแกน" => {
3178                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3179                let w0=self.arg_num(&args,2,200.)? as f32; let h0=self.arg_num(&args,3,120.)? as f32;
3180                let dens=self.arg_num(&args,4,24.)? as usize;
3181                let th=self.ui_theme; let line=self.color_at(&args,5,th.track);
3182                self.draw_ui(&ling_ui::widgets::scanlines(x,y,w0,h0,dens, line));
3183                return Ok(Value::Unit);
3184            }
3185
3186            // ── Meters ───────────────────────────────────────────────────────
3187            #[cfg(not(target_arch = "wasm32"))]
3188            "ui_bar" | "进度条" | "バー" | "막대" | "แถบ" => {
3189                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3190                let w0=self.arg_num(&args,2,160.)? as f32; let h0=self.arg_num(&args,3,16.)? as f32;
3191                let val=self.arg_num(&args,4,0.)? as f32; let max=self.arg_num(&args,5,1.)? as f32;
3192                let th=self.ui_theme; let fill=self.color_at(&args,6,th.primary);
3193                self.draw_ui(&ling_ui::widgets::bar(x,y,w0,h0, val/max.max(1e-6), fill, th.track));
3194                return Ok(Value::Unit);
3195            }
3196            #[cfg(not(target_arch = "wasm32"))]
3197            "ui_segbar" | "分段条" | "分割バー" | "분할막대" | "แถบแบ่ง" => {
3198                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3199                let w0=self.arg_num(&args,2,160.)? as f32; let h0=self.arg_num(&args,3,16.)? as f32;
3200                let val=self.arg_num(&args,4,0.)? as f32; let max=self.arg_num(&args,5,1.)? as f32;
3201                let segs=self.arg_num(&args,6,10.)? as usize;
3202                let th=self.ui_theme; let fill=self.color_at(&args,7,th.primary);
3203                self.draw_ui(&ling_ui::widgets::segbar(x,y,w0,h0, val/max.max(1e-6), segs, fill, th.track));
3204                return Ok(Value::Unit);
3205            }
3206            #[cfg(not(target_arch = "wasm32"))]
3207            "ui_gauge" | "仪表" | "ゲージ" | "게이지" | "มาตรวัด" => {
3208                let cx=self.arg_num(&args,0,0.)? as f32; let cy=self.arg_num(&args,1,0.)? as f32;
3209                let r=self.arg_num(&args,2,50.)? as f32;
3210                let val=self.arg_num(&args,3,0.)? as f32; let max=self.arg_num(&args,4,1.)? as f32;
3211                let th=self.ui_theme; let needle=self.color_at(&args,5,th.warn);
3212                self.draw_ui(&ling_ui::widgets::gauge(cx,cy,r, val/max.max(1e-6), needle, th.accent, th.track));
3213                return Ok(Value::Unit);
3214            }
3215            #[cfg(not(target_arch = "wasm32"))]
3216            "ui_ring" | "环表" | "リングメーター" | "링미터" | "วงแหวนวัด" => {
3217                let cx=self.arg_num(&args,0,0.)? as f32; let cy=self.arg_num(&args,1,0.)? as f32;
3218                let r=self.arg_num(&args,2,40.)? as f32;
3219                let val=self.arg_num(&args,3,0.)? as f32; let max=self.arg_num(&args,4,1.)? as f32;
3220                let th=self.ui_theme; let fill=self.color_at(&args,5,th.primary);
3221                self.draw_ui(&ling_ui::widgets::ring(cx,cy,r, val/max.max(1e-6), fill, th.track));
3222                return Ok(Value::Unit);
3223            }
3224            #[cfg(not(target_arch = "wasm32"))]
3225            "ui_vu" | "音量条" | "VUメーター" | "음량막대" | "มาตรเสียง" => {
3226                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3227                let w0=self.arg_num(&args,2,160.)? as f32; let h0=self.arg_num(&args,3,60.)? as f32;
3228                let levels=self.arg_list_f32(&args,4);
3229                let th=self.ui_theme; let fill=self.color_at(&args,5,th.primary);
3230                self.draw_ui(&ling_ui::widgets::vu(x,y,w0,h0, &levels, fill, th.warn));
3231                return Ok(Value::Unit);
3232            }
3233            #[cfg(not(target_arch = "wasm32"))]
3234            "ui_spark" | "迷你图" | "スパークライン" | "스파크라인" | "กราฟจิ๋ว" => {
3235                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3236                let w0=self.arg_num(&args,2,160.)? as f32; let h0=self.arg_num(&args,3,40.)? as f32;
3237                let vals=self.arg_list_f32(&args,4);
3238                let th=self.ui_theme; let line=self.color_at(&args,5,th.accent);
3239                self.draw_ui(&ling_ui::widgets::spark(x,y,w0,h0, &vals, line));
3240                return Ok(Value::Unit);
3241            }
3242            #[cfg(not(target_arch = "wasm32"))]
3243            "ui_battery" | "电池" | "バッテリー" | "배터리" | "แบตเตอรี่" => {
3244                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3245                let w0=self.arg_num(&args,2,50.)? as f32; let h0=self.arg_num(&args,3,22.)? as f32;
3246                let val=self.arg_num(&args,4,1.)? as f32; let max=self.arg_num(&args,5,1.)? as f32;
3247                let th=self.ui_theme; let fill=self.color_at(&args,6,th.accent);
3248                self.draw_ui(&ling_ui::widgets::battery(x,y,w0,h0, val/max.max(1e-6), fill, th.track, th.warn));
3249                return Ok(Value::Unit);
3250            }
3251
3252            // ── Interface controls (interactive → return state) ──────────────
3253            #[cfg(not(target_arch = "wasm32"))]
3254            "ui_button" | "按钮" | "ボタン" | "버튼" | "ปุ่ม" => {
3255                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3256                let w0=self.arg_num(&args,2,120.)? as f32; let h0=self.arg_num(&args,3,40.)? as f32;
3257                let (mx,my,down)=self.mouse_now();
3258                let hover=ling_ui::holo::hit_rect(mx,my,x,y,w0,h0);
3259                let clicked = hover && down && !self.mouse_was_down;
3260                let th=self.ui_theme; let prim=self.color_at(&args,4,th.primary);
3261                self.draw_ui(&ling_ui::widgets::button(x,y,w0,h0, hover, down&&hover, prim, th.bg));
3262                return Ok(Value::Number(if clicked {1.0} else {0.0}));
3263            }
3264            #[cfg(not(target_arch = "wasm32"))]
3265            "ui_toggle" | "开关" | "トグル" | "토글" | "สวิตช์" => {
3266                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3267                let w0=self.arg_num(&args,2,52.)? as f32; let h0=self.arg_num(&args,3,24.)? as f32;
3268                let mut state=self.arg_num(&args,4,0.)? > 0.5;
3269                let (mx,my,down)=self.mouse_now();
3270                let hover=ling_ui::holo::hit_rect(mx,my,x,y,w0,h0);
3271                if hover && down && !self.mouse_was_down { state = !state; }
3272                let th=self.ui_theme; let on=self.color_at(&args,5,th.accent);
3273                self.draw_ui(&ling_ui::widgets::toggle(x,y,w0,h0, state, on, th.track));
3274                return Ok(Value::Number(if state {1.0} else {0.0}));
3275            }
3276            #[cfg(not(target_arch = "wasm32"))]
3277            "ui_slider" | "滑块" | "スライダー" | "슬라이더" | "แถบเลื่อน" => {
3278                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3279                let w0=self.arg_num(&args,2,160.)? as f32;
3280                let mut val=self.arg_num(&args,3,0.)? as f32;
3281                let mn=self.arg_num(&args,4,0.)? as f32; let mx_=self.arg_num(&args,5,1.)? as f32;
3282                let (mx,my,down)=self.mouse_now();
3283                let hover=ling_ui::holo::hit_rect(mx,my,x-8.0,y-10.0,w0+16.0,20.0);
3284                if hover && down {
3285                    let frac=((mx-x)/w0).max(0.0).min(1.0);
3286                    val = mn + (mx_-mn)*frac;
3287                }
3288                let frac=((val-mn)/(mx_-mn).abs().max(1e-6)).max(0.0).min(1.0);
3289                let th=self.ui_theme; let fill=self.color_at(&args,6,th.primary);
3290                self.draw_ui(&ling_ui::widgets::slider(x,y,w0, frac, hover, fill, th.track));
3291                return Ok(Value::Number(val as f64));
3292            }
3293            #[cfg(not(target_arch = "wasm32"))]
3294            "ui_checkbox" | "复选框" | "チェックボックス" | "체크박스" | "ช่องเลือก" => {
3295                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3296                let s=self.arg_num(&args,2,20.)? as f32;
3297                let mut checked=self.arg_num(&args,3,0.)? > 0.5;
3298                let (mx,my,down)=self.mouse_now();
3299                let hover=ling_ui::holo::hit_rect(mx,my,x,y,s,s);
3300                if hover && down && !self.mouse_was_down { checked = !checked; }
3301                let th=self.ui_theme; let prim=self.color_at(&args,4,th.primary);
3302                self.draw_ui(&ling_ui::widgets::checkbox(x,y,s, checked, hover, prim, th.track));
3303                return Ok(Value::Number(if checked {1.0} else {0.0}));
3304            }
3305            #[cfg(not(target_arch = "wasm32"))]
3306            "ui_tabs" | "标签页" | "タブ" | "탭" | "แท็บ" => {
3307                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3308                let w0=self.arg_num(&args,2,240.)? as f32; let h0=self.arg_num(&args,3,28.)? as f32;
3309                let count=self.arg_num(&args,4,3.)? as usize;
3310                let mut active=self.arg_num(&args,5,0.)? as i32;
3311                let (mx,my,down)=self.mouse_now();
3312                let mut hover=-1;
3313                if my>=y && my<=y+h0 && mx>=x && mx<=x+w0 && count>0 {
3314                    hover = (((mx-x)/(w0/count as f32)) as i32).max(0).min(count as i32-1);
3315                    if down && !self.mouse_was_down { active = hover; }
3316                }
3317                let th=self.ui_theme; let prim=self.color_at(&args,6,th.primary);
3318                self.draw_ui(&ling_ui::widgets::tabs(x,y,w0,h0, count, active as usize, hover, prim, th.track));
3319                return Ok(Value::Number(active as f64));
3320            }
3321            #[cfg(not(target_arch = "wasm32"))]
3322            "ui_progress" | "进度" | "プログレス" | "진행바" | "ความคืบหน้า" => {
3323                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3324                let w0=self.arg_num(&args,2,200.)? as f32; let h0=self.arg_num(&args,3,12.)? as f32;
3325                let frac=self.arg_num(&args,4,0.)? as f32;
3326                let th=self.ui_theme; let fill=self.color_at(&args,5,th.accent);
3327                self.draw_ui(&ling_ui::widgets::progress(x,y,w0,h0, frac, fill, th.track));
3328                return Ok(Value::Unit);
3329            }
3330            #[cfg(not(target_arch = "wasm32"))]
3331            "ui_tooltip" | "提示框" | "ツールチップ" | "툴팁" | "คำแนะนำ" => {
3332                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3333                let w0=self.arg_num(&args,2,120.)? as f32; let h0=self.arg_num(&args,3,28.)? as f32;
3334                let th=self.ui_theme; let prim=self.color_at(&args,4,th.primary);
3335                self.draw_ui(&ling_ui::widgets::tooltip(x,y,w0,h0, prim, th.bg));
3336                return Ok(Value::Unit);
3337            }
3338            #[cfg(not(target_arch = "wasm32"))]
3339            "ui_stepper" | "步进器" | "ステッパー" | "스테퍼" | "ตัวปรับค่า" => {
3340                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3341                let w0=self.arg_num(&args,2,120.)? as f32; let h0=self.arg_num(&args,3,28.)? as f32;
3342                let mut val=self.arg_num(&args,4,0.)? as f32; let step=self.arg_num(&args,5,1.)? as f32;
3343                let (mx,my,down)=self.mouse_now();
3344                let hm=ling_ui::holo::hit_rect(mx,my,x,y,h0,h0);
3345                let hp=ling_ui::holo::hit_rect(mx,my,x+w0-h0,y,h0,h0);
3346                if down && !self.mouse_was_down { if hm { val -= step; } if hp { val += step; } }
3347                let th=self.ui_theme; let prim=self.color_at(&args,6,th.primary);
3348                self.draw_ui(&ling_ui::widgets::stepper(x,y,w0,h0, hm, hp, prim, th.track));
3349                return Ok(Value::Number(val as f64));
3350            }
3351
3352            // ── Game UI ──────────────────────────────────────────────────────
3353            #[cfg(not(target_arch = "wasm32"))]
3354            "ui_healthbar" | "血条" | "体力バー" | "체력바" | "แถบพลังชีวิต" => {
3355                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3356                let w0=self.arg_num(&args,2,180.)? as f32; let h0=self.arg_num(&args,3,16.)? as f32;
3357                let val=self.arg_num(&args,4,1.)? as f32; let max=self.arg_num(&args,5,1.)? as f32;
3358                let pulse=self.arg_num(&args,6,0.)? as f32;
3359                let th=self.ui_theme; let full=self.color_at(&args,7,th.accent);
3360                self.draw_ui(&ling_ui::widgets::healthbar(x,y,w0,h0, val/max.max(1e-6), pulse, full, th.warn, th.track));
3361                return Ok(Value::Unit);
3362            }
3363            #[cfg(not(target_arch = "wasm32"))]
3364            "ui_cooldown" | "冷却" | "クールダウン" | "쿨다운" | "คูลดาวน์" => {
3365                let cx=self.arg_num(&args,0,0.)? as f32; let cy=self.arg_num(&args,1,0.)? as f32;
3366                let r=self.arg_num(&args,2,28.)? as f32; let frac=self.arg_num(&args,3,0.)? as f32;
3367                let th=self.ui_theme; let fill=self.color_at(&args,4,th.primary);
3368                self.draw_ui(&ling_ui::widgets::cooldown(cx,cy,r, frac, fill, th.track));
3369                return Ok(Value::Unit);
3370            }
3371            #[cfg(not(target_arch = "wasm32"))]
3372            "ui_counter" | "计数器" | "カウンター" | "카운터" | "ตัวนับ" => {
3373                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3374                let dw=self.arg_num(&args,2,14.)? as f32; let dh=self.arg_num(&args,3,24.)? as f32;
3375                let val=self.arg_num(&args,4,0.)? as i64; let digits=self.arg_num(&args,5,4.)? as usize;
3376                let th=self.ui_theme; let on=self.color_at(&args,6,th.primary);
3377                let off=ling_ui::widgets::shade(th.track,0.5);
3378                self.draw_ui(&ling_ui::widgets::counter(x,y,dw,dh, val, digits, on, off));
3379                return Ok(Value::Unit);
3380            }
3381            #[cfg(not(target_arch = "wasm32"))]
3382            "ui_minimap" | "小地图" | "ミニマップ" | "미니맵" | "แผนที่ย่อ" => {
3383                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3384                let w0=self.arg_num(&args,2,140.)? as f32; let h0=self.arg_num(&args,3,140.)? as f32;
3385                let th=self.ui_theme; let prim=self.color_at(&args,4,th.primary);
3386                self.draw_ui(&ling_ui::widgets::minimap(x,y,w0,h0, prim, th.bg));
3387                return Ok(Value::Unit);
3388            }
3389            #[cfg(not(target_arch = "wasm32"))]
3390            "ui_dpad" | "方向键" | "方向パッド" | "방향패드" | "ปุ่มทิศทาง" => {
3391                let cx=self.arg_num(&args,0,0.)? as f32; let cy=self.arg_num(&args,1,0.)? as f32;
3392                let r=self.arg_num(&args,2,50.)? as f32;
3393                let (mx,my,down)=self.mouse_now();
3394                let mut dir=0;
3395                if down {
3396                    let (dx,dy)=(mx-cx, my-cy);
3397                    if dx*dx+dy*dy <= r*r {
3398                        if dx.abs() > dy.abs() { dir = if dx>0.0 {2} else {4}; }
3399                        else { dir = if dy>0.0 {3} else {1}; }
3400                    }
3401                }
3402                let th=self.ui_theme; let prim=self.color_at(&args,3,th.primary);
3403                self.draw_ui(&ling_ui::widgets::dpad(cx,cy,r, dir, prim, th.track));
3404                return Ok(Value::Number(dir as f64));
3405            }
3406            #[cfg(not(target_arch = "wasm32"))]
3407            "ui_slotgrid" | "物品格" | "スロットグリッド" | "슬롯격자" | "ช่องไอเทม" => {
3408                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3409                let cols=self.arg_num(&args,2,4.)? as usize; let rows=self.arg_num(&args,3,1.)? as usize;
3410                let cell=self.arg_num(&args,4,36.)? as f32; let sel=self.arg_num(&args,5,-1.)? as i32;
3411                let th=self.ui_theme; let prim=self.color_at(&args,6,th.primary);
3412                self.draw_ui(&ling_ui::widgets::slotgrid(x,y,cols,rows,cell, sel, prim, th.track));
3413                return Ok(Value::Unit);
3414            }
3415            #[cfg(not(target_arch = "wasm32"))]
3416            "ui_vignette" | "暗角" | "ビネット" | "비네트" | "ขอบมืด" => {
3417                let intensity=self.arg_num(&args,0,0.5)? as f32;
3418                let (w,h)={ let g=self.gfx.borrow(); (g.width as f32, g.height as f32) };
3419                let th=self.ui_theme; let col=self.color_at(&args,1,th.warn);
3420                self.draw_ui(&ling_ui::widgets::vignette(w,h, intensity, col));
3421                return Ok(Value::Unit);
3422            }
3423
3424            // ── Faux-3D in 2D space ──────────────────────────────────────────
3425            #[cfg(not(target_arch = "wasm32"))]
3426            "ui_gauge3d" | "立体仪表" | "立体ゲージ" | "입체게이지" | "มาตรวัด3มิติ" => {
3427                let cx=self.arg_num(&args,0,0.)? as f32; let cy=self.arg_num(&args,1,0.)? as f32;
3428                let r=self.arg_num(&args,2,50.)? as f32;
3429                let val=self.arg_num(&args,3,0.)? as f32; let max=self.arg_num(&args,4,1.)? as f32;
3430                let spin=self.arg_num(&args,5,0.)? as f32;
3431                let th=self.ui_theme; let fill=self.color_at(&args,6,th.primary);
3432                self.draw_ui(&ling_ui::widgets::gauge3d(cx,cy,r, val/max.max(1e-6), spin, fill, th.track));
3433                return Ok(Value::Unit);
3434            }
3435            #[cfg(not(target_arch = "wasm32"))]
3436            "ui_panel3d" | "立体面板" | "立体パネル" | "입체패널" | "แผง3มิติ" => {
3437                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32;
3438                let w0=self.arg_num(&args,2,200.)? as f32; let h0=self.arg_num(&args,3,120.)? as f32;
3439                let depth=self.arg_num(&args,4,14.)? as f32;
3440                let th=self.ui_theme; let prim=self.color_at(&args,5,th.primary);
3441                self.draw_ui(&ling_ui::widgets::panel3d(x,y,w0,h0,depth, prim, th.bg));
3442                return Ok(Value::Unit);
3443            }
3444            #[cfg(not(target_arch = "wasm32"))]
3445            "ui_radar3d" | "立体雷达" | "立体レーダー" | "입체레이더" | "เรดาร์3มิติ" => {
3446                let cx=self.arg_num(&args,0,0.)? as f32; let cy=self.arg_num(&args,1,0.)? as f32;
3447                let r=self.arg_num(&args,2,60.)? as f32; let tilt=self.arg_num(&args,3,0.9)? as f32;
3448                let sweep=self.arg_num(&args,4,0.)? as f32;
3449                let th=self.ui_theme; let prim=self.color_at(&args,5,th.primary);
3450                self.draw_ui(&ling_ui::widgets::radar3d(cx,cy,r,tilt,sweep, prim, th.track));
3451                return Ok(Value::Unit);
3452            }
3453
3454            // ── Interface sounds ─────────────────────────────────────────────
3455            #[cfg(not(target_arch = "wasm32"))]
3456            "audio_blip" | "提示音" | "ビープ音" | "효과음" | "เสียงบี๊บ" => {
3457                let freq=self.arg_num(&args,0,660.)? as f32;
3458                let dur=self.arg_num(&args,1,0.08)? as f32;
3459                let wave=Wave::from_name(&self.arg_str(&args,2,"sine"));
3460                let amp=self.arg_num(&args,3,0.25)? as f32;
3461                if let Some(audio)=&self.audio { audio.blip(freq, amp, dur, wave); }
3462                return Ok(Value::Unit);
3463            }
3464            #[cfg(not(target_arch = "wasm32"))]
3465            "ui_sound" | "界面音" | "UI音" | "인터페이스음" | "เสียงปุ่ม" => {
3466                let name=self.arg_str(&args,0,"click");
3467                if let Some(audio)=&self.audio {
3468                    match name.as_str() {
3469                        "hover"   => audio.blip(880.0, 0.10, 0.04, Wave::Sine),
3470                        "confirm" => { audio.blip(660.0, 0.22, 0.07, Wave::Square); audio.blip(990.0, 0.18, 0.10, Wave::Square); }
3471                        "error"   => { audio.blip(180.0, 0.30, 0.16, Wave::Saw); audio.blip(140.0, 0.30, 0.18, Wave::Saw); }
3472                        "toggle"  => audio.blip(520.0, 0.22, 0.05, Wave::Triangle),
3473                        "tick"    => audio.blip(1500.0, 0.12, 0.02, Wave::Square),
3474                        _         => audio.blip(720.0, 0.26, 0.05, Wave::Square), // "click"
3475                    }
3476                }
3477                return Ok(Value::Unit);
3478            }
3479
3480            // ══════════════════════════════════════════════════════════════════
3481            // MUSIC TOOLKIT  (crates/ling-music) — decode · analysis · GM synth ·
3482            // rhythm · karaoke. Analysis/decoding need no audio device; playback
3483            // and synthesis lazily start a dedicated music engine.
3484            // ══════════════════════════════════════════════════════════════════
3485
3486            // music_load(path) -> track handle (decodes WAV/FLAC/OGG/MP3/AAC)
3487            #[cfg(not(target_arch = "wasm32"))]
3488            "music_load" | "载入音乐" | "音楽読込" | "음악로드" | "โหลดเพลง" => {
3489                let path = self.arg_str(&args, 0, "");
3490                let resolved = if std::path::Path::new(&path).exists() { path.clone() }
3491                    else if let Some(d) = &self.source_dir { d.join(&path).to_string_lossy().into_owned() }
3492                    else { path.clone() };
3493                match ling_music::load(&resolved) {
3494                    Ok(t) => { let id = self.tracks.len(); self.tracks.push(t); return Ok(Value::Number(id as f64)); }
3495                    Err(e) => { eprintln!("music_load failed ({path}): {e}"); return Ok(Value::Number(-1.0)); }
3496                }
3497            }
3498            #[cfg(not(target_arch = "wasm32"))]
3499            "music_duration" | "音乐时长" | "音楽長さ" | "음악길이" | "ความยาวเพลง" => {
3500                let id = self.arg_num(&args,0,0.0)? as i64;
3501                let d = self.tracks.get(id as usize).map(|t| t.duration).unwrap_or(0.0);
3502                return Ok(Value::Number(d as f64));
3503            }
3504            #[cfg(not(target_arch = "wasm32"))]
3505            "music_bpm" | "节拍速度" | "テンポ" | "템포" | "จังหวะต่อนาที" => {
3506                let id = self.arg_num(&args,0,0.0)? as i64;
3507                let b = self.tracks.get(id as usize).map(|t| ling_music::analysis::bpm(&t.mono, t.rate)).unwrap_or(0.0);
3508                return Ok(Value::Number(b as f64));
3509            }
3510            #[cfg(not(target_arch = "wasm32"))]
3511            "music_key" | "调性" | "調性" | "조성" | "คีย์เพลง" => {
3512                let id = self.arg_num(&args,0,0.0)? as i64;
3513                let k = self.tracks.get(id as usize).map(|t| ling_music::analysis::key_name(&t.mono, t.rate)).unwrap_or_default();
3514                return Ok(Value::Str(k));
3515            }
3516            #[cfg(not(target_arch = "wasm32"))]
3517            "music_onsets" | "音符起点" | "オンセット" | "온셋" | "จุดเริ่มเสียง" => {
3518                let id = self.arg_num(&args,0,0.0)? as i64;
3519                let v = self.tracks.get(id as usize).map(|t| ling_music::analysis::onsets(&t.mono, t.rate)).unwrap_or_default();
3520                return Ok(Value::List(v.into_iter().map(|x| Value::Number(x as f64)).collect()));
3521            }
3522            #[cfg(not(target_arch = "wasm32"))]
3523            "music_beat_grid" | "节拍网格" | "ビートグリッド" | "비트그리드" | "กริดจังหวะ" => {
3524                let id = self.arg_num(&args,0,0.0)? as i64;
3525                let beats = self.tracks.get(id as usize).map(|t| {
3526                    let b = ling_music::analysis::bpm(&t.mono, t.rate);
3527                    ling_music::analysis::beat_grid(&t.mono, t.rate, b)
3528                }).unwrap_or_default();
3529                return Ok(Value::List(beats.into_iter().map(|x| Value::Number(x as f64)).collect()));
3530            }
3531
3532            // ── playback ──
3533            #[cfg(not(target_arch = "wasm32"))]
3534            "music_play" | "播放音乐" | "音楽再生" | "음악재생" | "เล่นเพลง" => {
3535                let id = self.arg_num(&args,0,0.0)? as i64;
3536                if self.ensure_music() {
3537                    let track = self.tracks.get(id as usize).map(|t| (t.stereo.clone(), t.rate));
3538                    if let (Some((st, rate)), Some(m)) = (track, &self.music) { m.set_track(st, rate); m.play(); }
3539                    else if let Some(m) = &self.music { m.play(); }
3540                }
3541                return Ok(Value::Unit);
3542            }
3543            #[cfg(not(target_arch = "wasm32"))]
3544            "music_pause" | "暂停音乐" | "音楽一時停止" | "음악일시정지" | "หยุดเพลงชั่วคราว" => {
3545                if let Some(m) = &self.music { m.pause(); } return Ok(Value::Unit);
3546            }
3547            #[cfg(not(target_arch = "wasm32"))]
3548            "music_stop" | "停止音乐" | "音楽停止" | "음악정지" | "หยุดเพลง" => {
3549                if let Some(m) = &self.music { m.stop(); } return Ok(Value::Unit);
3550            }
3551            #[cfg(not(target_arch = "wasm32"))]
3552            "music_seek" | "定位音乐" | "音楽シーク" | "음악탐색" | "ค้นหาเพลง" => {
3553                let sec = self.arg_num(&args,0,0.0)? as f32;
3554                if let Some(m) = &self.music { m.seek(sec); } return Ok(Value::Unit);
3555            }
3556            #[cfg(not(target_arch = "wasm32"))]
3557            "music_pos" | "音乐位置" | "音楽位置" | "음악위치" | "ตำแหน่งเพลง" => {
3558                let p = self.music.as_ref().map(|m| m.position()).unwrap_or(0.0);
3559                return Ok(Value::Number(p as f64));
3560            }
3561            #[cfg(not(target_arch = "wasm32"))]
3562            "music_volume" | "音乐音量" | "音楽音量" | "음악음량" | "ระดับเพลง" => {
3563                let v = self.arg_num(&args,0,0.8)? as f32;
3564                if self.ensure_music() { if let Some(m) = &self.music { m.set_volume(v); } }
3565                return Ok(Value::Unit);
3566            }
3567
3568            // ── synthesis (GM-capable, patches from .ling files) ──
3569            #[cfg(not(target_arch = "wasm32"))]
3570            "music_patch" | "乐器音色" | "音色読込" | "악기패치" | "แพตช์เครื่องดนตรี" => {
3571                let path = self.arg_str(&args, 0, "");
3572                let resolved = if std::path::Path::new(&path).exists() { path.clone() }
3573                    else if let Some(d) = &self.source_dir { d.join(&path).to_string_lossy().into_owned() }
3574                    else { path.clone() };
3575                if !self.ensure_music() { return Ok(Value::Number(-1.0)); }
3576                match ling_music::patch::from_path(&resolved) {
3577                    Ok(p) => { let id = self.music.as_ref().unwrap().add_patch(p); return Ok(Value::Number(id as f64)); }
3578                    Err(e) => { eprintln!("music_patch failed ({path}): {e}"); return Ok(Value::Number(-1.0)); }
3579                }
3580            }
3581            #[cfg(not(target_arch = "wasm32"))]
3582            "music_note" | "弹音符" | "音符演奏" | "음표연주" | "เล่นโน้ต" => {
3583                let inst = self.arg_num(&args,0,0.0)? as usize;
3584                let midi = self.pitch_arg(&args, 1, 60);
3585                let dur  = self.arg_num(&args,2,0.5)? as f32;
3586                let vel  = self.arg_num(&args,3,0.9)? as f32;
3587                if self.ensure_music() { if let Some(m) = &self.music { m.note(inst, midi, vel, dur); } }
3588                return Ok(Value::Unit);
3589            }
3590            #[cfg(not(target_arch = "wasm32"))]
3591            "music_note_on" | "音符开始" | "音符オン" | "음표켜기" | "โน้ตเริ่ม" => {
3592                let inst = self.arg_num(&args,0,0.0)? as usize;
3593                let midi = self.pitch_arg(&args, 1, 60);
3594                let vel  = self.arg_num(&args,2,0.9)? as f32;
3595                if self.ensure_music() { if let Some(m) = &self.music { m.note_on(inst, midi, vel); } }
3596                return Ok(Value::Unit);
3597            }
3598            #[cfg(not(target_arch = "wasm32"))]
3599            "music_note_off" | "音符结束" | "音符オフ" | "음표끄기" | "โน้ตจบ" => {
3600                let inst = self.arg_num(&args,0,0.0)? as usize;
3601                let midi = self.pitch_arg(&args, 1, 60);
3602                if let Some(m) = &self.music { m.note_off(inst, midi); }
3603                return Ok(Value::Unit);
3604            }
3605
3606            // ── rhythm-game judging ──
3607            #[cfg(not(target_arch = "wasm32"))]
3608            "music_judge" | "判定" | "判定する" | "판정" | "ตัดสินจังหวะ" => {
3609                let delta_ms = self.arg_num(&args,0,9999.0)? as f32;
3610                return Ok(Value::Number(ling_music::Grade::judge(delta_ms).index() as f64));
3611            }
3612            #[cfg(not(target_arch = "wasm32"))]
3613            "music_grade_name" | "判定名" | "判定名称" | "판정이름" | "ชื่อการตัดสิน" => {
3614                let idx = self.arg_num(&args,0,4.0)? as i32;
3615                return Ok(Value::Str(ling_music::Grade::from_index(idx).name().to_string()));
3616            }
3617
3618            // ── karaoke ──
3619            #[cfg(not(target_arch = "wasm32"))]
3620            "music_lrc" | "载入歌词" | "歌詞読込" | "가사로드" | "โหลดเนื้อเพลง" => {
3621                let path = self.arg_str(&args, 0, "");
3622                let resolved = if std::path::Path::new(&path).exists() { path.clone() }
3623                    else if let Some(d) = &self.source_dir { d.join(&path).to_string_lossy().into_owned() }
3624                    else { path.clone() };
3625                match std::fs::read_to_string(&resolved) {
3626                    Ok(text) => { let id = self.lyrics.len(); self.lyrics.push(ling_music::Lyrics::parse(&text)); return Ok(Value::Number(id as f64)); }
3627                    Err(e) => { eprintln!("music_lrc failed ({path}): {e}"); return Ok(Value::Number(-1.0)); }
3628                }
3629            }
3630            #[cfg(not(target_arch = "wasm32"))]
3631            "music_lyric" | "当前歌词" | "現在歌詞" | "현재가사" | "เนื้อเพลงปัจจุบัน" => {
3632                let id = self.arg_num(&args,0,0.0)? as i64;
3633                let t  = self.arg_num(&args,1,0.0)? as f32;
3634                let line = self.lyrics.get(id as usize).map(|l| l.line_at(t).to_string()).unwrap_or_default();
3635                return Ok(Value::Str(line));
3636            }
3637            #[cfg(not(target_arch = "wasm32"))]
3638            "music_mic_pitch" | "麦克风音高" | "マイク音程" | "마이크음정" | "ระดับเสียงไมค์" => {
3639                let hz = if let Some(mic) = self.mic.as_ref() {
3640                    let s = mic.latest_samples();
3641                    let rate = mic.sample_rate();
3642                    ling_music::pitch::detect(&s, rate).unwrap_or(0.0)
3643                } else { 0.0 };
3644                return Ok(Value::Number(hz as f64));
3645            }
3646            #[cfg(not(target_arch = "wasm32"))]
3647            "music_note_name" | "音名" | "音名称" | "음이름" | "ชื่อโน้ต" => {
3648                let hz = self.arg_num(&args,0,0.0)? as f32;
3649                return Ok(Value::Str(ling_music::note::hz_to_name(hz)));
3650            }
3651            #[cfg(not(target_arch = "wasm32"))]
3652            "music_hz" | "音符频率" | "音符周波数" | "음표주파수" | "ความถี่โน้ต" => {
3653                let midi = self.pitch_arg(&args, 0, 69);
3654                return Ok(Value::Number(ling_music::note::midi_to_hz(midi as f32) as f64));
3655            }
3656            #[cfg(not(target_arch = "wasm32"))]
3657            "music_pitch_score" | "音准评分" | "音程スコア" | "음정점수" | "คะแนนเสียง" => {
3658                let hz = self.arg_num(&args,0,0.0)? as f32;
3659                let target = self.arg_num(&args,1,0.0)? as f32;
3660                return Ok(Value::Number(ling_music::karaoke::pitch_score(hz, target) as f64));
3661            }
3662
3663            // ── MIDI (inaudible note source: drive coins, cues, etc.) ──
3664            #[cfg(not(target_arch = "wasm32"))]
3665            "music_midi_load" | "载入MIDI" | "MIDI読込" | "미디로드" | "โหลดมิดี" => {
3666                let path = self.arg_str(&args, 0, "");
3667                let resolved = if std::path::Path::new(&path).exists() { path.clone() }
3668                    else if let Some(d) = &self.source_dir { d.join(&path).to_string_lossy().into_owned() }
3669                    else { path.clone() };
3670                match ling_music::midi::load(&resolved) {
3671                    Ok(m) => { let id = self.midis.len(); self.midis.push(m); return Ok(Value::Number(id as f64)); }
3672                    Err(e) => { eprintln!("music_midi_load failed ({path}): {e}"); return Ok(Value::Number(-1.0)); }
3673                }
3674            }
3675            #[cfg(not(target_arch = "wasm32"))]
3676            "music_midi_count" | "MIDI数量" | "MIDI数" | "미디수" | "จำนวนมิดี" => {
3677                let id = self.arg_num(&args,0,0.0)? as i64;
3678                let n = self.midis.get(id as usize).map(|m| m.notes.len()).unwrap_or(0);
3679                return Ok(Value::Number(n as f64));
3680            }
3681            // music_midi_notes(id) -> flat [time, midi, time, midi, …]
3682            #[cfg(not(target_arch = "wasm32"))]
3683            "music_midi_notes" | "MIDI音符" | "MIDIノート" | "미디음표" | "โน้ตมิดี" => {
3684                let id = self.arg_num(&args,0,0.0)? as i64;
3685                let mut out = Vec::new();
3686                if let Some(m) = self.midis.get(id as usize) {
3687                    for n in &m.notes { out.push(Value::Number(n.time as f64)); out.push(Value::Number(n.midi as f64)); }
3688                }
3689                return Ok(Value::List(out));
3690            }
3691            // music_midi_bars(id) -> flat [time, midi, dur, …] (for karaoke note bars)
3692            #[cfg(not(target_arch = "wasm32"))]
3693            "music_midi_bars" | "MIDI音条" | "MIDIバー" | "미디바" | "แท่งมิดี" => {
3694                let id = self.arg_num(&args,0,0.0)? as i64;
3695                let mut out = Vec::new();
3696                if let Some(m) = self.midis.get(id as usize) {
3697                    for n in &m.notes {
3698                        out.push(Value::Number(n.time as f64));
3699                        out.push(Value::Number(n.midi as f64));
3700                        out.push(Value::Number(n.dur as f64));
3701                    }
3702                }
3703                return Ok(Value::List(out));
3704            }
3705
3706            // music_fft(track_id, nbands) -> spectrum at the current playback position
3707            #[cfg(not(target_arch = "wasm32"))]
3708            "music_fft" | "音乐频谱" | "音楽スペクトル" | "음악스펙트럼" | "สเปกตรัมเพลง" => {
3709                let id = self.arg_num(&args,0,0.0)? as i64;
3710                let nbands = self.arg_num(&args,1,16.0)? as usize;
3711                let pos = self.music.as_ref().map(|m| m.position()).unwrap_or(0.0);
3712                if let Some(t) = self.tracks.get(id as usize) {
3713                    let idx = (pos * t.rate as f32) as usize;
3714                    let end = (idx + 2048).min(t.mono.len());
3715                    if end > idx + 64 {
3716                        self.fft.borrow_mut().push_samples(&t.mono[idx..end]);
3717                    }
3718                }
3719                let bands = self.fft.borrow().freq_bands(nbands);
3720                return Ok(Value::List(bands.into_iter().map(|x| Value::Number(x as f64)).collect()));
3721            }
3722
3723            // ── spatial (2D/3D/4D) one-shot SFX ──
3724            #[cfg(not(target_arch = "wasm32"))]
3725            "audio_sfx" | "音效" | "空間効果音" | "공간효과음" | "เสียงเอฟเฟกต์" => {
3726                let x=self.arg_num(&args,0,0.0)? as f32; let y=self.arg_num(&args,1,0.0)? as f32; let z=self.arg_num(&args,2,0.0)? as f32;
3727                let w=self.arg_num(&args,3,1.0)? as f32; let freq=self.arg_num(&args,4,440.0)? as f32;
3728                let amp=self.arg_num(&args,5,0.3)? as f32; let dur=self.arg_num(&args,6,0.15)? as f32;
3729                let wave=Wave::from_name(&self.arg_str(&args,7,"sine"));
3730                if let Some(a)=&self.audio { a.sfx(x,y,z,w,freq,amp,dur,wave); }
3731                return Ok(Value::Unit);
3732            }
3733            // ── sample load / positional play / loop / stop ──
3734            #[cfg(not(target_arch = "wasm32"))]
3735            "audio_sample_load" | "载入采样" | "サンプル読込" | "샘플로드" | "โหลดตัวอย่างเสียง" => {
3736                let path = self.arg_str(&args, 0, "");
3737                let resolved = if std::path::Path::new(&path).exists() { path.clone() }
3738                    else if let Some(d) = &self.source_dir { d.join(&path).to_string_lossy().into_owned() }
3739                    else { path.clone() };
3740                match ling_music::load(&resolved) {
3741                    Ok(t) => {
3742                        if let Some(a)=&self.audio { return Ok(Value::Number(a.add_sample(t.mono, t.rate) as f64)); }
3743                        return Ok(Value::Number(-1.0));
3744                    }
3745                    Err(e) => { eprintln!("audio_sample_load failed ({path}): {e}"); return Ok(Value::Number(-1.0)); }
3746                }
3747            }
3748            #[cfg(not(target_arch = "wasm32"))]
3749            "audio_sample_play" | "播放采样" | "サンプル再生" | "샘플재생" | "เล่นตัวอย่างเสียง" => {
3750                let id=self.arg_num(&args,0,0.0)? as usize;
3751                let x=self.arg_num(&args,1,0.0)? as f32; let y=self.arg_num(&args,2,0.0)? as f32; let z=self.arg_num(&args,3,0.0)? as f32;
3752                let w=self.arg_num(&args,4,1.0)? as f32; let vol=self.arg_num(&args,5,1.0)? as f32;
3753                let looping=self.arg_num(&args,6,0.0)? > 0.5;
3754                let v = self.audio.as_ref().map(|a| a.play_sample(id,x,y,z,w,vol,looping)).unwrap_or(0);
3755                return Ok(Value::Number(v as f64));
3756            }
3757            #[cfg(not(target_arch = "wasm32"))]
3758            "audio_sample_stop" | "停止采样" | "サンプル停止" | "샘플정지" | "หยุดตัวอย่างเสียง" => {
3759                let v=self.arg_num(&args,0,0.0)? as u32;
3760                if let Some(a)=&self.audio { a.stop_sample(v); }
3761                return Ok(Value::Unit);
3762            }
3763            // ── master FX: delay / reverb / low-pass (underwater) ──
3764            #[cfg(not(target_arch = "wasm32"))]
3765            "audio_fx_delay" | "回声" | "ディレイ効果" | "딜레이" | "เสียงสะท้อน" => {
3766                let time=self.arg_num(&args,0,0.3)? as f32; let fb=self.arg_num(&args,1,0.3)? as f32; let mix=self.arg_num(&args,2,0.3)? as f32;
3767                if let Some(a)=&self.audio { a.fx_delay(time,fb,mix); }
3768                return Ok(Value::Unit);
3769            }
3770            #[cfg(not(target_arch = "wasm32"))]
3771            "audio_fx_reverb" | "混响" | "リバーブ" | "리버브" | "เสียงก้อง" => {
3772                let mix=self.arg_num(&args,0,0.3)? as f32;
3773                if let Some(a)=&self.audio { a.fx_reverb(mix); }
3774                return Ok(Value::Unit);
3775            }
3776            #[cfg(not(target_arch = "wasm32"))]
3777            "audio_fx_lowpass" | "低通滤波" | "ローパス" | "저역통과" | "กรองความถี่ต่ำ" => {
3778                let cutoff=self.arg_num(&args,0,1.0)? as f32;
3779                if let Some(a)=&self.audio { a.fx_lowpass(cutoff); }
3780                return Ok(Value::Unit);
3781            }
3782
3783            // ══════════════════════════════════════════════════════════════════
3784            // PHYSICS BUILTINS  (crates/ling-physics) — soft bodies, rigid+angular,
3785            // and a fast 2-D water/oil liquid sim mappable onto 3-D surfaces.
3786            // ══════════════════════════════════════════════════════════════════
3787
3788            // ── soft bodies (deformable bouncy balls) ──
3789            #[cfg(not(target_arch = "wasm32"))]
3790            "soft_ball" | "软球" | "ソフトボール" | "소프트볼" | "ลูกบอลนุ่ม" => {
3791                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32; let z=self.arg_num(&args,2,0.)? as f32;
3792                let r=self.arg_num(&args,3,1.0)? as f32;
3793                let b = ling_physics::soft::SoftBody::sphere(ling_physics::Vec3::new(x,y,z), r, 8, 12, 1.0);
3794                let id = self.soft_bodies.len(); self.soft_bodies.push(b);
3795                return Ok(Value::Number(id as f64));
3796            }
3797            #[cfg(not(target_arch = "wasm32"))]
3798            "soft_step" | "软体步进" | "ソフト更新" | "소프트스텝" | "ก้าวนุ่ม" => {
3799                let id=self.arg_num(&args,0,0.)? as usize; let dt=self.arg_num(&args,1,0.016)? as f32;
3800                let gy=self.arg_num(&args,2,15.0)? as f32;
3801                if let Some(b)=self.soft_bodies.get_mut(id) { b.integrate(dt, ling_physics::Vec3::new(0.0,gy,0.0), 4); }
3802                return Ok(Value::Unit);
3803            }
3804            #[cfg(not(target_arch = "wasm32"))]
3805            "soft_bounce" | "软体落地" | "ソフト着地" | "소프트바운스" | "เด้งนุ่ม" => {
3806                let id=self.arg_num(&args,0,0.)? as usize; let fy=self.arg_num(&args,1,0.)? as f32; let rest=self.arg_num(&args,2,0.5)? as f32;
3807                if let Some(b)=self.soft_bodies.get_mut(id) { b.floor_collision(fy, rest); }
3808                return Ok(Value::Unit);
3809            }
3810            #[cfg(not(target_arch = "wasm32"))]
3811            "soft_contain" | "软体边界" | "ソフト箱" | "소프트경계" | "กล่องนุ่ม" => {
3812                let id=self.arg_num(&args,0,0.)? as usize;
3813                let nx=self.arg_num(&args,1,-5.)? as f32; let ny=self.arg_num(&args,2,-5.)? as f32; let nz=self.arg_num(&args,3,-5.)? as f32;
3814                let mx=self.arg_num(&args,4,5.)? as f32; let my=self.arg_num(&args,5,5.)? as f32; let mz=self.arg_num(&args,6,5.)? as f32;
3815                let rest=self.arg_num(&args,7,0.6)? as f32;
3816                if let Some(b)=self.soft_bodies.get_mut(id) { b.contain(ling_physics::Vec3::new(nx,ny,nz), ling_physics::Vec3::new(mx,my,mz), rest); }
3817                return Ok(Value::Unit);
3818            }
3819            #[cfg(not(target_arch = "wasm32"))]
3820            "soft_kick" | "软体踢" | "ソフト衝撃" | "소프트킥" | "เตะนุ่ม" => {
3821                let id=self.arg_num(&args,0,0.)? as usize;
3822                let dx=self.arg_num(&args,1,0.)? as f32; let dy=self.arg_num(&args,2,0.)? as f32; let dz=self.arg_num(&args,3,0.)? as f32;
3823                let s=self.arg_num(&args,4,0.1)? as f32;
3824                if let Some(b)=self.soft_bodies.get_mut(id) { b.kick(ling_physics::Vec3::new(dx,dy,dz), s); }
3825                return Ok(Value::Unit);
3826            }
3827            #[cfg(not(target_arch = "wasm32"))]
3828            "soft_deform" | "形变量" | "変形量" | "변형량" | "ความบิดเบี้ยว" => {
3829                let id=self.arg_num(&args,0,0.)? as usize;
3830                let d=self.soft_bodies.get(id).map(|b| b.deformation()).unwrap_or(0.0);
3831                return Ok(Value::Number(d as f64));
3832            }
3833            #[cfg(not(target_arch = "wasm32"))]
3834            "soft_centroid" | "软体质心" | "ソフト重心" | "소프트중심" | "จุดศูนย์กลางนุ่ม" => {
3835                let id=self.arg_num(&args,0,0.)? as usize;
3836                let c=self.soft_bodies.get(id).map(|b| b.centroid()).unwrap_or(ling_physics::Vec3::ZERO);
3837                return Ok(Value::List(vec![Value::Number(c.x as f64),Value::Number(c.y as f64),Value::Number(c.z as f64)]));
3838            }
3839            // soft_nodes(id) -> flat [x,y,z, x,y,z, …] for rendering the deformed mesh
3840            #[cfg(not(target_arch = "wasm32"))]
3841            "soft_nodes" | "软体节点" | "ソフト節点" | "소프트노드" | "จุดนุ่ม" => {
3842                let id=self.arg_num(&args,0,0.)? as usize;
3843                let mut out=Vec::new();
3844                if let Some(b)=self.soft_bodies.get(id) {
3845                    for n in &b.nodes { out.push(Value::Number(n.pos.x as f64)); out.push(Value::Number(n.pos.y as f64)); out.push(Value::Number(n.pos.z as f64)); }
3846                }
3847                return Ok(Value::List(out));
3848            }
3849
3850            // ── rigid bodies with angular dynamics ──
3851            #[cfg(not(target_arch = "wasm32"))]
3852            "rb_add" | "刚体添加" | "剛体追加" | "강체추가" | "เพิ่มวัตถุแข็ง" => {
3853                let x=self.arg_num(&args,0,0.)? as f32; let y=self.arg_num(&args,1,0.)? as f32; let z=self.arg_num(&args,2,0.)? as f32;
3854                let mass=self.arg_num(&args,3,1.0)? as f32;
3855                let mut b = ling_physics::rigid::RigidBody::new(ling_physics::Vec3::new(x,y,z), mass);
3856                b.restitution = 0.6;
3857                return Ok(Value::Number(self.rigid_world.add(b) as f64));
3858            }
3859            #[cfg(not(target_arch = "wasm32"))]
3860            "rb_torque" | "扭矩" | "トルク" | "토크" | "แรงบิด" => {
3861                let i=self.arg_num(&args,0,0.)? as usize;
3862                let tx=self.arg_num(&args,1,0.)? as f32; let ty=self.arg_num(&args,2,0.)? as f32; let tz=self.arg_num(&args,3,0.)? as f32;
3863                if let Some(b)=self.rigid_world.bodies.get_mut(i) { b.apply_torque(ling_physics::Vec3::new(tx,ty,tz)); }
3864                return Ok(Value::Unit);
3865            }
3866            #[cfg(not(target_arch = "wasm32"))]
3867            "rb_spin" | "自旋" | "スピン" | "스핀" | "หมุน" => {
3868                let i=self.arg_num(&args,0,0.)? as usize;
3869                let wx=self.arg_num(&args,1,0.)? as f32; let wy=self.arg_num(&args,2,0.)? as f32; let wz=self.arg_num(&args,3,0.)? as f32;
3870                if let Some(b)=self.rigid_world.bodies.get_mut(i) { b.apply_spin(ling_physics::Vec3::new(wx,wy,wz)); }
3871                return Ok(Value::Unit);
3872            }
3873            #[cfg(not(target_arch = "wasm32"))]
3874            "rb_impulse" | "刚体冲量" | "剛体インパルス" | "강체충격" | "แรงดลแข็ง" => {
3875                let i=self.arg_num(&args,0,0.)? as usize;
3876                let ix=self.arg_num(&args,1,0.)? as f32; let iy=self.arg_num(&args,2,0.)? as f32; let iz=self.arg_num(&args,3,0.)? as f32;
3877                if let Some(b)=self.rigid_world.bodies.get_mut(i) { b.apply_impulse(ling_physics::Vec3::new(ix,iy,iz)); }
3878                return Ok(Value::Unit);
3879            }
3880            #[cfg(not(target_arch = "wasm32"))]
3881            "rb_floor" | "刚体落地" | "剛体着地" | "강체바닥" | "พื้นแข็ง" => {
3882                let i=self.arg_num(&args,0,0.)? as usize; let fy=self.arg_num(&args,1,0.)? as f32;
3883                let rest=self.arg_num(&args,2,0.6)? as f32; let fric=self.arg_num(&args,3,0.6)? as f32;
3884                if let Some(b)=self.rigid_world.bodies.get_mut(i) { b.bounce_floor(fy, rest, fric); }
3885                return Ok(Value::Unit);
3886            }
3887            #[cfg(not(target_arch = "wasm32"))]
3888            "rb_gravity" | "刚体重力" | "剛体重力" | "강체중력" | "แรงโน้มถ่วงแข็ง" => {
3889                let gx=self.arg_num(&args,0,0.)? as f32; let gy=self.arg_num(&args,1,9.81)? as f32; let gz=self.arg_num(&args,2,0.)? as f32;
3890                self.rigid_world.gravity = ling_physics::Vec3::new(gx,gy,gz);
3891                return Ok(Value::Unit);
3892            }
3893            #[cfg(not(target_arch = "wasm32"))]
3894            "rb_step" | "刚体步进" | "剛体更新" | "강체스텝" | "ก้าวแข็ง" => {
3895                let dt=self.arg_num(&args,0,0.016)? as f32;
3896                self.rigid_world.step(dt);
3897                return Ok(Value::Unit);
3898            }
3899            #[cfg(not(target_arch = "wasm32"))]
3900            "rb_pos" | "刚体位置" | "剛体位置" | "강체위치" | "ตำแหน่งแข็ง" => {
3901                let i=self.arg_num(&args,0,0.)? as usize;
3902                let p=self.rigid_world.bodies.get(i).map(|b| b.pos).unwrap_or(ling_physics::Vec3::ZERO);
3903                return Ok(Value::List(vec![Value::Number(p.x as f64),Value::Number(p.y as f64),Value::Number(p.z as f64)]));
3904            }
3905            #[cfg(not(target_arch = "wasm32"))]
3906            "rb_rot" | "刚体旋转" | "剛体回転" | "강체회전" | "การหมุนแข็ง" => {
3907                let i=self.arg_num(&args,0,0.)? as usize;
3908                let q=self.rigid_world.bodies.get(i).map(|b| b.orientation).unwrap_or(ling_physics::Quat::IDENTITY);
3909                return Ok(Value::List(vec![Value::Number(q.x as f64),Value::Number(q.y as f64),Value::Number(q.z as f64),Value::Number(q.w as f64)]));
3910            }
3911
3912            // ── liquid sim (water + oil, immiscible) ──
3913            #[cfg(not(target_arch = "wasm32"))]
3914            "liquid_new" | "新建液体" | "液体新規" | "액체생성" | "สร้างของเหลว" => {
3915                let w=self.arg_num(&args,0,64.)? as usize; let h=self.arg_num(&args,1,64.)? as usize;
3916                let id=self.liquids.len(); self.liquids.push(ling_physics::liquid::LiquidGrid::new(w,h));
3917                return Ok(Value::Number(id as f64));
3918            }
3919            #[cfg(not(target_arch = "wasm32"))]
3920            "liquid_splat" | "液体注入" | "液体追加" | "액체분사" | "หยดของเหลว" => {
3921                let id=self.arg_num(&args,0,0.)? as usize;
3922                let x=self.arg_num(&args,1,0.)? as f32; let y=self.arg_num(&args,2,0.)? as f32;
3923                let kind=self.arg_num(&args,3,0.)? as i32; let amt=self.arg_num(&args,4,1.0)? as f32; let rad=self.arg_num(&args,5,4.0)? as f32;
3924                if let Some(g)=self.liquids.get_mut(id) { g.splat(x,y,kind,amt,rad); }
3925                return Ok(Value::Unit);
3926            }
3927            #[cfg(not(target_arch = "wasm32"))]
3928            "liquid_gravity" | "液体重力" | "液体重力ベクトル" | "액체중력" | "แรงโน้มถ่วงเหลว" => {
3929                let id=self.arg_num(&args,0,0.)? as usize;
3930                let gx=self.arg_num(&args,1,0.)? as f32; let gy=self.arg_num(&args,2,60.)? as f32;
3931                if let Some(g)=self.liquids.get_mut(id) { g.set_gravity(gx,gy); }
3932                return Ok(Value::Unit);
3933            }
3934            #[cfg(not(target_arch = "wasm32"))]
3935            "liquid_step" | "液体步进" | "液体更新" | "액체스텝" | "ก้าวของเหลว" => {
3936                let id=self.arg_num(&args,0,0.)? as usize; let dt=self.arg_num(&args,1,0.016)? as f32;
3937                if let Some(g)=self.liquids.get_mut(id) { g.step(dt); }
3938                return Ok(Value::Unit);
3939            }
3940            // liquid_draw(id, sx, sy, scale) — fast flat 2-D blit of the colour field
3941            #[cfg(not(target_arch = "wasm32"))]
3942            "liquid_draw" | "绘制液体" | "液体描画" | "액체그리기" | "วาดของเหลว" => {
3943                let id=self.arg_num(&args,0,0.)? as usize;
3944                let sx=self.arg_num(&args,1,0.)? as i32; let sy=self.arg_num(&args,2,0.)? as i32;
3945                let scale=(self.arg_num(&args,3,4.)? as i32).max(1);
3946                if id < self.liquids.len() {
3947                    let (gw,gh)={ let g=&self.liquids[id]; (g.w,g.h) };
3948                    let mut gfx=self.gfx.borrow_mut(); let (w,h)=(gfx.width as i32, gfx.height as i32);
3949                    let g=&self.liquids[id];
3950                    for cy in 0..gh { for cx in 0..gw {
3951                        let col=g.sample_rgb(cx,cy);
3952                        let bx=sx + cx as i32*scale; let by=sy + cy as i32*scale;
3953                        for dy in 0..scale { for dx in 0..scale {
3954                            let px=bx+dx; let py=by+dy;
3955                            if px>=0 && py>=0 && px<w && py<h { gfx.buffer[(py*w+px) as usize]=col; }
3956                        }}
3957                    }}
3958                }
3959                return Ok(Value::Unit);
3960            }
3961            // liquid_draw_surface(id, kind, cx,cy,cz, radius, height)
3962            //   kind: 0 plane · 1 sphere · 2 cylinder · 3 cone · 4 dome
3963            #[cfg(not(target_arch = "wasm32"))]
3964            "liquid_draw_surface" | "液体贴面" | "液体曲面" | "액체곡면" | "ของเหลวบนพื้นผิว" => {
3965                let id=self.arg_num(&args,0,0.)? as usize;
3966                let kind=self.arg_num(&args,1,1.)? as i32;
3967                let cx=self.arg_num(&args,2,0.)? as f32; let cy=self.arg_num(&args,3,0.)? as f32; let cz=self.arg_num(&args,4,0.)? as f32;
3968                let radius=self.arg_num(&args,5,2.0)? as f32; let height=self.arg_num(&args,6,3.0)? as f32;
3969                if id < self.liquids.len() {
3970                    let (gw,gh)={ let g=&self.liquids[id]; (g.w,g.h) };
3971                    let mut gfx=self.gfx.borrow_mut();
3972                    let (w,h)=(gfx.width as i32, gfx.height as i32);
3973                    let cam=gfx.camera.clone();
3974                    let near = -cam.zdist + 0.05;
3975                    let g=&self.liquids[id];
3976                    let tau=std::f32::consts::TAU; let pi=std::f32::consts::PI;
3977                    let mut cyc=0usize;
3978                    while cyc<gh {
3979                        let v=cyc as f32/(gh as f32-1.0);
3980                        let mut cxc=0usize;
3981                        while cxc<gw {
3982                            let u=cxc as f32/(gw as f32-1.0);
3983                            // surface point P and outward normal N
3984                            let (px_,py_,pz_,nx_,ny_,nz_);
3985                            if kind==0 { // plane
3986                                px_=cx+(u-0.5)*2.0*radius; py_=cy; pz_=cz+(v-0.5)*2.0*radius; nx_=0.0; ny_=-1.0; nz_=0.0;
3987                            } else if kind==2 { // cylinder
3988                                let th=u*tau; px_=cx+th.cos()*radius; py_=cy+(v-0.5)*height; pz_=cz+th.sin()*radius; nx_=th.cos(); ny_=0.0; nz_=th.sin();
3989                            } else if kind==3 { // cone
3990                                let th=u*tau; let rr=radius*(1.0-v); px_=cx+th.cos()*rr; py_=cy+(v-0.5)*height; pz_=cz+th.sin()*rr;
3991                                let s=(radius/height.max(0.01)).atan(); nx_=th.cos()*s.cos(); ny_=s.sin(); nz_=th.sin()*s.cos();
3992                            } else if kind==4 { // dome (upper hemisphere)
3993                                let th=u*tau; let ph=v*pi*0.5; px_=cx+ph.sin()*th.cos()*radius; py_=cy-ph.cos()*radius; pz_=cz+ph.sin()*th.sin()*radius; nx_=ph.sin()*th.cos(); ny_=-ph.cos(); nz_=ph.sin()*th.sin();
3994                            } else { // sphere
3995                                let th=u*tau; let ph=v*pi; px_=cx+ph.sin()*th.cos()*radius; py_=cy+ph.cos()*radius; pz_=cz+ph.sin()*th.sin()*radius; nx_=ph.sin()*th.cos(); ny_=ph.cos(); nz_=ph.sin()*th.sin();
3996                            }
3997                            let dP=cam.depth(px_,py_,pz_);
3998                            if dP>near {
3999                                // back-face cull (skip for the plane): outward normal pointing away → deeper
4000                                let cull = kind!=0 && cam.depth(px_+nx_*0.06, py_+ny_*0.06, pz_+nz_*0.06) > dP;
4001                                if !cull {
4002                                    let (spx,spy,depth)=cam.project(px_,py_,pz_);
4003                                    if depth>0.0 {
4004                                        let col=g.sample_rgb(cxc,cyc);
4005                                        let bs=((radius*cam.focal/(depth*gw as f32))*1.6).max(1.0).min(7.0) as i32;
4006                                        let ix=spx as i32; let iy=spy as i32;
4007                                        let mut dy=-bs; while dy<=bs { let mut dx=-bs; while dx<=bs {
4008                                            let qx=ix+dx; let qy=iy+dy;
4009                                            if qx>=0 && qy>=0 && qx<w && qy<h { gfx.buffer[(qy*w+qx) as usize]=col; }
4010                                            dx+=1; } dy+=1; }
4011                                    }
4012                                }
4013                            }
4014                            cxc+=1;
4015                        }
4016                        cyc+=1;
4017                    }
4018                }
4019                return Ok(Value::Unit);
4020            }
4021
4022            // text_poll() — fold newly-typed keys into the input buffer, return it
4023            #[cfg(not(target_arch = "wasm32"))]
4024            "text_poll" => {
4025                let keys = { let gfx = self.gfx.borrow(); gfx.window.as_ref().map(|w| w.get_keys_pressed(minifb::KeyRepeat::No)).unwrap_or_default() };
4026                for k in keys {
4027                    if k == minifb::Key::Backspace { self.text_buffer.pop(); }
4028                    else if let Some(c) = key_char(k) { self.text_buffer.push(c); }
4029                }
4030                return Ok(Value::Str(self.text_buffer.clone()));
4031            }
4032            "text_get"   => return Ok(Value::Str(self.text_buffer.clone())),
4033            "text_set"   => { self.text_buffer = self.arg_str(&args,0,""); return Ok(Value::Unit); }
4034            "text_clear" => { self.text_buffer.clear(); return Ok(Value::Unit); }
4035            // record_frame() — append the current framebuffer as a PPM, return frame #
4036            #[cfg(not(target_arch = "wasm32"))]
4037            "record_frame" => {
4038                let n = self.record_n;
4039                let (buf, w, h) = { let gfx = self.gfx.borrow(); (gfx.buffer.clone(), gfx.width, gfx.height) };
4040                let _ = std::fs::create_dir_all("recordings");
4041                let mut out = Vec::with_capacity(w*h*3 + 32);
4042                out.extend_from_slice(format!("P6\n{w} {h}\n255\n").as_bytes());
4043                for px in &buf { let p = *px; out.push((p>>16) as u8); out.push((p>>8) as u8); out.push(p as u8); }
4044                let _ = std::fs::write(format!("recordings/frame_{n:05}.ppm"), out);
4045                self.record_n += 1;
4046                return Ok(Value::Number(n as f64));
4047            }
4048            "record_count" => return Ok(Value::Number(self.record_n as f64)),
4049            // ── microphone → crypto donut ──
4050            // mic_capture() — append the latest mic samples to the record buffer
4051            // (call each frame while recording). Returns the buffer length.
4052            #[cfg(not(target_arch = "wasm32"))]
4053            "mic_capture" => {
4054                if let Some(mic) = self.mic.as_ref() {
4055                    let s = mic.latest_samples();
4056                    self.mic_buffer.extend_from_slice(&s);
4057                    let cap = 96_000usize; // ~2 s @ 48 kHz
4058                    if self.mic_buffer.len() > cap {
4059                        let drop = self.mic_buffer.len() - cap;
4060                        self.mic_buffer.drain(0..drop);
4061                    }
4062                }
4063                return Ok(Value::Number(self.mic_buffer.len() as f64));
4064            }
4065            // mic_seed() — SHA3-256 hex of the recorded audio, usable as a donut seed
4066            #[cfg(not(target_arch = "wasm32"))]
4067            "mic_seed" => {
4068                let mut bytes = Vec::with_capacity(self.mic_buffer.len() * 4);
4069                for f in &self.mic_buffer { bytes.extend_from_slice(&f.to_le_bytes()); }
4070                return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(&bytes))));
4071            }
4072            #[cfg(not(target_arch = "wasm32"))]
4073            "mic_clear" => { self.mic_buffer.clear(); return Ok(Value::Number(0.0)); }
4074            // flush the 3-D depth queue onto the framebuffer WITHOUT presenting,
4075            // so 2-D UI drawn afterwards overlays the 3-D scene.
4076            #[cfg(not(target_arch = "wasm32"))]
4077            "flush_3d" | "render_3d" => {
4078                let mut gfx = self.gfx.borrow_mut();
4079                if !gfx.depth_queue.is_empty() {
4080                    let w = gfx.width; let h = gfx.height;
4081                    let queue = std::mem::take(&mut gfx.depth_queue);
4082                    queue.flush(&mut gfx.buffer, w, h);
4083                }
4084                return Ok(Value::Unit);
4085            }
4086
4087            "set_rim" | "设置边缘光" | "リム設定" | "림라이트" | "ตั้งขอบเรือง" => {
4088                let s=self.arg_num(&args,0,0.6)? as f32;
4089                let r=self.arg_num(&args,1,115.)? as f32/255.0;
4090                let g=self.arg_num(&args,2,217.)? as f32/255.0;
4091                let b=self.arg_num(&args,3,255.)? as f32/255.0;
4092                let mut gfx=self.gfx.borrow_mut();
4093                gfx.shade.rim = s; gfx.shade.rim_color = [r,g,b];
4094                return Ok(Value::Unit);
4095            }
4096
4097            // ══════════════════════════════════════════════════════════════════
4098            // 3-D PRIMITIVES  (src/gfx/shapes.rs)  — "Inkscape for 3-D"
4099            //   shape(cx,cy,cz,  sx,sy,sz,  rx,ry,rz,  mode,  e0,e1,e2)
4100            //     centre (cx,cy,cz), per-axis scale, Euler rotation (radians),
4101            //     mode: 0 filled · 1 wireframe · 2 both,
4102            //     e0..e2: shape-specific (segments / sides / ratio …).
4103            //   Pen colour (set_color) drives fill lighting and wireframe colour.
4104            // ══════════════════════════════════════════════════════════════════
4105            n if crate::gfx::shapes::canon(n).is_some() => {
4106                let kind = crate::gfx::shapes::canon(n).unwrap();
4107                let cx=self.arg_num(&args,0,0.)? as f32; let cy=self.arg_num(&args,1,0.)? as f32; let cz=self.arg_num(&args,2,0.)? as f32;
4108                let sx=self.arg_num(&args,3,1.)? as f32; let sy=self.arg_num(&args,4,1.)? as f32; let sz=self.arg_num(&args,5,1.)? as f32;
4109                let rx=self.arg_num(&args,6,0.)? as f32; let ry=self.arg_num(&args,7,0.)? as f32; let rz=self.arg_num(&args,8,0.)? as f32;
4110                let mode=self.arg_num(&args,9,0.)? as i32;
4111                let e0=self.arg_num(&args,10,0.)? as f32; let e1=self.arg_num(&args,11,0.)? as f32; let e2=self.arg_num(&args,12,0.)? as f32;
4112                if let Some(mesh)=crate::gfx::shapes::build(kind,[cx,cy,cz,sx,sy,sz,rx,ry,rz],e0,e1,e2){
4113                    let mut gfx=self.gfx.borrow_mut();
4114                    gfx.emit_mesh(&mesh,mode);
4115                }
4116                return Ok(Value::Unit);
4117            }
4118
4119            _ => {}
4120        }
4121
4122        // User-defined function
4123        if let Some(def) = self.functions.get(name).cloned() {
4124            let mut call_env = Env::new();
4125            // Seed env with non-Do globals (skip entry-point blocks to avoid infinite recursion).
4126            // Pass 1: evaluate each global with the call-site env — simple literals succeed.
4127            // Pass 2: retry failed globals with the partially-built call_env so that compound
4128            //         globals (e.g. `FM = (NZ + FZ) / 2.0`) can resolve their dependencies.
4129            let non_do_globals: Vec<_> = self.globals.iter()
4130                .filter(|(_, expr)| !matches!(expr, Expr::Do(_)))
4131                .map(|(k, e)| (k.clone(), e.clone()))
4132                .collect();
4133            let mut pending: Vec<(String, Expr)> = Vec::new();
4134            for (k, expr) in &non_do_globals {
4135                let mut tmp = env.clone();
4136                if let Ok(v) = self.eval_expr(expr, &mut tmp) {
4137                    call_env.insert(k.clone(), v);
4138                } else {
4139                    pending.push((k.clone(), expr.clone()));
4140                }
4141            }
4142            // Second pass: retry compound globals now that literals are in call_env.
4143            for (k, expr) in &pending {
4144                let mut tmp = env.clone();
4145                tmp.extend(call_env.clone());
4146                if let Ok(v) = self.eval_expr(expr, &mut tmp) {
4147                    call_env.insert(k.clone(), v);
4148                }
4149            }
4150            for (param, arg) in def.params.iter().zip(args) {
4151                call_env.insert(param.clone(), arg);
4152            }
4153            return match self.exec_block(&def.body, &mut call_env) {
4154                Ok(v) => Ok(v.unwrap_or(Value::Unit)),
4155                Err(EvalErr::Return(v)) => Ok(v),
4156                Err(e) => Err(e),
4157            };
4158        }
4159
4160        Err(EvalErr::from(format!("unknown function '{name}'")))
4161    }
4162
4163    fn call_value(&mut self, v: Value, args: Vec<Value>) -> EvalResult {
4164        match v {
4165            Value::Fn(params, body, mut captured) => {
4166                for (p, a) in params.iter().zip(args) {
4167                    captured.insert(p.clone(), a);
4168                }
4169                match self.exec_block(&body, &mut captured) {
4170                    Ok(v) => Ok(v.unwrap_or(Value::Unit)),
4171                    Err(EvalErr::Return(v)) => Ok(v),
4172                    Err(e) => Err(e),
4173                }
4174            }
4175            other => Err(EvalErr::from(format!("cannot call {:?}", other))),
4176        }
4177    }
4178
4179    fn call_method(&self, recv: Value, method: &str, args: Vec<Value>) -> EvalResult {
4180        match (&recv, method) {
4181            (Value::Str(s), "is_empty" | "是空") => Ok(Value::Bool(s.is_empty())),
4182            (Value::Str(s), "len" | "长")        => Ok(Value::Number(s.len() as f64)),
4183            (Value::Str(s), "to_string" | "转文") => Ok(Value::Str(s.clone())),
4184            (Value::Str(s), "contains" | "包含") => {
4185                if let Some(Value::Str(sub)) = args.first() {
4186                    Ok(Value::Bool(s.contains(sub.as_str())))
4187                } else { Ok(Value::Bool(false)) }
4188            }
4189            (Value::Str(s), "push_str" | "推_文") => {
4190                let mut s2 = s.clone();
4191                if let Some(Value::Str(a)) = args.first() { s2.push_str(a); }
4192                Ok(Value::Str(s2))
4193            }
4194            (Value::List(v), "len" | "长") => Ok(Value::Number(v.len() as f64)),
4195            (Value::List(v), "push" | "推") => {
4196                let mut v2 = v.clone();
4197                if let Some(a) = args.first() { v2.push(a.clone()); }
4198                Ok(Value::List(v2))
4199            }
4200            (Value::Ok(inner), _) | (Value::Err(inner), _) => Ok(*inner.clone()),
4201            _ => Err(EvalErr::from(format!("no method '{method}' on {recv}"))),
4202        }
4203    }
4204
4205    // ─── Pattern matching ─────────────────────────────────────────────────────
4206
4207    fn match_pattern(&self, pat: &Pattern, val: &Value) -> Option<Env> {
4208        match (pat, val) {
4209            (Pattern::Wildcard, _) => Some(Env::new()),
4210            (Pattern::Str(s), Value::Str(v)) if s == v => Some(Env::new()),
4211            (Pattern::Number(n), Value::Number(v)) if (n - v).abs() < 1e-12 => Some(Env::new()),
4212            (Pattern::Bool(b), Value::Bool(v)) if b == v => Some(Env::new()),
4213            (Pattern::Ident(name), _) => {
4214                let mut e = Env::new();
4215                e.insert(name.clone(), val.clone());
4216                Some(e)
4217            }
4218            (Pattern::Constructor(ctor, inner_pat), _) => {
4219                let (matches, inner_val) = match (ctor.as_str(), val) {
4220                    ("ok"  | "好", Value::Ok(v))  => (true, Some(v.as_ref().clone())),
4221                    ("bad" | "坏", Value::Err(v)) => (true, Some(v.as_ref().clone())),
4222                    ("ok"  | "好", v) if !matches!(v, Value::Err(_)) => (true, Some(v.clone())),
4223                    _ => (false, None),
4224                };
4225                if !matches { return None; }
4226                match (inner_pat, inner_val) {
4227                    (Some(p), Some(v)) => self.match_pattern(p, &v),
4228                    (None, _)          => Some(Env::new()),
4229                    (Some(p), None)    => self.match_pattern(p, &Value::Unit),
4230                }
4231            }
4232            _ => None,
4233        }
4234    }
4235
4236    // ─── Utilities ───────────────────────────────────────────────────────────
4237
4238    fn value_to_iter(&self, val: Value) -> Result<Vec<Value>, EvalErr> {
4239        match val {
4240            Value::List(v)   => Ok(v),
4241            Value::Str(s)    => Ok(s.chars().map(|c| Value::Str(c.to_string())).collect()),
4242            Value::Number(n) => Ok((0..n as i64).map(|i| Value::Number(i as f64)).collect()),
4243            other => Err(EvalErr::from(format!("cannot iterate over {:?}", other))),
4244        }
4245    }
4246
4247    fn is_truthy(&self, val: &Value) -> bool {
4248        match val {
4249            Value::Bool(b)     => *b,
4250            Value::Unit        => false,
4251            Value::Number(n)   => *n != 0.0,
4252            Value::Str(s)      => !s.is_empty(),
4253            Value::List(v)     => !v.is_empty(),
4254            Value::Ok(_)       => true,
4255            Value::Err(_)      => false,
4256            Value::Fn(_, _, _) => true,
4257        }
4258    }
4259
4260    fn to_number(&self, val: &Value) -> Result<f64, EvalErr> {
4261        match val {
4262            Value::Number(n) => Ok(*n),
4263            Value::Str(s)    => s.parse().map_err(|_| EvalErr::from(format!("cannot convert '{s}' to number"))),
4264            other => Err(EvalErr::from(format!("expected number, got {:?}", other))),
4265        }
4266    }
4267
4268    /// Get the n-th argument as f64, falling back to `default` if missing.
4269    fn arg_num(&self, args: &[Value], n: usize, default: f64) -> Result<f64, EvalErr> {
4270        match args.get(n) {
4271            Some(v) => self.to_number(v),
4272            None    => Ok(default),
4273        }
4274    }
4275
4276    fn arg_str(&self, args: &[Value], n: usize, default: &str) -> String {
4277        args.get(n).map(|v| v.to_string()).unwrap_or_else(|| default.to_string())
4278    }
4279
4280    /// Read a list-of-numbers argument as `Vec<f32>` (empty if absent/not a list).
4281    #[allow(dead_code)]
4282    fn arg_list_f32(&self, args: &[Value], n: usize) -> Vec<f32> {
4283        match args.get(n) {
4284            Some(Value::List(v)) => v.iter().filter_map(|x| match x {
4285                Value::Number(n) => Some(*n as f32),
4286                _ => None,
4287            }).collect(),
4288            _ => Vec::new(),
4289        }
4290    }
4291
4292    /// Optional `r,g,b` colour override starting at arg `i` → packed 0x00RRGGBB,
4293    /// or `default` if those three numeric args aren't present.
4294    #[cfg(not(target_arch = "wasm32"))]
4295    fn color_at(&self, args: &[Value], i: usize, default: u32) -> u32 {
4296        match (args.get(i), args.get(i + 1), args.get(i + 2)) {
4297            (Some(a), Some(b), Some(c)) => match (self.to_number(a), self.to_number(b), self.to_number(c)) {
4298                (Ok(r), Ok(g), Ok(bl)) =>
4299                    ((r as u32 & 0xFF) << 16) | ((g as u32 & 0xFF) << 8) | (bl as u32 & 0xFF),
4300                _ => default,
4301            },
4302            _ => default,
4303        }
4304    }
4305
4306    /// A pitch argument: a note-name string (`"C4"`, `"A#3"`) or a numeric MIDI value.
4307    #[cfg(not(target_arch = "wasm32"))]
4308    fn pitch_arg(&self, args: &[Value], i: usize, default: i32) -> i32 {
4309        match args.get(i) {
4310            Some(Value::Str(s)) => ling_music::note::parse_pitch(s).unwrap_or(default),
4311            Some(Value::Number(n)) => *n as i32,
4312            _ => default,
4313        }
4314    }
4315
4316    /// Current mouse position + left-button-down (native window only).
4317    #[cfg(not(target_arch = "wasm32"))]
4318    fn mouse_now(&self) -> (f32, f32, bool) {
4319        let gfx = self.gfx.borrow();
4320        let (mx, my) = gfx.window.as_ref()
4321            .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp)).unwrap_or((0.0, 0.0));
4322        let down = gfx.window.as_ref()
4323            .map(|w| w.get_mouse_down(minifb::MouseButton::Left)).unwrap_or(false);
4324        (mx, my, down)
4325    }
4326
4327    /// Rasterize a UI [`ling_ui::widgets::Draw`] into the framebuffer: filled
4328    /// polygons via the AA scanline fill, polylines via AA lines, honouring the
4329    /// current blend mode.
4330    #[cfg(not(target_arch = "wasm32"))]
4331    fn draw_ui(&self, d: &ling_ui::widgets::Draw) {
4332        let mut gfx = self.gfx.borrow_mut();
4333        let (w, h, add) = (gfx.width, gfx.height, gfx.blend == 1);
4334        for (c, poly) in &d.fills {
4335            crate::gfx::raster::fill_contours_aa(&mut gfx.buffer, w, h, *c, add, std::slice::from_ref(poly));
4336        }
4337        for (c, pl) in &d.strokes {
4338            for s in pl.windows(2) {
4339                crate::gfx::raster::draw_line_aa(&mut gfx.buffer, w, h, *c, add, s[0][0], s[0][1], s[1][0], s[1][1]);
4340            }
4341        }
4342    }
4343
4344    /// Parse (dst_x, dst_y, width, height) from the first four args of a tex_* builtin.
4345    fn tex_rect(&self, args: &[Value]) -> Result<(usize, usize, usize, usize), EvalErr> {
4346        let tx = self.arg_num(args, 0, 0.0)? as usize;
4347        let ty = self.arg_num(args, 1, 0.0)? as usize;
4348        let tw = self.arg_num(args, 2, 256.0)? as usize;
4349        let th = self.arg_num(args, 3, 256.0)? as usize;
4350        Ok((tx, ty, tw.max(1), th.max(1)))
4351    }
4352
4353    fn apply_binop(&self, op: &BinOp, l: Value, r: Value) -> EvalResult {
4354        match op {
4355            BinOp::Add => match (l, r) {
4356                (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
4357                (Value::Str(a), Value::Str(b))       => Ok(Value::Str(a + &b)),
4358                (Value::Str(a), b)                   => Ok(Value::Str(a + &b.to_string())),
4359                (a, Value::Str(b))                   => Ok(Value::Str(a.to_string() + &b)),
4360                (a, b) => Err(EvalErr::from(format!("cannot add {:?} and {:?}", a, b))),
4361            },
4362            BinOp::Sub => Ok(Value::Number(self.to_number(&l)? - self.to_number(&r)?)),
4363            BinOp::Mul => Ok(Value::Number(self.to_number(&l)? * self.to_number(&r)?)),
4364            BinOp::Div => Ok(Value::Number(self.to_number(&l)? / self.to_number(&r)?)),
4365            BinOp::Rem => Ok(Value::Number(self.to_number(&l)? % self.to_number(&r)?)),
4366            BinOp::Eq  => Ok(Value::Bool(values_equal(&l, &r))),
4367            BinOp::Ne  => Ok(Value::Bool(!values_equal(&l, &r))),
4368            BinOp::Lt  => Ok(Value::Bool(self.to_number(&l)? < self.to_number(&r)?)),
4369            BinOp::Gt  => Ok(Value::Bool(self.to_number(&l)? > self.to_number(&r)?)),
4370            BinOp::Le  => Ok(Value::Bool(self.to_number(&l)? <= self.to_number(&r)?)),
4371            BinOp::Ge  => Ok(Value::Bool(self.to_number(&l)? >= self.to_number(&r)?)),
4372            BinOp::And => Ok(Value::Bool(self.is_truthy(&l) && self.is_truthy(&r))),
4373            BinOp::Or  => Ok(Value::Bool(self.is_truthy(&l) || self.is_truthy(&r))),
4374        }
4375    }
4376
4377    fn builtin_format(&self, args: &[Value]) -> Result<String, EvalErr> {
4378        if args.is_empty() { return Ok(String::new()); }
4379        let fmt = match &args[0] {
4380            Value::Str(s) => s.clone(),
4381            other => return Ok(other.to_string()),
4382        };
4383
4384        let mut result = String::new();
4385        let mut arg_idx = 1usize;
4386        let mut chars = fmt.chars().peekable();
4387        while let Some(c) = chars.next() {
4388            if c == '{' {
4389                if chars.peek() == Some(&'}') {
4390                    chars.next();
4391                    if arg_idx < args.len() {
4392                        result.push_str(&args[arg_idx].to_string());
4393                        arg_idx += 1;
4394                    }
4395                } else {
4396                    let mut spec = String::new();
4397                    for ch in chars.by_ref() {
4398                        if ch == '}' { break; }
4399                        spec.push(ch);
4400                    }
4401                    if arg_idx < args.len() {
4402                        if spec.starts_with(":.") {
4403                            if let Value::Number(n) = &args[arg_idx] {
4404                                let prec: usize = spec[2..].trim_end_matches('f')
4405                                    .parse().unwrap_or(2);
4406                                result.push_str(&format!("{:.prec$}", n));
4407                                arg_idx += 1;
4408                                continue;
4409                            }
4410                        }
4411                        result.push_str(&args[arg_idx].to_string());
4412                        arg_idx += 1;
4413                    }
4414                }
4415            } else {
4416                result.push(c);
4417            }
4418        }
4419        Ok(result)
4420    }
4421}
4422
4423#[cfg(not(target_arch = "wasm32"))]
4424fn str_to_minifb_key(name: &str) -> Option<minifb::Key> {
4425    use minifb::Key;
4426    Some(match name {
4427        "numpad0" | "kp0" => Key::NumPad0,
4428        "numpad1" | "kp1" => Key::NumPad1,
4429        "numpad2" | "kp2" => Key::NumPad2,
4430        "numpad3" | "kp3" => Key::NumPad3,
4431        "numpad4" | "kp4" => Key::NumPad4,
4432        "numpad5" | "kp5" => Key::NumPad5,
4433        "numpad6" | "kp6" => Key::NumPad6,
4434        "numpad7" | "kp7" => Key::NumPad7,
4435        "numpad8" | "kp8" => Key::NumPad8,
4436        "numpad9" | "kp9" => Key::NumPad9,
4437        "numpad+" | "kp+" => Key::NumPadPlus,
4438        "numpad-" | "kp-" => Key::NumPadMinus,
4439        "numpad*" | "kp*" => Key::NumPadAsterisk,
4440        "numpad/" | "kp/" => Key::NumPadSlash,
4441        "left"   => Key::Left,
4442        "right"  => Key::Right,
4443        "up"     => Key::Up,
4444        "down"   => Key::Down,
4445        "space"  => Key::Space,
4446        "enter"  => Key::Enter,
4447        "escape" => Key::Escape,
4448        "pageup" => Key::PageUp,
4449        "pagedown" => Key::PageDown,
4450        "lshift" | "leftshift"  => Key::LeftShift,
4451        "rshift" | "rightshift" => Key::RightShift,
4452        "lctrl"  | "leftctrl"   => Key::LeftCtrl,
4453        "rctrl"  | "rightctrl"  => Key::RightCtrl,
4454        "tab"    => Key::Tab,
4455        "backspace" => Key::Backspace,
4456        "delete" => Key::Delete,
4457        "insert" => Key::Insert,
4458        "home"   => Key::Home,
4459        "end"    => Key::End,
4460        "a" => Key::A, "b" => Key::B, "c" => Key::C, "d" => Key::D,
4461        "e" => Key::E, "f" => Key::F, "g" => Key::G, "h" => Key::H,
4462        "i" => Key::I, "j" => Key::J, "k" => Key::K, "l" => Key::L,
4463        "m" => Key::M, "n" => Key::N, "o" => Key::O, "p" => Key::P,
4464        "q" => Key::Q, "r" => Key::R, "s" => Key::S, "t" => Key::T,
4465        "u" => Key::U, "v" => Key::V, "w" => Key::W, "x" => Key::X,
4466        "y" => Key::Y, "z" => Key::Z,
4467        "0" => Key::Key0, "1" => Key::Key1, "2" => Key::Key2,
4468        "3" => Key::Key3, "4" => Key::Key4, "5" => Key::Key5,
4469        "6" => Key::Key6, "7" => Key::Key7, "8" => Key::Key8,
4470        "9" => Key::Key9,
4471        _ => return None,
4472    })
4473}
4474
4475fn values_equal(a: &Value, b: &Value) -> bool {
4476    match (a, b) {
4477        (Value::Number(x), Value::Number(y)) => (x - y).abs() < 1e-12,
4478        (Value::Str(x), Value::Str(y))       => x == y,
4479        (Value::Bool(x), Value::Bool(y))     => x == y,
4480        (Value::Unit, Value::Unit)            => true,
4481        _ => false,
4482    }
4483}
4484
4485// Rasteriser functions live in crate::gfx::raster — imported at top of file.
4486
4487// ── Window platform helpers ────────────────────────────────────────────────────
4488
4489/// Hide the console window that the OS auto-attaches to console-subsystem
4490/// processes. No-op on non-Windows and when no console is present.
4491#[cfg(not(target_arch = "wasm32"))]
4492fn hide_console_window() {
4493    #[cfg(windows)]
4494    unsafe {
4495        extern "system" {
4496            fn GetConsoleWindow() -> isize;
4497            fn ShowWindow(hwnd: isize, nCmdShow: i32) -> i32;
4498        }
4499        let hwnd = GetConsoleWindow();
4500        if hwnd != 0 {
4501            ShowWindow(hwnd, 0); // SW_HIDE = 0
4502        }
4503    }
4504}
4505
4506/// Move and resize the foreground window so it fills the primary monitor
4507/// (0,0 → screen_w × screen_h) and sits above the taskbar (HWND_TOPMOST).
4508#[cfg(all(not(target_arch = "wasm32"), windows))]
4509fn reposition_fullscreen(screen_w: i32, screen_h: i32) {
4510    unsafe {
4511        extern "system" {
4512            fn GetForegroundWindow() -> isize;
4513            fn SetWindowPos(hwnd: isize, insert_after: isize,
4514                            x: i32, y: i32, cx: i32, cy: i32,
4515                            flags: u32) -> i32;
4516        }
4517        let hwnd = GetForegroundWindow();
4518        if hwnd != 0 {
4519            // HWND_TOPMOST = -1, SWP_SHOWWINDOW = 0x0040
4520            SetWindowPos(hwnd, -1isize, 0, 0, screen_w, screen_h, 0x0040);
4521        }
4522    }
4523}
4524
4525/// Query the primary display resolution on non-Windows platforms.
4526/// Falls back to 1920×1080 if the size cannot be determined.
4527#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
4528fn native_screen_size() -> (f64, f64) {
4529    // On Linux/macOS we don't have an easy dependency-free call; return a
4530    // sensible default. Callers can always pass explicit dimensions.
4531    (1920.0, 1080.0)
4532}