Skip to main content

ling/runtime/
mod.rs

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