Skip to main content

ling/runtime/
mod.rs

1// src/runtime/mod.rs — tree-walking interpreter with graphics support
2use std::cell::RefCell;
3use std::collections::HashMap;
4use crate::parser::ast::*;
5use crate::gfx::{GfxState, Light};
6#[cfg(not(target_arch = "wasm32"))]
7use crate::gfx::raster::{fill_triangle, draw_line};
8#[cfg(not(target_arch = "wasm32"))]
9use ling_audio::{AudioEngine, ToneParams};
10
11#[cfg(not(target_arch = "wasm32"))]
12use ling_audio::FftAnalyzer;
13
14// ─── Values ──────────────────────────────────────────────────────────────────
15
16#[derive(Debug, Clone)]
17pub enum Value {
18    Str(String),
19    Number(f64),
20    Bool(bool),
21    Unit,
22    List(Vec<Value>),
23    Ok(Box<Value>),
24    Err(Box<Value>),
25    Fn(Vec<String>, Vec<Stmt>, Env),
26}
27
28type Env = HashMap<String, Value>;
29
30impl std::fmt::Display for Value {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            Value::Str(s)    => write!(f, "{s}"),
34            Value::Number(n) => {
35                if n.fract() == 0.0 && n.abs() < 1e15 { write!(f, "{}", *n as i64) }
36                else { write!(f, "{n}") }
37            }
38            Value::Bool(b)   => write!(f, "{b}"),
39            Value::Unit      => write!(f, "()"),
40            Value::List(v)   => {
41                write!(f, "[")?;
42                for (i, x) in v.iter().enumerate() {
43                    if i > 0 { write!(f, ", ")?; }
44                    write!(f, "{x}")?;
45                }
46                write!(f, "]")
47            }
48            Value::Ok(v)     => write!(f, "Ok({v})"),
49            Value::Err(v)    => write!(f, "Err({v})"),
50            Value::Fn(_, _, _) => write!(f, "<fn>"),
51        }
52    }
53}
54
55// ─── Control flow ────────────────────────────────────────────────────────────
56
57#[derive(Debug)]
58enum EvalErr {
59    Runtime(String),
60    Return(Value),
61    #[allow(dead_code)] // reserved for future `break` statement support
62    Break,
63}
64
65impl From<String> for EvalErr {
66    fn from(s: String) -> Self { EvalErr::Runtime(s) }
67}
68
69type EvalResult = Result<Value, EvalErr>;
70
71// GfxState is now defined in crate::gfx — see src/gfx/mod.rs.
72
73// ─── SVG writer ───────────────────────────────────────────────────────────────
74
75struct SvgWriter {
76    path:     String,
77    width:    f64,
78    height:   f64,
79    elements: Vec<String>,
80}
81
82impl SvgWriter {
83    fn new(path: String, width: f64, height: f64) -> Self {
84        let bg = format!(
85            "<rect width=\"{width}\" height=\"{height}\" fill=\"#0a0a0a\"/>"
86        );
87        Self { path, width, height, elements: vec![bg] }
88    }
89
90    fn save(&self) -> std::io::Result<()> {
91        let w = self.width;
92        let h = self.height;
93        let mut out = format!(
94            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
95             <svg xmlns=\"http://www.w3.org/2000/svg\" \
96             width=\"{w}\" height=\"{h}\" viewBox=\"0 0 {w} {h}\">\n"
97        );
98        for elem in &self.elements {
99            out.push_str("  ");
100            out.push_str(elem);
101            out.push('\n');
102        }
103        out.push_str("</svg>\n");
104        // Create parent directory if it doesn't exist.
105        if let Some(parent) = std::path::Path::new(&self.path).parent() {
106            if !parent.as_os_str().is_empty() {
107                let _ = std::fs::create_dir_all(parent);
108            }
109        }
110        std::fs::write(&self.path, out.as_bytes())
111    }
112}
113
114fn hsl_to_hex(h: f64, s: f64, l: f64) -> String {
115    let s = s / 100.0;
116    let l = l / 100.0;
117    let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
118    let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
119    let m = l - c / 2.0;
120    let (r1, g1, b1) = if h < 60.0       { (c, x, 0.0) }
121                       else if h < 120.0  { (x, c, 0.0) }
122                       else if h < 180.0  { (0.0, c, x) }
123                       else if h < 240.0  { (0.0, x, c) }
124                       else if h < 300.0  { (x, 0.0, c) }
125                       else               { (c, 0.0, x) };
126    let r = ((r1 + m) * 255.0).round() as u8;
127    let g = ((g1 + m) * 255.0).round() as u8;
128    let b = ((b1 + m) * 255.0).round() as u8;
129    format!("#{r:02x}{g:02x}{b:02x}")
130}
131
132// ─── Procedural texture helpers ───────────────────────────────────────────────
133
134fn tex_hash(x: i32, y: i32, seed: u32) -> f32 {
135    let mut h = (x as u32).wrapping_add((y as u32).wrapping_mul(2654435769)).wrapping_add(seed.wrapping_mul(1234567891));
136    h ^= h >> 16; h = h.wrapping_mul(0x45d9f3b); h ^= h >> 16;
137    h as f32 / u32::MAX as f32
138}
139
140fn tex_vnoise(x: f32, y: f32, seed: u32) -> f32 {
141    let xi = x.floor() as i32; let yi = y.floor() as i32;
142    let sm = |t: f32| t * t * (3.0 - 2.0 * t);
143    let xf = sm(x - xi as f32); let yf = sm(y - yi as f32);
144    let a = tex_hash(xi, yi, seed); let b = tex_hash(xi+1, yi, seed);
145    let c = tex_hash(xi, yi+1, seed); let d = tex_hash(xi+1, yi+1, seed);
146    a + (b-a)*xf + (c-a)*yf + (a-b-c+d)*xf*yf
147}
148
149fn tex_fbm(x: f32, y: f32, octaves: u32, seed: u32) -> f32 {
150    let mut v = 0.0f32; let mut amp = 0.5f32; let mut f = 1.0f32;
151    for i in 0..octaves {
152        v += tex_vnoise(x * f, y * f, seed.wrapping_add(i * 7919)) * amp;
153        amp *= 0.5; f *= 2.0;
154    }
155    v
156}
157
158fn tex_palette(name: &str, t: f32) -> [f32; 3] {
159    let (a, b, c, d): ([f32;3],[f32;3],[f32;3],[f32;3]) = match name {
160        "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]),
161        "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]),
162        "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]),
163        "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]),
164        "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]),
165        _             => ([0.5,0.5,0.5],[0.5,0.5,0.5],[1.0,1.0,1.0],[0.0,0.333,0.667]),
166    };
167    [0,1,2].map(|i| (a[i] + b[i] * (std::f32::consts::TAU * (c[i] * t + d[i])).cos()).clamp(0.0, 1.0))
168}
169
170fn tex_rgb(r: f32, g: f32, b: f32) -> u32 {
171    ((r * 255.0) as u32) << 16 | ((g * 255.0) as u32) << 8 | (b * 255.0) as u32
172}
173
174// ─── Interpreter ─────────────────────────────────────────────────────────────
175
176pub struct Interpreter {
177    globals:   HashMap<String, Expr>,
178    functions: HashMap<String, FnDef>,
179    _modules:  HashMap<String, Vec<FnDef>>,
180    gfx:       RefCell<GfxState>,
181    svg:       RefCell<Option<SvgWriter>>,
182    /// Directory of the primary source file, for relative `use` resolution.
183    pub source_dir: Option<std::path::PathBuf>,
184    /// Files already loaded — prevents circular imports.
185    loaded_files: std::collections::HashSet<String>,
186    /// Optional audio engine — `None` if no audio device is available.
187    #[cfg(not(target_arch = "wasm32"))]
188    audio:     Option<AudioEngine>,
189    #[cfg(not(target_arch = "wasm32"))]
190    fft:       RefCell<FftAnalyzer>,
191    fft_bands_cache: RefCell<Vec<f32>>,
192}
193
194impl Interpreter {
195    pub fn new() -> Self {
196        #[cfg(not(target_arch = "wasm32"))]
197        let audio = AudioEngine::new()
198            .map_err(|e| eprintln!("audio init failed (no sound): {e}"))
199            .ok();
200        Self {
201            globals:   HashMap::new(),
202            functions: HashMap::new(),
203            _modules:  HashMap::new(),
204            gfx:       RefCell::new(GfxState::new()),
205            svg:       RefCell::new(None),
206            source_dir: None,
207            loaded_files: std::collections::HashSet::new(),
208            #[cfg(not(target_arch = "wasm32"))]
209            audio,
210            #[cfg(not(target_arch = "wasm32"))]
211            fft: RefCell::new(FftAnalyzer::new(2048, 44100)),
212            fft_bands_cache: RefCell::new(vec![]),
213        }
214    }
215
216    pub fn run_program(&mut self, program: &Program) -> Result<(), String> {
217        for item in &program.items {
218            self.register_item("", item)?;
219        }
220        let entry = self.find_entry()
221            .ok_or("no entry point — need `bind start = do {...}` or `ผูก เริ่ม = ทำ {...}`")?;
222        // Seed the entry env with non-Do globals so top-level `令` bindings
223        // are visible in the main Do block (same two-pass logic as call_named).
224        let mut env = Env::new();
225        let non_do: Vec<_> = self.globals.iter()
226            .filter(|(_, e)| !matches!(e, Expr::Do(_)))
227            .map(|(k, e)| (k.clone(), e.clone()))
228            .collect();
229        let mut pending: Vec<(String, Expr)> = Vec::new();
230        for (k, expr) in &non_do {
231            let mut tmp = Env::new();
232            if let Ok(v) = self.eval_expr(expr, &mut tmp) {
233                env.insert(k.clone(), v);
234            } else {
235                pending.push((k.clone(), expr.clone()));
236            }
237        }
238        for (k, expr) in &pending {
239            let mut tmp = env.clone();
240            if let Ok(v) = self.eval_expr(expr, &mut tmp) {
241                env.insert(k.clone(), v);
242            }
243        }
244        self.eval_expr(&entry, &mut env).map(|_| ()).map_err(|e| match e {
245            EvalErr::Runtime(s) => s,
246            EvalErr::Return(_)  => "unexpected top-level return".to_string(),
247            EvalErr::Break      => "unexpected break at top level".to_string(),
248        })
249    }
250
251    fn register_item(&mut self, ns: &str, item: &Item) -> Result<(), String> {
252        match item {
253            Item::Bind(name, expr) => {
254                let key = if ns.is_empty() { name.clone() } else { format!("{ns}::{name}") };
255                self.globals.insert(key, expr.clone());
256            }
257            Item::Fn(def) => {
258                let key = if ns.is_empty() { def.name.clone() } else { format!("{ns}::{}", def.name) };
259                self.functions.insert(key, def.clone());
260            }
261            Item::Mod(name, body) => {
262                let child_ns = if ns.is_empty() { name.clone() } else { format!("{ns}::{name}") };
263                for child in body {
264                    self.register_item(&child_ns, child)?;
265                }
266            }
267            Item::TypeAlias(_, _) => {}
268            Item::Use { path, alias } => {
269                self.load_module(path, alias.as_deref(), ns)?;
270            }
271        }
272        Ok(())
273    }
274
275    /// Resolve `path` relative to `source_dir`, load and parse it, then
276    /// register all its definitions.  If `alias` is given, every name is
277    /// prefixed with `<parent_ns>::<alias>`.  Circular imports are silently
278    /// skipped.
279    fn load_module(&mut self, path: &str, alias: Option<&str>, parent_ns: &str) -> Result<(), String> {
280        // Build candidate file paths (.ling extension variants)
281        let base_dir = self.source_dir.clone().unwrap_or_else(|| std::path::PathBuf::from("."));
282        let raw = std::path::Path::new(path);
283        let candidates: Vec<std::path::PathBuf> = vec![
284            base_dir.join(format!("{}.ling", path)),
285            base_dir.join(format!("{}.灵", path)),
286            base_dir.join(format!("{}.령", path)),
287            base_dir.join(format!("{}.霊", path)),
288            base_dir.join(format!("{}.ลิง", path)),
289            // exact path if already has extension
290            base_dir.join(raw),
291            std::path::PathBuf::from(format!("{}.ling", path)),
292            std::path::PathBuf::from(path),
293        ];
294
295        let resolved = candidates.into_iter().find(|p| p.exists())
296            .ok_or_else(|| format!("use: cannot find module '{path}'"))?;
297
298        let canonical = resolved.canonicalize()
299            .unwrap_or_else(|_| resolved.clone())
300            .to_string_lossy()
301            .to_string();
302
303        // Skip if already loaded (circular import guard)
304        if self.loaded_files.contains(&canonical) {
305            return Ok(());
306        }
307        self.loaded_files.insert(canonical.clone());
308
309        let source = std::fs::read_to_string(&resolved)
310            .map_err(|e| format!("use: failed to read '{path}': {e}"))?;
311
312        // Save/restore source_dir for nested relative imports
313        let prev_dir = self.source_dir.clone();
314        self.source_dir = resolved.parent().map(|p| p.to_path_buf());
315
316        let program = crate::parser::parse(&source)
317            .map_err(|e| format!("use: parse error in '{path}': {e}"))?;
318
319        // Compute target namespace: parent_ns :: alias (or just alias, or just parent_ns)
320        let target_ns = match (parent_ns.is_empty(), alias) {
321            (_, Some(a)) if !parent_ns.is_empty() => format!("{parent_ns}::{a}"),
322            (_, Some(a)) => a.to_string(),
323            (false, None) => parent_ns.to_string(),
324            (true,  None) => String::new(),
325        };
326
327        for item in &program.items {
328            self.register_item(&target_ns, item)?;
329        }
330
331        self.source_dir = prev_dir;
332        Ok(())
333    }
334
335    fn find_entry(&self) -> Option<Expr> {
336        // Try all known entry-point names in multiple human languages
337        for key in &[
338            "start", "main",
339            "启",
340            "เริ่ม",           // Thai
341            "시작",
342            "начать", "начало",
343            "inicio", "comenzar",
344            "début", "commencer",
345            "anfang", "starten",
346            "início",
347            "शुरू",
348            "ابدأ",
349        ] {
350            if let Some(e) = self.globals.get(*key) { return Some(e.clone()); }
351        }
352        self.globals.values().find(|e| matches!(e, Expr::Do(_))).cloned()
353    }
354
355    // ─── Expression evaluation ────────────────────────────────────────────────
356
357    fn eval_expr(&self, expr: &Expr, env: &mut Env) -> EvalResult {
358        match expr {
359            Expr::Str(s)    => Ok(Value::Str(s.clone())),
360            Expr::Number(n) => Ok(Value::Number(*n)),
361            Expr::Bool(b)   => Ok(Value::Bool(*b)),
362            Expr::Unit      => Ok(Value::Unit),
363            Expr::Array(elems) => {
364                let vs: Vec<_> = elems.iter()
365                    .map(|e| self.eval_expr(e, env))
366                    .collect::<Result<_,_>>()?;
367                Ok(Value::List(vs))
368            }
369
370            Expr::Ident(name) => self.lookup(name, env),
371
372            Expr::Path(segs) => {
373                if segs.len() == 1 { return self.lookup(&segs[0], env); }
374                Ok(Value::Str(segs.join("::")))
375            }
376
377            Expr::Ref(inner) => self.eval_expr(inner, env),
378            Expr::Await(inner) => self.eval_expr(inner, env),
379
380            Expr::Do(stmts) => {
381                let mut local = env.clone();
382                Ok(self.exec_block(stmts, &mut local)?.unwrap_or(Value::Unit))
383            }
384
385            Expr::BinOp(op, lhs, rhs) => {
386                let l = self.eval_expr(lhs, env)?;
387                let r = self.eval_expr(rhs, env)?;
388                self.apply_binop(op, l, r)
389            }
390
391            Expr::If { cond, then, elseifs, else_body } => {
392                if self.is_truthy(&self.eval_expr(cond, env)?) {
393                    return Ok(self.exec_block(then, env)?.unwrap_or(Value::Unit));
394                }
395                for (ei_cond, ei_body) in elseifs {
396                    if self.is_truthy(&self.eval_expr(ei_cond, env)?) {
397                        return Ok(self.exec_block(ei_body, env)?.unwrap_or(Value::Unit));
398                    }
399                }
400                if let Some(eb) = else_body {
401                    return Ok(self.exec_block(eb, env)?.unwrap_or(Value::Unit));
402                }
403                Ok(Value::Unit)
404            }
405
406            Expr::While { cond, body } => {
407                // Run the body directly in the *outer* env so that
408                // `bind counter = counter + 1` persists across iterations,
409                // which is the expected behaviour in a scripting language.
410                loop {
411                    let cv = self.eval_expr(cond, env)?;
412                    if !self.is_truthy(&cv) { break; }
413                    match self.exec_block(body, env) {
414                        Ok(_) => {}
415                        Err(EvalErr::Break) => break,
416                        Err(e) => return Err(e),
417                    }
418                }
419                Ok(Value::Unit)
420            }
421
422            Expr::For { var, iter, body } => {
423                let iter_val = self.eval_expr(iter, env)?;
424                let items = self.value_to_iter(iter_val)?;
425                for item in items {
426                    let mut local = env.clone();
427                    local.insert(var.clone(), item);
428                    match self.exec_block(body, &mut local) {
429                        Ok(_) => {}
430                        Err(EvalErr::Break) => break,
431                        Err(e) => return Err(e),
432                    }
433                }
434                Ok(Value::Unit)
435            }
436
437            Expr::Match(subject, arms) => {
438                let subj = self.eval_expr(subject, env)?;
439                for arm in arms {
440                    if let Some(bindings) = self.match_pattern(&arm.pattern, &subj) {
441                        let mut local = env.clone();
442                        local.extend(bindings);
443                        return self.eval_expr(&arm.body, &mut local);
444                    }
445                }
446                Ok(Value::Unit)
447            }
448
449            Expr::Range(lo, hi) => {
450                let lo_v = self.eval_expr(lo, env)?;
451                let hi_v = self.eval_expr(hi, env)?;
452                let lo_n = self.to_number(&lo_v)? as i64;
453                let hi_n = self.to_number(&hi_v)? as i64;
454                Ok(Value::List((lo_n..hi_n).map(|i| Value::Number(i as f64)).collect()))
455            }
456
457            Expr::Index(base, idx) => {
458                let b = self.eval_expr(base, env)?;
459                let i = self.eval_expr(idx, env)?;
460                let n = self.to_number(&i)? as usize;
461                match b {
462                    Value::List(v) => v.get(n).cloned()
463                        .ok_or_else(|| EvalErr::from(format!("index {n} out of bounds"))),
464                    Value::Str(s)  => s.chars().nth(n)
465                        .map(|c| Value::Str(c.to_string()))
466                        .ok_or_else(|| EvalErr::from(format!("index {n} out of bounds"))),
467                    other => Err(EvalErr::from(format!("cannot index {:?}", other))),
468                }
469            }
470
471            Expr::Call(callee, args) => {
472                let arg_vals: Vec<Value> = args.iter()
473                    .map(|a| self.eval_expr(a, env))
474                    .collect::<Result<_,_>>()?;
475                match callee.as_ref() {
476                    Expr::Ident(name) => self.call_named(name, arg_vals, env),
477                    Expr::Path(segs)  => self.call_named(&segs.join("::"), arg_vals, env),
478                    _ => {
479                        let v = self.eval_expr(callee, env)?;
480                        self.call_value(v, arg_vals)
481                    }
482                }
483            }
484
485            Expr::MethodCall { receiver, method, args } => {
486                let recv = self.eval_expr(receiver, env)?;
487                let arg_vals: Vec<Value> = args.iter()
488                    .map(|a| self.eval_expr(a, env))
489                    .collect::<Result<_,_>>()?;
490                self.call_method(recv, method, arg_vals)
491            }
492
493            Expr::Closure(params, body) => {
494                Ok(Value::Fn(params.clone(), vec![Stmt::Expr(*body.clone())], env.clone()))
495            }
496        }
497    }
498
499    // ─── Block execution ─────────────────────────────────────────────────────
500
501    fn exec_block(&self, stmts: &[Stmt], env: &mut Env) -> Result<Option<Value>, EvalErr> {
502        let mut last: Option<Value> = None;
503        for stmt in stmts {
504            match stmt {
505                Stmt::Bind(name, expr) => {
506                    let v = self.eval_expr(expr, env)?;
507                    env.insert(name.clone(), v);
508                    last = None;
509                }
510                Stmt::Return(expr) => {
511                    let v = self.eval_expr(expr, env)?;
512                    return Err(EvalErr::Return(v));
513                }
514                Stmt::Expr(expr) => {
515                    last = Some(self.eval_expr(expr, env)?);
516                }
517            }
518        }
519        Ok(last)
520    }
521
522    // ─── Dispatch helpers ─────────────────────────────────────────────────────
523
524    fn lookup(&self, name: &str, env: &Env) -> EvalResult {
525        if let Some(v) = env.get(name) { return Ok(v.clone()); }
526        if self.functions.contains_key(name) {
527            let def = &self.functions[name];
528            return Ok(Value::Fn(def.params.clone(), def.body.clone(), Env::new()));
529        }
530        // Math constants usable as plain identifiers (e.g. `sin(pi)`)
531        match name {
532            "pi" | "π" | "พาย" => return Ok(Value::Number(std::f64::consts::PI)),
533            "tau" | "τ"        => return Ok(Value::Number(std::f64::consts::TAU)),
534            _ => {}
535        }
536        Err(EvalErr::from(format!("undefined: '{name}'")))
537    }
538
539    fn call_named(&self, name: &str, args: Vec<Value>, env: &Env) -> EvalResult {
540        match name {
541            // ── Print ──
542            "print" | "println" | "印" | "พิมพ์" | "출력" | "вывести" | "imprimir" | "afficher" => {
543                let s = args.iter().map(|v| v.to_string()).collect::<Vec<_>>().join("");
544                println!("{s}");
545                return Ok(Value::Unit);
546            }
547            // ── Format ──
548            "format" | "格式" | "รูปแบบ" | "форматировать" | "formatear" | "formater" => {
549                return Ok(Value::Str(self.builtin_format(&args)?));
550            }
551            // ── String join / concatenation ──
552            "格式::拼接" | "format::join" => {
553                match args.first() {
554                    Some(Value::List(items)) => {
555                        return Ok(Value::Str(items.iter().map(|v| v.to_string()).collect()));
556                    }
557                    _ => return Ok(Value::Str(self.builtin_format(&args)?)),
558                }
559            }
560            // ── Result constructors ──
561            "ok" | "好" => {
562                let val = args.into_iter().next().unwrap_or(Value::Unit);
563                return Ok(Value::Ok(Box::new(val)));
564            }
565            "bad" | "坏" | "err" => {
566                let val = args.into_iter().next().unwrap_or(Value::Unit);
567                return Ok(Value::Err(Box::new(val)));
568            }
569            // ── Vec constructors ──
570            "向量::从" | "Vec::from" => {
571                if let Some(Value::List(v)) = args.first() {
572                    return Ok(Value::List(v.clone()));
573                }
574                return Ok(Value::List(args));
575            }
576            "向量::有容量" | "Vec::with_capacity" => return Ok(Value::List(Vec::new())),
577            // ── Timer stubs ──
578            "计时::获取当前小时" | "Timer::hour" => return Ok(Value::Number(14.0)),
579            "计时::现在" | "Timer::now"          => return Ok(Value::Number(1000.0)),
580            // ── Sleep ──
581            "sleep" | "หยุด" | "sleep_ms" | "流水::睡眠" | "Flow::sleep" => {
582                if let Some(ms_val) = args.first() {
583                    if let Ok(ms) = self.to_number(ms_val) {
584                        std::thread::sleep(std::time::Duration::from_millis(ms as u64));
585                    }
586                }
587                return Ok(Value::Unit);
588            }
589            // ── Flow::parallel stub ──
590            "流水::并行" | "Flow::parallel" => {
591                if let Some(Value::Fn(params, body, mut cap)) = args.first().cloned() {
592                    let _ = params;
593                    match self.exec_block(&body, &mut cap) {
594                        Ok(Some(v)) => return Ok(v),
595                        Ok(None) => return Ok(Value::Unit),
596                        Err(EvalErr::Return(v)) => return Ok(v),
597                        Err(e) => return Err(e),
598                    }
599                }
600                return Ok(Value::Unit);
601            }
602
603            // ══════════════════════════════════════════════════════════════════
604            // MATH BUILTINS  (all args and results are f64)
605            // Thai aliases: ไซน์ โคไซน์ แทนเจนต์ รากที่สอง ค่าสัมบูรณ์
606            //               ปัดลง ปัดขึ้น ปัดเศษ ตัดทศนิยม ต่ำสุด สูงสุด
607            //               จำกัด ยกกำลัง ลอการิทึม พาย
608            // ══════════════════════════════════════════════════════════════════
609
610            // ── Trigonometry (input in radians) ──
611            "sin" | "ไซน์" => {
612                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.sin()));
613            }
614            "cos" | "โคไซน์" => {
615                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.cos()));
616            }
617
618            // ── Hyperbolic functions ──
619            // Hyperbolic tangent
620            "tanh" | "tanhf" => {
621                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.tanh()));
622            }
623
624
625            "tan" | "แทนเจนต์" => {
626                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.tan()));
627            }
628            "asin" | "arcsin" => {
629                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.asin()));
630            }
631            "acos" | "arccos" => {
632                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.acos()));
633            }
634            "atan" | "arctan" => {
635                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.atan()));
636            }
637            "atan2" | "arctan2" => {
638                let y = self.arg_num(&args, 0, 0.0)?;
639                let x = self.arg_num(&args, 1, 1.0)?;
640                return Ok(Value::Number(y.atan2(x)));
641            }
642
643            // ── Roots / powers ──
644            "sqrt" | "รากที่สอง" => {
645                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.sqrt()));
646            }
647            "cbrt" => {
648                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.cbrt()));
649            }
650            "pow" | "ยกกำลัง" => {
651                let base = self.arg_num(&args, 0, 0.0)?;
652                let exp  = self.arg_num(&args, 1, 1.0)?;
653                return Ok(Value::Number(base.powf(exp)));
654            }
655            "exp" => {
656                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.exp()));
657            }
658            "hypot" => {
659                let x = self.arg_num(&args, 0, 0.0)?;
660                let y = self.arg_num(&args, 1, 0.0)?;
661                return Ok(Value::Number(x.hypot(y)));
662            }
663
664            // ── Logarithms ──
665            "ln" | "log" | "ลอการิทึม" => {
666                return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.ln()));
667            }
668            "log2" => {
669                return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.log2()));
670            }
671            "log10" => {
672                return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.log10()));
673            }
674
675            // ── Rounding / truncation ──
676            "abs" | "ค่าสัมบูรณ์" => {
677                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.abs()));
678            }
679            "floor" | "ปัดลง" => {
680                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.floor()));
681            }
682            "ceil" | "ปัดขึ้น" => {
683                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.ceil()));
684            }
685            "round" | "ปัดเศษ" => {
686                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.round()));
687            }
688            "trunc" | "int" | "ตัดทศนิยม" => {
689                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.trunc()));
690            }
691            "fract" => {
692                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.fract()));
693            }
694
695            // ── min / max / clamp ──
696            "min" | "ต่ำสุด" => {
697                let a = self.arg_num(&args, 0, 0.0)?;
698                let b = self.arg_num(&args, 1, 0.0)?;
699                return Ok(Value::Number(a.min(b)));
700            }
701            "max" | "สูงสุด" => {
702                let a = self.arg_num(&args, 0, 0.0)?;
703                let b = self.arg_num(&args, 1, 0.0)?;
704                return Ok(Value::Number(a.max(b)));
705            }
706            "clamp" | "จำกัด" => {
707                let x  = self.arg_num(&args, 0, 0.0)?;
708                let lo = self.arg_num(&args, 1, 0.0)?;
709                let hi = self.arg_num(&args, 2, 1.0)?;
710                return Ok(Value::Number(x.clamp(lo, hi)));
711            }
712
713            // ── Constants (also accessible as plain identifiers via lookup) ──
714            "pi" | "π" | "พาย" => return Ok(Value::Number(std::f64::consts::PI)),
715            "tau" | "τ"        => return Ok(Value::Number(std::f64::consts::TAU)),
716
717            // ══════════════════════════════════════════════════════════════════
718            // GRAPHICS BUILTINS
719            // Thai names first, then English aliases.
720            // ══════════════════════════════════════════════════════════════════
721
722            // ── เปิดหน้าต่าง(width, height, title) — open_window ──
723            "เปิดหน้าต่าง" | "open_window" | "gfx_window" => {
724                let w = self.arg_num(&args, 0, 800.0)? as usize;
725                let h = self.arg_num(&args, 1, 600.0)? as usize;
726                #[cfg(not(target_arch = "wasm32"))]
727                {
728                    let title = args.get(2).map(|v| v.to_string()).unwrap_or_else(|| "Ling".into());
729                    let mut gfx = self.gfx.borrow_mut();
730                    let mut win = minifb::Window::new(
731                        &title, w, h,
732                        minifb::WindowOptions {
733                            resize: false,
734                            scale: minifb::Scale::X1,
735                            ..Default::default()
736                        },
737                    ).map_err(|e| EvalErr::from(format!("cannot open window: {e}")))?;
738                    #[allow(deprecated)]
739                    win.limit_update_rate(Some(std::time::Duration::from_millis(8)));
740                    gfx.buffer = vec![0u32; w * h];
741                    gfx.width  = w;
742                    gfx.height = h;
743                    gfx.window = Some(win);
744                    gfx.sync_projection();
745                    hide_console_window();
746                }
747                #[cfg(target_arch = "wasm32")]
748                {
749                    let mut gfx = self.gfx.borrow_mut();
750                    gfx.width  = w;
751                    gfx.height = h;
752                    gfx.sync_projection();
753                    crate::gfx::webgl::resize(w as u32, h as u32);
754                }
755                return Ok(Value::Unit);
756            }
757
758            // ── เติม(r, g, b) — fill / clear screen with colour ──
759            "เติม" | "fill" | "gfx_fill" | "clear" => {
760                let r = self.arg_num(&args, 0, 0.0)? as u32;
761                let g = self.arg_num(&args, 1, 0.0)? as u32;
762                let b = self.arg_num(&args, 2, 0.0)? as u32;
763                #[cfg(not(target_arch = "wasm32"))]
764                {
765                    let c = (r << 16) | (g << 8) | b;
766                    self.gfx.borrow_mut().buffer.fill(c);
767                }
768                #[cfg(target_arch = "wasm32")]
769                {
770                    let mut gfx = self.gfx.borrow_mut();
771                    gfx.fill_r = r as f32 / 255.0;
772                    gfx.fill_g = g as f32 / 255.0;
773                    gfx.fill_b = b as f32 / 255.0;
774                }
775                return Ok(Value::Unit);
776            }
777
778            // ── set_color_hsl(h, s, l) — set drawing colour from HSL ──
779            // h: 0–360 degrees, s: 0–100 saturation, l: 0–100 lightness
780            "set_color_hsl" | "颜色HSL" | "สีHSLวาด" => {
781                let h = self.arg_num(&args, 0, 0.0)?;
782                let s = self.arg_num(&args, 1, 70.0)?;
783                let l = self.arg_num(&args, 2, 50.0)?;
784                let hex = hsl_to_hex(h, s, l);
785                let r = u32::from_str_radix(&hex[1..3], 16).unwrap_or(255);
786                let g = u32::from_str_radix(&hex[3..5], 16).unwrap_or(255);
787                let b = u32::from_str_radix(&hex[5..7], 16).unwrap_or(255);
788                self.gfx.borrow_mut().color = (r << 16) | (g << 8) | b;
789                return Ok(Value::Unit);
790            }
791
792            // ── สีดินสอ(r, g, b) — set drawing colour ──
793            "สีดินสอ" | "set_color" | "gfx_color" | "color" => {
794                let r = self.arg_num(&args, 0, 255.0)? as u32;
795                let g = self.arg_num(&args, 1, 255.0)? as u32;
796                let b = self.arg_num(&args, 2, 255.0)? as u32;
797                self.gfx.borrow_mut().color = (r << 16) | (g << 8) | b;
798                return Ok(Value::Unit);
799            }
800
801            // ── วาดสามเหลี่ยม(x1,y1, x2,y2, x3,y3) — draw filled triangle ──
802            "วาดสามเหลี่ยม" | "draw_triangle" | "gfx_triangle" | "triangle" => {
803                let x0 = self.arg_num(&args, 0, 0.0)? as f32;
804                let y0 = self.arg_num(&args, 1, 0.0)? as f32;
805                let x1 = self.arg_num(&args, 2, 0.0)? as f32;
806                let y1 = self.arg_num(&args, 3, 0.0)? as f32;
807                let x2 = self.arg_num(&args, 4, 0.0)? as f32;
808                let y2 = self.arg_num(&args, 5, 0.0)? as f32;
809                let mut gfx = self.gfx.borrow_mut();
810                let color = gfx.color;
811                #[cfg(not(target_arch = "wasm32"))]
812                {
813                    let w = gfx.width;
814                    let h = gfx.height;
815                    fill_triangle(&mut gfx.buffer, w, h, color, x0, y0, x1, y1, x2, y2);
816                }
817                #[cfg(target_arch = "wasm32")]
818                gfx.depth_queue.push_triangle(0.0, color, x0, y0, x1, y1, x2, y2);
819                return Ok(Value::Unit);
820            }
821
822            // ── วาดเส้น(x1,y1, x2,y2) — draw line ──
823            "วาดเส้น" | "draw_line" | "gfx_line" | "line" => {
824                let x0 = self.arg_num(&args, 0, 0.0)? as f32;
825                let y0 = self.arg_num(&args, 1, 0.0)? as f32;
826                let x1 = self.arg_num(&args, 2, 0.0)? as f32;
827                let y1 = self.arg_num(&args, 3, 0.0)? as f32;
828                let mut gfx = self.gfx.borrow_mut();
829                let color = gfx.color;
830                #[cfg(not(target_arch = "wasm32"))]
831                {
832                    let w = gfx.width;
833                    let h = gfx.height;
834                    draw_line(&mut gfx.buffer, w, h, color, x0, y0, x1, y1);
835                }
836                #[cfg(target_arch = "wasm32")]
837                gfx.depth_queue.push_line(0.0, color, x0, y0, x1, y1);
838                return Ok(Value::Unit);
839            }
840
841            // ── วาดจุด(x, y) — plot a single pixel ──
842            "วาดจุด" | "draw_pixel" | "gfx_pixel" | "pixel" => {
843                let px = self.arg_num(&args, 0, 0.0)? as i32;
844                let py = self.arg_num(&args, 1, 0.0)? as i32;
845                #[cfg(not(target_arch = "wasm32"))]
846                {
847                    let mut gfx = self.gfx.borrow_mut();
848                    let color = gfx.color;
849                    let w = gfx.width;
850                    let h = gfx.height;
851                    if px >= 0 && py >= 0 && (px as usize) < w && (py as usize) < h {
852                        gfx.buffer[py as usize * w + px as usize] = color;
853                    }
854                }
855                #[cfg(target_arch = "wasm32")]
856                {
857                    // Render pixel as a 1×1 square via two triangles.
858                    let mut gfx = self.gfx.borrow_mut();
859                    let color = gfx.color;
860                    let x = px as f32; let y = py as f32;
861                    gfx.depth_queue.push_triangle(0.0, color, x, y, x+1.0, y, x+1.0, y+1.0);
862                    gfx.depth_queue.push_triangle(0.0, color, x, y, x+1.0, y+1.0, x, y+1.0);
863                }
864                return Ok(Value::Unit);
865            }
866
867            // ── แสดงผล() — flush depth queue, then present frame to screen ──
868            "แสดงผล" | "present" | "gfx_present" | "show" => {
869                #[cfg(not(target_arch = "wasm32"))]
870                {
871                    // Flush depth queue and present — release borrow before reading mouse.
872                    {
873                        let mut gfx = self.gfx.borrow_mut();
874                        if !gfx.depth_queue.is_empty() {
875                            let w = gfx.width;
876                            let h = gfx.height;
877                            let queue = std::mem::take(&mut gfx.depth_queue);
878                            queue.flush(&mut gfx.buffer, w, h);
879                        }
880                        let buf = gfx.buffer.clone();
881                        let w   = gfx.width;
882                        let h   = gfx.height;
883                        if let Some(win) = gfx.window.as_mut() {
884                            win.update_with_buffer(&buf, w, h)
885                                .map_err(|e| EvalErr::from(format!("present error: {e}")))?;
886                        }
887                    }
888                    // Read mouse AFTER update_with_buffer so events are processed.
889                    let mouse_pos = {
890                        let gfx = self.gfx.borrow();
891                        gfx.window.as_ref()
892                            .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
893                    };
894                    let mut gfx = self.gfx.borrow_mut();
895                    if gfx.mouse_captured {
896                        if let Some((mx, my)) = mouse_pos {
897                            if gfx.last_mx.is_nan() {
898                                gfx.mouse_dx = 0.0; gfx.mouse_dy = 0.0;
899                            } else {
900                                gfx.mouse_dx = mx - gfx.last_mx;
901                                gfx.mouse_dy = my - gfx.last_my;
902                            }
903                            gfx.last_mx = mx; gfx.last_my = my;
904                        } else {
905                            gfx.mouse_dx = 0.0; gfx.mouse_dy = 0.0;
906                        }
907                        // Clip cursor to window bounds so it can't escape.
908                        #[cfg(windows)]
909                        unsafe {
910                            #[repr(C)]
911                            struct RECT { left: i32, top: i32, right: i32, bottom: i32 }
912                            extern "system" {
913                                fn ClipCursor(lpRect: *const std::ffi::c_void) -> i32;
914                                fn GetForegroundWindow() -> isize;
915                                fn GetWindowRect(hwnd: isize, lpRect: *mut RECT) -> i32;
916                            }
917                            let hwnd = GetForegroundWindow();
918                            let mut rect = RECT { left: 0, top: 0, right: 0, bottom: 0 };
919                            if GetWindowRect(hwnd, &mut rect) != 0 {
920                                ClipCursor(&rect as *const RECT as *const std::ffi::c_void);
921                            }
922                        }
923                    } else if let Some((mx, my)) = mouse_pos {
924                        if gfx.last_mx.is_nan() {
925                            gfx.mouse_dx = 0.0; gfx.mouse_dy = 0.0;
926                        } else {
927                            gfx.mouse_dx = mx - gfx.last_mx;
928                            gfx.mouse_dy = my - gfx.last_my;
929                        }
930                        gfx.last_mx = mx; gfx.last_my = my;
931                    } else {
932                        gfx.mouse_dx = 0.0; gfx.mouse_dy = 0.0;
933                    }
934                }
935                #[cfg(target_arch = "wasm32")]
936                {
937                    let mut gfx = self.gfx.borrow_mut();
938                    let w  = gfx.width;
939                    let h  = gfx.height;
940                    let fr = gfx.fill_r;
941                    let fg = gfx.fill_g;
942                    let fb = gfx.fill_b;
943                    let queue = std::mem::take(&mut gfx.depth_queue);
944                    queue.flush_to_webgl(fr, fg, fb, w, h);
945                }
946                return Ok(Value::Unit);
947            }
948
949            // ── เปิดหน้าต่างเต็มจอ(title) — true native-res fullscreen window ──
950            "เปิดหน้าต่างเต็มจอ" | "open_fullscreen" | "fullscreen" => {
951                // In WASM the canvas defines the viewport; use its current size
952                // as the default so the projection matches what's actually visible.
953                #[cfg(target_arch = "wasm32")]
954                let (default_w, default_h) = {
955                    let (cw, ch) = crate::gfx::webgl::canvas_size();
956                    (cw as f64, ch as f64)
957                };
958                // On native: query the actual primary monitor resolution.
959                #[cfg(all(not(target_arch = "wasm32"), windows))]
960                let (default_w, default_h) = unsafe {
961                    extern "system" { fn GetSystemMetrics(nIndex: i32) -> i32; }
962                    (GetSystemMetrics(0) as f64, GetSystemMetrics(1) as f64)
963                };
964                #[cfg(all(not(target_arch = "wasm32"), not(windows)))]
965                let (default_w, default_h) = native_screen_size();
966
967                let w = args.get(1).map(|v| self.to_number(v).unwrap_or(default_w) as usize).unwrap_or(default_w as usize);
968                let h = args.get(2).map(|v| self.to_number(v).unwrap_or(default_h) as usize).unwrap_or(default_h as usize);
969                #[cfg(not(target_arch = "wasm32"))]
970                {
971                    let title = args.get(0).map(|v| v.to_string()).unwrap_or_else(|| "Ling".into());
972                    let mut gfx = self.gfx.borrow_mut();
973                    let mut win = minifb::Window::new(
974                        &title, w, h,
975                        minifb::WindowOptions {
976                            borderless: true,
977                            title:      false,
978                            resize:     false,
979                            scale:      minifb::Scale::X1,
980                            ..Default::default()
981                        },
982                    ).map_err(|e| EvalErr::from(format!("cannot open fullscreen: {e}")))?;
983                    #[allow(deprecated)]
984                    win.limit_update_rate(Some(std::time::Duration::from_millis(8)));
985                    gfx.buffer = vec![0u32; w * h];
986                    gfx.width  = w;
987                    gfx.height = h;
988                    gfx.window = Some(win);
989                    gfx.sync_projection();
990                    // Snap to top-left, cover full screen, bring above taskbar.
991                    #[cfg(windows)]
992                    reposition_fullscreen(w as i32, h as i32);
993                    hide_console_window();
994                }
995                #[cfg(target_arch = "wasm32")]
996                {
997                    let mut gfx = self.gfx.borrow_mut();
998                    gfx.width  = w;
999                    gfx.height = h;
1000                    gfx.sync_projection();
1001                    crate::gfx::webgl::resize(w as u32, h as u32);
1002                }
1003                return Ok(Value::Unit);
1004            }
1005
1006            // ── ความกว้าง() / ความสูง() — current framebuffer size ──
1007            "get_width" | "ความกว้าง" => {
1008                return Ok(Value::Number(self.gfx.borrow().width as f64));
1009            }
1010            "get_height" | "ความสูง" => {
1011                return Ok(Value::Number(self.gfx.borrow().height as f64));
1012            }
1013
1014            // ── หน้าต่างเปิดอยู่() → bool — is the window still open? ──
1015            "หน้าต่างเปิดอยู่" | "window_is_open" | "gfx_is_open" | "is_open" => {
1016                #[cfg(not(target_arch = "wasm32"))]
1017                {
1018                    let gfx = self.gfx.borrow();
1019                    let open = gfx.window.as_ref()
1020                        .map(|w| w.is_open() && !w.is_key_down(minifb::Key::Escape))
1021                        .unwrap_or(false);
1022                    return Ok(Value::Bool(open));
1023                }
1024                #[cfg(target_arch = "wasm32")]
1025                return Ok(Value::Bool(true));
1026            }
1027
1028            // ── key_down(name) → bool — is a key held? ──
1029            "key_down" | "กดค้าง" => {
1030                #[cfg(not(target_arch = "wasm32"))]
1031                {
1032                    let name = self.arg_str(&args, 0, "");
1033                    let gfx  = self.gfx.borrow();
1034                    let down = gfx.window.as_ref()
1035                        .and_then(|w| str_to_minifb_key(&name).map(|k| w.is_key_down(k)))
1036                        .unwrap_or(false);
1037                    return Ok(Value::Bool(down));
1038                }
1039                #[cfg(target_arch = "wasm32")]
1040                return Ok(Value::Bool(false));
1041            }
1042
1043            // ── key_pressed(name) → bool — was a key pressed this frame? ──
1044            "key_pressed" | "กดปุ่ม" => {
1045                #[cfg(not(target_arch = "wasm32"))]
1046                {
1047                    let name = self.arg_str(&args, 0, "");
1048                    let gfx  = self.gfx.borrow();
1049                    let pressed = gfx.window.as_ref()
1050                        .and_then(|w| str_to_minifb_key(&name)
1051                            .map(|k| w.is_key_pressed(k, minifb::KeyRepeat::No)))
1052                        .unwrap_or(false);
1053                    return Ok(Value::Bool(pressed));
1054                }
1055                #[cfg(target_arch = "wasm32")]
1056                return Ok(Value::Bool(false));
1057            }
1058
1059            // ── mouse_dx() / mouse_dy() → f64 — delta since last frame ──
1060            "mouse_dx" | "เมาส์X" => {
1061                #[cfg(not(target_arch = "wasm32"))]
1062                return Ok(Value::Number(self.gfx.borrow().mouse_dx as f64));
1063                #[cfg(target_arch = "wasm32")]
1064                return Ok(Value::Number(0.0));
1065            }
1066            "mouse_dy" | "เมาส์Y" => {
1067                #[cfg(not(target_arch = "wasm32"))]
1068                return Ok(Value::Number(self.gfx.borrow().mouse_dy as f64));
1069                #[cfg(target_arch = "wasm32")]
1070                return Ok(Value::Number(0.0));
1071            }
1072
1073            // ── set_camera_pos(x, y, z) — move camera to world position ──
1074            "set_camera_pos" | "ตั้งตำแหน่งกล้อง" => {
1075                let x = self.arg_num(&args, 0, 0.0)? as f32;
1076                let y = self.arg_num(&args, 1, 0.0)? as f32;
1077                let z = self.arg_num(&args, 2, 0.0)? as f32;
1078                let mut gfx = self.gfx.borrow_mut();
1079                gfx.camera.tx = x; gfx.camera.ty = y; gfx.camera.tz = z;
1080                return Ok(Value::Unit);
1081            }
1082
1083            // ── move_camera(dx, dy, dz) — translate camera by delta ──
1084            "move_camera" => {
1085                let dx = self.arg_num(&args, 0, 0.0)? as f32;
1086                let dy = self.arg_num(&args, 1, 0.0)? as f32;
1087                let dz = self.arg_num(&args, 2, 0.0)? as f32;
1088                let mut gfx = self.gfx.borrow_mut();
1089                gfx.camera.tx += dx; gfx.camera.ty += dy; gfx.camera.tz += dz;
1090                return Ok(Value::Unit);
1091            }
1092
1093            // ── set_zdist(d) — set perspective z-offset (field-of-view taper) ──
1094            "set_zdist" | "ตั้งระยะห่าง" => {
1095                let d = self.arg_num(&args, 0, 5.0)? as f32;
1096                self.gfx.borrow_mut().camera.zdist = d;
1097                return Ok(Value::Unit);
1098            }
1099
1100            // ── capture_mouse() — hide cursor and warp to centre each frame ──
1101            "capture_mouse" | "จับเมาส์" => {
1102                #[cfg(not(target_arch = "wasm32"))]
1103                {
1104                    let mut gfx = self.gfx.borrow_mut();
1105                    gfx.mouse_captured = true;
1106                    gfx.last_mx = f32::NAN;
1107                    if let Some(win) = gfx.window.as_mut() {
1108                        win.set_cursor_visibility(false);
1109                    }
1110                }
1111                return Ok(Value::Unit);
1112            }
1113
1114            // ── release_mouse() — restore cursor and remove clip region ──
1115            "release_mouse" => {
1116                #[cfg(not(target_arch = "wasm32"))]
1117                {
1118                    let mut gfx = self.gfx.borrow_mut();
1119                    gfx.mouse_captured = false;
1120                    gfx.last_mx = f32::NAN;
1121                    if let Some(win) = gfx.window.as_mut() {
1122                        win.set_cursor_visibility(true);
1123                    }
1124                    #[cfg(windows)]
1125                    unsafe {
1126                        // Null releases the clip; reuse the RECT-typed declaration above.
1127                        extern "system" { fn ClipCursor(lpRect: *const std::ffi::c_void) -> i32; }
1128                        ClipCursor(std::ptr::null());
1129                    }
1130                }
1131                return Ok(Value::Unit);
1132            }
1133
1134            // ══════════════════════════════════════════════════════════════════
1135            // 3-D / 4-D DRAWING — camera, lights, depth-sorted geometry
1136            // ══════════════════════════════════════════════════════════════════
1137
1138            // ── set_camera(cry, sry, crx, srx) — store precomputed camera trig ──
1139            // Call once per frame after computing cos/sin of your rotation angles.
1140            "set_camera" | "ตั้งกล้อง" => {
1141                let cry = self.arg_num(&args, 0, 1.0)? as f32;
1142                let sry = self.arg_num(&args, 1, 0.0)? as f32;
1143                let crx = self.arg_num(&args, 2, 1.0)? as f32;
1144                let srx = self.arg_num(&args, 3, 0.0)? as f32;
1145                let mut gfx = self.gfx.borrow_mut();
1146                gfx.camera.cry = cry; gfx.camera.sry = sry;
1147                gfx.camera.crx = crx; gfx.camera.srx = srx;
1148                return Ok(Value::Unit);
1149            }
1150
1151            // ── set_projection(cx, cy, focal, zdist) — override projection params ──
1152            // Automatically set when the window opens; override only if needed.
1153            "set_projection" | "ตั้งโปรเจกชัน" => {
1154                let cx    = self.arg_num(&args, 0, 960.0)? as f32;
1155                let cy    = self.arg_num(&args, 1, 540.0)? as f32;
1156                let focal = self.arg_num(&args, 2, 1080.0)? as f32;
1157                let zdist = self.arg_num(&args, 3, 5.0)? as f32;
1158                let mut gfx = self.gfx.borrow_mut();
1159                gfx.camera.cx    = cx;
1160                gfx.camera.cy    = cy;
1161                gfx.camera.focal = focal;
1162                gfx.camera.zdist = zdist;
1163                return Ok(Value::Unit);
1164            }
1165
1166            // ── add_light(x, y, z, r, g, b, intensity, radius) ──
1167            // Adds a point light in world space.  r/g/b in [0..1].
1168            // radius == 0 → no distance falloff.
1169            "add_light" | "เพิ่มแสง" => {
1170                let x   = self.arg_num(&args, 0, 0.0)? as f32;
1171                let y   = self.arg_num(&args, 1, -3.0)? as f32;
1172                let z   = self.arg_num(&args, 2, 3.0)? as f32;
1173                let r   = self.arg_num(&args, 3, 1.0)? as f32;
1174                let g   = self.arg_num(&args, 4, 1.0)? as f32;
1175                let b   = self.arg_num(&args, 5, 1.0)? as f32;
1176                let intensity = self.arg_num(&args, 6, 1.0)? as f32;
1177                let radius    = self.arg_num(&args, 7, 0.0)? as f32;
1178                self.gfx.borrow_mut().lights.push(Light { x, y, z, r, g, b, intensity, radius });
1179                return Ok(Value::Unit);
1180            }
1181
1182            // ── clear_lights() — remove all lights ──
1183            "clear_lights" | "ล้างแสง" => {
1184                self.gfx.borrow_mut().lights.clear();
1185                return Ok(Value::Unit);
1186            }
1187
1188            // ── set_ambient(v) — ambient light level [0..1] ──
1189            "set_ambient" | "ตั้งแสงรอบข้าง" => {
1190                let v = self.arg_num(&args, 0, 0.15)? as f32;
1191                self.gfx.borrow_mut().ambient = v;
1192                return Ok(Value::Unit);
1193            }
1194
1195            // ── วาดสามเหลี่ยม3มิติ(ax,ay,az, bx,by,bz, cx,cy,cz) ──
1196            // Computes lighting from world-space normal + active lights (cel shading),
1197            // projects via the stored camera, and pushes to the depth queue.
1198            "วาดสามเหลี่ยม3มิติ" | "draw_triangle_3d" | "triangle3d" => {
1199                let ax = self.arg_num(&args, 0, 0.0)? as f32;
1200                let ay = self.arg_num(&args, 1, 0.0)? as f32;
1201                let az = self.arg_num(&args, 2, 0.0)? as f32;
1202                let bx = self.arg_num(&args, 3, 0.0)? as f32;
1203                let by = self.arg_num(&args, 4, 0.0)? as f32;
1204                let bz = self.arg_num(&args, 5, 0.0)? as f32;
1205                let cx = self.arg_num(&args, 6, 0.0)? as f32;
1206                let cy = self.arg_num(&args, 7, 0.0)? as f32;
1207                let cz = self.arg_num(&args, 8, 0.0)? as f32;
1208
1209                let mut gfx = self.gfx.borrow_mut();
1210
1211                // World-space face normal  N = (B−A) × (C−A)
1212                let ux = bx-ax; let uy = by-ay; let uz = bz-az;
1213                let vx = cx-ax; let vy = cy-ay; let vz = cz-az;
1214                let normal = [
1215                    uy*vz - uz*vy,
1216                    uz*vx - ux*vz,
1217                    ux*vy - uy*vx,
1218                ];
1219                // World-space centroid
1220                let centroid = [
1221                    (ax+bx+cx)/3.0,
1222                    (ay+by+cy)/3.0,
1223                    (az+bz+cz)/3.0,
1224                ];
1225
1226                // Cel-shaded colour
1227                let lit_color = crate::gfx::light::compute_lit_color(
1228                    gfx.color, normal, centroid, &gfx.lights, gfx.ambient,
1229                );
1230
1231                // Near-plane cull — skip any triangle that has a vertex
1232                // behind or at the camera near plane (avoids projected-to-infinity blowup).
1233                let near = -gfx.camera.zdist + 0.05;
1234                let da_raw = gfx.camera.depth(ax, ay, az);
1235                let db_raw = gfx.camera.depth(bx, by, bz);
1236                let dc_raw = gfx.camera.depth(cx, cy, cz);
1237                if da_raw <= near || db_raw <= near || dc_raw <= near {
1238                    return Ok(Value::Unit);
1239                }
1240
1241                // Project to screen
1242                let (sax, say, da) = gfx.camera.project(ax, ay, az);
1243                let (sbx, sby, db) = gfx.camera.project(bx, by, bz);
1244                let (scx, scy, dc) = gfx.camera.project(cx, cy, cz);
1245
1246                // Average camera depth (used for painter's sort)
1247                let depth = (da + db + dc) / 3.0;
1248
1249                gfx.depth_queue.push_triangle(
1250                    depth, lit_color,
1251                    sax, say, sbx, sby, scx, scy,
1252                );
1253                return Ok(Value::Unit);
1254            }
1255
1256            // ── วาดเส้น3มิติ(ax,ay,az, bx,by,bz) ──
1257            // Projects two world-space points via the stored camera and pushes
1258            // a line to the depth queue.
1259            "วาดเส้น3มิติ" | "draw_line_3d" | "line3d" => {
1260                let ax = self.arg_num(&args, 0, 0.0)? as f32;
1261                let ay = self.arg_num(&args, 1, 0.0)? as f32;
1262                let az = self.arg_num(&args, 2, 0.0)? as f32;
1263                let bx = self.arg_num(&args, 3, 0.0)? as f32;
1264                let by = self.arg_num(&args, 4, 0.0)? as f32;
1265                let bz = self.arg_num(&args, 5, 0.0)? as f32;
1266
1267                let mut gfx = self.gfx.borrow_mut();
1268                let color = gfx.color;
1269                // Near-plane clip in 3-D before perspective divide
1270                let near = -gfx.camera.zdist + 0.05;
1271                let mut lax = ax; let mut lay = ay; let mut laz = az;
1272                let mut lbx = bx; let mut lby = by; let mut lbz = bz;
1273                let da_raw = gfx.camera.depth(lax, lay, laz);
1274                let db_raw = gfx.camera.depth(lbx, lby, lbz);
1275                if da_raw <= near && db_raw <= near {
1276                    return Ok(Value::Unit);
1277                }
1278                if da_raw <= near {
1279                    let t = (near - da_raw) / (db_raw - da_raw);
1280                    lax += t * (lbx - lax);
1281                    lay += t * (lby - lay);
1282                    laz += t * (lbz - laz);
1283                } else if db_raw <= near {
1284                    let t = (near - da_raw) / (db_raw - da_raw);
1285                    lbx = lax + t * (lbx - lax);
1286                    lby = lay + t * (lby - lay);
1287                    lbz = laz + t * (lbz - laz);
1288                }
1289                let (sax, say, da) = gfx.camera.project(lax, lay, laz);
1290                let (sbx, sby, db) = gfx.camera.project(lbx, lby, lbz);
1291                let depth = (da + db) / 2.0;
1292                gfx.depth_queue.push_line(depth, color, sax, say, sbx, sby);
1293                return Ok(Value::Unit);
1294            }
1295
1296            // ══════════════════════════════════════════════════════════════════
1297            // VECTOR TEXTURE BUILTINS  (src/gfx/vtex.rs)
1298            // All patterns are depth-biased so they appear on top of surfaces.
1299            // Plane defined by: centre (cx,cy,cz) + U tangent + V tangent.
1300            // Last two args always: fr (frame f32), hue (phase offset f32).
1301            // ══════════════════════════════════════════════════════════════════
1302
1303            // vtex_grid(cx,cy,cz, ux,uy,uz, vx,vy,vz, cols,rows, cw,ch, fr,hue)
1304            "vtex_grid" | "ลายตาราง" | "纹格" | "格子模様" | "격자무늬" => {
1305                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;
1306                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;
1307                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;
1308                let cols=self.arg_num(&args,9,10.)?as usize; let rows=self.arg_num(&args,10,10.)?as usize;
1309                let cw=self.arg_num(&args,11,1.)?as f32;  let ch=self.arg_num(&args,12,1.)?as f32;
1310                let fr=self.arg_num(&args,13,0.)?as f32;  let hue=self.arg_num(&args,14,0.)?as f32;
1311                let mut gfx = self.gfx.borrow_mut();
1312                let cam = gfx.camera.clone();
1313                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);
1314                return Ok(Value::Unit);
1315            }
1316
1317            // vtex_rings(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_rings,n_sides, max_r,twist, fr,hue)
1318            "vtex_rings" | "ลายวงซ้อน" | "纹环" | "同心円" | "동심원" => {
1319                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;
1320                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;
1321                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;
1322                let nr=self.arg_num(&args,9,6.)?as usize; let ns=self.arg_num(&args,10,6.)?as usize;
1323                let mr=self.arg_num(&args,11,3.)?as f32;  let tw=self.arg_num(&args,12,0.)?as f32;
1324                let fr=self.arg_num(&args,13,0.)?as f32;  let hue=self.arg_num(&args,14,0.)?as f32;
1325                let mut gfx = self.gfx.borrow_mut();
1326                let cam = gfx.camera.clone();
1327                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);
1328                return Ok(Value::Unit);
1329            }
1330
1331            // vtex_star(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_pts,r_out,r_in, rot_speed, fr,hue)
1332            "vtex_star" | "ลายดาว" | "纹星" | "星模様" | "별무늬" => {
1333                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;
1334                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;
1335                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;
1336                let np=self.arg_num(&args,9,6.)?as usize;
1337                let ro=self.arg_num(&args,10,2.)?as f32; let ri=self.arg_num(&args,11,1.)?as f32;
1338                let rs=self.arg_num(&args,12,0.01)?as f32;
1339                let fr=self.arg_num(&args,13,0.)?as f32; let hue=self.arg_num(&args,14,0.)?as f32;
1340                let mut gfx = self.gfx.borrow_mut();
1341                let cam = gfx.camera.clone();
1342                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);
1343                return Ok(Value::Unit);
1344            }
1345
1346            // vtex_spiral(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_turns,max_r,steps, fr,hue)
1347            "vtex_spiral" | "ลายเกลียว" | "纹螺" | "螺旋" | "나선" => {
1348                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;
1349                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;
1350                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;
1351                let nt=self.arg_num(&args,9,3.)?as f32; let mr=self.arg_num(&args,10,3.)?as f32;
1352                let st=self.arg_num(&args,11,120.)?as usize;
1353                let fr=self.arg_num(&args,12,0.)?as f32; let hue=self.arg_num(&args,13,0.)?as f32;
1354                let mut gfx = self.gfx.borrow_mut();
1355                let cam = gfx.camera.clone();
1356                crate::gfx::vtex::draw_spiral(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, nt,mr,st, fr,hue);
1357                return Ok(Value::Unit);
1358            }
1359
1360            // vtex_flower(cx,cy,cz, ux,uy,uz, vx,vy,vz, radius,n_sides, fr,hue)
1361            "vtex_flower" | "ลายดอก" | "纹花" | "花模様" | "꽃무늬" => {
1362                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;
1363                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;
1364                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;
1365                let r=self.arg_num(&args,9,1.)?as f32; let ns=self.arg_num(&args,10,24.)?as usize;
1366                let fr=self.arg_num(&args,11,0.)?as f32; let hue=self.arg_num(&args,12,0.)?as f32;
1367                let mut gfx = self.gfx.borrow_mut();
1368                let cam = gfx.camera.clone();
1369                crate::gfx::vtex::draw_flower(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, r,ns, fr,hue);
1370                return Ok(Value::Unit);
1371            }
1372
1373            // vtex_letter_rain(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_cols,n_vis, col_w,row_h, speed, fr,hue)
1374            "vtex_letter_rain" | "ลายอักษรไหล" | "纹字雨" | "文字雨" | "글자비" => {
1375                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;
1376                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;
1377                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;
1378                let nc=self.arg_num(&args,9,16.)?as usize; let nv=self.arg_num(&args,10,14.)?as usize;
1379                let cw=self.arg_num(&args,11,0.65)?as f32; let rh=self.arg_num(&args,12,0.60)?as f32;
1380                let sp=self.arg_num(&args,13,0.025)?as f32;
1381                let fr=self.arg_num(&args,14,0.)?as f32; let hue=self.arg_num(&args,15,0.)?as f32;
1382                let mut gfx = self.gfx.borrow_mut();
1383                let cam = gfx.camera.clone();
1384                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);
1385                return Ok(Value::Unit);
1386            }
1387
1388            // vtex_hyperbolic_uv(cx,cy,cz, ux,uy,uz, vx,vy,vz, max_r,n_circles,n_rays, fr,hue)
1389            "vtex_hyperbolic_uv" | "ลายไฮเพอร์โบลิก" | "纹曲面" | "双曲線" | "쌍곡선" => {
1390                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;
1391                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;
1392                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;
1393                let mr=self.arg_num(&args,9,5.)?as f32;
1394                let nc=self.arg_num(&args,10,12.)?as usize; let nr=self.arg_num(&args,11,18.)?as usize;
1395                let fr=self.arg_num(&args,12,0.)?as f32; let hue=self.arg_num(&args,13,0.)?as f32;
1396                let mut gfx = self.gfx.borrow_mut();
1397                let cam = gfx.camera.clone();
1398                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);
1399                return Ok(Value::Unit);
1400            }
1401
1402            // vtex_halftone(cx,cy,cz, ux,uy,uz, vx,vy,vz, cols,rows, cell_w,cell_h, density, fr,hue)
1403            "vtex_halftone" | "ลายจุด" | "纹半调" | "網点模様" | "망점" => {
1404                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;
1405                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;
1406                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;
1407                let cols=self.arg_num(&args,9,16.)?as usize; let rows=self.arg_num(&args,10,12.)?as usize;
1408                let cw=self.arg_num(&args,11,0.5)?as f32; let ch=self.arg_num(&args,12,0.5)?as f32;
1409                let dens=self.arg_num(&args,13,0.4)?as f32;
1410                let fr=self.arg_num(&args,14,0.)?as f32; let hue=self.arg_num(&args,15,0.)?as f32;
1411                let mut gfx = self.gfx.borrow_mut();
1412                let cam = gfx.camera.clone();
1413                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);
1414                return Ok(Value::Unit);
1415            }
1416
1417            // vtex_tessellated(cx,cy,cz, ux,uy,uz, vx,vy,vz, cols,rows, cell, amplitude,freq, fr,hue)
1418            "vtex_tessellated" | "ลายตาข่าย" | "纹镶嵌" | "網目模様" | "격자망" => {
1419                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;
1420                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;
1421                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;
1422                let cols=self.arg_num(&args,9,14.)?as usize; let rows=self.arg_num(&args,10,10.)?as usize;
1423                let cell=self.arg_num(&args,11,0.6)?as f32;
1424                let amp=self.arg_num(&args,12,0.25)?as f32; let freq=self.arg_num(&args,13,4.)?as f32;
1425                let fr=self.arg_num(&args,14,0.)?as f32; let hue=self.arg_num(&args,15,0.)?as f32;
1426                let mut gfx = self.gfx.borrow_mut();
1427                let cam = gfx.camera.clone();
1428                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);
1429                return Ok(Value::Unit);
1430            }
1431
1432            // vtex_lotus(cx,cy,cz, ux,uy,uz, vx,vy,vz, r_inner,r_outer,n_petals, fr,hue)
1433            "vtex_lotus" | "ลายดอกบัว" | "纹莲" | "蓮模様" | "연꽃무늬" => {
1434                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;
1435                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;
1436                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;
1437                let ri=self.arg_num(&args,9,1.)?as f32; let ro=self.arg_num(&args,10,2.)?as f32;
1438                let np=self.arg_num(&args,11,12.)?as usize;
1439                let fr=self.arg_num(&args,12,0.)?as f32; let hue=self.arg_num(&args,13,0.)?as f32;
1440                let mut gfx = self.gfx.borrow_mut();
1441                let cam = gfx.camera.clone();
1442                crate::gfx::vtex::draw_lotus(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, ri,ro,np, fr,hue);
1443                return Ok(Value::Unit);
1444            }
1445
1446            // vtex_chakra(cx,cy,cz, ux,uy,uz, vx,vy,vz, r,n_spokes, fr,hue)
1447            "vtex_chakra" | "ลายจักร" | "纹轮" | "輪模様" | "바퀴무늬" => {
1448                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;
1449                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;
1450                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;
1451                let r=self.arg_num(&args,9,2.)?as f32; let ns=self.arg_num(&args,10,8.)?as usize;
1452                let fr=self.arg_num(&args,11,0.)?as f32; let hue=self.arg_num(&args,12,0.)?as f32;
1453                let mut gfx = self.gfx.borrow_mut();
1454                let cam = gfx.camera.clone();
1455                crate::gfx::vtex::draw_chakra(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, r,ns, fr,hue);
1456                return Ok(Value::Unit);
1457            }
1458
1459            // vtex_yantra(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_layers,max_r, fr,hue)
1460            "vtex_yantra" | "ลายยันต์" | "纹咒" | "護符模様" | "부적무늬" => {
1461                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;
1462                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;
1463                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;
1464                let nl=self.arg_num(&args,9,4.)?as usize; let mr=self.arg_num(&args,10,3.)?as f32;
1465                let fr=self.arg_num(&args,11,0.)?as f32; let hue=self.arg_num(&args,12,0.)?as f32;
1466                let mut gfx = self.gfx.borrow_mut();
1467                let cam = gfx.camera.clone();
1468                crate::gfx::vtex::draw_yantra(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, nl,mr, fr,hue);
1469                return Ok(Value::Unit);
1470            }
1471
1472            // vtex_spiked_cog(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_teeth,r_body,r_spike,r_hub,n_spokes, fr,hue)
1473            "vtex_spiked_cog" | "ฟันเฟืองหนาม" | "纹棘轮" | "歯車模様" | "톱니바퀴" => {
1474                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;
1475                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;
1476                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;
1477                let nt=self.arg_num(&args,9,12.)?as usize; let rb=self.arg_num(&args,10,1.)?as f32;
1478                let rs=self.arg_num(&args,11,1.3)?as f32; let rh=self.arg_num(&args,12,0.2)?as f32;
1479                let ns=self.arg_num(&args,13,6.)?as usize;
1480                let fr=self.arg_num(&args,14,0.)?as f32; let hue=self.arg_num(&args,15,0.)?as f32;
1481                let mut gfx = self.gfx.borrow_mut();
1482                let cam = gfx.camera.clone();
1483                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);
1484                return Ok(Value::Unit);
1485            }
1486
1487            // vtex_torii(cx,cy,cz, ux,uy,uz, vx,vy,vz, width,height, fr,hue)
1488            "vtex_torii" | "ประตูโทริอิ" | "纹鸟居" | "鳥居" | "도리이" => {
1489                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;
1490                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;
1491                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;
1492                let w=self.arg_num(&args,9,4.)?as f32; let h=self.arg_num(&args,10,5.)?as f32;
1493                let fr=self.arg_num(&args,11,0.)?as f32; let hue=self.arg_num(&args,12,0.)?as f32;
1494                let mut gfx = self.gfx.borrow_mut();
1495                let cam = gfx.camera.clone();
1496                crate::gfx::vtex::draw_torii(&mut gfx.depth_queue,&cam, cx,cy,cz, ux,uy,uz, vx,vy,vz, w,h, fr,hue);
1497                return Ok(Value::Unit);
1498            }
1499
1500            // vtex_pagoda(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_tiers,base_w,tier_h,taper,eave_out, fr,hue)
1501            "vtex_pagoda" | "เจดีย์" | "纹塔" | "塔" | "탑" => {
1502                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;
1503                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;
1504                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;
1505                let nt=self.arg_num(&args,9,5.)?as usize; let bw=self.arg_num(&args,10,2.)?as f32;
1506                let th=self.arg_num(&args,11,1.)?as f32; let tp=self.arg_num(&args,12,0.72)?as f32;
1507                let eo=self.arg_num(&args,13,0.28)?as f32;
1508                let fr=self.arg_num(&args,14,0.)?as f32; let hue=self.arg_num(&args,15,0.)?as f32;
1509                let mut gfx = self.gfx.borrow_mut();
1510                let cam = gfx.camera.clone();
1511                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);
1512                return Ok(Value::Unit);
1513            }
1514
1515            // ══════════════════════════════════════════════════════════════════
1516            // AUDIO BUILTINS
1517            // ══════════════════════════════════════════════════════════════════
1518
1519            // audio_tone(idx, x, y, z, w, freq, amp, lfo_rate, lfo_depth)
1520            #[cfg(not(target_arch = "wasm32"))]
1521            "audio_tone" | "เสียงโทน" => {
1522                let idx  = self.arg_num(&args, 0, 0.0)? as usize;
1523                let x    = self.arg_num(&args, 1, 0.0)? as f32;
1524                let y    = self.arg_num(&args, 2, 0.0)? as f32;
1525                let z    = self.arg_num(&args, 3, 0.0)? as f32;
1526                let w    = self.arg_num(&args, 4, 1.0)? as f32;
1527                let freq = self.arg_num(&args, 5, 220.0)? as f32;
1528                let amp  = self.arg_num(&args, 6, 0.15)? as f32;
1529                let lfo_rate  = self.arg_num(&args, 7, 0.5)? as f32;
1530                let lfo_depth = self.arg_num(&args, 8, 0.02)? as f32;
1531                if let Some(audio) = &self.audio {
1532                    audio.set_tone(idx, ToneParams { x, y, z, w, freq, amp, lfo_rate, lfo_depth });
1533                }
1534                return Ok(Value::Unit);
1535            }
1536
1537            #[cfg(not(target_arch = "wasm32"))]
1538            "audio_listener" | "ผู้ฟัง" => {
1539                let cry = self.arg_num(&args, 0, 1.0)? as f32;
1540                let sry = self.arg_num(&args, 1, 0.0)? as f32;
1541                let crx = self.arg_num(&args, 2, 1.0)? as f32;
1542                let srx = self.arg_num(&args, 3, 0.0)? as f32;
1543                if let Some(audio) = &self.audio {
1544                    audio.set_listener(cry, sry, crx, srx);
1545                }
1546                return Ok(Value::Unit);
1547            }
1548
1549            #[cfg(not(target_arch = "wasm32"))]
1550            "audio_bgm" | "เพลงพื้นหลัง" => {
1551                let path = match args.first() {
1552                    Some(Value::Str(s)) => s.clone(),
1553                    _ => return Ok(Value::Unit),
1554                };
1555                let vol = self.arg_num(&args, 1, 0.5)? as f32;
1556                if let Some(audio) = &self.audio {
1557                    audio.load_bgm(&path, vol);
1558                }
1559                return Ok(Value::Unit);
1560            }
1561
1562            #[cfg(not(target_arch = "wasm32"))]
1563            "audio_bgm_volume" | "ระดับเสียงพื้นหลัง" => {
1564                let vol = self.arg_num(&args, 0, 0.5)? as f32;
1565                if let Some(audio) = &self.audio {
1566                    audio.set_bgm_volume(vol);
1567                }
1568                return Ok(Value::Unit);
1569            }
1570
1571            #[cfg(not(target_arch = "wasm32"))]
1572            "audio_volume" | "ระดับเสียง" => {
1573                let vol = self.arg_num(&args, 0, 0.7)? as f32;
1574                if let Some(audio) = &self.audio {
1575                    audio.set_master_volume(vol);
1576                }
1577                return Ok(Value::Unit);
1578            }
1579
1580            // WASM audio builtins — delegate to Web Audio API
1581            #[cfg(target_arch = "wasm32")]
1582            "audio_tone" | "เสียงโทน" => {
1583                let idx  = self.arg_num(&args, 0, 0.0)? as usize;
1584                let x    = self.arg_num(&args, 1, 0.0)? as f32;
1585                let y    = self.arg_num(&args, 2, 0.0)? as f32;
1586                let z    = self.arg_num(&args, 3, 0.0)? as f32;
1587                let w    = self.arg_num(&args, 4, 1.0)? as f32;
1588                let freq = self.arg_num(&args, 5, 220.0)? as f32;
1589                let amp  = self.arg_num(&args, 6, 0.15)? as f32;
1590                let lfo_rate  = self.arg_num(&args, 7, 0.5)? as f32;
1591                let lfo_depth = self.arg_num(&args, 8, 0.02)? as f32;
1592                crate::gfx::audio_web::set_tone(idx, x, y, z, w, freq, amp, lfo_rate, lfo_depth);
1593                return Ok(Value::Unit);
1594            }
1595
1596            #[cfg(target_arch = "wasm32")]
1597            "audio_listener" | "ผู้ฟัง" => {
1598                let cry = self.arg_num(&args, 0, 1.0)? as f32;
1599                let sry = self.arg_num(&args, 1, 0.0)? as f32;
1600                let crx = self.arg_num(&args, 2, 1.0)? as f32;
1601                let srx = self.arg_num(&args, 3, 0.0)? as f32;
1602                crate::gfx::audio_web::set_listener(cry, sry, crx, srx);
1603                return Ok(Value::Unit);
1604            }
1605
1606            #[cfg(target_arch = "wasm32")]
1607            "audio_bgm" | "เพลงพื้นหลัง" => {
1608                let path = self.arg_str(&args, 0, "");
1609                let vol  = self.arg_num(&args, 1, 0.5)? as f32;
1610                crate::gfx::audio_web::load_bgm(&path, vol);
1611                return Ok(Value::Unit);
1612            }
1613
1614            #[cfg(target_arch = "wasm32")]
1615            "audio_bgm_volume" | "ระดับเสียงพื้นหลัง" => {
1616                let vol = self.arg_num(&args, 0, 0.5)? as f32;
1617                crate::gfx::audio_web::set_bgm_volume(vol);
1618                return Ok(Value::Unit);
1619            }
1620
1621            #[cfg(target_arch = "wasm32")]
1622            "audio_volume" | "ระดับเสียง" => {
1623                let vol = self.arg_num(&args, 0, 0.7)? as f32;
1624                crate::gfx::audio_web::set_master_volume(vol);
1625                return Ok(Value::Unit);
1626            }
1627
1628            // ── รอหน้าต่าง() — block until window closed / Escape ──
1629            "รอหน้าต่าง" | "wait_window" | "gfx_wait" => {
1630                #[cfg(not(target_arch = "wasm32"))]
1631                loop {
1632                    let still_open = {
1633                        let gfx = self.gfx.borrow();
1634                        gfx.window.as_ref()
1635                            .map(|w| w.is_open() && !w.is_key_down(minifb::Key::Escape))
1636                            .unwrap_or(false)
1637                    };
1638                    if !still_open { break; }
1639                    let (buf, w, h) = {
1640                        let gfx = self.gfx.borrow();
1641                        (gfx.buffer.clone(), gfx.width, gfx.height)
1642                    };
1643                    let mut gfx = self.gfx.borrow_mut();
1644                    if let Some(win) = gfx.window.as_mut() {
1645                        if win.update_with_buffer(&buf, w, h).is_err() { break; }
1646                    }
1647                }
1648                return Ok(Value::Unit);
1649            }
1650
1651            // ── File I/O ──────────────────────────────────────────────────────
1652            "read_file" | "อ่านไฟล์" => {
1653                let path = self.arg_str(&args, 0, "");
1654                return std::fs::read_to_string(&path)
1655                    .map(Value::Str)
1656                    .map_err(|e| EvalErr::from(format!("read_file '{path}': {e}")));
1657            }
1658            "write_file" | "เขียนไฟล์" => {
1659                let path    = self.arg_str(&args, 0, "");
1660                let content = self.arg_str(&args, 1, "");
1661                std::fs::write(&path, content.as_bytes())
1662                    .map_err(|e| EvalErr::from(format!("write_file '{path}': {e}")))?;
1663                return Ok(Value::Unit);
1664            }
1665            "print_file" | "พิมพ์ไฟล์" => {
1666                let content = self.arg_str(&args, 0, "");
1667                print!("{content}");
1668                return Ok(Value::Unit);
1669            }
1670
1671            // ── CLI arguments ─────────────────────────────────────────────────
1672            "get_args" | "รับอาร์กิวเมนต์" => {
1673                let v: Vec<Value> = std::env::args().map(Value::Str).collect();
1674                return Ok(Value::List(v));
1675            }
1676
1677            // ── String utilities ──────────────────────────────────────────────
1678            "split" | "str_split" | "แยก" => {
1679                let s   = self.arg_str(&args, 0, "");
1680                let sep = self.arg_str(&args, 1, "\n");
1681                let sep = if sep.is_empty() { "\n".into() } else { sep };
1682                let parts: Vec<Value> = s.split(sep.as_str())
1683                    .map(|p| Value::Str(p.to_string())).collect();
1684                return Ok(Value::List(parts));
1685            }
1686            "trim" | "str_trim" | "ตัดช่องว่าง" => {
1687                let s = self.arg_str(&args, 0, "");
1688                return Ok(Value::Str(s.trim().to_string()));
1689            }
1690            "starts_with" | "str_starts_with" | "เริ่มด้วย" => {
1691                let s      = self.arg_str(&args, 0, "");
1692                let prefix = self.arg_str(&args, 1, "");
1693                return Ok(Value::Bool(s.starts_with(prefix.as_str())));
1694            }
1695            "ends_with" | "str_ends_with" | "ลงท้ายด้วย" => {
1696                let s      = self.arg_str(&args, 0, "");
1697                let suffix = self.arg_str(&args, 1, "");
1698                return Ok(Value::Bool(s.ends_with(suffix.as_str())));
1699            }
1700            "str_replace" | "แทนสตริง" => {
1701                let s    = self.arg_str(&args, 0, "");
1702                let from = self.arg_str(&args, 1, "");
1703                let to   = self.arg_str(&args, 2, "");
1704                return Ok(Value::Str(s.replace(from.as_str(), to.as_str())));
1705            }
1706            "str_find" | "หาในสตริง" => {
1707                let s      = self.arg_str(&args, 0, "");
1708                let needle = self.arg_str(&args, 1, "");
1709                // Return char index (not byte index) for consistency with substr
1710                let pos = s.find(needle.as_str())
1711                    .map(|byte_i| s[..byte_i].chars().count() as f64)
1712                    .unwrap_or(-1.0);
1713                return Ok(Value::Number(pos));
1714            }
1715            "substr" | "str_slice" | "ส่วนสตริง" => {
1716                let s     = self.arg_str(&args, 0, "");
1717                let start = self.arg_num(&args, 1, 0.0)? as usize;
1718                let len   = args.get(2)
1719                    .map(|v| self.to_number(v).unwrap_or(999999.0) as usize)
1720                    .unwrap_or_else(|| s.chars().count().saturating_sub(start));
1721                let chars: Vec<char> = s.chars().collect();
1722                let end   = (start + len).min(chars.len());
1723                let slice: String = chars.get(start..end).unwrap_or(&[]).iter().collect();
1724                return Ok(Value::Str(slice));
1725            }
1726            "to_str" | "str" | "num_str" | "แปลงสตริง" => {
1727                let v = args.into_iter().next().unwrap_or(Value::Unit);
1728                return Ok(Value::Str(v.to_string()));
1729            }
1730            "str_repeat" | "ทำซ้ำสตริง" => {
1731                let s = self.arg_str(&args, 0, "");
1732                let n = self.arg_num(&args, 1, 1.0)? as usize;
1733                return Ok(Value::Str(s.repeat(n)));
1734            }
1735            "str_upper" => {
1736                let s = self.arg_str(&args, 0, "");
1737                return Ok(Value::Str(s.to_uppercase()));
1738            }
1739            "str_lower" => {
1740                let s = self.arg_str(&args, 0, "");
1741                return Ok(Value::Str(s.to_lowercase()));
1742            }
1743            "str_len" | "len" | "ความยาว" => {
1744                match args.first() {
1745                    Some(Value::Str(s))  => return Ok(Value::Number(s.chars().count() as f64)),
1746                    Some(Value::List(v)) => return Ok(Value::Number(v.len() as f64)),
1747                    _ => return Ok(Value::Number(0.0)),
1748                }
1749            }
1750
1751            // ── FNV-1a hash (deterministic, normalized 0.0–1.0) ──────────────
1752            "hash_str" | "แฮช" => {
1753                let s = self.arg_str(&args, 0, "");
1754                let mut h: u64 = 14695981039346656037_u64;
1755                for b in s.bytes() { h ^= b as u64; h = h.wrapping_mul(1099511628211); }
1756                return Ok(Value::Number((h & 0xFFFFFF) as f64 / 16777215.0));
1757            }
1758            "hash_int" | "แฮชจำนวน" => {
1759                let s = self.arg_str(&args, 0, "");
1760                let n = self.arg_num(&args, 1, 100.0)? as u64;
1761                let mut h: u64 = 14695981039346656037_u64;
1762                for b in s.bytes() { h ^= b as u64; h = h.wrapping_mul(1099511628211); }
1763                return Ok(Value::Number((h % n.max(1)) as f64));
1764            }
1765
1766            // ── List utilities ────────────────────────────────────────────────
1767            "list_new" | "รายการใหม่" => {
1768                return Ok(Value::List(Vec::new()));
1769            }
1770            "list_push" | "เพิ่มรายการ" => {
1771                let lst = args.first().cloned().unwrap_or(Value::List(vec![]));
1772                let val = args.get(1).cloned().unwrap_or(Value::Unit);
1773                if let Value::List(mut v) = lst { v.push(val); return Ok(Value::List(v)); }
1774                return Ok(Value::List(vec![val]));
1775            }
1776            "list_get" | "รับรายการ" => {
1777                let lst = args.first().cloned().unwrap_or(Value::List(vec![]));
1778                let i   = self.arg_num(&args, 1, 0.0)? as usize;
1779                if let Value::List(v) = lst {
1780                    return Ok(v.get(i).cloned().unwrap_or(Value::Str(String::new())));
1781                }
1782                return Ok(Value::Str(String::new()));
1783            }
1784            "list_join" | "join" | "รวมรายการ" => {
1785                let lst = args.first().cloned().unwrap_or(Value::List(vec![]));
1786                let sep = args.get(1).map(|v| v.to_string()).unwrap_or_default();
1787                if let Value::List(v) = lst {
1788                    return Ok(Value::Str(v.iter().map(|x| x.to_string())
1789                        .collect::<Vec<_>>().join(&sep)));
1790                }
1791                return Ok(Value::Str(String::new()));
1792            }
1793
1794            // ══════════════════════════════════════════════════════════════════
1795            // SVG EXPORT  (svg_begin / svg_rect / svg_circle / svg_line /
1796            //              svg_polyline / svg_text / svg_end / hsl_color)
1797            // Chinese aliases: 开始SVG 结束SVG SVG矩形 SVG圆形 SVG线段 SVG折线 SVG文本 HSL颜色
1798            // Thai aliases:    เริ่มSVG จบSVG SVGสี่เหลี่ยม SVGวงกลม SVGเส้น SVGเส้นหัก SVGข้อความ สีHSL
1799            // ══════════════════════════════════════════════════════════════════
1800
1801            "svg_begin" | "开始SVG" | "เริ่มSVG" => {
1802                let path   = self.arg_str(&args, 0, "output.svg");
1803                let width  = self.arg_num(&args, 1, 800.0)?;
1804                let height = self.arg_num(&args, 2, 600.0)?;
1805                *self.svg.borrow_mut() = Some(SvgWriter::new(path, width, height));
1806                return Ok(Value::Unit);
1807            }
1808
1809            "svg_rect" | "SVG矩形" | "SVGสี่เหลี่ยม" => {
1810                let x    = self.arg_num(&args, 0, 0.0)?;
1811                let y    = self.arg_num(&args, 1, 0.0)?;
1812                let w    = self.arg_num(&args, 2, 10.0)?;
1813                let h    = self.arg_num(&args, 3, 10.0)?;
1814                let fill = self.arg_str(&args, 4, "#ffffff");
1815                if let Some(svg) = self.svg.borrow_mut().as_mut() {
1816                    svg.elements.push(format!(
1817                        "<rect x=\"{x:.1}\" y=\"{y:.1}\" width=\"{w:.1}\" \
1818                         height=\"{h:.1}\" fill=\"{fill}\"/>"));
1819                }
1820                return Ok(Value::Unit);
1821            }
1822
1823            "svg_circle" | "SVG圆形" | "SVGวงกลม" => {
1824                let cx   = self.arg_num(&args, 0, 0.0)?;
1825                let cy   = self.arg_num(&args, 1, 0.0)?;
1826                let r    = self.arg_num(&args, 2, 5.0)?;
1827                let fill = self.arg_str(&args, 3, "#ffffff");
1828                if let Some(svg) = self.svg.borrow_mut().as_mut() {
1829                    svg.elements.push(format!(
1830                        "<circle cx=\"{cx:.1}\" cy=\"{cy:.1}\" r=\"{r:.1}\" fill=\"{fill}\"/>"));
1831                }
1832                return Ok(Value::Unit);
1833            }
1834
1835            "svg_line" | "SVG线段" | "SVGเส้น" => {
1836                let x1     = self.arg_num(&args, 0, 0.0)?;
1837                let y1     = self.arg_num(&args, 1, 0.0)?;
1838                let x2     = self.arg_num(&args, 2, 0.0)?;
1839                let y2     = self.arg_num(&args, 3, 0.0)?;
1840                let stroke = self.arg_str(&args, 4, "#ffffff");
1841                let sw     = self.arg_num(&args, 5, 1.0)?;
1842                if let Some(svg) = self.svg.borrow_mut().as_mut() {
1843                    svg.elements.push(format!(
1844                        "<line x1=\"{x1:.1}\" y1=\"{y1:.1}\" x2=\"{x2:.1}\" y2=\"{y2:.1}\" \
1845                         stroke=\"{stroke}\" stroke-width=\"{sw:.1}\"/>"));
1846                }
1847                return Ok(Value::Unit);
1848            }
1849
1850            "svg_polyline" | "SVG折线" | "SVGเส้นหัก" => {
1851                let pts    = self.arg_str(&args, 0, "");
1852                let stroke = self.arg_str(&args, 1, "#ffffff");
1853                let sw     = self.arg_num(&args, 2, 1.0)?;
1854                if let Some(svg) = self.svg.borrow_mut().as_mut() {
1855                    svg.elements.push(format!(
1856                        "<polyline points=\"{pts}\" fill=\"none\" \
1857                         stroke=\"{stroke}\" stroke-width=\"{sw:.1}\"/>"));
1858                }
1859                return Ok(Value::Unit);
1860            }
1861
1862            "svg_text" | "SVG文本" | "SVGข้อความ" => {
1863                let x    = self.arg_num(&args, 0, 0.0)?;
1864                let y    = self.arg_num(&args, 1, 0.0)?;
1865                let text = self.arg_str(&args, 2, "");
1866                let fill = self.arg_str(&args, 3, "#ffffff");
1867                let size = self.arg_num(&args, 4, 12.0)?;
1868                if let Some(svg) = self.svg.borrow_mut().as_mut() {
1869                    let safe = text.replace('&', "&amp;").replace('<', "&lt;").replace('>', "&gt;");
1870                    svg.elements.push(format!(
1871                        "<text x=\"{x:.1}\" y=\"{y:.1}\" fill=\"{fill}\" \
1872                         font-family=\"monospace\" font-size=\"{size:.0}\">{safe}</text>"));
1873                }
1874                return Ok(Value::Unit);
1875            }
1876
1877            "svg_end" | "结束SVG" | "จบSVG" => {
1878                {
1879                    let borrow = self.svg.borrow();
1880                    if let Some(svg) = borrow.as_ref() {
1881                        svg.save().map_err(|e| EvalErr::from(format!("svg_end: {e}")))?;
1882                    }
1883                }
1884                *self.svg.borrow_mut() = None;
1885                return Ok(Value::Unit);
1886            }
1887
1888            "hsl_color" | "HSL颜色" | "สีHSL" => {
1889                let h = self.arg_num(&args, 0, 0.0)?;
1890                let s = self.arg_num(&args, 1, 70.0)?;
1891                let l = self.arg_num(&args, 2, 50.0)?;
1892                return Ok(Value::Str(hsl_to_hex(h, s, l)));
1893            }
1894
1895            // ══════════════════════════════════════════════════════════════════
1896            // FFT / AUDIO ANALYSIS BUILTINS  (native only)
1897            // ══════════════════════════════════════════════════════════════════
1898
1899            // fft_push(samples_list) — feed raw audio samples and run FFT
1900            #[cfg(not(target_arch = "wasm32"))]
1901            "fft_push" | "วิเคราะห์เสียง" => {
1902                if let Some(Value::List(v)) = args.first() {
1903                    let samples: Vec<f32> = v.iter()
1904                        .filter_map(|x| if let Value::Number(n) = x { Some(*n as f32) } else { None })
1905                        .collect();
1906                    self.fft.borrow_mut().push_samples(&samples);
1907                }
1908                return Ok(Value::Unit);
1909            }
1910
1911            // fft_bands(n) → list of n log-spaced magnitude bands (0..1)
1912            #[cfg(not(target_arch = "wasm32"))]
1913            "fft_bands" | "แถบความถี่" => {
1914                let n = self.arg_num(&args, 0, 32.0)? as usize;
1915                let bands = self.fft.borrow().freq_bands(n);
1916                *self.fft_bands_cache.borrow_mut() = bands.clone();
1917                return Ok(Value::List(bands.into_iter().map(|v| Value::Number(v as f64)).collect()));
1918            }
1919
1920            // fft_beat() → bool
1921            #[cfg(not(target_arch = "wasm32"))]
1922            "fft_beat" | "จังหวะเสียง" => {
1923                return Ok(Value::Bool(self.fft.borrow().is_beat()));
1924            }
1925
1926            // fft_beat_ratio() → f64  (1.0 = at threshold, >1 = strong beat)
1927            #[cfg(not(target_arch = "wasm32"))]
1928            "fft_beat_ratio" | "อัตราจังหวะ" => {
1929                return Ok(Value::Number(self.fft.borrow().beat_ratio() as f64));
1930            }
1931
1932            // fft_rms() → f64
1933            #[cfg(not(target_arch = "wasm32"))]
1934            "fft_rms" | "ระดับRMS" => {
1935                return Ok(Value::Number(self.fft.borrow().rms() as f64));
1936            }
1937
1938            // fft_dominant_freq() → f64  in Hz
1939            #[cfg(not(target_arch = "wasm32"))]
1940            "fft_dominant_freq" | "ความถี่หลัก" => {
1941                return Ok(Value::Number(self.fft.borrow().dominant_freq() as f64));
1942            }
1943
1944            // ── wasm32 stubs: fft builtins are no-ops on web ───────────────
1945            #[cfg(target_arch = "wasm32")]
1946            "fft_push" | "วิเคราะห์เสียง" => { return Ok(Value::Unit); }
1947            #[cfg(target_arch = "wasm32")]
1948            "fft_bands" | "แถบความถี่" => {
1949                let n = self.arg_num(&args, 0, 32.0)? as usize;
1950                return Ok(Value::List(vec![Value::Number(0.0); n]));
1951            }
1952            #[cfg(target_arch = "wasm32")]
1953            "fft_beat" | "จังหวะเสียง" => { return Ok(Value::Bool(false)); }
1954            #[cfg(target_arch = "wasm32")]
1955            "fft_beat_ratio" | "อัตราจังหวะ" => { return Ok(Value::Number(1.0)); }
1956            #[cfg(target_arch = "wasm32")]
1957            "fft_rms" | "ระดับRMS" => { return Ok(Value::Number(0.0)); }
1958            #[cfg(target_arch = "wasm32")]
1959            "fft_dominant_freq" | "ความถี่หลัก" => { return Ok(Value::Number(0.0)); }
1960
1961            // ══════════════════════════════════════════════════════════════════
1962            // PROCEDURAL TEXTURE BLIT BUILTINS  (screen-space)
1963            // All: name(dst_x, dst_y, width, height, ...params, palette)
1964            // palette: "rainbow" | "fire" | "ocean" | "psychedelic" | "neon" | "forest"
1965            // ══════════════════════════════════════════════════════════════════
1966
1967            // tex_checkerboard(x, y, w, h, tiles, r1,g1,b1, r2,g2,b2)
1968            "tex_checkerboard" | "ลายตารางหมากรุก" => {
1969                let (tx,ty,tw,th) = self.tex_rect(&args)?;
1970                let tiles = self.arg_num(&args, 4, 8.0)? as u32;
1971                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);
1972                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);
1973                let c1 = (r1<<16)|(g1<<8)|b1; let c2 = (r2<<16)|(g2<<8)|b2;
1974                let mut gfx = self.gfx.borrow_mut();
1975                let (bw, bh) = (gfx.width, gfx.height);
1976                for row in 0..th { for col in 0..tw {
1977                    let cx = col as u32 * tiles / tw as u32;
1978                    let cy = row as u32 * tiles / th as u32;
1979                    let (dx, dy) = (tx+col, ty+row);
1980                    if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = if (cx+cy)%2==0 { c1 } else { c2 }; }
1981                }}
1982                return Ok(Value::Unit);
1983            }
1984
1985            // tex_gradient(x, y, w, h, angle_deg, r1,g1,b1, r2,g2,b2)
1986            "tex_gradient" | "ลายไล่สี" => {
1987                let (tx,ty,tw,th) = self.tex_rect(&args)?;
1988                let angle = self.arg_num(&args, 4, 0.0)? as f32;
1989                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.);
1990                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.);
1991                let (ca, sa) = (angle.to_radians().cos(), angle.to_radians().sin());
1992                let mut gfx = self.gfx.borrow_mut();
1993                let (bw, bh) = (gfx.width, gfx.height);
1994                for row in 0..th { for col in 0..tw {
1995                    let nx = col as f32/tw as f32 - 0.5; let ny = row as f32/th as f32 - 0.5;
1996                    let t = ((nx*ca + ny*sa + 0.707)/1.414).clamp(0.,1.);
1997                    let (dx, dy) = (tx+col, ty+row);
1998                    if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(r1+(r2-r1)*t, g1+(g2-g1)*t, b1+(b2-b1)*t); }
1999                }}
2000                return Ok(Value::Unit);
2001            }
2002
2003            // tex_noise(x, y, w, h, scale, octaves, seed, palette)
2004            "tex_noise" | "ลายนอยส์" => {
2005                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2006                let scale   = self.arg_num(&args, 4, 4.0)? as f32;
2007                let octaves = self.arg_num(&args, 5, 4.0)? as u32;
2008                let seed    = self.arg_num(&args, 6, 0.0)? as u32;
2009                let palette = self.arg_str(&args, 7, "rainbow");
2010                let mut gfx = self.gfx.borrow_mut();
2011                let (bw, bh) = (gfx.width, gfx.height);
2012                for row in 0..th { for col in 0..tw {
2013                    let v = tex_fbm(col as f32*scale/tw as f32, row as f32*scale/th as f32, octaves, seed);
2014                    let [r,g,b] = tex_palette(&palette, v);
2015                    let (dx, dy) = (tx+col, ty+row);
2016                    if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(r, g, b); }
2017                }}
2018                return Ok(Value::Unit);
2019            }
2020
2021            // tex_freq_map(x, y, w, h, time, speed, palette)
2022            // Uses bands written by the last fft_bands() call.
2023            "tex_freq_map" | "ลายความถี่" => {
2024                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2025                let time    = self.arg_num(&args, 4, 0.0)? as f32;
2026                let speed   = self.arg_num(&args, 5, 0.3)? as f32;
2027                let palette = self.arg_str(&args, 6, "rainbow");
2028                let bands: Vec<f32> = {
2029                    let c = self.fft_bands_cache.borrow();
2030                    if c.is_empty() { vec![0.0; 32] } else { c.clone() }
2031                };
2032                let n = bands.len().max(1);
2033                let mut gfx = self.gfx.borrow_mut();
2034                let (bw, bh) = (gfx.width, gfx.height);
2035                for row in 0..th { for col in 0..tw {
2036                    let band_idx = (col * n / tw.max(1)).min(n-1);
2037                    let mag = bands[band_idx].clamp(0.,1.);
2038                    let fill_y = (mag * th as f32) as usize;
2039                    if row >= th.saturating_sub(fill_y) {
2040                        let t = (col as f32/tw as f32 + time*speed) % 1.0;
2041                        let [r,g,b] = tex_palette(&palette, t);
2042                        let bright = mag * (1.0 - row as f32/th as f32 * 0.5);
2043                        let (dx, dy) = (tx+col, ty+row);
2044                        if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(r*bright, g*bright, b*bright); }
2045                    }
2046                }}
2047                return Ok(Value::Unit);
2048            }
2049
2050            // tex_spiral(x, y, w, h, freq, bands, time, palette)
2051            "tex_spiral" | "ลายเกลียวหมุน" => {
2052                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2053                let freq    = self.arg_num(&args, 4, 5.0)? as f32;
2054                let n_bands = self.arg_num(&args, 5, 8.0)? as f32;
2055                let time    = self.arg_num(&args, 6, 0.0)? as f32;
2056                let palette = self.arg_str(&args, 7, "rainbow");
2057                let mut gfx = self.gfx.borrow_mut();
2058                let (bw, bh) = (gfx.width, gfx.height);
2059                for row in 0..th { for col in 0..tw {
2060                    let nx = col as f32/tw as f32 - 0.5; let ny = row as f32/th as f32 - 0.5;
2061                    let r  = (nx*nx + ny*ny).sqrt();
2062                    let theta = ny.atan2(nx);
2063                    let t = ((r*freq - theta/std::f32::consts::TAU + time*0.5) * n_bands % 1.0).abs();
2064                    let [cr,cg,cb] = tex_palette(&palette, t);
2065                    let (dx, dy) = (tx+col, ty+row);
2066                    if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(cr, cg, cb); }
2067                }}
2068                return Ok(Value::Unit);
2069            }
2070
2071            // tex_ripple(x, y, w, h, freq, cx, cy, time, palette)
2072            "tex_ripple" | "ลายระลอก" => {
2073                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2074                let freq    = self.arg_num(&args, 4, 10.0)? as f32;
2075                let rcx     = self.arg_num(&args, 5, 0.5)? as f32;
2076                let rcy     = self.arg_num(&args, 6, 0.5)? as f32;
2077                let time    = self.arg_num(&args, 7, 0.0)? as f32;
2078                let palette = self.arg_str(&args, 8, "ocean");
2079                let mut gfx = self.gfx.borrow_mut();
2080                let (bw, bh) = (gfx.width, gfx.height);
2081                for row in 0..th { for col in 0..tw {
2082                    let nx = col as f32/tw as f32 - rcx; let ny = row as f32/th as f32 - rcy;
2083                    let r = (nx*nx + ny*ny).sqrt();
2084                    let t = ((r*freq - time) % 1.0).abs();
2085                    let [cr,cg,cb] = tex_palette(&palette, t);
2086                    let (dx, dy) = (tx+col, ty+row);
2087                    if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(cr, cg, cb); }
2088                }}
2089                return Ok(Value::Unit);
2090            }
2091
2092            // tex_mandelbrot(x, y, w, h, zoom, cx, cy, max_iter, palette)
2093            "tex_mandelbrot" | "ลายแมนเดลบรอต" => {
2094                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2095                let zoom     = self.arg_num(&args, 4, 1.0)?;
2096                let mcx      = self.arg_num(&args, 5, -0.5)?;
2097                let mcy      = self.arg_num(&args, 6, 0.0)?;
2098                let max_iter = self.arg_num(&args, 7, 64.0)? as u32;
2099                let palette  = self.arg_str(&args, 8, "psychedelic");
2100                let mut gfx = self.gfx.borrow_mut();
2101                let (bw, bh) = (gfx.width, gfx.height);
2102                for row in 0..th { for col in 0..tw {
2103                    let zx0 = (col as f64/tw as f64 - 0.5)/zoom + mcx;
2104                    let zy0 = (row as f64/th as f64 - 0.5)/zoom + mcy;
2105                    let mut x = 0.0f64; let mut y = 0.0f64; let mut i = 0u32;
2106                    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; }
2107                    let t = if i==max_iter { 0.0f32 } else {
2108                        (i as f32 - (x as f32*x as f32+y as f32*y as f32).ln().ln()/2.0f32.ln()) / max_iter as f32
2109                    };
2110                    let [cr,cg,cb] = tex_palette(&palette, t.clamp(0.,1.));
2111                    let (dx, dy) = (tx+col, ty+row);
2112                    if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(cr, cg, cb); }
2113                }}
2114                return Ok(Value::Unit);
2115            }
2116
2117            // tex_julia(x, y, w, h, c_re, c_im, max_iter, palette)
2118            "tex_julia" | "ลายจูเลีย" => {
2119                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2120                let c_re     = self.arg_num(&args, 4, -0.7)?;
2121                let c_im     = self.arg_num(&args, 5, 0.27)?;
2122                let max_iter = self.arg_num(&args, 6, 64.0)? as u32;
2123                let palette  = self.arg_str(&args, 7, "neon");
2124                let mut gfx = self.gfx.borrow_mut();
2125                let (bw, bh) = (gfx.width, gfx.height);
2126                for row in 0..th { for col in 0..tw {
2127                    let mut zx = (col as f64/tw as f64 - 0.5)*3.5;
2128                    let mut zy = (row as f64/th as f64 - 0.5)*3.5;
2129                    let mut i = 0u32;
2130                    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; }
2131                    let t = i as f32 / max_iter as f32;
2132                    let [cr,cg,cb] = tex_palette(&palette, t);
2133                    let (dx, dy) = (tx+col, ty+row);
2134                    if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(cr, cg, cb); }
2135                }}
2136                return Ok(Value::Unit);
2137            }
2138
2139            // tex_voronoi(x, y, w, h, cells, seed, palette)
2140            "tex_voronoi" | "ลายโวโรนอย" => {
2141                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2142                let cells   = self.arg_num(&args, 4, 16.0)? as u32;
2143                let seed    = self.arg_num(&args, 5, 42.0)? as u32;
2144                let palette = self.arg_str(&args, 6, "rainbow");
2145                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();
2146                let mut gfx = self.gfx.borrow_mut();
2147                let (bw, bh) = (gfx.width, gfx.height);
2148                for row in 0..th { for col in 0..tw {
2149                    let (fx, fy) = (col as f32/tw as f32, row as f32/th as f32);
2150                    let (min_d, nearest) = pts.iter().enumerate().fold((f32::MAX,0usize), |(d,idx),(i,&[cx,cy])| {
2151                        let dd = (fx-cx).powi(2)+(fy-cy).powi(2);
2152                        if dd < d { (dd,i) } else { (d,idx) }
2153                    });
2154                    let t = (nearest as f32/cells as f32 + min_d*4.0) % 1.0;
2155                    let [cr,cg,cb] = tex_palette(&palette, t);
2156                    let (dx, dy) = (tx+col, ty+row);
2157                    if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(cr, cg, cb); }
2158                }}
2159                return Ok(Value::Unit);
2160            }
2161
2162            // tex_halftone(x, y, w, h, dot_size, time, palette)
2163            "tex_halftone" | "ลายฮาล์ฟโทน" => {
2164                let (tx,ty,tw,th) = self.tex_rect(&args)?;
2165                let dot_size = self.arg_num(&args, 4, 0.05)? as f32;
2166                let time     = self.arg_num(&args, 5, 0.0)? as f32;
2167                let palette  = self.arg_str(&args, 6, "rainbow");
2168                let mut gfx = self.gfx.borrow_mut();
2169                let (bw, bh) = (gfx.width, gfx.height);
2170                for row in 0..th { for col in 0..tw {
2171                    let (fx, fy) = (col as f32/tw as f32, row as f32/th as f32);
2172                    let gx = (fx/dot_size).floor(); let gy = (fy/dot_size).floor();
2173                    let lx = (fx/dot_size - gx - 0.5)*2.0; let ly = (fy/dot_size - gy - 0.5)*2.0;
2174                    let r = (lx*lx + ly*ly).sqrt();
2175                    let t = (gx/(1.0/dot_size) + time*0.1) % 1.0;
2176                    let a = if r < 0.7 { ((0.7-r)/0.7).clamp(0.,1.) } else { 0.0 };
2177                    if a > 0.0 {
2178                        let [cr,cg,cb] = tex_palette(&palette, t);
2179                        let (dx, dy) = (tx+col, ty+row);
2180                        if dx < bw && dy < bh { gfx.buffer[dy*bw+dx] = tex_rgb(cr, cg, cb); }
2181                    }
2182                }}
2183                return Ok(Value::Unit);
2184            }
2185
2186            _ => {}
2187        }
2188
2189        // User-defined function
2190        if let Some(def) = self.functions.get(name).cloned() {
2191            let mut call_env = Env::new();
2192            // Seed env with non-Do globals (skip entry-point blocks to avoid infinite recursion).
2193            // Pass 1: evaluate each global with the call-site env — simple literals succeed.
2194            // Pass 2: retry failed globals with the partially-built call_env so that compound
2195            //         globals (e.g. `FM = (NZ + FZ) / 2.0`) can resolve their dependencies.
2196            let non_do_globals: Vec<_> = self.globals.iter()
2197                .filter(|(_, expr)| !matches!(expr, Expr::Do(_)))
2198                .map(|(k, e)| (k.clone(), e.clone()))
2199                .collect();
2200            let mut pending: Vec<(String, Expr)> = Vec::new();
2201            for (k, expr) in &non_do_globals {
2202                let mut tmp = env.clone();
2203                if let Ok(v) = self.eval_expr(expr, &mut tmp) {
2204                    call_env.insert(k.clone(), v);
2205                } else {
2206                    pending.push((k.clone(), expr.clone()));
2207                }
2208            }
2209            // Second pass: retry compound globals now that literals are in call_env.
2210            for (k, expr) in &pending {
2211                let mut tmp = env.clone();
2212                tmp.extend(call_env.clone());
2213                if let Ok(v) = self.eval_expr(expr, &mut tmp) {
2214                    call_env.insert(k.clone(), v);
2215                }
2216            }
2217            for (param, arg) in def.params.iter().zip(args) {
2218                call_env.insert(param.clone(), arg);
2219            }
2220            return match self.exec_block(&def.body, &mut call_env) {
2221                Ok(v) => Ok(v.unwrap_or(Value::Unit)),
2222                Err(EvalErr::Return(v)) => Ok(v),
2223                Err(e) => Err(e),
2224            };
2225        }
2226
2227        Err(EvalErr::from(format!("unknown function '{name}'")))
2228    }
2229
2230    fn call_value(&self, v: Value, args: Vec<Value>) -> EvalResult {
2231        match v {
2232            Value::Fn(params, body, mut captured) => {
2233                for (p, a) in params.iter().zip(args) {
2234                    captured.insert(p.clone(), a);
2235                }
2236                match self.exec_block(&body, &mut captured) {
2237                    Ok(v) => Ok(v.unwrap_or(Value::Unit)),
2238                    Err(EvalErr::Return(v)) => Ok(v),
2239                    Err(e) => Err(e),
2240                }
2241            }
2242            other => Err(EvalErr::from(format!("cannot call {:?}", other))),
2243        }
2244    }
2245
2246    fn call_method(&self, recv: Value, method: &str, args: Vec<Value>) -> EvalResult {
2247        match (&recv, method) {
2248            (Value::Str(s), "is_empty" | "是空") => Ok(Value::Bool(s.is_empty())),
2249            (Value::Str(s), "len" | "长")        => Ok(Value::Number(s.len() as f64)),
2250            (Value::Str(s), "to_string" | "转文") => Ok(Value::Str(s.clone())),
2251            (Value::Str(s), "contains" | "包含") => {
2252                if let Some(Value::Str(sub)) = args.first() {
2253                    Ok(Value::Bool(s.contains(sub.as_str())))
2254                } else { Ok(Value::Bool(false)) }
2255            }
2256            (Value::Str(s), "push_str" | "推_文") => {
2257                let mut s2 = s.clone();
2258                if let Some(Value::Str(a)) = args.first() { s2.push_str(a); }
2259                Ok(Value::Str(s2))
2260            }
2261            (Value::List(v), "len" | "长") => Ok(Value::Number(v.len() as f64)),
2262            (Value::List(v), "push" | "推") => {
2263                let mut v2 = v.clone();
2264                if let Some(a) = args.first() { v2.push(a.clone()); }
2265                Ok(Value::List(v2))
2266            }
2267            (Value::Ok(inner), _) | (Value::Err(inner), _) => Ok(*inner.clone()),
2268            _ => Err(EvalErr::from(format!("no method '{method}' on {recv}"))),
2269        }
2270    }
2271
2272    // ─── Pattern matching ─────────────────────────────────────────────────────
2273
2274    fn match_pattern(&self, pat: &Pattern, val: &Value) -> Option<Env> {
2275        match (pat, val) {
2276            (Pattern::Wildcard, _) => Some(Env::new()),
2277            (Pattern::Str(s), Value::Str(v)) if s == v => Some(Env::new()),
2278            (Pattern::Number(n), Value::Number(v)) if (n - v).abs() < 1e-12 => Some(Env::new()),
2279            (Pattern::Bool(b), Value::Bool(v)) if b == v => Some(Env::new()),
2280            (Pattern::Ident(name), _) => {
2281                let mut e = Env::new();
2282                e.insert(name.clone(), val.clone());
2283                Some(e)
2284            }
2285            (Pattern::Constructor(ctor, inner_pat), _) => {
2286                let (matches, inner_val) = match (ctor.as_str(), val) {
2287                    ("ok"  | "好", Value::Ok(v))  => (true, Some(v.as_ref().clone())),
2288                    ("bad" | "坏", Value::Err(v)) => (true, Some(v.as_ref().clone())),
2289                    ("ok"  | "好", v) if !matches!(v, Value::Err(_)) => (true, Some(v.clone())),
2290                    _ => (false, None),
2291                };
2292                if !matches { return None; }
2293                match (inner_pat, inner_val) {
2294                    (Some(p), Some(v)) => self.match_pattern(p, &v),
2295                    (None, _)          => Some(Env::new()),
2296                    (Some(p), None)    => self.match_pattern(p, &Value::Unit),
2297                }
2298            }
2299            _ => None,
2300        }
2301    }
2302
2303    // ─── Utilities ───────────────────────────────────────────────────────────
2304
2305    fn value_to_iter(&self, val: Value) -> Result<Vec<Value>, EvalErr> {
2306        match val {
2307            Value::List(v)   => Ok(v),
2308            Value::Str(s)    => Ok(s.chars().map(|c| Value::Str(c.to_string())).collect()),
2309            Value::Number(n) => Ok((0..n as i64).map(|i| Value::Number(i as f64)).collect()),
2310            other => Err(EvalErr::from(format!("cannot iterate over {:?}", other))),
2311        }
2312    }
2313
2314    fn is_truthy(&self, val: &Value) -> bool {
2315        match val {
2316            Value::Bool(b)     => *b,
2317            Value::Unit        => false,
2318            Value::Number(n)   => *n != 0.0,
2319            Value::Str(s)      => !s.is_empty(),
2320            Value::List(v)     => !v.is_empty(),
2321            Value::Ok(_)       => true,
2322            Value::Err(_)      => false,
2323            Value::Fn(_, _, _) => true,
2324        }
2325    }
2326
2327    fn to_number(&self, val: &Value) -> Result<f64, EvalErr> {
2328        match val {
2329            Value::Number(n) => Ok(*n),
2330            Value::Str(s)    => s.parse().map_err(|_| EvalErr::from(format!("cannot convert '{s}' to number"))),
2331            other => Err(EvalErr::from(format!("expected number, got {:?}", other))),
2332        }
2333    }
2334
2335    /// Get the n-th argument as f64, falling back to `default` if missing.
2336    fn arg_num(&self, args: &[Value], n: usize, default: f64) -> Result<f64, EvalErr> {
2337        match args.get(n) {
2338            Some(v) => self.to_number(v),
2339            None    => Ok(default),
2340        }
2341    }
2342
2343    fn arg_str(&self, args: &[Value], n: usize, default: &str) -> String {
2344        args.get(n).map(|v| v.to_string()).unwrap_or_else(|| default.to_string())
2345    }
2346
2347    /// Parse (dst_x, dst_y, width, height) from the first four args of a tex_* builtin.
2348    fn tex_rect(&self, args: &[Value]) -> Result<(usize, usize, usize, usize), EvalErr> {
2349        let tx = self.arg_num(args, 0, 0.0)? as usize;
2350        let ty = self.arg_num(args, 1, 0.0)? as usize;
2351        let tw = self.arg_num(args, 2, 256.0)? as usize;
2352        let th = self.arg_num(args, 3, 256.0)? as usize;
2353        Ok((tx, ty, tw.max(1), th.max(1)))
2354    }
2355
2356    fn apply_binop(&self, op: &BinOp, l: Value, r: Value) -> EvalResult {
2357        match op {
2358            BinOp::Add => match (l, r) {
2359                (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
2360                (Value::Str(a), Value::Str(b))       => Ok(Value::Str(a + &b)),
2361                (Value::Str(a), b)                   => Ok(Value::Str(a + &b.to_string())),
2362                (a, Value::Str(b))                   => Ok(Value::Str(a.to_string() + &b)),
2363                (a, b) => Err(EvalErr::from(format!("cannot add {:?} and {:?}", a, b))),
2364            },
2365            BinOp::Sub => Ok(Value::Number(self.to_number(&l)? - self.to_number(&r)?)),
2366            BinOp::Mul => Ok(Value::Number(self.to_number(&l)? * self.to_number(&r)?)),
2367            BinOp::Div => Ok(Value::Number(self.to_number(&l)? / self.to_number(&r)?)),
2368            BinOp::Rem => Ok(Value::Number(self.to_number(&l)? % self.to_number(&r)?)),
2369            BinOp::Eq  => Ok(Value::Bool(values_equal(&l, &r))),
2370            BinOp::Ne  => Ok(Value::Bool(!values_equal(&l, &r))),
2371            BinOp::Lt  => Ok(Value::Bool(self.to_number(&l)? < self.to_number(&r)?)),
2372            BinOp::Gt  => Ok(Value::Bool(self.to_number(&l)? > self.to_number(&r)?)),
2373            BinOp::Le  => Ok(Value::Bool(self.to_number(&l)? <= self.to_number(&r)?)),
2374            BinOp::Ge  => Ok(Value::Bool(self.to_number(&l)? >= self.to_number(&r)?)),
2375            BinOp::And => Ok(Value::Bool(self.is_truthy(&l) && self.is_truthy(&r))),
2376            BinOp::Or  => Ok(Value::Bool(self.is_truthy(&l) || self.is_truthy(&r))),
2377        }
2378    }
2379
2380    fn builtin_format(&self, args: &[Value]) -> Result<String, EvalErr> {
2381        if args.is_empty() { return Ok(String::new()); }
2382        let fmt = match &args[0] {
2383            Value::Str(s) => s.clone(),
2384            other => return Ok(other.to_string()),
2385        };
2386
2387        let mut result = String::new();
2388        let mut arg_idx = 1usize;
2389        let mut chars = fmt.chars().peekable();
2390        while let Some(c) = chars.next() {
2391            if c == '{' {
2392                if chars.peek() == Some(&'}') {
2393                    chars.next();
2394                    if arg_idx < args.len() {
2395                        result.push_str(&args[arg_idx].to_string());
2396                        arg_idx += 1;
2397                    }
2398                } else {
2399                    let mut spec = String::new();
2400                    for ch in chars.by_ref() {
2401                        if ch == '}' { break; }
2402                        spec.push(ch);
2403                    }
2404                    if arg_idx < args.len() {
2405                        if spec.starts_with(":.") {
2406                            if let Value::Number(n) = &args[arg_idx] {
2407                                let prec: usize = spec[2..].trim_end_matches('f')
2408                                    .parse().unwrap_or(2);
2409                                result.push_str(&format!("{:.prec$}", n));
2410                                arg_idx += 1;
2411                                continue;
2412                            }
2413                        }
2414                        result.push_str(&args[arg_idx].to_string());
2415                        arg_idx += 1;
2416                    }
2417                }
2418            } else {
2419                result.push(c);
2420            }
2421        }
2422        Ok(result)
2423    }
2424}
2425
2426#[cfg(not(target_arch = "wasm32"))]
2427fn str_to_minifb_key(name: &str) -> Option<minifb::Key> {
2428    use minifb::Key;
2429    Some(match name {
2430        "numpad0" | "kp0" => Key::NumPad0,
2431        "numpad1" | "kp1" => Key::NumPad1,
2432        "numpad2" | "kp2" => Key::NumPad2,
2433        "numpad3" | "kp3" => Key::NumPad3,
2434        "numpad4" | "kp4" => Key::NumPad4,
2435        "numpad5" | "kp5" => Key::NumPad5,
2436        "numpad6" | "kp6" => Key::NumPad6,
2437        "numpad7" | "kp7" => Key::NumPad7,
2438        "numpad8" | "kp8" => Key::NumPad8,
2439        "numpad9" | "kp9" => Key::NumPad9,
2440        "numpad+" | "kp+" => Key::NumPadPlus,
2441        "numpad-" | "kp-" => Key::NumPadMinus,
2442        "numpad*" | "kp*" => Key::NumPadAsterisk,
2443        "numpad/" | "kp/" => Key::NumPadSlash,
2444        "left"   => Key::Left,
2445        "right"  => Key::Right,
2446        "up"     => Key::Up,
2447        "down"   => Key::Down,
2448        "space"  => Key::Space,
2449        "enter"  => Key::Enter,
2450        "escape" => Key::Escape,
2451        "pageup" => Key::PageUp,
2452        "pagedown" => Key::PageDown,
2453        "lshift" | "leftshift"  => Key::LeftShift,
2454        "rshift" | "rightshift" => Key::RightShift,
2455        "lctrl"  | "leftctrl"   => Key::LeftCtrl,
2456        "rctrl"  | "rightctrl"  => Key::RightCtrl,
2457        "tab"    => Key::Tab,
2458        "backspace" => Key::Backspace,
2459        "delete" => Key::Delete,
2460        "insert" => Key::Insert,
2461        "home"   => Key::Home,
2462        "end"    => Key::End,
2463        "a" => Key::A, "b" => Key::B, "c" => Key::C, "d" => Key::D,
2464        "e" => Key::E, "f" => Key::F, "g" => Key::G, "h" => Key::H,
2465        "i" => Key::I, "j" => Key::J, "k" => Key::K, "l" => Key::L,
2466        "m" => Key::M, "n" => Key::N, "o" => Key::O, "p" => Key::P,
2467        "q" => Key::Q, "r" => Key::R, "s" => Key::S, "t" => Key::T,
2468        "u" => Key::U, "v" => Key::V, "w" => Key::W, "x" => Key::X,
2469        "y" => Key::Y, "z" => Key::Z,
2470        "0" => Key::Key0, "1" => Key::Key1, "2" => Key::Key2,
2471        "3" => Key::Key3, "4" => Key::Key4, "5" => Key::Key5,
2472        "6" => Key::Key6, "7" => Key::Key7, "8" => Key::Key8,
2473        "9" => Key::Key9,
2474        _ => return None,
2475    })
2476}
2477
2478fn values_equal(a: &Value, b: &Value) -> bool {
2479    match (a, b) {
2480        (Value::Number(x), Value::Number(y)) => (x - y).abs() < 1e-12,
2481        (Value::Str(x), Value::Str(y))       => x == y,
2482        (Value::Bool(x), Value::Bool(y))     => x == y,
2483        (Value::Unit, Value::Unit)            => true,
2484        _ => false,
2485    }
2486}
2487
2488// Rasteriser functions live in crate::gfx::raster — imported at top of file.
2489
2490// ── Window platform helpers ────────────────────────────────────────────────────
2491
2492/// Hide the console window that the OS auto-attaches to console-subsystem
2493/// processes. No-op on non-Windows and when no console is present.
2494#[cfg(not(target_arch = "wasm32"))]
2495fn hide_console_window() {
2496    #[cfg(windows)]
2497    unsafe {
2498        extern "system" {
2499            fn GetConsoleWindow() -> isize;
2500            fn ShowWindow(hwnd: isize, nCmdShow: i32) -> i32;
2501        }
2502        let hwnd = GetConsoleWindow();
2503        if hwnd != 0 {
2504            ShowWindow(hwnd, 0); // SW_HIDE = 0
2505        }
2506    }
2507}
2508
2509/// Move and resize the foreground window so it fills the primary monitor
2510/// (0,0 → screen_w × screen_h) and sits above the taskbar (HWND_TOPMOST).
2511#[cfg(all(not(target_arch = "wasm32"), windows))]
2512fn reposition_fullscreen(screen_w: i32, screen_h: i32) {
2513    unsafe {
2514        extern "system" {
2515            fn GetForegroundWindow() -> isize;
2516            fn SetWindowPos(hwnd: isize, insert_after: isize,
2517                            x: i32, y: i32, cx: i32, cy: i32,
2518                            flags: u32) -> i32;
2519        }
2520        let hwnd = GetForegroundWindow();
2521        if hwnd != 0 {
2522            // HWND_TOPMOST = -1, SWP_SHOWWINDOW = 0x0040
2523            SetWindowPos(hwnd, -1isize, 0, 0, screen_w, screen_h, 0x0040);
2524        }
2525    }
2526}
2527
2528/// Query the primary display resolution on non-Windows platforms.
2529/// Falls back to 1920×1080 if the size cannot be determined.
2530#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
2531fn native_screen_size() -> (f64, f64) {
2532    // On Linux/macOS we don't have an easy dependency-free call; return a
2533    // sensible default. Callers can always pass explicit dimensions.
2534    (1920.0, 1080.0)
2535}