Skip to main content

ling/runtime/
mod.rs

1// src/runtime/mod.rs — tree-walking interpreter with graphics support
2#[cfg(not(target_arch = "wasm32"))]
3mod ai;
4#[cfg(not(target_arch = "wasm32"))]
5mod gamepad;
6#[cfg(target_arch = "wasm32")]
7mod input_web;
8#[cfg(not(target_arch = "wasm32"))]
9pub(crate) mod jit_abi;
10#[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
11mod llm;
12#[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
13mod vision;
14
15/// Initialize the AOT/JIT runtime. Must be called before any AOT-compiled code.
16/// Creates a new interpreter instance for runtime function dispatch.
17#[cfg(not(target_arch = "wasm32"))]
18pub fn init_aot_runtime() {
19    let interp = Interpreter::new();
20    jit_abi::init(interp);
21}
22
23/// Returns seconds since Unix epoch. On wasm32 uses `js_sys::Date::now()`
24/// (milliseconds / 1000); on native uses `SystemTime`.
25pub fn now_secs() -> f64 {
26    #[cfg(target_arch = "wasm32")]
27    {
28        js_sys::Date::now() / 1000.0
29    }
30    #[cfg(not(target_arch = "wasm32"))]
31    {
32        std::time::SystemTime::now()
33            .duration_since(std::time::UNIX_EPOCH)
34            .map(|d| d.as_secs_f64())
35            .unwrap_or(0.0)
36    }
37}
38
39/// Global hue-cycle for wireframe line strokes (enabled via `set_line_hue_cycle`).
40/// Stored as f64 bits: the cycle rate (radians/sec) and the epoch-seconds baseline
41/// captured when it was last (re)enabled. A rate of 0 disables the effect.
42static LINE_HUE_RATE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
43static LINE_HUE_START: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
44
45/// Enable/disable the wireframe line hue-cycle. `rate` is in radians/sec (0 = off).
46pub fn set_line_hue_rate(rate: f64) {
47    LINE_HUE_RATE.store(rate.to_bits(), std::sync::atomic::Ordering::Relaxed);
48    LINE_HUE_START.store(now_secs().to_bits(), std::sync::atomic::Ordering::Relaxed);
49}
50
51/// Current hue phase (radians) for line strokes, or `None` when the cycle is off.
52/// Elapsed is computed in f64 (epoch seconds are huge) before the caller casts to
53/// f32, so `sin` keeps precision across a long session.
54pub fn line_hue_phase() -> Option<f64> {
55    let rate = f64::from_bits(LINE_HUE_RATE.load(std::sync::atomic::Ordering::Relaxed));
56    if rate <= 0.0 {
57        return None;
58    }
59    let start = f64::from_bits(LINE_HUE_START.load(std::sync::atomic::Ordering::Relaxed));
60    Some((now_secs() - start) * rate)
61}
62
63// Wasm-only module registry: seeded by JS before `run_program` is called so
64// that `use "path"` statements resolve without a real filesystem.
65#[cfg(target_arch = "wasm32")]
66thread_local! {
67    static WASM_MODULES: std::cell::RefCell<std::collections::HashMap<String, String>> =
68        std::cell::RefCell::new(std::collections::HashMap::new());
69}
70
71/// Register a module source for wasm32 `use` resolution.
72/// Called from JS via `wasm_bindgen` before `run_program`.
73#[cfg(target_arch = "wasm32")]
74pub fn register_wasm_module(path: &str, source: &str) {
75    WASM_MODULES.with(|m| m.borrow_mut().insert(path.to_string(), source.to_string()));
76}
77
78/// Look up a registered module source on wasm32.
79#[cfg(target_arch = "wasm32")]
80pub(crate) fn get_wasm_module(path: &str) -> Option<String> {
81    WASM_MODULES.with(|m| m.borrow().get(path).cloned())
82}
83
84#[cfg(target_arch = "wasm32")]
85#[inline]
86fn wasm_sleep_ms(ms: i32) {
87    if ms <= 0 {
88        return;
89    }
90
91    // Keep this in Rust/js_sys so wasm-bindgen doesn't emit `require(...)`
92    // snippets, which break worker/no-modules output.
93    let global = js_sys::global();
94    let has_sab = js_sys::Reflect::has(
95        &global,
96        &wasm_bindgen::JsValue::from_str("SharedArrayBuffer"),
97    )
98    .unwrap_or(false);
99    let has_atomics =
100        js_sys::Reflect::has(&global, &wasm_bindgen::JsValue::from_str("Atomics")).unwrap_or(false);
101    if has_sab && has_atomics {
102        let sab = js_sys::SharedArrayBuffer::new(4);
103        let i32a = js_sys::Int32Array::new(&sab);
104        if js_sys::Atomics::wait_with_timeout(&i32a, 0, 0, ms as f64).is_ok() {
105            return;
106        }
107    }
108
109    let end = js_sys::Date::now() + ms as f64;
110    while js_sys::Date::now() < end {}
111}
112
113#[cfg(target_arch = "wasm32")]
114fn wasm_fetch_sync(
115    path: &str,
116    response_type: &str,
117    return_expr: &str,
118) -> Result<wasm_bindgen::JsValue, String> {
119    let quoted = js_sys::JSON::stringify(&wasm_bindgen::JsValue::from_str(path))
120        .ok()
121        .and_then(|s| s.as_string())
122        .unwrap_or_else(|| "\"\"".to_string());
123
124    let script = format!(
125        "(function(){{\n  var xhr = new XMLHttpRequest();\n  xhr.open('GET', {quoted}, false);\n  xhr.responseType = '{response_type}';\n  xhr.send(null);\n  if ((xhr.status|0) !== 200 && (xhr.status|0) !== 0) {{ throw new Error('HTTP ' + xhr.status + ' for ' + {quoted}); }}\n  return {return_expr};\n}})()"
126    );
127
128    js_sys::eval(&script).map_err(|e| {
129        e.as_string()
130            .unwrap_or_else(|| format!("JS eval failed: {:?}", e))
131    })
132}
133
134#[cfg(target_arch = "wasm32")]
135fn wasm_fetch_bytes(path: &str) -> Result<Vec<u8>, String> {
136    let value = wasm_fetch_sync(
137        path,
138        "arraybuffer",
139        "new Uint8Array(xhr.response || new ArrayBuffer(0))",
140    )?;
141    let arr = js_sys::Uint8Array::new(&value);
142    let mut out = vec![0u8; arr.length() as usize];
143    arr.copy_to(&mut out);
144    Ok(out)
145}
146
147#[cfg(target_arch = "wasm32")]
148fn wasm_fetch_text(path: &str) -> Result<String, String> {
149    let value = wasm_fetch_sync(path, "text", "String(xhr.responseText || '')")?;
150    Ok(value.as_string().unwrap_or_default())
151}
152
153#[cfg(not(target_arch = "wasm32"))]
154mod net;
155#[cfg(not(target_arch = "wasm32"))]
156mod ssh;
157#[cfg(not(target_arch = "wasm32"))]
158mod lingtp;
159#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
160pub mod web;
161use crate::gfx::{GfxState, Light};
162use crate::parser::ast::*;
163#[cfg(target_arch = "wasm32")]
164use js_sys;
165use std::cell::RefCell;
166use std::collections::HashMap;
167use std::rc::Rc;
168// `raster` is wasm-safe (pure CPU framebuffer), so `draw_line` is available on
169// web too; `fill_triangle` is only reached from native-gated 3-D fill paths.
170use crate::gfx::raster::draw_line;
171#[cfg(not(target_arch = "wasm32"))]
172use crate::gfx::raster::fill_triangle;
173#[cfg(not(target_arch = "wasm32"))]
174use ling_audio::{AudioEngine, ToneParams, Wave};
175
176#[cfg(not(target_arch = "wasm32"))]
177use ling_audio::FftAnalyzer;
178
179#[cfg(not(target_arch = "wasm32"))]
180use ling_mic;
181
182// ─── Values ──────────────────────────────────────────────────────────────────
183
184#[derive(Debug, Clone)]
185pub enum Value {
186    Str(String),
187    Number(f64),
188    Bool(bool),
189    Unit,
190    List(Rc<Vec<Value>>),
191    Ok(Box<Value>),
192    Err(Box<Value>),
193    Fn(Vec<String>, Vec<Stmt>, Env),
194    /// `form` record instance — ordered named fields.
195    Struct {
196        name: String,
197        fields: Vec<(String, Value)>,
198    },
199    /// `choose` enum instance — variant tag plus ordered payload.
200    Variant {
201        enum_name: String,
202        variant: String,
203        payload: Vec<Value>,
204    },
205}
206
207// Interpreter-hot maps use a fast non-crypto hasher: short Thai identifier keys
208// are hashed on every variable access and builtin dispatch, where SipHash dominates.
209use rustc_hash::FxHashMap;
210type Env = FxHashMap<String, Value>;
211
212#[inline]
213fn new_env() -> Env {
214    FxHashMap::default()
215}
216
217impl std::fmt::Display for Value {
218    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219        match self {
220            Value::Str(s) => write!(f, "{s}"),
221            Value::Number(n) => {
222                if n.fract() == 0.0 && n.abs() < 1e15 {
223                    write!(f, "{}", *n as i64)
224                } else {
225                    write!(f, "{n}")
226                }
227            },
228            Value::Bool(b) => write!(f, "{b}"),
229            Value::Unit => write!(f, "()"),
230            Value::List(v) => {
231                write!(f, "[")?;
232                for (i, x) in v.iter().enumerate() {
233                    if i > 0 {
234                        write!(f, ", ")?;
235                    }
236                    write!(f, "{x}")?;
237                }
238                write!(f, "]")
239            },
240            Value::Ok(v) => write!(f, "Ok({v})"),
241            Value::Err(v) => write!(f, "Err({v})"),
242            Value::Fn(_, _, _) => write!(f, "<fn>"),
243            Value::Struct { name, fields } => {
244                write!(f, "{name} {{ ")?;
245                for (i, (k, v)) in fields.iter().enumerate() {
246                    if i > 0 {
247                        write!(f, ", ")?;
248                    }
249                    write!(f, "{k}: {v}")?;
250                }
251                write!(f, " }}")
252            },
253            Value::Variant { variant, payload, .. } => {
254                write!(f, "{variant}")?;
255                if !payload.is_empty() {
256                    write!(f, "(")?;
257                    for (i, v) in payload.iter().enumerate() {
258                        if i > 0 {
259                            write!(f, ", ")?;
260                        }
261                        write!(f, "{v}")?;
262                    }
263                    write!(f, ")")?;
264                }
265                Ok(())
266            },
267        }
268    }
269}
270
271#[cfg(target_arch = "wasm32")]
272fn wasm_unsupported_builtin(name: &str) -> Option<Value> {
273    Some(match name {
274        // interface blips (native-only today)
275        "audio_blip"
276        | "提示音"
277        | "ビープ音"
278        | "효과음"
279        | "เสียงบี๊บ"
280        | "ui_sound"
281        | "界面音"
282        | "UI音"
283        | "인터페이스음"
284        | "เสียงปุ่ม"
285        | "audio_stop_sfx"
286        | "停止音效"
287        | "効果音停止"
288        | "효과음정지"
289        | "หยุดเอฟเฟกต์ทั้งหมด"
290        | "بوق_کوتاه"
291        | "نغمة_قصيرة"
292        | "ביפ"
293        | "بلپ_آواز" => Value::Unit,
294
295        // music loading / analysis / playback / midi (native-only today)
296        "music_load"
297        | "载入音乐"
298        | "音楽読込"
299        | "음악로드"
300        | "โหลดเพลง"
301        | "music_patch"
302        | "乐器音色"
303        | "音色読込"
304        | "악기패치"
305        | "แพตช์เครื่องดนตรี"
306        | "music_lrc"
307        | "载入歌词"
308        | "歌詞読込"
309        | "가사로드"
310        | "โหลดเนื้อเพลง"
311        | "music_midi_load"
312        | "载入MIDI"
313        | "MIDI読込"
314        | "미디로드"
315        | "โหลดมิดี"
316        | "بارگذاری_موسیقی"
317        | "تحميل_الموسيقى"
318        | "טעינת_מוזיקה"
319        | "موسیقی_لوڈ" => Value::Number(-1.0),
320
321        "music_duration"
322        | "音乐时长"
323        | "音楽長さ"
324        | "음악길이"
325        | "ความยาวเพลง"
326        | "music_bpm"
327        | "节拍速度"
328        | "テンポ"
329        | "템포"
330        | "จังหวะต่อนาที"
331        | "music_pos"
332        | "音乐位置"
333        | "音楽位置"
334        | "음악위치"
335        | "ตำแหน่งเพลง"
336        | "music_mic_pitch"
337        | "麦克风音高"
338        | "マイク音程"
339        | "마이크음정"
340        | "ระดับเสียงไมค์"
341        | "music_hz"
342        | "音符频率"
343        | "音符周波数"
344        | "음표주파수"
345        | "ความถี่โน้ต"
346        | "music_pitch_score"
347        | "音准评分"
348        | "音程スコア"
349        | "음정점수"
350        | "คะแนนเสียง"
351        | "music_judge"
352        | "判定"
353        | "判定する"
354        | "판정"
355        | "ตัดสินจังหวะ"
356        | "music_midi_count"
357        | "MIDI数量"
358        | "MIDI数"
359        | "미디수"
360        | "จำนวนมิดี"
361        | "مدت_موسیقی"
362        | "مدة_الموسيقى"
363        | "משך_מוזיקה"
364        | "موسیقی_دورانیہ" => Value::Number(0.0),
365
366        "music_key"
367        | "调性"
368        | "調性"
369        | "조성"
370        | "คีย์เพลง"
371        | "music_lyric"
372        | "当前歌词"
373        | "現在歌詞"
374        | "현재가사"
375        | "เนื้อเพลงปัจจุบัน"
376        | "music_note_name"
377        | "音名"
378        | "音名称"
379        | "음이름"
380        | "ชื่อโน้ต"
381        | "music_grade_name"
382        | "判定名"
383        | "判定名称"
384        | "판정이름"
385        | "ชื่อการตัดสิน"
386        | "گام_موسیقی"
387        | "مقام_الموسيقى"
388        | "סולם_מוזיקלי"
389        | "موسیقی_کلید" => Value::Str(String::new()),
390
391        "music_onsets"
392        | "音符起点"
393        | "オンセット"
394        | "온셋"
395        | "จุดเริ่มเสียง"
396        | "music_beat_grid"
397        | "节拍网格"
398        | "ビートグリッド"
399        | "비트그리드"
400        | "กริดจังหวะ"
401        | "music_midi_notes"
402        | "MIDI音符"
403        | "MIDIノート"
404        | "미디음표"
405        | "โน้ตมิดี"
406        | "music_midi_bars"
407        | "MIDI音条"
408        | "MIDIバー"
409        | "미디바"
410        | "แท่งมิดี"
411        | "music_fft"
412        | "音乐频谱"
413        | "音楽スペクトル"
414        | "음악스펙트럼"
415        | "สเปกตรัมเพลง"
416        | "آغازهای_نت"
417        | "بدايات_النغمات"
418        | "התחלות_תווים"
419        | "نوٹ_شروعات" => Value::List(Vec::new().into()),
420
421        "music_play"
422        | "播放音乐"
423        | "音楽再生"
424        | "음악재생"
425        | "เล่นเพลง"
426        | "music_pause"
427        | "暂停音乐"
428        | "音楽一時停止"
429        | "음악일시정지"
430        | "หยุดเพลงชั่วคราว"
431        | "music_stop"
432        | "停止音乐"
433        | "音楽停止"
434        | "음악정지"
435        | "หยุดเพลง"
436        | "music_seek"
437        | "定位音乐"
438        | "音楽シーク"
439        | "음악탐색"
440        | "ค้นหาเพลง"
441        | "music_volume"
442        | "音乐音量"
443        | "音楽音量"
444        | "음악음량"
445        | "ระดับเพลง"
446        | "music_note"
447        | "弹音符"
448        | "音符演奏"
449        | "음표연주"
450        | "เล่นโน้ต"
451        | "music_note_on"
452        | "音符开始"
453        | "音符オン"
454        | "음표켜기"
455        | "โน้ตเริ่ม"
456        | "music_note_off"
457        | "音符结束"
458        | "音符オフ"
459        | "음표끄기"
460        | "โน้ตจบ"
461        | "پخش_موسیقی"
462        | "شغّل_الموسيقى"
463        | "נגן_מוזיקה"
464        | "موسیقی_چلاؤ" => Value::Unit,
465
466        // liquid sim — return handle 0 for new, Unit for everything else
467        "liquid_new"
468        | "新建液体"
469        | "液体新規"
470        | "액체생성"
471        | "สร้างของเหลว"
472        | "مایع_جدید"
473        | "سائل_جديد"
474        | "נוזל_חדש"
475        | "نیا_مائع" => Value::Number(0.0),
476        "liquid_mix"
477        | "液体混合"
478        | "液体混合度"
479        | "액체혼합"
480        | "การผสมของเหลว"
481        | "ترکیب_مایع"
482        | "مزج_سائل"
483        | "ערבוב_נוזל"
484        | "مائع_ملاؤ" => Value::Number(0.0),
485        "liquid_set_colors"
486        | "液体颜色"
487        | "液体配色"
488        | "액체색상"
489        | "สีของเหลว"
490        | "liquid_splat"
491        | "液体注入"
492        | "液体追加"
493        | "액체분사"
494        | "หยดของเหลว"
495        | "liquid_gravity"
496        | "液体重力"
497        | "液体重力ベクトル"
498        | "액체중력"
499        | "แรงโน้มถ่วงเหลว"
500        | "liquid_step"
501        | "液体步进"
502        | "液体更新"
503        | "액체스텝"
504        | "ก้าวของเหลว"
505        | "liquid_step_all"
506        | "液体全步进"
507        | "液体全更新"
508        | "전체액체스텝"
509        | "ก้าวของเหลวทั้งหมด"
510        | "liquid_rainbow"
511        | "液体彩虹"
512        | "液体虹"
513        | "액체무지개"
514        | "ของเหลวสายรุ้ง"
515        | "liquid_draw"
516        | "绘制液体"
517        | "液体描画"
518        | "액체그리기"
519        | "วาดของเหลว"
520        | "liquid_draw_surface"
521        | "液体贴面"
522        | "液体曲面"
523        | "액체곡면"
524        | "ของเหลวบนพื้นผิว"
525        | "تنظیم_رنگ_مایع"
526        | "عيّن_ألوان_السائل"
527        | "קבע_צבעי_נוזל"
528        | "مائع_رنگ_مقرر_کرو" => Value::Unit,
529
530        // ── game AI: neural networks ─────────────────────────────────────────
531        "nn_new"
532        | "建神经网"
533        | "ニューラル作成"
534        | "신경망생성"
535        | "สร้างโครงข่าย"
536        | "nn_load"
537        | "载入网"
538        | "網読込"
539        | "신경망불러오기"
540        | "โหลดโครงข่าย"
541        | "شبکه_جدید"
542        | "شبكة_جديدة"
543        | "רשת_חדשה"
544        | "نئی_نیورل_نیٹ" => Value::Number(-1.0),
545        "nn_forward"
546        | "神经前向"
547        | "順伝播"
548        | "순전파"
549        | "ส่งต่อโครงข่าย"
550        | "پیش‌روی_شبکه"
551        | "تمرير_أمامي"
552        | "העברה_קדימה"
553        | "فارورڈ_پاس" => Value::List(Vec::new().into()),
554        "nn_train"
555        | "训练网"
556        | "ニューラル学習"
557        | "신경망학습"
558        | "ฝึกโครงข่าย"
559        | "nn_dense"
560        | "密集层"
561        | "密層追加"
562        | "밀집층"
563        | "ชั้นหนาแน่น"
564        | "آموزش_شبکه"
565        | "درّب_الشبكة"
566        | "אמן_רשת"
567        | "نیٹ_ٹریننگ" => Value::Number(0.0),
568        "nn_save"
569        | "保存网"
570        | "網保存"
571        | "신경망저장"
572        | "บันทึกโครงข่าย"
573        | "ذخیره_شبکه"
574        | "احفظ_الشبكة"
575        | "שמור_רשת"
576        | "نیٹ_محفوظ_کرو" => Value::Bool(false),
577
578        // ── game AI: behavior trees ─────────────────────────────────────────
579        "bt_build"
580        | "建行为树"
581        | "行動木構築"
582        | "행동트리구성"
583        | "สร้างต้นไม้พฤติกรรม"
584        | "ساخت_درخت_رفتار"
585        | "ابنِ_شجرة_السلوك"
586        | "בנה_עץ_התנהגות"
587        | "بی_ٹی_تعمیر" => Value::Number(-1.0),
588        "bt_tick"
589        | "行为树滴答"
590        | "行動木更新"
591        | "행동트리틱"
592        | "เดินต้นไม้พฤติกรรม"
593        | "تیک_درخت_رفتار"
594        | "نبضة_شجرة_السلوك"
595        | "טיק_עץ_התנהגות"
596        | "بی_ٹی_ٹک" => Value::Str(String::new()),
597        "bt_status"
598        | "行为树状态"
599        | "行動木状態"
600        | "행동트리상태"
601        | "สถานะต้นไม้พฤติกรรม"
602        | "وضعیت_درخت_رفتار"
603        | "حالة_شجرة_السلوك"
604        | "סטטוס_עץ_התנהגות"
605        | "بی_ٹی_حالت" => Value::Number(0.0),
606        "bt_set"
607        | "设事实"
608        | "事実設定"
609        | "사실설정"
610        | "ตั้งข้อเท็จจริง"
611        | "تنظیم_واقعیت"
612        | "عيّن_حقيقة"
613        | "קבע_עובדה"
614        | "بی_ٹی_سیٹ" => Value::Unit,
615
616        // ── game AI: dialog LLM ─────────────────────────────────────────────
617        "dialog_new"
618        | "建对话模型"
619        | "対話モデル作成"
620        | "대화모델생성"
621        | "สร้างโมเดลสนทนา"
622        | "dialog_load_model"
623        | "对话载模"
624        | "対話モデル読込"
625        | "대화모델불러오기"
626        | "โหลดโมเดลสนทนา"
627        | "dialog_train"
628        | "对话训练"
629        | "対話訓練"
630        | "대화훈련"
631        | "ฝึกสนทนา"
632        | "dialog_load"
633        | "对话载入"
634        | "対話読込"
635        | "대화불러오기"
636        | "โหลดชุดสนทนา"
637        | "مدل_گفتگوی_جدید"
638        | "نموذج_حوار_جديد"
639        | "מודל_דיאלוג_חדש"
640        | "نیا_مکالمہ_ماڈل" => Value::Number(-1.0),
641        "dialog_say"
642        | "对话生成"
643        | "対話生成"
644        | "대화생성"
645        | "พูดสนทนา"
646        | "بگو"
647        | "قل"
648        | "אמור"
649        | "کہو" => Value::Str(String::new()),
650        "dialog_save"
651        | "对话存模"
652        | "対話モデル保存"
653        | "대화모델저장"
654        | "บันทึกโมเดลสนทนา"
655        | "ذخیره_مدل_گفتگو"
656        | "احفظ_نموذج_الحوار"
657        | "שמור_מודל_דיאלוג"
658        | "مکالمہ_ماڈل_محفوظ" => Value::Bool(false),
659        "dialog_learn"
660        | "对话学习"
661        | "対話学習"
662        | "대화학습"
663        | "เรียนรู้สนทนา"
664        | "یادگیری_گفتگو"
665        | "تعلّم_الحوار"
666        | "למד_דיאלוג"
667        | "مکالمہ_سیکھو" => Value::Unit,
668
669        // ── networking ──────────────────────────────────────────────────────
670        "net_connect"
671        | "联网"
672        | "ネット接続"
673        | "네트연결"
674        | "เชื่อมเน็ต"
675        | "net_listen"
676        | "监听"
677        | "待機"
678        | "리슨"
679        | "รอรับ"
680        | "net_send"
681        | "发送"
682        | "送信"
683        | "전송"
684        | "ส่ง"
685        | "اتصال_شبکه"
686        | "اتصل_بالشبكة"
687        | "התחבר_לרשת"
688        | "نیٹ_کنیکٹ" => Value::Number(-1.0),
689        "net_recv"
690        | "接收"
691        | "受信"
692        | "수신"
693        | "รับ"
694        | "net_status"
695        | "连接状态"
696        | "接続状態"
697        | "연결상태"
698        | "สถานะการเชื่อม"
699        | "دریافت_شبکه"
700        | "استقبل_من_الشبكة"
701        | "קבל_מרשת"
702        | "نیٹ_وصول" => Value::Str(String::new()),
703        "net_discover"
704        | "发现"
705        | "探索"
706        | "검색"
707        | "ค้นหาเครือข่าย"
708        | "کشف_شبکه"
709        | "اكتشف_الشبكة"
710        | "גלה_רשת"
711        | "نیٹ_دریافت" => Value::List(Vec::new().into()),
712        "net_close"
713        | "断开"
714        | "切断"
715        | "연결종료"
716        | "ตัดเชื่อม"
717        | "net_test"
718        | "测连接"
719        | "接続テスト"
720        | "연결테스트"
721        | "ทดสอบเน็ต"
722        | "بستن_شبکه"
723        | "أغلق_الشبكة"
724        | "סגור_רשת"
725        | "نیٹ_بند" => Value::Number(0.0),
726
727        // catch-all: any other native-only builtin silently no-ops on wasm32
728        _ => Value::Unit,
729    })
730}
731
732// ─── Control flow ────────────────────────────────────────────────────────────
733
734#[derive(Debug)]
735pub(crate) enum EvalErr {
736    Runtime(String),
737    Return(Value),
738    #[allow(dead_code)] // reserved for future `break` statement support
739    Break,
740}
741
742impl From<String> for EvalErr {
743    fn from(s: String) -> Self {
744        EvalErr::Runtime(s)
745    }
746}
747
748type EvalResult = Result<Value, EvalErr>;
749
750/// RFC 4648 base32 encode (no padding) — used for TOTP secrets.
751#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
752fn base32_encode(data: &[u8]) -> String {
753    const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
754    let mut out = String::new();
755    let mut buffer: u32 = 0;
756    let mut bits = 0u32;
757    for &b in data {
758        buffer = (buffer << 8) | b as u32;
759        bits += 8;
760        while bits >= 5 {
761            bits -= 5;
762            out.push(ALPHABET[((buffer >> bits) & 0x1f) as usize] as char);
763        }
764    }
765    if bits > 0 {
766        out.push(ALPHABET[((buffer << (5 - bits)) & 0x1f) as usize] as char);
767    }
768    out
769}
770
771/// RFC 4648 base32 decode (case-insensitive, ignores padding/whitespace).
772#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
773fn base32_decode(s: &str) -> Option<Vec<u8>> {
774    let mut buffer: u32 = 0;
775    let mut bits = 0u32;
776    let mut out = Vec::new();
777    for c in s.chars() {
778        if c == '=' || c.is_whitespace() {
779            continue;
780        }
781        let v = match c.to_ascii_uppercase() {
782            'A'..='Z' => c.to_ascii_uppercase() as u32 - 'A' as u32,
783            '2'..='7' => c as u32 - '2' as u32 + 26,
784            _ => return None,
785        };
786        buffer = (buffer << 5) | v;
787        bits += 5;
788        if bits >= 8 {
789            bits -= 8;
790            out.push((buffer >> bits) as u8);
791        }
792    }
793    Some(out)
794}
795
796/// One RFC 6238 TOTP code (HMAC-SHA1, 6 digits) for the given 30s time step.
797#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
798fn totp_code(secret_b32: &str, step: u64) -> Option<String> {
799    let key = base32_decode(secret_b32)?;
800    let msg = step.to_be_bytes();
801    let mac = hmac_sha1(&key, &msg);
802    let offset = (mac[19] & 0x0f) as usize;
803    let bin = ((mac[offset] as u32 & 0x7f) << 24)
804        | ((mac[offset + 1] as u32) << 16)
805        | ((mac[offset + 2] as u32) << 8)
806        | (mac[offset + 3] as u32);
807    Some(format!("{:06}", bin % 1_000_000))
808}
809
810/// TOTP verify with a ±1 step window (tolerates minor clock skew).
811#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
812fn totp_check(secret_b32: &str, code: &str) -> bool {
813    if code.len() != 6 || !code.bytes().all(|b| b.is_ascii_digit()) {
814        return false;
815    }
816    let now_step = (crate::runtime::now_secs() as u64) / 30;
817    for delta in [-1i64, 0, 1] {
818        let step = (now_step as i64 + delta) as u64;
819        if let Some(expected) = totp_code(secret_b32, step) {
820            // constant-time-ish compare (fixed 6-char length)
821            if expected.as_bytes().ct_eq_str(code.as_bytes()) {
822                return true;
823            }
824        }
825    }
826    false
827}
828
829/// HMAC-SHA1 built on the `hmac`+`sha1` crates (both via the web feature).
830#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
831fn hmac_sha1(key: &[u8], msg: &[u8]) -> [u8; 20] {
832    use hmac::{Mac, SimpleHmac};
833    let mut mac = SimpleHmac::<sha1::Sha1>::new_from_slice(key).expect("hmac key");
834    mac.update(msg);
835    let out = mac.finalize().into_bytes();
836    let mut arr = [0u8; 20];
837    arr.copy_from_slice(&out);
838    arr
839}
840
841/// Tiny fixed-length constant-time byte compare helper for TOTP codes.
842#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
843trait CtEqStr {
844    fn ct_eq_str(&self, other: &[u8]) -> bool;
845}
846#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
847impl CtEqStr for [u8] {
848    fn ct_eq_str(&self, other: &[u8]) -> bool {
849        if self.len() != other.len() {
850            return false;
851        }
852        let mut diff = 0u8;
853        for (a, b) in self.iter().zip(other.iter()) {
854            diff |= a ^ b;
855        }
856        diff == 0
857    }
858}
859
860/// Percent-decodes a URL query component (`+` → space, `%41` → `A`).
861fn url_decode(s: &str) -> String {
862    let bytes = s.as_bytes();
863    let mut out = Vec::with_capacity(bytes.len());
864    let mut i = 0;
865    while i < bytes.len() {
866        match bytes[i] {
867            b'+' => {
868                out.push(b' ');
869                i += 1;
870            },
871            b'%' if i + 2 < bytes.len() => {
872                let hi = (bytes[i + 1] as char).to_digit(16);
873                let lo = (bytes[i + 2] as char).to_digit(16);
874                if let (Some(h), Some(l)) = (hi, lo) {
875                    out.push((h * 16 + l) as u8);
876                    i += 3;
877                } else {
878                    out.push(bytes[i]);
879                    i += 1;
880                }
881            },
882            b => {
883                out.push(b);
884                i += 1;
885            },
886        }
887    }
888    String::from_utf8_lossy(&out).into_owned()
889}
890
891/// Maps Ling values to owned rusqlite parameter values for `db_exec`/`db_query`.
892#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
893fn values_to_sql_params(args: &[Value]) -> Vec<ling_http::rusqlite::types::Value> {
894    use ling_http::rusqlite::types::Value as Sql;
895    args.iter()
896        .map(|v| match v {
897            Value::Number(n) if n.fract() == 0.0 && n.abs() < 9e15 => Sql::Integer(*n as i64),
898            Value::Number(n) => Sql::Real(*n),
899            Value::Bool(b) => Sql::Integer(*b as i64),
900            other => Sql::Text(other.to_string()),
901        })
902        .collect()
903}
904
905// GfxState is now defined in crate::gfx — see src/gfx/mod.rs.
906
907// ─── SVG writer ───────────────────────────────────────────────────────────────
908
909struct SvgWriter {
910    path: String,
911    width: f64,
912    height: f64,
913    elements: Vec<String>,
914}
915
916impl SvgWriter {
917    fn new(path: String, width: f64, height: f64) -> Self {
918        let bg = format!("<rect width=\"{width}\" height=\"{height}\" fill=\"#0a0a0a\"/>");
919        Self { path, width, height, elements: vec![bg] }
920    }
921
922    fn save(&self) -> std::io::Result<()> {
923        let w = self.width;
924        let h = self.height;
925        let mut out = format!(
926            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
927             <svg xmlns=\"http://www.w3.org/2000/svg\" \
928             width=\"{w}\" height=\"{h}\" viewBox=\"0 0 {w} {h}\">\n"
929        );
930        for elem in &self.elements {
931            out.push_str("  ");
932            out.push_str(elem);
933            out.push('\n');
934        }
935        out.push_str("</svg>\n");
936        // Create parent directory if it doesn't exist.
937        if let Some(parent) = std::path::Path::new(&self.path).parent() {
938            if !parent.as_os_str().is_empty() {
939                let _ = std::fs::create_dir_all(parent);
940            }
941        }
942        std::fs::write(&self.path, out.as_bytes())
943    }
944}
945
946fn hsl_to_hex(h: f64, s: f64, l: f64) -> String {
947    let s = s / 100.0;
948    let l = l / 100.0;
949    let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
950    let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
951    let m = l - c / 2.0;
952    let (r1, g1, b1) = if h < 60.0 {
953        (c, x, 0.0)
954    } else if h < 120.0 {
955        (x, c, 0.0)
956    } else if h < 180.0 {
957        (0.0, c, x)
958    } else if h < 240.0 {
959        (0.0, x, c)
960    } else if h < 300.0 {
961        (x, 0.0, c)
962    } else {
963        (c, 0.0, x)
964    };
965    let r = ((r1 + m) * 255.0).round() as u8;
966    let g = ((g1 + m) * 255.0).round() as u8;
967    let b = ((b1 + m) * 255.0).round() as u8;
968    format!("#{r:02x}{g:02x}{b:02x}")
969}
970
971// ─── Procedural texture helpers ───────────────────────────────────────────────
972
973fn tex_hash(x: i32, y: i32, seed: u32) -> f32 {
974    let mut h = (x as u32)
975        .wrapping_add((y as u32).wrapping_mul(2654435769))
976        .wrapping_add(seed.wrapping_mul(1234567891));
977    h ^= h >> 16;
978    h = h.wrapping_mul(0x45d9f3b);
979    h ^= h >> 16;
980    h as f32 / u32::MAX as f32
981}
982
983fn tex_vnoise(x: f32, y: f32, seed: u32) -> f32 {
984    let xi = x.floor() as i32;
985    let yi = y.floor() as i32;
986    let sm = |t: f32| t * t * (3.0 - 2.0 * t);
987    let xf = sm(x - xi as f32);
988    let yf = sm(y - yi as f32);
989    let a = tex_hash(xi, yi, seed);
990    let b = tex_hash(xi + 1, yi, seed);
991    let c = tex_hash(xi, yi + 1, seed);
992    let d = tex_hash(xi + 1, yi + 1, seed);
993    a + (b - a) * xf + (c - a) * yf + (a - b - c + d) * xf * yf
994}
995
996fn tex_fbm(x: f32, y: f32, octaves: u32, seed: u32) -> f32 {
997    let mut v = 0.0f32;
998    let mut amp = 0.5f32;
999    let mut f = 1.0f32;
1000    for i in 0..octaves {
1001        v += tex_vnoise(x * f, y * f, seed.wrapping_add(i * 7919)) * amp;
1002        amp *= 0.5;
1003        f *= 2.0;
1004    }
1005    v
1006}
1007
1008fn tex_palette(name: &str, t: f32) -> [f32; 3] {
1009    let (a, b, c, d): ([f32; 3], [f32; 3], [f32; 3], [f32; 3]) = match name {
1010        "fire" => (
1011            [0.8, 0.4, 0.1],
1012            [0.7, 0.3, 0.1],
1013            [1.0, 0.5, 0.3],
1014            [0.0, 0.5, 0.8],
1015        ),
1016        "ocean" => (
1017            [0.1, 0.4, 0.7],
1018            [0.3, 0.3, 0.4],
1019            [0.8, 1.0, 0.5],
1020            [0.3, 0.0, 0.6],
1021        ),
1022        "psychedelic" => (
1023            [0.5, 0.5, 0.5],
1024            [0.8, 0.8, 0.8],
1025            [1.0, 1.3, 0.7],
1026            [0.0, 0.15, 0.3],
1027        ),
1028        "neon" => (
1029            [0.5, 0.5, 0.5],
1030            [0.5, 0.5, 0.5],
1031            [2.0, 1.0, 0.0],
1032            [0.5, 0.2, 0.25],
1033        ),
1034        "forest" => (
1035            [0.3, 0.5, 0.2],
1036            [0.2, 0.3, 0.1],
1037            [1.0, 0.5, 0.8],
1038            [0.1, 0.3, 0.6],
1039        ),
1040        _ => (
1041            [0.5, 0.5, 0.5],
1042            [0.5, 0.5, 0.5],
1043            [1.0, 1.0, 1.0],
1044            [0.0, 0.333, 0.667],
1045        ),
1046    };
1047    [0, 1, 2]
1048        .map(|i| (a[i] + b[i] * (std::f32::consts::TAU * (c[i] * t + d[i])).cos()).clamp(0.0, 1.0))
1049}
1050
1051/// Map a physical key to a typed character for ling-ui text input (lowercase).
1052#[cfg(not(target_arch = "wasm32"))]
1053// Full US-QWERTY keyboard → char, shift-aware. `key_char` only ever emits
1054// printable ASCII (never Tab/Enter/control bytes — those keys have no case
1055// here at all) so anything read through `text_poll` is safe by construction
1056// to drop straight into the game's tab/comma/semicolon/pipe-framed wire
1057// protocols without needing per-keystroke sanitization.
1058fn key_char(k: minifb::Key, shift: bool) -> Option<char> {
1059    use minifb::Key::*;
1060    let base = match k {
1061        A => 'a',
1062        B => 'b',
1063        C => 'c',
1064        D => 'd',
1065        E => 'e',
1066        F => 'f',
1067        G => 'g',
1068        H => 'h',
1069        I => 'i',
1070        J => 'j',
1071        K => 'k',
1072        L => 'l',
1073        M => 'm',
1074        N => 'n',
1075        O => 'o',
1076        P => 'p',
1077        Q => 'q',
1078        R => 'r',
1079        S => 's',
1080        T => 't',
1081        U => 'u',
1082        V => 'v',
1083        W => 'w',
1084        X => 'x',
1085        Y => 'y',
1086        Z => 'z',
1087        Key0 => '0',
1088        Key1 => '1',
1089        Key2 => '2',
1090        Key3 => '3',
1091        Key4 => '4',
1092        Key5 => '5',
1093        Key6 => '6',
1094        Key7 => '7',
1095        Key8 => '8',
1096        Key9 => '9',
1097        Space => ' ',
1098        Minus => '-',
1099        Equal => '=',
1100        Period => '.',
1101        Comma => ',',
1102        Slash => '/',
1103        Backslash => '\\',
1104        Semicolon => ';',
1105        Apostrophe => '\'',
1106        LeftBracket => '[',
1107        RightBracket => ']',
1108        Backquote => '`',
1109        _ => return None,
1110    };
1111    if !shift {
1112        return Some(base);
1113    }
1114    Some(match base {
1115        'a'..='z' => base.to_ascii_uppercase(),
1116        '0' => ')',
1117        '1' => '!',
1118        '2' => '@',
1119        '3' => '#',
1120        '4' => '$',
1121        '5' => '%',
1122        '6' => '^',
1123        '7' => '&',
1124        '8' => '*',
1125        '9' => '(',
1126        '-' => '_',
1127        '=' => '+',
1128        '.' => '>',
1129        ',' => '<',
1130        '/' => '?',
1131        '\\' => '|',
1132        ';' => ':',
1133        '\'' => '"',
1134        '[' => '{',
1135        ']' => '}',
1136        '`' => '~',
1137        other => other,
1138    })
1139}
1140
1141/// Win32 virtual-key code → char, mirroring `key_char` exactly but keyed by
1142/// VK code instead of `minifb::Key` — used by the `GetAsyncKeyState` input
1143/// path (see `os_key_down` / the `topmost_window` fallback in `text_poll`).
1144#[cfg(all(not(target_arch = "wasm32"), windows))]
1145fn vk_char(vk: i32, shift: bool) -> Option<char> {
1146    let base = match vk {
1147        0x41..=0x5A => (b'a' + (vk - 0x41) as u8) as char, // 'A'..'Z'
1148        0x30..=0x39 => (b'0' + (vk - 0x30) as u8) as char, // '0'..'9'
1149        0x20 => ' ',                                       // VK_SPACE
1150        0xBD => '-',                                       // VK_OEM_MINUS
1151        0xBB => '=',                                       // VK_OEM_PLUS (unshifted '=')
1152        0xBE => '.',                                       // VK_OEM_PERIOD
1153        0xBC => ',',                                       // VK_OEM_COMMA
1154        0xBF => '/',                                       // VK_OEM_2
1155        0xDC => '\\',                                      // VK_OEM_5
1156        0xBA => ';',                                       // VK_OEM_1
1157        0xDE => '\'',                                      // VK_OEM_7
1158        0xDB => '[',                                       // VK_OEM_4
1159        0xDD => ']',                                       // VK_OEM_6
1160        0xC0 => '`',                                       // VK_OEM_3
1161        _ => return None,
1162    };
1163    if !shift {
1164        return Some(base);
1165    }
1166    Some(match base {
1167        'a'..='z' => base.to_ascii_uppercase(),
1168        '0' => ')',
1169        '1' => '!',
1170        '2' => '@',
1171        '3' => '#',
1172        '4' => '$',
1173        '5' => '%',
1174        '6' => '^',
1175        '7' => '&',
1176        '8' => '*',
1177        '9' => '(',
1178        '-' => '_',
1179        '=' => '+',
1180        '.' => '>',
1181        ',' => '<',
1182        '/' => '?',
1183        '\\' => '|',
1184        ';' => ':',
1185        '\'' => '"',
1186        '[' => '{',
1187        ']' => '}',
1188        '`' => '~',
1189        other => other,
1190    })
1191}
1192
1193/// The VK codes `text_poll`'s `GetAsyncKeyState` fallback scans each frame —
1194/// every key `vk_char` can turn into a character.
1195#[cfg(all(not(target_arch = "wasm32"), windows))]
1196const TEXT_POLL_VKS: &[i32] = &[
1197    0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
1198    0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
1199    0x36, 0x37, 0x38, 0x39, 0x20, 0xBD, 0xBB, 0xBE, 0xBC, 0xBF, 0xDC, 0xBA, 0xDE, 0xDB, 0xDD, 0xC0,
1200];
1201/// VK_BACK — polled separately from `TEXT_POLL_VKS` since it edits (pops)
1202/// rather than appends.
1203#[cfg(all(not(target_arch = "wasm32"), windows))]
1204const VK_BACK: i32 = 0x08;
1205#[cfg(all(not(target_arch = "wasm32"), windows))]
1206const VK_SHIFT: i32 = 0x10;
1207
1208/// Read the OS's live key-state table directly — unlike minifb's
1209/// `is_key_down`/`get_keys_pressed` (populated from `WM_KEYDOWN`, which only
1210/// arrives at a window holding real Win32 keyboard focus), this works even
1211/// when the borderless-fullscreen window is topmost/visually in front but
1212/// didn't actually win the OS focus fight (Windows' foreground-lock — see
1213/// `force_window_focus`). High bit of `GetAsyncKeyState` = currently down.
1214#[cfg(all(not(target_arch = "wasm32"), windows))]
1215fn os_key_down(vk: i32) -> bool {
1216    unsafe {
1217        extern "system" {
1218            fn GetAsyncKeyState(vkey: i32) -> i16;
1219        }
1220        (GetAsyncKeyState(vk) as u16 & 0x8000) != 0
1221    }
1222}
1223
1224/// True when `hwnd` is the actual OS foreground window. `GetAsyncKeyState`
1225/// (see `os_key_down`) reads the global key-state table regardless of which
1226/// window is focused, so the `topmost_window` input fallback must check this
1227/// before trusting it — otherwise alt-tabbing away from the topmost/
1228/// fullscreen window to type in some other app would still feed keystrokes
1229/// into the game sitting behind it, since nothing about GetAsyncKeyState
1230/// itself would notice the window lost focus.
1231#[cfg(all(not(target_arch = "wasm32"), windows))]
1232fn window_is_foreground(hwnd: isize) -> bool {
1233    unsafe {
1234        extern "system" {
1235            fn GetForegroundWindow() -> isize;
1236        }
1237        hwnd != 0 && GetForegroundWindow() == hwnd
1238    }
1239}
1240
1241/// Delay (seconds) a key must stay held before it starts auto-repeating —
1242/// matches the initial "hesitation" of a normal OS text field.
1243#[cfg(all(not(target_arch = "wasm32"), windows))]
1244const KEY_REPEAT_DELAY: f64 = 0.45;
1245/// Interval (seconds) between repeats once a held key is auto-repeating.
1246#[cfg(all(not(target_arch = "wasm32"), windows))]
1247const KEY_REPEAT_RATE: f64 = 0.045;
1248
1249/// Edge/repeat detector for the `GetAsyncKeyState` text-input fallback:
1250/// fires true on the initial press, then again every `KEY_REPEAT_RATE`
1251/// seconds once the key has been held past `KEY_REPEAT_DELAY` — the same
1252/// press-then-hold-to-repeat behavior minifb's `KeyRepeat::Yes` gives the
1253/// normal (focused-window) path, which this fallback otherwise lacks since
1254/// it does its own from-scratch edge detection per VK code.
1255#[cfg(all(not(target_arch = "wasm32"), windows))]
1256fn key_repeat_fire(
1257    now: f64,
1258    down: bool,
1259    was_down: bool,
1260    down_since: &mut f64,
1261    last_fire: &mut f64,
1262) -> bool {
1263    if !down {
1264        return false;
1265    }
1266    if !was_down {
1267        *down_since = now;
1268        *last_fire = now;
1269        return true;
1270    }
1271    if now - *down_since >= KEY_REPEAT_DELAY && now - *last_fire >= KEY_REPEAT_RATE {
1272        *last_fire = now;
1273        return true;
1274    }
1275    false
1276}
1277
1278/// Map the same key-name strings `key_down`/`key_pressed` already accept
1279/// (see `str_to_minifb_key`) to Win32 virtual-key codes, for the
1280/// `GetAsyncKeyState` fallback path.
1281#[cfg(all(not(target_arch = "wasm32"), windows))]
1282fn str_to_vk(name: &str) -> Option<i32> {
1283    Some(match name {
1284        "numpad0" | "kp0" => 0x60,
1285        "numpad1" | "kp1" => 0x61,
1286        "numpad2" | "kp2" => 0x62,
1287        "numpad3" | "kp3" => 0x63,
1288        "numpad4" | "kp4" => 0x64,
1289        "numpad5" | "kp5" => 0x65,
1290        "numpad6" | "kp6" => 0x66,
1291        "numpad7" | "kp7" => 0x67,
1292        "numpad8" | "kp8" => 0x68,
1293        "numpad9" | "kp9" => 0x69,
1294        "numpad+" | "kp+" => 0x6B,
1295        "numpad-" | "kp-" => 0x6D,
1296        "numpad*" | "kp*" => 0x6A,
1297        "numpad/" | "kp/" => 0x6F,
1298        "left" => 0x25,
1299        "up" => 0x26,
1300        "right" => 0x27,
1301        "down" => 0x28,
1302        "space" => 0x20,
1303        "enter" => 0x0D,
1304        "escape" => 0x1B,
1305        "pageup" => 0x21,
1306        "pagedown" => 0x22,
1307        "lshift" | "leftshift" => 0xA0,
1308        "rshift" | "rightshift" => 0xA1,
1309        "lctrl" | "leftctrl" => 0xA2,
1310        "rctrl" | "rightctrl" => 0xA3,
1311        "lalt" | "leftalt" => 0xA4,
1312        "ralt" | "rightalt" => 0xA5,
1313        "tab" => 0x09,
1314        "backspace" => VK_BACK,
1315        "delete" => 0x2E,
1316        "insert" => 0x2D,
1317        "home" => 0x24,
1318        "end" => 0x23,
1319        "a" => 0x41,
1320        "b" => 0x42,
1321        "c" => 0x43,
1322        "d" => 0x44,
1323        "e" => 0x45,
1324        "f" => 0x46,
1325        "g" => 0x47,
1326        "h" => 0x48,
1327        "i" => 0x49,
1328        "j" => 0x4A,
1329        "k" => 0x4B,
1330        "l" => 0x4C,
1331        "m" => 0x4D,
1332        "n" => 0x4E,
1333        "o" => 0x4F,
1334        "p" => 0x50,
1335        "q" => 0x51,
1336        "r" => 0x52,
1337        "s" => 0x53,
1338        "t" => 0x54,
1339        "u" => 0x55,
1340        "v" => 0x56,
1341        "w" => 0x57,
1342        "x" => 0x58,
1343        "y" => 0x59,
1344        "z" => 0x5A,
1345        "0" => 0x30,
1346        "1" => 0x31,
1347        "2" => 0x32,
1348        "3" => 0x33,
1349        "4" => 0x34,
1350        "5" => 0x35,
1351        "6" => 0x36,
1352        "7" => 0x37,
1353        "8" => 0x38,
1354        "9" => 0x39,
1355        _ => return None,
1356    })
1357}
1358
1359/// Lowercase-hex encode bytes (the wire format for crypto values in Ling).
1360fn hex_encode(bytes: &[u8]) -> String {
1361    let mut s = String::with_capacity(bytes.len() * 2);
1362    for b in bytes {
1363        s.push_str(&format!("{b:02x}"));
1364    }
1365    s
1366}
1367
1368/// Decode a `ling convert` blob: base64 → zlib-inflate → raw little-endian bytes.
1369#[cfg(not(target_arch = "wasm32"))]
1370fn decode_blob(s: &str) -> Result<Vec<u8>, String> {
1371    use base64::Engine as _;
1372    use std::io::Read as _;
1373    let comp = base64::engine::general_purpose::STANDARD
1374        .decode(s.trim())
1375        .map_err(|e| format!("base64: {e}"))?;
1376    let mut out = Vec::new();
1377    flate2::read::ZlibDecoder::new(&comp[..])
1378        .read_to_end(&mut out)
1379        .map_err(|e| format!("inflate: {e}"))?;
1380    Ok(out)
1381}
1382
1383/// Decode a lowercase/uppercase hex string to bytes (ignores malformed tail).
1384#[cfg(not(target_arch = "wasm32"))]
1385fn hex_decode(s: &str) -> Vec<u8> {
1386    let s = s.trim();
1387    (0..s.len() / 2)
1388        .filter_map(|i| u8::from_str_radix(s.get(i * 2..i * 2 + 2)?, 16).ok())
1389        .collect()
1390}
1391
1392/// Decode a hex string into a fixed 32-byte key (zero-padded / truncated).
1393#[cfg(not(target_arch = "wasm32"))]
1394fn hex_to_32(s: &str) -> [u8; 32] {
1395    let v = hex_decode(s);
1396    let mut out = [0u8; 32];
1397    let n = v.len().min(32);
1398    out[..n].copy_from_slice(&v[..n]);
1399    out
1400}
1401
1402/// Builds a minimal, valid PDF with one page per input image (decoded via
1403/// `image`, page sized to the image's own pixel dimensions), backing the
1404/// `pdf_from_images` builtin. No PDF crate: each page is three objects
1405/// (Page / Contents / Image XObject) hand-written directly, with the image
1406/// stream flate-compressed raw RGB8 (`/Filter /FlateDecode /ColorSpace
1407/// /DeviceRGB /BitsPerComponent 8`) — no separate re-encoding step beyond
1408/// what `flate2` (already a dependency) does for the stream itself.
1409#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1410fn build_pdf_from_images(
1411    paths: &[String],
1412    out_path: &str,
1413) -> Result<(), Box<dyn std::error::Error>> {
1414    use std::io::Write as _;
1415
1416    struct PageImg {
1417        w: u32,
1418        h: u32,
1419        compressed: Vec<u8>,
1420    }
1421
1422    let mut pages = Vec::with_capacity(paths.len());
1423    for p in paths {
1424        let img = image::open(p)?.to_rgb8();
1425        let (w, h) = (img.width(), img.height());
1426        let mut enc = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
1427        enc.write_all(img.as_raw())?;
1428        pages.push(PageImg { w, h, compressed: enc.finish()? });
1429    }
1430
1431    let n = pages.len();
1432    // Object numbers: 1=Catalog, 2=Pages, then per page i (0-indexed):
1433    // 3+3i=Page, 4+3i=Contents, 5+3i=Image XObject.
1434    let page_nums: Vec<u32> = (0..n).map(|i| 3 + (i as u32) * 3).collect();
1435    let total_objs = 2 + n * 3;
1436    let mut off = vec![0usize; total_objs + 1]; // 1-based; off[0] unused
1437
1438    let mut buf: Vec<u8> = Vec::new();
1439    buf.extend_from_slice(b"%PDF-1.4\n%\xE2\xE3\xCF\xD3\n");
1440
1441    off[1] = buf.len();
1442    buf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
1443
1444    off[2] = buf.len();
1445    let kids = page_nums
1446        .iter()
1447        .map(|n| format!("{n} 0 R"))
1448        .collect::<Vec<_>>()
1449        .join(" ");
1450    buf.extend_from_slice(
1451        format!("2 0 obj\n<< /Type /Pages /Kids [{kids}] /Count {n} >>\nendobj\n").as_bytes(),
1452    );
1453
1454    for (i, page) in pages.iter().enumerate() {
1455        let page_num = page_nums[i];
1456        let content_num = page_num + 1;
1457        let image_num = page_num + 2;
1458        let (w, h) = (page.w, page.h);
1459
1460        off[page_num as usize] = buf.len();
1461        buf.extend_from_slice(format!(
1462            "{page_num} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 {w} {h}] /Resources << /XObject << /Im0 {image_num} 0 R >> >> /Contents {content_num} 0 R >>\nendobj\n"
1463        ).as_bytes());
1464
1465        let content = format!("q {w} 0 0 {h} 0 0 cm /Im0 Do Q");
1466        off[content_num as usize] = buf.len();
1467        buf.extend_from_slice(
1468            format!(
1469                "{content_num} 0 obj\n<< /Length {} >>\nstream\n{content}\nendstream\nendobj\n",
1470                content.len()
1471            )
1472            .as_bytes(),
1473        );
1474
1475        off[image_num as usize] = buf.len();
1476        buf.extend_from_slice(format!(
1477            "{image_num} 0 obj\n<< /Type /XObject /Subtype /Image /Width {w} /Height {h} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /FlateDecode /Length {} >>\nstream\n",
1478            page.compressed.len()
1479        ).as_bytes());
1480        buf.extend_from_slice(&page.compressed);
1481        buf.extend_from_slice(b"\nendstream\nendobj\n");
1482    }
1483
1484    let xref_offset = buf.len();
1485    buf.extend_from_slice(format!("xref\n0 {}\n", total_objs + 1).as_bytes());
1486    buf.extend_from_slice(b"0000000000 65535 f \n");
1487    for entry in off.iter().skip(1) {
1488        buf.extend_from_slice(format!("{entry:010} 00000 n \n").as_bytes());
1489    }
1490    buf.extend_from_slice(
1491        format!(
1492            "trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref_offset}\n%%EOF",
1493            total_objs + 1
1494        )
1495        .as_bytes(),
1496    );
1497
1498    std::fs::write(out_path, buf)?;
1499    Ok(())
1500}
1501
1502fn tex_rgb(r: f32, g: f32, b: f32) -> u32 {
1503    ((r * 255.0) as u32) << 16 | ((g * 255.0) as u32) << 8 | (b * 255.0) as u32
1504}
1505
1506// ─── 3D Perlin Noise (Improved Perlin 2002) ───────────────────────────────────
1507
1508const PERM: [u8; 512] = [
1509    151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
1510    142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219,
1511    203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 35, 63, 189, 114, 56, 42, 123,
1512    165, 38, 72, 93, 69, 139, 138, 78, 149, 159, 56, 89, 152, 78, 61, 140, 63, 26, 142, 76, 124,
1513    132, 72, 11, 90, 44, 82, 59, 96, 41, 148, 126, 157, 13, 49, 27, 176, 33, 47, 14, 97, 78, 71,
1514    40, 87, 183, 4, 122, 92, 7, 72, 3, 246, 17, 225, 87, 91, 106, 203, 190, 57, 74, 76, 88, 207,
1515    208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146,
1516    157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196,
1517    167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94,
1518    11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55,
1519    109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186, 120, 37, 46, 28, 166, 180,
1520    198, 232, 221, 116, 31, 75, 189, 139, 138, 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185,
1521    134, 193, 29, 158, 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223,
1522    140, 161, 137, 13, 191, 230, 66, 104, 153, 199, 167, 147, 99, 179, 92,
1523    // Duplicate for wrap-around indexing
1524    151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
1525    142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219,
1526    203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 35, 63, 189, 114, 56, 42, 123,
1527    165, 38, 72, 93, 69, 139, 138, 78, 149, 159, 56, 89, 152, 78, 61, 140, 63, 26, 142, 76, 124,
1528    132, 72, 11, 90, 44, 82, 59, 96, 41, 148, 126, 157, 13, 49, 27, 176, 33, 47, 14, 97, 78, 71,
1529    40, 87, 183, 4, 122, 92, 7, 72, 3, 246, 17, 225, 87, 91, 106, 203, 190, 57, 74, 76, 88, 207,
1530    208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146,
1531    157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196,
1532    167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94,
1533    11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55,
1534    109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174,
1535];
1536
1537fn fade(t: f32) -> f32 {
1538    t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
1539}
1540
1541fn grad(hash: u8, x: f32, y: f32, z: f32) -> f32 {
1542    let h = hash & 15;
1543    let u = if h < 8 { x } else { y };
1544    let v = if h < 8 { y } else { z };
1545    (if (h & 1) == 0 { u } else { -u }) + (if (h & 2) == 0 { v } else { -v })
1546}
1547
1548fn perlin3(x: f32, y: f32, z: f32) -> f32 {
1549    let xi = (x.floor() as i32) & 255;
1550    let yi = (y.floor() as i32) & 255;
1551    let zi = (z.floor() as i32) & 255;
1552
1553    let xf = x - x.floor();
1554    let yf = y - y.floor();
1555    let zf = z - z.floor();
1556
1557    let u = fade(xf);
1558    let v = fade(yf);
1559    let w = fade(zf);
1560
1561    let p0 = PERM[xi as usize] as usize;
1562    let p1 = PERM[((xi + 1) & 255) as usize] as usize;
1563    let pa = PERM[(p0 + yi as usize) & 255] as usize;
1564    let pb = PERM[(p0 + ((yi + 1) & 255) as usize) & 255] as usize;
1565    let pc = PERM[(p1 + yi as usize) & 255] as usize;
1566    let pd = PERM[(p1 + ((yi + 1) & 255) as usize) & 255] as usize;
1567
1568    let g000 = grad(PERM[(pa + zi as usize) & 255], xf, yf, zf);
1569    let g001 = grad(
1570        PERM[(pa + ((zi + 1) & 255) as usize) & 255],
1571        xf,
1572        yf,
1573        zf - 1.0,
1574    );
1575    let g010 = grad(PERM[(pb + zi as usize) & 255], xf, yf - 1.0, zf);
1576    let g011 = grad(
1577        PERM[(pb + ((zi + 1) & 255) as usize) & 255],
1578        xf,
1579        yf - 1.0,
1580        zf - 1.0,
1581    );
1582    let g100 = grad(PERM[(pc + zi as usize) & 255], xf - 1.0, yf, zf);
1583    let g101 = grad(
1584        PERM[(pc + ((zi + 1) & 255) as usize) & 255],
1585        xf - 1.0,
1586        yf,
1587        zf - 1.0,
1588    );
1589    let g110 = grad(PERM[(pd + zi as usize) & 255], xf - 1.0, yf - 1.0, zf);
1590    let g111 = grad(
1591        PERM[(pd + ((zi + 1) & 255) as usize) & 255],
1592        xf - 1.0,
1593        yf - 1.0,
1594        zf - 1.0,
1595    );
1596
1597    let l00 = g000 + u * (g100 - g000);
1598    let l01 = g001 + u * (g101 - g001);
1599    let l10 = g010 + u * (g110 - g010);
1600    let l11 = g011 + u * (g111 - g011);
1601
1602    let l0 = l00 + v * (l10 - l00);
1603    let l1 = l01 + v * (l11 - l01);
1604
1605    l0 + w * (l1 - l0)
1606}
1607
1608fn fast_rand_f64(state: &mut u64) -> f64 {
1609    *state = state
1610        .wrapping_mul(6364136223846793005)
1611        .wrapping_add(1442695040888963407);
1612    ((*state >> 32) as u32) as f64 / 4294967296.0
1613}
1614
1615// ─── Circle Drawing Primitives ────────────────────────────────────────────────
1616
1617/// Write one pixel into the framebuffer (normal or additive blend).
1618#[inline]
1619fn put_px(buf: &mut [u32], idx: usize, color: u32, blend: u8) {
1620    if idx >= buf.len() {
1621        return;
1622    }
1623    if blend == 0 {
1624        buf[idx] = color;
1625    } else {
1626        let old = buf[idx];
1627        let r = (((old >> 16) & 255) + ((color >> 16) & 255)).min(255);
1628        let g = (((old >> 8) & 255) + ((color >> 8) & 255)).min(255);
1629        let b = ((old & 255) + (color & 255)).min(255);
1630        buf[idx] = (r << 16) | (g << 8) | b;
1631    }
1632}
1633
1634/// Pack three float colour channels (0..255) into a 0x00RRGGBB word, clamping.
1635#[inline]
1636fn rgb(r: f64, g: f64, b: f64) -> u32 {
1637    let r = (r as i64).clamp(0, 255) as u32;
1638    let g = (g as i64).clamp(0, 255) as u32;
1639    let b = (b as i64).clamp(0, 255) as u32;
1640    (r << 16) | (g << 8) | b
1641}
1642
1643#[allow(clippy::too_many_arguments)]
1644fn draw_circle_outline(
1645    buf: &mut [u32],
1646    w: i32,
1647    h: i32,
1648    cx: i32,
1649    cy: i32,
1650    r: i32,
1651    color: u32,
1652    blend: u8,
1653) {
1654    let r = r.clamp(0, 20000); // guard against overflow / runaway from tiny depths
1655    if r == 0 {
1656        return;
1657    }
1658    let mut x = 0;
1659    let mut y = r;
1660    let mut d = 3 - 2 * r;
1661    while x <= y {
1662        plot_circle_points(buf, w, h, cx, cy, x, y, color, blend);
1663        if d < 0 {
1664            d += 4 * x + 6;
1665        } else {
1666            d += 4 * (x - y) + 10;
1667            y -= 1;
1668        }
1669        x += 1;
1670    }
1671}
1672
1673#[allow(clippy::too_many_arguments)]
1674fn plot_circle_points(
1675    buf: &mut [u32],
1676    w: i32,
1677    h: i32,
1678    cx: i32,
1679    cy: i32,
1680    x: i32,
1681    y: i32,
1682    color: u32,
1683    blend: u8,
1684) {
1685    let points = [
1686        (cx + x, cy + y),
1687        (cx - x, cy + y),
1688        (cx + x, cy - y),
1689        (cx - x, cy - y),
1690        (cx + y, cy + x),
1691        (cx - y, cy + x),
1692        (cx + y, cy - x),
1693        (cx - y, cy - x),
1694    ];
1695    for &(px, py) in &points {
1696        if px >= 0 && px < w && py >= 0 && py < h {
1697            put_px(buf, (py * w + px) as usize, color, blend);
1698        }
1699    }
1700}
1701
1702#[allow(clippy::too_many_arguments)]
1703fn draw_circle_filled(
1704    buf: &mut [u32],
1705    w: i32,
1706    h: i32,
1707    cx: i32,
1708    cy: i32,
1709    r: i32,
1710    color: u32,
1711    blend: u8,
1712) {
1713    if r <= 0 {
1714        return;
1715    }
1716    for dy in -r..=r {
1717        let dx_max = ((r * r - dy * dy) as f64).sqrt() as i32;
1718        let py = cy + dy;
1719        if py < 0 || py >= h {
1720            continue;
1721        }
1722        for dx in -dx_max..=dx_max {
1723            let px = cx + dx;
1724            if px >= 0 && px < w {
1725                put_px(buf, (py * w + px) as usize, color, blend);
1726            }
1727        }
1728    }
1729}
1730
1731#[cfg(test)]
1732mod draw_tests {
1733    use super::*;
1734
1735    #[test]
1736    fn filled_circle_actually_writes_pixels() {
1737        let mut buf = vec![0u32; 100 * 100];
1738        draw_circle_filled(&mut buf, 100, 100, 50, 50, 10, 0xFF00FF, 0);
1739        assert_eq!(buf[50 * 100 + 50], 0xFF00FF, "centre pixel must be filled");
1740        assert_eq!(buf[0], 0, "far corner must stay clear");
1741        let n = buf.iter().filter(|&&p| p != 0).count();
1742        assert!(n > 200 && n < 500, "r=10 disc area ≈ 314, got {n}");
1743    }
1744
1745    #[test]
1746    fn circle_outline_writes_a_ring() {
1747        let mut buf = vec![0u32; 100 * 100];
1748        draw_circle_outline(&mut buf, 100, 100, 50, 50, 20, 0x00FF00, 0);
1749        assert_eq!(buf[50 * 100 + 50], 0, "outline must NOT fill the centre");
1750        assert!(
1751            buf.iter().any(|&p| p == 0x00FF00),
1752            "outline must draw a ring"
1753        );
1754    }
1755
1756    #[test]
1757    fn additive_blend_accumulates_channels() {
1758        let mut buf = vec![0x202020u32; 1];
1759        put_px(&mut buf, 0, 0x404040, 1);
1760        assert_eq!(buf[0], 0x606060);
1761    }
1762}
1763
1764// ─── Interpreter ─────────────────────────────────────────────────────────────
1765
1766/// Customizable colour palette for the vector UI toolkit (packed 0x00RRGGBB).
1767/// `ui_theme(...)` sets it; every widget falls back to these and accepts a
1768/// trailing `r,g,b` override.
1769#[derive(Clone, Copy)]
1770pub struct UiTheme {
1771    pub primary: u32,
1772    pub accent: u32,
1773    pub track: u32,
1774    pub warn: u32,
1775    pub text: u32,
1776    pub bg: u32,
1777}
1778
1779impl Default for UiTheme {
1780    fn default() -> Self {
1781        Self {
1782            primary: 0x00D2FF, // holographic cyan
1783            accent: 0x28FFB4,  // mint
1784            track: 0x2C3E64,   // dim slate
1785            warn: 0xFF5A5A,    // alert red
1786            text: 0xBEEBFF,    // pale cyan
1787            bg: 0x0A1018,      // near-black panel
1788        }
1789    }
1790}
1791
1792#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
1793pub struct Interpreter {
1794    globals: HashMap<String, Expr>,
1795    /// Globals evaluated ONCE at program start (immutable after load).
1796    /// call_named clones this instead of re-evaluating every global per call.
1797    global_seed: Env,
1798    functions: FxHashMap<String, Rc<FnDef>>,
1799    /// `form` definitions: struct name → ordered field names.
1800    pub(crate) structs: HashMap<String, Vec<String>>,
1801    /// `choose` variants: variant name (bare and `Enum::Variant`) → (enum name, arity).
1802    enum_variants: HashMap<String, (String, usize)>,
1803    _modules: HashMap<String, Vec<FnDef>>,
1804    gfx: RefCell<GfxState>,
1805    svg: RefCell<Option<SvgWriter>>,
1806    /// Directory of the primary source file, for relative `use` resolution.
1807    pub source_dir: Option<std::path::PathBuf>,
1808    /// Files already loaded — prevents circular imports.
1809    loaded_files: std::collections::HashSet<String>,
1810    /// Optional audio engine — `None` if no audio device is available.
1811    #[cfg(not(target_arch = "wasm32"))]
1812    audio: Option<AudioEngine>,
1813    #[cfg(not(target_arch = "wasm32"))]
1814    fft: RefCell<FftAnalyzer>,
1815    fft_bands_cache: RefCell<Vec<f32>>,
1816    /// Real-time clock — seconds since Unix epoch at startup (f64 works on both
1817    /// native and wasm32; Instant is not available on wasm32).
1818    start_time_secs: f64,
1819    /// Frame counter — incremented at each present()
1820    frame_num: u64,
1821    /// Target framerate used to pace `present()` on wasm32.
1822    #[cfg(target_arch = "wasm32")]
1823    wasm_target_fps: f64,
1824    /// Next frame deadline (ms since epoch) for wasm frame pacing.
1825    #[cfg(target_arch = "wasm32")]
1826    wasm_next_present_ms: f64,
1827    /// Random state for rand() builtin (xorshift)
1828    rand_state: u64,
1829    /// Microphone input (Phase 1 audio reactivity)
1830    #[cfg(not(target_arch = "wasm32"))]
1831    mic: Option<ling_mic::MicInput>,
1832    /// Persistent KEM keypairs (knot / hybrid identities), referenced by handle.
1833    #[cfg(not(target_arch = "wasm32"))]
1834    crypto_ids: Vec<ling_crypto::KnotIdentity>,
1835    /// Persistent Ed25519 signing keypairs, referenced by handle.
1836    #[cfg(not(target_arch = "wasm32"))]
1837    ed25519_ids: Vec<ling_crypto::Ed25519Keypair>,
1838    /// Persistent ML-DSA-65 (post-quantum) signing keypairs, referenced by handle.
1839    #[cfg(not(target_arch = "wasm32"))]
1840    mldsa_ids: Vec<ling_crypto::MlDsa65Keypair>,
1841    /// Editable text-input buffer (ling-ui text fields).
1842    text_buffer: String,
1843    /// Frame counter for record_frame().
1844    record_n: u32,
1845    /// Accumulated microphone samples (for turning sound into crypto donuts).
1846    #[cfg(not(target_arch = "wasm32"))]
1847    mic_buffer: Vec<f32>,
1848    /// Loaded vector UI fonts, referenced by handle (index) from `font_load`.
1849    #[cfg(not(target_arch = "wasm32"))]
1850    fonts: Vec<ling_graphics::VectorFont>,
1851    /// Loaded raster images (PNG/etc.), referenced by handle (index) from
1852    /// `image_load` — read pixel-by-pixel via `image_pixel_r/g/b/a`. The
1853    /// `image` crate is a native-only dependency (see Cargo.toml's
1854    /// `cfg(not(target_arch = "wasm32"))` dependency block), so this field —
1855    /// like its sibling fields above (mldsa_ids, mic_buffer, fonts, music) —
1856    /// only exists on native targets; every image_* builtin below has a
1857    /// wasm32 stub arm to match.
1858    #[cfg(not(target_arch = "wasm32"))]
1859    images: Vec<image::RgbaImage>,
1860    /// Customizable UI colour palette (set via `ui_theme`).
1861    ui_theme: UiTheme,
1862    /// Left-mouse state on the previous frame — for widget click-edge detection.
1863    mouse_was_down: bool,
1864    /// Live music engine (decode playback + GM synth) — lazily initialised.
1865    #[cfg(not(target_arch = "wasm32"))]
1866    music: Option<ling_music::MusicEngine>,
1867    #[cfg(not(target_arch = "wasm32"))]
1868    music_init: bool,
1869    /// Decoded tracks (for analysis + playback), by `music_load` handle.
1870    tracks: Vec<ling_music::DecodedAudio>,
1871    /// Parsed `.lrc` lyrics, by `music_lrc` handle.
1872    lyrics: Vec<ling_music::Lyrics>,
1873    /// Parsed MIDI songs, by `music_midi_load` handle.
1874    midis: Vec<ling_music::MidiSong>,
1875    /// Soft bodies (deformable balls), by `soft_ball` handle.
1876    soft_bodies: Vec<ling_physics::soft::SoftBody>,
1877    /// Rigid-body world (angular dynamics), shared by `rb_*`.
1878    rigid_world: ling_physics::rigid::PhysicsWorld,
1879    /// Liquid grids (water/oil), by `liquid_new` handle.
1880    liquids: Vec<ling_physics::liquid::LiquidGrid>,
1881    meshes: Vec<crate::gfx::shapes::ColorMesh>,
1882    /// Loaded glTF models (skeleton + skin weights + animations), by `mesh_load` handle.
1883    gltf_models: std::cell::RefCell<Vec<ling_physics::gltf::GltfModel>>,
1884    /// Active cinematic dialog box (Ocarina/Majora-style), if any.
1885    dialog: Option<ling_game::dialog::Dialog>,
1886    /// Dialog highlight colours by role: text, name, place, item (0x00RRGGBB).
1887    dialog_colors: [u32; 4],
1888    /// Active user-function call frames (names), for error tracebacks.
1889    frames: Vec<String>,
1890    /// Snapshot of `frames` captured the moment a runtime error first arose
1891    /// (the deepest call). Consumed by `take_error_trace`.
1892    error_trace: Option<Vec<String>>,
1893    /// Unified input (gamepads/joysticks/VR/touch via the ling-input
1894    /// "Sensorium"). Lazily initialised on the first `pad_*` builtin call;
1895    /// `None` if no native input backend is available.
1896    #[cfg(not(target_arch = "wasm32"))]
1897    input: RefCell<Option<InputState>>,
1898    /// Routes registered by `http_route(method, path, handler)`, consumed
1899    /// by `http_serve`. Lives here (not a global, unlike `net`) because the
1900    /// handler is a `Value::Fn` closure — `Value` holds `Rc` and so can't
1901    /// safely live in a `static`.
1902    #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1903    http_routes: Vec<(String, String, Value)>,
1904    /// SQLite handle opened by `db_open` — plain rusqlite Connection, no
1905    /// pool: the interpreter is single-threaded, so one connection is both
1906    /// sufficient and contention-free.
1907    #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1908    db: Option<ling_http::rusqlite::Connection>,
1909    /// `(url_prefix, disk_dir)` pairs registered by `http_static`, consumed by
1910    /// `http_serve` — served as raw bytes, bypassing the String-only Value bridge.
1911    #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1912    http_static_dirs: Vec<(String, String)>,
1913    /// Background jobs started by `http_post_async`, polled by `http_job_poll`.
1914    /// A plain `Arc<Mutex<..>>` handle (not `Value`), so it's fine to touch from
1915    /// the background tokio task that fills in each job's result.
1916    #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1917    async_jobs: web::AsyncJobs,
1918    /// Google sign-in exchanges started by `oauth_google_start`, polled by
1919    /// `oauth_google_done`/`_error`/`_sub`/`_email`/`_name`. Separate from
1920    /// `async_jobs` because a login result has three fields, not one string.
1921    #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1922    oauth_jobs: web::OAuthJobs,
1923    /// HTTP status of the last `http_get`/`http_get_headers` call — a paired
1924    /// accessor (`http_get_status()`) rather than a multi-value return, same
1925    /// shape as `ssh_exit_code()`. 0 if the request never got a response at
1926    /// all (DNS/connect/TLS failure).
1927    #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1928    last_http_status: u16,
1929    /// Routes registered by `lingtp_route(path, handler)`, consumed by
1930    /// `lingtp_serve` — GET-only (no method), matching lingtp:// v1's
1931    /// request shape. Unlike `http_routes`, not gated on `feature = "web"`:
1932    /// lingtp only depends on ling-crypto, already unconditional here.
1933    #[cfg(not(target_arch = "wasm32"))]
1934    lingtp_routes: Vec<(String, Value)>,
1935    /// Status of the last `lingtp_get` call — paired accessor, same shape as
1936    /// `http_get_status()`. 1 = ok, 0 = connect/handshake error, -1 =
1937    /// signature invalid, -2 = host key changed (refused).
1938    #[cfg(not(target_arch = "wasm32"))]
1939    last_lingtp_status: i32,
1940}
1941
1942/// Live gamepad input state: a ling-input hub fed by the native `gilrs` backend.
1943#[cfg(not(target_arch = "wasm32"))]
1944struct InputState {
1945    sensorium: ling_input::Sensorium,
1946    backend: ling_input::backend::GilrsBackend,
1947}
1948
1949impl Default for Interpreter {
1950    fn default() -> Self {
1951        Self::new()
1952    }
1953}
1954
1955impl Interpreter {
1956    pub fn new() -> Self {
1957        #[cfg(not(target_arch = "wasm32"))]
1958        let audio = AudioEngine::new()
1959            .map_err(|e| eprintln!("audio init failed (no sound): {e}"))
1960            .ok();
1961        Self {
1962            globals: HashMap::new(),
1963            global_seed: new_env(),
1964            functions: FxHashMap::default(),
1965            structs: HashMap::new(),
1966            enum_variants: HashMap::new(),
1967            _modules: HashMap::new(),
1968            gfx: RefCell::new(GfxState::new()),
1969            svg: RefCell::new(None),
1970            source_dir: None,
1971            loaded_files: std::collections::HashSet::new(),
1972            #[cfg(not(target_arch = "wasm32"))]
1973            audio,
1974            #[cfg(not(target_arch = "wasm32"))]
1975            fft: RefCell::new(FftAnalyzer::new(2048, 44100)),
1976            fft_bands_cache: RefCell::new(vec![]),
1977            start_time_secs: crate::runtime::now_secs(),
1978            frame_num: 0,
1979            #[cfg(target_arch = "wasm32")]
1980            wasm_target_fps: 60.0,
1981            #[cfg(target_arch = "wasm32")]
1982            wasm_next_present_ms: 0.0,
1983            rand_state: 0x123456789ABCDEF,
1984            #[cfg(not(target_arch = "wasm32"))]
1985            mic: None,
1986            #[cfg(not(target_arch = "wasm32"))]
1987            crypto_ids: Vec::new(),
1988            #[cfg(not(target_arch = "wasm32"))]
1989            ed25519_ids: Vec::new(),
1990            #[cfg(not(target_arch = "wasm32"))]
1991            mldsa_ids: Vec::new(),
1992            text_buffer: String::new(),
1993            record_n: 0,
1994            #[cfg(not(target_arch = "wasm32"))]
1995            mic_buffer: Vec::new(),
1996            #[cfg(not(target_arch = "wasm32"))]
1997            fonts: Vec::new(),
1998            #[cfg(not(target_arch = "wasm32"))]
1999            images: Vec::new(),
2000            ui_theme: UiTheme::default(),
2001            mouse_was_down: false,
2002            #[cfg(not(target_arch = "wasm32"))]
2003            music: None,
2004            #[cfg(not(target_arch = "wasm32"))]
2005            music_init: false,
2006            tracks: Vec::new(),
2007            lyrics: Vec::new(),
2008            midis: Vec::new(),
2009            soft_bodies: Vec::new(),
2010            rigid_world: ling_physics::rigid::PhysicsWorld::new(),
2011            liquids: Vec::new(),
2012            meshes: Vec::new(),
2013            gltf_models: std::cell::RefCell::new(Vec::new()),
2014            dialog: None,
2015            dialog_colors: [0xE6F2FF, 0xFFD24A, 0x4AD2FF, 0x6CFF8C], // text · name · place · item
2016            frames: Vec::new(),
2017            error_trace: None,
2018            #[cfg(not(target_arch = "wasm32"))]
2019            input: RefCell::new(None),
2020            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
2021            http_routes: Vec::new(),
2022            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
2023            db: None,
2024            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
2025            http_static_dirs: Vec::new(),
2026            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
2027            async_jobs: web::AsyncJobs::new(),
2028            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
2029            oauth_jobs: web::OAuthJobs::new(),
2030            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
2031            last_http_status: 0,
2032            #[cfg(not(target_arch = "wasm32"))]
2033            lingtp_routes: Vec::new(),
2034            #[cfg(not(target_arch = "wasm32"))]
2035            last_lingtp_status: 0,
2036        }
2037    }
2038
2039    /// Lazily initialise the input system and advance it one frame; returns the
2040    /// number of connected gamepads. Call once per game-loop iteration (like a
2041    /// window update) before reading `pad_*` state.
2042    #[cfg(not(target_arch = "wasm32"))]
2043    fn pad_poll(&self) -> usize {
2044        let mut slot = self.input.borrow_mut();
2045        if slot.is_none() {
2046            match ling_input::backend::GilrsBackend::new() {
2047                Ok(backend) => {
2048                    *slot = Some(InputState { sensorium: ling_input::Sensorium::new(4), backend });
2049                },
2050                Err(_) => return 0,
2051            }
2052        }
2053        let st = slot.as_mut().unwrap();
2054        st.sensorium.begin_frame();
2055        st.sensorium.pump(&mut st.backend);
2056        st.sensorium.update(1.0 / 60.0);
2057        st.sensorium.devices.count()
2058    }
2059
2060    /// Read player `slot`'s gamepad with `f`, or return `default` if there is no
2061    /// input system / no such pad.
2062    #[cfg(not(target_arch = "wasm32"))]
2063    fn with_pad<T>(&self, slot: usize, default: T, f: impl FnOnce(&ling_input::Gamepad) -> T) -> T {
2064        let inp = self.input.borrow();
2065        match inp.as_ref().and_then(|s| s.sensorium.player(slot)) {
2066            Some(p) => f(p),
2067            None => default,
2068        }
2069    }
2070
2071    /// Take the call-stack snapshot captured at the deepest runtime error, if any.
2072    /// Frames are ordered outermost-first (entry point first, failing call last).
2073    pub fn take_error_trace(&mut self) -> Vec<String> {
2074        self.error_trace.take().unwrap_or_default()
2075    }
2076
2077    #[cfg(target_arch = "wasm32")]
2078    fn wasm_pace_frame(&mut self) {
2079        let fps = self.wasm_target_fps.max(1.0);
2080        let frame_ms = 1000.0 / fps;
2081        let now = js_sys::Date::now();
2082        if self.wasm_next_present_ms <= 0.0 {
2083            self.wasm_next_present_ms = now + frame_ms;
2084            return;
2085        }
2086
2087        let wait_ms = (self.wasm_next_present_ms - now).floor() as i32;
2088        if wait_ms > 0 {
2089            wasm_sleep_ms(wait_ms);
2090        }
2091
2092        let after = js_sys::Date::now();
2093        if after > self.wasm_next_present_ms + frame_ms * 3.0 {
2094            self.wasm_next_present_ms = after + frame_ms;
2095        } else {
2096            self.wasm_next_present_ms += frame_ms;
2097        }
2098    }
2099
2100    /// Run `body`, recording `name` as a call frame and snapshotting the stack on
2101    /// the first runtime error so a traceback can be reported.
2102    fn framed<T, F>(&mut self, name: &str, body: F) -> Result<T, EvalErr>
2103    where
2104        F: FnOnce(&mut Self) -> Result<T, EvalErr>,
2105    {
2106        self.frames.push(name.to_string());
2107        let result = body(self);
2108        if matches!(result, Err(EvalErr::Runtime(_))) && self.error_trace.is_none() {
2109            self.error_trace = Some(self.frames.clone());
2110        }
2111        self.frames.pop();
2112        result
2113    }
2114
2115    /// Render the active dialog box: beveled frame + dark fill, then the visible
2116    /// (typewriter-revealed) text word-wrapped with colour-coded runs, plus a
2117    /// blinking advance arrow once the page is fully typed.
2118    #[cfg(not(target_arch = "wasm32"))]
2119    fn render_dialog(&mut self, x: f32, y: f32, w: f32, h: f32, font: i64, t: f32) {
2120        let (runs, typing) = match &self.dialog {
2121            Some(d) if !d.is_closed() => {
2122                let runs: Vec<(String, usize, bool)> = d
2123                    .visible_runs()
2124                    .into_iter()
2125                    .map(|r| (r.text, r.role.index(), r.newline_before))
2126                    .collect();
2127                (runs, d.is_typing())
2128            },
2129            _ => return,
2130        };
2131        let colors = self.dialog_colors;
2132        // ── frame + fill ──
2133        let b = 12.0;
2134        let corners: Vec<[f32; 2]> = vec![
2135            [x + b, y],
2136            [x + w - b, y],
2137            [x + w, y + b],
2138            [x + w, y + h - b],
2139            [x + w - b, y + h],
2140            [x + b, y + h],
2141            [x, y + h - b],
2142            [x, y + b],
2143            [x + b, y],
2144        ];
2145        {
2146            let mut gfx = self.gfx.borrow_mut();
2147            let (bw, bh) = (gfx.width, gfx.height);
2148            crate::gfx::raster::fill_contours_aa(
2149                &mut gfx.buffer,
2150                bw,
2151                bh,
2152                0x0A1018,
2153                false,
2154                std::slice::from_ref(&corners),
2155            );
2156            for seg in corners.windows(2) {
2157                crate::gfx::raster::draw_line_aa(
2158                    &mut gfx.buffer,
2159                    bw,
2160                    bh,
2161                    0x00D2FF,
2162                    false,
2163                    seg[0][0],
2164                    seg[0][1],
2165                    seg[1][0],
2166                    seg[1][1],
2167                );
2168            }
2169        }
2170        // ── word-wrapped, colour-coded text ──
2171        let px = 22.0f32;
2172        let pad = 20.0f32;
2173        let line_h = px * 1.45;
2174        let mut cx = x + pad;
2175        let mut cy = y + pad;
2176        let use_font = font >= 0 && (font as usize) < self.fonts.len();
2177        for (text, role, nl) in &runs {
2178            if *nl {
2179                cx = x + pad;
2180                cy += line_h;
2181            }
2182            for word in text.split_inclusive(' ') {
2183                let wpx = if use_font {
2184                    self.fonts[font as usize].measure(word, px)
2185                } else {
2186                    ling_ui::holo::text_width(word, px * 0.6, px * 0.24)
2187                };
2188                if cx + wpx > x + w - pad && cx > x + pad + 1.0 {
2189                    cx = x + pad;
2190                    cy += line_h;
2191                }
2192                if cy + line_h > y + h {
2193                    break;
2194                }
2195                let col = colors[(*role).min(3)];
2196                if use_font {
2197                    let glyphs = self.font_layout_2d_glyphs(font as usize, cx, cy, px, word);
2198                    let mut gfx = self.gfx.borrow_mut();
2199                    let (bw, bh, add) = (gfx.width, gfx.height, gfx.blend == 1);
2200                    for contours in &glyphs {
2201                        crate::gfx::raster::fill_contours_aa(
2202                            &mut gfx.buffer,
2203                            bw,
2204                            bh,
2205                            col,
2206                            add,
2207                            contours,
2208                        );
2209                    }
2210                } else {
2211                    let segs = ling_ui::holo::text_lines(word, cx, cy, px * 0.6, px, px * 0.24);
2212                    let mut gfx = self.gfx.borrow_mut();
2213                    let (bw, bh) = (gfx.width, gfx.height);
2214                    for s in segs {
2215                        draw_line(&mut gfx.buffer, bw, bh, col, s[0], s[1], s[2], s[3]);
2216                    }
2217                }
2218                cx += wpx;
2219            }
2220        }
2221        // ── blinking advance arrow when fully typed ──
2222        if !typing && (t * 3.0).sin() > 0.0 {
2223            let ax = x + w - 26.0;
2224            let ay = y + h - 22.0;
2225            let mut gfx = self.gfx.borrow_mut();
2226            let (bw, bh) = (gfx.width, gfx.height);
2227            crate::gfx::raster::fill_contours_aa(
2228                &mut gfx.buffer,
2229                bw,
2230                bh,
2231                0x00D2FF,
2232                false,
2233                std::slice::from_ref(&vec![
2234                    [ax - 7.0, ay],
2235                    [ax + 7.0, ay],
2236                    [ax, ay + 9.0],
2237                    [ax - 7.0, ay],
2238                ]),
2239            );
2240        }
2241    }
2242
2243    /// Lazily start the music engine on first use (playback/synth need a device;
2244    /// analysis/decoding do not). Returns `false` if no audio device is available.
2245    #[cfg(not(target_arch = "wasm32"))]
2246    fn ensure_music(&mut self) -> bool {
2247        if self.music.is_some() {
2248            return true;
2249        }
2250        if self.music_init {
2251            return false;
2252        }
2253        self.music_init = true;
2254        match ling_music::MusicEngine::new() {
2255            Ok(m) => {
2256                self.music = Some(m);
2257                true
2258            },
2259            Err(e) => {
2260                eprintln!("music engine init failed (no music playback): {e}");
2261                false
2262            },
2263        }
2264    }
2265
2266    #[cfg(target_arch = "wasm32")]
2267    fn wasm_resolve_source_path(&self, path: &str) -> String {
2268        let p = path.trim();
2269        if p.is_empty() {
2270            return String::new();
2271        }
2272        if p.contains("://") || p.starts_with('/') || p.starts_with("./") || p.starts_with("../") {
2273            return p.to_string();
2274        }
2275        if let Some(d) = &self.source_dir {
2276            let base = d.to_string_lossy().replace('\\', "/");
2277            if !base.is_empty() {
2278                return format!(
2279                    "{}/{}",
2280                    base.trim_end_matches('/'),
2281                    p.trim_start_matches("./")
2282                );
2283            }
2284        }
2285        p.to_string()
2286    }
2287
2288    #[cfg(target_arch = "wasm32")]
2289    fn wasm_music_builtin(&mut self, name: &str, args: &[Value]) -> Result<Option<Value>, EvalErr> {
2290        match name {
2291            // music_load(path) -> track handle (decode from fetched bytes)
2292            "music_load"
2293            | "载入音乐"
2294            | "音楽読込"
2295            | "음악로드"
2296            | "โหลดเพลง"
2297            | "بارگذاری_موسیقی"
2298            | "تحميل_الموسيقى"
2299            | "טעינת_מוזיקה"
2300            | "موسیقی_لوڈ"
2301            | "charger_musique"
2302            | "musik_laden"
2303            | "загрузить_музыку" => {
2304                let path = self.arg_str(args, 0, "");
2305                let resolved = self.wasm_resolve_source_path(&path);
2306                match wasm_fetch_bytes(&resolved)
2307                    .and_then(|bytes| ling_music::from_bytes(&bytes).map_err(|e| e.to_string()))
2308                {
2309                    Ok(t) => {
2310                        let id = self.tracks.len();
2311                        self.tracks.push(t);
2312                        return Ok(Some(Value::Number(id as f64)));
2313                    },
2314                    Err(e) => {
2315                        eprintln!("music_load failed ({path}): {e}");
2316                        return Ok(Some(Value::Number(-1.0)));
2317                    },
2318                }
2319            },
2320            "music_duration"
2321            | "音乐时长"
2322            | "音楽長さ"
2323            | "음악길이"
2324            | "ความยาวเพลง"
2325            | "مدت_موسیقی"
2326            | "مدة_الموسيقى"
2327            | "משך_מוזיקה"
2328            | "موسیقی_دورانیہ"
2329            | "durée_musique"
2330            | "musik_dauer"
2331            | "длительность_музыки" => {
2332                let id = self.arg_num(args, 0, 0.0)? as i64;
2333                let d = self
2334                    .tracks
2335                    .get(id as usize)
2336                    .map(|t| t.duration)
2337                    .unwrap_or(0.0);
2338                return Ok(Some(Value::Number(d as f64)));
2339            },
2340            "music_bpm"
2341            | "节拍速度"
2342            | "テンポ"
2343            | "템포"
2344            | "จังหวะต่อนาที"
2345            | "ضربان_در_دقیقه"
2346            | "نبضات_بالدقيقة"
2347            | "פעימות_לדקה"
2348            | "بی_پی_ایم"
2349            | "bpm_musique"
2350            | "musik_bpm"
2351            | "музыка_bpm" => {
2352                let id = self.arg_num(args, 0, 0.0)? as i64;
2353                let b = self
2354                    .tracks
2355                    .get(id as usize)
2356                    .map(|t| ling_music::analysis::bpm(&t.mono, t.rate))
2357                    .unwrap_or(0.0);
2358                return Ok(Some(Value::Number(b as f64)));
2359            },
2360            "music_key"
2361            | "调性"
2362            | "調性"
2363            | "조성"
2364            | "คีย์เพลง"
2365            | "گام_موسیقی"
2366            | "مقام_الموسيقى"
2367            | "סולם_מוזיקלי"
2368            | "موسیقی_کلید"
2369            | "tonalité_musique"
2370            | "musik_tonart"
2371            | "тональность_музыки" => {
2372                let id = self.arg_num(args, 0, 0.0)? as i64;
2373                let k = self
2374                    .tracks
2375                    .get(id as usize)
2376                    .map(|t| ling_music::analysis::key_name(&t.mono, t.rate))
2377                    .unwrap_or_default();
2378                return Ok(Some(Value::Str(k)));
2379            },
2380            "music_onsets"
2381            | "音符起点"
2382            | "オンセット"
2383            | "온셋"
2384            | "จุดเริ่มเสียง"
2385            | "آغازهای_نت"
2386            | "بدايات_النغمات"
2387            | "התחלות_תווים"
2388            | "نوٹ_شروعات"
2389            | "attaques_musique"
2390            | "musik_einsätze"
2391            | "атаки_музыки" => {
2392                let id = self.arg_num(args, 0, 0.0)? as i64;
2393                let v = self
2394                    .tracks
2395                    .get(id as usize)
2396                    .map(|t| ling_music::analysis::onsets(&t.mono, t.rate))
2397                    .unwrap_or_default();
2398                return Ok(Some(Value::List(
2399                    v.into_iter()
2400                        .map(|x| Value::Number(x as f64))
2401                        .collect::<Vec<_>>()
2402                        .into(),
2403                )));
2404            },
2405            "music_beat_grid"
2406            | "节拍网格"
2407            | "ビートグリッド"
2408            | "비트그리드"
2409            | "กริดจังหวะ"
2410            | "شبکه_ضرب"
2411            | "شبكة_الإيقاع"
2412            | "רשת_פעימות"
2413            | "بیٹ_گرڈ"
2414            | "grille_temps_musique"
2415            | "musik_taktraster"
2416            | "сетка_ритма_музыки" => {
2417                let id = self.arg_num(args, 0, 0.0)? as i64;
2418                let beats = self
2419                    .tracks
2420                    .get(id as usize)
2421                    .map(|t| {
2422                        let b = ling_music::analysis::bpm(&t.mono, t.rate);
2423                        ling_music::analysis::beat_grid(&t.mono, t.rate, b)
2424                    })
2425                    .unwrap_or_default();
2426                return Ok(Some(Value::List(
2427                    beats
2428                        .into_iter()
2429                        .map(|x| Value::Number(x as f64))
2430                        .collect::<Vec<_>>()
2431                        .into(),
2432                )));
2433            },
2434            "music_lrc"
2435            | "载入歌词"
2436            | "歌詞読込"
2437            | "가사로드"
2438            | "โหลดเนื้อเพลง"
2439            | "بارگذاری_متن_ترانه"
2440            | "تحميل_كلمات_الأغنية"
2441            | "טעינת_מילות_שיר"
2442            | "گیت_متن_لوڈ"
2443            | "lrc_musique"
2444            | "musik_lrc"
2445            | "lrc_музыки" => {
2446                let path = self.arg_str(args, 0, "");
2447                let resolved = self.wasm_resolve_source_path(&path);
2448                match wasm_fetch_text(&resolved) {
2449                    Ok(text) => {
2450                        let id = self.lyrics.len();
2451                        self.lyrics.push(ling_music::Lyrics::parse(&text));
2452                        return Ok(Some(Value::Number(id as f64)));
2453                    },
2454                    Err(e) => {
2455                        eprintln!("music_lrc failed ({path}): {e}");
2456                        return Ok(Some(Value::Number(-1.0)));
2457                    },
2458                }
2459            },
2460            "music_lyric"
2461            | "当前歌词"
2462            | "現在歌詞"
2463            | "현재가사"
2464            | "เนื้อเพลงปัจจุบัน"
2465            | "متن_ترانه_فعلی"
2466            | "كلمات_الأغنية_الحالية"
2467            | "מילות_שיר_נוכחיות"
2468            | "موجودہ_گیت_متن"
2469            | "paroles_musique"
2470            | "musik_liedtext"
2471            | "текст_песни" => {
2472                let id = self.arg_num(args, 0, 0.0)? as i64;
2473                let t = self.arg_num(args, 1, 0.0)? as f32;
2474                let line = self
2475                    .lyrics
2476                    .get(id as usize)
2477                    .map(|l| l.line_at(t).to_string())
2478                    .unwrap_or_default();
2479                return Ok(Some(Value::Str(line)));
2480            },
2481            "music_midi_load"
2482            | "载入MIDI"
2483            | "MIDI読込"
2484            | "미디로드"
2485            | "โหลดมิดี"
2486            | "بارگذاری_MIDI"
2487            | "تحميل_MIDI"
2488            | "טעינת_MIDI"
2489            | "MIDI_لوڈ"
2490            | "charger_midi_musique"
2491            | "musik_midi_laden"
2492            | "загрузить_midi_музыки" => {
2493                let path = self.arg_str(args, 0, "");
2494                let resolved = self.wasm_resolve_source_path(&path);
2495                match wasm_fetch_bytes(&resolved).and_then(|bytes| {
2496                    ling_music::midi::from_bytes(&bytes).map_err(|e| e.to_string())
2497                }) {
2498                    Ok(m) => {
2499                        let id = self.midis.len();
2500                        self.midis.push(m);
2501                        return Ok(Some(Value::Number(id as f64)));
2502                    },
2503                    Err(e) => {
2504                        eprintln!("music_midi_load failed ({path}): {e}");
2505                        return Ok(Some(Value::Number(-1.0)));
2506                    },
2507                }
2508            },
2509            "music_midi_count"
2510            | "MIDI数量"
2511            | "MIDI数"
2512            | "미디수"
2513            | "จำนวนมิดี"
2514            | "تعداد_MIDI"
2515            | "عدد_MIDI"
2516            | "מספר_MIDI"
2517            | "MIDI_تعداد"
2518            | "nombre_midi_musique"
2519            | "musik_midi_anzahl"
2520            | "число_midi_музыки" => {
2521                let id = self.arg_num(args, 0, 0.0)? as i64;
2522                let n = self
2523                    .midis
2524                    .get(id as usize)
2525                    .map(|m| m.notes.len())
2526                    .unwrap_or(0);
2527                return Ok(Some(Value::Number(n as f64)));
2528            },
2529            "music_midi_notes"
2530            | "MIDI音符"
2531            | "MIDIノート"
2532            | "미디음표"
2533            | "โน้ตมิดี"
2534            | "نت‌های_MIDI"
2535            | "نغمات_MIDI"
2536            | "תווי_MIDI"
2537            | "MIDI_نوٹس"
2538            | "notes_midi_musique"
2539            | "musik_midi_noten"
2540            | "ноты_midi_музыки" => {
2541                let id = self.arg_num(args, 0, 0.0)? as i64;
2542                let mut out = Vec::new();
2543                if let Some(m) = self.midis.get(id as usize) {
2544                    for n in &m.notes {
2545                        out.push(Value::Number(n.time as f64));
2546                        out.push(Value::Number(n.midi as f64));
2547                    }
2548                }
2549                return Ok(Some(Value::List(out.into())));
2550            },
2551            "music_midi_bars"
2552            | "MIDI音条"
2553            | "MIDIバー"
2554            | "미디바"
2555            | "แท่งมิดี"
2556            | "میله‌های_MIDI"
2557            | "أعمدة_MIDI"
2558            | "עמודות_MIDI"
2559            | "MIDI_بارز"
2560            | "mesures_midi_musique"
2561            | "musik_midi_takte"
2562            | "такты_midi_музыки" => {
2563                let id = self.arg_num(args, 0, 0.0)? as i64;
2564                let mut out = Vec::new();
2565                if let Some(m) = self.midis.get(id as usize) {
2566                    for n in &m.notes {
2567                        out.push(Value::Number(n.time as f64));
2568                        out.push(Value::Number(n.midi as f64));
2569                        out.push(Value::Number(n.dur as f64));
2570                    }
2571                }
2572                return Ok(Some(Value::List(out.into())));
2573            },
2574            "music_judge"
2575            | "判定"
2576            | "判定する"
2577            | "판정"
2578            | "ตัดสินจังหวะ"
2579            | "داوری_ضرب"
2580            | "حكم_الإيقاع"
2581            | "שיפוט_קצב"
2582            | "بیٹ_فیصلہ"
2583            | "juger_musique"
2584            | "musik_bewerten"
2585            | "оценить_музыку" => {
2586                let delta_ms = self.arg_num(args, 0, 9999.0)? as f32;
2587                return Ok(Some(Value::Number(
2588                    ling_music::Grade::judge(delta_ms).index() as f64,
2589                )));
2590            },
2591            "music_grade_name"
2592            | "判定名"
2593            | "判定名称"
2594            | "판정이름"
2595            | "ชื่อการตัดสิน"
2596            | "نام_رتبه"
2597            | "اسم_التقييم"
2598            | "שם_דירוג"
2599            | "گریڈ_نام"
2600            | "nom_grade_musique"
2601            | "musik_bewertungsname"
2602            | "имя_оценки_музыки" => {
2603                let idx = self.arg_num(args, 0, 4.0)? as i32;
2604                return Ok(Some(Value::Str(
2605                    ling_music::Grade::from_index(idx).name().to_string(),
2606                )));
2607            },
2608            "music_note_name"
2609            | "音名"
2610            | "音名称"
2611            | "음이름"
2612            | "ชื่อโน้ต"
2613            | "نام_نت"
2614            | "اسم_النغمة"
2615            | "שם_תו"
2616            | "نوٹ_نام"
2617            | "nom_note_musique"
2618            | "musik_notenname"
2619            | "имя_ноты_музыки" => {
2620                let hz = self.arg_num(args, 0, 0.0)? as f32;
2621                return Ok(Some(Value::Str(ling_music::note::hz_to_name(hz))));
2622            },
2623            "music_hz"
2624            | "音符频率"
2625            | "音符周波数"
2626            | "음표주파수"
2627            | "ความถี่โน้ต"
2628            | "فرکانس_نت"
2629            | "تردد_النغمة"
2630            | "תדר_תו"
2631            | "نوٹ_ہرٹز"
2632            | "hz_musique"
2633            | "musik_hz"
2634            | "музыка_гц" => {
2635                let midi = match args.get(0) {
2636                    Some(Value::Str(s)) => ling_music::note::parse_pitch(s).unwrap_or(69),
2637                    Some(Value::Number(n)) => *n as i32,
2638                    _ => 69,
2639                };
2640                return Ok(Some(Value::Number(
2641                    ling_music::note::midi_to_hz(midi as f32) as f64,
2642                )));
2643            },
2644            "music_pitch_score"
2645            | "音准评分"
2646            | "音程スコア"
2647            | "음정점수"
2648            | "คะแนนเสียง"
2649            | "امتیاز_زیروبمی"
2650            | "درجة_طبقة_الصوت"
2651            | "ציון_גובה_צליל"
2652            | "پچ_اسکور"
2653            | "score_hauteur_musique"
2654            | "musik_tonhöhen_punktzahl"
2655            | "счёт_высоты_тона" => {
2656                let hz = self.arg_num(args, 0, 0.0)? as f32;
2657                let target = self.arg_num(args, 1, 0.0)? as f32;
2658                return Ok(Some(Value::Number(
2659                    ling_music::karaoke::pitch_score(hz, target) as f64,
2660                )));
2661            },
2662
2663            // ── Playback ──────────────────────────────────────────────────────
2664            "music_play"
2665            | "播放音乐"
2666            | "音楽再生"
2667            | "음악재생"
2668            | "เล่นเพลง"
2669            | "پخش_موسیقی"
2670            | "شغّل_الموسيقى"
2671            | "נגן_מוזיקה"
2672            | "موسیقی_چلاؤ"
2673            | "jouer_musique"
2674            | "musik_abspielen"
2675            | "играть_музыку" => {
2676                let id = self.arg_num(args, 0, 0.0)? as usize;
2677                if let Some(t) = self.tracks.get(id) {
2678                    crate::gfx::audio_web::play_music(id, &t.stereo, t.channels, t.rate, 1.0);
2679                }
2680                return Ok(Some(Value::Unit));
2681            },
2682            "music_pause"
2683            | "暂停音乐"
2684            | "音楽一時停止"
2685            | "음악일시정지"
2686            | "หยุดเพลงชั่วคราว"
2687            | "music_stop"
2688            | "停止音乐"
2689            | "音楽停止"
2690            | "음악정지"
2691            | "หยุดเพลง"
2692            | "مکث_موسیقی"
2693            | "ألبث_الموسيقى"
2694            | "השהה_מוזיקה"
2695            | "موسیقی_روکو_مؤقت"
2696            | "pause_musique"
2697            | "musik_pausieren"
2698            | "пауза_музыки" => {
2699                let id = self.arg_num(args, 0, 0.0)? as usize;
2700                crate::gfx::audio_web::stop_music(id);
2701                return Ok(Some(Value::Unit));
2702            },
2703            "music_seek"
2704            | "定位音乐"
2705            | "音楽シーク"
2706            | "음악탐색"
2707            | "ค้นหาเพลง"
2708            | "جستجوی_موسیقی"
2709            | "ابحث_في_الموسيقى"
2710            | "חפש_במוזיקה"
2711            | "موسیقی_تلاش"
2712            | "chercher_musique"
2713            | "musik_suchen"
2714            | "перемотать_музыку" => {
2715                // Seek is not straightforward on AudioBufferSourceNode; no-op for now.
2716                return Ok(Some(Value::Unit));
2717            },
2718            "music_pos"
2719            | "音乐位置"
2720            | "音楽位置"
2721            | "음악위치"
2722            | "ตำแหน่งเพลง"
2723            | "موقعیت_موسیقی"
2724            | "موضع_الموسيقى"
2725            | "מיקום_מוזיקה"
2726            | "موسیقی_مقام"
2727            | "position_musique"
2728            | "musik_position"
2729            | "позиция_музыки" => {
2730                return Ok(Some(Value::Number(
2731                    crate::gfx::audio_web::current_music_position(),
2732                )));
2733            },
2734            "music_volume"
2735            | "音乐音量"
2736            | "音楽音量"
2737            | "음악음량"
2738            | "ระดับเพลง"
2739            | "بلندی_موسیقی"
2740            | "مستوى_الموسيقى"
2741            | "עוצמת_מוזיקה"
2742            | "موسیقی_شدت"
2743            | "volume_musique"
2744            | "musik_lautstärke"
2745            | "громкость_музыки" => {
2746                let vol = self.arg_num(args, 0, 0.8)? as f32;
2747                // Apply to the most-recently started slot (slot 0 is typical).
2748                crate::gfx::audio_web::set_music_volume(0, vol);
2749                return Ok(Some(Value::Unit));
2750            },
2751
2752            // ── FFT bands at current playback position ─────────────────────
2753            "music_fft"
2754            | "音乐频谱"
2755            | "音楽スペクトル"
2756            | "음악스펙트럼"
2757            | "สเปกตรัมเพลง"
2758            | "طیف_موسیقی"
2759            | "طيف_الموسيقى"
2760            | "ספקטרום_מוזיקה"
2761            | "میوزک_اسپیکٹرم"
2762            | "fft_musique"
2763            | "musik_fft"
2764            | "fft_музыки" => {
2765                let id = self.arg_num(args, 0, 0.0)? as usize;
2766                let nbands = self.arg_num(args, 1, 16.0)? as usize;
2767                let pos = crate::gfx::audio_web::current_music_position() as f32;
2768                let bands = if let Some(t) = self.tracks.get(id) {
2769                    ling_music::analysis::fft_bands_at_pos(&t.mono, t.rate, pos, nbands)
2770                } else {
2771                    vec![0.0f32; nbands]
2772                };
2773                return Ok(Some(Value::List(
2774                    bands
2775                        .into_iter()
2776                        .map(|x| Value::Number(x as f64))
2777                        .collect::<Vec<_>>()
2778                        .into(),
2779                )));
2780            },
2781
2782            _ => {},
2783        }
2784        Ok(None)
2785    }
2786
2787    /// Lay out `text` for font `id` at size `px`, returning every glyph contour as
2788    /// a screen-space polyline (x→right, y→down). `(x, y)` is the text box top-left;
2789    /// the baseline is placed `ascent*px` below it. Curves are flattened to 0.3 px.
2790    #[cfg(not(target_arch = "wasm32"))]
2791    fn font_layout_2d(
2792        &mut self,
2793        id: usize,
2794        x: f32,
2795        y: f32,
2796        px: f32,
2797        text: &str,
2798    ) -> Vec<Vec<[f32; 2]>> {
2799        let mut out = Vec::new();
2800        for g in self.font_layout_2d_glyphs(id, x, y, px, text) {
2801            out.extend(g);
2802        }
2803        out
2804    }
2805
2806    /// Same as [`font_layout_2d`] but grouped per glyph (so a fill can apply the
2807    /// non-zero winding rule within each glyph, preserving interior holes).
2808    #[cfg(not(target_arch = "wasm32"))]
2809    fn font_layout_2d_glyphs(
2810        &mut self,
2811        id: usize,
2812        x: f32,
2813        y: f32,
2814        px: f32,
2815        text: &str,
2816    ) -> Vec<Vec<Vec<[f32; 2]>>> {
2817        let font = &mut self.fonts[id];
2818        let asc = font.ascent();
2819        let tol = 0.3 / px;
2820        let mut pen = 0.0f32;
2821        let mut glyphs = Vec::new();
2822        for ch in text.chars() {
2823            let go = font.glyph_outline(ch, tol);
2824            let mut contours = Vec::with_capacity(go.polylines.len());
2825            for pl in &go.polylines {
2826                let mapped: Vec<[f32; 2]> = pl
2827                    .iter()
2828                    .map(|p| [x + (pen + p[0]) * px, y + (asc - p[1]) * px])
2829                    .collect();
2830                contours.push(mapped);
2831            }
2832            glyphs.push(contours);
2833            pen += go.advance;
2834        }
2835        glyphs
2836    }
2837
2838    /// Register every item (functions, structs, globals) and evaluate the
2839    /// non-`do` globals into `global_seed`, WITHOUT running the entry. Used to
2840    /// prime the JIT's fallback interpreter so cranelift-skipped (oversized)
2841    /// functions can still be interpreted with full access to globals + peers.
2842    pub fn register_program(&mut self, program: &Program) -> Result<(), String> {
2843        for item in &program.items {
2844            self.register_item("", item)?;
2845        }
2846        let mut env = new_env();
2847        let non_do: Vec<_> = self
2848            .globals
2849            .iter()
2850            .filter(|(_, e)| !matches!(e, Expr::Do(_)))
2851            .map(|(k, e)| (k.clone(), e.clone()))
2852            .collect();
2853        let mut pending: Vec<(String, Expr)> = Vec::new();
2854        for (k, expr) in &non_do {
2855            let mut tmp = new_env();
2856            if let Ok(v) = self.eval_expr(expr, &mut tmp) {
2857                env.insert(k.clone(), v);
2858            } else {
2859                pending.push((k.clone(), expr.clone()));
2860            }
2861        }
2862        for (k, expr) in &pending {
2863            let mut tmp = env.clone();
2864            if let Ok(v) = self.eval_expr(expr, &mut tmp) {
2865                env.insert(k.clone(), v);
2866            }
2867        }
2868        self.global_seed = env;
2869        Ok(())
2870    }
2871
2872    pub fn run_program(&mut self, program: &Program) -> Result<(), String> {
2873        self.register_program(program)?;
2874        let entry = self
2875            .find_entry()
2876            .ok_or("no entry point — need `bind start = do {...}` or `ผูก เริ่ม = ทำ {...}`")?;
2877        let mut env = self.global_seed.clone();
2878        self.framed("start", |me| me.eval_expr(&entry, &mut env))
2879            .map(|_| ())
2880            .map_err(|e| match e {
2881                EvalErr::Runtime(s) => s,
2882                EvalErr::Return(_) => "unexpected top-level return".to_string(),
2883                EvalErr::Break => "unexpected break at top level".to_string(),
2884            })
2885    }
2886
2887    fn register_item(&mut self, ns: &str, item: &Item) -> Result<(), String> {
2888        match item {
2889            Item::Bind(name, expr) => {
2890                let key = if ns.is_empty() {
2891                    name.clone()
2892                } else {
2893                    format!("{ns}::{name}")
2894                };
2895                self.globals.insert(key, expr.clone());
2896            },
2897            Item::Fn(def) => {
2898                let key = if ns.is_empty() {
2899                    def.name.clone()
2900                } else {
2901                    format!("{ns}::{}", def.name)
2902                };
2903                self.functions.insert(key, Rc::new(def.clone()));
2904            },
2905            Item::Mod(name, body) => {
2906                let child_ns = if ns.is_empty() {
2907                    name.clone()
2908                } else {
2909                    format!("{ns}::{name}")
2910                };
2911                for child in body {
2912                    self.register_item(&child_ns, child)?;
2913                }
2914            },
2915            Item::TypeAlias(_, _) => {},
2916            Item::Struct(name, fields) => {
2917                self.structs.insert(name.clone(), fields.clone());
2918                if !ns.is_empty() {
2919                    self.structs.insert(format!("{ns}::{name}"), fields.clone());
2920                }
2921            },
2922            Item::Enum(name, variants) => {
2923                for v in variants {
2924                    self.enum_variants
2925                        .insert(v.name.clone(), (name.clone(), v.arity));
2926                    self.enum_variants
2927                        .insert(format!("{name}::{}", v.name), (name.clone(), v.arity));
2928                    if !ns.is_empty() {
2929                        self.enum_variants
2930                            .insert(format!("{ns}::{name}::{}", v.name), (name.clone(), v.arity));
2931                    }
2932                }
2933            },
2934            Item::Use { path, alias } => {
2935                self.load_module(path, alias.as_deref(), ns)?;
2936            },
2937        }
2938        Ok(())
2939    }
2940
2941    /// Resolve `path` relative to `source_dir`, load and parse it, then
2942    /// register all its definitions.  If `alias` is given, every name is
2943    /// prefixed with `<parent_ns>::<alias>`.  Circular imports are silently
2944    /// skipped.
2945    fn load_module(
2946        &mut self,
2947        path: &str,
2948        alias: Option<&str>,
2949        parent_ns: &str,
2950    ) -> Result<(), String> {
2951        // ── Wasm32: no filesystem — use the pre-registered module registry ──
2952        #[cfg(target_arch = "wasm32")]
2953        let (source, sub_dir) = {
2954            // Skip if already loaded (circular import guard)
2955            if self.loaded_files.contains(path) {
2956                return Ok(());
2957            }
2958            self.loaded_files.insert(path.to_string());
2959
2960            let src = crate::runtime::get_wasm_module(path)
2961                .or_else(|| crate::runtime::get_wasm_module(&format!("{}.ling", path)))
2962                .ok_or_else(|| format!("use: cannot find module '{path}'"))?;
2963            (src, None::<std::path::PathBuf>)
2964        };
2965
2966        // ── Native: resolve against filesystem ──
2967        #[cfg(not(target_arch = "wasm32"))]
2968        let (source, sub_dir) = {
2969            let base_dir = self
2970                .source_dir
2971                .clone()
2972                .unwrap_or_else(|| std::path::PathBuf::from("."));
2973            let raw = std::path::Path::new(path);
2974            let candidates: Vec<std::path::PathBuf> = vec![
2975                base_dir.join(format!("{}.ling", path)),
2976                base_dir.join(format!("{}.灵", path)),
2977                base_dir.join(format!("{}.령", path)),
2978                base_dir.join(format!("{}.霊", path)),
2979                base_dir.join(format!("{}.ลิง", path)),
2980                base_dir.join(raw),
2981                std::path::PathBuf::from(format!("{}.ling", path)),
2982                std::path::PathBuf::from(path),
2983            ];
2984
2985            let resolved = candidates
2986                .into_iter()
2987                .find(|p| p.exists())
2988                .ok_or_else(|| format!("use: cannot find module '{path}'"))?;
2989
2990            let canonical = resolved
2991                .canonicalize()
2992                .unwrap_or_else(|_| resolved.clone())
2993                .to_string_lossy()
2994                .to_string();
2995
2996            // Skip if already loaded (circular import guard)
2997            if self.loaded_files.contains(&canonical) {
2998                return Ok(());
2999            }
3000            self.loaded_files.insert(canonical.clone());
3001
3002            let src = std::fs::read_to_string(&resolved)
3003                .map_err(|e| format!("use: failed to read '{path}': {e}"))?;
3004            let dir = resolved.parent().map(|p| p.to_path_buf());
3005            (src, dir)
3006        };
3007
3008        let program = crate::parser::parse(&source)
3009            .map_err(|e| format!("use: parse error in '{path}': {e}"))?;
3010
3011        // Compute target namespace: parent_ns :: alias (or just alias, or just parent_ns)
3012        let target_ns = match (parent_ns.is_empty(), alias) {
3013            (_, Some(a)) if !parent_ns.is_empty() => format!("{parent_ns}::{a}"),
3014            (_, Some(a)) => a.to_string(),
3015            (false, None) => parent_ns.to_string(),
3016            (true, None) => String::new(),
3017        };
3018
3019        // Save/restore source_dir for nested relative imports
3020        let prev_dir = self.source_dir.clone();
3021        self.source_dir = sub_dir;
3022
3023        for item in &program.items {
3024            self.register_item(&target_ns, item)?;
3025        }
3026
3027        self.source_dir = prev_dir;
3028        Ok(())
3029    }
3030
3031    fn find_entry(&self) -> Option<Expr> {
3032        // Known entry-point names across supported human languages.
3033        for key in crate::entry::ENTRY_NAMES {
3034            if let Some(e) = self.globals.get(*key) {
3035                return Some(e.clone());
3036            }
3037        }
3038        self.globals
3039            .values()
3040            .find(|e| matches!(e, Expr::Do(_)))
3041            .cloned()
3042    }
3043
3044    // ─── Expression evaluation ────────────────────────────────────────────────
3045
3046    fn eval_expr(&mut self, expr: &Expr, env: &mut Env) -> EvalResult {
3047        match expr {
3048            Expr::Str(s) => Ok(Value::Str(s.clone())),
3049            Expr::Number(n) => Ok(Value::Number(*n)),
3050            Expr::Bool(b) => Ok(Value::Bool(*b)),
3051            Expr::Unit => Ok(Value::Unit),
3052            Expr::Array(elems) => {
3053                let vs: Vec<_> = elems
3054                    .iter()
3055                    .map(|e| self.eval_expr(e, env))
3056                    .collect::<Result<_, _>>()?;
3057                Ok(Value::List(Rc::new(vs)))
3058            },
3059
3060            Expr::Ident(name) => self.lookup(name, env),
3061
3062            Expr::Path(segs) => {
3063                if segs.len() == 1 {
3064                    return self.lookup(&segs[0], env);
3065                }
3066                Ok(Value::Str(segs.join("::")))
3067            },
3068
3069            Expr::Ref(inner) => self.eval_expr(inner, env),
3070            Expr::Await(inner) => self.eval_expr(inner, env),
3071
3072            Expr::Do(stmts) => {
3073                let mut local = env.clone();
3074                Ok(self.exec_block(stmts, &mut local)?.unwrap_or(Value::Unit))
3075            },
3076
3077            Expr::BinOp(op, lhs, rhs) => {
3078                let l = self.eval_expr(lhs, env)?;
3079                let r = self.eval_expr(rhs, env)?;
3080                self.apply_binop(op, l, r)
3081            },
3082
3083            Expr::If { cond, then, elseifs, else_body } => {
3084                let cond_val = self.eval_expr(cond, env)?;
3085                if self.is_truthy(&cond_val) {
3086                    return Ok(self.exec_block(then, env)?.unwrap_or(Value::Unit));
3087                }
3088                for (ei_cond, ei_body) in elseifs {
3089                    let ei_cond_val = self.eval_expr(ei_cond, env)?;
3090                    if self.is_truthy(&ei_cond_val) {
3091                        return Ok(self.exec_block(ei_body, env)?.unwrap_or(Value::Unit));
3092                    }
3093                }
3094                if let Some(eb) = else_body {
3095                    return Ok(self.exec_block(eb, env)?.unwrap_or(Value::Unit));
3096                }
3097                Ok(Value::Unit)
3098            },
3099
3100            Expr::While { cond, body } => {
3101                // Run the body directly in the *outer* env so that
3102                // `bind counter = counter + 1` persists across iterations,
3103                // which is the expected behaviour in a scripting language.
3104                loop {
3105                    let cv = self.eval_expr(cond, env)?;
3106                    if !self.is_truthy(&cv) {
3107                        break;
3108                    }
3109                    match self.exec_block(body, env) {
3110                        Ok(_) => {},
3111                        Err(EvalErr::Break) => break,
3112                        Err(e) => return Err(e),
3113                    }
3114                }
3115                Ok(Value::Unit)
3116            },
3117
3118            Expr::For { var, iter, body } => {
3119                let iter_val = self.eval_expr(iter, env)?;
3120                let items = self.value_to_iter(iter_val)?;
3121                for item in items {
3122                    let mut local = env.clone();
3123                    local.insert(var.clone(), item);
3124                    match self.exec_block(body, &mut local) {
3125                        Ok(_) => {},
3126                        Err(EvalErr::Break) => break,
3127                        Err(e) => return Err(e),
3128                    }
3129                }
3130                Ok(Value::Unit)
3131            },
3132
3133            Expr::Match(subject, arms) => {
3134                let subj = self.eval_expr(subject, env)?;
3135                for arm in arms {
3136                    if let Some(bindings) = self.match_pattern(&arm.pattern, &subj) {
3137                        let mut local = env.clone();
3138                        local.extend(bindings);
3139                        return self.eval_expr(&arm.body, &mut local);
3140                    }
3141                }
3142                Ok(Value::Unit)
3143            },
3144
3145            Expr::Range(lo, hi) => {
3146                let lo_v = self.eval_expr(lo, env)?;
3147                let hi_v = self.eval_expr(hi, env)?;
3148                let lo_n = self.to_number(&lo_v)? as i64;
3149                let hi_n = self.to_number(&hi_v)? as i64;
3150                Ok(Value::List(Rc::new(
3151                    (lo_n..hi_n).map(|i| Value::Number(i as f64)).collect(),
3152                )))
3153            },
3154
3155            Expr::Index(base, idx) => {
3156                let b = self.eval_expr(base, env)?;
3157                let i = self.eval_expr(idx, env)?;
3158                let n = self.to_number(&i)? as usize;
3159                match b {
3160                    Value::List(v) => v
3161                        .get(n)
3162                        .cloned()
3163                        .ok_or_else(|| EvalErr::from(format!("index {n} out of bounds"))),
3164                    Value::Str(s) => s
3165                        .chars()
3166                        .nth(n)
3167                        .map(|c| Value::Str(c.to_string()))
3168                        .ok_or_else(|| EvalErr::from(format!("index {n} out of bounds"))),
3169                    other => Err(EvalErr::from(format!("cannot index {:?}", other))),
3170                }
3171            },
3172
3173            Expr::Call(callee, args) => {
3174                let arg_vals: Vec<Value> = args
3175                    .iter()
3176                    .map(|a| self.eval_expr(a, env))
3177                    .collect::<Result<_, _>>()?;
3178                match callee.as_ref() {
3179                    Expr::Ident(name) => self.call_named(name, arg_vals, env),
3180                    Expr::Path(segs) => self.call_named(&segs.join("::"), arg_vals, env),
3181                    _ => {
3182                        let v = self.eval_expr(callee, env)?;
3183                        self.call_value(v, arg_vals)
3184                    },
3185                }
3186            },
3187
3188            Expr::MethodCall { receiver, method, args } => {
3189                let recv = self.eval_expr(receiver, env)?;
3190                let arg_vals: Vec<Value> = args
3191                    .iter()
3192                    .map(|a| self.eval_expr(a, env))
3193                    .collect::<Result<_, _>>()?;
3194                self.call_method(recv, method, arg_vals)
3195            },
3196
3197            Expr::Closure(params, body) => Ok(Value::Fn(
3198                params.clone(),
3199                vec![Stmt::Expr(*body.clone())],
3200                env.clone(),
3201            )),
3202
3203            Expr::Asm(_) => Ok(Value::Unit),
3204        }
3205    }
3206
3207    // ─── Block execution ─────────────────────────────────────────────────────
3208
3209    fn exec_block(&mut self, stmts: &[Stmt], env: &mut Env) -> Result<Option<Value>, EvalErr> {
3210        let mut last: Option<Value> = None;
3211        for stmt in stmts {
3212            match stmt {
3213                Stmt::Bind(name, expr) => {
3214                    match self.try_inplace_list_update(name, expr, env)? {
3215                        Some(v) => env.insert(name.clone(), v),
3216                        None => {
3217                            let v = self.eval_expr(expr, env)?;
3218                            env.insert(name.clone(), v)
3219                        },
3220                    };
3221                    last = None;
3222                },
3223                Stmt::Return(expr) => {
3224                    let v = self.eval_expr(expr, env)?;
3225                    return Err(EvalErr::Return(v));
3226                },
3227                Stmt::Expr(expr) => {
3228                    last = Some(self.eval_expr(expr, env)?);
3229                },
3230            }
3231        }
3232        Ok(last)
3233    }
3234
3235    /// Fast path for `bind v = list_push(v, x)` / `bind v = list_set(v, i, x)`:
3236    /// the binding aliases the same list being rebuilt, so the env copy keeps the
3237    /// `Rc` shared and `make_mut` copies the whole vector every call. Taking the
3238    /// value out of env first leaves the `Rc` unique (unless truly aliased
3239    /// elsewhere, where copy-on-write still applies), turning O(n) into O(1).
3240    /// Returns `None` to fall back to normal evaluation.
3241    fn try_inplace_list_update(
3242        &mut self,
3243        name: &str,
3244        expr: &Expr,
3245        env: &mut Env,
3246    ) -> Result<Option<Value>, EvalErr> {
3247        let Expr::Call(callee, args) = expr else { return Ok(None) };
3248        let Expr::Ident(fname) = callee.as_ref() else { return Ok(None) };
3249        let is_push = matches!(
3250            fname.as_str(),
3251            "list_push" | "เพิ่มรายการ" | "列表添加" | "リスト追加" | "목록추가"
3252        );
3253        let is_set = matches!(
3254            fname.as_str(),
3255            "list_set" | "ตั้งรายการ" | "设元素" | "要素設定" | "요소설정"
3256        );
3257        if !is_push && !is_set {
3258            return Ok(None);
3259        }
3260        // First arg must be the same variable we are binding, and the builtin
3261        // must not be shadowed by a user function.
3262        match args.first() {
3263            Some(Expr::Ident(a0)) if a0 == name => {},
3264            _ => return Ok(None),
3265        }
3266        if self.functions.contains_key(fname.as_str()) {
3267            return Ok(None);
3268        }
3269        if is_push {
3270            if args.len() != 2 {
3271                return Ok(None);
3272            }
3273            let val = self.eval_expr(&args[1], env)?;
3274            match env.remove(name) {
3275                Some(Value::List(mut v)) => {
3276                    Rc::make_mut(&mut v).push(val);
3277                    Ok(Some(Value::List(v)))
3278                },
3279                other => {
3280                    if let Some(o) = other {
3281                        env.insert(name.to_string(), o);
3282                    }
3283                    Ok(None)
3284                },
3285            }
3286        } else {
3287            if args.len() != 3 {
3288                return Ok(None);
3289            }
3290            let idx_v = self.eval_expr(&args[1], env)?;
3291            let idx = self.to_number(&idx_v).unwrap_or(0.0) as usize;
3292            let val = self.eval_expr(&args[2], env)?;
3293            match env.remove(name) {
3294                Some(Value::List(mut v)) => {
3295                    if idx < v.len() {
3296                        Rc::make_mut(&mut v)[idx] = val;
3297                    }
3298                    Ok(Some(Value::List(v)))
3299                },
3300                other => {
3301                    if let Some(o) = other {
3302                        env.insert(name.to_string(), o);
3303                    }
3304                    Ok(None)
3305                },
3306            }
3307        }
3308    }
3309
3310    // ─── Dispatch helpers ─────────────────────────────────────────────────────
3311
3312    fn lookup(&self, name: &str, env: &Env) -> EvalResult {
3313        if let Some(v) = env.get(name) {
3314            return Ok(v.clone());
3315        }
3316        // Globals are an immutable load-time snapshot shared by every call frame;
3317        // a function reads them here instead of receiving a per-call clone.
3318        if let Some(v) = self.global_seed.get(name) {
3319            return Ok(v.clone());
3320        }
3321        if self.functions.contains_key(name) {
3322            let def = &self.functions[name];
3323            return Ok(Value::Fn(def.params.clone(), def.body.clone(), new_env()));
3324        }
3325        // Bare nullary enum variant used as a value (e.g. `bind p = Origin`).
3326        if let Some((enum_name, 0)) = self.enum_variants.get(name).cloned() {
3327            let variant = name.rsplit("::").next().unwrap_or(name).to_string();
3328            return Ok(Value::Variant { enum_name, variant, payload: Vec::new() });
3329        }
3330        // Math constants usable as plain identifiers (e.g. `sin(pi)`)
3331        match name {
3332            "pi" | "π" | "พาย" | "圆周率" | "円周率" | "파이" | "پی" | "باي" | "פאי" | "پائی"
3333            | "пи" => return Ok(Value::Number(std::f64::consts::PI)),
3334            "tau" | "τ" | "双周率" | "タウ" | "타우" | "ทาว" | "تاو" | "טאו" | "ٹاؤ" | "тау" => {
3335                return Ok(Value::Number(std::f64::consts::TAU))
3336            },
3337            _ => {},
3338        }
3339        Err(EvalErr::from(format!("undefined: '{name}'")))
3340    }
3341
3342    /// Profiling wrapper around the real dispatch. Zero overhead unless
3343    /// `LING_PROFILE` is set (one thread-local bool check per call). When on,
3344    /// it tallies per-name call count + inclusive time and, on each frame
3345    /// boundary (`present`), prints a sorted top-down report every
3346    /// `LING_PROFILE_EVERY` frames (default 240). Both the JIT (`ling_builtin` →
3347    /// here) and the tree-walker route through this, so it sees every builtin —
3348    /// in JIT mode user fns are native, so it's a clean builtin/render/physics
3349    /// profile with no nesting double-count.
3350    pub(crate) fn call_named(&mut self, name: &str, args: Vec<Value>, env: &Env) -> EvalResult {
3351        if !ling_profile_enabled() {
3352            return self.call_named_inner(name, args, env);
3353        }
3354        let t0 = crate::runtime::now_secs();
3355        let r = self.call_named_inner(name, args, env);
3356        ling_profile_record(
3357            name,
3358            ((crate::runtime::now_secs() - t0) * 1_000_000_000.0) as u128,
3359        );
3360        r
3361    }
3362
3363    fn call_named_inner(&mut self, name: &str, args: Vec<Value>, env: &Env) -> EvalResult {
3364        // A user-defined function shadows any builtin of the same name, matching
3365        // the JIT/AOT backends (which always resolve a defined function first).
3366        if let Some(def) = self.functions.get(name).cloned() {
3367            let mut call_env =
3368                FxHashMap::with_capacity_and_hasher(def.params.len(), Default::default());
3369            let _ = env; // call-site locals are intentionally NOT visible to fns
3370            for (param, arg) in def.params.iter().zip(args) {
3371                call_env.insert(param.clone(), arg);
3372            }
3373            return match self.framed(name, |me| me.exec_block(&def.body, &mut call_env)) {
3374                Ok(v) => Ok(v.unwrap_or(Value::Unit)),
3375                Err(EvalErr::Return(v)) => Ok(v),
3376                Err(e) => Err(e),
3377            };
3378        }
3379
3380        #[cfg(target_arch = "wasm32")]
3381        if let Some(v) = self.wasm_music_builtin(name, &args)? {
3382            return Ok(v);
3383        }
3384
3385        match name {
3386            // Module global read emitted by the MIR backend: resolve against the
3387            // evaluated global snapshot (functions see globals read-only).
3388            "__ling_global" => {
3389                if let Some(Value::Str(g)) = args.first() {
3390                    if let Some(v) = self.global_seed.get(g.as_str()) {
3391                        return Ok(v.clone());
3392                    }
3393                }
3394                return Ok(Value::Unit);
3395            },
3396            // ── Print ──
3397            "print" | "println" | "印" | "打印" | "印刷" | "พิมพ์" | "출력" | "вывести"
3398            | "imprimir" | "afficher" | "چاپ" | "اطبع" | "הדפס" | "چھاپو" | "drucken"
3399            | "печать" => {
3400                let s = args
3401                    .iter()
3402                    .map(|v| v.to_string())
3403                    .collect::<Vec<_>>()
3404                    .join("");
3405                println!("{s}");
3406                return Ok(Value::Unit);
3407            },
3408            // print_color(colorIdx, text...) — ANSI-coloured console line.
3409            //   colorIdx 0..7 → bright fg (90+idx): 1=red 2=green 3=yellow 4=blue 6=cyan 7=white.
3410            "print_color" | "พิมพ์สี" | "چاپ_رنگی" | "اطبع_بلون" | "הדפס_בצבע" | "رنگین_چھاپو" =>
3411            {
3412                #[cfg(windows)]
3413                {
3414                    use std::sync::Once;
3415                    static VT: Once = Once::new();
3416                    VT.call_once(|| {
3417                        extern "system" {
3418                            fn GetStdHandle(n: u32) -> *mut std::ffi::c_void;
3419                            fn GetConsoleMode(h: *mut std::ffi::c_void, m: *mut u32) -> i32;
3420                            fn SetConsoleMode(h: *mut std::ffi::c_void, m: u32) -> i32;
3421                        }
3422                        unsafe {
3423                            let h = GetStdHandle(0xFFFF_FFF5u32); // STD_OUTPUT_HANDLE (-11)
3424                            let mut mode = 0u32;
3425                            if GetConsoleMode(h, &mut mode) != 0 {
3426                                SetConsoleMode(h, mode | 0x0004); // ENABLE_VIRTUAL_TERMINAL_PROCESSING
3427                            }
3428                        }
3429                    });
3430                }
3431                let col = self.arg_num(&args, 0, 7.0)? as i64;
3432                let s = args
3433                    .iter()
3434                    .skip(1)
3435                    .map(|v| v.to_string())
3436                    .collect::<Vec<_>>()
3437                    .join("");
3438                let code = 90 + col.clamp(0, 7);
3439                println!("\x1b[1;{code}m{s}\x1b[0m");
3440                return Ok(Value::Unit);
3441            },
3442            // ── Format ──
3443            "format"
3444            | "格式"
3445            | "フォーマット"
3446            | "서식"
3447            | "รูปแบบ"
3448            | "форматировать"
3449            | "formatear"
3450            | "formater"
3451            | "قالب‌بندی"
3452            | "نسّق"
3453            | "פרמט"
3454            | "فارمیٹ"
3455            | "formatieren" => {
3456                return Ok(Value::Str(self.builtin_format(&args)?));
3457            },
3458            // ── String join / concatenation ──
3459            "格式::拼接" | "format::join" => match args.first() {
3460                Some(Value::List(items)) => {
3461                    return Ok(Value::Str(items.iter().map(|v| v.to_string()).collect()));
3462                },
3463                _ => return Ok(Value::Str(self.builtin_format(&args)?)),
3464            },
3465            // ── Result constructors ──
3466            "ok" | "好" | "良し" | "좋아" | "โอเค" | "تایید" | "تمام" | "בסדר" | "ٹھیک" | "bon"
3467            | "gut" | "хорошо" => {
3468                let val = args.into_iter().next().unwrap_or(Value::Unit);
3469                return Ok(Value::Ok(Box::new(val)));
3470            },
3471            "bad" | "坏" | "err" | "悪い" | "나쁨" | "ผิด" | "بد" | "سيء" | "רע" | "برا"
3472            | "mauvais" | "schlecht" | "плохо" => {
3473                let val = args.into_iter().next().unwrap_or(Value::Unit);
3474                return Ok(Value::Err(Box::new(val)));
3475            },
3476            // ── Vec constructors ──
3477            "向量::从" | "Vec::from" => {
3478                if let Some(Value::List(v)) = args.first() {
3479                    return Ok(Value::List(v.clone()));
3480                }
3481                return Ok(Value::List(Rc::new(args)));
3482            },
3483            "向量::有容量" | "Vec::with_capacity" => {
3484                return Ok(Value::List(Rc::new(Vec::new())))
3485            },
3486            // ── Timer stubs ──
3487            "计时::获取当前小时" | "Timer::hour" => return Ok(Value::Number(14.0)),
3488            "计时::现在" | "Timer::now" => return Ok(Value::Number(1000.0)),
3489            // ── Sleep ──
3490            "sleep" | "หยุด" | "นอน" | "sleep_ms" | "睡眠" | "眠る" | "スリープ" | "잠자기"
3491            | "잠" | "流水::睡眠" | "Flow::sleep" | "خواب" | "نم" | "שינה" | "سو_جاؤ"
3492            | "dormir" | "schlafen" | "спать" => {
3493                if let Some(ms_val) = args.first() {
3494                    if let Ok(ms) = self.to_number(ms_val) {
3495                        #[cfg(target_arch = "wasm32")]
3496                        wasm_sleep_ms(ms.max(0.0) as i32);
3497                        #[cfg(not(target_arch = "wasm32"))]
3498                        std::thread::sleep(std::time::Duration::from_millis(ms as u64));
3499                    }
3500                }
3501                return Ok(Value::Unit);
3502            },
3503            // ── Flow::parallel stub ──
3504            "流水::并行" | "Flow::parallel" => {
3505                if let Some(Value::Fn(params, body, mut cap)) = args.first().cloned() {
3506                    let _ = params;
3507                    match self.exec_block(&body, &mut cap) {
3508                        Ok(Some(v)) => return Ok(v),
3509                        Ok(None) => return Ok(Value::Unit),
3510                        Err(EvalErr::Return(v)) => return Ok(v),
3511                        Err(e) => return Err(e),
3512                    }
3513                }
3514                return Ok(Value::Unit);
3515            },
3516
3517            // ══════════════════════════════════════════════════════════════════
3518            // MATH BUILTINS  (all args and results are f64)
3519            // Thai aliases: ไซน์ โคไซน์ แทนเจนต์ รากที่สอง ค่าสัมบูรณ์
3520            //               ปัดลง ปัดขึ้น ปัดเศษ ตัดทศนิยม ต่ำสุด สูงสุด
3521            //               จำกัด ยกกำลัง ลอการิทึม พาย
3522            // ══════════════════════════════════════════════════════════════════
3523
3524            // ── Trigonometry (input in radians) ──
3525            "sin" | "ไซน์" | "正弦" | "サイン" | "사인" | "سینوس" | "جا" | "סינוס" | "سائن"
3526            | "sinus" | "синус" => {
3527                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.sin()));
3528            },
3529            "cos" | "โคไซน์" | "余弦" | "コサイン" | "코사인" | "کسینوس" | "جتا" | "קוסינוס"
3530            | "کوسائن" | "cosinus" | "kosinus" | "косинус" => {
3531                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.cos()));
3532            },
3533
3534            // ── Hyperbolic functions ──
3535            // Hyperbolic tangent
3536            "tanh" | "tanhf" | "双曲正切" | "双曲線正接" | "쌍곡탄젠트" => {
3537                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.tanh()));
3538            },
3539
3540            "tan"
3541            | "แทนเจนต์"
3542            | "正切"
3543            | "タンジェント"
3544            | "탄젠트"
3545            | "تانژانت"
3546            | "ظا"
3547            | "טנגנס"
3548            | "ٹینجنٹ"
3549            | "tangente"
3550            | "tangens"
3551            | "тангенс" => {
3552                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.tan()));
3553            },
3554            "asin"
3555            | "arcsin"
3556            | "反正弦"
3557            | "アークサイン"
3558            | "아크사인"
3559            | "อาร์กไซน์"
3560            | "آرک‌سینوس"
3561            | "قوس_جا"
3562            | "ארקסינוס"
3563            | "آرک_سائن"
3564            | "arcsinus"
3565            | "arkussinus"
3566            | "арксинус" => {
3567                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.asin()));
3568            },
3569            "acos"
3570            | "arccos"
3571            | "反余弦"
3572            | "アークコサイン"
3573            | "아크코사인"
3574            | "อาร์กโคไซน์"
3575            | "آرک‌کسینوس"
3576            | "قوس_جتا"
3577            | "ארקוקוסינוס"
3578            | "آرک_کوسائن"
3579            | "arccosinus"
3580            | "arkuskosinus"
3581            | "арккосинус" => {
3582                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.acos()));
3583            },
3584            "atan"
3585            | "arctan"
3586            | "反正切"
3587            | "アークタンジェント"
3588            | "아크탄젠트"
3589            | "อาร์กแทนเจนต์"
3590            | "آرک‌تانژانت"
3591            | "قوس_ظا"
3592            | "ארקטנגנס"
3593            | "آرک_ٹینجنٹ"
3594            | "arctangente"
3595            | "arkustangens"
3596            | "арктангенс" => {
3597                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.atan()));
3598            },
3599            "atan2" | "arctan2" | "反正切2" | "アークタンジェント2" | "아크탄젠트2" =>
3600            {
3601                let y = self.arg_num(&args, 0, 0.0)?;
3602                let x = self.arg_num(&args, 1, 1.0)?;
3603                return Ok(Value::Number(y.atan2(x)));
3604            },
3605
3606            // ── Roots / powers ──
3607            "sqrt"
3608            | "รากที่สอง"
3609            | "平方根"
3610            | "根"
3611            | "제곱근"
3612            | "جذر"
3613            | "שורש"
3614            | "racine_carrée"
3615            | "quadratwurzel"
3616            | "корень" => {
3617                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.sqrt()));
3618            },
3619            "cbrt"
3620            | "立方根"
3621            | "세제곱근"
3622            | "รากที่สาม"
3623            | "ریشه_سوم"
3624            | "جذر_تكعيبي"
3625            | "שורש_שלישי"
3626            | "مکعب_جذر"
3627            | "racine_cubique"
3628            | "kubikwurzel"
3629            | "кубический_корень" => {
3630                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.cbrt()));
3631            },
3632            "pow"
3633            | "ยกกำลัง"
3634            | "幂"
3635            | "べき乗"
3636            | "거듭제곱"
3637            | "توان"
3638            | "أس"
3639            | "חזקה"
3640            | "قوت"
3641            | "puissance"
3642            | "potenz"
3643            | "степень" => {
3644                let base = self.arg_num(&args, 0, 0.0)?;
3645                let exp = self.arg_num(&args, 1, 1.0)?;
3646                return Ok(Value::Number(base.powf(exp)));
3647            },
3648            "exp" | "指数" | "指数関数" | "지수" => {
3649                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.exp()));
3650            },
3651            "hypot" | "斜边" | "斜辺" | "빗변" => {
3652                let x = self.arg_num(&args, 0, 0.0)?;
3653                let y = self.arg_num(&args, 1, 0.0)?;
3654                return Ok(Value::Number(x.hypot(y)));
3655            },
3656
3657            // ── Logarithms ──
3658            "ln"
3659            | "log"
3660            | "ลอการิทึม"
3661            | "对数"
3662            | "対数"
3663            | "로그"
3664            | "لگاریتم_طبیعی"
3665            | "لوغاريتم_طبيعي"
3666            | "לוגריתם_טבעי"
3667            | "فطری_لوگ" => {
3668                return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.ln()));
3669            },
3670            "log2" | "对数2" | "対数2" | "로그2" => {
3671                return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.log2()));
3672            },
3673            "log10" | "对数10" | "対数10" | "로그10" => {
3674                return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.log10()));
3675            },
3676
3677            // ── Rounding / truncation ──
3678            "abs"
3679            | "ค่าสัมบูรณ์"
3680            | "绝对值"
3681            | "绝对"
3682            | "絶対値"
3683            | "절댓값"
3684            | "절대값"
3685            | "قدرمطلق"
3686            | "مطلق"
3687            | "ערך_מוחלט"
3688            | "مطلق_قدر"
3689            | "valeur_absolue"
3690            | "betrag"
3691            | "модуль_числа" => {
3692                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.abs()));
3693            },
3694            "floor" | "ปัดลง" | "向下取整" | "下整" | "床関数" | "내림" | "کف" | "أرضية"
3695            | "רצפה" | "فرش" | "plancher" | "abrunden" | "вниз" => {
3696                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.floor()));
3697            },
3698            "ceil"
3699            | "ปัดขึ้น"
3700            | "向上取整"
3701            | "上整"
3702            | "天井関数"
3703            | "올림"
3704            | "سقف"
3705            | "תקרה"
3706            | "چھت"
3707            | "plafond"
3708            | "aufrunden"
3709            | "вверх" => {
3710                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.ceil()));
3711            },
3712            "round" | "ปัดเศษ" | "四舍五入" | "四舍" | "四捨五入" | "반올림" | "گرد_کردن"
3713            | "تقريب" | "עיגול" | "گول" | "arrondir" | "runden" | "округлить" =>
3714            {
3715                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.round()));
3716            },
3717            "trunc"
3718            | "int"
3719            | "ตัดทศนิยม"
3720            | "取整"
3721            | "整数化"
3722            | "整数"
3723            | "截整"
3724            | "정수화"
3725            | "정수"
3726            | "切り捨て"
3727            | "버림"
3728            | "برش"
3729            | "اقتطاع"
3730            | "קיטום"
3731            | "کٹائی"
3732            | "tronquer"
3733            | "abschneiden"
3734            | "усечь" => {
3735                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.trunc()));
3736            },
3737            "fract" | "小数部分" | "小数部" | "소수부" => {
3738                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.fract()));
3739            },
3740
3741            // ── min / max / clamp ──
3742            "min" | "ต่ำสุด" | "最小" | "최솟값" | "کمینه" | "أصغر" | "מינימום" | "کم_ترین"
3743            | "minimum" | "минимум" => {
3744                let a = self.arg_num(&args, 0, 0.0)?;
3745                let b = self.arg_num(&args, 1, 0.0)?;
3746                return Ok(Value::Number(a.min(b)));
3747            },
3748            "max"
3749            | "สูงสุด"
3750            | "最大"
3751            | "최댓값"
3752            | "بیشینه"
3753            | "أكبر"
3754            | "מקסימום"
3755            | "زیادہ_ترین"
3756            | "maximum"
3757            | "максимум" => {
3758                let a = self.arg_num(&args, 0, 0.0)?;
3759                let b = self.arg_num(&args, 1, 0.0)?;
3760                return Ok(Value::Number(a.max(b)));
3761            },
3762            "clamp"
3763            | "จำกัด"
3764            | "截取"
3765            | "範囲制限"
3766            | "범위제한"
3767            | "محدود"
3768            | "قيّد"
3769            | "הגבל"
3770            | "limiter"
3771            | "begrenzen"
3772            | "ограничить" => {
3773                let x = self.arg_num(&args, 0, 0.0)?;
3774                let lo = self.arg_num(&args, 1, 0.0)?;
3775                let hi = self.arg_num(&args, 2, 1.0)?;
3776                return Ok(Value::Number(x.clamp(lo, hi)));
3777            },
3778
3779            // ── Constants (also accessible as plain identifiers via lookup) ──
3780            "pi" | "π" | "พาย" | "圆周率" | "円周率" | "파이" | "پی" | "باي" | "פאי" | "پائی"
3781            | "пи" => return Ok(Value::Number(std::f64::consts::PI)),
3782            "tau" | "τ" | "双周率" | "タウ" | "타우" | "ทาว" | "تاو" | "טאו" | "ٹاؤ" | "тау" => {
3783                return Ok(Value::Number(std::f64::consts::TAU))
3784            },
3785
3786            // ══════════════════════════════════════════════════════════════════
3787            // PHASE 1: DMT TRIP CODER FEATURES
3788            // ══════════════════════════════════════════════════════════════════
3789
3790            // ── Step 1: Noise Functions ──
3791            "vnoise"
3792            | "noise2"
3793            | "นอยส์2ดี"
3794            | "柏林噪声2D"
3795            | "バリューノイズ2D"
3796            | "값노이즈2D"
3797            | "نویز_برداری"
3798            | "ضجيج_متجه"
3799            | "רעש_וקטורי"
3800            | "ویکٹر_نوائز"
3801            | "bruit_v"
3802            | "v_rauschen"
3803            | "шум_v" => {
3804                let x = self.arg_num(&args, 0, 0.0)? as f32;
3805                let y = self.arg_num(&args, 1, 0.0)? as f32;
3806                let seed = self.arg_num(&args, 2, 0.0)? as u32;
3807                return Ok(Value::Number(tex_vnoise(x, y, seed) as f64));
3808            },
3809
3810            "fbm"
3811            | "นอยส์ออร์แกนิก"
3812            | "分形噪声"
3813            | "フラクタルノイズ"
3814            | "프랙탈노이즈"
3815            | "نویز_فراکتالی"
3816            | "ضجيج_عضوي"
3817            | "רעש_פרקטלי"
3818            | "فریکٹل_نوائز" => {
3819                let x = self.arg_num(&args, 0, 0.0)? as f32;
3820                let y = self.arg_num(&args, 1, 0.0)? as f32;
3821                let octaves = self.arg_num(&args, 2, 4.0)? as u32;
3822                let seed = self.arg_num(&args, 3, 0.0)? as u32;
3823                return Ok(Value::Number(tex_fbm(x, y, octaves, seed) as f64));
3824            },
3825
3826            "perlin"
3827            | "perlin3"
3828            | "เพอร์ลิน3ดี"
3829            | "柏林噪声3D"
3830            | "パーリンノイズ3D"
3831            | "펄린노이즈3D"
3832            | "نویز_پرلین"
3833            | "بيرلين"
3834            | "רעש_פרלין"
3835            | "پرلن_نوائز"
3836            | "перлин" => {
3837                let x = self.arg_num(&args, 0, 0.0)? as f32;
3838                let y = self.arg_num(&args, 1, 0.0)? as f32;
3839                let z = self.arg_num(&args, 2, 0.0)? as f32;
3840                return Ok(Value::Number(perlin3(x, y, z) as f64));
3841            },
3842
3843            // ── Step 2: Math Ergonomics ──
3844            "lerp"
3845            | "ค่าระหว่าง"
3846            | "线性插值"
3847            | "線形補間"
3848            | "선형보간"
3849            | "میان‌یابی"
3850            | "استيفاء"
3851            | "אינטרפולציה"
3852            | "درمیانی_قدر"
3853            | "interpoler"
3854            | "interpolieren"
3855            | "интерполировать" => {
3856                let a = self.arg_num(&args, 0, 0.0)?;
3857                let b = self.arg_num(&args, 1, 1.0)?;
3858                let t = self.arg_num(&args, 2, 0.0)?;
3859                return Ok(Value::Number(a + (b - a) * t));
3860            },
3861
3862            "smoothstep"
3863            | "เปลี่ยนแบบนุ่ม"
3864            | "平滑步进"
3865            | "スムーズステップ"
3866            | "스무스스텝"
3867            | "گام_نرم"
3868            | "تدرج_ناعم"
3869            | "מדרגה_חלקה"
3870            | "ہموار_قدم"
3871            | "lissage"
3872            | "glättung"
3873            | "сглаживание" => {
3874                let lo = self.arg_num(&args, 0, 0.0)?;
3875                let hi = self.arg_num(&args, 1, 1.0)?;
3876                let x = self.arg_num(&args, 2, 0.5)?;
3877                let t = ((x - lo) / (hi - lo)).clamp(0.0, 1.0);
3878                return Ok(Value::Number(t * t * (3.0 - 2.0 * t)));
3879            },
3880
3881            "rand" | "สุ่ม" | "随机" | "乱数" | "난수" | "تصادفی" | "عشوائي" | "אקראי"
3882            | "بے_ترتیب" | "aléatoire" | "zufall" | "случайное" => {
3883                let val = fast_rand_f64(&mut self.rand_state);
3884                return Ok(Value::Number(val));
3885            },
3886
3887            "sign"
3888            | "เครื่องหมาย"
3889            | "符号"
3890            | "符号関数"
3891            | "부호"
3892            | "علامت"
3893            | "إشارة"
3894            | "סימן"
3895            | "نشان"
3896            | "signe"
3897            | "vorzeichen"
3898            | "знак" => {
3899                let x = self.arg_num(&args, 0, 0.0)?;
3900                return Ok(Value::Number(x.signum()));
3901            },
3902
3903            "hsv_to_rgb"
3904            | "เอชเอสวีเป็นRGB"
3905            | "HSV转RGB"
3906            | "HSV変換RGB"
3907            | "HSV변환RGB"
3908            | "HSV_به_RGB"
3909            | "HSV_إلى_RGB"
3910            | "HSV_ל_RGB"
3911            | "HSV_سے_RGB"
3912            | "hsv_vers_rgb"
3913            | "hsv_zu_rgb"
3914            | "hsv_в_rgb" => {
3915                let h = self.arg_num(&args, 0, 0.0)?; // 0-360
3916                let s = self.arg_num(&args, 1, 1.0)?; // 0-1
3917                let v = self.arg_num(&args, 2, 1.0)?; // 0-1
3918                let c = v * s;
3919                let x = c * (1.0 - (((h / 60.0) % 2.0) - 1.0).abs());
3920                let m = v - c;
3921                let (r1, g1, b1) = if h < 60.0 {
3922                    (c, x, 0.0)
3923                } else if h < 120.0 {
3924                    (x, c, 0.0)
3925                } else if h < 180.0 {
3926                    (0.0, c, x)
3927                } else if h < 240.0 {
3928                    (0.0, x, c)
3929                } else if h < 300.0 {
3930                    (x, 0.0, c)
3931                } else {
3932                    (c, 0.0, x)
3933                };
3934                let r = ((r1 + m) * 255.0).round();
3935                let g = ((g1 + m) * 255.0).round();
3936                let b = ((b1 + m) * 255.0).round();
3937                return Ok(Value::List(Rc::new(vec![
3938                    Value::Number(r),
3939                    Value::Number(g),
3940                    Value::Number(b),
3941                ])));
3942            },
3943
3944            "lerp_color"
3945            | "ไล่สี"
3946            | "颜色插值"
3947            | "色補間"
3948            | "색보간"
3949            | "میان‌یابی_رنگ"
3950            | "استيفاء_اللون"
3951            | "אינטרפולציית_צבע"
3952            | "رنگ_درمیانی_قدر"
3953            | "interpoler_couleur"
3954            | "farbe_interpolieren"
3955            | "интерполировать_цвет" => {
3956                let r1 = self.arg_num(&args, 0, 0.0)?;
3957                let g1 = self.arg_num(&args, 1, 0.0)?;
3958                let b1 = self.arg_num(&args, 2, 0.0)?;
3959                let r2 = self.arg_num(&args, 3, 255.0)?;
3960                let g2 = self.arg_num(&args, 4, 255.0)?;
3961                let b2 = self.arg_num(&args, 5, 255.0)?;
3962                let t = self.arg_num(&args, 6, 0.0)?;
3963                let r = r1 + (r2 - r1) * t;
3964                let g = g1 + (g2 - g1) * t;
3965                let b = b1 + (b2 - b1) * t;
3966                let c = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
3967                self.gfx.borrow_mut().color = c;
3968                return Ok(Value::Unit);
3969            },
3970
3971            // ── Step 3: Real-Time Clock ──
3972            "time_now"
3973            | "เวลาปัจจุบัน"
3974            | "当前时间"
3975            | "経過時間"
3976            | "현재시간"
3977            | "زمان_اکنون"
3978            | "الوقت_الآن"
3979            | "הזמן_עכשיו"
3980            | "ابھی_کا_وقت"
3981            | "temps_actuel"
3982            | "aktuelle_zeit"
3983            | "текущее_время" => {
3984                return Ok(Value::Number(
3985                    crate::runtime::now_secs() - self.start_time_secs,
3986                ));
3987            },
3988
3989            // Wall-clock seconds since the Unix epoch (real date/time). Lets a
3990            // program defer deterministic-yet-evolving generation to the actual
3991            // datetime — same clock → same world, advancing as real time passes.
3992            "epoch_now"
3993            | "เวลาโลก"
3994            | "datetime"
3995            | "现在时刻"
3996            | "現在時刻"
3997            | "현재시각"
3998            | "مهر_زمانی_اکنون"
3999            | "طابع_الوقت_الآن"
4000            | "חותמת_זמן_עכשיו"
4001            | "ایپاک_وقت" => {
4002                return Ok(Value::Number(crate::runtime::now_secs()));
4003            },
4004
4005            "frame_count"
4006            | "เฟรม"
4007            | "帧数"
4008            | "フレーム数"
4009            | "프레임수"
4010            | "شمار_فریم"
4011            | "عدد_الإطارات"
4012            | "ספירת_פריימים"
4013            | "فریم_شمار"
4014            | "nombre_images"
4015            | "bildanzahl"
4016            | "число_кадров" => {
4017                return Ok(Value::Number(self.frame_num as f64));
4018            },
4019
4020            // ── Step 4: Microphone Input ──
4021            "mic_open"
4022            | "เปิดไมค์"
4023            | "开麦克风"
4024            | "マイク開く"
4025            | "마이크열기"
4026            | "باز_کردن_میکروفون"
4027            | "افتح_الميكروفون"
4028            | "פתח_מיקרופון"
4029            | "مائیکروفون_کھولو"
4030            | "ouvrir_micro"
4031            | "mikrofon_öffnen"
4032            | "открыть_микрофон" => {
4033                #[cfg(not(target_arch = "wasm32"))]
4034                {
4035                    match ling_mic::MicInput::open(Default::default()) {
4036                        Ok(mic) => {
4037                            let _ = mic.start(|_samples: &[f32]| {}); // No-op callback
4038                            self.mic = Some(mic);
4039                            return Ok(Value::Number(1.0)); // opened
4040                        },
4041                        // No device / permission denied → graceful: don't crash the game loop.
4042                        // Returns 0.0; mic_rms/mic_peak return 0.0 while self.mic is None.
4043                        Err(_e) => {
4044                            self.mic = None;
4045                            return Ok(Value::Number(0.0));
4046                        },
4047                    }
4048                }
4049                #[cfg(target_arch = "wasm32")]
4050                return Ok(Value::Unit);
4051            },
4052
4053            "mic_rms"
4054            | "เสียงRMS"
4055            | "麦克风音量"
4056            | "マイクRMS"
4057            | "마이크RMS"
4058            | "RMS_میکروفون"
4059            | "RMS_الميكروفون"
4060            | "RMS_מיקרופון"
4061            | "مائیکروفون_RMS"
4062            | "rms_micro"
4063            | "mikrofon_rms"
4064            | "rms_микрофона" => {
4065                #[cfg(not(target_arch = "wasm32"))]
4066                {
4067                    let rms = self
4068                        .mic
4069                        .as_ref()
4070                        .map(|m: &ling_mic::MicInput| m.rms())
4071                        .unwrap_or(0.0);
4072                    return Ok(Value::Number(rms as f64));
4073                }
4074                #[cfg(target_arch = "wasm32")]
4075                return Ok(Value::Number(0.0));
4076            },
4077
4078            "mic_peak"
4079            | "เสียงพีค"
4080            | "麦克风峰值"
4081            | "マイクピーク"
4082            | "마이크피크"
4083            | "اوج_میکروفون"
4084            | "ذروة_الميكروفون"
4085            | "שיא_מיקרופון"
4086            | "مائیکروفون_چوٹی"
4087            | "crête_micro"
4088            | "mikrofon_spitze"
4089            | "пик_микрофона" => {
4090                #[cfg(not(target_arch = "wasm32"))]
4091                {
4092                    let peak = self
4093                        .mic
4094                        .as_ref()
4095                        .map(|m: &ling_mic::MicInput| m.peak())
4096                        .unwrap_or(0.0);
4097                    return Ok(Value::Number(peak as f64));
4098                }
4099                #[cfg(target_arch = "wasm32")]
4100                return Ok(Value::Number(0.0));
4101            },
4102
4103            "mic_fft"
4104            | "วิเคราะห์เสียงสด"
4105            | "实时频谱"
4106            | "リアルタイムFFT"
4107            | "실시간FFT"
4108            | "FFT_میکروفون"
4109            | "FFT_الميكروفون"
4110            | "FFT_מיקרופון"
4111            | "مائیکروفون_FFT"
4112            | "fft_micro"
4113            | "mikrofon_fft"
4114            | "fft_микрофона" => {
4115                #[cfg(not(target_arch = "wasm32"))]
4116                {
4117                    let n = self.arg_num(&args, 0, 8.0)? as usize;
4118                    if let Some(mic) = self.mic.as_ref() {
4119                        let samples = mic.latest_samples();
4120                        self.fft.borrow_mut().push_samples(&samples);
4121                    }
4122                    let bands = self.fft.borrow().freq_bands(n);
4123                    let result: Vec<Value> =
4124                        bands.iter().map(|&v| Value::Number(v as f64)).collect();
4125                    return Ok(Value::List(Rc::new(result)));
4126                }
4127                #[cfg(target_arch = "wasm32")]
4128                return Ok(Value::List(Vec::new().into()));
4129            },
4130
4131            // ── Step 5: Additive Blend Mode ──
4132            "set_blend"
4133            | "โหมดผสม"
4134            | "混合模式"
4135            | "ブレンドモード"
4136            | "블렌드모드"
4137            | "تنظیم_ترکیب"
4138            | "عيّن_المزج"
4139            | "קבע_מיזוג"
4140            | "بلینڈ_مقرر_کرو"
4141            | "définir_mélange"
4142            | "mischmodus_setzen"
4143            | "задать_смешивание" => {
4144                let mode = self.arg_num(&args, 0, 0.0)? as u8;
4145                let mut gfx = self.gfx.borrow_mut();
4146                gfx.blend = mode;
4147                let a = gfx.alpha;
4148                gfx.depth_queue.set_state(mode, a); // 3-D queue captures blend for subsequent pushes
4149                return Ok(Value::Unit);
4150            },
4151
4152            // set_antialias(on) — smooth wireframe strokes (lines / edges / arcs /
4153            // circle outlines) via Xiaolin-Wu coverage. Default OFF = crisp,
4154            // opaque, aliased pixels; pass 1 to opt into smooth edges.
4155            "set_antialias"
4156            | "ตั้งลบรอยหยัก"
4157            | "抗锯齿"
4158            | "アンチエイリアス"
4159            | "안티에일리어싱"
4160            | "تنظیم_ضدلبه‌دندانه"
4161            | "عيّن_مضاد_التسنن"
4162            | "קבע_החלקת_קצוות"
4163            | "اینٹی_الائیسنگ_مقرر_کرو" => {
4164                let on = self.arg_num(&args, 0, 1.0)? > 0.5;
4165                self.gfx.borrow_mut().antialias = on;
4166                return Ok(Value::Unit);
4167            },
4168            // get_antialias() -> bool — current wireframe anti-aliasing state.
4169            "get_antialias"
4170            | "อ่านลบรอยหยัก"
4171            | "读取抗锯齿"
4172            | "アンチエイリアス取得"
4173            | "안티에일리어싱상태"
4174            | "خواندن_ضدلبه‌دندانه"
4175            | "اقرأ_مضاد_التسنن"
4176            | "קרא_החלקת_קצוות"
4177            | "اینٹی_الائیسنگ_پڑھو" => {
4178                return Ok(Value::Bool(self.gfx.borrow().antialias));
4179            },
4180
4181            // set_font_antialias(on) — smooth `font_text`/`font_text_fill` glyph
4182            // edges, independent of `set_antialias` (which only covers wireframe
4183            // strokes). Default OFF = crisp, hard-edged text; pass 1 to opt in.
4184            "set_font_antialias" | "글꼴안티에일리어싱" => {
4185                let on = self.arg_num(&args, 0, 1.0)? > 0.5;
4186                self.gfx.borrow_mut().font_antialias = on;
4187                return Ok(Value::Unit);
4188            },
4189            // get_font_antialias() -> bool — current font anti-aliasing state.
4190            "get_font_antialias" | "글꼴안티에일리어싱상태" => {
4191                return Ok(Value::Bool(self.gfx.borrow().font_antialias));
4192            },
4193
4194            // ── Step 6: Circle Primitives ──
4195            "draw_circle"
4196            | "วาดวงกลม"
4197            | "画圆"
4198            | "円描画"
4199            | "원그리기"
4200            | "رسم_دایره"
4201            | "ارسم_دائرة"
4202            | "צייר_עיגול"
4203            | "دائرہ_کھینچو"
4204            | "dessiner_cercle"
4205            | "kreis_zeichnen"
4206            | "рисовать_круг" => {
4207                let cx = self.arg_num(&args, 0, 0.0)? as i32;
4208                let cy = self.arg_num(&args, 1, 0.0)? as i32;
4209                let r = self.arg_num(&args, 2, 10.0)? as i32;
4210                let mut gfx = self.gfx.borrow_mut();
4211                let (w, h, color, blend) =
4212                    (gfx.width as i32, gfx.height as i32, gfx.color, gfx.blend);
4213                if gfx.antialias {
4214                    let (uw, uh) = (gfx.width, gfx.height);
4215                    let segs = ((r.max(1) as u32) * 4).clamp(24, 512);
4216                    crate::gfx::raster::draw_arc(
4217                        &mut gfx.buffer,
4218                        uw,
4219                        uh,
4220                        color,
4221                        true,
4222                        blend == 1,
4223                        cx as f32,
4224                        cy as f32,
4225                        r as f32,
4226                        0.0,
4227                        std::f32::consts::TAU,
4228                        segs,
4229                    );
4230                } else {
4231                    draw_circle_outline(&mut gfx.buffer, w, h, cx, cy, r, color, blend);
4232                }
4233                return Ok(Value::Unit);
4234            },
4235
4236            "draw_filled_circle"
4237            | "draw_disc"
4238            | "วาดวงกลมทึบ"
4239            | "画实心圆"
4240            | "塗りつぶし円"
4241            | "원채우기"
4242            | "رسم_دایره_توپر"
4243            | "ارسم_دائرة_ممتلئة"
4244            | "צייר_עיגול_מלא"
4245            | "بھرا_دائرہ_کھینچو" => {
4246                let cx = self.arg_num(&args, 0, 0.0)? as i32;
4247                let cy = self.arg_num(&args, 1, 0.0)? as i32;
4248                let r = self.arg_num(&args, 2, 10.0)? as i32;
4249                let mut gfx = self.gfx.borrow_mut();
4250                let (w, h, color, blend) =
4251                    (gfx.width as i32, gfx.height as i32, gfx.color, gfx.blend);
4252                draw_circle_filled(&mut gfx.buffer, w, h, cx, cy, r, color, blend);
4253                return Ok(Value::Unit);
4254            },
4255
4256            // draw_arc(cx, cy, r, a0, a1 [, segments]) — stroke a circular arc in
4257            // the pen colour (full circle when a1-a0 = TAU). Honors the antialias
4258            // flag; opaque by default (additive when blend = 1).
4259            "draw_arc"
4260            | "arc"
4261            | "วาดส่วนโค้ง"
4262            | "画弧"
4263            | "円弧描画"
4264            | "호그리기"
4265            | "رسم_کمان"
4266            | "ارسم_قوسا"
4267            | "צייר_קשת"
4268            | "آرک_کھینچو" => {
4269                let cx = self.arg_num(&args, 0, 0.0)? as f32;
4270                let cy = self.arg_num(&args, 1, 0.0)? as f32;
4271                let r = self.arg_num(&args, 2, 10.0)? as f32;
4272                let a0 = self.arg_num(&args, 3, 0.0)? as f32;
4273                let a1 = self.arg_num(&args, 4, std::f64::consts::TAU)? as f32;
4274                let default_segs = ((r.abs() * (a1 - a0).abs()).ceil() as u32).clamp(8, 1024);
4275                let segs = self.arg_num(&args, 5, default_segs as f64)? as u32;
4276                let mut gfx = self.gfx.borrow_mut();
4277                let color = gfx.color;
4278                #[cfg(not(target_arch = "wasm32"))]
4279                {
4280                    let (uw, uh, aa, add) = (gfx.width, gfx.height, gfx.antialias, gfx.blend == 1);
4281                    crate::gfx::raster::draw_arc(
4282                        &mut gfx.buffer,
4283                        uw,
4284                        uh,
4285                        color,
4286                        aa,
4287                        add,
4288                        cx,
4289                        cy,
4290                        r,
4291                        a0,
4292                        a1,
4293                        segs,
4294                    );
4295                }
4296                #[cfg(target_arch = "wasm32")]
4297                {
4298                    let segs_f = segs.max(1);
4299                    let step = (a1 - a0) / segs_f as f32;
4300                    let mut px = cx + r * a0.cos();
4301                    let mut py = cy + r * a0.sin();
4302                    let mut i = 1u32;
4303                    while i <= segs_f {
4304                        let a = a0 + step * i as f32;
4305                        let nx = cx + r * a.cos();
4306                        let ny = cy + r * a.sin();
4307                        gfx.depth_queue.push_line(0.0, color, px, py, nx, ny);
4308                        px = nx;
4309                        py = ny;
4310                        i += 1;
4311                    }
4312                }
4313                return Ok(Value::Unit);
4314            },
4315
4316            // ── Step 7: Transparent fills, gradient surfaces & colored shadows ──
4317            // These all write straight into the software framebuffer (gfx.buffer)
4318            // on both native and web, so no target gating is needed.
4319
4320            // set_alpha(a) — pen opacity 0..1 for the alpha-blended fills below.
4321            "set_alpha"
4322            | "ตั้งความโปร่งใส"
4323            | "设透明"
4324            | "アルファ設定"
4325            | "투명도설정"
4326            | "تنظیم_شفافیت"
4327            | "عيّن_الشفافية"
4328            | "קבע_שקיפות"
4329            | "شفافیت_مقرر_کرو"
4330            | "définir_alpha"
4331            | "alpha_setzen"
4332            | "задать_альфа" => {
4333                let a = self.arg_num(&args, 0, 1.0)? as f32;
4334                let mut gfx = self.gfx.borrow_mut();
4335                gfx.alpha = a.clamp(0.0, 1.0);
4336                let (m, al) = (gfx.blend, gfx.alpha);
4337                gfx.depth_queue.set_state(m, al); // 3-D queue captures alpha for subsequent pushes
4338                return Ok(Value::Unit);
4339            },
4340
4341            // mesh_hue(radians) — hue-rotate the baked per-tri colours of every
4342            // subsequent mesh_draw (.lmesh). 0 resets. Cheap: one matrix per call.
4343            "mesh_hue" | "หมุนสีเมช" | "فام_مش" | "صبغة_الشبكة" | "גוון_רשת" | "میش_ہیو" =>
4344            {
4345                let h = self.arg_num(&args, 0, 0.0)? as f32;
4346                let g = self.arg_num(&args, 1, 1.0)? as f32;
4347                let mut gfx = self.gfx.borrow_mut();
4348                gfx.mesh_hue = h;
4349                gfx.mesh_hue_gain = g.max(0.0);
4350                return Ok(Value::Unit);
4351            },
4352
4353            // set_frame_blur(amount 0..0.95) — afterimage trails: blend the previous
4354            // presented frame into each new one. 0 = off (also frees the ghost buffer).
4355            "set_frame_blur"
4356            | "frame_blur"
4357            | "เบลอเฟรม"
4358            | "تنظیم_تاری_فریم"
4359            | "عيّن_ضبابية_الإطار"
4360            | "קבע_טשטוש_פריים"
4361            | "فریم_بلر_مقرر_کرو" => {
4362                let a = self.arg_num(&args, 0, 0.0)? as f32;
4363                let mut gfx = self.gfx.borrow_mut();
4364                gfx.frame_blur = a.clamp(0.0, 0.95);
4365                if gfx.frame_blur <= 0.0 {
4366                    gfx.prev_frame = Vec::new();
4367                }
4368                return Ok(Value::Unit);
4369            },
4370
4371            // set_line_hue_cycle(rate) — rapidly cycle the hue of ALL wireframe line
4372            // strokes (draw_line / draw_line_3d). `rate` in radians/sec; 0 = off.
4373            // Process-global so a single call covers every stroke, every frame.
4374            "set_line_hue_cycle"
4375            | "ตั้งวนสีเส้น"
4376            | "تنظیم_چرخه_فام_خط"
4377            | "عيّن_دورة_صبغة_الخط"
4378            | "קבע_מחזור_גוון_קו"
4379            | "لائن_ہیو_سائیکل_مقرر_کرو" => {
4380                let rate = self.arg_num(&args, 0, 0.0)?;
4381                crate::runtime::set_line_hue_rate(rate);
4382                return Ok(Value::Unit);
4383            },
4384
4385            // set_color_space(mode) — 0 = legacy sRGB compositing (default),
4386            // 1 = gamma-correct linear-light compositing (blend in linear, store
4387            // sRGB) so alpha and gradients don't darken/shift hue.
4388            "set_color_space"
4389            | "ปริภูมิสี"
4390            | "色彩空间"
4391            | "色空間"
4392            | "색공간"
4393            | "تنظیم_فضای_رنگ"
4394            | "عيّن_فضاء_اللون"
4395            | "קבע_מרחב_צבע"
4396            | "کلر_اسپیس_مقرر_کرو"
4397            | "définir_espace_couleur"
4398            | "farbraum_setzen"
4399            | "задать_цветовое_пространство" => {
4400                let m = self.arg_num(&args, 0, 0.0)? as i64;
4401                self.gfx.borrow_mut().linear_blend = m != 0;
4402                return Ok(Value::Unit);
4403            },
4404
4405            // set_gradient_space(mode) — 1 = perceptual OkLab gradient interp
4406            // (default), 0 = legacy sRGB. Affects grad_triangle / grad_rect.
4407            "set_gradient_space"
4408            | "ปริภูมิไล่สี"
4409            | "渐变空间"
4410            | "グラデ空間"
4411            | "그라데이션공간"
4412            | "تنظیم_فضای_گرادیان"
4413            | "عيّن_فضاء_التدرج"
4414            | "קבע_מרחב_גרדיאנט"
4415            | "گریڈینٹ_اسپیس_مقرر_کرو"
4416            | "définir_espace_dégradé"
4417            | "verlaufsraum_setzen"
4418            | "задать_пространство_градиента" => {
4419                let m = self.arg_num(&args, 0, 1.0)? as i64;
4420                self.gfx.borrow_mut().grad_oklab = m != 0;
4421                return Ok(Value::Unit);
4422            },
4423
4424            // mix_color(r0,g0,b0, r1,g1,b1, t) — set the pen colour to the
4425            // perceptual OkLab blend of two colours (t in 0..1). Far nicer
4426            // mid-tones than a raw RGB lerp.
4427            "mix_color"
4428            | "ผสมสี"
4429            | "混合颜色"
4430            | "色混合"
4431            | "색혼합"
4432            | "ترکیب_رنگ"
4433            | "امزج_اللون"
4434            | "ערבב_צבע"
4435            | "رنگ_ملاؤ"
4436            | "mélanger_couleur"
4437            | "farbe_mischen"
4438            | "смешать_цвет" => {
4439                let c0 = rgb(
4440                    self.arg_num(&args, 0, 0.0)?,
4441                    self.arg_num(&args, 1, 0.0)?,
4442                    self.arg_num(&args, 2, 0.0)?,
4443                );
4444                let c1 = rgb(
4445                    self.arg_num(&args, 3, 255.0)?,
4446                    self.arg_num(&args, 4, 255.0)?,
4447                    self.arg_num(&args, 5, 255.0)?,
4448                );
4449                let t = self.arg_num(&args, 6, 0.5)? as f32;
4450                self.gfx.borrow_mut().color = crate::gfx::color::mix_oklab(c0, c1, t);
4451                return Ok(Value::Unit);
4452            },
4453
4454            // set_depth_test(on) — enable the per-pixel z-buffer for the deferred
4455            // 3-D/queued draws (correct interpenetration) instead of painter's-
4456            // only sort. 0 = off (default), non-zero = on.
4457            "set_depth_test"
4458            | "ทดสอบความลึก"
4459            | "深度测试"
4460            | "深度テスト"
4461            | "깊이테스트"
4462            | "تنظیم_آزمون_عمق"
4463            | "عيّن_اختبار_العمق"
4464            | "קבע_בדיקת_עומק"
4465            | "ڈیپتھ_ٹیسٹ_مقرر_کرو"
4466            | "définir_test_profondeur"
4467            | "tiefentest_setzen"
4468            | "задать_тест_глубины" => {
4469                let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
4470                self.gfx.borrow_mut().depth_test = on;
4471                return Ok(Value::Unit);
4472            },
4473
4474            // set_flat_shade(on) / ตั้งแฟลตเชด — perf test: skip all per-triangle/mesh
4475            // lighting (compute_lit_color) and draw with the raw pen colour.
4476            "set_flat_shade"
4477            | "ตั้งแฟลตเชด"
4478            | "平面着色"
4479            | "フラット着色"
4480            | "평면음영"
4481            | "تنظیم_سایه‌پردازی_تخت"
4482            | "عيّن_تظليلا_مسطحا"
4483            | "קבע_הצללה_שטוחה"
4484            | "فلیٹ_شیڈ_مقرر_کرو" => {
4485                let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
4486                self.gfx.borrow_mut().flat_shade = on;
4487                return Ok(Value::Unit);
4488            },
4489
4490            // set_normal_override(x,y,z) - force subsequent triangle/mesh lighting
4491            // to use a stylized world-space normal until reset_normal_override().
4492            "set_normal_override" => {
4493                let x = self.arg_num(&args, 0, 0.0)? as f32;
4494                let y = self.arg_num(&args, 1, -1.0)? as f32;
4495                let z = self.arg_num(&args, 2, 0.0)? as f32;
4496                self.gfx.borrow_mut().normal_override = Some([x, y, z]);
4497                return Ok(Value::Unit);
4498            },
4499
4500            "reset_normal_override" => {
4501                self.gfx.borrow_mut().normal_override = None;
4502                return Ok(Value::Unit);
4503            },
4504
4505            // clear_depth() / ล้างความลึก — force the z-buffer to clear on the next
4506            // flush. `เติม` already does this; call explicitly to start a fresh
4507            // depth pass mid-frame (e.g. a separate overlay scene).
4508            "clear_depth"
4509            | "ล้างความลึก"
4510            | "清深度"
4511            | "深度クリア"
4512            | "깊이지우기"
4513            | "پاک‌کردن_عمق"
4514            | "امسح_العمق"
4515            | "נקה_עומק"
4516            | "گہرائی_صاف_کرو" => {
4517                self.gfx.borrow_mut().zbuf_needs_clear = true;
4518                return Ok(Value::Unit);
4519            },
4520
4521            // depth_blur(focus, range, radius) / เบลอความลึก — depth-of-field post
4522            // pass over the framebuffer using the z-buffer: sharp at camera-space
4523            // depth `focus`, blurred up to `radius` px as depth departs by `range`.
4524            // Background (no geometry) blurs fully. Call AFTER `flush_3d` (so the
4525            // z-buffer is populated) and BEFORE `present`. Needs `set_depth_test(1)`.
4526            "depth_blur"
4527            | "เบลอความลึก"
4528            | "dof"
4529            | "depth_of_field"
4530            | "景深"
4531            | "تاری_عمق"
4532            | "ضبابية_العمق"
4533            | "טשטוש_עומק"
4534            | "ڈیپتھ_بلر" => {
4535                let focus = self.arg_num(&args, 0, 30.0)? as f32;
4536                let range = self.arg_num(&args, 1, 60.0)? as f32;
4537                let radius = self.arg_num(&args, 2, 3.0)?.max(0.0) as usize;
4538                // oil [0..1] — oil-slick treatment of the blurred zone:
4539                // iridescent chroma fringe + hue swirl (water / heat haze).
4540                let oil = self.arg_num(&args, 3, 0.0)? as f32;
4541                let mut gfx = self.gfx.borrow_mut();
4542                let w = gfx.width;
4543                let h = gfx.height;
4544                if gfx.depth_buf.len() == w * h {
4545                    let g = &mut *gfx;
4546                    crate::gfx::raster::depth_of_field(
4547                        &mut g.buffer,
4548                        &g.depth_buf,
4549                        w,
4550                        h,
4551                        focus,
4552                        range,
4553                        radius,
4554                        oil,
4555                    );
4556                }
4557                return Ok(Value::Unit);
4558            },
4559
4560            // light_pool(x, y, z, radius, r, g, b, intensity) / แอ่งแสง —
4561            // volumetric light splash: a soft additive radial vector gradient on
4562            // the floor at height y — the coloured pool a light throws on the
4563            // ground (underwater-light look). Smooth transparent edge, distance-
4564            // fog aware. Colours 0-255; intensity ~0.2-1.5.
4565            "light_pool"
4566            | "แอ่งแสง"
4567            | "光池"
4568            | "ライトプール"
4569            | "빛웅덩이"
4570            | "برکه_نور"
4571            | "بركة_ضوء"
4572            | "בריכת_אור"
4573            | "روشنی_تالاب"
4574            | "bassin_lumière"
4575            | "lichtpfütze"
4576            | "лужа_света" => {
4577                let x = self.arg_num(&args, 0, 0.0)? as f32;
4578                let y = self.arg_num(&args, 1, 0.0)? as f32;
4579                let z = self.arg_num(&args, 2, 0.0)? as f32;
4580                let radius = self.arg_num(&args, 3, 20.0)? as f32;
4581                let r = self.arg_num(&args, 4, 255.0)? as f32 / 255.0;
4582                let g = self.arg_num(&args, 5, 255.0)? as f32 / 255.0;
4583                let b = self.arg_num(&args, 6, 255.0)? as f32 / 255.0;
4584                let inten = self.arg_num(&args, 7, 1.0)? as f32;
4585                self.gfx
4586                    .borrow_mut()
4587                    .emit_light_pool(x, y, z, radius, [r, g, b], inten);
4588                return Ok(Value::Unit);
4589            },
4590
4591            // light_beam(x, y, z, floor_y, radius, r, g, b, intensity) / ลำแสงไฟ —
4592            // volumetric god-ray shaft: a soft additive double-cone from the
4593            // light position down to the floor plane, spreading to `radius`.
4594            // Pair with light_pool at the base. Colours 0-255.
4595            "light_beam"
4596            | "ลำแสงไฟ"
4597            | "光柱"
4598            | "ライトビーム"
4599            | "빛기둥"
4600            | "پرتو_نور"
4601            | "شعاع_ضوء"
4602            | "קרן_אור"
4603            | "روشنی_شعاع"
4604            | "faisceau_lumière"
4605            | "lichtstrahl"
4606            | "луч_света" => {
4607                let x = self.arg_num(&args, 0, 0.0)? as f32;
4608                let y = self.arg_num(&args, 1, 0.0)? as f32;
4609                let z = self.arg_num(&args, 2, 0.0)? as f32;
4610                let fy = self.arg_num(&args, 3, 0.0)? as f32;
4611                let radius = self.arg_num(&args, 4, 14.0)? as f32;
4612                let r = self.arg_num(&args, 5, 255.0)? as f32 / 255.0;
4613                let g = self.arg_num(&args, 6, 255.0)? as f32 / 255.0;
4614                let b = self.arg_num(&args, 7, 255.0)? as f32 / 255.0;
4615                let inten = self.arg_num(&args, 8, 1.0)? as f32;
4616                self.gfx
4617                    .borrow_mut()
4618                    .emit_light_beam(x, y, z, fy, radius, [r, g, b], inten);
4619                return Ok(Value::Unit);
4620            },
4621
4622            // grad_triangle(x0,y0,r0,g0,b0, x1,y1,r1,g1,b1, x2,y2,r2,g2,b2)
4623            // Smooth per-vertex gradient triangle — a cheap lit surface: put the
4624            // bright colour on the vertex facing the light. Honours set_alpha.
4625            "grad_triangle"
4626            | "สามเหลี่ยมไล่สี"
4627            | "渐变三角"
4628            | "グラデ三角"
4629            | "그라데삼각"
4630            | "مثلث_گرادیان"
4631            | "مثلث_متدرج"
4632            | "משולש_גרדיאנט"
4633            | "گریڈینٹ_مثلث"
4634            | "triangle_dégradé"
4635            | "dreieck_verlauf"
4636            | "градиент_треугольник" => {
4637                let x0 = self.arg_num(&args, 0, 0.0)? as f32;
4638                let y0 = self.arg_num(&args, 1, 0.0)? as f32;
4639                let c0 = rgb(
4640                    self.arg_num(&args, 2, 255.0)?,
4641                    self.arg_num(&args, 3, 255.0)?,
4642                    self.arg_num(&args, 4, 255.0)?,
4643                );
4644                let x1 = self.arg_num(&args, 5, 0.0)? as f32;
4645                let y1 = self.arg_num(&args, 6, 0.0)? as f32;
4646                let c1 = rgb(
4647                    self.arg_num(&args, 7, 255.0)?,
4648                    self.arg_num(&args, 8, 255.0)?,
4649                    self.arg_num(&args, 9, 255.0)?,
4650                );
4651                let x2 = self.arg_num(&args, 10, 0.0)? as f32;
4652                let y2 = self.arg_num(&args, 11, 0.0)? as f32;
4653                let c2 = rgb(
4654                    self.arg_num(&args, 12, 255.0)?,
4655                    self.arg_num(&args, 13, 255.0)?,
4656                    self.arg_num(&args, 14, 255.0)?,
4657                );
4658                let mut gfx = self.gfx.borrow_mut();
4659                let (w, h, alpha, mode, lin, ok) = (
4660                    gfx.width,
4661                    gfx.height,
4662                    gfx.alpha,
4663                    gfx.blend,
4664                    gfx.linear_blend,
4665                    gfx.grad_oklab,
4666                );
4667                crate::gfx::raster::fill_triangle_grad(
4668                    &mut gfx.buffer,
4669                    w,
4670                    h,
4671                    alpha,
4672                    mode,
4673                    lin,
4674                    ok,
4675                    x0,
4676                    y0,
4677                    c0,
4678                    x1,
4679                    y1,
4680                    c1,
4681                    x2,
4682                    y2,
4683                    c2,
4684                );
4685                return Ok(Value::Unit);
4686            },
4687
4688            // grad_rect(x,y,w,h, r0,g0,b0, r1,g1,b1, dir) — linear-gradient rect.
4689            // dir 0 = horizontal (left→right), else vertical (top→bottom).
4690            "grad_rect"
4691            | "สี่เหลี่ยมไล่สี"
4692            | "渐变矩形"
4693            | "グラデ矩形"
4694            | "그라데사각"
4695            | "مستطیل_گرادیان"
4696            | "مستطيل_متدرج"
4697            | "מלבן_גרדיאנט"
4698            | "گریڈینٹ_مستطیل"
4699            | "rectangle_dégradé"
4700            | "rechteck_verlauf"
4701            | "градиент_прямоугольник" => {
4702                let x = self.arg_num(&args, 0, 0.0)? as f32;
4703                let y = self.arg_num(&args, 1, 0.0)? as f32;
4704                let rw = self.arg_num(&args, 2, 0.0)? as f32;
4705                let rh = self.arg_num(&args, 3, 0.0)? as f32;
4706                let c0 = rgb(
4707                    self.arg_num(&args, 4, 255.0)?,
4708                    self.arg_num(&args, 5, 255.0)?,
4709                    self.arg_num(&args, 6, 255.0)?,
4710                );
4711                let c1 = rgb(
4712                    self.arg_num(&args, 7, 0.0)?,
4713                    self.arg_num(&args, 8, 0.0)?,
4714                    self.arg_num(&args, 9, 0.0)?,
4715                );
4716                let dir = self.arg_num(&args, 10, 1.0)? as u8;
4717                let mut gfx = self.gfx.borrow_mut();
4718                let (w, h, alpha, mode, lin, ok) = (
4719                    gfx.width,
4720                    gfx.height,
4721                    gfx.alpha,
4722                    gfx.blend,
4723                    gfx.linear_blend,
4724                    gfx.grad_oklab,
4725                );
4726                crate::gfx::raster::fill_rect_grad(
4727                    &mut gfx.buffer,
4728                    w,
4729                    h,
4730                    alpha,
4731                    mode,
4732                    lin,
4733                    ok,
4734                    x,
4735                    y,
4736                    rw,
4737                    rh,
4738                    c0,
4739                    c1,
4740                    dir,
4741                );
4742                return Ok(Value::Unit);
4743            },
4744
4745            // shadow_blob(cx,cy, rx,ry, alpha) — soft colored shadow ellipse in
4746            // the current pen colour. Dark colour = normal shadow; any hue = a
4747            // tinted/coloured shadow. Edge softness comes from shadow_params.
4748            "shadow_blob"
4749            | "เงาวงรี"
4750            | "阴影斑"
4751            | "影ブロブ"
4752            | "그림자블롭"
4753            | "لکه_سایه"
4754            | "بقعة_ظل"
4755            | "כתם_צל"
4756            | "سایہ_دھبہ"
4757            | "tache_ombre"
4758            | "schattenklecks"
4759            | "пятно_тени" => {
4760                let cx = self.arg_num(&args, 0, 0.0)? as f32;
4761                let cy = self.arg_num(&args, 1, 0.0)? as f32;
4762                let rx = self.arg_num(&args, 2, 16.0)? as f32;
4763                let ry = self.arg_num(&args, 3, 8.0)? as f32;
4764                let a = self.arg_num(&args, 4, 0.5)? as f32;
4765                let mut gfx = self.gfx.borrow_mut();
4766                let (w, h, color, soft, mode, lin) = (
4767                    gfx.width,
4768                    gfx.height,
4769                    gfx.color,
4770                    gfx.shadow.soft,
4771                    gfx.blend,
4772                    gfx.linear_blend,
4773                );
4774                crate::gfx::raster::fill_disc_soft(
4775                    &mut gfx.buffer,
4776                    w,
4777                    h,
4778                    cx,
4779                    cy,
4780                    rx,
4781                    ry,
4782                    color,
4783                    a,
4784                    soft,
4785                    mode,
4786                    lin,
4787                );
4788                return Ok(Value::Unit);
4789            },
4790
4791            // cast_shadow(cx,cy, height) — height-driven contact shadow in the
4792            // current pen colour. Closer to the surface (small height) = smaller,
4793            // darker, sharper; farther (large height) = bigger, fainter, softer.
4794            // Tune the ramp with shadow_params.
4795            "cast_shadow"
4796            | "ทอดเงา"
4797            | "投射阴影"
4798            | "影を落とす"
4799            | "그림자드리우기"
4800            | "افکندن_سایه"
4801            | "ألقِ_ظلا"
4802            | "הטל_צל"
4803            | "سایہ_ڈالو"
4804            | "projeter_ombre"
4805            | "schatten_werfen"
4806            | "отбросить_тень" => {
4807                let cx = self.arg_num(&args, 0, 0.0)? as f32;
4808                let cy = self.arg_num(&args, 1, 0.0)? as f32;
4809                let height = (self.arg_num(&args, 2, 0.0)? as f32).max(0.0);
4810                let mut gfx = self.gfx.borrow_mut();
4811                let sp = gfx.shadow;
4812                let radius = (sp.base + sp.grow * height).max(0.5);
4813                let alpha = (sp.alpha - sp.fade * height).clamp(0.04, 1.0);
4814                let soft = (sp.soft + height * 0.004).clamp(0.0, 0.95);
4815                let (w, h, color, mode, lin) = (
4816                    gfx.width,
4817                    gfx.height,
4818                    gfx.color,
4819                    gfx.blend,
4820                    gfx.linear_blend,
4821                );
4822                crate::gfx::raster::fill_disc_soft(
4823                    &mut gfx.buffer,
4824                    w,
4825                    h,
4826                    cx,
4827                    cy,
4828                    radius,
4829                    radius * 0.62,
4830                    color,
4831                    alpha,
4832                    soft,
4833                    mode,
4834                    lin,
4835                );
4836                return Ok(Value::Unit);
4837            },
4838
4839            // shadow_params(base, grow, alpha, fade, soft) — tune cast_shadow.
4840            // Each arg defaults to the current value, so you can set just one.
4841            "shadow_params"
4842            | "ตั้งค่าเงา"
4843            | "阴影参数"
4844            | "影設定"
4845            | "그림자설정"
4846            | "پارامترهای_سایه"
4847            | "معاملات_الظل"
4848            | "פרמטרי_צל"
4849            | "سایہ_پیرامیٹرز"
4850            | "paramètres_ombre"
4851            | "schattenparameter"
4852            | "параметры_тени" => {
4853                let cur = self.gfx.borrow().shadow;
4854                let base = self.arg_num(&args, 0, cur.base as f64)? as f32;
4855                let grow = self.arg_num(&args, 1, cur.grow as f64)? as f32;
4856                let alpha = self.arg_num(&args, 2, cur.alpha as f64)? as f32;
4857                let fade = self.arg_num(&args, 3, cur.fade as f64)? as f32;
4858                let soft = self.arg_num(&args, 4, cur.soft as f64)? as f32;
4859                self.gfx.borrow_mut().shadow =
4860                    crate::gfx::ShadowParams { base, grow, alpha, fade, soft };
4861                return Ok(Value::Unit);
4862            },
4863
4864            // depth_triangle(x0,y0, x1,y1, x2,y2, z) — queue a depth-sorted tri in
4865            // the current colour. Drawn back-to-front (painter's algorithm) at
4866            // present(); larger z = farther away. Lets 2-D sprites/quads sort by
4867            // depth the same way 3-D faces do.
4868            "depth_triangle"
4869            | "สามเหลี่ยมเรียงลึก"
4870            | "深度三角"
4871            | "深度三角形"
4872            | "깊이삼각"
4873            | "مثلث_عمق"
4874            | "مثلث_العمق"
4875            | "משולש_עומק"
4876            | "گہرائی_مثلث"
4877            | "triangle_profondeur"
4878            | "tiefendreieck"
4879            | "глубина_треугольник" => {
4880                let x0 = self.arg_num(&args, 0, 0.0)? as f32;
4881                let y0 = self.arg_num(&args, 1, 0.0)? as f32;
4882                let x1 = self.arg_num(&args, 2, 0.0)? as f32;
4883                let y1 = self.arg_num(&args, 3, 0.0)? as f32;
4884                let x2 = self.arg_num(&args, 4, 0.0)? as f32;
4885                let y2 = self.arg_num(&args, 5, 0.0)? as f32;
4886                let z = self.arg_num(&args, 6, 0.0)? as f32;
4887                let mut gfx = self.gfx.borrow_mut();
4888                let color = gfx.color;
4889                gfx.depth_queue
4890                    .push_triangle(z, color, x0, y0, x1, y1, x2, y2);
4891                return Ok(Value::Unit);
4892            },
4893
4894            // depth_line(x0,y0, x1,y1, z) — queue a depth-sorted line in the
4895            // current colour (same painter's queue as depth_triangle).
4896            "depth_line"
4897            | "เส้นเรียงลึก"
4898            | "深度线"
4899            | "深度線"
4900            | "깊이선"
4901            | "خط_عمق"
4902            | "خط_العمق"
4903            | "קו_עומק"
4904            | "گہرائی_لکیر"
4905            | "ligne_profondeur"
4906            | "tiefenlinie"
4907            | "глубина_линия" => {
4908                let x0 = self.arg_num(&args, 0, 0.0)? as f32;
4909                let y0 = self.arg_num(&args, 1, 0.0)? as f32;
4910                let x1 = self.arg_num(&args, 2, 0.0)? as f32;
4911                let y1 = self.arg_num(&args, 3, 0.0)? as f32;
4912                let z = self.arg_num(&args, 4, 0.0)? as f32;
4913                let mut gfx = self.gfx.borrow_mut();
4914                let color = gfx.color;
4915                gfx.depth_queue.push_line(z, color, x0, y0, x1, y1);
4916                return Ok(Value::Unit);
4917            },
4918
4919            // ══════════════════════════════════════════════════════════════════
4920            // GRAPHICS BUILTINS
4921            // Thai names first, then English aliases.
4922            // ══════════════════════════════════════════════════════════════════
4923
4924            // ── เปิดหน้าต่าง(width, height, title) — open_window ──
4925            "เปิดหน้าต่าง"
4926            | "open_window"
4927            | "gfx_window"
4928            | "开窗"
4929            | "ウィンドウ開く"
4930            | "창열기"
4931            | "باز_کردن_پنجره"
4932            | "افتح_نافذة"
4933            | "פתח_חלון"
4934            | "ونڈو_کھولو" => {
4935                let w = self.arg_num(&args, 0, 800.0)? as usize;
4936                let h = self.arg_num(&args, 1, 600.0)? as usize;
4937                #[cfg(not(target_arch = "wasm32"))]
4938                {
4939                    let title = args
4940                        .get(2)
4941                        .map(|v| v.to_string())
4942                        .unwrap_or_else(|| "Ling".into());
4943                    let mut gfx = self.gfx.borrow_mut();
4944                    let mut win = minifb::Window::new(
4945                        &title,
4946                        w,
4947                        h,
4948                        minifb::WindowOptions {
4949                            resize: false,
4950                            scale: minifb::Scale::X1,
4951                            ..Default::default()
4952                        },
4953                    )
4954                    .map_err(|e| EvalErr::from(format!("cannot open window: {e}")))?;
4955                    apply_frame_pacing(&mut win, gfx.vsync);
4956                    gfx.buffer = vec![0u32; w * h];
4957                    gfx.width = w;
4958                    gfx.height = h;
4959                    gfx.window = Some(win);
4960                    gfx.topmost_window = false;
4961                    gfx.sync_projection();
4962                }
4963                #[cfg(target_arch = "wasm32")]
4964                {
4965                    let mut gfx = self.gfx.borrow_mut();
4966                    gfx.width = w;
4967                    gfx.height = h;
4968                    gfx.buffer.resize(w * h, 0); // keep the CPU framebuffer in sync
4969                    gfx.sync_projection();
4970                    crate::gfx::webgl::resize(w as u32, h as u32);
4971                }
4972                return Ok(Value::Unit);
4973            },
4974
4975            // ── เติม(r, g, b) — fill / clear screen with colour ──
4976            "เติม" | "fill" | "gfx_fill" | "clear" | "填" | "塗り潰し" | "채우기" | "清"
4977            | "消去" | "지우기" | "پر_کن" | "املأ" | "מלא" | "بھرو" => {
4978                let r = self.arg_num(&args, 0, 0.0)? as u32;
4979                let g = self.arg_num(&args, 1, 0.0)? as u32;
4980                let b = self.arg_num(&args, 2, 0.0)? as u32;
4981                #[cfg(not(target_arch = "wasm32"))]
4982                {
4983                    let c = (r << 16) | (g << 8) | b;
4984                    let mut gfx = self.gfx.borrow_mut();
4985                    gfx.buffer.fill(c);
4986                    gfx.zbuf_needs_clear = true; // clear color ⇒ clear depth next flush
4987                    gfx.edge_set.clear(); // reset shared-edge dedup for new frame
4988                }
4989                #[cfg(target_arch = "wasm32")]
4990                {
4991                    let mut gfx = self.gfx.borrow_mut();
4992                    gfx.fill_r = r as f32 / 255.0;
4993                    gfx.fill_g = g as f32 / 255.0;
4994                    gfx.fill_b = b as f32 / 255.0;
4995                    let c = (r << 16) | (g << 8) | b;
4996                    gfx.buffer.fill(c);
4997                    gfx.zbuf_needs_clear = true;
4998                    gfx.edge_set.clear();
4999                }
5000                return Ok(Value::Unit);
5001            },
5002
5003            // ── set_color_hsl(h, s, l) — set drawing colour from HSL ──
5004            // h: 0–360 degrees, s: 0–100 saturation, l: 0–100 lightness
5005            "set_color_hsl"
5006            | "颜色HSL"
5007            | "色相"
5008            | "HSL色"
5009            | "HSL색설정"
5010            | "สีHSLวาด"
5011            | "تنظیم_رنگ_HSL"
5012            | "عيّن_اللون_HSL"
5013            | "קבע_צבע_HSL"
5014            | "HSL_رنگ_مقرر_کرو"
5015            | "définir_couleur_hsl"
5016            | "farbe_hsl_setzen"
5017            | "задать_цвет_hsl" => {
5018                let h = self.arg_num(&args, 0, 0.0)?;
5019                let s = self.arg_num(&args, 1, 70.0)?;
5020                let l = self.arg_num(&args, 2, 50.0)?;
5021                let hex = hsl_to_hex(h, s, l);
5022                let r = u32::from_str_radix(&hex[1..3], 16).unwrap_or(255);
5023                let g = u32::from_str_radix(&hex[3..5], 16).unwrap_or(255);
5024                let b = u32::from_str_radix(&hex[5..7], 16).unwrap_or(255);
5025                self.gfx.borrow_mut().color = (r << 16) | (g << 8) | b;
5026                return Ok(Value::Unit);
5027            },
5028
5029            // ── สีดินสอ(r, g, b) — set drawing colour ──
5030            "สีดินสอ"
5031            | "set_color"
5032            | "gfx_color"
5033            | "color"
5034            | "设色"
5035            | "色設定"
5036            | "색설정"
5037            | "تنظیم_رنگ"
5038            | "عيّن_اللون"
5039            | "קבע_צבע"
5040            | "رنگ_مقرر_کرو" => {
5041                let r = self.arg_num(&args, 0, 255.0)? as u32;
5042                let g = self.arg_num(&args, 1, 255.0)? as u32;
5043                let b = self.arg_num(&args, 2, 255.0)? as u32;
5044                self.gfx.borrow_mut().color = (r << 16) | (g << 8) | b;
5045                return Ok(Value::Unit);
5046            },
5047
5048            // ── วาดสามเหลี่ยม(x1,y1, x2,y2, x3,y3) — draw filled triangle ──
5049            "วาดสามเหลี่ยม"
5050            | "draw_triangle"
5051            | "gfx_triangle"
5052            | "triangle"
5053            | "画三角"
5054            | "三角形描画"
5055            | "삼각형그리기"
5056            | "رسم_مثلث"
5057            | "ارسم_مثلثا"
5058            | "צייר_משולש"
5059            | "مثلث_کھینچو" => {
5060                let x0 = self.arg_num(&args, 0, 0.0)? as f32;
5061                let y0 = self.arg_num(&args, 1, 0.0)? as f32;
5062                let x1 = self.arg_num(&args, 2, 0.0)? as f32;
5063                let y1 = self.arg_num(&args, 3, 0.0)? as f32;
5064                let x2 = self.arg_num(&args, 4, 0.0)? as f32;
5065                let y2 = self.arg_num(&args, 5, 0.0)? as f32;
5066                let mut gfx = self.gfx.borrow_mut();
5067                let color = gfx.color;
5068                #[cfg(not(target_arch = "wasm32"))]
5069                {
5070                    let w = gfx.width;
5071                    let h = gfx.height;
5072                    fill_triangle(&mut gfx.buffer, w, h, color, x0, y0, x1, y1, x2, y2);
5073                }
5074                #[cfg(target_arch = "wasm32")]
5075                gfx.depth_queue
5076                    .push_triangle(0.0, color, x0, y0, x1, y1, x2, y2);
5077                return Ok(Value::Unit);
5078            },
5079
5080            // ── วาดเส้น(x1,y1, x2,y2) — draw line ──
5081            "วาดเส้น"
5082            | "draw_line"
5083            | "gfx_line"
5084            | "line"
5085            | "画线"
5086            | "線描く"
5087            | "선그리기"
5088            | "رسم_خط"
5089            | "ارسم_خط"
5090            | "צייר_קו"
5091            | "لکیر_کھینچو" => {
5092                let x0 = self.arg_num(&args, 0, 0.0)? as f32;
5093                let y0 = self.arg_num(&args, 1, 0.0)? as f32;
5094                let x1 = self.arg_num(&args, 2, 0.0)? as f32;
5095                let y1 = self.arg_num(&args, 3, 0.0)? as f32;
5096                let mut gfx = self.gfx.borrow_mut();
5097                let color = gfx.color;
5098                #[cfg(not(target_arch = "wasm32"))]
5099                {
5100                    let w = gfx.width;
5101                    let h = gfx.height;
5102                    let aa = gfx.antialias;
5103                    let add = gfx.blend == 1;
5104                    if aa {
5105                        crate::gfx::raster::draw_line_aa(
5106                            &mut gfx.buffer,
5107                            w,
5108                            h,
5109                            color,
5110                            add,
5111                            x0,
5112                            y0,
5113                            x1,
5114                            y1,
5115                        );
5116                    } else {
5117                        draw_line(&mut gfx.buffer, w, h, color, x0, y0, x1, y1);
5118                    }
5119                }
5120                #[cfg(target_arch = "wasm32")]
5121                gfx.depth_queue.push_line(0.0, color, x0, y0, x1, y1);
5122                return Ok(Value::Unit);
5123            },
5124
5125            // ── วาดจุด(x, y) — plot a single pixel ──
5126            "วาดจุด"
5127            | "draw_pixel"
5128            | "gfx_pixel"
5129            | "pixel"
5130            | "画点"
5131            | "点描く"
5132            | "점그리기"
5133            | "رسم_نقطه"
5134            | "ارسم_نقطة"
5135            | "צייר_פיקסל"
5136            | "پکسل_کھینچو" => {
5137                let px = self.arg_num(&args, 0, 0.0)? as i32;
5138                let py = self.arg_num(&args, 1, 0.0)? as i32;
5139                #[cfg(not(target_arch = "wasm32"))]
5140                {
5141                    let mut gfx = self.gfx.borrow_mut();
5142                    let color = gfx.color;
5143                    let w = gfx.width;
5144                    let h = gfx.height;
5145                    if px >= 0 && py >= 0 && (px as usize) < w && (py as usize) < h {
5146                        gfx.buffer[py as usize * w + px as usize] = color;
5147                    }
5148                }
5149                #[cfg(target_arch = "wasm32")]
5150                {
5151                    // Render pixel as a 1×1 square via two triangles.
5152                    let mut gfx = self.gfx.borrow_mut();
5153                    let color = gfx.color;
5154                    let x = px as f32;
5155                    let y = py as f32;
5156                    gfx.depth_queue
5157                        .push_triangle(0.0, color, x, y, x + 1.0, y, x + 1.0, y + 1.0);
5158                    gfx.depth_queue
5159                        .push_triangle(0.0, color, x, y, x + 1.0, y + 1.0, x, y + 1.0);
5160                }
5161                return Ok(Value::Unit);
5162            },
5163
5164            // ── แสดงผล() — flush depth queue, then present frame to screen ──
5165            "แสดงผล" | "present" | "gfx_present" | "show" | "显" | "呈现" | "表示" | "표시"
5166            | "نمایش" | "اعرض" | "הצג" | "دکھاؤ" => {
5167                // Click-edge widgets (ui_button etc.) compare THIS frame's
5168                // mouse_now() against `mouse_was_down` to detect a fresh
5169                // press. That comparison only works if mouse_was_down
5170                // reflects what the script itself observed this frame — i.e.
5171                // the state from BEFORE update_with_buffer below pulls in new
5172                // OS events. Capturing it after (as the "freshest" read)
5173                // would mean a just-arrived click is already baked into
5174                // mouse_was_down by the time next frame's ui_button compares
5175                // against it, so `down && !mouse_was_down` is never true and
5176                // clicks never register at all. Declared at the top of the
5177                // match arm (not inside the block below) so it survives past
5178                // the wasm32/non-wasm32 split further down.
5179                #[cfg(not(target_arch = "wasm32"))]
5180                let pre_update_mouse_down = self
5181                    .gfx
5182                    .borrow()
5183                    .window
5184                    .as_ref()
5185                    .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
5186                    .unwrap_or(false);
5187                #[cfg(not(target_arch = "wasm32"))]
5188                {
5189                    ling_fps_tick();
5190                    ling_phase_frame();
5191                    // Flush depth queue and present — release borrow before reading mouse.
5192                    {
5193                        let mut gfx = self.gfx.borrow_mut();
5194                        if !gfx.depth_queue.is_empty() {
5195                            let w = gfx.width;
5196                            let h = gfx.height;
5197                            let dt = gfx.depth_test;
5198                            let reset_z = gfx.zbuf_needs_clear;
5199                            let (bm, ba) = (gfx.blend, gfx.alpha);
5200                            let aa = gfx.antialias;
5201                            let queue = std::mem::take(&mut gfx.depth_queue);
5202                            {
5203                                let g = &mut *gfx;
5204                                let z = if dt { Some(&mut g.depth_buf) } else { None };
5205                                queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
5206                            }
5207                            gfx.depth_queue.set_state(bm, ba);
5208                            gfx.zbuf_needs_clear = false;
5209                        }
5210                        let _t = std::time::Instant::now();
5211                        if !gfx.post_done {
5212                            gfx.toon_post_process();
5213                        }
5214                        gfx.post_done = false;
5215                        ling_phase_add(phase::TOON, _t.elapsed().as_nanos());
5216                        let w = gfx.width;
5217                        let h = gfx.height;
5218                        let g = &mut *gfx;
5219                        if g.frame_blur > 0.0 {
5220                            // Afterimage trails: previous frame decays by `frame_blur`
5221                            // per frame and composites with MAX — fresh content stays
5222                            // full-brightness, ghosts fade out over time.
5223                            // retention 0.98 @60fps ≈ trails last ~2.6 s.
5224                            let a = (g.frame_blur.clamp(0.0, 0.995) * 256.0) as u32;
5225                            if g.prev_frame.len() != g.buffer.len() {
5226                                g.prev_frame = g.buffer.clone();
5227                            }
5228                            for (dst, prev) in g.buffer.iter_mut().zip(g.prev_frame.iter_mut()) {
5229                                let c = *dst;
5230                                let pv = *prev;
5231                                let pr = (((pv >> 16) & 0xFF) * a) >> 8;
5232                                let pg = (((pv >> 8) & 0xFF) * a) >> 8;
5233                                let pb = ((pv & 0xFF) * a) >> 8;
5234                                let cr = (c >> 16) & 0xFF;
5235                                let cg = (c >> 8) & 0xFF;
5236                                let cb = c & 0xFF;
5237                                let outp = (cr.max(pr) << 16) | (cg.max(pg) << 8) | cb.max(pb);
5238                                *dst = outp;
5239                                *prev = outp;
5240                            }
5241                        }
5242                        if let Some(win) = g.window.as_mut() {
5243                            let _b = std::time::Instant::now();
5244                            win.update_with_buffer(&g.buffer, w, h)
5245                                .map_err(|e| EvalErr::from(format!("present error: {e}")))?;
5246                            ling_phase_add(phase::BLIT, _b.elapsed().as_nanos());
5247                        }
5248                    }
5249                    // Read mouse AFTER update_with_buffer so events are processed.
5250                    let mouse_pos = {
5251                        let gfx = self.gfx.borrow();
5252                        gfx.window
5253                            .as_ref()
5254                            .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
5255                    };
5256                    let mut gfx = self.gfx.borrow_mut();
5257                    if gfx.mouse_captured {
5258                        let w = gfx.width as f32;
5259                        let h = gfx.height as f32;
5260                        if let Some((mx, my)) = mouse_pos {
5261                            if gfx.last_mx.is_nan() {
5262                                gfx.mouse_dx = 0.0;
5263                                gfx.mouse_dy = 0.0;
5264                                gfx.last_mx = mx;
5265                                gfx.last_my = my;
5266                            } else {
5267                                gfx.mouse_dx = mx - gfx.last_mx;
5268                                gfx.mouse_dy = my - gfx.last_my;
5269                                // Wrap the cursor at every edge (L/R/U/D) → infinite look
5270                                // on both axes, and the cursor is NOT trapped (alt-tab works).
5271                                let margin = 6.0;
5272                                let (mut nx, mut ny, mut warp) = (mx, my, false);
5273                                if mx < margin {
5274                                    nx = w - margin - 2.0;
5275                                    warp = true;
5276                                } else if mx > w - margin {
5277                                    nx = margin + 2.0;
5278                                    warp = true;
5279                                }
5280                                if my < margin {
5281                                    ny = h - margin - 2.0;
5282                                    warp = true;
5283                                } else if my > h - margin {
5284                                    ny = margin + 2.0;
5285                                    warp = true;
5286                                }
5287                                if warp {
5288                                    #[cfg(windows)]
5289                                    unsafe {
5290                                        #[repr(C)]
5291                                        struct RECT {
5292                                            left: i32,
5293                                            top: i32,
5294                                            right: i32,
5295                                            bottom: i32,
5296                                        }
5297                                        extern "system" {
5298                                            fn GetForegroundWindow() -> isize;
5299                                            fn GetWindowRect(hwnd: isize, lpRect: *mut RECT)
5300                                                -> i32;
5301                                            fn SetCursorPos(x: i32, y: i32) -> i32;
5302                                        }
5303                                        let hwnd = GetForegroundWindow();
5304                                        let mut rect =
5305                                            RECT { left: 0, top: 0, right: 0, bottom: 0 };
5306                                        if GetWindowRect(hwnd, &mut rect) != 0 {
5307                                            SetCursorPos(
5308                                                rect.left + nx as i32,
5309                                                rect.top + ny as i32,
5310                                            );
5311                                        }
5312                                    }
5313                                    gfx.last_mx = nx;
5314                                    gfx.last_my = ny;
5315                                } else {
5316                                    gfx.last_mx = mx;
5317                                    gfx.last_my = my;
5318                                }
5319                            }
5320                        } else {
5321                            gfx.mouse_dx = 0.0;
5322                            gfx.mouse_dy = 0.0;
5323                        }
5324                    } else if let Some((mx, my)) = mouse_pos {
5325                        if gfx.last_mx.is_nan() {
5326                            gfx.mouse_dx = 0.0;
5327                            gfx.mouse_dy = 0.0;
5328                        } else {
5329                            gfx.mouse_dx = mx - gfx.last_mx;
5330                            gfx.mouse_dy = my - gfx.last_my;
5331                        }
5332                        gfx.last_mx = mx;
5333                        gfx.last_my = my;
5334                    } else {
5335                        gfx.mouse_dx = 0.0;
5336                        gfx.mouse_dy = 0.0;
5337                    }
5338
5339                    // Alt-tab support: minifb has no WM_KILLFOCUS handler on Windows,
5340                    // so a key/button released while another window was focused can
5341                    // still read as "down" for one stale frame right after the user
5342                    // alt-tabs back. Detect the unfocused→focused transition and
5343                    // swallow raw input for a short grace window afterward instead of
5344                    // letting a phantom held key jerk the camera. See key_down /
5345                    // mouse_down* below (they early-out on gfx.input_suppressed()).
5346                    let is_active = gfx.window.as_mut().map(|w| w.is_active()).unwrap_or(true);
5347                    if is_active && !gfx.was_active {
5348                        gfx.focus_grace_frames = 5;
5349                        // Regained focus: restore HWND_TOPMOST so the borderless-
5350                        // fullscreen window covers the taskbar again.
5351                        #[cfg(windows)]
5352                        if gfx.topmost_window {
5353                            if let Some(w) = gfx.window.as_ref() {
5354                                set_window_topmost(w.get_window_handle() as isize, true);
5355                            }
5356                        }
5357                    } else if !is_active && gfx.was_active {
5358                        // Lost focus (alt-tab): drop topmost so the game stops
5359                        // covering whatever window the user just switched to —
5360                        // otherwise a topmost borderless window visually "wins"
5361                        // even though it's no longer focused, making alt-tab
5362                        // look broken.
5363                        #[cfg(windows)]
5364                        if gfx.topmost_window {
5365                            if let Some(w) = gfx.window.as_ref() {
5366                                set_window_topmost(w.get_window_handle() as isize, false);
5367                            }
5368                        }
5369                    }
5370                    gfx.was_active = is_active;
5371                    if gfx.focus_grace_frames > 0 {
5372                        gfx.focus_grace_frames -= 1;
5373                    }
5374                }
5375                #[cfg(target_arch = "wasm32")]
5376                {
5377                    {
5378                        // Software-render everything (3-D depth queue + 2-D vtex/ui that
5379                        // already wrote into the buffer) into the framebuffer, exactly
5380                        // like native, then upload that buffer to the canvas in one blit.
5381                        let mut gfx = self.gfx.borrow_mut();
5382                        let w = gfx.width;
5383                        let h = gfx.height;
5384                        if gfx.buffer.len() != w * h {
5385                            gfx.buffer.resize(w * h, 0);
5386                        }
5387                        if !gfx.depth_queue.is_empty() {
5388                            let dt = gfx.depth_test;
5389                            let reset_z = gfx.zbuf_needs_clear;
5390                            let aa = gfx.antialias;
5391                            let queue = std::mem::take(&mut gfx.depth_queue);
5392                            {
5393                                let g = &mut *gfx;
5394                                let z = if dt { Some(&mut g.depth_buf) } else { None };
5395                                queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
5396                            }
5397                            gfx.zbuf_needs_clear = false;
5398                        }
5399                        if !gfx.post_done {
5400                            gfx.toon_post_process();
5401                        }
5402                        gfx.post_done = false;
5403                        crate::gfx::webgl::blit_rgb(&gfx.buffer, w, h);
5404                    }
5405                    self.wasm_pace_frame();
5406                }
5407                // Update the click-edge latch for interactive UI widgets —
5408                // using the PRE-update_with_buffer snapshot captured at the
5409                // top of this function (see the comment there for why).
5410                #[cfg(not(target_arch = "wasm32"))]
5411                {
5412                    self.mouse_was_down = pre_update_mouse_down;
5413                }
5414                // Increment frame counter
5415                self.frame_num += 1;
5416                return Ok(Value::Unit);
5417            },
5418
5419            // ── เปิดหน้าต่างเต็มจอ(title) — true native-res fullscreen window ──
5420            "เปิดหน้าต่างเต็มจอ"
5421            | "open_fullscreen"
5422            | "fullscreen"
5423            | "全屏"
5424            | "全画面"
5425            | "전체화면"
5426            | "باز_کردن_تمام‌صفحه"
5427            | "افتح_ملء_الشاشة"
5428            | "פתח_מסך_מלא"
5429            | "فل_سکرین_کھولو" => {
5430                // In WASM the canvas defines the viewport; use its current size
5431                // as the default so the projection matches what's actually visible.
5432                #[cfg(target_arch = "wasm32")]
5433                let (default_w, default_h) = {
5434                    let (cw, ch) = crate::gfx::webgl::canvas_size();
5435                    (cw as f64, ch as f64)
5436                };
5437                // On native: query the actual primary monitor resolution.
5438                #[cfg(all(not(target_arch = "wasm32"), windows))]
5439                let (default_w, default_h) = unsafe {
5440                    extern "system" {
5441                        fn GetSystemMetrics(nIndex: i32) -> i32;
5442                    }
5443                    (GetSystemMetrics(0) as f64, GetSystemMetrics(1) as f64)
5444                };
5445                #[cfg(all(not(target_arch = "wasm32"), not(windows)))]
5446                let (default_w, default_h) = native_screen_size();
5447
5448                let w = args
5449                    .get(1)
5450                    .map(|v| self.to_number(v).unwrap_or(default_w) as usize)
5451                    .unwrap_or(default_w as usize);
5452                let h = args
5453                    .get(2)
5454                    .map(|v| self.to_number(v).unwrap_or(default_h) as usize)
5455                    .unwrap_or(default_h as usize);
5456                #[cfg(not(target_arch = "wasm32"))]
5457                {
5458                    let title = args
5459                        .first()
5460                        .map(|v| v.to_string())
5461                        .unwrap_or_else(|| "Ling".into());
5462                    let mut gfx = self.gfx.borrow_mut();
5463                    let mut win = minifb::Window::new(
5464                        &title,
5465                        w,
5466                        h,
5467                        minifb::WindowOptions {
5468                            borderless: true,
5469                            title: false,
5470                            resize: false,
5471                            topmost: true,
5472                            scale: minifb::Scale::X1,
5473                            ..Default::default()
5474                        },
5475                    )
5476                    .map_err(|e| EvalErr::from(format!("cannot open fullscreen: {e}")))?;
5477                    apply_frame_pacing(&mut win, gfx.vsync);
5478                    // Grab the native handle *before* moving the window into gfx.
5479                    #[cfg(windows)]
5480                    let hwnd = win.get_window_handle() as isize;
5481                    gfx.buffer = vec![0u32; w * h];
5482                    gfx.width = w;
5483                    gfx.height = h;
5484                    gfx.window = Some(win);
5485                    gfx.topmost_window = true;
5486                    #[cfg(windows)]
5487                    {
5488                        gfx.hwnd = hwnd;
5489                    }
5490                    gfx.sync_projection();
5491                    // Strip all chrome and cover the full screen, above the taskbar.
5492                    #[cfg(windows)]
5493                    make_borderless_fullscreen(hwnd, w as i32, h as i32);
5494                    #[cfg(windows)]
5495                    force_window_focus(hwnd);
5496                }
5497                #[cfg(target_arch = "wasm32")]
5498                {
5499                    let mut gfx = self.gfx.borrow_mut();
5500                    gfx.width = w;
5501                    gfx.height = h;
5502                    gfx.buffer.resize(w * h, 0); // keep the CPU framebuffer in sync
5503                    gfx.sync_projection();
5504                    crate::gfx::webgl::resize(w as u32, h as u32);
5505                }
5506                return Ok(Value::Unit);
5507            },
5508
5509            // ── ความกว้าง() / ความสูง() — current framebuffer size ──
5510            "get_width"
5511            | "ความกว้าง"
5512            | "宽"
5513            | "幅取得"
5514            | "너비"
5515            | "عرض"
5516            | "العرض"
5517            | "רוחב"
5518            | "چوڑائی"
5519            | "obtenir_largeur"
5520            | "breite_abrufen"
5521            | "получить_ширину" => {
5522                return Ok(Value::Number(self.gfx.borrow().width as f64));
5523            },
5524            "get_height"
5525            | "ความสูง"
5526            | "高"
5527            | "高取得"
5528            | "높이"
5529            | "ارتفاع"
5530            | "الارتفاع"
5531            | "גובה"
5532            | "اونچائی"
5533            | "obtenir_hauteur"
5534            | "höhe_abrufen"
5535            | "получить_высоту" => {
5536                return Ok(Value::Number(self.gfx.borrow().height as f64));
5537            },
5538
5539            // ── monitor detection: physical display, not the framebuffer ──────
5540            // monitor_width() → primary-monitor pixel width
5541            "monitor_width"
5542            | "screen_width"
5543            | "屏宽"
5544            | "画面幅"
5545            | "화면너비"
5546            | "ความกว้างจอ"
5547            | "عرض_مانیتور"
5548            | "عرض_الشاشة"
5549            | "רוחב_צג"
5550            | "مانیٹر_چوڑائی"
5551            | "largeur_moniteur"
5552            | "monitor_breite"
5553            | "ширина_монитора" => {
5554                return Ok(Value::Number(monitor_info().0 as f64));
5555            },
5556            // monitor_height() → primary-monitor pixel height
5557            "monitor_height"
5558            | "screen_height"
5559            | "屏高"
5560            | "画面高"
5561            | "화면높이"
5562            | "ความสูงจอ"
5563            | "ارتفاع_مانیتور"
5564            | "ارتفاع_الشاشة"
5565            | "גובה_צג"
5566            | "مانیٹر_اونچائی"
5567            | "hauteur_moniteur"
5568            | "monitor_höhe"
5569            | "высота_монитора" => {
5570                return Ok(Value::Number(monitor_info().1 as f64));
5571            },
5572            // monitor_refresh() → refresh rate in Hz (a.k.a. the monitor framerate)
5573            "monitor_refresh"
5574            | "monitor_hz"
5575            | "monitor_fps"
5576            | "refresh_rate"
5577            | "刷新率"
5578            | "リフレッシュレート"
5579            | "주사율"
5580            | "อัตรารีเฟรช"
5581            | "نرخ_بروزرسانی_مانیتور"
5582            | "معدل_تحديث_الشاشة"
5583            | "קצב_רענון_צג"
5584            | "مانیٹر_ریفریش_ریٹ"
5585            | "fréquence_moniteur"
5586            | "monitor_bildwiederholrate"
5587            | "частота_монитора" => {
5588                return Ok(Value::Number(monitor_info().2 as f64));
5589            },
5590            // monitor_info() → [width, height, refresh_hz]
5591            "monitor_info"
5592            | "screen_info"
5593            | "屏幕信息"
5594            | "画面情報"
5595            | "화면정보"
5596            | "ข้อมูลจอ"
5597            | "اطلاعات_مانیتور"
5598            | "معلومات_الشاشة"
5599            | "מידע_צג"
5600            | "مانیٹر_معلومات"
5601            | "info_moniteur"
5602            | "bildschirminfo"
5603            | "инфо_монитора" => {
5604                let (w, h, hz) = monitor_info();
5605                return Ok(Value::List(Rc::new(vec![
5606                    Value::Number(w as f64),
5607                    Value::Number(h as f64),
5608                    Value::Number(hz as f64),
5609                ])));
5610            },
5611            // set_fps(n) → cap the render loop at n frames per second
5612            "set_fps"
5613            | "set_target_fps"
5614            | "target_fps"
5615            | "设帧率"
5616            | "フレームレート設定"
5617            | "프레임설정"
5618            | "ตั้งเฟรมเรต"
5619            | "تنظیم_نرخ_فریم"
5620            | "عيّن_معدل_الإطارات"
5621            | "קבע_קצב_פריימים"
5622            | "ایف_پی_ایس_مقرر_کرو"
5623            | "définir_fps"
5624            | "fps_setzen"
5625            | "задать_fps" => {
5626                #[cfg(not(target_arch = "wasm32"))]
5627                {
5628                    let fps = self.arg_num(&args, 0, 60.0)?.max(1.0) as usize;
5629                    let mut gfx = self.gfx.borrow_mut();
5630                    if let Some(win) = gfx.window.as_mut() {
5631                        win.set_target_fps(fps);
5632                    }
5633                }
5634                #[cfg(target_arch = "wasm32")]
5635                {
5636                    self.wasm_target_fps = self.arg_num(&args, 0, 60.0)?.max(1.0);
5637                    self.wasm_next_present_ms = 0.0;
5638                }
5639                return Ok(Value::Unit);
5640            },
5641
5642            // set_vsync(on) → pace the window to the monitor's refresh rate.
5643            // Frame-rate pacing (minifb has no swap-interval), not tear-free
5644            // vsync; `LING_FPS_CAP` and an explicit `set_fps` call still win.
5645            "set_vsync"
5646            | "vsync"
5647            | "垂直同步"
5648            | "垂直同期"
5649            | "수직동기"
5650            | "ตั้งวีซิงก์"
5651            | "تنظیم_وی‌سینک"
5652            | "عيّن_تزامن_رأسي"
5653            | "קבע_וי_סינק"
5654            | "وی_سینک_مقرر_کرو"
5655            | "définir_vsync"
5656            | "vsync_setzen"
5657            | "задать_vsync" => {
5658                let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
5659                #[cfg(not(target_arch = "wasm32"))]
5660                {
5661                    let mut gfx = self.gfx.borrow_mut();
5662                    gfx.vsync = on;
5663                    if let Some(win) = gfx.window.as_mut() {
5664                        apply_frame_pacing(win, on);
5665                    }
5666                }
5667                #[cfg(target_arch = "wasm32")]
5668                {
5669                    self.wasm_target_fps = if on { monitor_info().2 as f64 } else { 240.0 };
5670                    self.wasm_next_present_ms = 0.0;
5671                }
5672                return Ok(Value::Unit);
5673            },
5674
5675            // ── หน้าต่างเปิดอยู่() → bool — is the window still open? ──
5676            "หน้าต่างเปิดอยู่"
5677            | "window_is_open"
5678            | "gfx_is_open"
5679            | "is_open"
5680            | "窗开"
5681            | "開いている"
5682            | "창열림"
5683            | "پنجره_باز_است"
5684            | "النافذة_مفتوحة"
5685            | "החלון_פתוח"
5686            | "ونڈو_کھلی_ہے" => {
5687                #[cfg(not(target_arch = "wasm32"))]
5688                {
5689                    let gfx = self.gfx.borrow();
5690                    if gfx.want_quit {
5691                        return Ok(Value::Bool(false));
5692                    }
5693                    // Escape-to-quit needs the same GetAsyncKeyState fallback
5694                    // as key_down/key_pressed/text_poll (see those) — raw
5695                    // w.is_key_down(Escape) is WM_KEYDOWN-based and silently
5696                    // never fires if this topmost window didn't actually win
5697                    // real Win32 keyboard focus.
5698                    #[cfg(windows)]
5699                    let escape_down = if gfx.topmost_window {
5700                        window_is_foreground(gfx.hwnd) && os_key_down(0x1B) // VK_ESCAPE
5701                    } else {
5702                        gfx.window
5703                            .as_ref()
5704                            .map(|w| w.is_key_down(minifb::Key::Escape))
5705                            .unwrap_or(false)
5706                    };
5707                    #[cfg(not(windows))]
5708                    let escape_down = gfx
5709                        .window
5710                        .as_ref()
5711                        .map(|w| w.is_key_down(minifb::Key::Escape))
5712                        .unwrap_or(false);
5713                    let open =
5714                        gfx.window.as_ref().map(|w| w.is_open()).unwrap_or(false) && !escape_down;
5715                    return Ok(Value::Bool(open));
5716                }
5717                #[cfg(target_arch = "wasm32")]
5718                return Ok(Value::Bool(true));
5719            },
5720
5721            // quit() — close the window the same way Escape does, for a
5722            // script-drawn UI element (an exit button) to call.
5723            "quit" | "exit_game" | "close_window" => {
5724                #[cfg(not(target_arch = "wasm32"))]
5725                {
5726                    self.gfx.borrow_mut().want_quit = true;
5727                }
5728                return Ok(Value::Unit);
5729            },
5730
5731            // ── key_down(name) → bool — is a key held? ──
5732            "key_down"
5733            | "กดค้าง"
5734            | "按键"
5735            | "キー押す"
5736            | "키누름"
5737            | "کلید_فشرده"
5738            | "المفتاح_مضغوط"
5739            | "מקש_לחוץ"
5740            | "بٹن_دبا_ہوا"
5741            | "touche_enfoncée"
5742            | "taste_gedrückt"
5743            | "клавиша_нажата" => {
5744                #[cfg(not(target_arch = "wasm32"))]
5745                {
5746                    let name = self.arg_str(&args, 0, "");
5747                    let mut gfx = self.gfx.borrow_mut();
5748                    // The borderless-fullscreen/topmost window can be
5749                    // visually in front without ever winning real Win32
5750                    // keyboard focus (Windows' foreground-lock) — minifb's
5751                    // is_key_down is populated from WM_KEYDOWN, which then
5752                    // never arrives. GetAsyncKeyState reads the OS key-state
5753                    // table directly and doesn't need focus, so use it
5754                    // whenever this is that window (see force_window_focus).
5755                    #[cfg(windows)]
5756                    if gfx.topmost_window {
5757                        if !window_is_foreground(gfx.hwnd) {
5758                            return Ok(Value::Bool(false));
5759                        }
5760                        return Ok(Value::Bool(
5761                            str_to_vk(&name).map(os_key_down).unwrap_or(false),
5762                        ));
5763                    }
5764                    if gfx.input_suppressed() {
5765                        return Ok(Value::Bool(false));
5766                    }
5767                    let down = gfx
5768                        .window
5769                        .as_ref()
5770                        .and_then(|w| str_to_minifb_key(&name).map(|k| w.is_key_down(k)))
5771                        .unwrap_or(false);
5772                    return Ok(Value::Bool(down));
5773                }
5774                #[cfg(target_arch = "wasm32")]
5775                {
5776                    let name = self.arg_str(&args, 0, "");
5777                    return Ok(Value::Bool(crate::gfx::wasm_is_key_down(&name)));
5778                }
5779            },
5780
5781            // ── key_pressed(name) → bool — was a key pressed this frame? ──
5782            "key_pressed"
5783            | "กดปุ่ม"
5784            | "键按"
5785            | "キー押した"
5786            | "키눌림"
5787            | "فشردن_کلید"
5788            | "ضغط_المفتاح"
5789            | "לחיצת_מקש"
5790            | "بٹن_دبانا"
5791            | "touche_appuyée"
5792            | "taste_getippt"
5793            | "клавиша_нажатие" => {
5794                #[cfg(not(target_arch = "wasm32"))]
5795                {
5796                    let name = self.arg_str(&args, 0, "");
5797                    let pressed = {
5798                        let mut gfx = self.gfx.borrow_mut();
5799                        #[cfg(windows)]
5800                        let topmost = gfx.topmost_window;
5801                        #[cfg(not(windows))]
5802                        let topmost = false;
5803                        if topmost {
5804                            #[cfg(windows)]
5805                            {
5806                                if !window_is_foreground(gfx.hwnd) {
5807                                    false
5808                                } else {
5809                                    match str_to_vk(&name) {
5810                                        Some(vk) => {
5811                                            let idx = (vk as usize) & 0xFF;
5812                                            let down = os_key_down(vk);
5813                                            let was = gfx.raw_keys_prev[idx];
5814                                            gfx.raw_keys_prev[idx] = down;
5815                                            down && !was
5816                                        },
5817                                        None => false,
5818                                    }
5819                                }
5820                            }
5821                            #[cfg(not(windows))]
5822                            {
5823                                false
5824                            }
5825                        } else if gfx.input_suppressed() {
5826                            false
5827                        } else {
5828                            gfx.window
5829                                .as_ref()
5830                                .and_then(|w| {
5831                                    str_to_minifb_key(&name)
5832                                        .map(|k| w.is_key_pressed(k, minifb::KeyRepeat::No))
5833                                })
5834                                .unwrap_or(false)
5835                        }
5836                    };
5837                    // gamepad Start behaves like Enter everywhere
5838                    let pressed =
5839                        pressed || ((name == "enter" || name == "return") && gamepad::start_edge());
5840                    return Ok(Value::Bool(pressed));
5841                }
5842                #[cfg(target_arch = "wasm32")]
5843                {
5844                    let name = self.arg_str(&args, 0, "");
5845                    let pressed = crate::gfx::wasm_is_key_pressed(&name);
5846                    return Ok(Value::Bool(pressed));
5847                }
5848            },
5849
5850            // ── mouse_dx() / mouse_dy() → f64 — delta since last frame ──
5851            "mouse_dx"
5852            | "เมาส์X"
5853            | "鼠ΔX"
5854            | "マウスΔX"
5855            | "마우스ΔX"
5856            | "دلتا_ماوس_ایکس"
5857            | "فارق_الفأرة_س"
5858            | "דלתא_עכבר_X"
5859            | "ماؤس_ڈیلٹا_ایکس"
5860            | "souris_dx"
5861            | "maus_dx"
5862            | "мышь_dx" => {
5863                #[cfg(not(target_arch = "wasm32"))]
5864                return Ok(Value::Number(self.gfx.borrow().mouse_dx as f64));
5865                #[cfg(target_arch = "wasm32")]
5866                return Ok(Value::Number(crate::gfx::wasm_mouse_dx() as f64));
5867            },
5868            // ── mouse_scroll() → f64 — vertical scroll-wheel delta this frame ──
5869            #[cfg(not(target_arch = "wasm32"))]
5870            "mouse_scroll"
5871            | "ล้อเมาส์"
5872            | "滚轮"
5873            | "ホイール"
5874            | "스크롤"
5875            | "غلتک_ماوس"
5876            | "عجلة_الفأرة"
5877            | "גלגלת_עכבר"
5878            | "ماؤس_اسکرول" => {
5879                let gfx = self.gfx.borrow();
5880                let s = gfx
5881                    .window
5882                    .as_ref()
5883                    .and_then(|w| w.get_scroll_wheel())
5884                    .map(|(_, y)| y as f64)
5885                    .unwrap_or(0.0);
5886                return Ok(Value::Number(s));
5887            },
5888            #[cfg(target_arch = "wasm32")]
5889            "mouse_scroll"
5890            | "ล้อเมาส์"
5891            | "滚轮"
5892            | "ホイール"
5893            | "스크롤"
5894            | "غلتک_ماوس"
5895            | "عجلة_الفأرة"
5896            | "גלגלת_עכבר"
5897            | "ماؤس_اسکرول" => {
5898                return Ok(Value::Number(0.0));
5899            },
5900            "mouse_dy"
5901            | "เมาส์Y"
5902            | "鼠ΔY"
5903            | "マウスΔY"
5904            | "마우스ΔY"
5905            | "دلتا_ماوس_ایگرگ"
5906            | "فارق_الفأرة_ص"
5907            | "דלתא_עכבר_Y"
5908            | "ماؤس_ڈیلٹا_وائی"
5909            | "souris_dy"
5910            | "maus_dy"
5911            | "мышь_dy" => {
5912                #[cfg(not(target_arch = "wasm32"))]
5913                return Ok(Value::Number(self.gfx.borrow().mouse_dy as f64));
5914                #[cfg(target_arch = "wasm32")]
5915                return Ok(Value::Number(crate::gfx::wasm_mouse_dy() as f64));
5916            },
5917
5918            // ── Gamepad / joystick input (ling-input "Sensorium" + gilrs) ──
5919            // pad_poll() → number — advance input one frame; returns # connected pads.
5920            "pad_poll"
5921            | "手柄轮询"
5922            | "パッド更新"
5923            | "패드폴링"
5924            | "อัปเดตแพด"
5925            | "بررسی_دسته"
5926            | "استطلع_اليد"
5927            | "בדוק_בקר"
5928            | "گیم_پیڈ_پول"
5929            | "interroger_manette"
5930            | "gamepad_abfragen"
5931            | "опросить_геймпад" => {
5932                #[cfg(not(target_arch = "wasm32"))]
5933                return Ok(Value::Number(self.pad_poll() as f64));
5934                #[cfg(target_arch = "wasm32")]
5935                return Ok(Value::Number(input_web::poll() as f64));
5936            },
5937            // pad_count() → number — connected gamepads.
5938            "pad_count"
5939            | "手柄数"
5940            | "パッド数"
5941            | "패드수"
5942            | "จำนวนแพด"
5943            | "تعداد_دسته"
5944            | "عدد_أيدي_التحكم"
5945            | "מספר_בקרים"
5946            | "گیم_پیڈ_تعداد"
5947            | "nombre_manettes"
5948            | "gamepad_anzahl"
5949            | "число_геймпадов" => {
5950                #[cfg(not(target_arch = "wasm32"))]
5951                {
5952                    let inp = self.input.borrow();
5953                    let n = inp.as_ref().map_or(0, |s| s.sensorium.devices.count());
5954                    return Ok(Value::Number(n as f64));
5955                }
5956                #[cfg(target_arch = "wasm32")]
5957                return Ok(Value::Number(input_web::count() as f64));
5958            },
5959            // pad_connected(i) → bool.
5960            "pad_connected"
5961            | "手柄连接"
5962            | "パッド接続"
5963            | "패드연결"
5964            | "แพดเชื่อม"
5965            | "دسته_متصل"
5966            | "يد_التحكم_متصلة"
5967            | "בקר_מחובר"
5968            | "گیم_پیڈ_منسلک"
5969            | "manette_connectée"
5970            | "gamepad_verbunden"
5971            | "геймпад_подключён" => {
5972                #[cfg(not(target_arch = "wasm32"))]
5973                {
5974                    let i = self.arg_num(&args, 0, 0.0)? as usize;
5975                    let inp = self.input.borrow();
5976                    let c = inp
5977                        .as_ref()
5978                        .is_some_and(|s| s.sensorium.devices.for_player(i as u8).is_some());
5979                    return Ok(Value::Bool(c));
5980                }
5981                #[cfg(target_arch = "wasm32")]
5982                {
5983                    let i = self.arg_num(&args, 0, 0.0)? as usize;
5984                    return Ok(Value::Bool(input_web::is_connected(i)));
5985                }
5986            },
5987            // pad_button(i, name) → bool — is the button held?
5988            "pad_button"
5989            | "手柄按键"
5990            | "パッドボタン"
5991            | "패드버튼"
5992            | "ปุ่มแพด"
5993            | "دکمه_دسته"
5994            | "زر_اليد"
5995            | "כפתור_בקר"
5996            | "گیم_پیڈ_بٹن"
5997            | "bouton_manette"
5998            | "gamepad_taste"
5999            | "кнопка_геймпада" => {
6000                #[cfg(not(target_arch = "wasm32"))]
6001                {
6002                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6003                    let name = self.arg_str(&args, 1, "");
6004                    let down = parse_pad_button(&name)
6005                        .is_some_and(|b| self.with_pad(i, false, |p| p.is_down(b)));
6006                    return Ok(Value::Bool(down));
6007                }
6008                #[cfg(target_arch = "wasm32")]
6009                {
6010                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6011                    let name = self.arg_str(&args, 1, "");
6012                    return Ok(Value::Bool(input_web::button_down(i, &name)));
6013                }
6014            },
6015            // pad_pressed(i, name) → bool — pressed this frame?
6016            // On WASM we only have the current snapshot, so treat as button_down.
6017            "pad_pressed"
6018            | "手柄按下"
6019            | "パッド押下"
6020            | "패드눌림"
6021            | "แพดกด"
6022            | "دکمه_دسته_فشرده"
6023            | "زر_اليد_مضغوط"
6024            | "כפתור_בקר_לחוץ"
6025            | "گیم_پیڈ_دبایا"
6026            | "manette_appuyée"
6027            | "gamepad_gedrückt"
6028            | "геймпад_нажат" => {
6029                #[cfg(not(target_arch = "wasm32"))]
6030                {
6031                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6032                    let name = self.arg_str(&args, 1, "");
6033                    let p = parse_pad_button(&name)
6034                        .is_some_and(|b| self.with_pad(i, false, |g| g.just_pressed(b)));
6035                    return Ok(Value::Bool(p));
6036                }
6037                #[cfg(target_arch = "wasm32")]
6038                {
6039                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6040                    let name = self.arg_str(&args, 1, "");
6041                    return Ok(Value::Bool(input_web::button_down(i, &name)));
6042                }
6043            },
6044            // pad_lx(i)/pad_ly(i)/pad_rx(i)/pad_ry(i) → number — stick axes (−1..=1).
6045            "pad_lx"
6046            | "手柄左X"
6047            | "パッド左X"
6048            | "패드왼X"
6049            | "แพดซ้ายX"
6050            | "آنالوگ_چپ_ایکس"
6051            | "عصا_اليسرى_س"
6052            | "ג'ויסטיק_שמאל_X"
6053            | "بائیں_اسٹک_ایکس"
6054            | "manette_axe_gauche_x"
6055            | "gamepad_lx"
6056            | "геймпад_ось_лево_x" => {
6057                #[cfg(not(target_arch = "wasm32"))]
6058                {
6059                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6060                    return Ok(Value::Number(
6061                        self.with_pad(i, 0.0, |p| p.left_stick.x as f64),
6062                    ));
6063                }
6064                #[cfg(target_arch = "wasm32")]
6065                {
6066                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6067                    return Ok(Value::Number(input_web::axis_lx(i) as f64));
6068                }
6069            },
6070            "pad_ly"
6071            | "手柄左Y"
6072            | "パッド左Y"
6073            | "패드왼Y"
6074            | "แพดซ้ายY"
6075            | "آنالوگ_چپ_ایگرگ"
6076            | "عصا_اليسرى_ص"
6077            | "ג'ויסטיק_שמאל_Y"
6078            | "بائیں_اسٹک_وائی"
6079            | "manette_axe_gauche_y"
6080            | "gamepad_ly"
6081            | "геймпад_ось_лево_y" => {
6082                #[cfg(not(target_arch = "wasm32"))]
6083                {
6084                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6085                    return Ok(Value::Number(
6086                        self.with_pad(i, 0.0, |p| p.left_stick.y as f64),
6087                    ));
6088                }
6089                #[cfg(target_arch = "wasm32")]
6090                {
6091                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6092                    return Ok(Value::Number(input_web::axis_ly(i) as f64));
6093                }
6094            },
6095            "pad_rx"
6096            | "手柄右X"
6097            | "パッド右X"
6098            | "패드오X"
6099            | "แพดขวาX"
6100            | "آنالوگ_راست_ایکس"
6101            | "عصا_اليمنى_س"
6102            | "ג'ויסטיק_ימין_X"
6103            | "دائیں_اسٹک_ایکس"
6104            | "manette_axe_droit_x"
6105            | "gamepad_rx"
6106            | "геймпад_ось_право_x" => {
6107                #[cfg(not(target_arch = "wasm32"))]
6108                {
6109                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6110                    return Ok(Value::Number(
6111                        self.with_pad(i, 0.0, |p| p.right_stick.x as f64),
6112                    ));
6113                }
6114                #[cfg(target_arch = "wasm32")]
6115                {
6116                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6117                    return Ok(Value::Number(input_web::axis_rx(i) as f64));
6118                }
6119            },
6120            "pad_ry"
6121            | "手柄右Y"
6122            | "パッド右Y"
6123            | "패드오Y"
6124            | "แพดขวาY"
6125            | "آنالوگ_راست_ایگرگ"
6126            | "عصا_اليمنى_ص"
6127            | "ג'ויסטיק_ימין_Y"
6128            | "دائیں_اسٹک_وائی"
6129            | "manette_axe_droit_y"
6130            | "gamepad_ry"
6131            | "геймпад_ось_право_y" => {
6132                #[cfg(not(target_arch = "wasm32"))]
6133                {
6134                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6135                    return Ok(Value::Number(
6136                        self.with_pad(i, 0.0, |p| p.right_stick.y as f64),
6137                    ));
6138                }
6139                #[cfg(target_arch = "wasm32")]
6140                {
6141                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6142                    return Ok(Value::Number(input_web::axis_ry(i) as f64));
6143                }
6144            },
6145            // pad_lt(i)/pad_rt(i) → number — analog triggers (0..=1).
6146            "pad_lt"
6147            | "手柄左扳机"
6148            | "パッド左トリガー"
6149            | "패드왼트리거"
6150            | "ไกแพดซ้าย"
6151            | "ماشه_چپ"
6152            | "زناد_اليسار"
6153            | "הדק_שמאל"
6154            | "بائیں_ٹریگر"
6155            | "manette_gâchette_gauche"
6156            | "gamepad_lt"
6157            | "геймпад_триггер_лево" => {
6158                #[cfg(not(target_arch = "wasm32"))]
6159                {
6160                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6161                    return Ok(Value::Number(
6162                        self.with_pad(i, 0.0, |p| p.left_trigger as f64),
6163                    ));
6164                }
6165                #[cfg(target_arch = "wasm32")]
6166                {
6167                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6168                    return Ok(Value::Number(input_web::trigger_lt(i) as f64));
6169                }
6170            },
6171            "pad_rt"
6172            | "手柄右扳机"
6173            | "パッド右トリガー"
6174            | "패드오트리거"
6175            | "ไกแพดขวา"
6176            | "ماشه_راست"
6177            | "زناد_اليمين"
6178            | "הדק_ימין"
6179            | "دائیں_ٹریگر"
6180            | "manette_gâchette_droite"
6181            | "gamepad_rt"
6182            | "геймпад_триггер_право" => {
6183                #[cfg(not(target_arch = "wasm32"))]
6184                {
6185                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6186                    return Ok(Value::Number(
6187                        self.with_pad(i, 0.0, |p| p.right_trigger as f64),
6188                    ));
6189                }
6190                #[cfg(target_arch = "wasm32")]
6191                {
6192                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6193                    return Ok(Value::Number(input_web::trigger_rt(i) as f64));
6194                }
6195            },
6196            // pad_rumble(i, lo, hi) → unit — set rumble motor amplitudes (0..=1).
6197            "pad_rumble"
6198            | "手柄震动"
6199            | "パッド振動"
6200            | "패드진동"
6201            | "แพดสั่น"
6202            | "لرزش_دسته"
6203            | "اهتزاز_اليد"
6204            | "רטט_בקר"
6205            | "گیم_پیڈ_تھرتھراہٹ"
6206            | "vibration_manette"
6207            | "gamepad_vibration"
6208            | "вибрация_геймпада" => {
6209                #[cfg(not(target_arch = "wasm32"))]
6210                {
6211                    use ling_input::backend::InputBackend;
6212                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6213                    let lo = self.arg_num(&args, 1, 0.0)? as f32;
6214                    let hi = self.arg_num(&args, 2, lo as f64)? as f32;
6215                    let mut inp = self.input.borrow_mut();
6216                    if let Some(s) = inp.as_mut() {
6217                        if let Some(dev) = s.sensorium.devices.for_player(i as u8).map(|d| d.id) {
6218                            s.backend.set_rumble(
6219                                dev,
6220                                ling_input::Rumble { low: lo, high: hi, ..Default::default() },
6221                            );
6222                        }
6223                    }
6224                    return Ok(Value::Unit);
6225                }
6226                #[cfg(target_arch = "wasm32")]
6227                return Ok(Value::Unit);
6228            },
6229
6230            // ── set_camera_pos(x, y, z) — move camera to world position ──
6231            "set_camera_pos"
6232            | "ตั้งตำแหน่งกล้อง"
6233            | "镜坐标"
6234            | "カメラ座標"
6235            | "카메라좌표"
6236            | "تنظیم_موقعیت_دوربین"
6237            | "عيّن_موضع_الكاميرا"
6238            | "קבע_מיקום_מצלמה"
6239            | "کیمرہ_مقام_مقرر_کرو"
6240            | "définir_position_caméra"
6241            | "kameraposition_setzen"
6242            | "задать_позицию_камеры" => {
6243                let x = self.arg_num(&args, 0, 0.0)? as f32;
6244                let y = self.arg_num(&args, 1, 0.0)? as f32;
6245                let z = self.arg_num(&args, 2, 0.0)? as f32;
6246                {
6247                    let mut gfx = self.gfx.borrow_mut();
6248                    gfx.camera.tx = x;
6249                    gfx.camera.ty = y;
6250                    gfx.camera.tz = z;
6251                }
6252                #[cfg(not(target_arch = "wasm32"))]
6253                if let Some(audio) = &self.audio {
6254                    audio.set_listener_pos(x, y, z);
6255                }
6256                return Ok(Value::Unit);
6257            },
6258
6259            // ── move_camera(dx, dy, dz) — translate camera by delta ──
6260            "move_camera" => {
6261                let dx = self.arg_num(&args, 0, 0.0)? as f32;
6262                let dy = self.arg_num(&args, 1, 0.0)? as f32;
6263                let dz = self.arg_num(&args, 2, 0.0)? as f32;
6264                let mut gfx = self.gfx.borrow_mut();
6265                gfx.camera.tx += dx;
6266                gfx.camera.ty += dy;
6267                gfx.camera.tz += dz;
6268                return Ok(Value::Unit);
6269            },
6270
6271            // ── set_zdist(d) — set perspective z-offset (field-of-view taper) ──
6272            "set_zdist"
6273            | "ตั้งระยะห่าง"
6274            | "镜距"
6275            | "Z距離設定"
6276            | "Z거리설정"
6277            | "تنظیم_فاصله_عمق"
6278            | "عيّن_مسافة_العمق"
6279            | "קבע_מרחק_עומק"
6280            | "گہرائی_فاصلہ_مقرر_کرو"
6281            | "définir_distance_z"
6282            | "z_abstand_setzen"
6283            | "задать_дистанцию_z" => {
6284                let d = self.arg_num(&args, 0, 5.0)? as f32;
6285                self.gfx.borrow_mut().camera.zdist = d;
6286                return Ok(Value::Unit);
6287            },
6288
6289            // ── capture_mouse() — hide cursor and warp to centre each frame ──
6290            "capture_mouse"
6291            | "จับเมาส์"
6292            | "捕鼠"
6293            | "マウス捕捉"
6294            | "마우스잡기"
6295            | "ضبط_ماوس"
6296            | "امسك_الفأرة"
6297            | "לכוד_עכבר"
6298            | "ماؤس_پکڑو"
6299            | "capturer_souris"
6300            | "maus_erfassen"
6301            | "захватить_мышь" => {
6302                #[cfg(not(target_arch = "wasm32"))]
6303                {
6304                    let mut gfx = self.gfx.borrow_mut();
6305                    gfx.mouse_captured = true;
6306                    gfx.last_mx = f32::NAN;
6307                    if let Some(win) = gfx.window.as_mut() {
6308                        win.set_cursor_visibility(false);
6309                    }
6310                }
6311                return Ok(Value::Unit);
6312            },
6313
6314            // ── release_mouse() — restore cursor and remove clip region ──
6315            "release_mouse" => {
6316                #[cfg(not(target_arch = "wasm32"))]
6317                {
6318                    let mut gfx = self.gfx.borrow_mut();
6319                    gfx.mouse_captured = false;
6320                    gfx.last_mx = f32::NAN;
6321                    if let Some(win) = gfx.window.as_mut() {
6322                        win.set_cursor_visibility(true);
6323                    }
6324                    #[cfg(windows)]
6325                    unsafe {
6326                        // Null releases the clip; reuse the RECT-typed declaration above.
6327                        extern "system" {
6328                            fn ClipCursor(lpRect: *const std::ffi::c_void) -> i32;
6329                        }
6330                        ClipCursor(std::ptr::null());
6331                    }
6332                }
6333                return Ok(Value::Unit);
6334            },
6335
6336            // ── cursor_hide() / cursor_show() — just the OS cursor's visibility,
6337            // no warp-to-centre or clip region (unlike capture_mouse/release_mouse,
6338            // which are for FPS-style look-around). For point-and-click play where
6339            // the cursor still needs to move freely and mouse_x()/mouse_y() still
6340            // need to track real position, just hide the system pointer glyph.
6341            "cursor_hide" => {
6342                #[cfg(not(target_arch = "wasm32"))]
6343                if let Some(win) = self.gfx.borrow_mut().window.as_mut() {
6344                    win.set_cursor_visibility(false);
6345                }
6346                return Ok(Value::Unit);
6347            },
6348            "cursor_show" => {
6349                #[cfg(not(target_arch = "wasm32"))]
6350                if let Some(win) = self.gfx.borrow_mut().window.as_mut() {
6351                    win.set_cursor_visibility(true);
6352                }
6353                return Ok(Value::Unit);
6354            },
6355
6356            // ══════════════════════════════════════════════════════════════════
6357            // 3-D / 4-D DRAWING — camera, lights, depth-sorted geometry
6358            // ══════════════════════════════════════════════════════════════════
6359
6360            // ── set_camera(cry, sry, crx, srx) — store precomputed camera trig ──
6361            // Call once per frame after computing cos/sin of your rotation angles.
6362            "set_camera"
6363            | "ตั้งกล้อง"
6364            | "设镜"
6365            | "设置摄像机"
6366            | "カメラ設定"
6367            | "카메라설정"
6368            | "تنظیم_دوربین"
6369            | "عيّن_الكاميرا"
6370            | "קבע_מצלמה"
6371            | "کیمرہ_مقرر_کرو"
6372            | "définir_caméra"
6373            | "kamera_setzen"
6374            | "задать_камеру" => {
6375                let cry = self.arg_num(&args, 0, 1.0)? as f32;
6376                let sry = self.arg_num(&args, 1, 0.0)? as f32;
6377                let crx = self.arg_num(&args, 2, 1.0)? as f32;
6378                let srx = self.arg_num(&args, 3, 0.0)? as f32;
6379                let mut gfx = self.gfx.borrow_mut();
6380                gfx.camera.cry = cry;
6381                gfx.camera.sry = sry;
6382                gfx.camera.crx = crx;
6383                gfx.camera.srx = srx;
6384                return Ok(Value::Unit);
6385            },
6386
6387            // ── set_projection(cx, cy, focal, zdist) — override projection params ──
6388            // Automatically set when the window opens; override only if needed.
6389            "set_projection"
6390            | "ตั้งโปรเจกชัน"
6391            | "投影"
6392            | "投影設定"
6393            | "투영설정"
6394            | "تنظیم_فرافکنی"
6395            | "عيّن_الإسقاط"
6396            | "קבע_הטלה"
6397            | "پروجیکشن_مقرر_کرو"
6398            | "définir_projection"
6399            | "projektion_setzen"
6400            | "задать_проекцию" => {
6401                let cx = self.arg_num(&args, 0, 960.0)? as f32;
6402                let cy = self.arg_num(&args, 1, 540.0)? as f32;
6403                let focal = self.arg_num(&args, 2, 1080.0)? as f32;
6404                let zdist = self.arg_num(&args, 3, 5.0)? as f32;
6405                let mut gfx = self.gfx.borrow_mut();
6406                gfx.camera.cx = cx;
6407                gfx.camera.cy = cy;
6408                gfx.camera.focal = focal;
6409                gfx.camera.zdist = zdist;
6410                return Ok(Value::Unit);
6411            },
6412
6413            // ── mesh_load(path) → handle · loads a glb/gltf (skeleton + skin + animation) ──
6414            "gltf_load" => {
6415                let path = self.arg_str(&args, 0, "");
6416                match ling_physics::gltf::GltfModel::load(&path) {
6417                    Ok(m) => {
6418                        self.gltf_models.borrow_mut().push(m);
6419                        let h = self.gltf_models.borrow().len() - 1;
6420                        return Ok(Value::Number(h as f64));
6421                    },
6422                    Err(e) => {
6423                        eprintln!("mesh_load failed ({path}): {e}");
6424                        return Ok(Value::Number(-1.0));
6425                    },
6426                }
6427            },
6428            // mesh_anim_count(handle) → number of animation clips
6429            "gltf_anim_count" => {
6430                let h = self.arg_num(&args, 0, -1.0)? as i64;
6431                let n = self
6432                    .gltf_models
6433                    .borrow()
6434                    .get(h as usize)
6435                    .map(|m| m.animations.len())
6436                    .unwrap_or(0);
6437                return Ok(Value::Number(n as f64));
6438            },
6439            // mesh_anim_name(handle, i) → clip name
6440            "gltf_anim_name" => {
6441                let h = self.arg_num(&args, 0, -1.0)? as i64;
6442                let i = self.arg_num(&args, 1, 0.0)? as usize;
6443                let s = self
6444                    .gltf_models
6445                    .borrow()
6446                    .get(h as usize)
6447                    .and_then(|m| m.animations.get(i))
6448                    .map(|a| a.name.clone())
6449                    .unwrap_or_default();
6450                return Ok(Value::Str(s));
6451            },
6452            // mesh_anim_dur(handle, i) → clip duration (seconds)
6453            "gltf_anim_dur" => {
6454                let h = self.arg_num(&args, 0, -1.0)? as i64;
6455                let i = self.arg_num(&args, 1, 0.0)? as usize;
6456                let d = self
6457                    .gltf_models
6458                    .borrow()
6459                    .get(h as usize)
6460                    .and_then(|m| m.animations.get(i))
6461                    .map(|a| a.duration)
6462                    .unwrap_or(0.0);
6463                return Ok(Value::Number(d as f64));
6464            },
6465            // mesh_tris(handle) → total triangle count (perf sanity check)
6466            "gltf_tris" => {
6467                let h = self.arg_num(&args, 0, -1.0)? as i64;
6468                let n: usize = self
6469                    .gltf_models
6470                    .borrow()
6471                    .get(h as usize)
6472                    .map(|m| m.meshes.iter().map(|mm| mm.indices.len()).sum::<usize>() / 3)
6473                    .unwrap_or(0);
6474                return Ok(Value::Number(n as f64));
6475            },
6476
6477            // gltf_joint_count(handle) → number of skin joints (bones)
6478            "gltf_joint_count" => {
6479                let h = self.arg_num(&args, 0, -1.0)? as i64;
6480                let n = self
6481                    .gltf_models
6482                    .borrow()
6483                    .get(h as usize)
6484                    .and_then(|m| m.skins.first())
6485                    .map(|s| s.joints.len())
6486                    .unwrap_or(0);
6487                return Ok(Value::Number(n as f64));
6488            },
6489            // gltf_joint_name(handle, j) → bone name (its node's name)
6490            "gltf_joint_name" => {
6491                let h = self.arg_num(&args, 0, -1.0)? as i64;
6492                let j = self.arg_num(&args, 1, 0.0)? as usize;
6493                let models = self.gltf_models.borrow();
6494                let s = models
6495                    .get(h as usize)
6496                    .and_then(|m| {
6497                        m.skins
6498                            .first()
6499                            .and_then(|sk| sk.joints.get(j))
6500                            .and_then(|jt| m.nodes.get(jt.node_idx))
6501                            .map(|n| n.name.clone())
6502                    })
6503                    .unwrap_or_default();
6504                return Ok(Value::Str(s));
6505            },
6506
6507            // ── gltf_draw(handle, ox,oy,oz, scale, yaw) — filled render of a loaded model ──
6508            //   glTF is Y-up / -Z-forward; the engine is Y-down, so we flip Y and Z, then
6509            //   yaw about Y, scale, translate. Per-part colour by mesh name. Lit + depth-queued
6510            //   exactly like draw_mesh, so it shares the camera + z-buffer.
6511            "gltf_draw" => {
6512                let hh = self.arg_num(&args, 0, -1.0)? as i64;
6513                let ox = self.arg_num(&args, 1, 0.0)? as f32;
6514                let oy = self.arg_num(&args, 2, 0.0)? as f32;
6515                let oz = self.arg_num(&args, 3, 0.0)? as f32;
6516                let scale = self.arg_num(&args, 4, 1.0)? as f32;
6517                let yaw = self.arg_num(&args, 5, 0.0)? as f32;
6518                let (sy, cyy) = yaw.sin_cos();
6519                let models = self.gltf_models.borrow();
6520                let model = match models.get(hh as usize) {
6521                    Some(m) => m,
6522                    None => return Ok(Value::Unit),
6523                };
6524                let mut gfx = self.gfx.borrow_mut();
6525                let cp = {
6526                    let c = &gfx.camera;
6527                    ling_gpu::CameraParams {
6528                        cry: c.cry,
6529                        sry: c.sry,
6530                        crx: c.crx,
6531                        srx: c.srx,
6532                        cx: c.cx,
6533                        cy: c.cy,
6534                        focal: c.focal,
6535                        zdist: c.zdist,
6536                        tx: c.tx,
6537                        ty: c.ty,
6538                        tz: c.tz,
6539                    }
6540                };
6541                let near = -gfx.camera.zdist + 0.02;
6542                let ambient = gfx.ambient;
6543                for mesh in &model.meshes {
6544                    let nlow = mesh.name.to_lowercase();
6545                    let base: u32 = if nlow.contains("hair") {
6546                        0x7a4a28
6547                    } else if nlow.contains("cloth") || nlow.contains("top") {
6548                        0x4a86e0
6549                    } else if nlow.contains("wing") {
6550                        0xe6ecf5
6551                    } else if nlow.contains("star") {
6552                        0xffd24d
6553                    } else {
6554                        0xf2d6b8
6555                    };
6556                    let nv = mesh.verts.len();
6557                    if nv == 0 {
6558                        continue;
6559                    }
6560                    let mut world = vec![0.0f32; nv * 3];
6561                    for (i, v) in mesh.verts.iter().enumerate() {
6562                        let gx = v.pos.x * scale;
6563                        let gy = -v.pos.y * scale;
6564                        let gz = -v.pos.z * scale;
6565                        let rx = gx * cyy + gz * sy;
6566                        let rz = -gx * sy + gz * cyy;
6567                        world[i * 3] = ox + rx;
6568                        world[i * 3 + 1] = oy + gy;
6569                        world[i * 3 + 2] = oz + rz;
6570                    }
6571                    let mut proj = vec![0.0f32; nv * 3];
6572                    ling_gpu::backend().project_points(&world, &cp, &mut proj);
6573                    let idx = &mesh.indices;
6574                    let nt = idx.len() / 3;
6575                    for t in 0..nt {
6576                        let ia = idx[t * 3] as usize;
6577                        let ib = idx[t * 3 + 1] as usize;
6578                        let ic = idx[t * 3 + 2] as usize;
6579                        if ia >= nv || ib >= nv || ic >= nv {
6580                            continue;
6581                        }
6582                        let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
6583                        if (da + db + dc) / 3.0 <= near {
6584                            continue;
6585                        }
6586                        let col = {
6587                            let (ax, ay, az) =
6588                                (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
6589                            let (bx, by, bz) =
6590                                (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
6591                            let (px, py, pz) =
6592                                (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
6593                            let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
6594                            let (vx, vy, vz) = (px - ax, py - ay, pz - az);
6595                            let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
6596                            let centroid = [
6597                                (ax + bx + px) / 3.0,
6598                                (ay + by + py) / 3.0,
6599                                (az + bz + pz) / 3.0,
6600                            ];
6601                            if gfx.flat_shade {
6602                                base
6603                            } else {
6604                                crate::gfx::light::compute_lit_color(
6605                                    base,
6606                                    normal,
6607                                    centroid,
6608                                    &gfx.lights,
6609                                    ambient,
6610                                )
6611                            }
6612                        };
6613                        let depth = (da + db + dc) / 3.0;
6614                        let col = gfx.fog_apply(col, depth);
6615                        gfx.depth_queue.push_triangle_zv(
6616                            col,
6617                            proj[ia * 3],
6618                            proj[ia * 3 + 1],
6619                            da,
6620                            proj[ib * 3],
6621                            proj[ib * 3 + 1],
6622                            db,
6623                            proj[ic * 3],
6624                            proj[ic * 3 + 1],
6625                            dc,
6626                        );
6627                    }
6628                }
6629                return Ok(Value::Unit);
6630            },
6631
6632            // ── gltf_autorig(handle) → synthesize a humanoid skeleton + skin weights ──
6633            "gltf_autorig" => {
6634                let hh = self.arg_num(&args, 0, -1.0)? as i64;
6635                let mut models = self.gltf_models.borrow_mut();
6636                if let Some(m) = models.get_mut(hh as usize) {
6637                    return Ok(Value::Number(m.autorig() as f64));
6638                }
6639                return Ok(Value::Number(0.0));
6640            },
6641
6642            // ── gltf_pose_draw(handle, ox,oy,oz, scale, yaw, poseList) ──
6643            //   Like gltf_draw, but linear-blend-skins the mesh by `poseList` first.
6644            //   poseList = flat XYZ-euler radians, 3 per bone (12 bones → 36 values).
6645            "gltf_pose_draw" => {
6646                let hh = self.arg_num(&args, 0, -1.0)? as i64;
6647                let ox = self.arg_num(&args, 1, 0.0)? as f32;
6648                let oy = self.arg_num(&args, 2, 0.0)? as f32;
6649                let oz = self.arg_num(&args, 3, 0.0)? as f32;
6650                let scale = self.arg_num(&args, 4, 1.0)? as f32;
6651                let yaw = self.arg_num(&args, 5, 0.0)? as f32;
6652                let euler: Vec<f32> = match args.get(6) {
6653                    Some(Value::List(v)) => v
6654                        .iter()
6655                        .map(|x| self.to_number(x).unwrap_or(0.0) as f32)
6656                        .collect(),
6657                    _ => Vec::new(),
6658                };
6659                let (sy, cyy) = yaw.sin_cos();
6660                let models = self.gltf_models.borrow();
6661                let model = match models.get(hh as usize) {
6662                    Some(m) => m,
6663                    None => return Ok(Value::Unit),
6664                };
6665                let skinned = model.skin_local(&euler);
6666                let mut gfx = self.gfx.borrow_mut();
6667                let cp = {
6668                    let c = &gfx.camera;
6669                    ling_gpu::CameraParams {
6670                        cry: c.cry,
6671                        sry: c.sry,
6672                        crx: c.crx,
6673                        srx: c.srx,
6674                        cx: c.cx,
6675                        cy: c.cy,
6676                        focal: c.focal,
6677                        zdist: c.zdist,
6678                        tx: c.tx,
6679                        ty: c.ty,
6680                        tz: c.tz,
6681                    }
6682                };
6683                let near = -gfx.camera.zdist + 0.02;
6684                let ambient = gfx.ambient;
6685                for (mi, mesh) in model.meshes.iter().enumerate() {
6686                    let nlow = mesh.name.to_lowercase();
6687                    let base: u32 = if nlow.contains("hair") {
6688                        0x7a4a28
6689                    } else if nlow.contains("cloth") || nlow.contains("top") {
6690                        0x4a86e0
6691                    } else if nlow.contains("wing") {
6692                        0xe6ecf5
6693                    } else if nlow.contains("star") {
6694                        0xffd24d
6695                    } else {
6696                        0xf2d6b8
6697                    };
6698                    let sk = match skinned.get(mi) {
6699                        Some(s) => s,
6700                        None => continue,
6701                    };
6702                    let nv = sk.len();
6703                    if nv == 0 {
6704                        continue;
6705                    }
6706                    let mut world = vec![0.0f32; nv * 3];
6707                    for i in 0..nv {
6708                        let gx = sk[i][0] * scale;
6709                        let gy = -sk[i][1] * scale;
6710                        let gz = -sk[i][2] * scale;
6711                        let rx = gx * cyy + gz * sy;
6712                        let rz = -gx * sy + gz * cyy;
6713                        world[i * 3] = ox + rx;
6714                        world[i * 3 + 1] = oy + gy;
6715                        world[i * 3 + 2] = oz + rz;
6716                    }
6717                    let mut proj = vec![0.0f32; nv * 3];
6718                    ling_gpu::backend().project_points(&world, &cp, &mut proj);
6719                    let idx = &mesh.indices;
6720                    let nt = idx.len() / 3;
6721                    for t in 0..nt {
6722                        let ia = idx[t * 3] as usize;
6723                        let ib = idx[t * 3 + 1] as usize;
6724                        let ic = idx[t * 3 + 2] as usize;
6725                        if ia >= nv || ib >= nv || ic >= nv {
6726                            continue;
6727                        }
6728                        let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
6729                        if (da + db + dc) / 3.0 <= near {
6730                            continue;
6731                        }
6732                        let col = {
6733                            let (ax, ay, az) =
6734                                (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
6735                            let (bx, by, bz) =
6736                                (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
6737                            let (px, py, pz) =
6738                                (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
6739                            let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
6740                            let (vx, vy, vz) = (px - ax, py - ay, pz - az);
6741                            let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
6742                            let centroid = [
6743                                (ax + bx + px) / 3.0,
6744                                (ay + by + py) / 3.0,
6745                                (az + bz + pz) / 3.0,
6746                            ];
6747                            if gfx.flat_shade {
6748                                base
6749                            } else {
6750                                crate::gfx::light::compute_lit_color(
6751                                    base,
6752                                    normal,
6753                                    centroid,
6754                                    &gfx.lights,
6755                                    ambient,
6756                                )
6757                            }
6758                        };
6759                        let depth = (da + db + dc) / 3.0;
6760                        let col = gfx.fog_apply(col, depth);
6761                        gfx.depth_queue.push_triangle_zv(
6762                            col,
6763                            proj[ia * 3],
6764                            proj[ia * 3 + 1],
6765                            da,
6766                            proj[ib * 3],
6767                            proj[ib * 3 + 1],
6768                            db,
6769                            proj[ic * 3],
6770                            proj[ic * 3 + 1],
6771                            dc,
6772                        );
6773                    }
6774                }
6775                return Ok(Value::Unit);
6776            },
6777
6778            // ── draw_mesh(pos, idx, ox, oy, oz, scale, mode) ──
6779            //   Native batched triangle mesh. pos = flat [x,y,z,…], idx = flat tri indices.
6780            //   mode 0 = lit with current pen colour; 1 = per-face hue cycle.
6781            //   Vertices are batch-projected via ling-gpu (CPU fallback, or CUDA when the
6782            //   `cuda` feature is on); the per-triangle loop runs natively (not in the
6783            //   interpreter) so dense meshes (imported glTF, grids) stay fast.
6784            "draw_mesh"
6785            | "วาดเมช"
6786            | "رسم_مش"
6787            | "ارسم_شبكة"
6788            | "צייר_רשת"
6789            | "میش_کھینچو"
6790            | "dessiner_maillage"
6791            | "netz_zeichnen"
6792            | "рисовать_меш" => {
6793                let pos = match args.first() {
6794                    Some(Value::List(v)) => v,
6795                    _ => return Ok(Value::Unit),
6796                };
6797                let idx = match args.get(1) {
6798                    Some(Value::List(v)) => v,
6799                    _ => return Ok(Value::Unit),
6800                };
6801                let ox = self.arg_num(&args, 2, 0.0)? as f32;
6802                let oy = self.arg_num(&args, 3, 0.0)? as f32;
6803                let oz = self.arg_num(&args, 4, 0.0)? as f32;
6804                let scale = self.arg_num(&args, 5, 1.0)? as f32;
6805                let mode = self.arg_num(&args, 6, 0.0)? as i64;
6806                let nv = pos.len() / 3;
6807                if nv == 0 {
6808                    return Ok(Value::Unit);
6809                }
6810                let mut world = vec![0.0f32; nv * 3];
6811                for i in 0..nv {
6812                    world[i * 3] = ox + self.to_number(&pos[i * 3]).unwrap_or(0.0) as f32 * scale;
6813                    world[i * 3 + 1] =
6814                        oy + self.to_number(&pos[i * 3 + 1]).unwrap_or(0.0) as f32 * scale;
6815                    world[i * 3 + 2] =
6816                        oz + self.to_number(&pos[i * 3 + 2]).unwrap_or(0.0) as f32 * scale;
6817                }
6818                let mut gfx = self.gfx.borrow_mut();
6819                let cp = {
6820                    let c = &gfx.camera;
6821                    ling_gpu::CameraParams {
6822                        cry: c.cry,
6823                        sry: c.sry,
6824                        crx: c.crx,
6825                        srx: c.srx,
6826                        cx: c.cx,
6827                        cy: c.cy,
6828                        focal: c.focal,
6829                        zdist: c.zdist,
6830                        tx: c.tx,
6831                        ty: c.ty,
6832                        tz: c.tz,
6833                    }
6834                };
6835                let near = -gfx.camera.zdist + 0.02;
6836                let base = gfx.color;
6837                let ambient = gfx.ambient;
6838                let mut proj = vec![0.0f32; nv * 3]; // (sx, sy, depth) per vertex
6839                ling_gpu::backend().project_points(&world, &cp, &mut proj);
6840                let nt = idx.len() / 3;
6841                for t in 0..nt {
6842                    let ia = self.to_number(&idx[t * 3]).unwrap_or(0.0) as usize;
6843                    let ib = self.to_number(&idx[t * 3 + 1]).unwrap_or(0.0) as usize;
6844                    let ic = self.to_number(&idx[t * 3 + 2]).unwrap_or(0.0) as usize;
6845                    if ia >= nv || ib >= nv || ic >= nv {
6846                        continue;
6847                    }
6848                    let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
6849                    if (da + db + dc) / 3.0 <= near {
6850                        continue;
6851                    } // near-plane cull (centroid)
6852                    let col = if mode == 1 {
6853                        let h = t as f32 * 0.6;
6854                        let r = ((h.sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
6855                        let g = (((h + 2.094).sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
6856                        let b = (((h + 4.189).sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
6857                        (r << 16) | (g << 8) | b
6858                    } else {
6859                        let (ax, ay, az) = (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
6860                        let (bx, by, bz) = (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
6861                        let (px, py, pz) = (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
6862                        let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
6863                        let (vx, vy, vz) = (px - ax, py - ay, pz - az);
6864                        let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
6865                        let centroid = [
6866                            (ax + bx + px) / 3.0,
6867                            (ay + by + py) / 3.0,
6868                            (az + bz + pz) / 3.0,
6869                        ];
6870                        if gfx.flat_shade {
6871                            base
6872                        } else {
6873                            crate::gfx::light::compute_lit_color(
6874                                base,
6875                                normal,
6876                                centroid,
6877                                &gfx.lights,
6878                                ambient,
6879                            )
6880                        }
6881                    };
6882                    let depth = (da + db + dc) / 3.0;
6883                    let col = gfx.fog_apply(col, depth);
6884                    // True per-vertex depth so the z-buffer resolves mesh
6885                    // self-occlusion (when depth_test is off, the flush ignores
6886                    // z and uses the screen x/y exactly as before).
6887                    gfx.depth_queue.push_triangle_zv(
6888                        col,
6889                        proj[ia * 3],
6890                        proj[ia * 3 + 1],
6891                        da,
6892                        proj[ib * 3],
6893                        proj[ib * 3 + 1],
6894                        db,
6895                        proj[ic * 3],
6896                        proj[ic * 3 + 1],
6897                        dc,
6898                    );
6899                }
6900                return Ok(Value::Unit);
6901            },
6902
6903            // ── add_light(x, y, z, r, g, b, intensity, radius) ──
6904            // Adds a point light in world space.  r/g/b in [0..1].
6905            // radius == 0 → no distance falloff.
6906            "add_light"
6907            | "เพิ่มแสง"
6908            | "加灯"
6909            | "ライト追加"
6910            | "조명추가"
6911            | "افزودن_نور"
6912            | "أضف_ضوء"
6913            | "הוסף_אור"
6914            | "روشنی_شامل_کرو"
6915            | "ajouter_lumière"
6916            | "licht_hinzufügen"
6917            | "добавить_свет" => {
6918                let x = self.arg_num(&args, 0, 0.0)? as f32;
6919                let y = self.arg_num(&args, 1, -3.0)? as f32;
6920                let z = self.arg_num(&args, 2, 3.0)? as f32;
6921                let mut r = self.arg_num(&args, 3, 1.0)? as f32;
6922                let mut g = self.arg_num(&args, 4, 1.0)? as f32;
6923                let mut b = self.arg_num(&args, 5, 1.0)? as f32;
6924                // Forgive 0-255 colour values: if any channel is clearly > 1,
6925                // treat the triple as 0-255 and normalise. Keeps 0-1 callers exact.
6926                if r > 1.5 || g > 1.5 || b > 1.5 {
6927                    r /= 255.0;
6928                    g /= 255.0;
6929                    b /= 255.0;
6930                }
6931                let intensity = self.arg_num(&args, 6, 1.0)? as f32;
6932                let radius = self.arg_num(&args, 7, 0.0)? as f32;
6933                self.gfx
6934                    .borrow_mut()
6935                    .lights
6936                    .push(Light { x, y, z, r, g, b, intensity, radius });
6937                return Ok(Value::Unit);
6938            },
6939
6940            // ── clear_lights() — remove all lights ──
6941            "clear_lights"
6942            | "ล้างแสง"
6943            | "清灯"
6944            | "ライト消去"
6945            | "조명초기화"
6946            | "پاک‌کردن_نورها"
6947            | "امسح_الأضواء"
6948            | "נקה_אורות"
6949            | "روشنیاں_صاف_کرو"
6950            | "effacer_lumières"
6951            | "lichter_löschen"
6952            | "очистить_свет" => {
6953                self.gfx.borrow_mut().lights.clear();
6954                return Ok(Value::Unit);
6955            },
6956
6957            // ── set_material(key, value) — configure LingMaterial field ──
6958            // Activates the material BSDF for subsequent polygon/triangle draws.
6959            // Keys (string): "albedo" "roughness" "metallic" "emission"
6960            //   "emission_strength" "specular" "specular_tint" "subsurface"
6961            //   "subsurface_color" "clearcoat" "clearcoat_roughness"
6962            //   "transmission" "ior" "iridescence" "sheen" "anisotropy"
6963            //   "anisotropy_angle" "toon_bands" "shadow_softness"
6964            //   "outline_px" "outline_color" "highlight_color"
6965            // Value: number (or packed 0xRRGGBB for colour fields)
6966            "set_material"
6967            | "ตั้งวัสดุ"
6968            | "设置材质"
6969            | "マテリアル設定"
6970            | "재질설정"
6971            | "تنظیم_متریال"
6972            | "عيّن_المادة"
6973            | "קבע_חומר"
6974            | "میٹریل_مقرر_کرو" => {
6975                let key = self.arg_str(&args, 0, "");
6976                let val = self.arg_num(&args, 1, 0.0)?;
6977                let mut gfx = self.gfx.borrow_mut();
6978                let mat = gfx
6979                    .material
6980                    .get_or_insert_with(crate::gfx::LingMaterial::default);
6981                match key.as_str() {
6982                    "albedo" => mat.albedo = val as u32,
6983                    "roughness" => mat.roughness = val as f32,
6984                    "metallic" => mat.metallic = val as f32,
6985                    "emission" => mat.emission = val as u32,
6986                    "emission_strength" => mat.emission_strength = val as f32,
6987                    "specular" => mat.specular = val as f32,
6988                    "specular_tint" => mat.specular_tint = val as f32,
6989                    "subsurface" => mat.subsurface = val as f32,
6990                    "subsurface_color" => mat.subsurface_color = val as u32,
6991                    "clearcoat" => mat.clearcoat = val as f32,
6992                    "clearcoat_roughness" => mat.clearcoat_roughness = val as f32,
6993                    "transmission" => mat.transmission = val as f32,
6994                    "ior" => mat.ior = val as f32,
6995                    "iridescence" => mat.iridescence = val as f32,
6996                    "sheen" => mat.sheen = val as f32,
6997                    "anisotropy" => mat.anisotropy = val as f32,
6998                    "anisotropy_angle" => mat.anisotropy_angle = val as f32,
6999                    "toon_bands" => mat.toon_bands = val as u32,
7000                    "shadow_softness" => mat.shadow_softness = val as f32,
7001                    "outline_px" => mat.outline_px = val as f32,
7002                    "outline_color" => mat.outline_color = val as u32,
7003                    "highlight_color" => mat.highlight_color = val as u32,
7004                    _ => {},
7005                }
7006                return Ok(Value::Unit);
7007            },
7008
7009            // ── reset_material() — disable material override ──
7010            // After this call, draws use the legacy compute_lit_color_linear path.
7011            "reset_material"
7012            | "รีเซ็ตวัสดุ"
7013            | "重置材质"
7014            | "マテリアルリセット"
7015            | "재질초기화"
7016            | "بازنشانی_متریال"
7017            | "أعد_ضبط_المادة"
7018            | "אפס_חומר"
7019            | "میٹریل_ری_سیٹ" => {
7020                self.gfx.borrow_mut().material = None;
7021                return Ok(Value::Unit);
7022            },
7023
7024            // ── toon_outlines(thickness, color, threshold) ──
7025            // Enable vector-smooth ink outlines on depth discontinuities.
7026            //   thickness  — ink-line half-width in pixels (0 = off, 1.5 = anime default)
7027            //   color      — 0xRRGGBB ink colour (default black = 0)
7028            //   threshold  — depth delta that triggers an edge (0.05 recommended)
7029            "toon_outlines"
7030            | "ตั้งเส้นขอบการ์ตูน"
7031            | "卡通轮廓"
7032            | "トゥーンアウトライン"
7033            | "툰아웃라인"
7034            | "خطوط_کارتونی"
7035            | "حدود_كرتونية"
7036            | "קווי_מתאר_מצוירים"
7037            | "ٹون_آؤٹ_لائنز" => {
7038                let px = self.arg_num(&args, 0, 0.0)? as f32;
7039                let color = self.arg_num(&args, 1, 0.0)? as u32;
7040                let thresh = self.arg_num(&args, 2, 0.05)? as f32;
7041                let mut gfx = self.gfx.borrow_mut();
7042                gfx.toon.outline_px = px;
7043                gfx.toon.outline_color = color;
7044                gfx.toon.outline_thresh = thresh;
7045                return Ok(Value::Unit);
7046            },
7047
7048            // ── tone_stop(t, value) ──
7049            // Add a stop to the tone ramp.
7050            //   t      — input luminance position [0..1]
7051            //   value  — output brightness [0..1]
7052            // Stops are automatically sorted; call tone_ramp_reset() first to clear.
7053            "tone_stop"
7054            | "ตั้งจุดโทน"
7055            | "色调停止"
7056            | "トーンストップ"
7057            | "톤스톱"
7058            | "نقطه_توقف_تن_رنگ"
7059            | "نقطة_توقف_اللون"
7060            | "נקודת_עצירת_גוון"
7061            | "ٹون_اسٹاپ" => {
7062                let t = self.arg_num(&args, 0, 0.0)? as f32;
7063                let val = self.arg_num(&args, 1, 1.0)? as f32;
7064                let mut gfx = self.gfx.borrow_mut();
7065                gfx.toon.ramp.stops.push(crate::gfx::toon::ToneStop {
7066                    t: t.clamp(0.0, 1.0),
7067                    value: val.clamp(0.0, 1.0),
7068                });
7069                gfx.toon
7070                    .ramp
7071                    .stops
7072                    .sort_by(|a, b| a.t.partial_cmp(&b.t).unwrap_or(std::cmp::Ordering::Equal));
7073                return Ok(Value::Unit);
7074            },
7075
7076            // ── tone_smooth(enabled) ──
7077            // 0 = hard cel snap between stops (default); 1 = smooth gradient lerp.
7078            "tone_smooth"
7079            | "ตั้งโทนนุ่ม"
7080            | "色调平滑"
7081            | "トーンスムーズ"
7082            | "톤스무스"
7083            | "تن_رنگ_نرم"
7084            | "تدرج_لون_ناعم"
7085            | "גוון_חלק"
7086            | "ٹون_ہموار" => {
7087                let v = self.arg_num(&args, 0, 0.0)? as f32;
7088                self.gfx.borrow_mut().toon.ramp.smooth = v > 0.5;
7089                return Ok(Value::Unit);
7090            },
7091
7092            // ── tone_bezier(y1, y2) ──
7093            // Apply a cubic Bézier remap to the input luminance before stop lookup.
7094            //   y1, y2 — control-point y-values (identity: y1=0.333 y2=0.667)
7095            //   0 args or tone_bezier(0, 0)  → ease-in (shadow-heavy)
7096            //   tone_bezier(1, 1)            → ease-out (highlight-heavy)
7097            //   tone_bezier(0.1, 0.9)        → S-curve (smooth both ends)
7098            //   tone_bezier_off()            → disable (back to linear)
7099            "tone_bezier"
7100            | "ตั้งโทนเบซิเยร์"
7101            | "色调贝塞尔"
7102            | "トーンベジェ"
7103            | "톤베지어"
7104            | "تن_رنگ_بزیه"
7105            | "تدرج_لون_بيزيه"
7106            | "גוון_בזייה"
7107            | "بیزیئر_ٹون" => {
7108                let y1 = self.arg_num(&args, 0, 1.0 / 3.0)? as f32;
7109                let y2 = self.arg_num(&args, 1, 2.0 / 3.0)? as f32;
7110                self.gfx.borrow_mut().toon.ramp.bezier = Some([y1, y2]);
7111                return Ok(Value::Unit);
7112            },
7113
7114            // ── tone_bezier_off() — disable Bézier remap ──
7115            "tone_bezier_off"
7116            | "ปิดโทนเบซิเยร์"
7117            | "关闭色调贝塞尔"
7118            | "トーンベジェオフ"
7119            | "톤베지어끄기"
7120            | "خاموش‌کردن_بزیه"
7121            | "إيقاف_تدرج_بيزيه"
7122            | "כבה_גוון_בזייה"
7123            | "بیزیئر_ٹون_بند" => {
7124                self.gfx.borrow_mut().toon.ramp.bezier = None;
7125                return Ok(Value::Unit);
7126            },
7127
7128            // ── tone_ramp_reset() — restore default 3-band cel ramp ──
7129            "tone_ramp_reset"
7130            | "รีเซ็ตการไล่โทน"
7131            | "重置色调渐变"
7132            | "トーンランプリセット"
7133            | "톤램프리셋"
7134            | "بازنشانی_شیب_تن_رنگ"
7135            | "أعد_ضبط_تدرج_اللون"
7136            | "אפס_שיפוע_גוון"
7137            | "ٹون_ریمپ_ری_سیٹ" => {
7138                self.gfx.borrow_mut().toon.ramp = crate::gfx::toon::ToneRamp::default();
7139                return Ok(Value::Unit);
7140            },
7141
7142            // ── tone_ramp_clear() — clear all stops (build your own ramp) ──
7143            "tone_ramp_clear"
7144            | "ล้างการไล่โทน"
7145            | "清除色调渐变"
7146            | "トーンランプクリア"
7147            | "톤램프클리어"
7148            | "پاک‌کردن_شیب_تن_رنگ"
7149            | "امسح_تدرج_اللون"
7150            | "נקה_שיפוע_גוון"
7151            | "ٹون_ریمپ_صاف" => {
7152                self.gfx.borrow_mut().toon.ramp.stops.clear();
7153                return Ok(Value::Unit);
7154            },
7155
7156            // ── tone_soft(soft, sheen) — band-edge softness + highlight sheen ──
7157            //   soft  [0..1] — fraction of each band gap that blends smoothly
7158            //                  across the boundary (0 = crisp cel, ~0.3 = soft
7159            //                  Wind Waker shadow edges). Default 0.32.
7160            //   sheen [0..1] — bright pixels keep their smooth gradient instead
7161            //                  of being quantised (clean specular/rim sheen
7162            //                  rather than scratchy banded highlights). 0.65.
7163            "tone_soft"
7164            | "โทนขอบนุ่ม"
7165            | "色调柔边"
7166            | "トーンソフト"
7167            | "톤소프트"
7168            | "تن_رنگ_لبه‌نرم"
7169            | "تدرج_ناعم_الحواف"
7170            | "גוון_קצה_רך"
7171            | "نرم_کنارہ_ٹون"
7172            | "tonalité_douce"
7173            | "weicher_ton"
7174            | "мягкий_тон" => {
7175                let s = self.arg_num(&args, 0, 0.32)? as f32;
7176                let sh = self.arg_num(&args, 1, 0.65)? as f32;
7177                let mut gfx = self.gfx.borrow_mut();
7178                gfx.toon.ramp.soft = s.clamp(0.0, 1.0);
7179                gfx.toon.ramp.sheen = sh.clamp(0.0, 1.0);
7180                return Ok(Value::Unit);
7181            },
7182
7183            // ── set_ssao(strength, radius_px, zrange) — ambient occlusion ──
7184            // Depth-buffer contact shading: soft darkening in corners/under
7185            // objects, computed half-res + smoothed (no grain). Needs
7186            // set_depth_test(1). strength 0 disables. Defaults (0.35, 6, 12).
7187            "set_ssao"
7188            | "ตั้งเงาสัมผัส"
7189            | "环境光遮蔽"
7190            | "アンビエントオクルージョン"
7191            | "앰비언트오클루전"
7192            | "تنظیم_انسداد_محیطی"
7193            | "عيّن_تظليل_محيطي"
7194            | "קבע_הצללה_סביבתית"
7195            | "ایس_ایس_اے_او_مقرر_کرو"
7196            | "définir_ssao"
7197            | "ssao_setzen"
7198            | "задать_ssao" => {
7199                let s = self.arg_num(&args, 0, 0.35)? as f32;
7200                let r = self.arg_num(&args, 1, 6.0)? as f32;
7201                let z = self.arg_num(&args, 2, 12.0)? as f32;
7202                let mut gfx = self.gfx.borrow_mut();
7203                gfx.toon.ao_strength = s.clamp(0.0, 1.0);
7204                gfx.toon.ao_radius = r.max(1.0);
7205                gfx.toon.ao_range = z.max(0.01);
7206                return Ok(Value::Unit);
7207            },
7208
7209            // ── set_fxaa(on) — FXAA-lite screen-space edge anti-aliasing ──
7210            // Softens polygon stair-steps and ink-line jaggies over the whole
7211            // frame; flat fills are untouched. Applied last in the present
7212            // post-chain. (set_antialias smooths wireframe STROKES; this pass
7213            // smooths the composited IMAGE.)
7214            "set_fxaa"
7215            | "ลบรอยหยัก"
7216            | "屏幕抗锯齿"
7217            | "画面アンチエイリアス"
7218            | "화면안티앨리어싱"
7219            | "تنظیم_ضدلبه‌دندانه_سریع"
7220            | "عيّن_مضاد_التسنن_السريع"
7221            | "קבע_החלקת_מסך"
7222            | "ایف_ایکس_اے_اے_مقرر_کرو"
7223            | "définir_fxaa"
7224            | "fxaa_setzen"
7225            | "задать_fxaa" => {
7226                let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
7227                self.gfx.borrow_mut().toon.fxaa = on;
7228                return Ok(Value::Unit);
7229            },
7230
7231            // ── set_bloom(strength, threshold) — soft HDR-style glow ──
7232            // Bright pixels (rim sheen, emissive, additive FX) bleed a soft
7233            // quarter-res glow — the "HDR material" feel for toon/vector art.
7234            // strength 0 disables; threshold = luminance cutoff [0..1].
7235            "set_bloom"
7236            | "ตั้งบลูม"
7237            | "泛光"
7238            | "ブルーム"
7239            | "블룸"
7240            | "تنظیم_درخشش"
7241            | "عيّن_التوهج"
7242            | "קבע_זוהר"
7243            | "بلوم_مقرر_کرو"
7244            | "définir_bloom"
7245            | "bloom_setzen"
7246            | "задать_блум" => {
7247                let s = self.arg_num(&args, 0, 0.45)? as f32;
7248                let t = self.arg_num(&args, 1, 0.74)? as f32;
7249                let mut gfx = self.gfx.borrow_mut();
7250                gfx.toon.bloom_strength = s.max(0.0);
7251                gfx.toon.bloom_thresh = t.clamp(0.0, 0.99);
7252                return Ok(Value::Unit);
7253            },
7254
7255            // ── shadow_smooth(softness) [compat] → tone_smooth + tone_bezier ──
7256            // Deprecated: use tone_smooth + tone_bezier instead.
7257            "shadow_smooth"
7258            | "ตั้งเงานุ่ม"
7259            | "柔化阴影"
7260            | "影ソフト"
7261            | "그림자부드럽게"
7262            | "سایه_نرم"
7263            | "ظل_ناعم"
7264            | "צל_חלק"
7265            | "نرم_سایہ" => {
7266                let s = self.arg_num(&args, 0, 0.0)? as f32;
7267                let mut gfx = self.gfx.borrow_mut();
7268                gfx.toon.ramp.smooth = s > 0.05;
7269                if s > 0.05 {
7270                    let y1 = (0.333 + s * 0.2).clamp(0.0, 1.0);
7271                    let y2 = (0.667 - s * 0.2).clamp(0.0, 1.0);
7272                    gfx.toon.ramp.bezier = Some([y1, y2]);
7273                } else {
7274                    gfx.toon.ramp.bezier = None;
7275                }
7276                return Ok(Value::Unit);
7277            },
7278
7279            // ── toon_highlight [compat] — no-op, use tone_stop instead ──
7280            "toon_highlight"
7281            | "ตั้งไฮไลท์การ์ตูน"
7282            | "卡通高光"
7283            | "トゥーンハイライト"
7284            | "툰하이라이트"
7285            | "هایلایت_کارتونی"
7286            | "إبراز_كرتوني"
7287            | "הדגשה_מצוירת"
7288            | "ٹون_ہائی_لائٹ" => {
7289                // Remap as a lit-band brightness boost: adds a stop near the highlight threshold.
7290                let _strength = self.arg_num(&args, 0, 0.0)? as f32;
7291                let _thresh = self.arg_num(&args, 2, 0.78)? as f32;
7292                // No-op: configure via tone_stop() for precise control.
7293                return Ok(Value::Unit);
7294            },
7295
7296            // ── set_ambient(v) — ambient light level [0..1] ──
7297            "set_ambient"
7298            | "ตั้งแสงรอบข้าง"
7299            | "环境光"
7300            | "環境光設定"
7301            | "환경광설정"
7302            | "تنظیم_نور_محیطی"
7303            | "عيّن_الإضاءة_المحيطة"
7304            | "קבע_תאורה_סביבתית"
7305            | "ماحولیاتی_روشنی_مقرر_کرو"
7306            | "définir_ambiante"
7307            | "umgebungslicht_setzen"
7308            | "задать_фон" => {
7309                let v = self.arg_num(&args, 0, 0.15)? as f32;
7310                self.gfx.borrow_mut().ambient = v;
7311                return Ok(Value::Unit);
7312            },
7313
7314            // ── set_fog(r,g,b, start, end) — distance fog toward (r,g,b).
7315            //    triangles/lines fade from `start`..`end` camera depth. end<=0 = off.
7316            "set_fog"
7317            | "ตั้งหมอก"
7318            | "雾"
7319            | "霧設定"
7320            | "안개설정"
7321            | "تنظیم_مه"
7322            | "عيّن_الضباب"
7323            | "קבע_ערפל"
7324            | "دھند_مقرر_کرو" => {
7325                let r = self.arg_num(&args, 0, 0.0)?.clamp(0.0, 255.0) as u32;
7326                let g = self.arg_num(&args, 1, 0.0)?.clamp(0.0, 255.0) as u32;
7327                let b = self.arg_num(&args, 2, 0.0)?.clamp(0.0, 255.0) as u32;
7328                let start = self.arg_num(&args, 3, 0.0)? as f32;
7329                let end = self.arg_num(&args, 4, 0.0)? as f32;
7330                let mut gfx = self.gfx.borrow_mut();
7331                gfx.fog_color = (r << 16) | (g << 8) | b;
7332                gfx.fog_start = start;
7333                gfx.fog_end = end;
7334                return Ok(Value::Unit);
7335            },
7336
7337            // ── วาดสามเหลี่ยม3มิติ(ax,ay,az, bx,by,bz, cx,cy,cz) ──
7338            // Computes lighting from world-space normal + active lights (cel shading),
7339            // projects via the stored camera, and pushes to the depth queue.
7340            "วาดสามเหลี่ยม3มิติ"
7341            | "draw_triangle_3d"
7342            | "triangle3d"
7343            | "رسم_مثلث_سه‌بعدی"
7344            | "ارسم_مثلثا_ثلاثي_الأبعاد"
7345            | "צייר_משולש_תלת_ממדי"
7346            | "تھری_ڈی_مثلث_کھینچو" => {
7347                let ax = self.arg_num(&args, 0, 0.0)? as f32;
7348                let ay = self.arg_num(&args, 1, 0.0)? as f32;
7349                let az = self.arg_num(&args, 2, 0.0)? as f32;
7350                let bx = self.arg_num(&args, 3, 0.0)? as f32;
7351                let by = self.arg_num(&args, 4, 0.0)? as f32;
7352                let bz = self.arg_num(&args, 5, 0.0)? as f32;
7353                let cx = self.arg_num(&args, 6, 0.0)? as f32;
7354                let cy = self.arg_num(&args, 7, 0.0)? as f32;
7355                let cz = self.arg_num(&args, 8, 0.0)? as f32;
7356
7357                let mut gfx = self.gfx.borrow_mut();
7358
7359                // Mesh capture: record raw local coords + pen colour, skip submit.
7360                if gfx.mesh_capture.is_some() {
7361                    let col = gfx.color;
7362                    gfx.mesh_capture
7363                        .as_mut()
7364                        .unwrap()
7365                        .push(([ax, ay, az, bx, by, bz, cx, cy, cz], col));
7366                    return Ok(Value::Unit);
7367                }
7368
7369                gfx.submit_triangle(ax, ay, az, bx, by, bz, cx, cy, cz);
7370                return Ok(Value::Unit);
7371            },
7372
7373            // ── เริ่มอบเมช() — begin capturing 3-D triangles into a display list ──
7374            "เริ่มอบเมช"
7375            | "mesh_bake_begin"
7376            | "شروع_پخت_مش"
7377            | "ابدأ_خبز_الشبكة"
7378            | "התחל_אפיית_רשת"
7379            | "میش_بیک_شروع" => {
7380                self.gfx.borrow_mut().mesh_capture = Some(Vec::new());
7381                return Ok(Value::Unit);
7382            },
7383
7384            // ── เมชแคชรับ(key) — keyed display-list cache lookup (-1 = miss) ──
7385            "เมชแคชรับ"
7386            | "mesh_cache_get"
7387            | "دریافت_کش_مش"
7388            | "اجلب_مخبأ_الشبكة"
7389            | "קבל_מטמון_רשת"
7390            | "میش_کیش_حاصل_کرو" => {
7391                let key = self.arg_num(&args, 0, 0.0)? as i64;
7392                let h = self.gfx.borrow().mesh_cache.get(&key).copied();
7393                return Ok(Value::Number(h.map(|x| x as f64).unwrap_or(-1.0)));
7394            },
7395
7396            // ── เมชแคชตั้ง(key, handle) — store a baked mesh under key (bounded) ──
7397            "เมชแคชตั้ง"
7398            | "mesh_cache_put"
7399            | "ذخیره_در_کش_مش"
7400            | "ضع_في_مخبأ_الشبكة"
7401            | "שמור_במטמון_רשת"
7402            | "میش_کیش_رکھو" => {
7403                let key = self.arg_num(&args, 0, 0.0)? as i64;
7404                let h = self.arg_num(&args, 1, 0.0)? as usize;
7405                let mut gfx = self.gfx.borrow_mut();
7406                const CAP: usize = 256;
7407                if gfx.mesh_cache.len() >= CAP {
7408                    let evict: Vec<usize> = gfx.mesh_cache.values().copied().collect();
7409                    gfx.mesh_cache.clear();
7410                    for id in evict {
7411                        if id < gfx.meshes.len() {
7412                            gfx.meshes[id].clear();
7413                            gfx.mesh_free.push(id);
7414                        }
7415                    }
7416                }
7417                gfx.mesh_cache.insert(key, h);
7418                return Ok(Value::Unit);
7419            },
7420
7421            // ── เมชแคชล้าง() — drop the keyed cache (e.g. on level change) ──
7422            "เมชแคชล้าง"
7423            | "mesh_cache_clear"
7424            | "پاک‌کردن_کش_مش"
7425            | "امسح_مخبأ_الشبكة"
7426            | "נקה_מטמון_רשת"
7427            | "میش_کیش_صاف" => {
7428                let mut gfx = self.gfx.borrow_mut();
7429                let evict: Vec<usize> = gfx.mesh_cache.values().copied().collect();
7430                gfx.mesh_cache.clear();
7431                for id in evict {
7432                    if id < gfx.meshes.len() {
7433                        gfx.meshes[id].clear();
7434                        gfx.mesh_free.push(id);
7435                    }
7436                }
7437                return Ok(Value::Unit);
7438            },
7439
7440            // ── จบอบเมช() — bake captured triangles, return mesh handle ──
7441            "จบอบเมช"
7442            | "mesh_bake_end"
7443            | "پایان_پخت_مش"
7444            | "أنهِ_خبز_الشبكة"
7445            | "סיים_אפיית_רשת"
7446            | "میش_بیک_ختم" => {
7447                let mut gfx = self.gfx.borrow_mut();
7448                let tris = gfx.mesh_capture.take().unwrap_or_default();
7449                let id = gfx.mesh_register(tris);
7450                return Ok(Value::Number(id as f64));
7451            },
7452
7453            // ── วาดอบเมช[สี](id, ox,oy,oz, rx,ry,rz, ux,uy,uz, s) — draw a baked mesh ──
7454            //   วาดอบเมช: current pen colour (tinted glyphs)
7455            //   วาดอบเมชสี: per-triangle baked colour (multi-colour models)
7456            "วาดอบเมช"
7457            | "mesh_bake_draw"
7458            | "วาดอบเมชสี"
7459            | "mesh_bake_draw_col"
7460            | "رسم_مش_پخته"
7461            | "ارسم_شبكة_مخبوزة"
7462            | "צייר_רשת_אפויה"
7463            | "بیکڈ_میش_کھینچو" => {
7464                let baked_col = matches!(name, "วาดอบเมชสี" | "mesh_bake_draw_col");
7465                let id = self.arg_num(&args, 0, 0.0)? as usize;
7466                let ox = self.arg_num(&args, 1, 0.0)? as f32;
7467                let oy = self.arg_num(&args, 2, 0.0)? as f32;
7468                let oz = self.arg_num(&args, 3, 0.0)? as f32;
7469                let rx = self.arg_num(&args, 4, 1.0)? as f32;
7470                let ry = self.arg_num(&args, 5, 0.0)? as f32;
7471                let rz = self.arg_num(&args, 6, 0.0)? as f32;
7472                let ux = self.arg_num(&args, 7, 0.0)? as f32;
7473                let uy = self.arg_num(&args, 8, 1.0)? as f32;
7474                let uz = self.arg_num(&args, 9, 0.0)? as f32;
7475                let s = self.arg_num(&args, 10, 1.0)? as f32;
7476                self.gfx
7477                    .borrow_mut()
7478                    .mesh_draw(id, ox, oy, oz, rx, ry, rz, ux, uy, uz, s, baked_col);
7479                return Ok(Value::Unit);
7480            },
7481
7482            // ── draw_quad_3d / draw_pent_3d / draw_hex_3d / draw_polygon_3d ──
7483            // Fan-triangulate convex n-gons.  Lighting, near-plane clip, fog, and
7484            // Gouraud shading all mirror draw_triangle_3d exactly.
7485            "draw_quad_3d"
7486            | "quad3d"
7487            | "วาดสี่เหลี่ยม3มิติ"
7488            | "draw_pent_3d"
7489            | "pent3d"
7490            | "วาดห้าเหลี่ยม3มิติ"
7491            | "draw_hex_3d"
7492            | "hex3d"
7493            | "วาดหกเหลี่ยม3มิติ"
7494            | "draw_polygon_3d"
7495            | "polygon3d"
7496            | "วาดรูปหลายเหลี่ยม3มิติ"
7497            | "رسم_چهارضلعی_سه‌بعدی"
7498            | "ارسم_رباعيا_ثلاثي_الأبعاد"
7499            | "צייר_מרובע_תלת_ממדי"
7500            | "تھری_ڈی_چوکور_کھینچو" => {
7501                // Collect (wx, wy, wz) triples from args or list
7502                let mut wxs: [f32; 8] = [0.0; 8];
7503                let mut wys: [f32; 8] = [0.0; 8];
7504                let mut wzs: [f32; 8] = [0.0; 8];
7505                let n_verts;
7506
7507                if args.len() == 1 {
7508                    // draw_polygon_3d([x0,y0,z0, x1,y1,z1, ...])
7509                    let list = match &args[0] {
7510                        Value::List(l) => l.clone(),
7511                        _ => {
7512                            return Err(EvalErr::from("draw_polygon_3d: expected list".to_string()))
7513                        },
7514                    };
7515                    let coords: Vec<f32> = list
7516                        .iter()
7517                        .map(|v| match v {
7518                            Value::Number(n) => *n as f32,
7519                            _ => 0.0,
7520                        })
7521                        .collect();
7522                    n_verts = (coords.len() / 3).min(8);
7523                    for i in 0..n_verts {
7524                        wxs[i] = coords[i * 3];
7525                        wys[i] = coords[i * 3 + 1];
7526                        wzs[i] = coords[i * 3 + 2];
7527                    }
7528                } else {
7529                    // draw_quad/pent/hex_3d(x0,y0,z0, x1,y1,z1, ...)
7530                    n_verts = (args.len() / 3).min(8);
7531                    for i in 0..n_verts {
7532                        wxs[i] = self.arg_num(&args, i * 3, 0.0)? as f32;
7533                        wys[i] = self.arg_num(&args, i * 3 + 1, 0.0)? as f32;
7534                        wzs[i] = self.arg_num(&args, i * 3 + 2, 0.0)? as f32;
7535                    }
7536                }
7537                if n_verts < 3 {
7538                    return Ok(Value::Unit);
7539                }
7540
7541                let mut gfx = self.gfx.borrow_mut();
7542
7543                // Mesh capture: fan-triangulate and record raw local coords +
7544                // pen colour, exactly like วาดสามเหลี่ยม3มิติ. Quads used to
7545                // fall through here and bake EMPTY display lists — the 3-D
7546                // glyph fonts (letter pickups) are built from draw_quad_3d,
7547                // which made every baked glyph invisible.
7548                if gfx.mesh_capture.is_some() {
7549                    let col = gfx.color;
7550                    let cap = gfx.mesh_capture.as_mut().unwrap();
7551                    for i in 1..n_verts - 1 {
7552                        cap.push((
7553                            [
7554                                wxs[0],
7555                                wys[0],
7556                                wzs[0],
7557                                wxs[i],
7558                                wys[i],
7559                                wzs[i],
7560                                wxs[i + 1],
7561                                wys[i + 1],
7562                                wzs[i + 1],
7563                            ],
7564                            col,
7565                        ));
7566                    }
7567                    return Ok(Value::Unit);
7568                }
7569
7570                // Face normal from first triangle of the fan
7571                let normal = crate::gfx::poly::face_normal(
7572                    wxs[0], wys[0], wzs[0], wxs[1], wys[1], wzs[1], wxs[2], wys[2], wzs[2],
7573                );
7574
7575                // Per-vertex lit colours
7576                let mut wcs: [u32; 8] = [0; 8];
7577                if gfx.flat_shade {
7578                    let c = gfx.color;
7579                    for wc in wcs.iter_mut().take(n_verts) {
7580                        *wc = c;
7581                    }
7582                } else if let Some(ref mat) = gfx.material.clone() {
7583                    let cam = [gfx.camera.cx, gfx.camera.cy, gfx.camera.zdist];
7584                    let lights: Vec<_> = gfx.lights.clone();
7585                    let ambient = gfx.ambient;
7586                    for i in 0..n_verts {
7587                        let v = [wxs[i], wys[i], wzs[i]];
7588                        let vd = [cam[0] - v[0], cam[1] - v[1], cam[2] - v[2]];
7589                        wcs[i] = crate::gfx::material::shade(mat, normal, vd, v, &lights, ambient);
7590                    }
7591                } else {
7592                    let base = gfx.color;
7593                    let lights: Vec<_> = gfx.lights.clone();
7594                    let ambient = gfx.ambient;
7595                    for i in 0..n_verts {
7596                        wcs[i] = crate::gfx::light::compute_lit_color_linear(
7597                            base,
7598                            normal,
7599                            [wxs[i], wys[i], wzs[i]],
7600                            &lights,
7601                            ambient,
7602                        );
7603                    }
7604                }
7605
7606                // Near-plane clip (Sutherland-Hodgman per vertex)
7607                let near = -gfx.camera.zdist + 0.05;
7608                let mut clip_in: [(f32, f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
7609                    [(0.0, 0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
7610                for i in 0..n_verts {
7611                    let d = gfx.camera.depth(wxs[i], wys[i], wzs[i]);
7612                    clip_in[i] = (wxs[i], wys[i], wzs[i], d, wcs[i]);
7613                }
7614                let mut clip_out: [(f32, f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
7615                    [(0.0, 0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
7616                let pn = crate::gfx::poly::clip_near(&clip_in, n_verts, near, &mut clip_out);
7617                if pn < 3 {
7618                    return Ok(Value::Unit);
7619                }
7620
7621                // Project + fog
7622                let mut proj: [(f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
7623                    [(0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
7624                for i in 0..pn {
7625                    let (sx, sy, sz) =
7626                        gfx.camera
7627                            .project(clip_out[i].0, clip_out[i].1, clip_out[i].2);
7628                    let fc = gfx.fog_apply(clip_out[i].4, sz);
7629                    proj[i] = (sx, sy, sz, fc);
7630                }
7631
7632                // Fan-triangulate and push
7633                let unlit = gfx.flat_shade;
7634                crate::gfx::poly::fan_emit_proj(
7635                    &proj,
7636                    pn,
7637                    |x0, y0, z0, c0, x1, y1, z1, c1, x2, y2, z2, c2| {
7638                        gfx.depth_queue.push_triangle_g_zv(
7639                            x0, y0, z0, c0, x1, y1, z1, c1, x2, y2, z2, c2, 3, unlit,
7640                        );
7641                    },
7642                );
7643                return Ok(Value::Unit);
7644            },
7645
7646            // ── วาดเส้น3มิติ(ax,ay,az, bx,by,bz) ──
7647            // Projects two world-space points via the stored camera and pushes
7648            // a line to the depth queue.
7649            "วาดเส้น3มิติ"
7650            | "draw_line_3d"
7651            | "line3d"
7652            | "画3D线"
7653            | "3D線描く"
7654            | "3D선그리기"
7655            | "رسم_خط_سه‌بعدی"
7656            | "ارسم_خطا_ثلاثي_الأبعاد"
7657            | "צייר_קו_תלת_ממדי"
7658            | "تھری_ڈی_لکیر_کھینچو" => {
7659                let ax = self.arg_num(&args, 0, 0.0)? as f32;
7660                let ay = self.arg_num(&args, 1, 0.0)? as f32;
7661                let az = self.arg_num(&args, 2, 0.0)? as f32;
7662                let bx = self.arg_num(&args, 3, 0.0)? as f32;
7663                let by = self.arg_num(&args, 4, 0.0)? as f32;
7664                let bz = self.arg_num(&args, 5, 0.0)? as f32;
7665
7666                let mut gfx = self.gfx.borrow_mut();
7667                let color = gfx.color;
7668                // Near-plane clip in 3-D before perspective divide
7669                let near = -gfx.camera.zdist + 0.05;
7670                let mut lax = ax;
7671                let mut lay = ay;
7672                let mut laz = az;
7673                let mut lbx = bx;
7674                let mut lby = by;
7675                let mut lbz = bz;
7676                let da_raw = gfx.camera.depth(lax, lay, laz);
7677                let db_raw = gfx.camera.depth(lbx, lby, lbz);
7678                if da_raw <= near && db_raw <= near {
7679                    return Ok(Value::Unit);
7680                }
7681                if da_raw <= near {
7682                    let t = (near - da_raw) / (db_raw - da_raw);
7683                    lax += t * (lbx - lax);
7684                    lay += t * (lby - lay);
7685                    laz += t * (lbz - laz);
7686                } else if db_raw <= near {
7687                    let t = (near - da_raw) / (db_raw - da_raw);
7688                    lbx = lax + t * (lbx - lax);
7689                    lby = lay + t * (lby - lay);
7690                    lbz = laz + t * (lbz - laz);
7691                }
7692                // Shared-edge dedup: skip if this world-space edge was already queued.
7693                if !gfx.edge_set.try_insert(lax, lay, laz, lbx, lby, lbz) {
7694                    return Ok(Value::Unit);
7695                }
7696                let (sax, say, da) = gfx.camera.project(lax, lay, laz);
7697                let (sbx, sby, db) = gfx.camera.project(lbx, lby, lbz);
7698                let depth = (da + db) / 2.0;
7699                let color = gfx.fog_apply(color, depth);
7700                gfx.depth_queue.push_line(depth, color, sax, say, sbx, sby);
7701                return Ok(Value::Unit);
7702            },
7703
7704            // orb_shell(cx,cy,cz, radius, rot_y, rot_x, density, r,g,b)
7705            //   A single trippy, grayscale, depth-faded vector pattern wound around
7706            //   a sphere — two families of interleaved spherical spirals (a guilloché
7707            //   weave), NOT a lat/long cage. Each segment's brightness follows its
7708            //   facing (front bright, back dim), so it reads as a translucent
7709            //   grayscale "texture" with alpha rather than a hard wireframe; the
7710            //   inner marble shows through. `rot_y`/`rot_x` roll the texture around
7711            //   the orb; `density` = spirals per winding direction. r,g,b tint it
7712            //   (pass a gray like 230,230,230 for pure grayscale).
7713            #[cfg(not(target_arch = "wasm32"))]
7714            "orb_shell"
7715            | "球壳"
7716            | "オーブ殻"
7717            | "오브껍질"
7718            | "เปลือกทรงกลม"
7719            | "پوسته_کروی"
7720            | "قشرة_كروية"
7721            | "קליפת_כדור"
7722            | "کروی_خول"
7723            | "coque_orbe"
7724            | "orb_hülle"
7725            | "оболочка_сферы" => {
7726                let cx = self.arg_num(&args, 0, 0.)? as f32;
7727                let cy = self.arg_num(&args, 1, 0.)? as f32;
7728                let cz = self.arg_num(&args, 2, 0.)? as f32;
7729                let radius = self.arg_num(&args, 3, 1.0)? as f32;
7730                let ry = self.arg_num(&args, 4, 0.)? as f32;
7731                let rx = self.arg_num(&args, 5, 0.)? as f32;
7732                let density = (self.arg_num(&args, 6, 10.)? as i32).clamp(1, 48);
7733                let tr = (self.arg_num(&args, 7, 230.)? as f32).clamp(0., 255.);
7734                let tg = (self.arg_num(&args, 8, 230.)? as f32).clamp(0., 255.);
7735                let tb = (self.arg_num(&args, 9, 235.)? as f32).clamp(0., 255.);
7736                let (cyr, syr) = (ry.cos(), ry.sin());
7737                let (cxr, sxr) = (rx.cos(), rx.sin());
7738                let tau = std::f32::consts::TAU;
7739                let pi = std::f32::consts::PI;
7740                let turns = 6.0_f32; // how many times each spiral wraps pole→pole
7741                let nseg = 96; // segments per spiral (smoothness)
7742                let inv_r = if radius.abs() > 1e-5 {
7743                    1.0 / radius
7744                } else {
7745                    0.0
7746                };
7747                // a point along a spiral (param u 0..1, start angle theta0, winding dir),
7748                // spun by ry/rx — returns (world point, facing 0..1 where 1 = toward camera)
7749                let pt = |u: f32, theta0: f32, dir: f32| -> ([f32; 3], f32) {
7750                    let phi = pi * u; // 0..pi  (north → south)
7751                    let th = dir * turns * tau * u + theta0;
7752                    let (mut x, y, mut z) = (
7753                        phi.sin() * th.cos() * radius,
7754                        phi.cos() * radius,
7755                        phi.sin() * th.sin() * radius,
7756                    );
7757                    let x1 = x * cyr + z * syr; // yaw about Y
7758                    let z1 = -x * syr + z * cyr;
7759                    x = x1;
7760                    z = z1;
7761                    let y2 = y * cxr - z * sxr; // pitch about X
7762                    let z2 = y * sxr + z * cxr;
7763                    // facing: camera sits at -zdist looking +z, so smaller z2 = nearer = brighter
7764                    let facing = (0.5 - 0.5 * z2 * inv_r).clamp(0.0, 1.0);
7765                    ([cx + x, cy + y2, cz + z2], facing)
7766                };
7767                let mut gfx = self.gfx.borrow_mut();
7768                let near = -gfx.camera.zdist + 0.05;
7769                // draw one segment (near-clipped) in a grayscale tint scaled by `lum`
7770                let seg = |gfx: &mut crate::gfx::GfxState, a: [f32; 3], b: [f32; 3], lum: f32| {
7771                    let (mut lax, mut lay, mut laz) = (a[0], a[1], a[2]);
7772                    let (mut lbx, mut lby, mut lbz) = (b[0], b[1], b[2]);
7773                    let da = gfx.camera.depth(lax, lay, laz);
7774                    let db = gfx.camera.depth(lbx, lby, lbz);
7775                    if da <= near && db <= near {
7776                        return;
7777                    }
7778                    if da <= near {
7779                        let t = (near - da) / (db - da);
7780                        lax += t * (lbx - lax);
7781                        lay += t * (lby - lay);
7782                        laz += t * (lbz - laz);
7783                    } else if db <= near {
7784                        let t = (near - da) / (db - da);
7785                        lbx = lax + t * (lbx - lax);
7786                        lby = lay + t * (lby - lay);
7787                        lbz = laz + t * (lbz - laz);
7788                    }
7789                    let (sax, say, da2) = gfx.camera.project(lax, lay, laz);
7790                    let (sbx, sby, db2) = gfx.camera.project(lbx, lby, lbz);
7791                    // grayscale-alpha: front-facing bright, back faded toward black
7792                    let l = (0.12 + 0.88 * lum).clamp(0.0, 1.0);
7793                    let cr = (tr * l) as u32;
7794                    let cg = (tg * l) as u32;
7795                    let cb = (tb * l) as u32;
7796                    let color = (cr << 16) | (cg << 8) | cb;
7797                    gfx.depth_queue
7798                        .push_line((da2 + db2) * 0.5, color, sax, say, sbx, sby);
7799                };
7800                // two opposite winding directions → a soft guilloché weave (not a cage)
7801                for &dir in &[1.0_f32, -1.0_f32] {
7802                    for s in 0..density {
7803                        let theta0 = s as f32 * tau / density as f32;
7804                        let mut prev = pt(0.0, theta0, dir);
7805                        for k in 1..=nseg {
7806                            let cur = pt(k as f32 / nseg as f32, theta0, dir);
7807                            seg(&mut gfx, prev.0, cur.0, (prev.1 + cur.1) * 0.5);
7808                            prev = cur;
7809                        }
7810                    }
7811                }
7812                return Ok(Value::Unit);
7813            },
7814
7815            // orb_particles(cx,cy,cz, radius, count, t, r,g,b)
7816            //   Fills the VOLUME of a sphere with `count` swirling vector points —
7817            //   like motes suspended inside a snow-globe orb. Points are distributed
7818            //   uniformly through the ball, slowly tumble as a cloud + wobble
7819            //   individually over time `t`, and are depth-shaded (near = bright,
7820            //   far = dim) so the cloud has real volume. Additive, so it layers under
7821            //   a shell / over a liquid marble.
7822            #[cfg(not(target_arch = "wasm32"))]
7823            "orb_particles"
7824            | "球内粒子"
7825            | "オーブ粒子"
7826            | "오브입자"
7827            | "อนุภาคทรงกลม"
7828            | "ذرات_کروی"
7829            | "جسيمات_كروية"
7830            | "חלקיקי_כדור"
7831            | "کروی_ذرات"
7832            | "particules_orbe"
7833            | "orb_partikel"
7834            | "частицы_сферы" => {
7835                let cx = self.arg_num(&args, 0, 0.)? as f32;
7836                let cy = self.arg_num(&args, 1, 0.)? as f32;
7837                let cz = self.arg_num(&args, 2, 0.)? as f32;
7838                let radius = self.arg_num(&args, 3, 1.0)? as f32;
7839                let count = (self.arg_num(&args, 4, 160.)? as i32).clamp(1, 4000);
7840                let t = self.arg_num(&args, 5, 0.)? as f32;
7841                let tr = (self.arg_num(&args, 6, 255.)? as f32).clamp(0., 255.);
7842                let tg = (self.arg_num(&args, 7, 255.)? as f32).clamp(0., 255.);
7843                let tb = (self.arg_num(&args, 8, 255.)? as f32).clamp(0., 255.);
7844                let inv_r = if radius.abs() > 1e-5 {
7845                    1.0 / radius
7846                } else {
7847                    0.0
7848                };
7849                // cheap deterministic hash → [0,1)
7850                let h = |mut x: u32| -> f32 {
7851                    x = x.wrapping_mul(747796405).wrapping_add(2891336453);
7852                    x = ((x >> ((x >> 28).wrapping_add(4))) ^ x).wrapping_mul(277803737);
7853                    (((x >> 22) ^ x) & 0xFFFFFF) as f32 / 16_777_216.0
7854                };
7855                let tau = std::f32::consts::TAU;
7856                // slow tumble of the whole cloud
7857                let (cyr, syr) = ((t * 0.5).cos(), (t * 0.5).sin());
7858                let (cxr, sxr) = ((t * 0.23).cos(), (t * 0.23).sin());
7859                let mut gfx = self.gfx.borrow_mut();
7860                let near = -gfx.camera.zdist + 0.05;
7861                let (sw, sh) = (gfx.width as i32, gfx.height as i32);
7862                for i in 0..count {
7863                    let i = i as u32;
7864                    // uniform-in-volume: r = cbrt(u) * radius; direction from two hashes
7865                    let u = h(i.wrapping_mul(3) + 1);
7866                    let rr = u.cbrt() * radius * (0.85 + 0.15 * (t * 1.3 + i as f32).sin()); // gentle pulse
7867                    let th = h(i.wrapping_mul(3) + 2) * tau + t * (0.3 + 0.5 * h(i * 7 + 5)); // per-mote orbit
7868                    let ph = (h(i.wrapping_mul(3) + 3) * 2.0 - 1.0).acos(); // uniform cos(phi)
7869                    let (mut x, y, mut z) = (
7870                        rr * ph.sin() * th.cos(),
7871                        rr * ph.cos(),
7872                        rr * ph.sin() * th.sin(),
7873                    );
7874                    // tumble the cloud (yaw then pitch)
7875                    let x1 = x * cyr + z * syr;
7876                    let z1 = -x * syr + z * cyr;
7877                    x = x1;
7878                    z = z1;
7879                    let y2 = y * cxr - z * sxr;
7880                    let z2 = y * sxr + z * cxr;
7881                    let (wx, wy, wz) = (cx + x, cy + y2, cz + z2);
7882                    if gfx.camera.depth(wx, wy, wz) <= near {
7883                        continue;
7884                    }
7885                    let (sx, sy, dep) = gfx.camera.project(wx, wy, wz);
7886                    let sxi = sx as i32;
7887                    let syi = sy as i32;
7888                    if sxi < 0 || syi < 0 || sxi >= sw || syi >= sh {
7889                        continue;
7890                    }
7891                    // depth-shade: nearer (smaller z2) = brighter
7892                    let facing = (0.5 - 0.5 * z2 * inv_r).clamp(0.15, 1.0);
7893                    let l = facing;
7894                    let cr = (tr * l) as u32;
7895                    let cg = (tg * l) as u32;
7896                    let cb = (tb * l) as u32;
7897                    let color = (cr << 16) | (cg << 8) | cb;
7898                    // a 1–2px dot (bigger when near) as a short segment in the depth queue
7899                    let len = if facing > 0.7 { 1.0 } else { 0.0 };
7900                    gfx.depth_queue.push_line(dep, color, sx, sy, sx + len, sy);
7901                }
7902                return Ok(Value::Unit);
7903            },
7904
7905            // project_3d(x,y,z) -> [screen_x, screen_y, depth]; behind the camera
7906            // returns a sentinel ([-99999,-99999, depth]) so scripts can skip it.
7907            // Lets scripts place 2-D overlays (e.g. filled teardrop flames) onto 3-D points.
7908            "project_3d"
7909            | "投影3D"
7910            | "3D投影"
7911            | "3D투영"
7912            | "ฉาย3มิติ"
7913            | "فرافکنی_سه‌بعدی"
7914            | "إسقاط_ثلاثي_الأبعاد"
7915            | "הטלה_תלת_ממדית"
7916            | "تھری_ڈی_پروجیکشن"
7917            | "projeter_3d"
7918            | "projizieren_3d"
7919            | "проекция_3d" => {
7920                let x = self.arg_num(&args, 0, 0.0)? as f32;
7921                let y = self.arg_num(&args, 1, 0.0)? as f32;
7922                let z = self.arg_num(&args, 2, 0.0)? as f32;
7923                let gfx = self.gfx.borrow();
7924                let near = -gfx.camera.zdist + 0.05;
7925                let d = gfx.camera.depth(x, y, z);
7926                if d <= near {
7927                    return Ok(Value::List(Rc::new(vec![
7928                        Value::Number(-99999.0),
7929                        Value::Number(-99999.0),
7930                        Value::Number(d as f64),
7931                    ])));
7932                }
7933                let (sx, sy, depth) = gfx.camera.project(x, y, z);
7934                return Ok(Value::List(Rc::new(vec![
7935                    Value::Number(sx as f64),
7936                    Value::Number(sy as f64),
7937                    Value::Number(depth as f64),
7938                ])));
7939            },
7940
7941            // mouse_ray() -> [ox,oy,oz, dx,dy,dz] — world-space ray from the eye
7942            // through the actual mouse cursor pixel, exact inverse of project_3d's
7943            // pipeline (translate → Y-rotate → X-rotate → perspective divide by
7944            // rz+zdist). Scripts previously marched a ray along the CENTRE-SCREEN
7945            // forward vector regardless of where the cursor was — accurate only by
7946            // coincidence when the cursor happened to sit near the crosshair.
7947            #[cfg(not(target_arch = "wasm32"))]
7948            "mouse_ray" => {
7949                let gfx = self.gfx.borrow();
7950                let (mx, my) = gfx
7951                    .window
7952                    .as_ref()
7953                    .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
7954                    .unwrap_or((gfx.camera.cx, gfx.camera.cy));
7955                let cam = &gfx.camera;
7956                // Eye-relative pinhole direction for this pixel, in rotation-space
7957                // (before undoing the Y-then-X rotation project() applied).
7958                let dcx = (mx - cam.cx) / cam.focal;
7959                let dcy = (my - cam.cy) / cam.focal;
7960                // Undo the X-rotation, then the Y-rotation (reverse of project()'s
7961                // forward order), on the direction vector (dcx, dcy, 1.0).
7962                let a_x = dcx;
7963                let a_y = cam.crx * dcy + cam.srx * 1.0;
7964                let a_z = 0.0 - cam.srx * dcy + cam.crx * 1.0;
7965                let dir_x = cam.cry * a_x + cam.sry * a_z;
7966                let dir_y = a_y;
7967                let dir_z = 0.0 - cam.sry * a_x + cam.cry * a_z;
7968                let dlen = (dir_x * dir_x + dir_y * dir_y + dir_z * dir_z)
7969                    .sqrt()
7970                    .max(1e-6);
7971                let (dir_x, dir_y, dir_z) = (dir_x / dlen, dir_y / dlen, dir_z / dlen);
7972                // Origin: the camera's rotation pivot (tx,ty,tz) — i.e. wherever
7973                // the script last put it with set_camera_pos, NOT the "true"
7974                // pinhole eye zdist further back. The pivot is what scripts
7975                // already keep clear of the ground (a ground-collision pull-in
7976                // loop is standard practice for orbit cameras); the true eye
7977                // would need its own separate ground clearance since zdist is
7978                // often large relative to a close-in camera distance, and
7979                // starting a ray underground makes it hit "ground" instantly
7980                // regardless of aim. The zdist offset only matters for the
7981                // near-field parallax, which a click-to-move ray (aimed at
7982                // terrain many units out) doesn't need.
7983                let ox = cam.tx;
7984                let oy = cam.ty;
7985                let oz = cam.tz;
7986                return Ok(Value::List(Rc::new(vec![
7987                    Value::Number(ox as f64),
7988                    Value::Number(oy as f64),
7989                    Value::Number(oz as f64),
7990                    Value::Number(dir_x as f64),
7991                    Value::Number(dir_y as f64),
7992                    Value::Number(dir_z as f64),
7993                ])));
7994            },
7995            #[cfg(target_arch = "wasm32")]
7996            "mouse_ray" => {
7997                let gfx = self.gfx.borrow();
7998                let mx = crate::gfx::wasm_mouse_x();
7999                let my = crate::gfx::wasm_mouse_y();
8000                let cam = &gfx.camera;
8001                let dcx = (mx - cam.cx) / cam.focal;
8002                let dcy = (my - cam.cy) / cam.focal;
8003                let a_x = dcx;
8004                let a_y = cam.crx * dcy + cam.srx * 1.0;
8005                let a_z = 0.0 - cam.srx * dcy + cam.crx * 1.0;
8006                let dir_x = cam.cry * a_x + cam.sry * a_z;
8007                let dir_y = a_y;
8008                let dir_z = 0.0 - cam.sry * a_x + cam.cry * a_z;
8009                let dlen = (dir_x * dir_x + dir_y * dir_y + dir_z * dir_z)
8010                    .sqrt()
8011                    .max(1e-6);
8012                let (dir_x, dir_y, dir_z) = (dir_x / dlen, dir_y / dlen, dir_z / dlen);
8013                let ox = cam.tx;
8014                let oy = cam.ty;
8015                let oz = cam.tz;
8016                return Ok(Value::List(Rc::new(vec![
8017                    Value::Number(ox as f64),
8018                    Value::Number(oy as f64),
8019                    Value::Number(oz as f64),
8020                    Value::Number(dir_x as f64),
8021                    Value::Number(dir_y as f64),
8022                    Value::Number(dir_z as f64),
8023                ])));
8024            },
8025            // draw_poly([x0,y0,x1,y1,…]) — filled 2-D polygon in the current colour,
8026            // honouring the blend mode (additive → translucent glow). Auto-closes.
8027            #[cfg(not(target_arch = "wasm32"))]
8028            "draw_poly"
8029            | "填充多边形"
8030            | "ポリゴン塗り"
8031            | "다각형채우기"
8032            | "เติมรูปหลายเหลี่ยม"
8033            | "رسم_چندضلعی"
8034            | "ارسم_مضلع"
8035            | "צייר_מצולע"
8036            | "کثیر_الاضلاع_کھینچو"
8037            | "dessiner_polygone"
8038            | "polygon_zeichnen"
8039            | "рисовать_полигон" => {
8040                let mut pts: Vec<[f32; 2]> = Vec::new();
8041                if let Some(Value::List(v)) = args.first() {
8042                    let mut i = 0;
8043                    while i + 1 < v.len() {
8044                        let x = self.to_number(&v[i]).unwrap_or(0.0) as f32;
8045                        let y = self.to_number(&v[i + 1]).unwrap_or(0.0) as f32;
8046                        pts.push([x, y]);
8047                        i += 2;
8048                    }
8049                }
8050                if pts.len() >= 3 {
8051                    if pts[0] != pts[pts.len() - 1] {
8052                        let p0 = pts[0];
8053                        pts.push(p0);
8054                    } // close
8055                    let mut gfx = self.gfx.borrow_mut();
8056                    let (w, h, color, add) = (gfx.width, gfx.height, gfx.color, gfx.blend == 1);
8057                    crate::gfx::raster::fill_contours_aa(
8058                        &mut gfx.buffer,
8059                        w,
8060                        h,
8061                        color,
8062                        add,
8063                        std::slice::from_ref(&pts),
8064                    );
8065                }
8066                return Ok(Value::Unit);
8067            },
8068
8069            // ══════════════════════════════════════════════════════════════════
8070            // VECTOR TEXTURE BUILTINS  (src/gfx/vtex.rs)
8071            // All patterns are depth-biased so they appear on top of surfaces.
8072            // Plane defined by: centre (cx,cy,cz) + U tangent + V tangent.
8073            // Last two args always: fr (frame f32), hue (phase offset f32).
8074            // ══════════════════════════════════════════════════════════════════
8075
8076            // vtex_grid(cx,cy,cz, ux,uy,uz, vx,vy,vz, cols,rows, cw,ch, fr,hue)
8077            "vtex_grid"
8078            | "ลายตาราง"
8079            | "纹格"
8080            | "格子模様"
8081            | "격자무늬"
8082            | "الگوی_شبکه"
8083            | "نقش_شبكة"
8084            | "דוגמת_רשת"
8085            | "نقش_جالی"
8086            | "motif_grille"
8087            | "muster_gitter"
8088            | "узор_сетка" => {
8089                let cx = self.arg_num(&args, 0, 0.)? as f32;
8090                let cy = self.arg_num(&args, 1, 0.)? as f32;
8091                let cz = self.arg_num(&args, 2, 0.)? as f32;
8092                let ux = self.arg_num(&args, 3, 1.)? as f32;
8093                let uy = self.arg_num(&args, 4, 0.)? as f32;
8094                let uz = self.arg_num(&args, 5, 0.)? as f32;
8095                let vx = self.arg_num(&args, 6, 0.)? as f32;
8096                let vy = self.arg_num(&args, 7, 0.)? as f32;
8097                let vz = self.arg_num(&args, 8, 1.)? as f32;
8098                let cols = self.arg_num(&args, 9, 10.)? as usize;
8099                let rows = self.arg_num(&args, 10, 10.)? as usize;
8100                let cw = self.arg_num(&args, 11, 1.)? as f32;
8101                let ch = self.arg_num(&args, 12, 1.)? as f32;
8102                let fr = self.arg_num(&args, 13, 0.)? as f32;
8103                let hue = self.arg_num(&args, 14, 0.)? as f32;
8104                let mut gfx = self.gfx.borrow_mut();
8105                let cam = gfx.camera.clone();
8106                crate::gfx::vtex::draw_grid(
8107                    &mut gfx.depth_queue,
8108                    &cam,
8109                    cx,
8110                    cy,
8111                    cz,
8112                    ux,
8113                    uy,
8114                    uz,
8115                    vx,
8116                    vy,
8117                    vz,
8118                    cols,
8119                    rows,
8120                    cw,
8121                    ch,
8122                    fr,
8123                    hue,
8124                );
8125                return Ok(Value::Unit);
8126            },
8127
8128            // vtex_rings(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_rings,n_sides, max_r,twist, fr,hue)
8129            "vtex_rings"
8130            | "ลายวงซ้อน"
8131            | "纹环"
8132            | "同心円"
8133            | "동심원"
8134            | "الگوی_حلقه"
8135            | "نقش_حلقات"
8136            | "דוגמת_טבעות"
8137            | "نقش_حلقے"
8138            | "motif_anneaux"
8139            | "muster_ringe"
8140            | "узор_кольца" => {
8141                let cx = self.arg_num(&args, 0, 0.)? as f32;
8142                let cy = self.arg_num(&args, 1, 0.)? as f32;
8143                let cz = self.arg_num(&args, 2, 0.)? as f32;
8144                let ux = self.arg_num(&args, 3, 1.)? as f32;
8145                let uy = self.arg_num(&args, 4, 0.)? as f32;
8146                let uz = self.arg_num(&args, 5, 0.)? as f32;
8147                let vx = self.arg_num(&args, 6, 0.)? as f32;
8148                let vy = self.arg_num(&args, 7, 0.)? as f32;
8149                let vz = self.arg_num(&args, 8, 1.)? as f32;
8150                let nr = self.arg_num(&args, 9, 6.)? as usize;
8151                let ns = self.arg_num(&args, 10, 6.)? as usize;
8152                let mr = self.arg_num(&args, 11, 3.)? as f32;
8153                let tw = self.arg_num(&args, 12, 0.)? as f32;
8154                let fr = self.arg_num(&args, 13, 0.)? as f32;
8155                let hue = self.arg_num(&args, 14, 0.)? as f32;
8156                let mut gfx = self.gfx.borrow_mut();
8157                let cam = gfx.camera.clone();
8158                crate::gfx::vtex::draw_rings(
8159                    &mut gfx.depth_queue,
8160                    &cam,
8161                    cx,
8162                    cy,
8163                    cz,
8164                    ux,
8165                    uy,
8166                    uz,
8167                    vx,
8168                    vy,
8169                    vz,
8170                    nr,
8171                    ns,
8172                    mr,
8173                    tw,
8174                    fr,
8175                    hue,
8176                );
8177                return Ok(Value::Unit);
8178            },
8179
8180            // vtex_star(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_pts,r_out,r_in, rot_speed, fr,hue)
8181            "vtex_star"
8182            | "ลายดาว"
8183            | "纹星"
8184            | "星模様"
8185            | "별무늬"
8186            | "الگوی_ستاره"
8187            | "نقش_نجمة"
8188            | "דוגמת_כוכב"
8189            | "نقش_ستارہ"
8190            | "motif_étoile"
8191            | "muster_stern"
8192            | "узор_звезда" => {
8193                let cx = self.arg_num(&args, 0, 0.)? as f32;
8194                let cy = self.arg_num(&args, 1, 0.)? as f32;
8195                let cz = self.arg_num(&args, 2, 0.)? as f32;
8196                let ux = self.arg_num(&args, 3, 1.)? as f32;
8197                let uy = self.arg_num(&args, 4, 0.)? as f32;
8198                let uz = self.arg_num(&args, 5, 0.)? as f32;
8199                let vx = self.arg_num(&args, 6, 0.)? as f32;
8200                let vy = self.arg_num(&args, 7, 0.)? as f32;
8201                let vz = self.arg_num(&args, 8, 1.)? as f32;
8202                let np = self.arg_num(&args, 9, 6.)? as usize;
8203                let ro = self.arg_num(&args, 10, 2.)? as f32;
8204                let ri = self.arg_num(&args, 11, 1.)? as f32;
8205                let rs = self.arg_num(&args, 12, 0.01)? as f32;
8206                let fr = self.arg_num(&args, 13, 0.)? as f32;
8207                let hue = self.arg_num(&args, 14, 0.)? as f32;
8208                let mut gfx = self.gfx.borrow_mut();
8209                let cam = gfx.camera.clone();
8210                crate::gfx::vtex::draw_star(
8211                    &mut gfx.depth_queue,
8212                    &cam,
8213                    cx,
8214                    cy,
8215                    cz,
8216                    ux,
8217                    uy,
8218                    uz,
8219                    vx,
8220                    vy,
8221                    vz,
8222                    np,
8223                    ro,
8224                    ri,
8225                    rs,
8226                    fr,
8227                    hue,
8228                );
8229                return Ok(Value::Unit);
8230            },
8231
8232            // vtex_spiral(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_turns,max_r,steps, fr,hue)
8233            "vtex_spiral"
8234            | "ลายเกลียว"
8235            | "纹螺"
8236            | "螺旋"
8237            | "나선"
8238            | "الگوی_مارپیچ"
8239            | "نقش_حلزوني"
8240            | "דוגמת_ספירלה"
8241            | "نقش_سرپیچ"
8242            | "motif_spirale"
8243            | "muster_spirale"
8244            | "узор_спираль" => {
8245                let cx = self.arg_num(&args, 0, 0.)? as f32;
8246                let cy = self.arg_num(&args, 1, 0.)? as f32;
8247                let cz = self.arg_num(&args, 2, 0.)? as f32;
8248                let ux = self.arg_num(&args, 3, 1.)? as f32;
8249                let uy = self.arg_num(&args, 4, 0.)? as f32;
8250                let uz = self.arg_num(&args, 5, 0.)? as f32;
8251                let vx = self.arg_num(&args, 6, 0.)? as f32;
8252                let vy = self.arg_num(&args, 7, 0.)? as f32;
8253                let vz = self.arg_num(&args, 8, 1.)? as f32;
8254                let nt = self.arg_num(&args, 9, 3.)? as f32;
8255                let mr = self.arg_num(&args, 10, 3.)? as f32;
8256                let st = self.arg_num(&args, 11, 120.)? as usize;
8257                let fr = self.arg_num(&args, 12, 0.)? as f32;
8258                let hue = self.arg_num(&args, 13, 0.)? as f32;
8259                let mut gfx = self.gfx.borrow_mut();
8260                let cam = gfx.camera.clone();
8261                crate::gfx::vtex::draw_spiral(
8262                    &mut gfx.depth_queue,
8263                    &cam,
8264                    cx,
8265                    cy,
8266                    cz,
8267                    ux,
8268                    uy,
8269                    uz,
8270                    vx,
8271                    vy,
8272                    vz,
8273                    nt,
8274                    mr,
8275                    st,
8276                    fr,
8277                    hue,
8278                );
8279                return Ok(Value::Unit);
8280            },
8281
8282            // vtex_flower(cx,cy,cz, ux,uy,uz, vx,vy,vz, radius,n_sides, fr,hue)
8283            "vtex_flower"
8284            | "ลายดอก"
8285            | "纹花"
8286            | "花模様"
8287            | "꽃무늬"
8288            | "الگوی_گل"
8289            | "نقش_زهرة"
8290            | "דוגמת_פרח"
8291            | "نقش_پھول"
8292            | "motif_fleur"
8293            | "muster_blume"
8294            | "узор_цветок" => {
8295                let cx = self.arg_num(&args, 0, 0.)? as f32;
8296                let cy = self.arg_num(&args, 1, 0.)? as f32;
8297                let cz = self.arg_num(&args, 2, 0.)? as f32;
8298                let ux = self.arg_num(&args, 3, 1.)? as f32;
8299                let uy = self.arg_num(&args, 4, 0.)? as f32;
8300                let uz = self.arg_num(&args, 5, 0.)? as f32;
8301                let vx = self.arg_num(&args, 6, 0.)? as f32;
8302                let vy = self.arg_num(&args, 7, 0.)? as f32;
8303                let vz = self.arg_num(&args, 8, 1.)? as f32;
8304                let r = self.arg_num(&args, 9, 1.)? as f32;
8305                let ns = self.arg_num(&args, 10, 24.)? as usize;
8306                let fr = self.arg_num(&args, 11, 0.)? as f32;
8307                let hue = self.arg_num(&args, 12, 0.)? as f32;
8308                let mut gfx = self.gfx.borrow_mut();
8309                let cam = gfx.camera.clone();
8310                crate::gfx::vtex::draw_flower(
8311                    &mut gfx.depth_queue,
8312                    &cam,
8313                    cx,
8314                    cy,
8315                    cz,
8316                    ux,
8317                    uy,
8318                    uz,
8319                    vx,
8320                    vy,
8321                    vz,
8322                    r,
8323                    ns,
8324                    fr,
8325                    hue,
8326                );
8327                return Ok(Value::Unit);
8328            },
8329
8330            // vtex_letter_rain(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_cols,n_vis, col_w,row_h, speed, fr,hue)
8331            "vtex_letter_rain"
8332            | "ลายอักษรไหล"
8333            | "纹字雨"
8334            | "文字雨"
8335            | "글자비"
8336            | "الگوی_باران_حروف"
8337            | "نقش_مطر_الحروف"
8338            | "דוגמת_גשם_אותיות"
8339            | "نقش_حروف_بارش"
8340            | "motif_pluie_lettres"
8341            | "muster_buchstabenregen"
8342            | "узор_дождь_букв" => {
8343                let cx = self.arg_num(&args, 0, 0.)? as f32;
8344                let cy = self.arg_num(&args, 1, 0.)? as f32;
8345                let cz = self.arg_num(&args, 2, 0.)? as f32;
8346                let ux = self.arg_num(&args, 3, 1.)? as f32;
8347                let uy = self.arg_num(&args, 4, 0.)? as f32;
8348                let uz = self.arg_num(&args, 5, 0.)? as f32;
8349                let vx = self.arg_num(&args, 6, 0.)? as f32;
8350                let vy = self.arg_num(&args, 7, 0.)? as f32;
8351                let vz = self.arg_num(&args, 8, 1.)? as f32;
8352                let nc = self.arg_num(&args, 9, 16.)? as usize;
8353                let nv = self.arg_num(&args, 10, 14.)? as usize;
8354                let cw = self.arg_num(&args, 11, 0.65)? as f32;
8355                let rh = self.arg_num(&args, 12, 0.60)? as f32;
8356                let sp = self.arg_num(&args, 13, 0.025)? as f32;
8357                let fr = self.arg_num(&args, 14, 0.)? as f32;
8358                let hue = self.arg_num(&args, 15, 0.)? as f32;
8359                let mut gfx = self.gfx.borrow_mut();
8360                let cam = gfx.camera.clone();
8361                crate::gfx::vtex::draw_letter_rain(
8362                    &mut gfx.depth_queue,
8363                    &cam,
8364                    cx,
8365                    cy,
8366                    cz,
8367                    ux,
8368                    uy,
8369                    uz,
8370                    vx,
8371                    vy,
8372                    vz,
8373                    nc,
8374                    nv,
8375                    cw,
8376                    rh,
8377                    sp,
8378                    fr,
8379                    hue,
8380                );
8381                return Ok(Value::Unit);
8382            },
8383
8384            // vtex_hyperbolic_uv(cx,cy,cz, ux,uy,uz, vx,vy,vz, max_r,n_circles,n_rays, fr,hue)
8385            "vtex_hyperbolic_uv"
8386            | "ลายไฮเพอร์โบลิก"
8387            | "纹曲面"
8388            | "双曲線"
8389            | "쌍곡선"
8390            | "الگوی_هذلولی"
8391            | "نقش_زائدي"
8392            | "דוגמת_היפרבולית"
8393            | "نقش_ہائپربولک"
8394            | "motif_uv_hyperbolique"
8395            | "muster_hyperbolische_uv"
8396            | "узор_гиперболический_uv" => {
8397                let cx = self.arg_num(&args, 0, 0.)? as f32;
8398                let cy = self.arg_num(&args, 1, 0.)? as f32;
8399                let cz = self.arg_num(&args, 2, 0.)? as f32;
8400                let ux = self.arg_num(&args, 3, 1.)? as f32;
8401                let uy = self.arg_num(&args, 4, 0.)? as f32;
8402                let uz = self.arg_num(&args, 5, 0.)? as f32;
8403                let vx = self.arg_num(&args, 6, 0.)? as f32;
8404                let vy = self.arg_num(&args, 7, 0.)? as f32;
8405                let vz = self.arg_num(&args, 8, 1.)? as f32;
8406                let mr = self.arg_num(&args, 9, 5.)? as f32;
8407                let nc = self.arg_num(&args, 10, 12.)? as usize;
8408                let nr = self.arg_num(&args, 11, 18.)? as usize;
8409                let fr = self.arg_num(&args, 12, 0.)? as f32;
8410                let hue = self.arg_num(&args, 13, 0.)? as f32;
8411                let mut gfx = self.gfx.borrow_mut();
8412                let cam = gfx.camera.clone();
8413                crate::gfx::vtex::draw_hyperbolic_uv(
8414                    &mut gfx.depth_queue,
8415                    &cam,
8416                    cx,
8417                    cy,
8418                    cz,
8419                    ux,
8420                    uy,
8421                    uz,
8422                    vx,
8423                    vy,
8424                    vz,
8425                    mr,
8426                    nc,
8427                    nr,
8428                    fr,
8429                    hue,
8430                );
8431                return Ok(Value::Unit);
8432            },
8433
8434            // vtex_halftone(cx,cy,cz, ux,uy,uz, vx,vy,vz, cols,rows, cell_w,cell_h, density, fr,hue)
8435            "vtex_halftone"
8436            | "ลายจุด"
8437            | "纹半调"
8438            | "網点模様"
8439            | "망점"
8440            | "الگوی_نیم‌تن"
8441            | "نقش_نصفي"
8442            | "דוגמת_חצי_גוון"
8443            | "نقش_ہاف_ٹون"
8444            | "motif_demi_ton"
8445            | "muster_halbton"
8446            | "узор_растр" => {
8447                let cx = self.arg_num(&args, 0, 0.)? as f32;
8448                let cy = self.arg_num(&args, 1, 0.)? as f32;
8449                let cz = self.arg_num(&args, 2, 0.)? as f32;
8450                let ux = self.arg_num(&args, 3, 1.)? as f32;
8451                let uy = self.arg_num(&args, 4, 0.)? as f32;
8452                let uz = self.arg_num(&args, 5, 0.)? as f32;
8453                let vx = self.arg_num(&args, 6, 0.)? as f32;
8454                let vy = self.arg_num(&args, 7, 0.)? as f32;
8455                let vz = self.arg_num(&args, 8, 1.)? as f32;
8456                let cols = self.arg_num(&args, 9, 16.)? as usize;
8457                let rows = self.arg_num(&args, 10, 12.)? as usize;
8458                let cw = self.arg_num(&args, 11, 0.5)? as f32;
8459                let ch = self.arg_num(&args, 12, 0.5)? as f32;
8460                let dens = self.arg_num(&args, 13, 0.4)? as f32;
8461                let fr = self.arg_num(&args, 14, 0.)? as f32;
8462                let hue = self.arg_num(&args, 15, 0.)? as f32;
8463                let mut gfx = self.gfx.borrow_mut();
8464                let cam = gfx.camera.clone();
8465                crate::gfx::vtex::draw_halftone(
8466                    &mut gfx.depth_queue,
8467                    &cam,
8468                    cx,
8469                    cy,
8470                    cz,
8471                    ux,
8472                    uy,
8473                    uz,
8474                    vx,
8475                    vy,
8476                    vz,
8477                    cols,
8478                    rows,
8479                    cw,
8480                    ch,
8481                    dens,
8482                    fr,
8483                    hue,
8484                );
8485                return Ok(Value::Unit);
8486            },
8487
8488            // vtex_tessellated(cx,cy,cz, ux,uy,uz, vx,vy,vz, cols,rows, cell, amplitude,freq, fr,hue)
8489            "vtex_tessellated"
8490            | "ลายตาข่าย"
8491            | "纹镶嵌"
8492            | "網目模様"
8493            | "격자망"
8494            | "الگوی_کاشی‌کاری"
8495            | "نقش_مرصوف_متكرر"
8496            | "דוגמת_ריצוף_חוזר"
8497            | "نقش_ٹائلنگ"
8498            | "motif_tesselle"
8499            | "muster_tessellation"
8500            | "узор_мозаика" => {
8501                let cx = self.arg_num(&args, 0, 0.)? as f32;
8502                let cy = self.arg_num(&args, 1, 0.)? as f32;
8503                let cz = self.arg_num(&args, 2, 0.)? as f32;
8504                let ux = self.arg_num(&args, 3, 1.)? as f32;
8505                let uy = self.arg_num(&args, 4, 0.)? as f32;
8506                let uz = self.arg_num(&args, 5, 0.)? as f32;
8507                let vx = self.arg_num(&args, 6, 0.)? as f32;
8508                let vy = self.arg_num(&args, 7, 0.)? as f32;
8509                let vz = self.arg_num(&args, 8, 1.)? as f32;
8510                let cols = self.arg_num(&args, 9, 14.)? as usize;
8511                let rows = self.arg_num(&args, 10, 10.)? as usize;
8512                let cell = self.arg_num(&args, 11, 0.6)? as f32;
8513                let amp = self.arg_num(&args, 12, 0.25)? as f32;
8514                let freq = self.arg_num(&args, 13, 4.)? as f32;
8515                let fr = self.arg_num(&args, 14, 0.)? as f32;
8516                let hue = self.arg_num(&args, 15, 0.)? as f32;
8517                let mut gfx = self.gfx.borrow_mut();
8518                let cam = gfx.camera.clone();
8519                crate::gfx::vtex::draw_tessellated(
8520                    &mut gfx.depth_queue,
8521                    &cam,
8522                    cx,
8523                    cy,
8524                    cz,
8525                    ux,
8526                    uy,
8527                    uz,
8528                    vx,
8529                    vy,
8530                    vz,
8531                    cols,
8532                    rows,
8533                    cell,
8534                    amp,
8535                    freq,
8536                    fr,
8537                    hue,
8538                );
8539                return Ok(Value::Unit);
8540            },
8541
8542            // vtex_lotus(cx,cy,cz, ux,uy,uz, vx,vy,vz, r_inner,r_outer,n_petals, fr,hue)
8543            "vtex_lotus"
8544            | "ลายดอกบัว"
8545            | "纹莲"
8546            | "蓮模様"
8547            | "연꽃무늬"
8548            | "الگوی_لوتوس"
8549            | "نقش_لوتس"
8550            | "דוגמת_לוטוס"
8551            | "نقش_کنول"
8552            | "motif_lotus"
8553            | "muster_lotus"
8554            | "узор_лотос" => {
8555                let cx = self.arg_num(&args, 0, 0.)? as f32;
8556                let cy = self.arg_num(&args, 1, 0.)? as f32;
8557                let cz = self.arg_num(&args, 2, 0.)? as f32;
8558                let ux = self.arg_num(&args, 3, 1.)? as f32;
8559                let uy = self.arg_num(&args, 4, 0.)? as f32;
8560                let uz = self.arg_num(&args, 5, 0.)? as f32;
8561                let vx = self.arg_num(&args, 6, 0.)? as f32;
8562                let vy = self.arg_num(&args, 7, 0.)? as f32;
8563                let vz = self.arg_num(&args, 8, 1.)? as f32;
8564                let ri = self.arg_num(&args, 9, 1.)? as f32;
8565                let ro = self.arg_num(&args, 10, 2.)? as f32;
8566                let np = self.arg_num(&args, 11, 12.)? as usize;
8567                let fr = self.arg_num(&args, 12, 0.)? as f32;
8568                let hue = self.arg_num(&args, 13, 0.)? as f32;
8569                let mut gfx = self.gfx.borrow_mut();
8570                let cam = gfx.camera.clone();
8571                crate::gfx::vtex::draw_lotus(
8572                    &mut gfx.depth_queue,
8573                    &cam,
8574                    cx,
8575                    cy,
8576                    cz,
8577                    ux,
8578                    uy,
8579                    uz,
8580                    vx,
8581                    vy,
8582                    vz,
8583                    ri,
8584                    ro,
8585                    np,
8586                    fr,
8587                    hue,
8588                );
8589                return Ok(Value::Unit);
8590            },
8591
8592            // vtex_chakra(cx,cy,cz, ux,uy,uz, vx,vy,vz, r,n_spokes, fr,hue)
8593            "vtex_chakra"
8594            | "ลายจักร"
8595            | "纹轮"
8596            | "輪模様"
8597            | "바퀴무늬"
8598            | "الگوی_چاکرا"
8599            | "نقش_تشاكرا"
8600            | "דוגמת_צ'אקרה"
8601            | "نقش_چکر"
8602            | "motif_chakra"
8603            | "muster_chakra"
8604            | "узор_чакра" => {
8605                let cx = self.arg_num(&args, 0, 0.)? as f32;
8606                let cy = self.arg_num(&args, 1, 0.)? as f32;
8607                let cz = self.arg_num(&args, 2, 0.)? as f32;
8608                let ux = self.arg_num(&args, 3, 1.)? as f32;
8609                let uy = self.arg_num(&args, 4, 0.)? as f32;
8610                let uz = self.arg_num(&args, 5, 0.)? as f32;
8611                let vx = self.arg_num(&args, 6, 0.)? as f32;
8612                let vy = self.arg_num(&args, 7, 0.)? as f32;
8613                let vz = self.arg_num(&args, 8, 1.)? as f32;
8614                let r = self.arg_num(&args, 9, 2.)? as f32;
8615                let ns = self.arg_num(&args, 10, 8.)? as usize;
8616                let fr = self.arg_num(&args, 11, 0.)? as f32;
8617                let hue = self.arg_num(&args, 12, 0.)? as f32;
8618                let mut gfx = self.gfx.borrow_mut();
8619                let cam = gfx.camera.clone();
8620                crate::gfx::vtex::draw_chakra(
8621                    &mut gfx.depth_queue,
8622                    &cam,
8623                    cx,
8624                    cy,
8625                    cz,
8626                    ux,
8627                    uy,
8628                    uz,
8629                    vx,
8630                    vy,
8631                    vz,
8632                    r,
8633                    ns,
8634                    fr,
8635                    hue,
8636                );
8637                return Ok(Value::Unit);
8638            },
8639
8640            // vtex_yantra(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_layers,max_r, fr,hue)
8641            "vtex_yantra"
8642            | "ลายยันต์"
8643            | "纹咒"
8644            | "護符模様"
8645            | "부적무늬"
8646            | "الگوی_یانترا"
8647            | "نقش_يانترا"
8648            | "דוגמת_יאנטרה"
8649            | "نقش_ینترا"
8650            | "motif_yantra"
8651            | "muster_yantra"
8652            | "узор_янтра" => {
8653                let cx = self.arg_num(&args, 0, 0.)? as f32;
8654                let cy = self.arg_num(&args, 1, 0.)? as f32;
8655                let cz = self.arg_num(&args, 2, 0.)? as f32;
8656                let ux = self.arg_num(&args, 3, 1.)? as f32;
8657                let uy = self.arg_num(&args, 4, 0.)? as f32;
8658                let uz = self.arg_num(&args, 5, 0.)? as f32;
8659                let vx = self.arg_num(&args, 6, 0.)? as f32;
8660                let vy = self.arg_num(&args, 7, 0.)? as f32;
8661                let vz = self.arg_num(&args, 8, 1.)? as f32;
8662                let nl = self.arg_num(&args, 9, 4.)? as usize;
8663                let mr = self.arg_num(&args, 10, 3.)? as f32;
8664                let fr = self.arg_num(&args, 11, 0.)? as f32;
8665                let hue = self.arg_num(&args, 12, 0.)? as f32;
8666                let mut gfx = self.gfx.borrow_mut();
8667                let cam = gfx.camera.clone();
8668                crate::gfx::vtex::draw_yantra(
8669                    &mut gfx.depth_queue,
8670                    &cam,
8671                    cx,
8672                    cy,
8673                    cz,
8674                    ux,
8675                    uy,
8676                    uz,
8677                    vx,
8678                    vy,
8679                    vz,
8680                    nl,
8681                    mr,
8682                    fr,
8683                    hue,
8684                );
8685                return Ok(Value::Unit);
8686            },
8687
8688            // vtex_spiked_cog(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_teeth,r_body,r_spike,r_hub,n_spokes, fr,hue)
8689            "vtex_spiked_cog"
8690            | "ฟันเฟืองหนาม"
8691            | "纹棘轮"
8692            | "歯車模様"
8693            | "톱니바퀴"
8694            | "الگوی_چرخ‌دنده_خاردار"
8695            | "نقش_ترس_شائك"
8696            | "דוגמת_גלגל_קוצני"
8697            | "نقش_خاردار_گیئر"
8698            | "motif_engrenage_pointes"
8699            | "muster_stachelzahnrad"
8700            | "узор_шестерня_шипы" => {
8701                let cx = self.arg_num(&args, 0, 0.)? as f32;
8702                let cy = self.arg_num(&args, 1, 0.)? as f32;
8703                let cz = self.arg_num(&args, 2, 0.)? as f32;
8704                let ux = self.arg_num(&args, 3, 1.)? as f32;
8705                let uy = self.arg_num(&args, 4, 0.)? as f32;
8706                let uz = self.arg_num(&args, 5, 0.)? as f32;
8707                let vx = self.arg_num(&args, 6, 0.)? as f32;
8708                let vy = self.arg_num(&args, 7, 0.)? as f32;
8709                let vz = self.arg_num(&args, 8, 1.)? as f32;
8710                let nt = self.arg_num(&args, 9, 12.)? as usize;
8711                let rb = self.arg_num(&args, 10, 1.)? as f32;
8712                let rs = self.arg_num(&args, 11, 1.3)? as f32;
8713                let rh = self.arg_num(&args, 12, 0.2)? as f32;
8714                let ns = self.arg_num(&args, 13, 6.)? as usize;
8715                let fr = self.arg_num(&args, 14, 0.)? as f32;
8716                let hue = self.arg_num(&args, 15, 0.)? as f32;
8717                let mut gfx = self.gfx.borrow_mut();
8718                let cam = gfx.camera.clone();
8719                crate::gfx::vtex::draw_spiked_cog(
8720                    &mut gfx.depth_queue,
8721                    &cam,
8722                    cx,
8723                    cy,
8724                    cz,
8725                    ux,
8726                    uy,
8727                    uz,
8728                    vx,
8729                    vy,
8730                    vz,
8731                    nt,
8732                    rb,
8733                    rs,
8734                    rh,
8735                    ns,
8736                    fr,
8737                    hue,
8738                );
8739                return Ok(Value::Unit);
8740            },
8741
8742            // vtex_torii(cx,cy,cz, ux,uy,uz, vx,vy,vz, width,height, fr,hue)
8743            "vtex_torii"
8744            | "ประตูโทริอิ"
8745            | "纹鸟居"
8746            | "鳥居"
8747            | "도리이"
8748            | "الگوی_توری_ژاپنی"
8749            | "نقش_توري"
8750            | "דוגמת_טוריי"
8751            | "نقش_توری_گیٹ"
8752            | "motif_torii"
8753            | "muster_torii"
8754            | "узор_тории" => {
8755                let cx = self.arg_num(&args, 0, 0.)? as f32;
8756                let cy = self.arg_num(&args, 1, 0.)? as f32;
8757                let cz = self.arg_num(&args, 2, 0.)? as f32;
8758                let ux = self.arg_num(&args, 3, 1.)? as f32;
8759                let uy = self.arg_num(&args, 4, 0.)? as f32;
8760                let uz = self.arg_num(&args, 5, 0.)? as f32;
8761                let vx = self.arg_num(&args, 6, 0.)? as f32;
8762                let vy = self.arg_num(&args, 7, 0.)? as f32;
8763                let vz = self.arg_num(&args, 8, 1.)? as f32;
8764                let w = self.arg_num(&args, 9, 4.)? as f32;
8765                let h = self.arg_num(&args, 10, 5.)? as f32;
8766                let fr = self.arg_num(&args, 11, 0.)? as f32;
8767                let hue = self.arg_num(&args, 12, 0.)? as f32;
8768                let mut gfx = self.gfx.borrow_mut();
8769                let cam = gfx.camera.clone();
8770                crate::gfx::vtex::draw_torii(
8771                    &mut gfx.depth_queue,
8772                    &cam,
8773                    cx,
8774                    cy,
8775                    cz,
8776                    ux,
8777                    uy,
8778                    uz,
8779                    vx,
8780                    vy,
8781                    vz,
8782                    w,
8783                    h,
8784                    fr,
8785                    hue,
8786                );
8787                return Ok(Value::Unit);
8788            },
8789
8790            // vtex_pagoda(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_tiers,base_w,tier_h,taper,eave_out, fr,hue)
8791            "vtex_pagoda"
8792            | "เจดีย์"
8793            | "纹塔"
8794            | "塔"
8795            | "탑"
8796            | "الگوی_پاگودا"
8797            | "نقش_باغودا"
8798            | "דוגמת_פגודה"
8799            | "نقش_پگوڈا"
8800            | "motif_pagode"
8801            | "muster_pagode"
8802            | "узор_пагода" => {
8803                let cx = self.arg_num(&args, 0, 0.)? as f32;
8804                let cy = self.arg_num(&args, 1, 0.)? as f32;
8805                let cz = self.arg_num(&args, 2, 0.)? as f32;
8806                let ux = self.arg_num(&args, 3, 1.)? as f32;
8807                let uy = self.arg_num(&args, 4, 0.)? as f32;
8808                let uz = self.arg_num(&args, 5, 0.)? as f32;
8809                let vx = self.arg_num(&args, 6, 0.)? as f32;
8810                let vy = self.arg_num(&args, 7, 0.)? as f32;
8811                let vz = self.arg_num(&args, 8, 1.)? as f32;
8812                let nt = self.arg_num(&args, 9, 5.)? as usize;
8813                let bw = self.arg_num(&args, 10, 2.)? as f32;
8814                let th = self.arg_num(&args, 11, 1.)? as f32;
8815                let tp = self.arg_num(&args, 12, 0.72)? as f32;
8816                let eo = self.arg_num(&args, 13, 0.28)? as f32;
8817                let fr = self.arg_num(&args, 14, 0.)? as f32;
8818                let hue = self.arg_num(&args, 15, 0.)? as f32;
8819                let mut gfx = self.gfx.borrow_mut();
8820                let cam = gfx.camera.clone();
8821                crate::gfx::vtex::draw_pagoda(
8822                    &mut gfx.depth_queue,
8823                    &cam,
8824                    cx,
8825                    cy,
8826                    cz,
8827                    ux,
8828                    uy,
8829                    uz,
8830                    vx,
8831                    vy,
8832                    vz,
8833                    nt,
8834                    bw,
8835                    th,
8836                    tp,
8837                    eo,
8838                    fr,
8839                    hue,
8840                );
8841                return Ok(Value::Unit);
8842            },
8843
8844            // ══════════════════════════════════════════════════════════════════
8845            // AUDIO BUILTINS
8846            // ══════════════════════════════════════════════════════════════════
8847
8848            // audio_tone(idx, x, y, z, w, freq, amp, lfo_rate, lfo_depth)
8849            #[cfg(not(target_arch = "wasm32"))]
8850            "audio_tone"
8851            | "เสียงโทน"
8852            | "音调"
8853            | "音調"
8854            | "음조"
8855            | "空间音"
8856            | "空間音"
8857            | "공간음"
8858            | "تن_صدا"
8859            | "نغمة"
8860            | "צליל"
8861            | "آواز_کا_سر"
8862            | "tonalité_audio"
8863            | "audioton"
8864            | "звук_тон" => {
8865                let idx = self.arg_num(&args, 0, 0.0)? as usize;
8866                let x = self.arg_num(&args, 1, 0.0)? as f32;
8867                let y = self.arg_num(&args, 2, 0.0)? as f32;
8868                let z = self.arg_num(&args, 3, 0.0)? as f32;
8869                let w = self.arg_num(&args, 4, 1.0)? as f32;
8870                let freq = self.arg_num(&args, 5, 220.0)? as f32;
8871                let amp = self.arg_num(&args, 6, 0.15)? as f32;
8872                let lfo_rate = self.arg_num(&args, 7, 0.5)? as f32;
8873                let lfo_depth = self.arg_num(&args, 8, 0.02)? as f32;
8874                if let Some(audio) = &self.audio {
8875                    audio.set_tone(
8876                        idx,
8877                        ToneParams { x, y, z, w, freq, amp, lfo_rate, lfo_depth },
8878                    );
8879                }
8880                return Ok(Value::Unit);
8881            },
8882
8883            #[cfg(not(target_arch = "wasm32"))]
8884            "audio_listener"
8885            | "ผู้ฟัง"
8886            | "音频监听"
8887            | "音声リスナー"
8888            | "오디오리스너"
8889            | "شنونده_صدا"
8890            | "مستمع_الصوت"
8891            | "מאזין_קול"
8892            | "آواز_سننے_والا"
8893            | "auditeur_audio"
8894            | "audiohörer"
8895            | "звук_слушатель" => {
8896                let cry = self.arg_num(&args, 0, 1.0)? as f32;
8897                let sry = self.arg_num(&args, 1, 0.0)? as f32;
8898                let crx = self.arg_num(&args, 2, 1.0)? as f32;
8899                let srx = self.arg_num(&args, 3, 0.0)? as f32;
8900                if let Some(audio) = &self.audio {
8901                    audio.set_listener(cry, sry, crx, srx);
8902                }
8903                return Ok(Value::Unit);
8904            },
8905
8906            #[cfg(not(target_arch = "wasm32"))]
8907            "audio_bgm"
8908            | "เพลงพื้นหลัง"
8909            | "เพลงประกอบ"
8910            | "背景乐"
8911            | "BGM"
8912            | "배경음악"
8913            | "موسیقی_پس‌زمینه"
8914            | "موسيقى_خلفية"
8915            | "מוזיקת_רקע"
8916            | "پس_منظر_موسیقی"
8917            | "musique_fond"
8918            | "hintergrundmusik"
8919            | "фоновая_музыка" => {
8920                let path = match args.first() {
8921                    Some(Value::Str(s)) => s.clone(),
8922                    _ => return Ok(Value::Unit),
8923                };
8924                let vol = self.arg_num(&args, 1, 0.5)? as f32;
8925                if let Some(audio) = &self.audio {
8926                    audio.load_bgm(&path, vol);
8927                }
8928                return Ok(Value::Unit);
8929            },
8930
8931            #[cfg(not(target_arch = "wasm32"))]
8932            "audio_bgm_volume"
8933            | "ระดับเสียงพื้นหลัง"
8934            | "ระดับเพลงประกอบ"
8935            | "背景乐音量"
8936            | "BGM音量"
8937            | "배경음악음량"
8938            | "بلندی_موسیقی_پس‌زمینه"
8939            | "مستوى_موسيقى_الخلفية"
8940            | "עוצמת_מוזיקת_רקע"
8941            | "پس_منظر_موسیقی_شدت"
8942            | "volume_musique_fond"
8943            | "hintergrundmusiklautstärke"
8944            | "громкость_фоновой_музыки" => {
8945                let vol = self.arg_num(&args, 0, 0.5)? as f32;
8946                if let Some(audio) = &self.audio {
8947                    audio.set_bgm_volume(vol);
8948                }
8949                return Ok(Value::Unit);
8950            },
8951
8952            #[cfg(not(target_arch = "wasm32"))]
8953            "audio_volume"
8954            | "ระดับเสียง"
8955            | "音量"
8956            | "음량"
8957            | "بلندی_صدا"
8958            | "مستوى_الصوت"
8959            | "עוצמת_קול"
8960            | "آواز_کی_شدت"
8961            | "volume_audio"
8962            | "audiolautstärke"
8963            | "звук_громкость" => {
8964                let vol = self.arg_num(&args, 0, 0.7)? as f32;
8965                if let Some(audio) = &self.audio {
8966                    audio.set_master_volume(vol);
8967                }
8968                return Ok(Value::Unit);
8969            },
8970
8971            // WASM audio builtins — delegate to Web Audio API
8972            #[cfg(target_arch = "wasm32")]
8973            "audio_tone"
8974            | "เสียงโทน"
8975            | "音调"
8976            | "音調"
8977            | "음조"
8978            | "空间音"
8979            | "空間音"
8980            | "공간음"
8981            | "تن_صدا"
8982            | "نغمة"
8983            | "צליל"
8984            | "آواز_کا_سر"
8985            | "tonalité_audio"
8986            | "audioton"
8987            | "звук_тон" => {
8988                let idx = self.arg_num(&args, 0, 0.0)? as usize;
8989                let x = self.arg_num(&args, 1, 0.0)? as f32;
8990                let y = self.arg_num(&args, 2, 0.0)? as f32;
8991                let z = self.arg_num(&args, 3, 0.0)? as f32;
8992                let w = self.arg_num(&args, 4, 1.0)? as f32;
8993                let freq = self.arg_num(&args, 5, 220.0)? as f32;
8994                let amp = self.arg_num(&args, 6, 0.15)? as f32;
8995                let lfo_rate = self.arg_num(&args, 7, 0.5)? as f32;
8996                let lfo_depth = self.arg_num(&args, 8, 0.02)? as f32;
8997                crate::gfx::audio_web::set_tone(idx, x, y, z, w, freq, amp, lfo_rate, lfo_depth);
8998                return Ok(Value::Unit);
8999            },
9000
9001            #[cfg(target_arch = "wasm32")]
9002            "audio_listener"
9003            | "ผู้ฟัง"
9004            | "音频监听"
9005            | "音声リスナー"
9006            | "오디오리스너"
9007            | "شنونده_صدا"
9008            | "مستمع_الصوت"
9009            | "מאזין_קול"
9010            | "آواز_سننے_والا"
9011            | "auditeur_audio"
9012            | "audiohörer"
9013            | "звук_слушатель" => {
9014                let cry = self.arg_num(&args, 0, 1.0)? as f32;
9015                let sry = self.arg_num(&args, 1, 0.0)? as f32;
9016                let crx = self.arg_num(&args, 2, 1.0)? as f32;
9017                let srx = self.arg_num(&args, 3, 0.0)? as f32;
9018                crate::gfx::audio_web::set_listener(cry, sry, crx, srx);
9019                return Ok(Value::Unit);
9020            },
9021
9022            #[cfg(target_arch = "wasm32")]
9023            "audio_bgm"
9024            | "เพลงพื้นหลัง"
9025            | "เพลงประกอบ"
9026            | "背景乐"
9027            | "BGM"
9028            | "배경음악"
9029            | "موسیقی_پس‌زمینه"
9030            | "موسيقى_خلفية"
9031            | "מוזיקת_רקע"
9032            | "پس_منظر_موسیقی"
9033            | "musique_fond"
9034            | "hintergrundmusik"
9035            | "фоновая_музыка" => {
9036                let path = self.arg_str(&args, 0, "");
9037                let vol = self.arg_num(&args, 1, 0.5)? as f32;
9038                crate::gfx::audio_web::load_bgm(&path, vol);
9039                return Ok(Value::Unit);
9040            },
9041
9042            #[cfg(target_arch = "wasm32")]
9043            "audio_bgm_volume"
9044            | "ระดับเสียงพื้นหลัง"
9045            | "ระดับเพลงประกอบ"
9046            | "背景乐音量"
9047            | "BGM音量"
9048            | "배경음악음량"
9049            | "بلندی_موسیقی_پس‌زمینه"
9050            | "مستوى_موسيقى_الخلفية"
9051            | "עוצמת_מוזיקת_רקע"
9052            | "پس_منظر_موسیقی_شدت"
9053            | "volume_musique_fond"
9054            | "hintergrundmusiklautstärke"
9055            | "громкость_фоновой_музыки" => {
9056                let vol = self.arg_num(&args, 0, 0.5)? as f32;
9057                crate::gfx::audio_web::set_bgm_volume(vol);
9058                return Ok(Value::Unit);
9059            },
9060
9061            #[cfg(target_arch = "wasm32")]
9062            "audio_volume"
9063            | "ระดับเสียง"
9064            | "音量"
9065            | "음량"
9066            | "بلندی_صدا"
9067            | "مستوى_الصوت"
9068            | "עוצמת_קול"
9069            | "آواز_کی_شدت"
9070            | "volume_audio"
9071            | "audiolautstärke"
9072            | "звук_громкость" => {
9073                let vol = self.arg_num(&args, 0, 0.7)? as f32;
9074                crate::gfx::audio_web::set_master_volume(vol);
9075                return Ok(Value::Unit);
9076            },
9077
9078            // ── WASM sample load / play / stop / FX (Web Audio pool) ─────────
9079            #[cfg(target_arch = "wasm32")]
9080            "audio_sample_load"
9081            | "载入采样"
9082            | "サンプル読込"
9083            | "샘플로드"
9084            | "โหลดตัวอย่างเสียง"
9085            | "بارگذاری_نمونه_صدا"
9086            | "تحميل_عينة_صوتية"
9087            | "טעינת_דגימת_קול"
9088            | "آواز_نمونہ_لوڈ"
9089            | "charger_échantillon"
9090            | "sample_laden"
9091            | "загрузить_семпл" => {
9092                let path = self.arg_str(&args, 0, "");
9093                let resolved = self.wasm_resolve_source_path(&path);
9094                match wasm_fetch_bytes(&resolved)
9095                    .and_then(|bytes| ling_music::from_bytes(&bytes).map_err(|e| e.to_string()))
9096                {
9097                    Ok(t) => {
9098                        let id = crate::gfx::audio_web::add_sample(&t.stereo, t.channels, t.rate);
9099                        return Ok(Value::Number(id as f64));
9100                    },
9101                    Err(e) => {
9102                        eprintln!("audio_sample_load failed ({path}): {e}");
9103                        return Ok(Value::Number(-1.0));
9104                    },
9105                }
9106            },
9107            #[cfg(target_arch = "wasm32")]
9108            "audio_sample_play"
9109            | "播放采样"
9110            | "サンプル再生"
9111            | "샘플재생"
9112            | "เล่นตัวอย่างเสียง"
9113            | "پخش_نمونه_صدا"
9114            | "تشغيل_عينة_صوتية"
9115            | "נגינת_דגימת_קול"
9116            | "آواز_نمونہ_چلاؤ"
9117            | "jouer_échantillon"
9118            | "sample_abspielen"
9119            | "играть_семпл" => {
9120                let id = self.arg_num(&args, 0, 0.0)? as usize;
9121                let x = self.arg_num(&args, 1, 0.0)? as f32;
9122                let y = self.arg_num(&args, 2, 0.0)? as f32;
9123                let z = self.arg_num(&args, 3, 0.0)? as f32;
9124                // arg 4 is w (4th spatial dim) — ignored for 3-D panner
9125                let vol = self.arg_num(&args, 5, 1.0)? as f32;
9126                let looping = self.arg_num(&args, 6, 0.0)? > 0.5;
9127                crate::gfx::audio_web::play_sample(id, x, y, z, vol, looping);
9128                return Ok(Value::Number(0.0));
9129            },
9130            #[cfg(target_arch = "wasm32")]
9131            "audio_sample_stop"
9132            | "停止采样"
9133            | "サンプル停止"
9134            | "샘플정지"
9135            | "หยุดตัวอย่างเสียง"
9136            | "audio_fx_reverb"
9137            | "混响"
9138            | "リバーブ"
9139            | "리버브"
9140            | "เสียงก้อง"
9141            | "audio_fx_delay"
9142            | "回声"
9143            | "ディレイ効果"
9144            | "딜레이"
9145            | "เสียงสะท้อน"
9146            | "audio_fx_lowpass"
9147            | "低通滤波"
9148            | "ローパス"
9149            | "저역통과"
9150            | "กรองความถี่ต่ำ"
9151            | "توقف_نمونه_صدا"
9152            | "إيقاف_عينة_صوتية"
9153            | "עצירת_דגימת_קול"
9154            | "آواز_نمونہ_روکو"
9155            | "arrêter_échantillon"
9156            | "sample_stoppen"
9157            | "остановить_семпл" => {
9158                return Ok(Value::Unit);
9159            },
9160
9161            // ── รอหน้าต่าง() — block until window closed / Escape ──
9162            "รอหน้าต่าง"
9163            | "wait_window"
9164            | "gfx_wait"
9165            | "انتظار_پنجره"
9166            | "انتظر_النافذة"
9167            | "המתן_לחלון"
9168            | "ونڈو_انتظار" => {
9169                #[cfg(not(target_arch = "wasm32"))]
9170                loop {
9171                    let still_open = {
9172                        let gfx = self.gfx.borrow();
9173                        gfx.window
9174                            .as_ref()
9175                            .map(|w| w.is_open() && !w.is_key_down(minifb::Key::Escape))
9176                            .unwrap_or(false)
9177                    };
9178                    if !still_open {
9179                        break;
9180                    }
9181                    let (buf, w, h) = {
9182                        let gfx = self.gfx.borrow();
9183                        (gfx.buffer.clone(), gfx.width, gfx.height)
9184                    };
9185                    let mut gfx = self.gfx.borrow_mut();
9186                    if let Some(win) = gfx.window.as_mut() {
9187                        if win.update_with_buffer(&buf, w, h).is_err() {
9188                            break;
9189                        }
9190                    }
9191                }
9192                return Ok(Value::Unit);
9193            },
9194
9195            // ── File I/O ──────────────────────────────────────────────────────
9196            "read_file" | "อ่านไฟล์" | "خواندن_فایل" | "اقرأ_الملف" | "קרא_קובץ" | "فائل_پڑھو" =>
9197            {
9198                #[cfg(target_arch = "wasm32")]
9199                return Ok(Value::Str(String::new()));
9200                #[cfg(not(target_arch = "wasm32"))]
9201                {
9202                    let path = self.arg_str(&args, 0, "").replace('\\', "/");
9203                    return std::fs::read_to_string(&path)
9204                        .map(Value::Str)
9205                        .map_err(|e| EvalErr::from(format!("read_file '{path}': {e}")));
9206                }
9207            },
9208            // ── networking (TCP, 2-peer co-op) ───────────────────────────────
9209            #[cfg(not(target_arch = "wasm32"))]
9210            "net_host" | "เน็ตโฮสต์" | "میزبانی_شبکه" | "استضف_الشبكة" | "ארח_רשת" | "نیٹ_ہوسٹ" =>
9211            {
9212                let port = self.arg_num(&args, 0, 7777.0)? as u16;
9213                net::host(port);
9214                return Ok(Value::Unit);
9215            },
9216            #[cfg(not(target_arch = "wasm32"))]
9217            "net_join" | "เน็ตจอย" | "پیوستن_شبکه" | "انضم_للشبكة" | "הצטרף_לרשת" | "نیٹ_شمولیت" =>
9218            {
9219                let ip = self.arg_str(&args, 0, "127.0.0.1");
9220                let port = self.arg_num(&args, 1, 7777.0)? as u16;
9221                net::join(&ip, port);
9222                return Ok(Value::Unit);
9223            },
9224            #[cfg(not(target_arch = "wasm32"))]
9225            "net_send" | "เน็ตส่ง" | "ارسال_شبکه" | "أرسل_عبر_الشبكة" | "שלח_ברשת" | "نیٹ_بھیجو" =>
9226            {
9227                let s = self.arg_str(&args, 0, "");
9228                net::send(&s);
9229                return Ok(Value::Unit);
9230            },
9231            #[cfg(not(target_arch = "wasm32"))]
9232            "net_recv" | "เน็ตรับ" | "دریافت_شبکه" | "استقبل_من_الشبكة" | "קבל_מרשת" | "نیٹ_وصول" =>
9233            {
9234                return Ok(Value::Str(net::recv()));
9235            },
9236            #[cfg(not(target_arch = "wasm32"))]
9237            "net_status" | "เน็ตสถานะ" | "وضعیت_شبکه" | "حالة_الشبكة" | "סטטוס_רשת" | "نیٹ_حالت" =>
9238            {
9239                return Ok(Value::Number(net::status() as f64));
9240            },
9241            #[cfg(not(target_arch = "wasm32"))]
9242            "net_recv_from" => {
9243                return Ok(Value::Str(net::recv_from()));
9244            },
9245            #[cfg(not(target_arch = "wasm32"))]
9246            "net_send_to" => {
9247                let id = self.arg_num(&args, 0, 0.0)? as u64;
9248                let s = self.arg_str(&args, 1, "");
9249                net::send_to(id, &s);
9250                return Ok(Value::Unit);
9251            },
9252            #[cfg(not(target_arch = "wasm32"))]
9253            "net_close" | "연결종료" => {
9254                net::close();
9255                return Ok(Value::Unit);
9256            },
9257            #[cfg(not(target_arch = "wasm32"))]
9258            "net_clients" => {
9259                return Ok(Value::Str(net::clients()));
9260            },
9261            #[cfg(not(target_arch = "wasm32"))]
9262            "net_client_count" => {
9263                return Ok(Value::Number(net::client_count() as f64));
9264            },
9265            #[cfg(not(target_arch = "wasm32"))]
9266            "net_events" => {
9267                return Ok(Value::Str(net::events()));
9268            },
9269            // ── LAN lobby discovery (UDP broadcast) ──
9270            #[cfg(not(target_arch = "wasm32"))]
9271            "net_announce"
9272            | "เน็ตประกาศ"
9273            | "اعلام_شبکه"
9274            | "أعلن_في_الشبكة"
9275            | "הכרז_ברשת"
9276            | "نیٹ_اعلان" => {
9277                let port = self.arg_num(&args, 0, 7778.0)? as u16;
9278                let info = self.arg_str(&args, 1, "");
9279                net::announce(port, &info);
9280                return Ok(Value::Unit);
9281            },
9282            #[cfg(not(target_arch = "wasm32"))]
9283            "net_announce_stop"
9284            | "เน็ตหยุดประกาศ"
9285            | "توقف_اعلام"
9286            | "أوقف_الإعلان"
9287            | "עצור_הכרזה"
9288            | "اعلان_روکو" => {
9289                net::announce_stop();
9290                return Ok(Value::Unit);
9291            },
9292            #[cfg(not(target_arch = "wasm32"))]
9293            "net_discover" | "เน็ตค้นหา" | "کشف_شبکه" | "اكتشف_الشبكة" | "גלה_רשת" | "نیٹ_دریافت" =>
9294            {
9295                let port = self.arg_num(&args, 0, 7778.0)? as u16;
9296                return Ok(Value::Str(net::discover(port)));
9297            },
9298            #[cfg(not(target_arch = "wasm32"))]
9299            "net_test" | "เน็ตทดสอบ" | "آزمون_شبکه" | "اختبر_الشبكة" | "בדוק_רשת" | "نیٹ_ٹیسٹ" =>
9300            {
9301                let port = self.arg_num(&args, 0, 7777.0)? as u16;
9302                return Ok(Value::Str(net::test_bind(port)));
9303            },
9304            // ── SSH (real SSH2 client + server via `russh`; see runtime::ssh) ──
9305            #[cfg(not(target_arch = "wasm32"))]
9306            "ssh_config" => {
9307                let port = self.arg_num(&args, 0, 22.0)? as u16;
9308                let user = self.arg_str(&args, 1, "");
9309                let password = self.arg_str(&args, 2, "");
9310                let host_key_path = self.arg_str(&args, 3, "");
9311                ssh::configure(port, &user, &password, &host_key_path);
9312                return Ok(Value::Unit);
9313            },
9314            #[cfg(not(target_arch = "wasm32"))]
9315            "ssh_serve" => {
9316                return Ok(Value::Bool(ssh::serve()));
9317            },
9318            #[cfg(not(target_arch = "wasm32"))]
9319            "ssh_stop" => {
9320                ssh::stop();
9321                return Ok(Value::Unit);
9322            },
9323            #[cfg(not(target_arch = "wasm32"))]
9324            "ssh_serve_send" => {
9325                let id = self.arg_num(&args, 0, 0.0)? as u64;
9326                let s = self.arg_str(&args, 1, "");
9327                ssh::serve_send(id, &s);
9328                return Ok(Value::Unit);
9329            },
9330            #[cfg(not(target_arch = "wasm32"))]
9331            "ssh_serve_recv" => {
9332                return Ok(Value::Str(ssh::serve_recv()));
9333            },
9334            #[cfg(not(target_arch = "wasm32"))]
9335            "ssh_serve_exit" => {
9336                let id = self.arg_num(&args, 0, 0.0)? as u64;
9337                let code = self.arg_num(&args, 1, 0.0)? as u32;
9338                ssh::serve_exit(id, code);
9339                return Ok(Value::Unit);
9340            },
9341            #[cfg(not(target_arch = "wasm32"))]
9342            "ssh_serve_clients" => {
9343                return Ok(Value::Str(ssh::serve_clients()));
9344            },
9345            #[cfg(not(target_arch = "wasm32"))]
9346            "ssh_serve_client_count" => {
9347                return Ok(Value::Number(ssh::serve_client_count() as f64));
9348            },
9349            #[cfg(not(target_arch = "wasm32"))]
9350            "ssh_serve_events" => {
9351                return Ok(Value::Str(ssh::serve_events()));
9352            },
9353            #[cfg(not(target_arch = "wasm32"))]
9354            "ssh_serve_status" => {
9355                return Ok(Value::Number(ssh::serve_status() as f64));
9356            },
9357            #[cfg(not(target_arch = "wasm32"))]
9358            "ssh_connect" => {
9359                let host = self.arg_str(&args, 0, "");
9360                let port = self.arg_num(&args, 1, 22.0)? as u16;
9361                let user = self.arg_str(&args, 2, "");
9362                let password = self.arg_str(&args, 3, "");
9363                return Ok(Value::Number(ssh::connect(&host, port, &user, &password)));
9364            },
9365            #[cfg(not(target_arch = "wasm32"))]
9366            "ssh_exec" => {
9367                let cmd = self.arg_str(&args, 0, "");
9368                return Ok(Value::Str(ssh::exec(&cmd)));
9369            },
9370            #[cfg(not(target_arch = "wasm32"))]
9371            "ssh_exit_code" => {
9372                return Ok(Value::Number(ssh::exit_code()));
9373            },
9374            #[cfg(not(target_arch = "wasm32"))]
9375            "ssh_client_status" => {
9376                return Ok(Value::Number(ssh::client_status() as f64));
9377            },
9378            #[cfg(not(target_arch = "wasm32"))]
9379            "ssh_close" => {
9380                ssh::close();
9381                return Ok(Value::Unit);
9382            },
9383            // ── lingtp:// (post-quantum secure transport, see runtime::lingtp) ──
9384            // Client: fetch one resource, https-style but with an ML-KEM/
9385            // ML-DSA-87 handshake instead of TLS.
9386            #[cfg(not(target_arch = "wasm32"))]
9387            "lingtp_get" => {
9388                let host = self.arg_str(&args, 0, "");
9389                let port = self.arg_num(&args, 1, 7780.0)? as u16;
9390                let path = self.arg_str(&args, 2, "/");
9391                let (status, body) = lingtp::get(&host, port, &path);
9392                self.last_lingtp_status = status;
9393                return Ok(Value::Str(body));
9394            },
9395            #[cfg(not(target_arch = "wasm32"))]
9396            "lingtp_get_status" => {
9397                return Ok(Value::Number(self.last_lingtp_status as f64));
9398            },
9399            // Server: stage config (port + where to persist the host's
9400            // ML-DSA-87 identity key — generated once on first serve() and
9401            // reused after, so returning clients' TOFU pin stays meaningful).
9402            #[cfg(not(target_arch = "wasm32"))]
9403            "lingtp_config" => {
9404                let port = self.arg_num(&args, 0, 7780.0)? as u16;
9405                let host_key_path = self.arg_str(&args, 1, "");
9406                lingtp::configure(port, &host_key_path);
9407                return Ok(Value::Unit);
9408            },
9409            #[cfg(not(target_arch = "wasm32"))]
9410            "lingtp_route" => {
9411                let path = self.arg_str(&args, 0, "/");
9412                let handler = args.get(1).cloned().unwrap_or(Value::Unit);
9413                self.lingtp_routes.push((path, handler));
9414                return Ok(Value::Unit);
9415            },
9416            // Starts listening (per the staged lingtp_config) and blocks,
9417            // dispatching each request to whichever lingtp_route matches —
9418            // same shape as http_serve's loop, just polling lingtp::recv_request()
9419            // instead of draining a channel, since a TCP accept loop across
9420            // many raw std::thread connections (not a Receiver handed back
9421            // once) is what runtime::lingtp's server side hands us.
9422            #[cfg(not(target_arch = "wasm32"))]
9423            "lingtp_serve" => {
9424                if !lingtp::serve() {
9425                    return Ok(Value::Bool(false));
9426                }
9427                let routes = std::mem::take(&mut self.lingtp_routes);
9428                loop {
9429                    let Some(pending) = lingtp::recv_request() else {
9430                        if lingtp::status() == 0 {
9431                            break;
9432                        }
9433                        std::thread::sleep(std::time::Duration::from_millis(10));
9434                        continue;
9435                    };
9436                    let matched = routes.iter().find(|(p, _)| p == &pending.path);
9437                    let body = match matched {
9438                        Some((_, handler)) => match self.call_value(handler.clone(), vec![Value::Str(pending.path.clone())]) {
9439                            Ok(v) => v.to_string(),
9440                            Err(e) => format!("handler error: {e:?}"),
9441                        },
9442                        None => String::new(),
9443                    };
9444                    let _ = pending.respond_to.send(body);
9445                }
9446                return Ok(Value::Bool(true));
9447            },
9448            #[cfg(not(target_arch = "wasm32"))]
9449            "lingtp_stop" => {
9450                lingtp::stop();
9451                return Ok(Value::Unit);
9452            },
9453            #[cfg(not(target_arch = "wasm32"))]
9454            "lingtp_status" => {
9455                return Ok(Value::Number(lingtp::status() as f64));
9456            },
9457            #[cfg(not(target_arch = "wasm32"))]
9458            "lingtp_client_count" => {
9459                return Ok(Value::Number(lingtp::client_count() as f64));
9460            },
9461            #[cfg(not(target_arch = "wasm32"))]
9462            "lingtp_events" => {
9463                return Ok(Value::Str(lingtp::events()));
9464            },
9465            // ── HTTP server (interpreter <-> async bridge, see runtime::web) ──
9466            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9467            "http_route" | "เว็บเส้นทาง" | "مسیر_HTTP" | "مسار_HTTP" | "נתיב_HTTP" | "HTTP_روٹ" =>
9468            {
9469                let method = self.arg_str(&args, 0, "GET").to_uppercase();
9470                let path = self.arg_str(&args, 1, "/");
9471                let handler = args.get(2).cloned().unwrap_or(Value::Unit);
9472                self.http_routes.push((method, path, handler));
9473                return Ok(Value::Unit);
9474            },
9475            // Registers a directory to be served as raw bytes at `prefix` (fonts,
9476            // images, generated zips/PDFs) — bypasses the String-only Request/
9477            // Response bridge entirely, so binary files come through intact.
9478            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9479            "http_static"
9480            | "เว็บสแตติก"
9481            | "فایل_ایستای_HTTP"
9482            | "ملفات_HTTP_ثابتة"
9483            | "קבצים_סטטיים_HTTP"
9484            | "HTTP_مستقل_فائل" => {
9485                let prefix = self.arg_str(&args, 0, "/static");
9486                let dir = self.arg_str(&args, 1, "static");
9487                self.http_static_dirs.push((prefix, dir));
9488                return Ok(Value::Unit);
9489            },
9490            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9491            "http_serve" | "เว็บเสิร์ฟ" | "سرویس_HTTP" | "قدّم_HTTP" | "הגש_HTTP" | "HTTP_سرو" =>
9492            {
9493                let host = self.arg_str(&args, 0, "127.0.0.1");
9494                let port = self.arg_num(&args, 1, 8080.0)? as u16;
9495                let routes = std::mem::take(&mut self.http_routes);
9496                let static_dirs = std::mem::take(&mut self.http_static_dirs);
9497                // No premature "listening" print here: ling_http::serve_http
9498                // (called from spawn_server's background thread) now prints
9499                // its own banner, but only after the socket is actually
9500                // bound — a more honest signal than printing right after
9501                // requesting the background thread be spawned.
9502                let rx = web::spawn_server(host.clone(), port, static_dirs, web::TlsMode::Plain);
9503                return self.drain_http_requests(rx, routes);
9504            },
9505            // Real HTTPS: terminates TLS itself using a cert/key PEM pair
9506            // already on disk (e.g. from Let's Encrypt) — no reverse proxy
9507            // needed. Same route-registration/dispatch as http_serve, only
9508            // the transport underneath differs.
9509            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9510            "http_serve_tls" => {
9511                let host = self.arg_str(&args, 0, "127.0.0.1");
9512                let port = self.arg_num(&args, 1, 8080.0)? as u16;
9513                let cert_pem = self.arg_str(&args, 2, "");
9514                let key_pem = self.arg_str(&args, 3, "");
9515                let routes = std::mem::take(&mut self.http_routes);
9516                let static_dirs = std::mem::take(&mut self.http_static_dirs);
9517                let rx = web::spawn_server(
9518                    host.clone(),
9519                    port,
9520                    static_dirs,
9521                    web::TlsMode::Certs { cert_pem, key_pem },
9522                );
9523                return self.drain_http_requests(rx, routes);
9524            },
9525            // HTTPS with a throwaway self-signed cert generated at startup —
9526            // local development only; browsers/curl reject it as untrusted
9527            // (curl -k, or a script's own TOFU-style pinning, to proceed
9528            // anyway). Never use this for a public-facing service.
9529            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9530            "http_serve_dev_tls" => {
9531                let host = self.arg_str(&args, 0, "127.0.0.1");
9532                let port = self.arg_num(&args, 1, 8080.0)? as u16;
9533                let routes = std::mem::take(&mut self.http_routes);
9534                let static_dirs = std::mem::take(&mut self.http_static_dirs);
9535                let rx = web::spawn_server(host.clone(), port, static_dirs, web::TlsMode::Dev);
9536                return self.drain_http_requests(rx, routes);
9537            },
9538            // Fires a POST request on a background async runtime and returns a job
9539            // Retrieves `url` (http:// or https://, TLS handled transparently
9540            // by reqwest) and blocks until the body is in hand — the
9541            // "fetch a page" half of a browser-shaped script, paired with
9542            // http_get_status() for the response code. See get_blocking's
9543            // doc comment for why blocking is the right call here (unlike
9544            // http_serve's handlers, there's no other visitor to stall).
9545            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9546            "http_get" | "เว็บรับ" | "دریافت_HTTP" | "اجلب_HTTP" | "קבל_HTTP" | "HTTP_حاصل_کرو" => {
9547                let url = self.arg_str(&args, 0, "");
9548                let (status, body) = web::get_blocking(&url);
9549                self.last_http_status = status;
9550                return Ok(Value::Str(body));
9551            },
9552            // HTTP status of the last http_get call (0 if it never got a
9553            // response at all — DNS/connect/TLS failure, distinct from a
9554            // real 4xx/5xx).
9555            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9556            "http_get_status" => {
9557                return Ok(Value::Number(self.last_http_status as f64));
9558            },
9559            // Fires a POST request on a background async runtime and returns a job
9560            // id immediately — for slow external calls (e.g. local Stable Diffusion
9561            // generation) that must not block http_serve's single-threaded loop.
9562            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9563            "http_post_async"
9564            | "เว็บโพสต์ไม่บล็อก"
9565            | "ارسال_ناهمگام_HTTP"
9566            | "أرسل_HTTP_غير_متزامن"
9567            | "שלח_HTTP_אסינכרוני"
9568            | "HTTP_غیر_ہمزمان_بھیجو" => {
9569                let url = self.arg_str(&args, 0, "");
9570                let body = self.arg_str(&args, 1, "");
9571                let content_type = self.arg_str(&args, 2, "application/json");
9572                let id = self.async_jobs.start_post(url, content_type, body);
9573                return Ok(Value::Str(id));
9574            },
9575            // Non-blocking poll: "" while the job named by http_post_async (or
9576            // sdai_generate_start) is still running, the result once it
9577            // completes — same job table, same builtin polls both.
9578            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9579            "http_job_poll"
9580            | "เว็บงานสำรวจ"
9581            | "بررسی_وظیفه_HTTP"
9582            | "استطلع_مهمة_HTTP"
9583            | "בדוק_משימת_HTTP"
9584            | "HTTP_کام_پول" => {
9585                let id = self.arg_str(&args, 0, "");
9586                return Ok(Value::Str(self.async_jobs.poll(&id).unwrap_or_default()));
9587            },
9588            // Starts an AUTOMATIC1111-compatible txt2img generation in the
9589            // background against `base_url` (e.g. "http://127.0.0.1:1342").
9590            // Poll with http_job_poll: the result is the plain base64 PNG
9591            // once ready, or a string starting with "ERROR:" on failure —
9592            // the JSON response itself is parsed in Rust (see
9593            // AsyncJobs::start_sdai_txt2img), since `.ling` has no JSON parser.
9594            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9595            "sdai_generate_start"
9596            | "เอสดีเอไอเริ่มสร้าง"
9597            | "شروع_تولید_هوش"
9598            | "ابدأ_توليد_الذكاء"
9599            | "התחל_יצירת_בינה"
9600            | "اے_آئی_تخلیق_شروع" => {
9601                let base_url = self.arg_str(&args, 0, "http://127.0.0.1:1342");
9602                let prompt = self.arg_str(&args, 1, "");
9603                let width = self.arg_num(&args, 2, 512.0)? as u32;
9604                let height = self.arg_num(&args, 3, 512.0)? as u32;
9605                let id = self
9606                    .async_jobs
9607                    .start_sdai_txt2img(base_url, prompt, width, height);
9608                return Ok(Value::Str(id));
9609            },
9610            // ── query_param("q=a&page=2", "q", "") → "a" (URL-decoded) ──
9611            "query_param"
9612            | "พารามิเตอร์"
9613            | "پارامتر_پرسوجو"
9614            | "معامل_الاستعلام"
9615            | "פרמטר_שאילתה"
9616            | "کوئری_پیرامیٹر" => {
9617                let qs = self.arg_str(&args, 0, "");
9618                let name = self.arg_str(&args, 1, "");
9619                let default = self.arg_str(&args, 2, "");
9620                let mut found = default;
9621                for pair in qs.split('&') {
9622                    let mut it = pair.splitn(2, '=');
9623                    if it.next().unwrap_or("") == name {
9624                        found = url_decode(it.next().unwrap_or(""));
9625                        break;
9626                    }
9627                }
9628                return Ok(Value::Str(found));
9629            },
9630            // ── cookie_get("sid=abc; x=1", "sid", "") → "abc" ──
9631            "cookie_get"
9632            | "รับคุกกี้"
9633            | "دریافت_کوکی"
9634            | "اجلب_الكعكة"
9635            | "קבל_עוגייה"
9636            | "کوکی_حاصل_کرو" => {
9637                let header = self.arg_str(&args, 0, "");
9638                let name = self.arg_str(&args, 1, "");
9639                let default = self.arg_str(&args, 2, "");
9640                let mut found = default;
9641                for pair in header.split(';') {
9642                    let p = pair.trim();
9643                    let mut it = p.splitn(2, '=');
9644                    if it.next().unwrap_or("") == name {
9645                        found = it.next().unwrap_or("").to_string();
9646                        break;
9647                    }
9648                }
9649                return Ok(Value::Str(found));
9650            },
9651            // ── html_escape(s) — & < > " ' → entities, for echoing user input ──
9652            "html_escape"
9653            | "กันเอชทีเอ็มแอล"
9654            | "فرار_HTML"
9655            | "أفلت_HTML"
9656            | "בריחת_HTML"
9657            | "HTML_ایسکیپ" => {
9658                let s = self.arg_str(&args, 0, "");
9659                return Ok(Value::Str(
9660                    s.replace('&', "&amp;")
9661                        .replace('<', "&lt;")
9662                        .replace('>', "&gt;")
9663                        .replace('"', "&quot;")
9664                        .replace('\'', "&#39;"),
9665                ));
9666            },
9667            // json_escape(s) — escape a string for embedding inside a JSON
9668            // string literal (", \, and control chars). Needed because the
9669            // registry builds JSON API responses by concatenation; without
9670            // this a value containing " or \ breaks or injects into the JSON.
9671            "json_escape" | "หนีเจสัน" | "فرار_JSON" | "أفلت_JSON" | "בריחת_JSON" | "JSON_ایسکیپ" =>
9672            {
9673                let s = self.arg_str(&args, 0, "");
9674                let mut out = String::with_capacity(s.len() + 8);
9675                for c in s.chars() {
9676                    match c {
9677                        '"' => out.push_str("\\\""),
9678                        '\\' => out.push_str("\\\\"),
9679                        '\n' => out.push_str("\\n"),
9680                        '\r' => out.push_str("\\r"),
9681                        '\t' => out.push_str("\\t"),
9682                        c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
9683                        c => out.push(c),
9684                    }
9685                }
9686                return Ok(Value::Str(out));
9687            },
9688            // ── CLI arguments: cli_arg("port", "8080") reads `--port 6688` ──
9689            "cli_arg"
9690            | "อาร์กิวเมนต์"
9691            | "آرگومان_خط‌فرمان"
9692            | "معامل_سطر_الأوامر"
9693            | "ארגומנט_שורת_פקודה"
9694            | "سی_ایل_آئی_دلیل" => {
9695                let name = self.arg_str(&args, 0, "");
9696                let default = self.arg_str(&args, 1, "");
9697                let flag = format!("--{name}");
9698                let argv: Vec<String> = std::env::args().collect();
9699                let found = argv
9700                    .iter()
9701                    .position(|a| a == &flag)
9702                    .and_then(|i| argv.get(i + 1))
9703                    .cloned()
9704                    .unwrap_or(default);
9705                return Ok(Value::Str(found));
9706            },
9707            // ── SQLite (rusqlite, synchronous — matches the interpreter) ──
9708            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9709            "db_open"
9710            | "ฐานข้อมูลเปิด"
9711            | "باز_کردن_پایگاه_داده"
9712            | "افتح_قاعدة_البيانات"
9713            | "פתח_מסד_נתונים"
9714            | "ڈیٹا_بیس_کھولو" => {
9715                let path = self.arg_str(&args, 0, "app.db");
9716                let conn = ling_http::rusqlite::Connection::open(&path)
9717                    .map_err(|e| EvalErr::from(format!("db_open '{path}': {e}")))?;
9718                let _ = conn.execute_batch("PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL;");
9719                self.db = Some(conn);
9720                return Ok(Value::Unit);
9721            },
9722            // db_exec(sql, ...params) → rows affected. Params bind positionally
9723            // (?1, ?2, ...): numbers as REAL, bools as 0/1, everything else TEXT.
9724            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9725            "db_exec"
9726            | "ฐานข้อมูลรัน"
9727            | "اجرای_پایگاه_داده"
9728            | "نفّذ_في_قاعدة_البيانات"
9729            | "בצע_במסד_נתונים"
9730            | "ڈیٹا_بیس_عمل" => {
9731                let sql = self.arg_str(&args, 0, "");
9732                let params = values_to_sql_params(&args[1.min(args.len())..]);
9733                let conn = self
9734                    .db
9735                    .as_ref()
9736                    .ok_or_else(|| EvalErr::from("db_exec: call db_open first".to_string()))?;
9737                let n = conn
9738                    .execute(&sql, ling_http::rusqlite::params_from_iter(params.iter()))
9739                    .map_err(|e| EvalErr::from(format!("db_exec: {e}\n  sql: {sql}")))?;
9740                return Ok(Value::Number(n as f64));
9741            },
9742            // db_query(sql, ...params) → List of Row structs (row.column_name).
9743            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9744            "db_query"
9745            | "ฐานข้อมูลถาม"
9746            | "پرسوجوی_پایگاه_داده"
9747            | "استعلم_قاعدة_البيانات"
9748            | "שאילתת_מסד_נתונים"
9749            | "ڈیٹا_بیس_سوال" => {
9750                let sql = self.arg_str(&args, 0, "");
9751                let params = values_to_sql_params(&args[1.min(args.len())..]);
9752                let conn = self
9753                    .db
9754                    .as_ref()
9755                    .ok_or_else(|| EvalErr::from("db_query: call db_open first".to_string()))?;
9756                let mut stmt = conn
9757                    .prepare(&sql)
9758                    .map_err(|e| EvalErr::from(format!("db_query: {e}\n  sql: {sql}")))?;
9759                let col_names: Vec<String> =
9760                    stmt.column_names().iter().map(|s| s.to_string()).collect();
9761                let mut rows = stmt
9762                    .query(ling_http::rusqlite::params_from_iter(params.iter()))
9763                    .map_err(|e| EvalErr::from(format!("db_query: {e}")))?;
9764                let mut out = Vec::new();
9765                while let Some(row) = rows
9766                    .next()
9767                    .map_err(|e| EvalErr::from(format!("db_query row: {e}")))?
9768                {
9769                    let mut fields = Vec::with_capacity(col_names.len());
9770                    for (i, col) in col_names.iter().enumerate() {
9771                        use ling_http::rusqlite::types::ValueRef;
9772                        let v = match row.get_ref(i) {
9773                            Ok(ValueRef::Null) => Value::Str(String::new()),
9774                            Ok(ValueRef::Integer(n)) => Value::Number(n as f64),
9775                            Ok(ValueRef::Real(n)) => Value::Number(n),
9776                            Ok(ValueRef::Text(t)) => {
9777                                Value::Str(String::from_utf8_lossy(t).into_owned())
9778                            },
9779                            Ok(ValueRef::Blob(b)) => {
9780                                use base64::Engine as _;
9781                                Value::Str(base64::engine::general_purpose::STANDARD.encode(b))
9782                            },
9783                            Err(_) => Value::Str(String::new()),
9784                        };
9785                        fields.push((col.clone(), v));
9786                    }
9787                    out.push(Value::Struct { name: "Row".to_string(), fields });
9788                }
9789                return Ok(Value::List(Rc::new(out)));
9790            },
9791            // ── gamepad (gilrs) ──
9792            #[cfg(not(target_arch = "wasm32"))]
9793            "gamepad_poll"
9794            | "จอยโพล"
9795            | "بررسی_دسته_بازی"
9796            | "استطلع_يد_اللعب"
9797            | "בדוק_בקר_משחק"
9798            | "گیم_پیڈ_پول_کرو" => {
9799                gamepad::poll();
9800                return Ok(Value::Unit);
9801            },
9802            #[cfg(not(target_arch = "wasm32"))]
9803            "gamepad_button"
9804            | "จอยปุ่ม"
9805            | "دکمه_دسته_بازی"
9806            | "زر_يد_اللعب"
9807            | "כפתור_בקר_משחק"
9808            | "گیم_پیڈ_بٹن_دبایا" => {
9809                let name = self.arg_str(&args, 0, "");
9810                return Ok(Value::Number(if gamepad::button(&name) {
9811                    1.0
9812                } else {
9813                    0.0
9814                }));
9815            },
9816            #[cfg(not(target_arch = "wasm32"))]
9817            "gamepad_axis"
9818            | "จอยแกน"
9819            | "محور_دسته_بازی"
9820            | "محور_يد_اللعب"
9821            | "ציר_בקר_משחק"
9822            | "گیم_پیڈ_محور" => {
9823                let name = self.arg_str(&args, 0, "");
9824                return Ok(Value::Number(gamepad::axis(&name) as f64));
9825            },
9826            #[cfg(not(target_arch = "wasm32"))]
9827            "gamepad_rumble"
9828            | "จอยสั่น"
9829            | "لرزش_دسته_بازی"
9830            | "اهتزاز_يد_اللعب"
9831            | "רטט_בקר_משחק"
9832            | "گیم_پیڈ_لرزش" => {
9833                let low = self.arg_num(&args, 0, 0.0)? as f32;
9834                let high = self.arg_num(&args, 1, 0.0)? as f32;
9835                let ms = self.arg_num(&args, 2, 200.0)? as u32;
9836                gamepad::rumble(low, high, ms);
9837                return Ok(Value::Unit);
9838            },
9839            #[cfg(not(target_arch = "wasm32"))]
9840            "gamepad_list"
9841            | "จอยรายการ"
9842            | "فهرست_دسته‌های_بازی"
9843            | "قائمة_أيدي_اللعب"
9844            | "רשימת_בקרי_משחק"
9845            | "گیم_پیڈ_فہرست" => {
9846                return Ok(Value::Str(gamepad::list()));
9847            },
9848            #[cfg(not(target_arch = "wasm32"))]
9849            "gamepad_any"
9850            | "จอยใดๆ"
9851            | "هر_دسته_بازی"
9852            | "أي_يد_لعب"
9853            | "בקר_כלשהו"
9854            | "کوئی_بھی_گیم_پیڈ" => {
9855                return Ok(Value::Number(if gamepad::any_button() { 1.0 } else { 0.0 }));
9856            },
9857            // wasm32: gamepad not available — return safe no-op values
9858            #[cfg(target_arch = "wasm32")]
9859            "gamepad_poll"
9860            | "จอยโพล"
9861            | "gamepad_rumble"
9862            | "จอยสั่น"
9863            | "بررسی_دسته_بازی"
9864            | "استطلع_يد_اللعب"
9865            | "בדוק_בקר_משחק"
9866            | "گیم_پیڈ_پول_کرو" => {
9867                return Ok(Value::Unit);
9868            },
9869            #[cfg(target_arch = "wasm32")]
9870            "gamepad_button"
9871            | "จอยปุ่ม"
9872            | "gamepad_axis"
9873            | "จอยแกน"
9874            | "gamepad_any"
9875            | "จอยใดๆ"
9876            | "دکمه_دسته_بازی"
9877            | "زر_يد_اللعب"
9878            | "כפתור_בקר_משחק"
9879            | "گیم_پیڈ_بٹن" => {
9880                return Ok(Value::Number(0.0));
9881            },
9882            #[cfg(target_arch = "wasm32")]
9883            "gamepad_list"
9884            | "จอยรายการ"
9885            | "فهرست_دسته‌های_بازی"
9886            | "قائمة_أيدي_اللعب"
9887            | "רשימת_בקרי_משחק"
9888            | "گیم_پیڈ_فہرست" => {
9889                return Ok(Value::Str(String::new()));
9890            },
9891
9892            // ── game AI: neural networks ─────────────────────────────────────
9893            // nn_new(inputs[, seed]) → handle
9894            #[cfg(not(target_arch = "wasm32"))]
9895            "nn_new"
9896            | "建神经网"
9897            | "ニューラル作成"
9898            | "신경망생성"
9899            | "สร้างโครงข่าย"
9900            | "شبکه_جدید"
9901            | "شبكة_جديدة"
9902            | "רשת_חדשה"
9903            | "نئی_نیورل_نیٹ"
9904            | "nouveau_réseau"
9905            | "neues_netz"
9906            | "новая_сеть" => {
9907                let n_in = self.arg_num(&args, 0, 1.0)?.max(0.0) as usize;
9908                let seed = self.arg_num(&args, 1, 1.0)? as u64;
9909                return Ok(Value::Number(ai::nn_new(n_in, seed) as f64));
9910            },
9911            // nn_dense(handle, units[, activation]) — append a layer
9912            #[cfg(not(target_arch = "wasm32"))]
9913            "nn_dense"
9914            | "密集层"
9915            | "密層追加"
9916            | "밀집층"
9917            | "ชั้นหนาแน่น"
9918            | "لایه_متراکم"
9919            | "طبقة_كثيفة"
9920            | "שכבה_צפופה"
9921            | "ڈینس_لیئر"
9922            | "réseau_dense"
9923            | "netz_dicht"
9924            | "плотная_сеть" => {
9925                let id = self.arg_num(&args, 0, -1.0)? as i64;
9926                let units = self.arg_num(&args, 1, 1.0)?.max(1.0) as usize;
9927                let act = self.arg_str(&args, 2, "relu");
9928                ai::nn_dense(id, units, &act);
9929                return Ok(Value::Unit);
9930            },
9931            // nn_forward(handle, [inputs]) → [outputs]
9932            #[cfg(not(target_arch = "wasm32"))]
9933            "nn_forward"
9934            | "神经前向"
9935            | "順伝播"
9936            | "순전파"
9937            | "ส่งต่อโครงข่าย"
9938            | "پیش‌روی_شبکه"
9939            | "تمرير_أمامي"
9940            | "העברה_קדימה"
9941            | "فارورڈ_پاس"
9942            | "propager_réseau"
9943            | "netz_vorwärts"
9944            | "прямой_проход_сети" => {
9945                let id = self.arg_num(&args, 0, -1.0)? as i64;
9946                let input = self.arg_list_f32(&args, 1);
9947                let out = ai::nn_forward(id, &input);
9948                return Ok(Value::List(Rc::new(
9949                    out.into_iter().map(|v| Value::Number(v as f64)).collect(),
9950                )));
9951            },
9952            // nn_train(handle, [inputs], [targets][, lr]) → loss
9953            #[cfg(not(target_arch = "wasm32"))]
9954            "nn_train"
9955            | "训练网"
9956            | "ニューラル学習"
9957            | "신경망학습"
9958            | "ฝึกโครงข่าย"
9959            | "آموزش_شبکه"
9960            | "درّب_الشبكة"
9961            | "אמן_רשת"
9962            | "نیٹ_ٹریننگ"
9963            | "entraîner_réseau"
9964            | "netz_trainieren"
9965            | "обучить_сеть" => {
9966                let id = self.arg_num(&args, 0, -1.0)? as i64;
9967                let input = self.arg_list_f32(&args, 1);
9968                let target = self.arg_list_f32(&args, 2);
9969                let lr = self.arg_num(&args, 3, 0.01)? as f32;
9970                return Ok(Value::Number(ai::nn_train(id, &input, &target, lr) as f64));
9971            },
9972            // nn_save(handle, path) → bool
9973            #[cfg(not(target_arch = "wasm32"))]
9974            "nn_save"
9975            | "保存网"
9976            | "網保存"
9977            | "신경망저장"
9978            | "บันทึกโครงข่าย"
9979            | "ذخیره_شبکه"
9980            | "احفظ_الشبكة"
9981            | "שמור_רשת"
9982            | "نیٹ_محفوظ_کرو"
9983            | "sauvegarder_réseau"
9984            | "netz_speichern"
9985            | "сохранить_сеть" => {
9986                let id = self.arg_num(&args, 0, -1.0)? as i64;
9987                let path = self.arg_str(&args, 1, "model.lnn");
9988                return Ok(Value::Bool(ai::nn_save(id, &path)));
9989            },
9990            // nn_load(path) → handle (-1 on failure)
9991            #[cfg(not(target_arch = "wasm32"))]
9992            "nn_load"
9993            | "载入网"
9994            | "網読込"
9995            | "신경망불러오기"
9996            | "โหลดโครงข่าย"
9997            | "بارگذاری_شبکه"
9998            | "حمّل_الشبكة"
9999            | "טען_רשת"
10000            | "نیٹ_لوڈ"
10001            | "charger_réseau"
10002            | "netz_laden"
10003            | "загрузить_сеть" => {
10004                let path = self.arg_str(&args, 0, "model.lnn");
10005                return Ok(Value::Number(ai::nn_load(&path) as f64));
10006            },
10007
10008            // ── game AI: behavior trees ──────────────────────────────────────
10009            // bt_build(dsl_string) → handle
10010            #[cfg(not(target_arch = "wasm32"))]
10011            "bt_build"
10012            | "建行为树"
10013            | "行動木構築"
10014            | "행동트리구성"
10015            | "สร้างต้นไม้พฤติกรรม"
10016            | "ساخت_درخت_رفتار"
10017            | "ابنِ_شجرة_السلوك"
10018            | "בנה_עץ_התנהגות"
10019            | "بی_ٹی_تعمیر"
10020            | "construire_arbre_comportement"
10021            | "verhaltensbaum_bauen"
10022            | "построить_дерево_поведения" => {
10023                let spec = self.arg_str(&args, 0, "");
10024                return Ok(Value::Number(ai::bt_build(&spec) as f64));
10025            },
10026            // bt_set(handle, key, value) — set a blackboard fact
10027            #[cfg(not(target_arch = "wasm32"))]
10028            "bt_set"
10029            | "设事实"
10030            | "事実設定"
10031            | "사실설정"
10032            | "ตั้งข้อเท็จจริง"
10033            | "تنظیم_واقعیت"
10034            | "عيّن_حقيقة"
10035            | "קבע_עובדה"
10036            | "بی_ٹی_سیٹ"
10037            | "définir_arbre_comportement"
10038            | "verhaltensbaum_setzen"
10039            | "задать_дерево_поведения" => {
10040                let id = self.arg_num(&args, 0, -1.0)? as i64;
10041                let key = self.arg_str(&args, 1, "");
10042                let val = self.arg_num(&args, 2, 0.0)? as f32;
10043                ai::bt_set(id, &key, val);
10044                return Ok(Value::Unit);
10045            },
10046            // bt_tick(handle) → chosen action name ("" if none)
10047            #[cfg(not(target_arch = "wasm32"))]
10048            "bt_tick"
10049            | "行为树滴答"
10050            | "行動木更新"
10051            | "행동트리틱"
10052            | "เดินต้นไม้พฤติกรรม"
10053            | "تیک_درخت_رفتار"
10054            | "نبضة_شجرة_السلوك"
10055            | "טיק_עץ_התנהגות"
10056            | "بی_ٹی_ٹک"
10057            | "tick_arbre_comportement"
10058            | "verhaltensbaum_tick"
10059            | "тик_дерева_поведения" => {
10060                let id = self.arg_num(&args, 0, -1.0)? as i64;
10061                return Ok(Value::Str(ai::bt_tick(id)));
10062            },
10063            // bt_status(handle) → 0 fail / 1 success / 2 running
10064            #[cfg(not(target_arch = "wasm32"))]
10065            "bt_status"
10066            | "行为树状态"
10067            | "行動木状態"
10068            | "행동트리상태"
10069            | "สถานะต้นไม้พฤติกรรม"
10070            | "وضعیت_درخت_رفتار"
10071            | "حالة_شجرة_السلوك"
10072            | "סטטוס_עץ_התנהגות"
10073            | "بی_ٹی_حالت"
10074            | "statut_arbre_comportement"
10075            | "verhaltensbaum_status"
10076            | "статус_дерева_поведения" => {
10077                let id = self.arg_num(&args, 0, -1.0)? as i64;
10078                return Ok(Value::Number(ai::bt_status(id) as f64));
10079            },
10080
10081            // ── game AI: miniature dialog LLM ────────────────────────────────
10082            // dialog_new([ctx, embed, hidden, seed]) → handle
10083            #[cfg(not(target_arch = "wasm32"))]
10084            "dialog_new"
10085            | "建对话模型"
10086            | "対話モデル作成"
10087            | "대화모델생성"
10088            | "สร้างโมเดลสนทนา"
10089            | "مدل_گفتگوی_جدید"
10090            | "نموذج_حوار_جديد"
10091            | "מודל_דיאלוג_חדש"
10092            | "نیا_مکالمہ_ماڈل"
10093            | "nouveau_dialogue"
10094            | "neuer_dialog"
10095            | "новый_диалог" => {
10096                let ctx = self.arg_num(&args, 0, 3.0)?.max(1.0) as usize;
10097                let embed = self.arg_num(&args, 1, 32.0)?.max(1.0) as usize;
10098                let hidden = self.arg_num(&args, 2, 64.0)?.max(1.0) as usize;
10099                let seed = self.arg_num(&args, 3, 1.0)? as u64;
10100                return Ok(Value::Number(
10101                    ai::dialog_new(ctx, embed, hidden, seed) as f64
10102                ));
10103            },
10104            // dialog_learn(handle, text) — add one utterance to the corpus
10105            #[cfg(not(target_arch = "wasm32"))]
10106            "dialog_learn"
10107            | "对话学习"
10108            | "対話学習"
10109            | "대화학습"
10110            | "เรียนรู้สนทนา"
10111            | "یادگیری_گفتگو"
10112            | "تعلّم_الحوار"
10113            | "למד_דיאלוג"
10114            | "مکالمہ_سیکھو"
10115            | "apprendre_dialogue"
10116            | "dialog_lernen"
10117            | "обучить_диалог" => {
10118                let id = self.arg_num(&args, 0, -1.0)? as i64;
10119                let text = self.arg_str(&args, 1, "");
10120                ai::dialog_learn(id, &text);
10121                return Ok(Value::Unit);
10122            },
10123            // dialog_load(handle, path) → lines added (-1 on error)
10124            #[cfg(not(target_arch = "wasm32"))]
10125            "dialog_load"
10126            | "对话载入"
10127            | "対話読込"
10128            | "대화불러오기"
10129            | "โหลดชุดสนทนา"
10130            | "بارگذاری_مجموعه_گفتگو"
10131            | "حمّل_مجموعة_الحوار"
10132            | "טען_מערך_דיאלוג"
10133            | "مکالمہ_مجموعہ_لوڈ"
10134            | "charger_dialogue"
10135            | "dialog_laden"
10136            | "загрузить_диалог" => {
10137                let id = self.arg_num(&args, 0, -1.0)? as i64;
10138                let path = self.arg_str(&args, 1, "");
10139                return Ok(Value::Number(ai::dialog_load(id, &path) as f64));
10140            },
10141            // dialog_train(handle[, epochs, lr]) → loss
10142            #[cfg(not(target_arch = "wasm32"))]
10143            "dialog_train"
10144            | "对话训练"
10145            | "対話訓練"
10146            | "대화훈련"
10147            | "ฝึกสนทนา"
10148            | "آموزش_گفتگو"
10149            | "درّب_الحوار"
10150            | "אמן_דיאלוג"
10151            | "مکالمہ_ٹریننگ"
10152            | "entraîner_dialogue"
10153            | "dialog_trainieren"
10154            | "тренировать_диалог" => {
10155                let id = self.arg_num(&args, 0, -1.0)? as i64;
10156                let epochs = self.arg_num(&args, 1, 20.0)?.max(1.0) as usize;
10157                let lr = self.arg_num(&args, 2, 0.1)? as f32;
10158                return Ok(Value::Number(ai::dialog_train(id, epochs, lr) as f64));
10159            },
10160            // dialog_say(handle, prompt[, max_tokens, temperature]) → reply text
10161            #[cfg(not(target_arch = "wasm32"))]
10162            "dialog_say"
10163            | "对话生成"
10164            | "対話生成"
10165            | "대화생성"
10166            | "พูดสนทนา"
10167            | "بگو"
10168            | "قل"
10169            | "אמור"
10170            | "کہو"
10171            | "dire_dialogue"
10172            | "dialog_sagen"
10173            | "сказать_диалог" => {
10174                let id = self.arg_num(&args, 0, -1.0)? as i64;
10175                let prompt = self.arg_str(&args, 1, "");
10176                let max = self.arg_num(&args, 2, 24.0)?.max(1.0) as usize;
10177                let temp = self.arg_num(&args, 3, 0.8)? as f32;
10178                return Ok(Value::Str(ai::dialog_say(id, &prompt, max, temp)));
10179            },
10180            // dialog_save(handle, path) → bool
10181            #[cfg(not(target_arch = "wasm32"))]
10182            "dialog_save"
10183            | "对话存模"
10184            | "対話モデル保存"
10185            | "대화모델저장"
10186            | "บันทึกโมเดลสนทนา"
10187            | "ذخیره_مدل_گفتگو"
10188            | "احفظ_نموذج_الحوار"
10189            | "שמור_מודל_דיאלוג"
10190            | "مکالمہ_ماڈل_محفوظ"
10191            | "sauvegarder_dialogue"
10192            | "dialog_speichern"
10193            | "сохранить_диалог" => {
10194                let id = self.arg_num(&args, 0, -1.0)? as i64;
10195                let path = self.arg_str(&args, 1, "model.llm");
10196                return Ok(Value::Bool(ai::dialog_save(id, &path)));
10197            },
10198            // dialog_load_model(path) → handle (-1 on failure)
10199            #[cfg(not(target_arch = "wasm32"))]
10200            "dialog_load_model"
10201            | "对话载模"
10202            | "対話モデル読込"
10203            | "대화모델불러오기"
10204            | "โหลดโมเดลสนทนา"
10205            | "بارگذاری_مدل_گفتگو"
10206            | "حمّل_نموذج_الحوار"
10207            | "טען_מודל_דיאלוג"
10208            | "مکالمہ_ماڈل_لوڈ"
10209            | "charger_modèle_dialogue"
10210            | "dialog_modell_laden"
10211            | "загрузить_модель_диалога" => {
10212                let path = self.arg_str(&args, 0, "model.llm");
10213                return Ok(Value::Number(ai::dialog_load_model(&path) as f64));
10214            },
10215
10216            // ── in-process chat-completion (chat.ling-lang.org, Milestone 2) ──
10217            // Real transformer inference (candle-transformers, GGUF, CUDA) —
10218            // distinct from the miniature dialog_* LM above. Generation runs
10219            // on a background thread; these builtins only ever start/poll a
10220            // job, never block http_serve's single-threaded dispatch loop.
10221            // llm_load(gguf_path, tokenizer_path[, device_index]) → handle (-1 on failure)
10222            #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
10223            "llm_load" => {
10224                let gguf_path = self.arg_str(&args, 0, "");
10225                let tokenizer_path = self.arg_str(&args, 1, "");
10226                let device_index = self.arg_num(&args, 2, 0.0)? as i64;
10227                return Ok(Value::Number(
10228                    llm::llm_load(&gguf_path, &tokenizer_path, device_index) as f64,
10229                ));
10230            },
10231            // llm_generate_start(handle, system_prompt, history[, max_tokens, temperature, top_p]) → job_id ("" on failure)
10232            #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
10233            "llm_generate_start" => {
10234                let handle = self.arg_num(&args, 0, -1.0)? as i64;
10235                let system_prompt = self.arg_str(&args, 1, "");
10236                let history = self.arg_str(&args, 2, "");
10237                let max_tokens = self.arg_num(&args, 3, 512.0)? as i64;
10238                let temperature = self.arg_num(&args, 4, 0.7)?;
10239                let top_p = self.arg_num(&args, 5, 0.9)?;
10240                return Ok(Value::Str(llm::llm_generate_start(
10241                    handle,
10242                    &system_prompt,
10243                    &history,
10244                    max_tokens,
10245                    temperature,
10246                    top_p,
10247                )));
10248            },
10249            // llm_generate_poll(job_id) → text generated so far
10250            #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
10251            "llm_generate_poll" => {
10252                let job_id = self.arg_str(&args, 0, "");
10253                return Ok(Value::Str(llm::llm_generate_poll(&job_id)));
10254            },
10255            // llm_generate_done(job_id) → bool
10256            #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
10257            "llm_generate_done" => {
10258                let job_id = self.arg_str(&args, 0, "");
10259                return Ok(Value::Bool(llm::llm_generate_done(&job_id)));
10260            },
10261            // llm_job_status(job_id) → "pending" | "tool_call" | "done"
10262            #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
10263            "llm_job_status" => {
10264                let job_id = self.arg_str(&args, 0, "");
10265                return Ok(Value::Str(llm::llm_job_status(&job_id)));
10266            },
10267            // llm_job_tool_name(job_id) → "" until Milestone 5 lands
10268            #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
10269            "llm_job_tool_name" => {
10270                let job_id = self.arg_str(&args, 0, "");
10271                return Ok(Value::Str(llm::llm_job_tool_name(&job_id)));
10272            },
10273            // llm_job_provide_tool_result(job_id, result_text) → bool (always false until Milestone 5)
10274            #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
10275            "llm_job_provide_tool_result" => {
10276                let job_id = self.arg_str(&args, 0, "");
10277                let result_text = self.arg_str(&args, 1, "");
10278                return Ok(Value::Bool(llm::llm_job_provide_tool_result(
10279                    &job_id,
10280                    &result_text,
10281                )));
10282            },
10283            // llm_job_cancel(job_id)
10284            #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
10285            "llm_job_cancel" => {
10286                let job_id = self.arg_str(&args, 0, "");
10287                llm::llm_job_cancel(&job_id);
10288                return Ok(Value::Unit);
10289            },
10290
10291            // ── Google OAuth sign-in (chat.ling-lang.org) ──────────────────
10292            // Exchanging a code for a token and fetching the profile are both
10293            // outbound HTTPS calls, so this runs on a background thread and
10294            // is polled the same non-blocking way as http_post_async, above.
10295            // oauth_google_start(code, client_id, client_secret, redirect_uri) → job_id
10296            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10297            "oauth_google_start" => {
10298                let code = self.arg_str(&args, 0, "");
10299                let client_id = self.arg_str(&args, 1, "");
10300                let client_secret = self.arg_str(&args, 2, "");
10301                let redirect_uri = self.arg_str(&args, 3, "");
10302                let id = self.oauth_jobs.start_google_login(
10303                    code,
10304                    client_id,
10305                    client_secret,
10306                    redirect_uri,
10307                );
10308                return Ok(Value::Str(id));
10309            },
10310            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10311            "oauth_google_done" => {
10312                let id = self.arg_str(&args, 0, "");
10313                return Ok(Value::Bool(self.oauth_jobs.done(&id)));
10314            },
10315            // "" while pending or on success; the error message on failure.
10316            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10317            "oauth_google_error" => {
10318                let id = self.arg_str(&args, 0, "");
10319                return Ok(Value::Str(self.oauth_jobs.error(&id)));
10320            },
10321            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10322            "oauth_google_sub" => {
10323                let id = self.arg_str(&args, 0, "");
10324                return Ok(Value::Str(self.oauth_jobs.sub(&id)));
10325            },
10326            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10327            "oauth_google_email" => {
10328                let id = self.arg_str(&args, 0, "");
10329                return Ok(Value::Str(self.oauth_jobs.email(&id)));
10330            },
10331            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10332            "oauth_google_name" => {
10333                let id = self.arg_str(&args, 0, "");
10334                return Ok(Value::Str(self.oauth_jobs.name(&id)));
10335            },
10336
10337            // ── image understanding (chat.ling-lang.org attach-an-image) ──
10338            // Moondream2 (quantized). Same background-job pattern as llm_*.
10339            // vision_load(gguf_path, tokenizer_path[, device_index]) → handle (-1 on failure)
10340            #[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
10341            "vision_load" => {
10342                let gguf_path = self.arg_str(&args, 0, "");
10343                let tokenizer_path = self.arg_str(&args, 1, "");
10344                let device_index = self.arg_num(&args, 2, 0.0)? as i64;
10345                return Ok(Value::Number(vision::vision_load(
10346                    &gguf_path,
10347                    &tokenizer_path,
10348                    device_index,
10349                ) as f64));
10350            },
10351            // vision_analyze_start(handle, image_base64, question[, max_tokens, temperature, top_p]) → job_id ("" on failure)
10352            #[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
10353            "vision_analyze_start" => {
10354                let handle = self.arg_num(&args, 0, -1.0)? as i64;
10355                let image_base64 = self.arg_str(&args, 1, "");
10356                let question = self.arg_str(&args, 2, "Describe this image.");
10357                let max_tokens = self.arg_num(&args, 3, 256.0)? as i64;
10358                let temperature = self.arg_num(&args, 4, 0.7)?;
10359                let top_p = self.arg_num(&args, 5, 0.9)?;
10360                return Ok(Value::Str(vision::vision_analyze_start(
10361                    handle,
10362                    &image_base64,
10363                    &question,
10364                    max_tokens,
10365                    temperature,
10366                    top_p,
10367                )));
10368            },
10369            #[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
10370            "vision_analyze_poll" => {
10371                let job_id = self.arg_str(&args, 0, "");
10372                return Ok(Value::Str(vision::vision_analyze_poll(&job_id)));
10373            },
10374            #[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
10375            "vision_analyze_done" => {
10376                let job_id = self.arg_str(&args, 0, "");
10377                return Ok(Value::Bool(vision::vision_analyze_done(&job_id)));
10378            },
10379            // "" unless analysis failed (bad image data, decode error, etc).
10380            #[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
10381            "vision_analyze_error" => {
10382                let job_id = self.arg_str(&args, 0, "");
10383                return Ok(Value::Str(vision::vision_analyze_error(&job_id)));
10384            },
10385
10386            // Decodes `application/x-www-form-urlencoded` text: '+' -> space,
10387            // '%XX' -> byte. Needed to read plain HTML `<form>` POST bodies.
10388            "url_decode" | "网址解码" => {
10389                let s = self.arg_str(&args, 0, "");
10390                let bytes = s.as_bytes();
10391                let mut out = Vec::with_capacity(bytes.len());
10392                let mut i = 0;
10393                while i < bytes.len() {
10394                    match bytes[i] {
10395                        b'+' => {
10396                            out.push(b' ');
10397                            i += 1;
10398                        },
10399                        b'%' if i + 2 < bytes.len() => {
10400                            let hex = std::str::from_utf8(&bytes[i + 1..i + 3]).unwrap_or("");
10401                            match u8::from_str_radix(hex, 16) {
10402                                Ok(b) => {
10403                                    out.push(b);
10404                                    i += 3;
10405                                },
10406                                Err(_) => {
10407                                    out.push(bytes[i]);
10408                                    i += 1;
10409                                },
10410                            }
10411                        },
10412                        b => {
10413                            out.push(b);
10414                            i += 1;
10415                        },
10416                    }
10417                }
10418                return Ok(Value::Str(String::from_utf8_lossy(&out).into_owned()));
10419            },
10420            // Seconds since Unix epoch (float — sub-second precision). No ISO/date
10421            // formatting builtin exists yet; `.ling` code that wants a display
10422            // string currently just uses the raw number.
10423            "now_unix" | "现在时间" => {
10424                return Ok(Value::Number(now_secs()));
10425            },
10426            "file_exists" | "文件存在" => {
10427                #[cfg(target_arch = "wasm32")]
10428                return Ok(Value::Bool(false));
10429                #[cfg(not(target_arch = "wasm32"))]
10430                {
10431                    let path = self.arg_str(&args, 0, "").replace('\\', "/");
10432                    return Ok(Value::Bool(std::path::Path::new(&path).exists()));
10433                }
10434            },
10435            "write_file" | "เขียนไฟล์" | "نوشتن_فایل" | "اكتب_الملف" | "כתוב_קובץ" | "فائل_لکھو" =>
10436            {
10437                #[cfg(target_arch = "wasm32")]
10438                return Ok(Value::Unit);
10439                #[cfg(not(target_arch = "wasm32"))]
10440                {
10441                    let path = self.arg_str(&args, 0, "").replace('\\', "/");
10442                    let content = self.arg_str(&args, 1, "");
10443                    if let Some(parent) = std::path::Path::new(&path).parent() {
10444                        if !parent.as_os_str().is_empty() {
10445                            let _ = std::fs::create_dir_all(parent);
10446                        }
10447                    }
10448                    std::fs::write(&path, content.as_bytes())
10449                        .map_err(|e| EvalErr::from(format!("write_file '{path}': {e}")))?;
10450                    return Ok(Value::Unit);
10451                }
10452            },
10453            "print_file" | "พิมพ์ไฟล์" | "چاپ_فایل" | "اطبع_الملف" | "הדפס_קובץ" | "فائل_چھاپو" =>
10454            {
10455                let content = self.arg_str(&args, 0, "");
10456                print!("{content}");
10457                return Ok(Value::Unit);
10458            },
10459
10460            // ── CLI arguments ─────────────────────────────────────────────────
10461            "get_args"
10462            | "รับอาร์กิวเมนต์"
10463            | "دریافت_آرگومان‌ها"
10464            | "اجلب_المعاملات"
10465            | "קבל_ארגומנטים"
10466            | "دلائل_حاصل_کرو" => {
10467                let v: Vec<Value> = std::env::args().map(Value::Str).collect();
10468                return Ok(Value::List(Rc::new(v)));
10469            },
10470
10471            // ── Filesystem: directory walking, stat, content hashing (native) ──
10472            // These power headless batch tools (asset pipelines, indexers). Errors
10473            // degrade gracefully (empty list / 0 / "") so a walk never aborts on one
10474            // unreadable entry.
10475            #[cfg(not(target_arch = "wasm32"))]
10476            "list_dir"
10477            | "รายการไดเรกทอรี"
10478            | "فهرست_پوشه"
10479            | "اسرد_المجلد"
10480            | "רשום_תיקייה"
10481            | "فولڈر_فہرست" => {
10482                let path = self.arg_str(&args, 0, ".").replace('\\', "/");
10483                let mut paths: Vec<String> = Vec::new();
10484                if let Ok(rd) = std::fs::read_dir(&path) {
10485                    for e in rd.flatten() {
10486                        paths.push(e.path().to_string_lossy().replace('\\', "/"));
10487                    }
10488                }
10489                paths.sort();
10490                let out: Vec<Value> = paths.into_iter().map(Value::Str).collect();
10491                return Ok(Value::List(Rc::new(out)));
10492            },
10493            #[cfg(not(target_arch = "wasm32"))]
10494            "is_dir"
10495            | "เป็นไดเรกทอรี"
10496            | "آیا_پوشه_است"
10497            | "هل_مجلد"
10498            | "האם_תיקייה"
10499            | "کیا_فولڈر_ہے" => {
10500                let path = self.arg_str(&args, 0, "").replace('\\', "/");
10501                return Ok(Value::Bool(std::path::Path::new(&path).is_dir()));
10502            },
10503            #[cfg(not(target_arch = "wasm32"))]
10504            "is_file" | "เป็นไฟล์" | "آیا_فایل_است" | "هل_ملف" | "האם_קובץ" | "کیا_فائل_ہے" =>
10505            {
10506                let path = self.arg_str(&args, 0, "").replace('\\', "/");
10507                return Ok(Value::Bool(std::path::Path::new(&path).is_file()));
10508            },
10509            #[cfg(not(target_arch = "wasm32"))]
10510            "path_name" | "ชื่อไฟล์" | "نام_مسیر" | "اسم_المسار" | "שם_נתיב" | "پاتھ_نام" =>
10511            {
10512                let path = self.arg_str(&args, 0, "").replace('\\', "/");
10513                let name = std::path::Path::new(&path)
10514                    .file_name()
10515                    .map(|s| s.to_string_lossy().into_owned())
10516                    .unwrap_or_default();
10517                return Ok(Value::Str(name));
10518            },
10519            #[cfg(not(target_arch = "wasm32"))]
10520            "path_ext"
10521            | "นามสกุลไฟล์"
10522            | "پسوند_مسیر"
10523            | "امتداد_المسار"
10524            | "סיומת_נתיב"
10525            | "پاتھ_ایکسٹینشن" => {
10526                let path = self.arg_str(&args, 0, "").replace('\\', "/");
10527                let ext = std::path::Path::new(&path)
10528                    .extension()
10529                    .map(|s| s.to_string_lossy().to_lowercase())
10530                    .unwrap_or_default();
10531                return Ok(Value::Str(ext));
10532            },
10533            #[cfg(not(target_arch = "wasm32"))]
10534            "file_size" | "ขนาดไฟล์" | "اندازه_فایل" | "حجم_الملف" | "גודל_קובץ" | "فائل_سائز" =>
10535            {
10536                let path = self.arg_str(&args, 0, "");
10537                let sz = std::fs::metadata(&path).map(|m| m.len()).unwrap_or(0);
10538                return Ok(Value::Number(sz as f64));
10539            },
10540            #[cfg(not(target_arch = "wasm32"))]
10541            "file_modified"
10542            | "เวลาที่แก้ไข"
10543            | "زمان_تغییر_فایل"
10544            | "وقت_تعديل_الملف"
10545            | "זמן_עדכון_קובץ"
10546            | "فائل_ترمیم_وقت" => {
10547                let path = self.arg_str(&args, 0, "");
10548                let secs = std::fs::metadata(&path)
10549                    .and_then(|m| m.modified())
10550                    .ok()
10551                    .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
10552                    .map(|d| d.as_secs_f64())
10553                    .unwrap_or(0.0);
10554                return Ok(Value::Number(secs));
10555            },
10556            #[cfg(not(target_arch = "wasm32"))]
10557            "file_created"
10558            | "เวลาที่สร้าง"
10559            | "زمان_ایجاد_فایل"
10560            | "وقت_إنشاء_الملف"
10561            | "זמן_יצירת_קובץ"
10562            | "فائل_تخلیق_وقت" => {
10563                let path = self.arg_str(&args, 0, "");
10564                let secs = std::fs::metadata(&path)
10565                    .ok()
10566                    .and_then(|m| m.created().or_else(|_| m.modified()).ok())
10567                    .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
10568                    .map(|d| d.as_secs_f64())
10569                    .unwrap_or(0.0);
10570                return Ok(Value::Number(secs));
10571            },
10572            #[cfg(not(target_arch = "wasm32"))]
10573            "make_dir"
10574            | "สร้างไดเรกทอรี"
10575            | "ساخت_پوشه"
10576            | "أنشئ_مجلدا"
10577            | "צור_תיקייה"
10578            | "فولڈر_بناؤ" => {
10579                let path = self.arg_str(&args, 0, "");
10580                return Ok(Value::Bool(std::fs::create_dir_all(&path).is_ok()));
10581            },
10582            // remove_dir_all(path) -> bool. Recursively deletes a directory tree —
10583            // used by the build farm to throw away each build's disposable extracted
10584            // source/output folders once the sandbox run is done (success or fail),
10585            // so per-publish throwaway dirs don't accumulate on disk forever.
10586            // Not found is treated as success (nothing to clean up).
10587            #[cfg(not(target_arch = "wasm32"))]
10588            "remove_dir_all" | "ลบไดเรกทอรีทั้งหมด" => {
10589                let path = self.arg_str(&args, 0, "");
10590                let ok = match std::fs::remove_dir_all(&path) {
10591                    Ok(()) => true,
10592                    Err(e) => e.kind() == std::io::ErrorKind::NotFound,
10593                };
10594                return Ok(Value::Bool(ok));
10595            },
10596            // absolute_path(path) -> string. Resolves a relative path against the
10597            // process's current working directory, forward-slash-normalized — needed
10598            // because `docker run -v host:container` bind mounts require an absolute
10599            // host path (a relative one is silently misinterpreted). Deliberately joins
10600            // with current_dir() rather than fs::canonicalize(), which on Windows
10601            // returns a \\?\-prefixed UNC path that Docker Desktop's CLI doesn't parse
10602            // as a normal drive path. If `path` is already absolute, it's returned
10603            // unchanged (just slash-normalized).
10604            #[cfg(not(target_arch = "wasm32"))]
10605            "absolute_path" | "พาธสัมบูรณ์" => {
10606                let path = self.arg_str(&args, 0, "");
10607                let p = std::path::Path::new(&path);
10608                let joined = if p.is_absolute() {
10609                    p.to_path_buf()
10610                } else {
10611                    std::env::current_dir()
10612                        .map(|cwd| cwd.join(p))
10613                        .unwrap_or_else(|_| p.to_path_buf())
10614                };
10615                return Ok(Value::Str(joined.to_string_lossy().replace('\\', "/")));
10616            },
10617            // str_strip_prefix("Bearer x", "Bearer ") → "x" (unchanged if absent).
10618            "str_strip_prefix"
10619            | "ตัดคำนำหน้า"
10620            | "حذف_پیشوند"
10621            | "أزل_البادئة"
10622            | "הסר_קידומת"
10623            | "سابقہ_ہٹاؤ" => {
10624                let s = self.arg_str(&args, 0, "");
10625                let prefix = self.arg_str(&args, 1, "");
10626                return Ok(Value::Str(
10627                    s.strip_prefix(&prefix).unwrap_or(&s).to_string(),
10628                ));
10629            },
10630            // Classify a file by magic bytes: "gzip" | "zip" | "other" | "missing".
10631            // The build-verification gate: only real built archives may publish.
10632            #[cfg(not(target_arch = "wasm32"))]
10633            "file_magic"
10634            | "มายาไฟล์"
10635            | "امضای_فایل"
10636            | "توقيع_الملف"
10637            | "חתימת_קובץ"
10638            | "فائل_میجک" => {
10639                let path = self.arg_str(&args, 0, "");
10640                let kind = match std::fs::File::open(&path) {
10641                    Ok(mut f) => {
10642                        use std::io::Read;
10643                        let mut buf = [0u8; 8];
10644                        let n = f.read(&mut buf).unwrap_or(0);
10645                        if n >= 2 && buf[0] == 0x1f && buf[1] == 0x8b {
10646                            "gzip"
10647                        } else if n >= 4 && &buf[0..2] == b"PK" {
10648                            "zip"
10649                        } else if n >= 8 && &buf[0..8] == b"\x89PNG\r\n\x1a\n" {
10650                            "png"
10651                        } else {
10652                            "other"
10653                        }
10654                    },
10655                    Err(_) => "missing",
10656                };
10657                return Ok(Value::Str(kind.to_string()));
10658            },
10659            // Binary-safe file copy (backups): copy_file(src, dst) → bool.
10660            #[cfg(not(target_arch = "wasm32"))]
10661            "copy_file" | "คัดลอกไฟล์" | "کپی_فایل" | "انسخ_الملف" | "העתק_קובץ" | "فائل_کاپی" =>
10662            {
10663                let src = self.arg_str(&args, 0, "");
10664                let dst = self.arg_str(&args, 1, "");
10665                if let Some(parent) = std::path::Path::new(&dst).parent() {
10666                    let _ = std::fs::create_dir_all(parent);
10667                }
10668                return Ok(Value::Bool(std::fs::copy(&src, &dst).is_ok()));
10669            },
10670            // ── Read a .tgz (gzip tarball): list file entries / read one file ──
10671            // Powers the GitHub-style "Code / Files" browser: tar_gz_list gives
10672            // the file tree, tar_gz_read pulls one file's text for the viewer.
10673            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10674            "tar_gz_list"
10675            | "รายการทาร์"
10676            | "فهرست_TAR_GZ"
10677            | "اسرد_TAR_GZ"
10678            | "רשום_TAR_GZ"
10679            | "TAR_GZ_فہرست" => {
10680                let path = self.arg_str(&args, 0, "");
10681                let mut names: Vec<String> = Vec::new();
10682                if let Ok(file) = std::fs::File::open(&path) {
10683                    let gz = flate2::read::GzDecoder::new(file);
10684                    let mut ar = tar::Archive::new(gz);
10685                    if let Ok(entries) = ar.entries() {
10686                        for entry in entries.flatten() {
10687                            if entry.header().entry_type().is_file() {
10688                                if let Ok(p) = entry.path() {
10689                                    names.push(
10690                                        p.to_string_lossy()
10691                                            .trim_start_matches("./")
10692                                            .replace('\\', "/"),
10693                                    );
10694                                }
10695                            }
10696                        }
10697                    }
10698                }
10699                names.sort();
10700                names.dedup();
10701                let out: Vec<Value> = names.into_iter().map(Value::Str).collect();
10702                return Ok(Value::List(Rc::new(out)));
10703            },
10704            // tar_gz_read(archive, entry) → that file's text (utf-8 lossy,
10705            // capped at 256 KiB). Only reads entries that exist in the archive,
10706            // so a caller can't traverse outside it. "" if not found/unreadable.
10707            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10708            "tar_gz_read"
10709            | "อ่านทาร์"
10710            | "خواندن_TAR_GZ"
10711            | "اقرأ_TAR_GZ"
10712            | "קרא_TAR_GZ"
10713            | "TAR_GZ_پڑھو" => {
10714                let path = self.arg_str(&args, 0, "");
10715                let want = self.arg_str(&args, 1, "");
10716                let want = want.trim_start_matches("./").replace('\\', "/");
10717                let mut content = String::new();
10718                if let Ok(file) = std::fs::File::open(&path) {
10719                    let gz = flate2::read::GzDecoder::new(file);
10720                    let mut ar = tar::Archive::new(gz);
10721                    if let Ok(entries) = ar.entries() {
10722                        for entry in entries.flatten() {
10723                            let name = match entry.path() {
10724                                Ok(p) => p
10725                                    .to_string_lossy()
10726                                    .trim_start_matches("./")
10727                                    .replace('\\', "/"),
10728                                Err(_) => continue,
10729                            };
10730                            if name == want {
10731                                use std::io::Read;
10732                                let mut buf = Vec::new();
10733                                let cap = 256 * 1024;
10734                                if entry.take(cap as u64 + 1).read_to_end(&mut buf).is_ok() {
10735                                    let slice = if buf.len() > cap {
10736                                        &buf[..cap]
10737                                    } else {
10738                                        &buf[..]
10739                                    };
10740                                    content = String::from_utf8_lossy(slice).into_owned();
10741                                }
10742                                break;
10743                            }
10744                        }
10745                    }
10746                }
10747                return Ok(Value::Str(content));
10748            },
10749            // run_process(cmd, args_list, timeout_secs, log_path) -> number (exit code;
10750            // -1 = spawn/log-open failure; -2 = killed after timeout). No shell involved
10751            // (Command::new + .args, never a shell string), so args can't be reinterpreted;
10752            // this exists solely for the registry's own first-party controllers to drive
10753            // `docker run` for sandboxed package builds — never exposed to uploaded content.
10754            // stdout+stderr both append to log_path (two independent opens of the same
10755            // file — an approximation of 2>&1, fine for a build log, not byte-interleaved).
10756            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10757            "run_process" | "รันโปรเซส" => {
10758                let cmd = self.arg_str(&args, 0, "");
10759                let argv = self.arg_list_str(&args, 1);
10760                let timeout_secs = self.arg_num(&args, 2, 60.0)?.max(1.0) as u64;
10761                let log_path = self.arg_str(&args, 3, "");
10762
10763                let make_stdio = || -> std::process::Stdio {
10764                    if log_path.is_empty() {
10765                        std::process::Stdio::null()
10766                    } else {
10767                        std::fs::OpenOptions::new()
10768                            .create(true)
10769                            .append(true)
10770                            .open(&log_path)
10771                            .map(std::process::Stdio::from)
10772                            .unwrap_or_else(|_| std::process::Stdio::null())
10773                    }
10774                };
10775
10776                let spawned = std::process::Command::new(&cmd)
10777                    .args(&argv)
10778                    .stdin(std::process::Stdio::null())
10779                    .stdout(make_stdio())
10780                    .stderr(make_stdio())
10781                    .spawn();
10782
10783                let mut child = match spawned {
10784                    Ok(c) => c,
10785                    Err(_) => return Ok(Value::Number(-1.0)),
10786                };
10787
10788                let start = std::time::Instant::now();
10789                let timeout = std::time::Duration::from_secs(timeout_secs);
10790                loop {
10791                    match child.try_wait() {
10792                        Ok(Some(status)) => {
10793                            return Ok(Value::Number(status.code().unwrap_or(-1) as f64));
10794                        },
10795                        Ok(None) => {
10796                            if start.elapsed() > timeout {
10797                                let _ = child.kill();
10798                                let _ = child.wait();
10799                                return Ok(Value::Number(-2.0));
10800                            }
10801                            std::thread::sleep(std::time::Duration::from_millis(300));
10802                        },
10803                        Err(_) => return Ok(Value::Number(-1.0)),
10804                    }
10805                }
10806            },
10807            // tar_gz_extract_to(tar_path, entry_name, out_path) -> bool. Byte-perfect
10808            // single-entry extraction straight to disk — unlike tar_gz_read, no
10809            // UTF-8 round-trip, so binary entries (icons, images) come through intact.
10810            // Same in-archive-name string match as tar_gz_read (not a filesystem path
10811            // join), so an entry name can't traverse outside the archive's own listing.
10812            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10813            "tar_gz_extract_to" | "แตกไฟล์ทาร์" => {
10814                let path = self.arg_str(&args, 0, "");
10815                let want = self.arg_str(&args, 1, "");
10816                let want = want.trim_start_matches("./").replace('\\', "/");
10817                let out_path = self.arg_str(&args, 2, "");
10818                let mut found = false;
10819                if let Ok(file) = std::fs::File::open(&path) {
10820                    let gz = flate2::read::GzDecoder::new(file);
10821                    let mut ar = tar::Archive::new(gz);
10822                    if let Ok(entries) = ar.entries() {
10823                        for mut entry in entries.flatten() {
10824                            let name = match entry.path() {
10825                                Ok(p) => p
10826                                    .to_string_lossy()
10827                                    .trim_start_matches("./")
10828                                    .replace('\\', "/"),
10829                                Err(_) => continue,
10830                            };
10831                            if name == want {
10832                                if let Some(parent) = std::path::Path::new(&out_path).parent() {
10833                                    let _ = std::fs::create_dir_all(parent);
10834                                }
10835                                if let Ok(mut out) = std::fs::File::create(&out_path) {
10836                                    use std::io::copy;
10837                                    found = copy(&mut entry, &mut out).is_ok();
10838                                }
10839                                break;
10840                            }
10841                        }
10842                    }
10843                }
10844                return Ok(Value::Bool(found));
10845            },
10846            // tar_gz_extract_all(archive_path, dest_dir) -> bool. Unpacks every entry of
10847            // an uploaded package tarball into dest_dir — this is how the build farm
10848            // materializes a fresh, disposable copy of untrusted content to bind-mount
10849            // read-only into the sandbox container (see controllers/publish.ling,
10850            // ทำสร้างบิลด์). Delegates to tar-rs's own Archive::unpack, which already
10851            // rejects/strips ".."-traversing and absolute entry paths (the exact
10852            // zip-slip class of bug the build-farm security review flagged for
10853            // ling build's own [includes]/`use "../.."` resolvers) — no manual entry
10854            // path sanitization needed on top of that.
10855            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10856            "tar_gz_extract_all" | "แตกทาร์ทั้งหมด" => {
10857                let path = self.arg_str(&args, 0, "");
10858                let dest = self.arg_str(&args, 1, "");
10859                let ok = (|| -> std::io::Result<()> {
10860                    std::fs::create_dir_all(&dest)?;
10861                    let file = std::fs::File::open(&path)?;
10862                    let gz = flate2::read::GzDecoder::new(file);
10863                    let mut ar = tar::Archive::new(gz);
10864                    ar.unpack(&dest)
10865                })()
10866                .is_ok();
10867                return Ok(Value::Bool(ok));
10868            },
10869            // zip_dir(dir_path, out_path) -> bool. Recursively zips a directory,
10870            // preserving relative paths (unlike zip_files, which flattens everything
10871            // to basenames) — used to package a sandboxed build's output folder
10872            // (e.g. dist/web/ with nested assets) into one downloadable artifact.
10873            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10874            "zip_dir" | "บีบอัดโฟลเดอร์" => {
10875                let dir_path = self.arg_str(&args, 0, "");
10876                let out_path = self.arg_str(&args, 1, "out.zip");
10877                fn add_dir(
10878                    writer: &mut zip::ZipWriter<std::fs::File>,
10879                    base: &std::path::Path,
10880                    dir: &std::path::Path,
10881                    options: zip::write::FileOptions<'_, ()>,
10882                ) -> std::io::Result<()> {
10883                    for entry in std::fs::read_dir(dir)? {
10884                        let entry = entry?;
10885                        let path = entry.path();
10886                        let rel = path
10887                            .strip_prefix(base)
10888                            .unwrap_or(&path)
10889                            .to_string_lossy()
10890                            .replace('\\', "/");
10891                        if path.is_dir() {
10892                            add_dir(writer, base, &path, options)?;
10893                        } else {
10894                            let bytes = std::fs::read(&path)?;
10895                            writer.start_file(rel, options)?;
10896                            std::io::Write::write_all(writer, &bytes)?;
10897                        }
10898                    }
10899                    Ok(())
10900                }
10901                let ok = (|| -> std::io::Result<()> {
10902                    let base = std::path::Path::new(&dir_path);
10903                    let file = std::fs::File::create(&out_path)?;
10904                    let mut writer = zip::ZipWriter::new(file);
10905                    let options: zip::write::FileOptions<'_, ()> =
10906                        zip::write::FileOptions::default()
10907                            .compression_method(zip::CompressionMethod::Deflated);
10908                    add_dir(&mut writer, base, base, options)?;
10909                    writer.finish()?;
10910                    Ok(())
10911                })()
10912                .is_ok();
10913                return Ok(Value::Bool(ok));
10914            },
10915            // icon_convert_to_png(src_path, out_path, size) -> bool. Rasterizes any
10916            // ling-icon-supported source (.svg/.png/.jpg/.bmp/.ico) to a single
10917            // size×size PNG — used to turn a package manifest's icon into a web avatar.
10918            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10919            "icon_convert_to_png" | "แปลงไอคอนเป็นพีเอ็นจี" => {
10920                let src = self.arg_str(&args, 0, "");
10921                let out = self.arg_str(&args, 1, "");
10922                let size = self.arg_num(&args, 2, 300.0)? as u32;
10923                let ok = match ling_icon::png_bytes(std::path::Path::new(&src), size) {
10924                    Ok(bytes) => std::fs::write(&out, bytes).is_ok(),
10925                    Err(_) => false,
10926                };
10927                return Ok(Value::Bool(ok));
10928            },
10929            // ── TOTP (RFC 6238, HMAC-SHA1, 6 digits, 30s) for 2FA ──
10930            // Base32 secret compatible with Google Authenticator / Authy etc.
10931            // `base32_encode`/`totp_code`/`totp_check` only exist under this
10932            // same `feature = "web"` gate (see their definitions above) — a
10933            // build without it must skip these arms too, not just fail to
10934            // link; matches how `file_hash` etc. gate their own arms below.
10935            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10936            "totp_secret" | "โทเทนลับ" | "راز_TOTP" | "سر_TOTP" | "סוד_TOTP" | "TOTP_راز" =>
10937            {
10938                let mut bytes = [0u8; 20];
10939                rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut bytes);
10940                return Ok(Value::Str(base32_encode(&bytes)));
10941            },
10942            // otpauth:// URI to paste into an authenticator app (or make a QR of).
10943            // No cfg gate needed — pure string formatting, no dependency on
10944            // the web-only TOTP helpers.
10945            "totp_uri"
10946            | "โทเทนยูอาร์ไอ"
10947            | "آدرس_TOTP"
10948            | "رابط_TOTP"
10949            | "כתובת_TOTP"
10950            | "TOTP_یو_آر_آئی" => {
10951                let secret = self.arg_str(&args, 0, "");
10952                let account = self.arg_str(&args, 1, "user");
10953                let issuer = self.arg_str(&args, 2, "lingfu");
10954                return Ok(Value::Str(format!(
10955                    "otpauth://totp/{issuer}:{account}?secret={secret}&issuer={issuer}&algorithm=SHA1&digits=6&period=30"
10956                )));
10957            },
10958            // Verify a 6-digit code against the secret, allowing ±1 time step.
10959            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10960            "totp_verify"
10961            | "โทเทนตรวจ"
10962            | "تایید_TOTP"
10963            | "تحقق_TOTP"
10964            | "אמת_TOTP"
10965            | "TOTP_تصدیق" => {
10966                let secret = self.arg_str(&args, 0, "");
10967                let code = self.arg_str(&args, 1, "");
10968                let ok = totp_check(&secret, code.trim());
10969                return Ok(Value::Bool(ok));
10970            },
10971            // The current valid code, for tests/tools.
10972            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10973            "totp_now" | "โทเทนตอนนี้" | "TOTP_اکنون" | "TOTP_الآن" | "TOTP_עכשיו" | "TOTP_ابھی" =>
10974            {
10975                let secret = self.arg_str(&args, 0, "");
10976                let step = (crate::runtime::now_secs() as u64) / 30;
10977                return Ok(Value::Str(totp_code(&secret, step).unwrap_or_default()));
10978            },
10979            // BLAKE3 hex of a file's bytes (binary-safe content fingerprint).
10980            #[cfg(not(target_arch = "wasm32"))]
10981            "file_hash" | "แฮชไฟล์" | "درهم_فایل" | "بصمة_الملف" | "גיבוב_קובץ" | "فائل_ہیش" =>
10982            {
10983                let path = self.arg_str(&args, 0, "");
10984                match std::fs::read(&path) {
10985                    Ok(bytes) => {
10986                        return Ok(Value::Str(hex_encode(&ling_crypto::Blake3::hash(&bytes))))
10987                    },
10988                    Err(_) => return Ok(Value::Str(String::new())),
10989                }
10990            },
10991            // BLAKE3 hex of an arbitrary string (deterministic id/colour/role seed).
10992            "hash_hex"
10993            | "แฮชสตริง"
10994            | "درهم_هگزادسیمال"
10995            | "بصمة_سداسية"
10996            | "גיבוב_הקסדצימלי"
10997            | "ہیکس_ہیش" => {
10998                let s = self.arg_str(&args, 0, "");
10999                return Ok(Value::Str(hex_encode(&ling_crypto::Blake3::hash(
11000                    s.as_bytes(),
11001                ))));
11002            },
11003            // Read an environment variable, falling back to a default.
11004            #[cfg(not(target_arch = "wasm32"))]
11005            "env_get"
11006            | "รับตัวแปรแวดล้อม"
11007            | "دریافت_متغیر_محیطی"
11008            | "اجلب_متغير_البيئة"
11009            | "קבל_משתנה_סביבה"
11010            | "ماحولیاتی_متغیر_حاصل_کرو" => {
11011                let name = self.arg_str(&args, 0, "");
11012                let dflt = self.arg_str(&args, 1, "");
11013                return Ok(Value::Str(std::env::var(&name).unwrap_or(dflt)));
11014            },
11015
11016            // ── String utilities ──────────────────────────────────────────────
11017            // Parses a string to a number (0 on failure — degrades gracefully,
11018            // like the other filesystem/parsing builtins in this file).
11019            "to_number" | "转数字" => {
11020                let s = self.arg_str(&args, 0, "");
11021                return Ok(Value::Number(s.trim().parse().unwrap_or(0.0)));
11022            },
11023            // Plain SHA-256 hex — matches the browser's native SubtleCrypto
11024            // digest("SHA-256", ...), which is what proof-of-work mining uses
11025            // client-side (Web Crypto has no Blake3/SHA-3, so this is the one
11026            // hash both sides can compute natively and fast).
11027            "sha256_hex" | "SHA256哈希" => {
11028                use sha2::Digest;
11029                let s = self.arg_str(&args, 0, "");
11030                let mut h = sha2::Sha256::new();
11031                h.update(s.as_bytes());
11032                return Ok(Value::Str(hex_encode(&h.finalize())));
11033            },
11034            // Parses a hex string (no "0x" prefix) to a number — `to_number`
11035            // uses Rust's plain f64 parser, which doesn't understand hex.
11036            "hex_to_number" | "十六进制转数字" => {
11037                let s = self.arg_str(&args, 0, "");
11038                let v = u64::from_str_radix(s.trim(), 16).unwrap_or(0);
11039                return Ok(Value::Number(v as f64));
11040            },
11041            "split" | "str_split" | "แยก" | "جداسازی" | "قسّم" | "פצל" | "تقسیم_کرو" =>
11042            {
11043                let s = self.arg_str(&args, 0, "");
11044                let sep = self.arg_str(&args, 1, "\n");
11045                let sep = if sep.is_empty() { "\n".into() } else { sep };
11046                let parts: Vec<Value> = s
11047                    .split(sep.as_str())
11048                    .map(|p| Value::Str(p.to_string()))
11049                    .collect();
11050                return Ok(Value::List(Rc::new(parts)));
11051            },
11052            "trim"
11053            | "str_trim"
11054            | "ตัดช่องว่าง"
11055            | "حذف_فاصله"
11056            | "اقتطع_الفراغات"
11057            | "חתוך_רווחים"
11058            | "خالی_جگہ_کاٹو" => {
11059                let s = self.arg_str(&args, 0, "");
11060                return Ok(Value::Str(s.trim().to_string()));
11061            },
11062            "starts_with"
11063            | "str_starts_with"
11064            | "เริ่มด้วย"
11065            | "شروع_می‌شود_با"
11066            | "يبدأ_بـ"
11067            | "מתחיל_ב"
11068            | "شروع_ہوتا_ہے" => {
11069                let s = self.arg_str(&args, 0, "");
11070                let prefix = self.arg_str(&args, 1, "");
11071                return Ok(Value::Bool(s.starts_with(prefix.as_str())));
11072            },
11073            "ends_with"
11074            | "str_ends_with"
11075            | "ลงท้ายด้วย"
11076            | "پایان_می‌یابد_با"
11077            | "ينتهي_بـ"
11078            | "מסתיים_ב"
11079            | "ختم_ہوتا_ہے" => {
11080                let s = self.arg_str(&args, 0, "");
11081                let suffix = self.arg_str(&args, 1, "");
11082                return Ok(Value::Bool(s.ends_with(suffix.as_str())));
11083            },
11084            "str_replace"
11085            | "แทนสตริง"
11086            | "جایگزینی_رشته"
11087            | "استبدل_النص"
11088            | "החלף_מחרוזת"
11089            | "اسٹرنگ_تبدیل" => {
11090                let s = self.arg_str(&args, 0, "");
11091                let from = self.arg_str(&args, 1, "");
11092                let to = self.arg_str(&args, 2, "");
11093                return Ok(Value::Str(s.replace(from.as_str(), to.as_str())));
11094            },
11095            "str_find"
11096            | "หาในสตริง"
11097            | "جستجوی_رشته"
11098            | "ابحث_في_النص"
11099            | "חפש_מחרוזת"
11100            | "اسٹرنگ_تلاش" => {
11101                let s = self.arg_str(&args, 0, "");
11102                let needle = self.arg_str(&args, 1, "");
11103                // Return char index (not byte index) for consistency with substr
11104                let pos = s
11105                    .find(needle.as_str())
11106                    .map(|byte_i| s[..byte_i].chars().count() as f64)
11107                    .unwrap_or(-1.0);
11108                return Ok(Value::Number(pos));
11109            },
11110            "substr"
11111            | "str_slice"
11112            | "ส่วนสตริง"
11113            | "زیررشته"
11114            | "جزء_النص"
11115            | "תת_מחרוזת"
11116            | "ذیلی_اسٹرنگ" => {
11117                let s = self.arg_str(&args, 0, "");
11118                let start = self.arg_num(&args, 1, 0.0)? as usize;
11119                let len = args
11120                    .get(2)
11121                    .map(|v| self.to_number(v).unwrap_or(999999.0) as usize)
11122                    .unwrap_or_else(|| s.chars().count().saturating_sub(start));
11123                let chars: Vec<char> = s.chars().collect();
11124                let end = (start + len).min(chars.len());
11125                let slice: String = chars.get(start..end).unwrap_or(&[]).iter().collect();
11126                return Ok(Value::Str(slice));
11127            },
11128            "to_str"
11129            | "str"
11130            | "num_str"
11131            | "แปลงสตริง"
11132            | "تبدیل_به_رشته"
11133            | "حوّل_لنص"
11134            | "המר_למחרוזת"
11135            | "اسٹرنگ_میں_بدلو" => {
11136                let v = args.into_iter().next().unwrap_or(Value::Unit);
11137                return Ok(Value::Str(v.to_string()));
11138            },
11139            "str_repeat"
11140            | "ทำซ้ำสตริง"
11141            | "تکرار_رشته"
11142            | "كرّر_النص"
11143            | "חזור_על_מחרוזת"
11144            | "اسٹرنگ_دہراؤ" => {
11145                let s = self.arg_str(&args, 0, "");
11146                let n = self.arg_num(&args, 1, 1.0)? as usize;
11147                return Ok(Value::Str(s.repeat(n)));
11148            },
11149            "str_upper" => {
11150                let s = self.arg_str(&args, 0, "");
11151                return Ok(Value::Str(s.to_uppercase()));
11152            },
11153            "str_lower" => {
11154                let s = self.arg_str(&args, 0, "");
11155                return Ok(Value::Str(s.to_lowercase()));
11156            },
11157            "str_len"
11158            | "len"
11159            | "ความยาว"
11160            | "长度"
11161            | "長さ"
11162            | "길이"
11163            | "طول_رشته"
11164            | "طول_النص"
11165            | "אורך_מחרוזת"
11166            | "اسٹرنگ_لمبائی" => match args.first() {
11167                Some(Value::Str(s)) => return Ok(Value::Number(s.chars().count() as f64)),
11168                Some(Value::List(v)) => return Ok(Value::Number(v.len() as f64)),
11169                _ => return Ok(Value::Number(0.0)),
11170            },
11171
11172            // ── FNV-1a hash (deterministic, normalized 0.0–1.0) ──────────────
11173            "hash_str" | "แฮช" | "درهم_رشته" | "بصمة_نص" | "גיבוב_מחרוזת" | "اسٹرنگ_ہیش" =>
11174            {
11175                let s = self.arg_str(&args, 0, "");
11176                let mut h: u64 = 14695981039346656037_u64;
11177                for b in s.bytes() {
11178                    h ^= b as u64;
11179                    h = h.wrapping_mul(1099511628211);
11180                }
11181                return Ok(Value::Number((h & 0xFFFFFF) as f64 / 16777215.0));
11182            },
11183            "hash_int" | "แฮชจำนวน" | "درهم_عدد" | "بصمة_عدد" | "גיבוב_מספר" | "نمبر_ہیش" =>
11184            {
11185                let s = self.arg_str(&args, 0, "");
11186                let n = self.arg_num(&args, 1, 100.0)? as u64;
11187                let mut h: u64 = 14695981039346656037_u64;
11188                for b in s.bytes() {
11189                    h ^= b as u64;
11190                    h = h.wrapping_mul(1099511628211);
11191                }
11192                return Ok(Value::Number((h % n.max(1)) as f64));
11193            },
11194
11195            // ── List utilities ────────────────────────────────────────────────
11196            "list_new"
11197            | "รายการใหม่"
11198            | "新建列表"
11199            | "新規リスト"
11200            | "새목록"
11201            | "فهرست_جدید"
11202            | "قائمة_جديدة"
11203            | "רשימה_חדשה"
11204            | "نئی_فہرست"
11205            | "nouvelle_liste"
11206            | "neue_liste"
11207            | "новый_список" => {
11208                return Ok(Value::List(Rc::new(Vec::new())));
11209            },
11210            "list_push"
11211            | "เพิ่มรายการ"
11212            | "列表添加"
11213            | "リスト追加"
11214            | "목록추가"
11215            | "افزودن_به_فهرست"
11216            | "أضف_للقائمة"
11217            | "הוסף_לרשימה"
11218            | "فہرست_میں_شامل_کرو"
11219            | "ajouter_liste"
11220            | "liste_anhängen"
11221            | "добавить_в_список" => {
11222                let lst = args
11223                    .first()
11224                    .cloned()
11225                    .unwrap_or(Value::List(Rc::new(vec![])));
11226                let val = args.get(1).cloned().unwrap_or(Value::Unit);
11227                if let Value::List(mut v) = lst {
11228                    Rc::make_mut(&mut v).push(val);
11229                    return Ok(Value::List(v));
11230                }
11231                return Ok(Value::List(Rc::new(vec![val])));
11232            },
11233            "list_get"
11234            | "รับรายการ"
11235            | "取元素"
11236            | "要素取得"
11237            | "요소가져오기"
11238            | "دریافت_از_فهرست"
11239            | "اجلب_من_القائمة"
11240            | "קבל_מרשימה"
11241            | "فہرست_سے_حاصل_کرو"
11242            | "obtenir_liste"
11243            | "liste_abrufen"
11244            | "получить_из_списка" => {
11245                // Borrow the list; clone only the element (was cloning the whole list).
11246                let i = self.arg_num(&args, 1, 0.0)? as usize;
11247                if let Some(Value::List(v)) = args.first() {
11248                    return Ok(v.get(i).cloned().unwrap_or(Value::Str(String::new())));
11249                }
11250                return Ok(Value::Str(String::new()));
11251            },
11252            // list_max(numbers, default) / list_min(numbers, default) — `default`
11253            // is returned for an empty list (there's no numeric identity element
11254            // to fall back to otherwise).
11255            "list_max" | "列表最大值" => {
11256                let lst = args
11257                    .first()
11258                    .cloned()
11259                    .unwrap_or(Value::List(Rc::new(vec![])));
11260                let default = self.arg_num(&args, 1, 0.0)?;
11261                if let Value::List(v) = lst {
11262                    let mut best = default;
11263                    let mut any = false;
11264                    for item in v.iter() {
11265                        if let Value::Number(n) = item {
11266                            if !any || *n > best {
11267                                best = *n;
11268                                any = true;
11269                            }
11270                        }
11271                    }
11272                    return Ok(Value::Number(best));
11273                }
11274                return Ok(Value::Number(default));
11275            },
11276            "list_min" | "列表最小值" => {
11277                let lst = args
11278                    .first()
11279                    .cloned()
11280                    .unwrap_or(Value::List(Rc::new(vec![])));
11281                let default = self.arg_num(&args, 1, 0.0)?;
11282                if let Value::List(v) = lst {
11283                    let mut best = default;
11284                    let mut any = false;
11285                    for item in v.iter() {
11286                        if let Value::Number(n) = item {
11287                            if !any || *n < best {
11288                                best = *n;
11289                                any = true;
11290                            }
11291                        }
11292                    }
11293                    return Ok(Value::Number(best));
11294                }
11295                return Ok(Value::Number(default));
11296            },
11297            // list_set(lst, idx, val) → new list with index replaced. Engine builtin
11298            // (O(n) one copy) to replace the O(n²) ling `ตั้งรายการ` that looped
11299            // list_push + list_get (each of which copied the whole list).
11300            "list_set"
11301            | "ตั้งรายการ"
11302            | "设元素"
11303            | "要素設定"
11304            | "요소설정"
11305            | "تنظیم_عنصر_فهرست"
11306            | "عيّن_عنصر_القائمة"
11307            | "קבע_איבר_רשימה"
11308            | "فہرست_سیٹ" => {
11309                let idx = self.arg_num(&args, 1, 0.0)? as usize;
11310                let mut ai = args.into_iter();
11311                let lst = ai.next().unwrap_or(Value::List(Rc::new(vec![])));
11312                ai.next(); // skip idx
11313                let val = ai.next().unwrap_or(Value::Unit);
11314                if let Value::List(mut v) = lst {
11315                    if idx < v.len() {
11316                        Rc::make_mut(&mut v)[idx] = val;
11317                    }
11318                    return Ok(Value::List(v));
11319                }
11320                return Ok(Value::List(Rc::new(vec![])));
11321            },
11322            "list_join"
11323            | "join"
11324            | "รวมรายการ"
11325            | "连接"
11326            | "連結"
11327            | "연결"
11328            | "پیوستن_فهرست"
11329            | "اربط_القائمة"
11330            | "חבר_רשימה"
11331            | "فہرست_جوڑو" => {
11332                let lst = args
11333                    .first()
11334                    .cloned()
11335                    .unwrap_or(Value::List(Rc::new(vec![])));
11336                let sep = args.get(1).map(|v| v.to_string()).unwrap_or_default();
11337                if let Value::List(v) = lst {
11338                    return Ok(Value::Str(
11339                        v.iter()
11340                            .map(|x| x.to_string())
11341                            .collect::<Vec<_>>()
11342                            .join(&sep),
11343                    ));
11344                }
11345                return Ok(Value::Str(String::new()));
11346            },
11347            // list_map/list_filter/list_find — take a closure. Necessary as real
11348            // builtins (not expressible in `.ling` itself): a bare-identifier call
11349            // `f(x)` where `f` is a local variable always resolves through
11350            // `call_named`, which only looks at top-level `fn` definitions by
11351            // design ("call-site locals are intentionally NOT visible to fns") —
11352            // so a closure held in a variable/parameter can't be invoked from
11353            // `.ling` source directly. These call it from the Rust side instead,
11354            // the same way `http_serve` already invokes route-handler closures.
11355            "list_map" | "映射列表" => {
11356                let lst = args
11357                    .first()
11358                    .cloned()
11359                    .unwrap_or(Value::List(Rc::new(vec![])));
11360                let f = args.get(1).cloned().unwrap_or(Value::Unit);
11361                if let Value::List(v) = lst {
11362                    let mut out = Vec::with_capacity(v.len());
11363                    for item in v.iter() {
11364                        out.push(self.call_value(f.clone(), vec![item.clone()])?);
11365                    }
11366                    return Ok(Value::List(Rc::new(out)));
11367                }
11368                return Ok(Value::List(Rc::new(vec![])));
11369            },
11370            "list_filter" | "过滤列表" => {
11371                let lst = args
11372                    .first()
11373                    .cloned()
11374                    .unwrap_or(Value::List(Rc::new(vec![])));
11375                let f = args.get(1).cloned().unwrap_or(Value::Unit);
11376                if let Value::List(v) = lst {
11377                    let mut out = Vec::new();
11378                    for item in v.iter() {
11379                        if matches!(
11380                            self.call_value(f.clone(), vec![item.clone()])?,
11381                            Value::Bool(true)
11382                        ) {
11383                            out.push(item.clone());
11384                        }
11385                    }
11386                    return Ok(Value::List(Rc::new(out)));
11387                }
11388                return Ok(Value::List(Rc::new(vec![])));
11389            },
11390            // First element for which `f` returns true, or Unit if none match.
11391            "list_find" | "查找列表" => {
11392                let lst = args
11393                    .first()
11394                    .cloned()
11395                    .unwrap_or(Value::List(Rc::new(vec![])));
11396                let f = args.get(1).cloned().unwrap_or(Value::Unit);
11397                if let Value::List(v) = lst {
11398                    for item in v.iter() {
11399                        if matches!(
11400                            self.call_value(f.clone(), vec![item.clone()])?,
11401                            Value::Bool(true)
11402                        ) {
11403                            return Ok(item.clone());
11404                        }
11405                    }
11406                }
11407                return Ok(Value::Unit);
11408            },
11409            // blob_f32("<deflate+base64>") / blob_i32(...) — decode an embedded,
11410            // losslessly-compressed numeric blob into a list. Produced by
11411            // `ling convert`; lets converted assets carry geometry/PCM/etc. compactly.
11412            #[cfg(not(target_arch = "wasm32"))]
11413            "blob_f32" | "blob_i32" => {
11414                let s = self.arg_str(&args, 0, "");
11415                let is_i32 = name == "blob_i32";
11416                match decode_blob(&s) {
11417                    Ok(bytes) => {
11418                        let mut out = Vec::with_capacity(bytes.len() / 4);
11419                        for ch in bytes.chunks_exact(4) {
11420                            let arr = [ch[0], ch[1], ch[2], ch[3]];
11421                            let n = if is_i32 {
11422                                i32::from_le_bytes(arr) as f64
11423                            } else {
11424                                f32::from_le_bytes(arr) as f64
11425                            };
11426                            out.push(Value::Number(n));
11427                        }
11428                        return Ok(Value::List(Rc::new(out)));
11429                    },
11430                    Err(e) => {
11431                        eprintln!("blob decode failed: {e}");
11432                        return Ok(Value::List(Rc::new(vec![])));
11433                    },
11434                }
11435            },
11436
11437            // ══════════════════════════════════════════════════════════════════
11438            // SVG EXPORT  (svg_begin / svg_rect / svg_circle / svg_line /
11439            //              svg_polyline / svg_text / svg_end / hsl_color)
11440            // Chinese aliases: 开始SVG 结束SVG SVG矩形 SVG圆形 SVG线段 SVG折线 SVG文本 HSL颜色
11441            // Thai aliases:    เริ่มSVG จบSVG SVGสี่เหลี่ยม SVGวงกลม SVGเส้น SVGเส้นหัก SVGข้อความ สีHSL
11442            // ══════════════════════════════════════════════════════════════════
11443            "svg_begin" | "开始SVG" | "เริ่มSVG" | "شروع_SVG" | "ابدأ_SVG" | "התחל_SVG"
11444            | "SVG_شروع" | "commencer_svg" | "svg_beginnen" | "начать_svg" => {
11445                let path = self.arg_str(&args, 0, "output.svg");
11446                let width = self.arg_num(&args, 1, 800.0)?;
11447                let height = self.arg_num(&args, 2, 600.0)?;
11448                *self.svg.borrow_mut() = Some(SvgWriter::new(path, width, height));
11449                return Ok(Value::Unit);
11450            },
11451
11452            "svg_rect"
11453            | "SVG矩形"
11454            | "SVGสี่เหลี่ยม"
11455            | "مستطیل_SVG"
11456            | "مستطيل_SVG"
11457            | "מלבן_SVG"
11458            | "SVG_مستطیل"
11459            | "rectangle_svg"
11460            | "svg_rechteck"
11461            | "прямоугольник_svg" => {
11462                let x = self.arg_num(&args, 0, 0.0)?;
11463                let y = self.arg_num(&args, 1, 0.0)?;
11464                let w = self.arg_num(&args, 2, 10.0)?;
11465                let h = self.arg_num(&args, 3, 10.0)?;
11466                let fill = self.arg_str(&args, 4, "#ffffff");
11467                if let Some(svg) = self.svg.borrow_mut().as_mut() {
11468                    svg.elements.push(format!(
11469                        "<rect x=\"{x:.1}\" y=\"{y:.1}\" width=\"{w:.1}\" \
11470                         height=\"{h:.1}\" fill=\"{fill}\"/>"
11471                    ));
11472                }
11473                return Ok(Value::Unit);
11474            },
11475
11476            "svg_circle" | "SVG圆形" | "SVGวงกลม" | "دایره_SVG" | "دائرة_SVG" | "עיגול_SVG"
11477            | "SVG_دائرہ" | "cercle_svg" | "svg_kreis" | "круг_svg" => {
11478                let cx = self.arg_num(&args, 0, 0.0)?;
11479                let cy = self.arg_num(&args, 1, 0.0)?;
11480                let r = self.arg_num(&args, 2, 5.0)?;
11481                let fill = self.arg_str(&args, 3, "#ffffff");
11482                if let Some(svg) = self.svg.borrow_mut().as_mut() {
11483                    svg.elements.push(format!(
11484                        "<circle cx=\"{cx:.1}\" cy=\"{cy:.1}\" r=\"{r:.1}\" fill=\"{fill}\"/>"
11485                    ));
11486                }
11487                return Ok(Value::Unit);
11488            },
11489
11490            "svg_line" | "SVG线段" | "SVGเส้น" | "خط_SVG" | "קו_SVG" | "SVG_لکیر" | "ligne_svg"
11491            | "svg_linie" | "линия_svg" => {
11492                let x1 = self.arg_num(&args, 0, 0.0)?;
11493                let y1 = self.arg_num(&args, 1, 0.0)?;
11494                let x2 = self.arg_num(&args, 2, 0.0)?;
11495                let y2 = self.arg_num(&args, 3, 0.0)?;
11496                let stroke = self.arg_str(&args, 4, "#ffffff");
11497                let sw = self.arg_num(&args, 5, 1.0)?;
11498                if let Some(svg) = self.svg.borrow_mut().as_mut() {
11499                    svg.elements.push(format!(
11500                        "<line x1=\"{x1:.1}\" y1=\"{y1:.1}\" x2=\"{x2:.1}\" y2=\"{y2:.1}\" \
11501                         stroke=\"{stroke}\" stroke-width=\"{sw:.1}\"/>"
11502                    ));
11503                }
11504                return Ok(Value::Unit);
11505            },
11506
11507            "svg_polyline"
11508            | "SVG折线"
11509            | "SVGเส้นหัก"
11510            | "چندخطی_SVG"
11511            | "خط_متعدد_SVG"
11512            | "קו_שבור_SVG"
11513            | "SVG_پولی_لائن"
11514            | "polyligne_svg"
11515            | "svg_polylinie"
11516            | "ломаная_svg" => {
11517                let pts = self.arg_str(&args, 0, "");
11518                let stroke = self.arg_str(&args, 1, "#ffffff");
11519                let sw = self.arg_num(&args, 2, 1.0)?;
11520                if let Some(svg) = self.svg.borrow_mut().as_mut() {
11521                    svg.elements.push(format!(
11522                        "<polyline points=\"{pts}\" fill=\"none\" \
11523                         stroke=\"{stroke}\" stroke-width=\"{sw:.1}\"/>"
11524                    ));
11525                }
11526                return Ok(Value::Unit);
11527            },
11528
11529            "svg_text"
11530            | "SVG文本"
11531            | "SVGข้อความ"
11532            | "متن_SVG"
11533            | "نص_SVG"
11534            | "טקסט_SVG"
11535            | "SVG_متن"
11536            | "texte_svg"
11537            | "текст_svg" => {
11538                let x = self.arg_num(&args, 0, 0.0)?;
11539                let y = self.arg_num(&args, 1, 0.0)?;
11540                let text = self.arg_str(&args, 2, "");
11541                let fill = self.arg_str(&args, 3, "#ffffff");
11542                let size = self.arg_num(&args, 4, 12.0)?;
11543                if let Some(svg) = self.svg.borrow_mut().as_mut() {
11544                    let safe = text
11545                        .replace('&', "&amp;")
11546                        .replace('<', "&lt;")
11547                        .replace('>', "&gt;");
11548                    svg.elements.push(format!(
11549                        "<text x=\"{x:.1}\" y=\"{y:.1}\" fill=\"{fill}\" \
11550                         font-family=\"monospace\" font-size=\"{size:.0}\">{safe}</text>"
11551                    ));
11552                }
11553                return Ok(Value::Unit);
11554            },
11555
11556            "svg_end"
11557            | "结束SVG"
11558            | "จบSVG"
11559            | "پایان_SVG"
11560            | "أنهِ_SVG"
11561            | "סיים_SVG"
11562            | "SVG_ختم"
11563            | "terminer_svg"
11564            | "svg_beenden"
11565            | "закончить_svg" => {
11566                {
11567                    let borrow = self.svg.borrow();
11568                    if let Some(svg) = borrow.as_ref() {
11569                        svg.save()
11570                            .map_err(|e| EvalErr::from(format!("svg_end: {e}")))?;
11571                    }
11572                }
11573                *self.svg.borrow_mut() = None;
11574                return Ok(Value::Unit);
11575            },
11576
11577            "hsl_color" | "HSL颜色" | "สีHSL" | "رنگ_HSL" | "لون_HSL" | "צבע_HSL" | "HSL_رنگ"
11578            | "couleur_hsl" | "hsl_farbe" | "цвет_hsl" => {
11579                let h = self.arg_num(&args, 0, 0.0)?;
11580                let s = self.arg_num(&args, 1, 70.0)?;
11581                let l = self.arg_num(&args, 2, 50.0)?;
11582                return Ok(Value::Str(hsl_to_hex(h, s, l)));
11583            },
11584
11585            // ══════════════════════════════════════════════════════════════════
11586            // FFT / AUDIO ANALYSIS BUILTINS  (native only)
11587            // ══════════════════════════════════════════════════════════════════
11588
11589            // fft_push(samples_list) — feed raw audio samples and run FFT
11590            #[cfg(not(target_arch = "wasm32"))]
11591            "fft_push"
11592            | "วิเคราะห์เสียง"
11593            | "频谱输入"
11594            | "FFT入力"
11595            | "FFT입력"
11596            | "ورودی_FFT"
11597            | "أدخل_FFT"
11598            | "הזנת_FFT"
11599            | "FFT_ان_پٹ"
11600            | "fft_entrée"
11601            | "fft_eingabe"
11602            | "fft_вход" => {
11603                if let Some(Value::List(v)) = args.first() {
11604                    let samples: Vec<f32> = v
11605                        .iter()
11606                        .filter_map(|x| {
11607                            if let Value::Number(n) = x {
11608                                Some(*n as f32)
11609                            } else {
11610                                None
11611                            }
11612                        })
11613                        .collect();
11614                    self.fft.borrow_mut().push_samples(&samples);
11615                }
11616                return Ok(Value::Unit);
11617            },
11618
11619            // fft_bands(n) → list of n log-spaced magnitude bands (0..1)
11620            #[cfg(not(target_arch = "wasm32"))]
11621            "fft_bands"
11622            | "แถบความถี่"
11623            | "频段"
11624            | "周波数帯"
11625            | "주파수대"
11626            | "باندهای_FFT"
11627            | "نطاقات_FFT"
11628            | "פסי_FFT"
11629            | "FFT_بینڈز"
11630            | "fft_bandes"
11631            | "fft_bänder"
11632            | "fft_полосы" => {
11633                let n = self.arg_num(&args, 0, 32.0)? as usize;
11634                let bands = self.fft.borrow().freq_bands(n);
11635                *self.fft_bands_cache.borrow_mut() = bands.clone();
11636                return Ok(Value::List(Rc::new(
11637                    bands.into_iter().map(|v| Value::Number(v as f64)).collect(),
11638                )));
11639            },
11640
11641            // fft_beat() → bool
11642            #[cfg(not(target_arch = "wasm32"))]
11643            "fft_beat"
11644            | "จังหวะเสียง"
11645            | "节拍检测"
11646            | "ビート検出"
11647            | "비트"
11648            | "ضرب_FFT"
11649            | "نبضة_FFT"
11650            | "פעימת_FFT"
11651            | "FFT_دھڑکن"
11652            | "fft_battement"
11653            | "fft_takt"
11654            | "fft_удар" => {
11655                return Ok(Value::Bool(self.fft.borrow().is_beat()));
11656            },
11657
11658            // fft_beat_ratio() → f64  (1.0 = at threshold, >1 = strong beat)
11659            #[cfg(not(target_arch = "wasm32"))]
11660            "fft_beat_ratio"
11661            | "อัตราจังหวะ"
11662            | "节拍比"
11663            | "ビート比"
11664            | "비트비율"
11665            | "نسبت_ضرب_FFT"
11666            | "نسبة_نبضة_FFT"
11667            | "יחס_פעימת_FFT"
11668            | "FFT_بیٹ_تناسب" => {
11669                return Ok(Value::Number(self.fft.borrow().beat_ratio() as f64));
11670            },
11671
11672            // fft_rms() → f64
11673            #[cfg(not(target_arch = "wasm32"))]
11674            "fft_rms"
11675            | "ระดับRMS"
11676            | "均方根"
11677            | "二乗平均"
11678            | "RMS레벨"
11679            | "RMS_صدا"
11680            | "جذر_متوسط_مربع_FFT"
11681            | "RMS_של_FFT"
11682            | "FFT_RMS" => {
11683                return Ok(Value::Number(self.fft.borrow().rms() as f64));
11684            },
11685
11686            // fft_dominant_freq() → f64  in Hz
11687            #[cfg(not(target_arch = "wasm32"))]
11688            "fft_dominant_freq"
11689            | "ความถี่หลัก"
11690            | "主频"
11691            | "主要周波数"
11692            | "주파수"
11693            | "فرکانس_غالب"
11694            | "التردد_السائد"
11695            | "תדר_דומיננטי"
11696            | "غالب_فریکوئنسی"
11697            | "fft_fréquence_dominante"
11698            | "fft_dominante_frequenz"
11699            | "fft_доминирующая_частота" => {
11700                return Ok(Value::Number(self.fft.borrow().dominant_freq() as f64));
11701            },
11702
11703            // ── wasm32 stubs: fft builtins are no-ops on web ───────────────
11704            #[cfg(target_arch = "wasm32")]
11705            "fft_push"
11706            | "วิเคราะห์เสียง"
11707            | "频谱输入"
11708            | "FFT入力"
11709            | "FFT입력"
11710            | "ورودی_FFT"
11711            | "أدخل_FFT"
11712            | "הזנת_FFT"
11713            | "FFT_ان_پٹ"
11714            | "fft_entrée"
11715            | "fft_eingabe"
11716            | "fft_вход" => {
11717                return Ok(Value::Unit);
11718            },
11719            #[cfg(target_arch = "wasm32")]
11720            "fft_bands"
11721            | "แถบความถี่"
11722            | "频段"
11723            | "周波数帯"
11724            | "주파수대"
11725            | "باندهای_FFT"
11726            | "نطاقات_FFT"
11727            | "פסי_FFT"
11728            | "FFT_بینڈز"
11729            | "fft_bandes"
11730            | "fft_bänder"
11731            | "fft_полосы" => {
11732                let n = self.arg_num(&args, 0, 32.0)? as usize;
11733                return Ok(Value::List(vec![Value::Number(0.0); n].into()));
11734            },
11735            #[cfg(target_arch = "wasm32")]
11736            "fft_beat"
11737            | "จังหวะเสียง"
11738            | "节拍检测"
11739            | "ビート検出"
11740            | "비트"
11741            | "ضرب_FFT"
11742            | "نبضة_FFT"
11743            | "פעימת_FFT"
11744            | "FFT_دھڑکن"
11745            | "fft_battement"
11746            | "fft_takt"
11747            | "fft_удар" => {
11748                return Ok(Value::Bool(false));
11749            },
11750            #[cfg(target_arch = "wasm32")]
11751            "fft_beat_ratio"
11752            | "อัตราจังหวะ"
11753            | "节拍比"
11754            | "ビート比"
11755            | "비트비율"
11756            | "نسبت_ضرب_FFT"
11757            | "نسبة_نبضة_FFT"
11758            | "יחס_פעימת_FFT"
11759            | "FFT_بیٹ_تناسب" => {
11760                return Ok(Value::Number(1.0));
11761            },
11762            #[cfg(target_arch = "wasm32")]
11763            "fft_rms"
11764            | "ระดับRMS"
11765            | "均方根"
11766            | "二乗平均"
11767            | "RMS레벨"
11768            | "RMS_صدا"
11769            | "جذر_متوسط_مربع_FFT"
11770            | "RMS_של_FFT"
11771            | "FFT_RMS" => {
11772                return Ok(Value::Number(0.0));
11773            },
11774            #[cfg(target_arch = "wasm32")]
11775            "fft_dominant_freq"
11776            | "ความถี่หลัก"
11777            | "主频"
11778            | "主要周波数"
11779            | "주파수"
11780            | "فرکانس_غالب"
11781            | "التردد_السائد"
11782            | "תדר_דומיננטי"
11783            | "غالب_فریکوئنسی"
11784            | "fft_fréquence_dominante"
11785            | "fft_dominante_frequenz"
11786            | "fft_доминирующая_частота" => {
11787                return Ok(Value::Number(0.0));
11788            },
11789
11790            // ══════════════════════════════════════════════════════════════════
11791            // PROCEDURAL TEXTURE BLIT BUILTINS  (screen-space)
11792            // All: name(dst_x, dst_y, width, height, ...params, palette)
11793            // palette: "rainbow" | "fire" | "ocean" | "psychedelic" | "neon" | "forest"
11794            // ══════════════════════════════════════════════════════════════════
11795
11796            // tex_checkerboard(x, y, w, h, tiles, r1,g1,b1, r2,g2,b2)
11797            "tex_checkerboard"
11798            | "ลายตารางหมากรุก"
11799            | "بافت_شطرنجی"
11800            | "نسيج_رقعة_الشطرنج"
11801            | "מרקם_שחמט"
11802            | "شطرنج_ٹیکسچر" => {
11803                let (tx, ty, tw, th) = self.tex_rect(&args)?;
11804                let tiles = self.arg_num(&args, 4, 8.0)? as u32;
11805                let (r1, g1, b1) = (
11806                    self.arg_num(&args, 5, 255.)? as u32,
11807                    self.arg_num(&args, 6, 255.)? as u32,
11808                    self.arg_num(&args, 7, 255.)? as u32,
11809                );
11810                let (r2, g2, b2) = (
11811                    self.arg_num(&args, 8, 0.)? as u32,
11812                    self.arg_num(&args, 9, 0.)? as u32,
11813                    self.arg_num(&args, 10, 0.)? as u32,
11814                );
11815                let c1 = (r1 << 16) | (g1 << 8) | b1;
11816                let c2 = (r2 << 16) | (g2 << 8) | b2;
11817                let mut gfx = self.gfx.borrow_mut();
11818                let (bw, bh) = (gfx.width, gfx.height);
11819                for row in 0..th {
11820                    for col in 0..tw {
11821                        let cx = col as u32 * tiles / tw as u32;
11822                        let cy = row as u32 * tiles / th as u32;
11823                        let (dx, dy) = (tx + col, ty + row);
11824                        if dx < bw && dy < bh {
11825                            gfx.buffer[dy * bw + dx] = if (cx + cy) % 2 == 0 { c1 } else { c2 };
11826                        }
11827                    }
11828                }
11829                return Ok(Value::Unit);
11830            },
11831
11832            // tex_gradient(x, y, w, h, angle_deg, r1,g1,b1, r2,g2,b2)
11833            "tex_gradient"
11834            | "ลายไล่สี"
11835            | "بافت_گرادیان"
11836            | "نسيج_متدرج"
11837            | "מרקם_גרדיאנט"
11838            | "گریڈینٹ_ٹیکسچر" => {
11839                let (tx, ty, tw, th) = self.tex_rect(&args)?;
11840                let angle = self.arg_num(&args, 4, 0.0)? as f32;
11841                let (r1, g1, b1) = (
11842                    self.arg_num(&args, 5, 0.)? as f32 / 255.,
11843                    self.arg_num(&args, 6, 0.)? as f32 / 255.,
11844                    self.arg_num(&args, 7, 0.)? as f32 / 255.,
11845                );
11846                let (r2, g2, b2) = (
11847                    self.arg_num(&args, 8, 255.)? as f32 / 255.,
11848                    self.arg_num(&args, 9, 255.)? as f32 / 255.,
11849                    self.arg_num(&args, 10, 255.)? as f32 / 255.,
11850                );
11851                let (ca, sa) = (angle.to_radians().cos(), angle.to_radians().sin());
11852                let mut gfx = self.gfx.borrow_mut();
11853                let (bw, bh) = (gfx.width, gfx.height);
11854                for row in 0..th {
11855                    for col in 0..tw {
11856                        let nx = col as f32 / tw as f32 - 0.5;
11857                        let ny = row as f32 / th as f32 - 0.5;
11858                        let t = ((nx * ca + ny * sa + 0.707) / 1.414).clamp(0., 1.);
11859                        let (dx, dy) = (tx + col, ty + row);
11860                        if dx < bw && dy < bh {
11861                            gfx.buffer[dy * bw + dx] =
11862                                tex_rgb(r1 + (r2 - r1) * t, g1 + (g2 - g1) * t, b1 + (b2 - b1) * t);
11863                        }
11864                    }
11865                }
11866                return Ok(Value::Unit);
11867            },
11868
11869            // tex_noise(x, y, w, h, scale, octaves, seed, palette)
11870            "tex_noise" | "ลายนอยส์" | "بافت_نویز" | "نسيج_ضجيج" | "מרקם_רעש" | "نوائز_ٹیکسچر" =>
11871            {
11872                let (tx, ty, tw, th) = self.tex_rect(&args)?;
11873                let scale = self.arg_num(&args, 4, 4.0)? as f32;
11874                let octaves = self.arg_num(&args, 5, 4.0)? as u32;
11875                let seed = self.arg_num(&args, 6, 0.0)? as u32;
11876                let palette = self.arg_str(&args, 7, "rainbow");
11877                let mut gfx = self.gfx.borrow_mut();
11878                let (bw, bh) = (gfx.width, gfx.height);
11879                for row in 0..th {
11880                    for col in 0..tw {
11881                        let v = tex_fbm(
11882                            col as f32 * scale / tw as f32,
11883                            row as f32 * scale / th as f32,
11884                            octaves,
11885                            seed,
11886                        );
11887                        let [r, g, b] = tex_palette(&palette, v);
11888                        let (dx, dy) = (tx + col, ty + row);
11889                        if dx < bw && dy < bh {
11890                            gfx.buffer[dy * bw + dx] = tex_rgb(r, g, b);
11891                        }
11892                    }
11893                }
11894                return Ok(Value::Unit);
11895            },
11896
11897            // tex_freq_map(x, y, w, h, time, speed, palette)
11898            // Uses bands written by the last fft_bands() call.
11899            "tex_freq_map"
11900            | "ลายความถี่"
11901            | "نقشه_فرکانس_بافت"
11902            | "خريطة_تردد_النسيج"
11903            | "מפת_תדר_מרקם"
11904            | "فریکوئنسی_میپ_ٹیکسچر" => {
11905                let (tx, ty, tw, th) = self.tex_rect(&args)?;
11906                let time = self.arg_num(&args, 4, 0.0)? as f32;
11907                let speed = self.arg_num(&args, 5, 0.3)? as f32;
11908                let palette = self.arg_str(&args, 6, "rainbow");
11909                let bands: Vec<f32> = {
11910                    let c = self.fft_bands_cache.borrow();
11911                    if c.is_empty() {
11912                        vec![0.0; 32]
11913                    } else {
11914                        c.clone()
11915                    }
11916                };
11917                let n = bands.len().max(1);
11918                let mut gfx = self.gfx.borrow_mut();
11919                let (bw, bh) = (gfx.width, gfx.height);
11920                for row in 0..th {
11921                    for col in 0..tw {
11922                        let band_idx = (col * n / tw.max(1)).min(n - 1);
11923                        let mag = bands[band_idx].clamp(0., 1.);
11924                        let fill_y = (mag * th as f32) as usize;
11925                        if row >= th.saturating_sub(fill_y) {
11926                            let t = (col as f32 / tw as f32 + time * speed) % 1.0;
11927                            let [r, g, b] = tex_palette(&palette, t);
11928                            let bright = mag * (1.0 - row as f32 / th as f32 * 0.5);
11929                            let (dx, dy) = (tx + col, ty + row);
11930                            if dx < bw && dy < bh {
11931                                gfx.buffer[dy * bw + dx] =
11932                                    tex_rgb(r * bright, g * bright, b * bright);
11933                            }
11934                        }
11935                    }
11936                }
11937                return Ok(Value::Unit);
11938            },
11939
11940            // tex_spiral(x, y, w, h, freq, bands, time, palette)
11941            "tex_spiral"
11942            | "ลายเกลียวหมุน"
11943            | "بافت_مارپیچ"
11944            | "نسيج_حلزوني"
11945            | "מרקם_ספירלה"
11946            | "سرپیچ_ٹیکسچر" => {
11947                let (tx, ty, tw, th) = self.tex_rect(&args)?;
11948                let freq = self.arg_num(&args, 4, 5.0)? as f32;
11949                let n_bands = self.arg_num(&args, 5, 8.0)? as f32;
11950                let time = self.arg_num(&args, 6, 0.0)? as f32;
11951                let palette = self.arg_str(&args, 7, "rainbow");
11952                let mut gfx = self.gfx.borrow_mut();
11953                let (bw, bh) = (gfx.width, gfx.height);
11954                for row in 0..th {
11955                    for col in 0..tw {
11956                        let nx = col as f32 / tw as f32 - 0.5;
11957                        let ny = row as f32 / th as f32 - 0.5;
11958                        let r = (nx * nx + ny * ny).sqrt();
11959                        let theta = ny.atan2(nx);
11960                        let t = ((r * freq - theta / std::f32::consts::TAU + time * 0.5) * n_bands
11961                            % 1.0)
11962                            .abs();
11963                        let [cr, cg, cb] = tex_palette(&palette, t);
11964                        let (dx, dy) = (tx + col, ty + row);
11965                        if dx < bw && dy < bh {
11966                            gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
11967                        }
11968                    }
11969                }
11970                return Ok(Value::Unit);
11971            },
11972
11973            // tex_ripple(x, y, w, h, freq, cx, cy, time, palette)
11974            "tex_ripple" | "ลายระลอก" | "بافت_موج" | "نسيج_تموج" | "מרקם_אדווה" | "ریپل_ٹیکسچر" =>
11975            {
11976                let (tx, ty, tw, th) = self.tex_rect(&args)?;
11977                let freq = self.arg_num(&args, 4, 10.0)? as f32;
11978                let rcx = self.arg_num(&args, 5, 0.5)? as f32;
11979                let rcy = self.arg_num(&args, 6, 0.5)? as f32;
11980                let time = self.arg_num(&args, 7, 0.0)? as f32;
11981                let palette = self.arg_str(&args, 8, "ocean");
11982                let mut gfx = self.gfx.borrow_mut();
11983                let (bw, bh) = (gfx.width, gfx.height);
11984                for row in 0..th {
11985                    for col in 0..tw {
11986                        let nx = col as f32 / tw as f32 - rcx;
11987                        let ny = row as f32 / th as f32 - rcy;
11988                        let r = (nx * nx + ny * ny).sqrt();
11989                        let t = ((r * freq - time) % 1.0).abs();
11990                        let [cr, cg, cb] = tex_palette(&palette, t);
11991                        let (dx, dy) = (tx + col, ty + row);
11992                        if dx < bw && dy < bh {
11993                            gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
11994                        }
11995                    }
11996                }
11997                return Ok(Value::Unit);
11998            },
11999
12000            // tex_mandelbrot(x, y, w, h, zoom, cx, cy, max_iter, palette)
12001            "tex_mandelbrot"
12002            | "ลายแมนเดลบรอต"
12003            | "بافت_ماندلبرو"
12004            | "نسيج_مانديلبروت"
12005            | "מרקם_מנדלברוט"
12006            | "مینڈل_بروٹ_ٹیکسچر" => {
12007                let (tx, ty, tw, th) = self.tex_rect(&args)?;
12008                let zoom = self.arg_num(&args, 4, 1.0)?;
12009                let mcx = self.arg_num(&args, 5, -0.5)?;
12010                let mcy = self.arg_num(&args, 6, 0.0)?;
12011                let max_iter = self.arg_num(&args, 7, 64.0)? as u32;
12012                let palette = self.arg_str(&args, 8, "psychedelic");
12013                let mut gfx = self.gfx.borrow_mut();
12014                let (bw, bh) = (gfx.width, gfx.height);
12015                for row in 0..th {
12016                    for col in 0..tw {
12017                        let zx0 = (col as f64 / tw as f64 - 0.5) / zoom + mcx;
12018                        let zy0 = (row as f64 / th as f64 - 0.5) / zoom + mcy;
12019                        let mut x = 0.0f64;
12020                        let mut y = 0.0f64;
12021                        let mut i = 0u32;
12022                        while i < max_iter && x * x + y * y < 4.0 {
12023                            let t = x * x - y * y + zx0;
12024                            y = 2.0 * x * y + zy0;
12025                            x = t;
12026                            i += 1;
12027                        }
12028                        let t = if i == max_iter {
12029                            0.0f32
12030                        } else {
12031                            (i as f32
12032                                - (x as f32 * x as f32 + y as f32 * y as f32).ln().ln()
12033                                    / 2.0f32.ln())
12034                                / max_iter as f32
12035                        };
12036                        let [cr, cg, cb] = tex_palette(&palette, t.clamp(0., 1.));
12037                        let (dx, dy) = (tx + col, ty + row);
12038                        if dx < bw && dy < bh {
12039                            gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
12040                        }
12041                    }
12042                }
12043                return Ok(Value::Unit);
12044            },
12045
12046            // tex_julia(x, y, w, h, c_re, c_im, max_iter, palette)
12047            "tex_julia"
12048            | "ลายจูเลีย"
12049            | "بافت_ژولیا"
12050            | "نسيج_جوليا"
12051            | "מרקם_ג'וליה"
12052            | "جولیا_ٹیکسچر" => {
12053                let (tx, ty, tw, th) = self.tex_rect(&args)?;
12054                let c_re = self.arg_num(&args, 4, -0.7)?;
12055                let c_im = self.arg_num(&args, 5, 0.27)?;
12056                let max_iter = self.arg_num(&args, 6, 64.0)? as u32;
12057                let palette = self.arg_str(&args, 7, "neon");
12058                let mut gfx = self.gfx.borrow_mut();
12059                let (bw, bh) = (gfx.width, gfx.height);
12060                for row in 0..th {
12061                    for col in 0..tw {
12062                        let mut zx = (col as f64 / tw as f64 - 0.5) * 3.5;
12063                        let mut zy = (row as f64 / th as f64 - 0.5) * 3.5;
12064                        let mut i = 0u32;
12065                        while i < max_iter && zx * zx + zy * zy < 4.0 {
12066                            let t = zx * zx - zy * zy + c_re;
12067                            zy = 2.0 * zx * zy + c_im;
12068                            zx = t;
12069                            i += 1;
12070                        }
12071                        let t = i as f32 / max_iter as f32;
12072                        let [cr, cg, cb] = tex_palette(&palette, t);
12073                        let (dx, dy) = (tx + col, ty + row);
12074                        if dx < bw && dy < bh {
12075                            gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
12076                        }
12077                    }
12078                }
12079                return Ok(Value::Unit);
12080            },
12081
12082            // tex_voronoi(x, y, w, h, cells, seed, palette)
12083            "tex_voronoi"
12084            | "ลายโวโรนอย"
12085            | "بافت_ورونوی"
12086            | "نسيج_فورونوي"
12087            | "מרקם_וורונוי"
12088            | "ورونوئی_ٹیکسچر" => {
12089                let (tx, ty, tw, th) = self.tex_rect(&args)?;
12090                let cells = self.arg_num(&args, 4, 16.0)? as u32;
12091                let seed = self.arg_num(&args, 5, 42.0)? as u32;
12092                let palette = self.arg_str(&args, 6, "rainbow");
12093                let pts: Vec<[f32; 2]> = (0..cells)
12094                    .map(|i| {
12095                        [
12096                            tex_hash(i as i32, 0, seed),
12097                            tex_hash(i as i32, 1, seed + 999),
12098                        ]
12099                    })
12100                    .collect();
12101                let mut gfx = self.gfx.borrow_mut();
12102                let (bw, bh) = (gfx.width, gfx.height);
12103                for row in 0..th {
12104                    for col in 0..tw {
12105                        let (fx, fy) = (col as f32 / tw as f32, row as f32 / th as f32);
12106                        let (min_d, nearest) = pts.iter().enumerate().fold(
12107                            (f32::MAX, 0usize),
12108                            |(d, idx), (i, &[cx, cy])| {
12109                                let dd = (fx - cx).powi(2) + (fy - cy).powi(2);
12110                                if dd < d {
12111                                    (dd, i)
12112                                } else {
12113                                    (d, idx)
12114                                }
12115                            },
12116                        );
12117                        let t = (nearest as f32 / cells as f32 + min_d * 4.0) % 1.0;
12118                        let [cr, cg, cb] = tex_palette(&palette, t);
12119                        let (dx, dy) = (tx + col, ty + row);
12120                        if dx < bw && dy < bh {
12121                            gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
12122                        }
12123                    }
12124                }
12125                return Ok(Value::Unit);
12126            },
12127
12128            // tex_halftone(x, y, w, h, dot_size, time, palette)
12129            "tex_halftone"
12130            | "ลายฮาล์ฟโทน"
12131            | "بافت_نیم‌تن"
12132            | "نسيج_نصفي"
12133            | "מרקם_חצי_גוון"
12134            | "ہاف_ٹون_ٹیکسچر" => {
12135                let (tx, ty, tw, th) = self.tex_rect(&args)?;
12136                let dot_size = self.arg_num(&args, 4, 0.05)? as f32;
12137                let time = self.arg_num(&args, 5, 0.0)? as f32;
12138                let palette = self.arg_str(&args, 6, "rainbow");
12139                let mut gfx = self.gfx.borrow_mut();
12140                let (bw, bh) = (gfx.width, gfx.height);
12141                for row in 0..th {
12142                    for col in 0..tw {
12143                        let (fx, fy) = (col as f32 / tw as f32, row as f32 / th as f32);
12144                        let gx = (fx / dot_size).floor();
12145                        let gy = (fy / dot_size).floor();
12146                        let lx = (fx / dot_size - gx - 0.5) * 2.0;
12147                        let ly = (fy / dot_size - gy - 0.5) * 2.0;
12148                        let r = (lx * lx + ly * ly).sqrt();
12149                        let t = (gx / (1.0 / dot_size) + time * 0.1) % 1.0;
12150                        let a = if r < 0.7 {
12151                            ((0.7 - r) / 0.7).clamp(0., 1.)
12152                        } else {
12153                            0.0
12154                        };
12155                        if a > 0.0 {
12156                            let [cr, cg, cb] = tex_palette(&palette, t);
12157                            let (dx, dy) = (tx + col, ty + row);
12158                            if dx < bw && dy < bh {
12159                                gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
12160                            }
12161                        }
12162                    }
12163                }
12164                return Ok(Value::Unit);
12165            },
12166
12167            // ══════════════════════════════════════════════════════════════════
12168            // RENDER / LIGHTING MODES  (holographic cel shading)
12169            // ══════════════════════════════════════════════════════════════════
12170            // set_shade_mode(m) — 0 flat · 1 cel · 2 holo (default)
12171            "set_shade_mode"
12172            | "设置着色"
12173            | "シェード設定"
12174            | "셰이드모드"
12175            | "ตั้งการแรเงา"
12176            | "تنظیم_حالت_سایه‌پردازی"
12177            | "عيّن_نمط_التظليل"
12178            | "קבע_מצב_הצללה"
12179            | "شیڈ_موڈ_مقرر_کرو"
12180            | "définir_mode_ombrage"
12181            | "schattierungsmodus_setzen"
12182            | "задать_режим_затенения" => {
12183                let m = self.arg_num(&args, 0, 2.0)? as u8;
12184                self.gfx.borrow_mut().shade_mode = m;
12185                return Ok(Value::Unit);
12186            },
12187            // set_cel_bands(n) — number of posterisation bands (>=2)
12188            "set_cel_bands"
12189            | "设置色阶"
12190            | "セル段数"
12191            | "셀밴드"
12192            | "ตั้งระดับสี"
12193            | "تنظیم_باندهای_سل"
12194            | "عيّن_نطاقات_التظليل"
12195            | "קבע_רצועות_הצללה"
12196            | "سیل_بینڈز_مقرر_کرو"
12197            | "définir_bandes_cel"
12198            | "cel_bänder_setzen"
12199            | "задать_полосы_cel" => {
12200                let n = (self.arg_num(&args, 0, 4.0)? as u32).max(2);
12201                self.gfx.borrow_mut().shade.bands = n;
12202                return Ok(Value::Unit);
12203            },
12204            // set_shadow_color(r,g,b) — coloured-shadow tint, 0-255
12205            "set_shadow_color"
12206            | "设置阴影色"
12207            | "影の色"
12208            | "그림자색"
12209            | "ตั้งสีเงา"
12210            | "تنظیم_رنگ_سایه"
12211            | "عيّن_لون_الظل"
12212            | "קבע_צבע_צל"
12213            | "سایہ_رنگ_مقرر_کرو"
12214            | "définir_couleur_ombre"
12215            | "schattenfarbe_setzen"
12216            | "задать_цвет_тени" => {
12217                let r = self.arg_num(&args, 0, 26.)? as f32 / 255.0;
12218                let g = self.arg_num(&args, 1, 33.)? as f32 / 255.0;
12219                let b = self.arg_num(&args, 2, 77.)? as f32 / 255.0;
12220                self.gfx.borrow_mut().shade.shadow = [r, g, b];
12221                return Ok(Value::Unit);
12222            },
12223            // set_rim(strength, r,g,b) — holographic fresnel edge glow
12224            // ══════════════════════════════════════════════════════════════════
12225            // CRYPTOGRAPHY (ling-crypto) — geo suite, hybrid PQ KEM, holographic
12226            // Bytes cross the language boundary as lowercase hex strings.
12227            // ══════════════════════════════════════════════════════════════════
12228            #[cfg(not(target_arch = "wasm32"))]
12229            "crypto_hash"
12230            | "แฮชเข้ารหัส"
12231            | "几何哈希"
12232            | "幾何ハッシュ"
12233            | "기하해시"
12234            | "درهم_رمزنگاری"
12235            | "بصمة_تشفير"
12236            | "גיבוב_הצפנה"
12237            | "خفیہ_ہیش"
12238            | "hachage_crypto"
12239            | "krypto_hash"
12240            | "крипто_хеш" => {
12241                let s = self.arg_str(&args, 0, "");
12242                return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(
12243                    s.as_bytes(),
12244                ))));
12245            },
12246            #[cfg(target_arch = "wasm32")]
12247            "crypto_hash"
12248            | "แฮชเข้ารหัส"
12249            | "几何哈希"
12250            | "幾何ハッシュ"
12251            | "기하해시"
12252            | "درهم_رمزنگاری"
12253            | "بصمة_تشفير"
12254            | "גיבוב_הצפנה"
12255            | "خفیہ_ہیش"
12256            | "hachage_crypto"
12257            | "krypto_hash"
12258            | "крипто_хеш" => {
12259                let s = self.arg_str(&args, 0, "");
12260                return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(
12261                    s.as_bytes(),
12262                ))));
12263            },
12264            // 3-D torus-knot fingerprint of any text/key → flat [x,y,z, x,y,z, …]
12265            #[cfg(not(target_arch = "wasm32"))]
12266            "knot_points"
12267            | "จุดปม"
12268            | "结点坐标"
12269            | "結び目点"
12270            | "매듭점"
12271            | "نقاط_گره"
12272            | "نقاط_العقدة"
12273            | "נקודות_קשר"
12274            | "گرہ_پوائنٹس"
12275            | "points_nœud"
12276            | "knotenpunkte"
12277            | "точки_узла" => {
12278                let s = self.arg_str(&args, 0, "");
12279                let shape = ling_crypto::geo::KnotShape::from_bytes(s.as_bytes());
12280                let mut out = Vec::with_capacity(shape.points.len() * 3);
12281                for p in &shape.points {
12282                    out.push(Value::Number(p[0] as f64));
12283                    out.push(Value::Number(p[1] as f64));
12284                    out.push(Value::Number(p[2] as f64));
12285                }
12286                return Ok(Value::List(Rc::new(out)));
12287            },
12288            #[cfg(target_arch = "wasm32")]
12289            "knot_points"
12290            | "จุดปม"
12291            | "结点坐标"
12292            | "結び目点"
12293            | "매듭점"
12294            | "نقاط_گره"
12295            | "نقاط_العقدة"
12296            | "נקודות_קשר"
12297            | "گرہ_پوائنٹس"
12298            | "points_nœud"
12299            | "knotenpunkte"
12300            | "точки_узла" => {
12301                let s = self.arg_str(&args, 0, "");
12302                let shape = ling_crypto::geo::KnotShape::from_bytes(s.as_bytes());
12303                let mut out = Vec::with_capacity(shape.points.len() * 3);
12304                for p in &shape.points {
12305                    out.push(Value::Number(p[0] as f64));
12306                    out.push(Value::Number(p[1] as f64));
12307                    out.push(Value::Number(p[2] as f64));
12308                }
12309                return Ok(Value::List(out.into()));
12310            },
12311            #[cfg(not(target_arch = "wasm32"))]
12312            "knot_label"
12313            | "ป้ายปม"
12314            | "结点标签"
12315            | "結び目ラベル"
12316            | "매듭라벨"
12317            | "برچسب_گره"
12318            | "تسمية_العقدة"
12319            | "תווית_קשר"
12320            | "گرہ_لیبل"
12321            | "étiquette_nœud"
12322            | "knotenbezeichnung"
12323            | "метка_узла" => {
12324                let s = self.arg_str(&args, 0, "");
12325                return Ok(Value::Str(
12326                    ling_crypto::geo::KnotShape::from_bytes(s.as_bytes()).label(),
12327                ));
12328            },
12329            #[cfg(target_arch = "wasm32")]
12330            "knot_label"
12331            | "ป้ายปม"
12332            | "结点标签"
12333            | "結び目ラベル"
12334            | "매듭라벨"
12335            | "برچسب_گره"
12336            | "تسمية_العقدة"
12337            | "תווית_קשר"
12338            | "گرہ_لیبل"
12339            | "étiquette_nœud"
12340            | "knotenbezeichnung"
12341            | "метка_узла" => {
12342                let s = self.arg_str(&args, 0, "");
12343                return Ok(Value::Str(
12344                    ling_crypto::geo::KnotShape::from_bytes(s.as_bytes()).label(),
12345                ));
12346            },
12347            // KEM keypair (hybrid X25519+ML-KEM-768) → integer handle
12348            #[cfg(not(target_arch = "wasm32"))]
12349            "knot_keygen"
12350            | "hybrid_keygen"
12351            | "สร้างกุญแจปม"
12352            | "生成密钥"
12353            | "鍵生成"
12354            | "키생성"
12355            | "تولید_کلید_گره"
12356            | "توليد_مفتاح_العقدة"
12357            | "יצירת_מפתח_קשר"
12358            | "گرہ_کلید_تخلیق"
12359            | "génération_clé_nœud"
12360            | "knotenschlüsselerzeugung"
12361            | "генерация_ключа_узла" => {
12362                self.crypto_ids.push(ling_crypto::KnotIdentity::generate());
12363                return Ok(Value::Number((self.crypto_ids.len() - 1) as f64));
12364            },
12365            #[cfg(not(target_arch = "wasm32"))]
12366            "knot_public"
12367            | "hybrid_public"
12368            | "กุญแจสาธารณะปม"
12369            | "公钥"
12370            | "公開鍵"
12371            | "공개키"
12372            | "کلید_عمومی_گره"
12373            | "مفتاح_العقدة_العام"
12374            | "מפתח_ציבורי_קשר"
12375            | "گرہ_عوامی_کلید"
12376            | "clé_publique_nœud"
12377            | "knoten_öffentlicher_schlüssel"
12378            | "публичный_ключ_узла" => {
12379                let h = self.arg_num(&args, 0, 0.0)? as usize;
12380                let pk = self
12381                    .crypto_ids
12382                    .get(h)
12383                    .map(|id| hex_encode(id.public_key()))
12384                    .unwrap_or_default();
12385                return Ok(Value::Str(pk));
12386            },
12387            // encapsulate(pubkey_hex) → [ciphertext_hex, shared_secret_hex]
12388            #[cfg(not(target_arch = "wasm32"))]
12389            "knot_encapsulate"
12390            | "hybrid_encapsulate"
12391            | "ห่อกุญแจปม"
12392            | "封装密钥"
12393            | "カプセル化"
12394            | "캡슐화"
12395            | "کپسوله‌سازی_گره"
12396            | "تغليف_مفتاح_العقدة"
12397            | "עטיפת_קשר"
12398            | "گرہ_احاطہ"
12399            | "encapsuler_nœud"
12400            | "knoten_kapseln"
12401            | "инкапсулировать_узел" => {
12402                let pk = hex_decode(&self.arg_str(&args, 0, ""));
12403                match ling_crypto::geo::knot_encapsulate(&pk) {
12404                    Ok((ct, ss)) => {
12405                        return Ok(Value::List(Rc::new(vec![
12406                            Value::Str(hex_encode(&ct)),
12407                            Value::Str(hex_encode(&ss)),
12408                        ])))
12409                    },
12410                    Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
12411                }
12412            },
12413            // decapsulate(handle, ciphertext_hex) → shared_secret_hex
12414            #[cfg(not(target_arch = "wasm32"))]
12415            "knot_decapsulate"
12416            | "hybrid_decapsulate"
12417            | "แกะกุญแจปม"
12418            | "解封装密钥"
12419            | "カプセル解除"
12420            | "캡슐해제"
12421            | "بازکردن_کپسوله_گره"
12422            | "فك_تغليف_مفتاح_العقدة"
12423            | "פתיחת_עטיפת_קשר"
12424            | "گرہ_احاطہ_کھولو"
12425            | "décapsuler_nœud"
12426            | "knoten_entkapseln"
12427            | "декапсулировать_узел" => {
12428                let h = self.arg_num(&args, 0, 0.0)? as usize;
12429                let ct = hex_decode(&self.arg_str(&args, 1, ""));
12430                let ss = self
12431                    .crypto_ids
12432                    .get(h)
12433                    .and_then(|id| id.decapsulate(&ct).ok())
12434                    .map(|s| hex_encode(&s))
12435                    .unwrap_or_default();
12436                return Ok(Value::Str(ss));
12437            },
12438            // Authenticated encryption (XChaCha20-Poly1305) — seal(key_hex, text) → ct_hex
12439            #[cfg(not(target_arch = "wasm32"))]
12440            "crypto_seal"
12441            | "ผนึก"
12442            | "封印"
12443            | "封印する"
12444            | "봉인"
12445            | "مهر_رمزنگاری"
12446            | "ختم_تشفير"
12447            | "חתימת_הצפנה"
12448            | "خفیہ_مہر"
12449            | "sceller_crypto"
12450            | "krypto_versiegeln"
12451            | "запечатать_крипто" => {
12452                let key = hex_to_32(&self.arg_str(&args, 0, ""));
12453                let pt = self.arg_str(&args, 1, "");
12454                match ling_crypto::geo::holo_seal(key, pt.as_bytes()) {
12455                    Ok(ct) => return Ok(Value::Str(hex_encode(&ct))),
12456                    Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
12457                }
12458            },
12459            #[cfg(not(target_arch = "wasm32"))]
12460            "crypto_open"
12461            | "เปิดผนึก"
12462            | "解封"
12463            | "封印解除"
12464            | "봉인해제"
12465            | "بازکردن_مهر"
12466            | "فتح_الختم"
12467            | "פתיחת_חתימה"
12468            | "مہر_کھولو"
12469            | "ouvrir_crypto"
12470            | "krypto_öffnen"
12471            | "открыть_крипто" => {
12472                let key = hex_to_32(&self.arg_str(&args, 0, ""));
12473                let ct = hex_decode(&self.arg_str(&args, 1, ""));
12474                match ling_crypto::geo::holo_open(key, &ct) {
12475                    Ok(pt) => return Ok(Value::Str(String::from_utf8_lossy(&pt).into_owned())),
12476                    Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
12477                }
12478            },
12479            // Holographic all-or-nothing transform — 4-D fragment coords [a,b,c,d, …]
12480            #[cfg(not(target_arch = "wasm32"))]
12481            "holo_points"
12482            | "จุดโฮโลแกรม"
12483            | "全息点"
12484            | "ホログラム点"
12485            | "홀로그램점"
12486            | "نقاط_هولوگرام"
12487            | "نقاط_الهولوغرام"
12488            | "נקודות_הולוגרמה"
12489            | "ہولوگرام_پوائنٹس"
12490            | "points_holo"
12491            | "holo_punkte"
12492            | "точки_голо" => {
12493                let s = self.arg_str(&args, 0, "");
12494                let frags = ling_crypto::geo::scatter(s.as_bytes());
12495                let mut out = Vec::with_capacity(frags.len() * 4);
12496                for f in &frags {
12497                    for c in f.coord {
12498                        out.push(Value::Number(c as f64));
12499                    }
12500                }
12501                return Ok(Value::List(Rc::new(out)));
12502            },
12503            #[cfg(not(target_arch = "wasm32"))]
12504            "holo_fragment_count"
12505            | "จำนวนชิ้นโฮโลแกรม"
12506            | "全息碎片数"
12507            | "ホログラム断片数"
12508            | "홀로그램조각수"
12509            | "تعداد_قطعات_هولوگرام"
12510            | "عدد_شظايا_الهولوغرام"
12511            | "מספר_שברי_הולוגרמה"
12512            | "ہولوگرام_ٹکڑے_تعداد"
12513            | "nombre_fragments_holo"
12514            | "holo_fragmentanzahl"
12515            | "число_фрагментов_голо" => {
12516                let s = self.arg_str(&args, 0, "");
12517                return Ok(Value::Number(
12518                    ling_crypto::geo::scatter(s.as_bytes()).len() as f64
12519                ));
12520            },
12521            // SHAKE-256 XOF, squeezed to an arbitrary output length in bytes
12522            // (`shake_hex(s, 128)` = a 1024-bit seal digest).
12523            #[cfg(not(target_arch = "wasm32"))]
12524            "shake_hex" | "SHAKE哈希" => {
12525                let s = self.arg_str(&args, 0, "");
12526                let len = self.arg_num(&args, 1, 32.0)?.max(0.0) as usize;
12527                return Ok(Value::Str(hex_encode(&ling_crypto::Shake256::hash(
12528                    s.as_bytes(),
12529                    len,
12530                ))));
12531            },
12532            // Ed25519 signing keypair (issuer identity) → integer handle.
12533            #[cfg(not(target_arch = "wasm32"))]
12534            "ed25519_keygen"
12535            | "생성서명키"
12536            | "สร้างกุญแจลายเซ็น"
12537            | "生成签名密钥"
12538            | "署名鍵生成"
12539            | "تولید_کلید_امضا"
12540            | "توليد_مفتاح_التوقيع"
12541            | "יצירת_מפתח_חתימה"
12542            | "دستخط_کلید_تخلیق"
12543            | "génération_clé_signature"
12544            | "signaturschlüsselerzeugung"
12545            | "генерация_ключа_подписи" => {
12546                self.ed25519_ids
12547                    .push(ling_crypto::Ed25519Keypair::generate());
12548                return Ok(Value::Number((self.ed25519_ids.len() - 1) as f64));
12549            },
12550            // Deterministic keypair from a 32-byte hex seed — the same seed
12551            // always yields the same keypair, so a program can persist just the
12552            // seed (e.g. a bank's issuer identity) and rederive identical keys
12553            // across restarts instead of every run minting a fresh, unrelated one.
12554            #[cfg(not(target_arch = "wasm32"))]
12555            "ed25519_keygen_from_seed"
12556            | "씨앗에서생성서명키"
12557            | "สร้างกุญแจลายเซ็นจากเมล็ด"
12558            | "从种子生成签名密钥"
12559            | "シードから署名鍵生成"
12560            | "تولید_کلید_امضا_از_دانه"
12561            | "توليد_مفتاح_التوقيع_من_البذرة"
12562            | "יצירת_מפתח_חתימה_מזרע"
12563            | "بیج_سے_دستخط_کلید_تخلیق"
12564            | "génération_clé_signature_depuis_graine"
12565            | "signaturschlüsselerzeugung_aus_saat"
12566            | "генерация_ключа_подписи_из_семени" => {
12567                let seed = hex_to_32(&self.arg_str(&args, 0, ""));
12568                self.ed25519_ids
12569                    .push(ling_crypto::Ed25519Keypair::from_seed(seed));
12570                return Ok(Value::Number((self.ed25519_ids.len() - 1) as f64));
12571            },
12572            #[cfg(not(target_arch = "wasm32"))]
12573            "ed25519_public"
12574            | "서명공개키"
12575            | "กุญแจสาธารณะลายเซ็น"
12576            | "签名公钥"
12577            | "署名公開鍵"
12578            | "کلید_عمومی_امضا"
12579            | "مفتاح_التوقيع_العام"
12580            | "מפתח_חתימה_ציבורי"
12581            | "دستخط_عوامی_کلید"
12582            | "clé_publique_signature"
12583            | "signatur_öffentlicher_schlüssel"
12584            | "публичный_ключ_подписи" => {
12585                let h = self.arg_num(&args, 0, 0.0)? as usize;
12586                let pk = self
12587                    .ed25519_ids
12588                    .get(h)
12589                    .map(|kp| hex_encode(&kp.public_key()))
12590                    .unwrap_or_default();
12591                return Ok(Value::Str(pk));
12592            },
12593            // ed25519_sign(handle, message) → signature hex (64 bytes)
12594            #[cfg(not(target_arch = "wasm32"))]
12595            "ed25519_sign"
12596            | "서명하다"
12597            | "เซ็นชื่อ"
12598            | "签名"
12599            | "署名する"
12600            | "امضا_کردن"
12601            | "توقيع"
12602            | "לחתום"
12603            | "دستخط_کریں"
12604            | "signer"
12605            | "signieren"
12606            | "подписать" => {
12607                let h = self.arg_num(&args, 0, 0.0)? as usize;
12608                let msg = self.arg_str(&args, 1, "");
12609                let sig = self
12610                    .ed25519_ids
12611                    .get(h)
12612                    .map(|kp| hex_encode(&kp.sign(msg.as_bytes())))
12613                    .unwrap_or_default();
12614                return Ok(Value::Str(sig));
12615            },
12616            // ed25519_verify(pubkey_hex, message, signature_hex) → bool
12617            #[cfg(not(target_arch = "wasm32"))]
12618            "ed25519_verify"
12619            | "서명확인"
12620            | "ยืนยันลายเซ็น"
12621            | "验证签名"
12622            | "署名検証"
12623            | "تایید_امضا"
12624            | "التحقق_من_التوقيع"
12625            | "אימות_חתימה"
12626            | "دستخط_تصدیق"
12627            | "vérifier_signature"
12628            | "signatur_verifizieren"
12629            | "проверить_подпись" => {
12630                let pk_hex = self.arg_str(&args, 0, "");
12631                let msg = self.arg_str(&args, 1, "");
12632                let sig_hex = self.arg_str(&args, 2, "");
12633                let pk_bytes = hex_decode(&pk_hex);
12634                let sig_bytes = hex_decode(&sig_hex);
12635                let ok = (|| {
12636                    let pk: [u8; 32] = pk_bytes.try_into().ok()?;
12637                    let sig: [u8; 64] = sig_bytes.try_into().ok()?;
12638                    Some(ling_crypto::Ed25519Keypair::verify(&pk, msg.as_bytes(), &sig).is_ok())
12639                })()
12640                .unwrap_or(false);
12641                return Ok(Value::Bool(ok));
12642            },
12643            // ML-DSA-65 (FIPS 204, post-quantum) signing keypair → integer
12644            // handle. Meant to be composed with ed25519_* by application code
12645            // into a hybrid signature (verify passes only if both check out),
12646            // not as a replacement for Ed25519 on its own. Aliases below mirror
12647            // the ed25519_* ones with each language's word for "quantum" folded
12648            // in, the same way knot_keygen/hybrid_keygen distinguish themselves.
12649            #[cfg(not(target_arch = "wasm32"))]
12650            "mldsa_keygen"
12651            | "양자서명키생성"
12652            | "สร้างกุญแจลายเซ็นควอนตัม"
12653            | "生成量子签名密钥"
12654            | "量子署名鍵生成"
12655            | "تولید_کلید_امضای_کوانتومی"
12656            | "توليد_مفتاح_التوقيع_الكمي"
12657            | "יצירת_מפתח_חתימה_קוונטי"
12658            | "کوانٹم_دستخط_کلید_تخلیق"
12659            | "génération_clé_signature_quantique"
12660            | "quantensignaturschlüsselerzeugung"
12661            | "генерация_ключа_квантовой_подписи" => {
12662                self.mldsa_ids.push(ling_crypto::MlDsa65Keypair::generate());
12663                return Ok(Value::Number((self.mldsa_ids.len() - 1) as f64));
12664            },
12665            // Deterministic keypair from a 32-byte hex seed — same rederive-
12666            // from-seed contract as ed25519_keygen_from_seed.
12667            #[cfg(not(target_arch = "wasm32"))]
12668            "mldsa_keygen_from_seed"
12669            | "씨앗에서양자서명키생성"
12670            | "สร้างกุญแจลายเซ็นควอนตัมจากเมล็ด"
12671            | "从种子生成量子签名密钥"
12672            | "シードから量子署名鍵生成"
12673            | "تولید_کلید_امضای_کوانتومی_از_دانه"
12674            | "توليد_مفتاح_التوقيع_الكمي_من_البذرة"
12675            | "יצירת_מפתח_חתימה_קוונטי_מזרע"
12676            | "بیج_سے_کوانٹم_دستخط_کلید_تخلیق"
12677            | "génération_clé_signature_quantique_depuis_graine"
12678            | "quantensignaturschlüsselerzeugung_aus_saat"
12679            | "генерация_ключа_квантовой_подписи_из_семени" =>
12680            {
12681                let seed = hex_to_32(&self.arg_str(&args, 0, ""));
12682                self.mldsa_ids
12683                    .push(ling_crypto::MlDsa65Keypair::from_seed(seed));
12684                return Ok(Value::Number((self.mldsa_ids.len() - 1) as f64));
12685            },
12686            #[cfg(not(target_arch = "wasm32"))]
12687            "mldsa_public"
12688            | "양자서명공개키"
12689            | "กุญแจสาธารณะลายเซ็นควอนตัม"
12690            | "量子签名公钥"
12691            | "量子署名公開鍵"
12692            | "کلید_عمومی_امضای_کوانتومی"
12693            | "مفتاح_التوقيع_الكمي_العام"
12694            | "מפתח_חתימה_קוונטי_ציבורי"
12695            | "کوانٹم_دستخط_عوامی_کلید"
12696            | "clé_publique_signature_quantique"
12697            | "quantensignatur_öffentlicher_schlüssel"
12698            | "публичный_ключ_квантовой_подписи" => {
12699                let h = self.arg_num(&args, 0, 0.0)? as usize;
12700                let pk = self
12701                    .mldsa_ids
12702                    .get(h)
12703                    .map(|kp| hex_encode(&kp.public_key()))
12704                    .unwrap_or_default();
12705                return Ok(Value::Str(pk));
12706            },
12707            // mldsa_sign(handle, message) → signature hex (~3309 bytes)
12708            #[cfg(not(target_arch = "wasm32"))]
12709            "mldsa_sign"
12710            | "양자서명하다"
12711            | "เซ็นชื่อควอนตัม"
12712            | "量子签名"
12713            | "量子署名する"
12714            | "امضای_کوانتومی_کردن"
12715            | "توقيع_كمي"
12716            | "לחתום_קוונטית"
12717            | "کوانٹم_دستخط_کریں"
12718            | "signer_quantique"
12719            | "quantensignieren"
12720            | "подписать_квантово" => {
12721                let h = self.arg_num(&args, 0, 0.0)? as usize;
12722                let msg = self.arg_str(&args, 1, "");
12723                let sig = self
12724                    .mldsa_ids
12725                    .get(h)
12726                    .map(|kp| hex_encode(&kp.sign(msg.as_bytes())))
12727                    .unwrap_or_default();
12728                return Ok(Value::Str(sig));
12729            },
12730            // mldsa_verify(pubkey_hex, message, signature_hex) → bool
12731            #[cfg(not(target_arch = "wasm32"))]
12732            "mldsa_verify"
12733            | "양자서명확인"
12734            | "ยืนยันลายเซ็นควอนตัม"
12735            | "验证量子签名"
12736            | "量子署名検証"
12737            | "تایید_امضای_کوانتومی"
12738            | "التحقق_من_التوقيع_الكمي"
12739            | "אימות_חתימה_קוונטית"
12740            | "کوانٹم_دستخط_تصدیق"
12741            | "vérifier_signature_quantique"
12742            | "quantensignatur_verifizieren"
12743            | "проверить_квантовую_подпись" => {
12744                let pk_hex = self.arg_str(&args, 0, "");
12745                let msg = self.arg_str(&args, 1, "");
12746                let sig_hex = self.arg_str(&args, 2, "");
12747                let pk_bytes = hex_decode(&pk_hex);
12748                let sig_bytes = hex_decode(&sig_hex);
12749                let ok = ling_crypto::MlDsa65Keypair::verify(&pk_bytes, msg.as_bytes(), &sig_bytes)
12750                    .is_ok();
12751                return Ok(Value::Bool(ok));
12752            },
12753            // Argon2id password hashing — password_hash(pw) → PHC string,
12754            // password_verify(pw, phc_string) → bool.
12755            #[cfg(not(target_arch = "wasm32"))]
12756            "password_hash" | "비밀번호해시" => {
12757                let pw = self.arg_str(&args, 0, "");
12758                let hash = ling_crypto::Argon2idParams::default()
12759                    .hash_password(pw.as_bytes())
12760                    .unwrap_or_default();
12761                return Ok(Value::Str(hash));
12762            },
12763            #[cfg(not(target_arch = "wasm32"))]
12764            "password_verify" | "비밀번호확인" => {
12765                let pw = self.arg_str(&args, 0, "");
12766                let hash = self.arg_str(&args, 1, "");
12767                let ok = ling_crypto::Argon2idParams::verify_password(pw.as_bytes(), &hash).is_ok();
12768                return Ok(Value::Bool(ok));
12769            },
12770            // OS-CSPRNG random bytes as hex — session ids / nonces (not the
12771            // xorshift `rand` builtin, which is for game logic, not security).
12772            #[cfg(not(target_arch = "wasm32"))]
12773            "random_hex" | "무작위16진수" => {
12774                use rand::RngCore;
12775                let n = self.arg_num(&args, 0, 16.0)?.max(0.0) as usize;
12776                let mut buf = vec![0u8; n];
12777                rand::rngs::OsRng.fill_bytes(&mut buf);
12778                return Ok(Value::Str(hex_encode(&buf)));
12779            },
12780            // base64_encode(s) — text -> base64 (matches what canvas.toDataURL()
12781            // already produces client-side, so PNG uploads never need a binary
12782            // request body).
12783            #[cfg(not(target_arch = "wasm32"))]
12784            "base64_encode" | "base64인코딩" => {
12785                use base64::Engine as _;
12786                let s = self.arg_str(&args, 0, "");
12787                return Ok(Value::Str(
12788                    base64::engine::general_purpose::STANDARD.encode(s.as_bytes()),
12789                ));
12790            },
12791            // base64_decode_to_file(b64, path) — writes decoded bytes straight to
12792            // disk; returns true on success. The only way binary data (an
12793            // uploaded/rendered PNG) reaches the filesystem from `.ling` source.
12794            #[cfg(not(target_arch = "wasm32"))]
12795            // remove_file(path) -> bool. Deletes a file. Used to clear a
12796            // streamed upload temp file when a publish is rejected, so a
12797            // failed run cannot leave gigabytes behind in storage/tmp.
12798            "remove_file" | "ลบไฟล์" | "删除文件" | "ファイル削除" | "파일삭제" =>
12799            {
12800                let path = self.arg_str(&args, 0, "");
12801                return Ok(Value::Bool(std::fs::remove_file(&path).is_ok()));
12802            },
12803            // rename_file(from, to) -> bool. Moves a file, falling back to
12804            // copy+delete when the two ends are on different filesystems.
12805            // Finalising a streamed upload is a rename, not a copy: the
12806            // artifact is already fully written and can be hundreds of MB.
12807            "rename_file" | "เปลี่ยนชื่อไฟล์" | "重命名文件" | "ファイル改名" | "파일이름변경" =>
12808            {
12809                let from = self.arg_str(&args, 0, "");
12810                let to = self.arg_str(&args, 1, "");
12811                if std::fs::rename(&from, &to).is_ok() {
12812                    return Ok(Value::Bool(true));
12813                }
12814                let ok = std::fs::copy(&from, &to).is_ok();
12815                if ok {
12816                    let _ = std::fs::remove_file(&from);
12817                }
12818                return Ok(Value::Bool(ok));
12819            },
12820            "base64_decode_to_file" | "base64파일로저장" => {
12821                use base64::Engine as _;
12822                let b64 = self.arg_str(&args, 0, "");
12823                let path = self.arg_str(&args, 1, "");
12824                let b64 = b64.split(',').next_back().unwrap_or(&b64); // tolerate a "data:image/png;base64,..." prefix
12825                let ok = base64::engine::general_purpose::STANDARD
12826                    .decode(b64)
12827                    .ok()
12828                    .and_then(|bytes| std::fs::write(&path, bytes).ok())
12829                    .is_some();
12830                return Ok(Value::Bool(ok));
12831            },
12832            // qr_svg(text) — a scannable QR code as an inline <svg>...</svg>
12833            // string (e.g. for an otpauth:// 2FA enrollment URI). Kept as SVG
12834            // rather than a rasterized image so it fits the same "everything
12835            // stays vector" theme as the banknote seal art.
12836            #[cfg(not(target_arch = "wasm32"))]
12837            "qr_svg" | "QR코드" => {
12838                let text = self.arg_str(&args, 0, "");
12839                let svg = qrcode::QrCode::new(text.as_bytes())
12840                    .map(|code| {
12841                        code.render::<qrcode::render::svg::Color>()
12842                            .min_dimensions(240, 240)
12843                            .dark_color(qrcode::render::svg::Color("#1a0f3d"))
12844                            .light_color(qrcode::render::svg::Color("#ffffff"))
12845                            .build()
12846                    })
12847                    .unwrap_or_default();
12848                return Ok(Value::Str(svg));
12849            },
12850            // zip_files(paths_list, out_path) — bundles files into a zip archive
12851            // (used by the "render" step to package a note's PNG/SVG/PDF).
12852            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
12853            "zip_files" | "압축파일" => {
12854                let paths = match args.first() {
12855                    Some(Value::List(l)) => l.iter().map(|v| v.to_string()).collect::<Vec<_>>(),
12856                    _ => Vec::new(),
12857                };
12858                let out_path = self.arg_str(&args, 1, "out.zip");
12859                let ok = (|| -> std::io::Result<()> {
12860                    let file = std::fs::File::create(&out_path)?;
12861                    let mut writer = zip::ZipWriter::new(file);
12862                    let options: zip::write::FileOptions<'_, ()> =
12863                        zip::write::FileOptions::default()
12864                            .compression_method(zip::CompressionMethod::Deflated);
12865                    for p in &paths {
12866                        let name = std::path::Path::new(p)
12867                            .file_name()
12868                            .map(|n| n.to_string_lossy().into_owned())
12869                            .unwrap_or_else(|| p.clone());
12870                        let bytes = std::fs::read(p)?;
12871                        writer.start_file(name, options)?;
12872                        std::io::Write::write_all(&mut writer, &bytes)?;
12873                    }
12874                    writer.finish()?;
12875                    Ok(())
12876                })()
12877                .is_ok();
12878                return Ok(Value::Bool(ok));
12879            },
12880            // pdf_from_images(png_paths_list, out_path) — one page per image,
12881            // sized to its pixel dimensions. No PDF crate dependency: `image`
12882            // (decode) and `flate2` (deflate the page's raw RGB stream) are
12883            // already unconditional deps, so this hand-writes the handful of
12884            // PDF objects (Catalog/Pages/Page/Contents/Image XObject) directly.
12885            // `build_pdf_from_images` itself only exists under this same gate.
12886            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
12887            "pdf_from_images" | "PDF来自图片" => {
12888                let paths = match args.first() {
12889                    Some(Value::List(l)) => l.iter().map(|v| v.to_string()).collect::<Vec<_>>(),
12890                    _ => Vec::new(),
12891                };
12892                let out_path = self.arg_str(&args, 1, "out.pdf");
12893                let ok = build_pdf_from_images(&paths, &out_path).is_ok();
12894                return Ok(Value::Bool(ok));
12895            },
12896
12897            // ══════════════════════════════════════════════════════════════════
12898            // ling-ui — animation easings + holographic vector widgets + text I/O
12899            // ══════════════════════════════════════════════════════════════════
12900            "ease" => {
12901                let name = self.arg_str(&args, 0, "ease");
12902                let t = self.arg_num(&args, 1, 0.0)? as f32;
12903                return Ok(Value::Number(
12904                    ling_ui::Easing::from_name(&name).apply(t) as f64
12905                ));
12906            },
12907
12908            // ══════════════════════════════════════════════════════════════════
12909            // Anima — unified animation drivers (ling-animation). Organic 灵 +
12910            // mechanical 机 scalar drivers, callable per frame from a script.
12911            // ══════════════════════════════════════════════════════════════════
12912            "tween"
12913            | "补间"
12914            | "補間"
12915            | "트윈"
12916            | "แทรกค่า"
12917            | "میان‌فریم"
12918            | "تدرج_حركي"
12919            | "טווין"
12920            | "ٹوئین"
12921            | "твин" => {
12922                let a = self.arg_num(&args, 0, 0.0)?;
12923                let b = self.arg_num(&args, 1, 0.0)?;
12924                let t = self.arg_num(&args, 2, 0.0)?.clamp(0.0, 1.0);
12925                return Ok(Value::Number(a + (b - a) * t));
12926            },
12927            "tween_ease"
12928            | "缓动补间"
12929            | "緩和補間"
12930            | "이징트윈"
12931            | "แทรกนุ่ม"
12932            | "میان‌فریم_نرم"
12933            | "تدرج_ناعم_حركي"
12934            | "טווין_חלק"
12935            | "ٹوئین_ایز"
12936            | "tween_lisse"
12937            | "tween_glättung"
12938            | "твин_плавность" => {
12939                let a = self.arg_num(&args, 0, 0.0)? as f32;
12940                let b = self.arg_num(&args, 1, 0.0)? as f32;
12941                let t = self.arg_num(&args, 2, 0.0)? as f32;
12942                let kind = self.arg_str(&args, 3, "linear");
12943                let e = ling_animation::EaseFunction::from_name(&kind);
12944                return Ok(Value::Number(
12945                    ling_animation::ease::tween_ease(&a, &b, t, e) as f64,
12946                ));
12947            },
12948            // ── Organic 灵 ──
12949            "breathe" | "呼吸" | "호흡" | "หายใจ" | "تنفس" | "נשימה" | "سانس" | "respirer"
12950            | "atmen" | "дышать" => {
12951                let t = self.arg_num(&args, 0, 0.0)? as f32;
12952                let rate = self.arg_num(&args, 1, 1.0)? as f32;
12953                let depth = self.arg_num(&args, 2, 0.1)? as f32;
12954                return Ok(Value::Number(
12955                    ling_animation::scalar::breathe(t, rate, depth) as f64,
12956                ));
12957            },
12958            "wobble"
12959            | "摆动"
12960            | "揺れ"
12961            | "흔들림"
12962            | "โยก"
12963            | "نوسان"
12964            | "تذبذب"
12965            | "תנודה"
12966            | "لرزش"
12967            | "osciller"
12968            | "wackeln"
12969            | "покачивание" => {
12970                let t = self.arg_num(&args, 0, 0.0)? as f32;
12971                let freq = self.arg_num(&args, 1, 1.0)? as f32;
12972                let amp = self.arg_num(&args, 2, 1.0)? as f32;
12973                let phase = self.arg_num(&args, 3, 0.0)? as f32;
12974                return Ok(Value::Number(
12975                    ling_animation::scalar::wobble(t, freq, amp, phase) as f64,
12976                ));
12977            },
12978            "gait_phase"
12979            | "步相"
12980            | "歩相"
12981            | "걸음위상"
12982            | "เฟสก้าว"
12983            | "فاز_گام"
12984            | "طور_المشية"
12985            | "שלב_הליכה"
12986            | "چال_مرحلہ"
12987            | "phase_démarche"
12988            | "gangphase"
12989            | "фаза_походки" => {
12990                let t = self.arg_num(&args, 0, 0.0)? as f32;
12991                let speed = self.arg_num(&args, 1, 1.0)? as f32;
12992                return Ok(Value::Number(
12993                    ling_animation::scalar::gait_phase(t, speed) as f64
12994                ));
12995            },
12996            "gait_swing"
12997            | "步摆"
12998            | "歩振り"
12999            | "걸음흔들"
13000            | "ก้าวแกว่ง"
13001            | "نوسان_گام"
13002            | "أرجحة_المشية"
13003            | "נדנוד_הליכה"
13004            | "چال_جھولا"
13005            | "balancement_démarche"
13006            | "gangschwung"
13007            | "мах_походки" => {
13008                let t = self.arg_num(&args, 0, 0.0)? as f32;
13009                let speed = self.arg_num(&args, 1, 1.0)? as f32;
13010                let stride = self.arg_num(&args, 2, 1.0)? as f32;
13011                return Ok(Value::Number(
13012                    ling_animation::scalar::gait_swing(t, speed, stride) as f64,
13013                ));
13014            },
13015            "gait_lift"
13016            | "抬脚"
13017            | "足上げ"
13018            | "발들기"
13019            | "ยกเท้า"
13020            | "بلندشدن_گام"
13021            | "رفع_المشية"
13022            | "הרמת_הליכה"
13023            | "چال_اٹھاؤ"
13024            | "levée_démarche"
13025            | "ganghub"
13026            | "подъём_походки" => {
13027                let t = self.arg_num(&args, 0, 0.0)? as f32;
13028                let speed = self.arg_num(&args, 1, 1.0)? as f32;
13029                let height = self.arg_num(&args, 2, 1.0)? as f32;
13030                return Ok(Value::Number(
13031                    ling_animation::scalar::gait_lift(t, speed, height) as f64,
13032                ));
13033            },
13034            "spring_to"
13035            | "弹向"
13036            | "バネ寄せ"
13037            | "스프링이동"
13038            | "สปริงไป"
13039            | "فنر_به‌سوی"
13040            | "نابض_إلى"
13041            | "קפיץ_אל"
13042            | "اسپرنگ_تک"
13043            | "ressort_vers"
13044            | "feder_zu"
13045            | "пружина_к" => {
13046                let pos = self.arg_num(&args, 0, 0.0)? as f32;
13047                let vel = self.arg_num(&args, 1, 0.0)? as f32;
13048                let target = self.arg_num(&args, 2, 0.0)? as f32;
13049                let stiffness = self.arg_num(&args, 3, 120.0)? as f32;
13050                let damping = self.arg_num(&args, 4, 14.0)? as f32;
13051                let dt = self.arg_num(&args, 5, 1.0 / 60.0)? as f32;
13052                let (np, nv) =
13053                    ling_animation::scalar::spring_step(pos, vel, target, stiffness, damping, dt);
13054                return Ok(Value::List(Rc::new(vec![
13055                    Value::Number(np as f64),
13056                    Value::Number(nv as f64),
13057                ])));
13058            },
13059            "ik2"
13060            | "反解"
13061            | "逆運動"
13062            | "역운동"
13063            | "ไอเค2"
13064            | "سینماتیک_معکوس2"
13065            | "حركية_عكسية2"
13066            | "קינמטיקה_הפוכה2"
13067            | "آئی_کے2"
13068            | "cinématique_inverse2"
13069            | "inverse_kinematik2"
13070            | "обратная_кинематика2" => {
13071                let l1 = self.arg_num(&args, 0, 1.0)? as f32;
13072                let l2 = self.arg_num(&args, 1, 1.0)? as f32;
13073                let tx = self.arg_num(&args, 2, 0.0)? as f32;
13074                let ty = self.arg_num(&args, 3, 0.0)? as f32;
13075                let (sh, el) = ling_animation::scalar::two_bone_ik(l1, l2, tx, ty);
13076                return Ok(Value::List(Rc::new(vec![
13077                    Value::Number(sh as f64),
13078                    Value::Number(el as f64),
13079                ])));
13080            },
13081            // ── Mechanical 机 ──
13082            "gear_couple"
13083            | "齿轮联动"
13084            | "歯車連動"
13085            | "기어연동"
13086            | "เฟืองทด"
13087            | "جفت_چرخ‌دنده"
13088            | "اقتران_التروس"
13089            | "צימוד_גלגלי_שיניים"
13090            | "گیئر_جوڑا"
13091            | "accoupler_engrenage"
13092            | "zahnrad_koppeln"
13093            | "сцепить_шестерни" => {
13094                let angle = self.arg_num(&args, 0, 0.0)? as f32;
13095                let ti = self.arg_num(&args, 1, 1.0)? as f32;
13096                let to = self.arg_num(&args, 2, 1.0)? as f32;
13097                return Ok(Value::Number(
13098                    ling_animation::scalar::gear(angle, ti, to) as f64
13099                ));
13100            },
13101            "gear_train"
13102            | "齿轮组"
13103            | "歯車列"
13104            | "기어열"
13105            | "ชุดเฟือง"
13106            | "مجموعه_چرخ‌دنده"
13107            | "قطار_التروس"
13108            | "שרשרת_גלגלי_שיניים"
13109            | "گیئر_ٹرین"
13110            | "train_engrenages"
13111            | "zahnradgetriebe"
13112            | "передача_шестерён" => {
13113                let angle = self.arg_num(&args, 0, 0.0)? as f32;
13114                let teeth: Vec<f32> = match args.get(1) {
13115                    Some(Value::List(items)) => items
13116                        .iter()
13117                        .filter_map(|v| {
13118                            if let Value::Number(n) = v {
13119                                Some(*n as f32)
13120                            } else {
13121                                None
13122                            }
13123                        })
13124                        .collect(),
13125                    _ => Vec::new(),
13126                };
13127                let out = ling_animation::mechanism::gear_train(angle, &teeth);
13128                return Ok(Value::List(Rc::new(
13129                    out.into_iter().map(|a| Value::Number(a as f64)).collect(),
13130                )));
13131            },
13132            "cam_lift"
13133            | "凸轮升程"
13134            | "カム揚程"
13135            | "캠리프트"
13136            | "ยกลูกเบี้ยว"
13137            | "بلندشدن_بادامک"
13138            | "رفع_الكامة"
13139            | "הרמת_קאם"
13140            | "کیم_اٹھاؤ"
13141            | "levée_came"
13142            | "nockenhub"
13143            | "подъём_кулачка" => {
13144                let angle = self.arg_num(&args, 0, 0.0)? as f32;
13145                let lift = self.arg_num(&args, 1, 1.0)? as f32;
13146                return Ok(Value::Number(
13147                    ling_animation::scalar::cam_lift(angle, lift) as f64
13148                ));
13149            },
13150            "piston" | "活塞" | "ピストン" | "피스톤" | "ลูกสูบ" | "پیستون" | "مكبس" | "בוכנה"
13151            | "پسٹن" | "kolben" | "поршень" => {
13152                let angle = self.arg_num(&args, 0, 0.0)? as f32;
13153                let crank = self.arg_num(&args, 1, 1.0)? as f32;
13154                let rod = self.arg_num(&args, 2, 2.0)? as f32;
13155                return Ok(Value::Number(
13156                    ling_animation::scalar::piston(angle, crank, rod) as f64,
13157                ));
13158            },
13159            "rack"
13160            | "齿条"
13161            | "ラック"
13162            | "랙"
13163            | "แร็ค"
13164            | "زبانه‌دنده"
13165            | "سكة_مسننة"
13166            | "מוט_שיניים"
13167            | "ریک"
13168            | "crémaillère"
13169            | "zahnstange"
13170            | "рейка" => {
13171                let angle = self.arg_num(&args, 0, 0.0)? as f32;
13172                let radius = self.arg_num(&args, 1, 1.0)? as f32;
13173                return Ok(Value::Number(
13174                    ling_animation::scalar::rack(angle, radius) as f64
13175                ));
13176            },
13177            #[cfg(not(target_arch = "wasm32"))]
13178            "mouse_x" => {
13179                let gfx = self.gfx.borrow();
13180                let v = gfx
13181                    .window
13182                    .as_ref()
13183                    .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
13184                    .map(|p| p.0 as f64)
13185                    .unwrap_or(0.0);
13186                return Ok(Value::Number(v));
13187            },
13188            #[cfg(target_arch = "wasm32")]
13189            "mouse_x" => {
13190                return Ok(Value::Number(crate::gfx::wasm_mouse_x() as f64));
13191            },
13192            #[cfg(not(target_arch = "wasm32"))]
13193            "mouse_y" => {
13194                let gfx = self.gfx.borrow();
13195                let v = gfx
13196                    .window
13197                    .as_ref()
13198                    .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
13199                    .map(|p| p.1 as f64)
13200                    .unwrap_or(0.0);
13201                return Ok(Value::Number(v));
13202            },
13203            #[cfg(target_arch = "wasm32")]
13204            "mouse_y" => {
13205                return Ok(Value::Number(crate::gfx::wasm_mouse_y() as f64));
13206            },
13207            #[cfg(not(target_arch = "wasm32"))]
13208            "mouse_down" => {
13209                let mut gfx = self.gfx.borrow_mut();
13210                let d = !gfx.input_suppressed()
13211                    && gfx
13212                        .window
13213                        .as_ref()
13214                        .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
13215                        .unwrap_or(false);
13216                return Ok(Value::Bool(d));
13217            },
13218            #[cfg(target_arch = "wasm32")]
13219            "mouse_down" => {
13220                return Ok(Value::Bool(crate::gfx::wasm_mouse_down()));
13221            },
13222            #[cfg(not(target_arch = "wasm32"))]
13223            "mouse_down_right"
13224            | "เมาส์ขวา"
13225            | "ماوس_راست_فشرده"
13226            | "الفأرة_اليمنى_مضغوطة"
13227            | "עכבר_ימני_לחוץ"
13228            | "دایاں_ماؤس_دبا_ہوا" => {
13229                let mut gfx = self.gfx.borrow_mut();
13230                let d = !gfx.input_suppressed()
13231                    && gfx
13232                        .window
13233                        .as_ref()
13234                        .map(|w| w.get_mouse_down(minifb::MouseButton::Right))
13235                        .unwrap_or(false);
13236                return Ok(Value::Bool(d));
13237            },
13238            #[cfg(target_arch = "wasm32")]
13239            "mouse_down_right"
13240            | "เมาส์ขวา"
13241            | "ماوس_راست_فشرده"
13242            | "الفأرة_اليمنى_مضغوطة"
13243            | "עכבר_ימני_לחוץ"
13244            | "دایاں_ماؤس_دبا_ہوا" => {
13245                return Ok(Value::Bool(crate::gfx::wasm_mouse_down_right()));
13246            },
13247            #[cfg(not(target_arch = "wasm32"))]
13248            "mouse_down_middle"
13249            | "เมาส์กลาง"
13250            | "ماوس_وسط_فشرده"
13251            | "الفأرة_الوسطى_مضغوطة"
13252            | "עכבר_אמצעי_לחוץ"
13253            | "درمیانی_ماؤس_دبا_ہوا" => {
13254                let mut gfx = self.gfx.borrow_mut();
13255                let d = !gfx.input_suppressed()
13256                    && gfx
13257                        .window
13258                        .as_ref()
13259                        .map(|w| w.get_mouse_down(minifb::MouseButton::Middle))
13260                        .unwrap_or(false);
13261                return Ok(Value::Bool(d));
13262            },
13263            #[cfg(target_arch = "wasm32")]
13264            "mouse_down_middle"
13265            | "เมาส์กลาง"
13266            | "ماوس_وسط_فشرده"
13267            | "الفأرة_الوسطى_مضغوطة"
13268            | "עכבר_אמצעי_לחוץ"
13269            | "درمیانی_ماؤس_دبا_ہوا" => {
13270                return Ok(Value::Bool(crate::gfx::wasm_mouse_down_middle()));
13271            },
13272            #[cfg(not(target_arch = "wasm32"))]
13273            "ui_hot"
13274            | "热区"
13275            | "ホットエリア"
13276            | "핫존"
13277            | "พื้นที่สัมผัส"
13278            | "ناحیه_فعال"
13279            | "منطقة_ساخنة"
13280            | "אזור_חם"
13281            | "ہاٹ_زون"
13282            | "survol_ui"
13283            | "ui_hover"
13284            | "ui_наведение" => {
13285                let x = self.arg_num(&args, 0, 0.0)? as f32;
13286                let y = self.arg_num(&args, 1, 0.0)? as f32;
13287                let w = self.arg_num(&args, 2, 0.0)? as f32;
13288                let h = self.arg_num(&args, 3, 0.0)? as f32;
13289                let gfx = self.gfx.borrow();
13290                let (mx, my) = gfx
13291                    .window
13292                    .as_ref()
13293                    .and_then(|win| win.get_mouse_pos(minifb::MouseMode::Clamp))
13294                    .unwrap_or((0.0, 0.0));
13295                return Ok(Value::Bool(ling_ui::holo::hit_rect(mx, my, x, y, w, h)));
13296            },
13297            #[cfg(target_arch = "wasm32")]
13298            "ui_hot"
13299            | "热区"
13300            | "ホットエリア"
13301            | "핫존"
13302            | "พื้นที่สัมผัส"
13303            | "ناحیه_فعال"
13304            | "منطقة_ساخنة"
13305            | "אזור_חם"
13306            | "ہاٹ_زون"
13307            | "survol_ui"
13308            | "ui_hover"
13309            | "ui_наведение" => {
13310                return Ok(Value::Bool(false));
13311            },
13312            // ui_text(x, y, scale, "string") — holographic vector text
13313            "ui_text"
13314            | "界面文字"
13315            | "UI文字"
13316            | "UI텍스트"
13317            | "ข้อความหน้าจอ"
13318            | "متن_رابط"
13319            | "نص_الواجهة"
13320            | "טקסט_ממשק"
13321            | "یو_آئی_متن"
13322            | "texte_ui"
13323            | "ui_beschriftung"
13324            | "ui_текст" => {
13325                let x = self.arg_num(&args, 0, 0.0)? as f32;
13326                let y = self.arg_num(&args, 1, 0.0)? as f32;
13327                let scale = self.arg_num(&args, 2, 16.0)? as f32;
13328                let s = self.arg_str(&args, 3, "");
13329                let segs = ling_ui::holo::text_lines(&s, x, y, scale * 0.62, scale, scale * 0.24);
13330                let mut gfx = self.gfx.borrow_mut();
13331                let (w, h, color) = (gfx.width, gfx.height, gfx.color);
13332                for sg in segs {
13333                    draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
13334                }
13335                return Ok(Value::Unit);
13336            },
13337            // font_load("path.ttf") — load a vector font (outlines cached lazily as
13338            // cache/fonts/<stem>/<codepoint>.ling). Returns a handle, or -1 on failure.
13339            #[cfg(not(target_arch = "wasm32"))]
13340            "font_load"
13341            | "โหลดฟอนต์"
13342            | "加载字体"
13343            | "フォント読込"
13344            | "글꼴로드"
13345            | "بارگذاری_فونت"
13346            | "تحميل_الخط"
13347            | "טעינת_גופן"
13348            | "فونٹ_لوڈ"
13349            | "charger_police"
13350            | "schriftart_laden"
13351            | "загрузить_шрифт" => {
13352                let path = self.arg_str(&args, 0, "");
13353                // Optional 2nd arg: variable-font weight (e.g. 600 for a solid, bold UI).
13354                let weight = match self.arg_num(&args, 1, 0.0)? {
13355                    w if w > 0.0 => Some(w as f32),
13356                    _ => None,
13357                };
13358                // Try the path as given, then relative to the script's directory.
13359                let mut loaded = ling_graphics::VectorFont::from_path_weight(&path, weight);
13360                if loaded.is_err() {
13361                    if let Some(dir) = &self.source_dir {
13362                        let joined = dir.join(&path);
13363                        loaded = ling_graphics::VectorFont::from_path_weight(
13364                            &joined.to_string_lossy(),
13365                            weight,
13366                        );
13367                    }
13368                }
13369                match loaded {
13370                    Ok(f) => {
13371                        let id = self.fonts.len();
13372                        self.fonts.push(f);
13373                        return Ok(Value::Number(id as f64));
13374                    },
13375                    Err(e) => {
13376                        eprintln!("font_load failed ({path}): {e}");
13377                        return Ok(Value::Number(-1.0));
13378                    },
13379                }
13380            },
13381            #[cfg(target_arch = "wasm32")]
13382            "font_load"
13383            | "โหลดฟอนต์"
13384            | "加载字体"
13385            | "フォント読込"
13386            | "글꼴로드"
13387            | "بارگذاری_فونت"
13388            | "تحميل_الخط"
13389            | "טעינת_גופן"
13390            | "فونٹ_لوڈ"
13391            | "charger_police"
13392            | "schriftart_laden"
13393            | "загрузить_шрифт" => {
13394                // Web runtime does not load host TTF/OTF files yet.
13395                // Return -1 so scripts can fall back to ui_text.
13396                return Ok(Value::Number(-1.0));
13397            },
13398            // image_load("path.png") — decode a raster image (via the `image` crate)
13399            // for pixel sampling (image_width/image_height/image_pixel_r/g/b/a) —
13400            // used by the coin-stamp mosaic tool to read a source photo's
13401            // colour/darkness. Returns a handle, or -1 on failure.
13402            #[cfg(not(target_arch = "wasm32"))]
13403            "image_load" => {
13404                let path = self.arg_str(&args, 0, "").replace('\\', "/");
13405                let mut loaded = image::open(&path);
13406                if loaded.is_err() {
13407                    if let Some(dir) = &self.source_dir {
13408                        let joined = dir.join(&path);
13409                        loaded = image::open(&joined);
13410                    }
13411                }
13412                match loaded {
13413                    Ok(img) => {
13414                        let id = self.images.len();
13415                        self.images.push(img.to_rgba8());
13416                        return Ok(Value::Number(id as f64));
13417                    },
13418                    Err(e) => {
13419                        eprintln!("image_load failed ({path}): {e}");
13420                        return Ok(Value::Number(-1.0));
13421                    },
13422                }
13423            },
13424            #[cfg(target_arch = "wasm32")]
13425            "image_load" => {
13426                // Web runtime does not load host image files yet.
13427                return Ok(Value::Number(-1.0));
13428            },
13429            #[cfg(not(target_arch = "wasm32"))]
13430            "image_width" => {
13431                let id = self.arg_num(&args, 0, -1.0)? as i64;
13432                if id >= 0 && (id as usize) < self.images.len() {
13433                    return Ok(Value::Number(self.images[id as usize].width() as f64));
13434                }
13435                return Ok(Value::Number(0.0));
13436            },
13437            #[cfg(target_arch = "wasm32")]
13438            "image_width" => return Ok(Value::Number(0.0)),
13439            #[cfg(not(target_arch = "wasm32"))]
13440            "image_height" => {
13441                let id = self.arg_num(&args, 0, -1.0)? as i64;
13442                if id >= 0 && (id as usize) < self.images.len() {
13443                    return Ok(Value::Number(self.images[id as usize].height() as f64));
13444                }
13445                return Ok(Value::Number(0.0));
13446            },
13447            #[cfg(target_arch = "wasm32")]
13448            "image_height" => return Ok(Value::Number(0.0)),
13449            // image_load never succeeds on wasm32 (see its own wasm32 stub, always -1),
13450            // so any id here is always out-of-range there too — 0.0 matches exactly what
13451            // the native bounds-check-failure path below already returns for a bad id.
13452            #[cfg(target_arch = "wasm32")]
13453            "image_pixel_r" | "image_pixel_g" | "image_pixel_b" | "image_pixel_a" => {
13454                return Ok(Value::Number(0.0));
13455            },
13456            #[cfg(not(target_arch = "wasm32"))]
13457            "image_pixel_r" | "image_pixel_g" | "image_pixel_b" | "image_pixel_a" => {
13458                let id = self.arg_num(&args, 0, -1.0)? as i64;
13459                let px = self.arg_num(&args, 1, 0.0)? as i64;
13460                let py = self.arg_num(&args, 2, 0.0)? as i64;
13461                if id >= 0 && (id as usize) < self.images.len() {
13462                    let img = &self.images[id as usize];
13463                    if px >= 0 && py >= 0 && (px as u32) < img.width() && (py as u32) < img.height()
13464                    {
13465                        let p = img.get_pixel(px as u32, py as u32);
13466                        let ch = match name {
13467                            "image_pixel_r" => p[0],
13468                            "image_pixel_g" => p[1],
13469                            "image_pixel_b" => p[2],
13470                            _ => p[3],
13471                        };
13472                        return Ok(Value::Number(ch as f64));
13473                    }
13474                }
13475                return Ok(Value::Number(0.0));
13476            },
13477            // image_new(w, h) — a new blank (fully transparent) RGBA image the
13478            // script can paint into with image_set_pixel and write out with
13479            // image_save. Lives in the same self.images table as image_load,
13480            // so image_width/image_height/image_pixel_* all work on it too.
13481            // Used by the coin-stamp tool to build cropped, physically-sized
13482            // (mm x DPI) PNG exports — something a raw window screenshot()
13483            // can't do, since it always captures the whole on-screen
13484            // framebuffer at whatever size the window happens to be.
13485            #[cfg(not(target_arch = "wasm32"))]
13486            "image_new" => {
13487                let w = self.arg_num(&args, 0, 1.0)?.max(1.0) as u32;
13488                let h = self.arg_num(&args, 1, 1.0)?.max(1.0) as u32;
13489                let id = self.images.len();
13490                self.images.push(image::RgbaImage::new(w, h));
13491                return Ok(Value::Number(id as f64));
13492            },
13493            // Matches image_load's own wasm32 stub convention (-1 = failure/unavailable).
13494            #[cfg(target_arch = "wasm32")]
13495            "image_new" => return Ok(Value::Number(-1.0)),
13496            // image_set_pixel(id, x, y, r, g, b, a) — paint one pixel of an
13497            // image created with image_new (0..255 channels; out-of-bounds is
13498            // a silent no-op, matching image_pixel_*'s own out-of-bounds
13499            // behaviour).
13500            #[cfg(not(target_arch = "wasm32"))]
13501            "image_set_pixel" => {
13502                let id = self.arg_num(&args, 0, -1.0)? as i64;
13503                let px = self.arg_num(&args, 1, 0.0)? as i64;
13504                let py = self.arg_num(&args, 2, 0.0)? as i64;
13505                let r = self.arg_num(&args, 3, 0.0)?.clamp(0.0, 255.0) as u8;
13506                let g = self.arg_num(&args, 4, 0.0)?.clamp(0.0, 255.0) as u8;
13507                let b = self.arg_num(&args, 5, 0.0)?.clamp(0.0, 255.0) as u8;
13508                let a = self.arg_num(&args, 6, 255.0)?.clamp(0.0, 255.0) as u8;
13509                if id >= 0 && (id as usize) < self.images.len() {
13510                    let img = &mut self.images[id as usize];
13511                    if px >= 0 && py >= 0 && (px as u32) < img.width() && (py as u32) < img.height()
13512                    {
13513                        img.put_pixel(px as u32, py as u32, image::Rgba([r, g, b, a]));
13514                    }
13515                }
13516                return Ok(Value::Unit);
13517            },
13518            // image_new always fails on wasm32, so there's never a valid id to set a
13519            // pixel on — no-op, matching the native code's own bad-id no-op behaviour.
13520            #[cfg(target_arch = "wasm32")]
13521            "image_set_pixel" => return Ok(Value::Unit),
13522            // image_save(id, "path.png") — encode an image (from image_new or
13523            // image_load) to disk, alpha preserved. Returns 1 on success, -1
13524            // on failure (bad id or write error), mirroring image_load's own
13525            // -1-on-failure convention. Path resolves the same way
13526            // write_file/copy_file's outputs do: relative to the script's own
13527            // working directory (typically the app dir the launcher cd's
13528            // into), not source_dir.
13529            #[cfg(not(target_arch = "wasm32"))]
13530            "image_save" => {
13531                let id = self.arg_num(&args, 0, -1.0)? as i64;
13532                let path = self.arg_str(&args, 1, "");
13533                if id >= 0 && (id as usize) < self.images.len() {
13534                    if let Some(parent) = std::path::Path::new(&path).parent() {
13535                        if !parent.as_os_str().is_empty() {
13536                            let _ = std::fs::create_dir_all(parent);
13537                        }
13538                    }
13539                    if self.images[id as usize].save(&path).is_ok() {
13540                        return Ok(Value::Number(1.0));
13541                    }
13542                }
13543                return Ok(Value::Number(-1.0));
13544            },
13545            #[cfg(target_arch = "wasm32")]
13546            "image_save" => {
13547                return Ok(Value::Number(-1.0));
13548            },
13549            // image_draw(id, x, y, w, h) — blit an image (nearest-neighbour
13550            // scaled to w x h, alpha-blended against whatever's already in
13551            // the framebuffer) into the current frame. A native pixel loop,
13552            // not a .ling-level per-pixel image_pixel_*+pixel() loop: doing
13553            // this from script for even a modest thumbnail grid re-incurs
13554            // the exact per-frame interpreted-call-volume cost that made
13555            // small mosaic tiles hang the UI (see mosaic.ling's
13556            // xform_glyph_pts_fit fix) — this is the "read the framebuffer
13557            // out" direction's counterpart to screenshot().
13558            #[cfg(not(target_arch = "wasm32"))]
13559            "image_draw" => {
13560                let id = self.arg_num(&args, 0, -1.0)? as i64;
13561                let dx = self.arg_num(&args, 1, 0.0)? as i32;
13562                let dy = self.arg_num(&args, 2, 0.0)? as i32;
13563                let dw = self.arg_num(&args, 3, 0.0)?.max(0.0) as i32;
13564                let dh = self.arg_num(&args, 4, 0.0)?.max(0.0) as i32;
13565                if id >= 0 && (id as usize) < self.images.len() && dw > 0 && dh > 0 {
13566                    let img = &self.images[id as usize];
13567                    let sw = img.width() as i32;
13568                    let sh = img.height() as i32;
13569                    if sw > 0 && sh > 0 {
13570                        let mut gfx = self.gfx.borrow_mut();
13571                        let (fw, fh) = (gfx.width as i32, gfx.height as i32);
13572                        for py in 0..dh {
13573                            let ty = dy + py;
13574                            if ty < 0 || ty >= fh {
13575                                continue;
13576                            }
13577                            let sy = (py * sh / dh).clamp(0, sh - 1) as u32;
13578                            for px in 0..dw {
13579                                let tx = dx + px;
13580                                if tx < 0 || tx >= fw {
13581                                    continue;
13582                                }
13583                                let sx = (px * sw / dw).clamp(0, sw - 1) as u32;
13584                                let p = img.get_pixel(sx, sy);
13585                                let a = p[3] as u32;
13586                                if a == 0 {
13587                                    continue;
13588                                }
13589                                let idx = ty as usize * gfx.width + tx as usize;
13590                                if a >= 255 {
13591                                    gfx.buffer[idx] = ((p[0] as u32) << 16)
13592                                        | ((p[1] as u32) << 8)
13593                                        | (p[2] as u32);
13594                                } else {
13595                                    let bg = gfx.buffer[idx];
13596                                    let br = (bg >> 16) & 0xff;
13597                                    let bg_g = (bg >> 8) & 0xff;
13598                                    let bb = bg & 0xff;
13599                                    let r = (p[0] as u32 * a + br * (255 - a)) / 255;
13600                                    let g = (p[1] as u32 * a + bg_g * (255 - a)) / 255;
13601                                    let b = (p[2] as u32 * a + bb * (255 - a)) / 255;
13602                                    gfx.buffer[idx] = (r << 16) | (g << 8) | b;
13603                                }
13604                            }
13605                        }
13606                    }
13607                }
13608                return Ok(Value::Unit);
13609            },
13610            #[cfg(target_arch = "wasm32")]
13611            "image_draw" => {
13612                return Ok(Value::Unit);
13613            },
13614            // font_text(handle, x, y, px, "string") — anti-aliased *stroked* vector outline
13615            // in the current set_color / set_blend. (x,y) is the text box top-left.
13616            #[cfg(not(target_arch = "wasm32"))]
13617            "font_text"
13618            | "ข้อความฟอนต์"
13619            | "字体文本"
13620            | "フォント文字"
13621            | "글꼴텍스트"
13622            | "متن_فونت"
13623            | "نص_الخط"
13624            | "טקסט_גופן"
13625            | "فونٹ_متن"
13626            | "texte_police"
13627            | "schriftart_text"
13628            | "текст_шрифт" => {
13629                let id = self.arg_num(&args, 0, 0.0)? as i64;
13630                let x = self.arg_num(&args, 1, 0.0)? as f32;
13631                let y = self.arg_num(&args, 2, 0.0)? as f32;
13632                let px = self.arg_num(&args, 3, 16.0)? as f32;
13633                let s = self.arg_str(&args, 4, "");
13634                if id >= 0 && (id as usize) < self.fonts.len() && px > 0.0 {
13635                    let strokes = self.font_layout_2d(id as usize, x, y, px, &s);
13636                    let mut gfx = self.gfx.borrow_mut();
13637                    let (w, h, color, add, aa) = (
13638                        gfx.width,
13639                        gfx.height,
13640                        gfx.color,
13641                        gfx.blend == 1,
13642                        gfx.font_antialias,
13643                    );
13644                    for pl in &strokes {
13645                        for seg in pl.windows(2) {
13646                            if aa {
13647                                crate::gfx::raster::draw_line_aa(
13648                                    &mut gfx.buffer,
13649                                    w,
13650                                    h,
13651                                    color,
13652                                    add,
13653                                    seg[0][0],
13654                                    seg[0][1],
13655                                    seg[1][0],
13656                                    seg[1][1],
13657                                );
13658                            } else {
13659                                crate::gfx::raster::draw_line(
13660                                    &mut gfx.buffer,
13661                                    w,
13662                                    h,
13663                                    color,
13664                                    seg[0][0],
13665                                    seg[0][1],
13666                                    seg[1][0],
13667                                    seg[1][1],
13668                                );
13669                            }
13670                        }
13671                    }
13672                }
13673                return Ok(Value::Unit);
13674            },
13675            #[cfg(target_arch = "wasm32")]
13676            "font_text"
13677            | "ข้อความฟอนต์"
13678            | "字体文本"
13679            | "フォント文字"
13680            | "글꼴텍스트"
13681            | "متن_فونت"
13682            | "نص_الخط"
13683            | "טקסט_גופן"
13684            | "فونٹ_متن"
13685            | "texte_police"
13686            | "schriftart_text"
13687            | "текст_шрифт" => {
13688                return Ok(Value::Unit);
13689            },
13690            // font_text_fill(handle, x, y, px, "string") — filled vector glyphs;
13691            // anti-aliased when `set_font_antialias(1)` is on (default off = crisp).
13692            #[cfg(not(target_arch = "wasm32"))]
13693            "font_text_fill"
13694            | "เติมฟอนต์"
13695            | "填充字体"
13696            | "フォント塗り"
13697            | "글꼴채움"
13698            | "پرکردن_متن_فونت"
13699            | "تعبئة_نص_الخط"
13700            | "מילוי_טקסט_גופן"
13701            | "فونٹ_متن_بھرو"
13702            | "remplir_texte_police"
13703            | "schriftart_text_füllen"
13704            | "заполнить_текст_шрифт" => {
13705                let id = self.arg_num(&args, 0, 0.0)? as i64;
13706                let x = self.arg_num(&args, 1, 0.0)? as f32;
13707                let y = self.arg_num(&args, 2, 0.0)? as f32;
13708                let px = self.arg_num(&args, 3, 16.0)? as f32;
13709                let s = self.arg_str(&args, 4, "");
13710                if id >= 0 && (id as usize) < self.fonts.len() && px > 0.0 {
13711                    // fill each glyph independently so interior holes (winding) stay correct
13712                    let glyphs = self.font_layout_2d_glyphs(id as usize, x, y, px, &s);
13713                    let mut gfx = self.gfx.borrow_mut();
13714                    let (w, h, color, add, aa) = (
13715                        gfx.width,
13716                        gfx.height,
13717                        gfx.color,
13718                        gfx.blend == 1,
13719                        gfx.font_antialias,
13720                    );
13721                    for contours in &glyphs {
13722                        if aa {
13723                            crate::gfx::raster::fill_contours_aa(
13724                                &mut gfx.buffer,
13725                                w,
13726                                h,
13727                                color,
13728                                add,
13729                                contours,
13730                            );
13731                        } else {
13732                            crate::gfx::raster::fill_contours(
13733                                &mut gfx.buffer,
13734                                w,
13735                                h,
13736                                color,
13737                                add,
13738                                contours,
13739                            );
13740                        }
13741                    }
13742                }
13743                return Ok(Value::Unit);
13744            },
13745            #[cfg(target_arch = "wasm32")]
13746            "font_text_fill"
13747            | "เติมฟอนต์"
13748            | "填充字体"
13749            | "フォント塗り"
13750            | "글꼴채움"
13751            | "پرکردن_متن_فونت"
13752            | "تعبئة_نص_الخط"
13753            | "מילוי_טקסט_גופן"
13754            | "فونٹ_متن_بھرو"
13755            | "remplir_texte_police"
13756            | "schriftart_text_füllen"
13757            | "заполнить_текст_шрифт" => {
13758                return Ok(Value::Unit);
13759            },
13760            // font_text_3d(handle, cx,cy,cz, ux,uy,uz, vx,vy,vz, size, "string")
13761            // — stroked vector text on a 3D plane: u = advance dir, v = up dir, size = world/em.
13762            //   Flows through the depth-sorted line pipeline, so it rotates with the camera (and 4D).
13763            #[cfg(not(target_arch = "wasm32"))]
13764            "font_text_3d"
13765            | "ข้อความฟอนต์3มิติ"
13766            | "字体3D"
13767            | "フォント3D"
13768            | "글꼴3D"
13769            | "متن_فونت_سه‌بعدی"
13770            | "نص_خط_ثلاثي_الأبعاد"
13771            | "טקסט_גופן_תלת_ממדי"
13772            | "تھری_ڈی_فونٹ_متن"
13773            | "texte_police_3d"
13774            | "schriftart_text_3d"
13775            | "текст_шрифт_3d" => {
13776                let id = self.arg_num(&args, 0, 0.0)? as i64;
13777                let cx = self.arg_num(&args, 1, 0.0)? as f32;
13778                let cy = self.arg_num(&args, 2, 0.0)? as f32;
13779                let cz = self.arg_num(&args, 3, 0.0)? as f32;
13780                let ux = self.arg_num(&args, 4, 1.0)? as f32;
13781                let uy = self.arg_num(&args, 5, 0.0)? as f32;
13782                let uz = self.arg_num(&args, 6, 0.0)? as f32;
13783                let vx = self.arg_num(&args, 7, 0.0)? as f32;
13784                let vy = self.arg_num(&args, 8, 1.0)? as f32;
13785                let vz = self.arg_num(&args, 9, 0.0)? as f32;
13786                let size = self.arg_num(&args, 10, 1.0)? as f32;
13787                let s = self.arg_str(&args, 11, "");
13788                // Optional arg 12: fill_rows — when > 0, each glyph interior is
13789                // filled with that many even-odd scanline spans (true filled
13790                // letterforms, not a bounding box). 0/omitted = outline only.
13791                let fill_rows = self.arg_num(&args, 12, 0.0)? as i32;
13792                if id >= 0 && (id as usize) < self.fonts.len() && size > 0.0 {
13793                    // Build world-space polylines: world = C + (pen+ex)*size*U + ey*size*V
13794                    let font = &mut self.fonts[id as usize];
13795                    let asc = font.ascent();
13796                    let mut pen = 0.0f32;
13797                    let mut lines: Vec<[f32; 6]> = Vec::new();
13798                    for ch in s.chars() {
13799                        let go = font.glyph_outline(ch, 0.01);
13800                        let map = |p: [f32; 2], pen: f32| {
13801                            let a = pen + p[0];
13802                            let b = p[1] - asc; // shift so the top of the cap sits near C
13803                            [
13804                                cx + a * size * ux + b * size * vx,
13805                                cy + a * size * uy + b * size * vy,
13806                                cz + a * size * uz + b * size * vz,
13807                            ]
13808                        };
13809                        for pl in &go.polylines {
13810                            for seg in pl.windows(2) {
13811                                let p0 = map(seg[0], pen);
13812                                let p1 = map(seg[1], pen);
13813                                lines.push([p0[0], p0[1], p0[2], p1[0], p1[1], p1[2]]);
13814                            }
13815                        }
13816                        if fill_rows > 0 {
13817                            // Even-odd scanline fill in glyph space. Contours may
13818                            // omit their closing edge, so the implicit last→first
13819                            // segment is scanned too (skipped when degenerate).
13820                            let (mut ymin, mut ymax) = (f32::MAX, f32::MIN);
13821                            for pl in &go.polylines {
13822                                for p in pl {
13823                                    ymin = ymin.min(p[1]);
13824                                    ymax = ymax.max(p[1]);
13825                                }
13826                            }
13827                            if ymax > ymin {
13828                                for r in 0..fill_rows {
13829                                    let y =
13830                                        ymin + (r as f32 + 0.5) * (ymax - ymin) / fill_rows as f32;
13831                                    let mut xs: Vec<f32> = Vec::new();
13832                                    for pl in &go.polylines {
13833                                        let n = pl.len();
13834                                        if n < 2 {
13835                                            continue;
13836                                        }
13837                                        for k in 0..n {
13838                                            let p0 = pl[k];
13839                                            let p1 = pl[(k + 1) % n];
13840                                            if k + 1 == n
13841                                                && (p1[0] - p0[0]).abs() < 1e-6
13842                                                && (p1[1] - p0[1]).abs() < 1e-6
13843                                            {
13844                                                continue; // contour already closed
13845                                            }
13846                                            let (y0, y1) = (p0[1], p1[1]);
13847                                            if (y0 <= y && y1 > y) || (y1 <= y && y0 > y) {
13848                                                let t = (y - y0) / (y1 - y0);
13849                                                xs.push(p0[0] + t * (p1[0] - p0[0]));
13850                                            }
13851                                        }
13852                                    }
13853                                    xs.sort_by(|a, b| {
13854                                        a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
13855                                    });
13856                                    let mut k = 0;
13857                                    while k + 1 < xs.len() {
13858                                        let a = map([xs[k], y], pen);
13859                                        let b = map([xs[k + 1], y], pen);
13860                                        lines.push([a[0], a[1], a[2], b[0], b[1], b[2]]);
13861                                        k += 2;
13862                                    }
13863                                }
13864                            }
13865                        }
13866                        pen += go.advance;
13867                    }
13868                    let mut gfx = self.gfx.borrow_mut();
13869                    let color = gfx.color;
13870                    let near = -gfx.camera.zdist + 0.05;
13871                    for l in &lines {
13872                        let (mut ax, mut ay, mut az) = (l[0], l[1], l[2]);
13873                        let (mut bx, mut by, mut bz) = (l[3], l[4], l[5]);
13874                        let da = gfx.camera.depth(ax, ay, az);
13875                        let db = gfx.camera.depth(bx, by, bz);
13876                        if da <= near && db <= near {
13877                            continue;
13878                        }
13879                        if da <= near {
13880                            let t = (near - da) / (db - da);
13881                            ax += t * (bx - ax);
13882                            ay += t * (by - ay);
13883                            az += t * (bz - az);
13884                        } else if db <= near {
13885                            let t = (near - da) / (db - da);
13886                            bx = ax + t * (bx - ax);
13887                            by = ay + t * (by - ay);
13888                            bz = az + t * (bz - az);
13889                        }
13890                        let (sax, say, da2) = gfx.camera.project(ax, ay, az);
13891                        let (sbx, sby, db2) = gfx.camera.project(bx, by, bz);
13892                        let depth = (da2 + db2) / 2.0;
13893                        gfx.depth_queue.push_line(depth, color, sax, say, sbx, sby);
13894                    }
13895                }
13896                return Ok(Value::Unit);
13897            },
13898            #[cfg(target_arch = "wasm32")]
13899            "font_text_3d"
13900            | "ข้อความฟอนต์3มิติ"
13901            | "字体3D"
13902            | "フォント3D"
13903            | "글꼴3D"
13904            | "متن_فونت_سه‌بعدی"
13905            | "نص_خط_ثلاثي_الأبعاد"
13906            | "טקסט_גופן_תלת_ממדי"
13907            | "تھری_ڈی_فونٹ_متن"
13908            | "texte_police_3d"
13909            | "schriftart_text_3d"
13910            | "текст_шрифт_3d" => {
13911                return Ok(Value::Unit);
13912            },
13913            // font_width(handle, px, "string") — pixel width of a string in a loaded font.
13914            #[cfg(not(target_arch = "wasm32"))]
13915            "font_width"
13916            | "ความกว้างฟอนต์"
13917            | "字体宽度"
13918            | "フォント幅"
13919            | "글꼴너비"
13920            | "عرض_فونت"
13921            | "عرض_الخط"
13922            | "רוחב_גופן"
13923            | "فونٹ_چوڑائی"
13924            | "largeur_police"
13925            | "schriftart_breite"
13926            | "ширина_шрифта" => {
13927                let id = self.arg_num(&args, 0, 0.0)? as i64;
13928                let px = self.arg_num(&args, 1, 16.0)? as f32;
13929                let s = self.arg_str(&args, 2, "");
13930                if id >= 0 && (id as usize) < self.fonts.len() {
13931                    return Ok(Value::Number(self.fonts[id as usize].measure(&s, px) as f64));
13932                }
13933                return Ok(Value::Number(0.0));
13934            },
13935            #[cfg(target_arch = "wasm32")]
13936            "font_width"
13937            | "ความกว้างฟอนต์"
13938            | "字体宽度"
13939            | "フォント幅"
13940            | "글꼴너비"
13941            | "عرض_فونت"
13942            | "عرض_الخط"
13943            | "רוחב_גופן"
13944            | "فونٹ_چوڑائی"
13945            | "largeur_police"
13946            | "schriftart_breite"
13947            | "ширина_шрифта" => {
13948                return Ok(Value::Number(0.0));
13949            },
13950            // font_glyph_outline(handle, "char", tol_em) — flattened vector outline of
13951            // ONE glyph in normalized em space (x→right, y→up, baseline at 0). Returns a
13952            // list of contours; each contour is a flat list [x0,y0,x1,y1,…]. Curves are
13953            // subdivided so deviation stays under tol_em (default 0.01). Empty on failure.
13954            #[cfg(not(target_arch = "wasm32"))]
13955            "font_glyph_outline"
13956            | "font_outline"
13957            | "เส้นขอบฟอนต์"
13958            | "字体轮廓"
13959            | "フォント輪郭"
13960            | "글꼴윤곽"
13961            | "خط‌دور_نویسه_فونت"
13962            | "حدود_حرف_الخط"
13963            | "קו_מתאר_גליף"
13964            | "فونٹ_گلف_آؤٹ_لائن" => {
13965                let id = self.arg_num(&args, 0, 0.0)? as i64;
13966                let s = self.arg_str(&args, 1, "");
13967                let tol = self.arg_num(&args, 2, 0.01)? as f32;
13968                let ch = s.chars().next().unwrap_or(' ');
13969                if id >= 0 && (id as usize) < self.fonts.len() {
13970                    let go = self.fonts[id as usize].glyph_outline(ch, tol.max(1e-4));
13971                    let mut contours: Vec<Value> = Vec::with_capacity(go.polylines.len());
13972                    for pl in &go.polylines {
13973                        let mut flat: Vec<Value> = Vec::with_capacity(pl.len() * 2);
13974                        for p in pl {
13975                            flat.push(Value::Number(p[0] as f64));
13976                            flat.push(Value::Number(p[1] as f64));
13977                        }
13978                        contours.push(Value::List(Rc::new(flat)));
13979                    }
13980                    return Ok(Value::List(Rc::new(contours)));
13981                }
13982                return Ok(Value::List(Rc::new(vec![])));
13983            },
13984            #[cfg(target_arch = "wasm32")]
13985            "font_glyph_outline"
13986            | "font_outline"
13987            | "เส้นขอบฟอนต์"
13988            | "字体轮廓"
13989            | "フォント輪郭"
13990            | "글꼴윤곽"
13991            | "خط‌دور_نویسه_فونت"
13992            | "حدود_حرف_الخط"
13993            | "קו_מתאר_גליף"
13994            | "فونٹ_گلف_آؤٹ_لائن" => {
13995                return Ok(Value::List(Rc::new(vec![])));
13996            },
13997            // font_advance(handle, "char") — normalized em advance width of ONE glyph
13998            // (baseline metric, ignores side bearings). Multiply by px for pixels.
13999            #[cfg(not(target_arch = "wasm32"))]
14000            "font_advance"
14001            | "ระยะฟอนต์"
14002            | "字体步进"
14003            | "フォント送り"
14004            | "글꼴전진"
14005            | "پیشروی_فونت"
14006            | "تقدم_الخط"
14007            | "קידום_גופן"
14008            | "فونٹ_ایڈوانس" => {
14009                let id = self.arg_num(&args, 0, 0.0)? as i64;
14010                let s = self.arg_str(&args, 1, "");
14011                let ch = s.chars().next().unwrap_or(' ');
14012                if id >= 0 && (id as usize) < self.fonts.len() {
14013                    return Ok(Value::Number(self.fonts[id as usize].advance(ch) as f64));
14014                }
14015                return Ok(Value::Number(0.0));
14016            },
14017            #[cfg(target_arch = "wasm32")]
14018            "font_advance"
14019            | "ระยะฟอนต์"
14020            | "字体步进"
14021            | "フォント送り"
14022            | "글꼴전진"
14023            | "پیشروی_فونت"
14024            | "تقدم_الخط"
14025            | "קידום_גופן"
14026            | "فونٹ_ایڈوانس" => {
14027                return Ok(Value::Number(0.0));
14028            },
14029
14030            // ui_frame(x,y,w,h, bracketLen) — sci-fi corner brackets
14031            "ui_frame"
14032            | "边框"
14033            | "フレーム枠"
14034            | "프레임틀"
14035            | "กรอบ"
14036            | "قاب_رابط"
14037            | "إطار_الواجهة"
14038            | "מסגרת_ממשק"
14039            | "یو_آئی_فریم"
14040            | "cadre_ui"
14041            | "ui_rahmen"
14042            | "ui_рамка" => {
14043                let x = self.arg_num(&args, 0, 0.0)? as f32;
14044                let y = self.arg_num(&args, 1, 0.0)? as f32;
14045                let w0 = self.arg_num(&args, 2, 0.0)? as f32;
14046                let h0 = self.arg_num(&args, 3, 0.0)? as f32;
14047                let l = self.arg_num(&args, 4, 14.0)? as f32;
14048                let segs = ling_ui::holo::corner_brackets(x, y, w0, h0, l);
14049                let mut gfx = self.gfx.borrow_mut();
14050                let (w, h, color) = (gfx.width, gfx.height, gfx.color);
14051                for sg in segs {
14052                    draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
14053                }
14054                return Ok(Value::Unit);
14055            },
14056            // ui_bevel(x,y,w,h, bevel) — beveled holographic panel outline
14057            "ui_bevel"
14058            | "斜角框"
14059            | "ベベル枠"
14060            | "베벨틀"
14061            | "กรอบเฉียง"
14062            | "لبه_شیبدار"
14063            | "حافة_مشطوفة"
14064            | "מסגרת_משופעת"
14065            | "یو_آئی_بیول"
14066            | "biseau_ui"
14067            | "ui_fase"
14068            | "ui_фаска" => {
14069                let x = self.arg_num(&args, 0, 0.0)? as f32;
14070                let y = self.arg_num(&args, 1, 0.0)? as f32;
14071                let w0 = self.arg_num(&args, 2, 0.0)? as f32;
14072                let h0 = self.arg_num(&args, 3, 0.0)? as f32;
14073                let bv = self.arg_num(&args, 4, 10.0)? as f32;
14074                let segs = ling_ui::holo::beveled_rect(x, y, w0, h0, bv);
14075                let mut gfx = self.gfx.borrow_mut();
14076                let (w, h, color) = (gfx.width, gfx.height, gfx.color);
14077                for sg in segs {
14078                    draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
14079                }
14080                return Ok(Value::Unit);
14081            },
14082
14083            // ══════════════════════════════════════════════════════════════════
14084            // VECTOR UI TOOLKIT  (crates/ling-ui/src/widgets.rs)
14085            // All widgets are vector + theme-coloured with an optional trailing
14086            // r,g,b override; interactive ones read the mouse and return state.
14087            // ══════════════════════════════════════════════════════════════════
14088            #[cfg(not(target_arch = "wasm32"))]
14089            "ui_theme"
14090            | "界面主题"
14091            | "UIテーマ"
14092            | "인터페이스테마"
14093            | "ธีมส่วนติดต่อ"
14094            | "پوسته_رابط"
14095            | "سمة_الواجهة"
14096            | "ערכת_נושא"
14097            | "یو_آئی_تھیم"
14098            | "thème_ui"
14099            | "ui_thema"
14100            | "ui_тема" => {
14101                let cur = self.ui_theme;
14102                let primary = self.color_at(&args, 0, cur.primary);
14103                let accent = self.color_at(&args, 3, cur.accent);
14104                let track = self.color_at(&args, 6, cur.track);
14105                let warn = self.color_at(&args, 9, cur.warn);
14106                let text = self.color_at(&args, 12, cur.text);
14107                let bg = self.color_at(&args, 15, cur.bg);
14108                self.ui_theme = UiTheme { primary, accent, track, warn, text, bg };
14109                return Ok(Value::Unit);
14110            },
14111
14112            // ui_theme_colors() -> [pr,pg,pb, ar,ag,ab, tr,tg,tb, wr,wg,wb,
14113            // xr,xg,xb, br,bg,bb] — the live theme every ui_* widget already
14114            // draws from (primary/accent/track/warn/text/bg, each 0-255),
14115            // so script-drawn UI (e.g. a hand-rolled text field) can match it
14116            // instead of guessing its own colours.
14117            "ui_theme_colors" | "인터페이스테마색상" => {
14118                let th = self.ui_theme;
14119                let mut out = Vec::with_capacity(18);
14120                for c in [th.primary, th.accent, th.track, th.warn, th.text, th.bg] {
14121                    out.push(Value::Number(((c >> 16) & 0xFF) as f64));
14122                    out.push(Value::Number(((c >> 8) & 0xFF) as f64));
14123                    out.push(Value::Number((c & 0xFF) as f64));
14124                }
14125                return Ok(Value::List(Rc::new(out)));
14126            },
14127
14128            // ── HUD ──────────────────────────────────────────────────────────
14129            #[cfg(not(target_arch = "wasm32"))]
14130            "ui_radar"
14131            | "雷达"
14132            | "レーダー"
14133            | "레이더"
14134            | "เรดาร์"
14135            | "رادار_رابط"
14136            | "رادار_الواجهة"
14137            | "מכ״ם_ממשק"
14138            | "یو_آئی_ریڈار"
14139            | "radar_ui"
14140            | "ui_радар" => {
14141                let cx = self.arg_num(&args, 0, 0.)? as f32;
14142                let cy = self.arg_num(&args, 1, 0.)? as f32;
14143                let r = self.arg_num(&args, 2, 60.)? as f32;
14144                let sweep = self.arg_num(&args, 3, 0.)? as f32;
14145                let th = self.ui_theme;
14146                let prim = self.color_at(&args, 4, th.primary);
14147                self.draw_ui(&ling_ui::widgets::radar(
14148                    cx, cy, r, sweep, prim, th.accent, th.track,
14149                ));
14150                return Ok(Value::Unit);
14151            },
14152            #[cfg(not(target_arch = "wasm32"))]
14153            "ui_compass"
14154            | "罗盘"
14155            | "コンパス"
14156            | "나침반"
14157            | "เข็มทิศ"
14158            | "قطب‌نمای_رابط"
14159            | "بوصلة_الواجهة"
14160            | "מצפן_ממשק"
14161            | "یو_آئی_قطب_نما"
14162            | "boussole_ui"
14163            | "ui_kompass"
14164            | "ui_компас" => {
14165                let x = self.arg_num(&args, 0, 0.)? as f32;
14166                let y = self.arg_num(&args, 1, 0.)? as f32;
14167                let w0 = self.arg_num(&args, 2, 300.)? as f32;
14168                let h0 = self.arg_num(&args, 3, 24.)? as f32;
14169                let head = self.arg_num(&args, 4, 0.)? as f32;
14170                let th = self.ui_theme;
14171                let prim = self.color_at(&args, 5, th.primary);
14172                self.draw_ui(&ling_ui::widgets::compass(
14173                    x, y, w0, h0, head, prim, th.track,
14174                ));
14175                return Ok(Value::Unit);
14176            },
14177            #[cfg(not(target_arch = "wasm32"))]
14178            "ui_reticle"
14179            | "准星"
14180            | "照準"
14181            | "조준선"
14182            | "เป้าเล็ง"
14183            | "نشانه_رابط"
14184            | "علامة_تصويب"
14185            | "כוונת"
14186            | "نشانہ"
14187            | "réticule_ui"
14188            | "ui_fadenkreuz"
14189            | "ui_прицел" => {
14190                let cx = self.arg_num(&args, 0, 0.)? as f32;
14191                let cy = self.arg_num(&args, 1, 0.)? as f32;
14192                let r = self.arg_num(&args, 2, 30.)? as f32;
14193                let spread = self.arg_num(&args, 3, 0.)? as f32;
14194                let th = self.ui_theme;
14195                let prim = self.color_at(&args, 4, th.primary);
14196                self.draw_ui(&ling_ui::widgets::reticle(cx, cy, r, spread, prim));
14197                return Ok(Value::Unit);
14198            },
14199            #[cfg(not(target_arch = "wasm32"))]
14200            "ui_target"
14201            | "锁定框"
14202            | "ターゲット"
14203            | "표적"
14204            | "กรอบเป้า"
14205            | "قاب_هدف"
14206            | "إطار_الهدف"
14207            | "מסגרת_מטרה"
14208            | "ہدف_فریم"
14209            | "cible_ui"
14210            | "ui_ziel"
14211            | "ui_цель" => {
14212                let x = self.arg_num(&args, 0, 0.)? as f32;
14213                let y = self.arg_num(&args, 1, 0.)? as f32;
14214                let w0 = self.arg_num(&args, 2, 80.)? as f32;
14215                let h0 = self.arg_num(&args, 3, 80.)? as f32;
14216                let lock = self.arg_num(&args, 4, 0.)? as f32;
14217                let th = self.ui_theme;
14218                let prim = self.color_at(&args, 5, th.primary);
14219                self.draw_ui(&ling_ui::widgets::target(
14220                    x, y, w0, h0, lock, prim, th.accent,
14221                ));
14222                return Ok(Value::Unit);
14223            },
14224            #[cfg(not(target_arch = "wasm32"))]
14225            "ui_panel"
14226            | "面板"
14227            | "パネル"
14228            | "패널"
14229            | "แผง"
14230            | "پنل_رابط"
14231            | "لوحة_الواجهة"
14232            | "לוח_ממשק"
14233            | "یو_آئی_پینل"
14234            | "panneau_ui"
14235            | "ui_feld"
14236            | "ui_панель" => {
14237                let x = self.arg_num(&args, 0, 0.)? as f32;
14238                let y = self.arg_num(&args, 1, 0.)? as f32;
14239                let w0 = self.arg_num(&args, 2, 200.)? as f32;
14240                let h0 = self.arg_num(&args, 3, 120.)? as f32;
14241                let bv = self.arg_num(&args, 4, 12.)? as f32;
14242                let th = self.ui_theme;
14243                let prim = self.color_at(&args, 5, th.primary);
14244                self.draw_ui(&ling_ui::widgets::panel(x, y, w0, h0, bv, prim, th.bg));
14245                return Ok(Value::Unit);
14246            },
14247            #[cfg(not(target_arch = "wasm32"))]
14248            "ui_scanlines"
14249            | "扫描线"
14250            | "走査線"
14251            | "스캔라인"
14252            | "เส้นสแกน"
14253            | "خطوط_اسکن"
14254            | "خطوط_المسح"
14255            | "קווי_סריקה"
14256            | "اسکین_لائنز"
14257            | "lignes_balayage_ui"
14258            | "ui_abtastzeilen"
14259            | "ui_линии_развёртки" => {
14260                let x = self.arg_num(&args, 0, 0.)? as f32;
14261                let y = self.arg_num(&args, 1, 0.)? as f32;
14262                let w0 = self.arg_num(&args, 2, 200.)? as f32;
14263                let h0 = self.arg_num(&args, 3, 120.)? as f32;
14264                let dens = self.arg_num(&args, 4, 24.)? as usize;
14265                let th = self.ui_theme;
14266                let line = self.color_at(&args, 5, th.track);
14267                self.draw_ui(&ling_ui::widgets::scanlines(x, y, w0, h0, dens, line));
14268                return Ok(Value::Unit);
14269            },
14270
14271            // ── Meters ───────────────────────────────────────────────────────
14272            #[cfg(not(target_arch = "wasm32"))]
14273            "ui_bar"
14274            | "进度条"
14275            | "バー"
14276            | "막대"
14277            | "แถบ"
14278            | "نوار_رابط"
14279            | "شريط_الواجهة"
14280            | "סרגל_ממשק"
14281            | "یو_آئی_بار"
14282            | "barre_ui"
14283            | "ui_leiste"
14284            | "ui_полоса" => {
14285                let x = self.arg_num(&args, 0, 0.)? as f32;
14286                let y = self.arg_num(&args, 1, 0.)? as f32;
14287                let w0 = self.arg_num(&args, 2, 160.)? as f32;
14288                let h0 = self.arg_num(&args, 3, 16.)? as f32;
14289                let val = self.arg_num(&args, 4, 0.)? as f32;
14290                let max = self.arg_num(&args, 5, 1.)? as f32;
14291                let th = self.ui_theme;
14292                let fill = self.color_at(&args, 6, th.primary);
14293                self.draw_ui(&ling_ui::widgets::bar(
14294                    x,
14295                    y,
14296                    w0,
14297                    h0,
14298                    val / max.max(1e-6),
14299                    fill,
14300                    th.track,
14301                ));
14302                return Ok(Value::Unit);
14303            },
14304            #[cfg(not(target_arch = "wasm32"))]
14305            "ui_segbar"
14306            | "分段条"
14307            | "分割バー"
14308            | "분할막대"
14309            | "แถบแบ่ง"
14310            | "نوار_قطعه‌ای"
14311            | "شريط_مقسم"
14312            | "סרגל_מקוטע"
14313            | "سیگمنٹ_بار"
14314            | "barre_segmentée_ui"
14315            | "ui_segmentleiste"
14316            | "ui_сегментная_полоса" => {
14317                let x = self.arg_num(&args, 0, 0.)? as f32;
14318                let y = self.arg_num(&args, 1, 0.)? as f32;
14319                let w0 = self.arg_num(&args, 2, 160.)? as f32;
14320                let h0 = self.arg_num(&args, 3, 16.)? as f32;
14321                let val = self.arg_num(&args, 4, 0.)? as f32;
14322                let max = self.arg_num(&args, 5, 1.)? as f32;
14323                let segs = self.arg_num(&args, 6, 10.)? as usize;
14324                let th = self.ui_theme;
14325                let fill = self.color_at(&args, 7, th.primary);
14326                self.draw_ui(&ling_ui::widgets::segbar(
14327                    x,
14328                    y,
14329                    w0,
14330                    h0,
14331                    val / max.max(1e-6),
14332                    segs,
14333                    fill,
14334                    th.track,
14335                ));
14336                return Ok(Value::Unit);
14337            },
14338            #[cfg(not(target_arch = "wasm32"))]
14339            "ui_gauge"
14340            | "仪表"
14341            | "ゲージ"
14342            | "게이지"
14343            | "มาตรวัด"
14344            | "گیج_رابط"
14345            | "مقياس_الواجهة"
14346            | "מד_ממשק"
14347            | "یو_آئی_گیج"
14348            | "jauge_ui"
14349            | "ui_anzeige"
14350            | "ui_индикатор" => {
14351                let cx = self.arg_num(&args, 0, 0.)? as f32;
14352                let cy = self.arg_num(&args, 1, 0.)? as f32;
14353                let r = self.arg_num(&args, 2, 50.)? as f32;
14354                let val = self.arg_num(&args, 3, 0.)? as f32;
14355                let max = self.arg_num(&args, 4, 1.)? as f32;
14356                let th = self.ui_theme;
14357                let needle = self.color_at(&args, 5, th.warn);
14358                self.draw_ui(&ling_ui::widgets::gauge(
14359                    cx,
14360                    cy,
14361                    r,
14362                    val / max.max(1e-6),
14363                    needle,
14364                    th.accent,
14365                    th.track,
14366                ));
14367                return Ok(Value::Unit);
14368            },
14369            #[cfg(not(target_arch = "wasm32"))]
14370            "ui_ring"
14371            | "环表"
14372            | "リングメーター"
14373            | "링미터"
14374            | "วงแหวนวัด"
14375            | "حلقه_گیج"
14376            | "حلقة_قياس"
14377            | "טבעת_מד"
14378            | "رنگ_گیج"
14379            | "anneau_ui"
14380            | "ui_кольцо" => {
14381                let cx = self.arg_num(&args, 0, 0.)? as f32;
14382                let cy = self.arg_num(&args, 1, 0.)? as f32;
14383                let r = self.arg_num(&args, 2, 40.)? as f32;
14384                let val = self.arg_num(&args, 3, 0.)? as f32;
14385                let max = self.arg_num(&args, 4, 1.)? as f32;
14386                let th = self.ui_theme;
14387                let fill = self.color_at(&args, 5, th.primary);
14388                self.draw_ui(&ling_ui::widgets::ring(
14389                    cx,
14390                    cy,
14391                    r,
14392                    val / max.max(1e-6),
14393                    fill,
14394                    th.track,
14395                ));
14396                return Ok(Value::Unit);
14397            },
14398            #[cfg(not(target_arch = "wasm32"))]
14399            "ui_vu"
14400            | "音量条"
14401            | "VUメーター"
14402            | "음량막대"
14403            | "มาตรเสียง"
14404            | "گیج_صدا"
14405            | "مقياس_مستوى_الصوت"
14406            | "מד_עוצמה"
14407            | "وی_یو_میٹر"
14408            | "vumètre_ui"
14409            | "ui_vumeter"
14410            | "ui_вю_метр" => {
14411                let x = self.arg_num(&args, 0, 0.)? as f32;
14412                let y = self.arg_num(&args, 1, 0.)? as f32;
14413                let w0 = self.arg_num(&args, 2, 160.)? as f32;
14414                let h0 = self.arg_num(&args, 3, 60.)? as f32;
14415                let levels = self.arg_list_f32(&args, 4);
14416                let th = self.ui_theme;
14417                let fill = self.color_at(&args, 5, th.primary);
14418                self.draw_ui(&ling_ui::widgets::vu(x, y, w0, h0, &levels, fill, th.warn));
14419                return Ok(Value::Unit);
14420            },
14421            #[cfg(not(target_arch = "wasm32"))]
14422            "ui_spark"
14423            | "迷你图"
14424            | "スパークライン"
14425            | "스파크라인"
14426            | "กราฟจิ๋ว"
14427            | "نمودار_ریز"
14428            | "رسم_مصغر"
14429            | "גרף_זעיר"
14430            | "اسپارک_لائن"
14431            | "mini_graphe_ui"
14432            | "ui_sparkline"
14433            | "ui_мини_график" => {
14434                let x = self.arg_num(&args, 0, 0.)? as f32;
14435                let y = self.arg_num(&args, 1, 0.)? as f32;
14436                let w0 = self.arg_num(&args, 2, 160.)? as f32;
14437                let h0 = self.arg_num(&args, 3, 40.)? as f32;
14438                let vals = self.arg_list_f32(&args, 4);
14439                let th = self.ui_theme;
14440                let line = self.color_at(&args, 5, th.accent);
14441                self.draw_ui(&ling_ui::widgets::spark(x, y, w0, h0, &vals, line));
14442                return Ok(Value::Unit);
14443            },
14444            #[cfg(not(target_arch = "wasm32"))]
14445            "ui_battery"
14446            | "电池"
14447            | "バッテリー"
14448            | "배터리"
14449            | "แบตเตอรี่"
14450            | "نشانگر_باتری"
14451            | "مؤشر_البطارية"
14452            | "מחוון_סוללה"
14453            | "بیٹری_انڈیکیٹر"
14454            | "batterie_ui"
14455            | "ui_batterie"
14456            | "ui_батарея" => {
14457                let x = self.arg_num(&args, 0, 0.)? as f32;
14458                let y = self.arg_num(&args, 1, 0.)? as f32;
14459                let w0 = self.arg_num(&args, 2, 50.)? as f32;
14460                let h0 = self.arg_num(&args, 3, 22.)? as f32;
14461                let val = self.arg_num(&args, 4, 1.)? as f32;
14462                let max = self.arg_num(&args, 5, 1.)? as f32;
14463                let th = self.ui_theme;
14464                let fill = self.color_at(&args, 6, th.accent);
14465                self.draw_ui(&ling_ui::widgets::battery(
14466                    x,
14467                    y,
14468                    w0,
14469                    h0,
14470                    val / max.max(1e-6),
14471                    fill,
14472                    th.track,
14473                    th.warn,
14474                ));
14475                return Ok(Value::Unit);
14476            },
14477
14478            // ── Interface controls (interactive → return state) ──────────────
14479            #[cfg(not(target_arch = "wasm32"))]
14480            "ui_button"
14481            | "按钮"
14482            | "ボタン"
14483            | "버튼"
14484            | "ปุ่ม"
14485            | "دکمه_رابط"
14486            | "زر_الواجهة"
14487            | "כפתור_ממשק"
14488            | "یو_آئی_بٹن"
14489            | "bouton_ui"
14490            | "ui_knopf"
14491            | "ui_кнопка" => {
14492                let x = self.arg_num(&args, 0, 0.)? as f32;
14493                let y = self.arg_num(&args, 1, 0.)? as f32;
14494                let w0 = self.arg_num(&args, 2, 120.)? as f32;
14495                let h0 = self.arg_num(&args, 3, 40.)? as f32;
14496                let (mx, my, down) = self.mouse_now();
14497                let hover = ling_ui::holo::hit_rect(mx, my, x, y, w0, h0);
14498                let clicked = hover && down && !self.mouse_was_down;
14499                let th = self.ui_theme;
14500                let prim = self.color_at(&args, 4, th.primary);
14501                self.draw_ui(&ling_ui::widgets::button(
14502                    x,
14503                    y,
14504                    w0,
14505                    h0,
14506                    hover,
14507                    down && hover,
14508                    prim,
14509                    th.bg,
14510                ));
14511                return Ok(Value::Number(if clicked { 1.0 } else { 0.0 }));
14512            },
14513            #[cfg(not(target_arch = "wasm32"))]
14514            "ui_toggle"
14515            | "开关"
14516            | "トグル"
14517            | "토글"
14518            | "สวิตช์"
14519            | "کلید_ضامن"
14520            | "مفتاح_تبديل"
14521            | "מתג"
14522            | "ٹوگل"
14523            | "bascule_ui"
14524            | "ui_schalter"
14525            | "ui_переключатель" => {
14526                let x = self.arg_num(&args, 0, 0.)? as f32;
14527                let y = self.arg_num(&args, 1, 0.)? as f32;
14528                let w0 = self.arg_num(&args, 2, 52.)? as f32;
14529                let h0 = self.arg_num(&args, 3, 24.)? as f32;
14530                let mut state = self.arg_num(&args, 4, 0.)? > 0.5;
14531                let (mx, my, down) = self.mouse_now();
14532                let hover = ling_ui::holo::hit_rect(mx, my, x, y, w0, h0);
14533                if hover && down && !self.mouse_was_down {
14534                    state = !state;
14535                }
14536                let th = self.ui_theme;
14537                let on = self.color_at(&args, 5, th.accent);
14538                self.draw_ui(&ling_ui::widgets::toggle(x, y, w0, h0, state, on, th.track));
14539                return Ok(Value::Number(if state { 1.0 } else { 0.0 }));
14540            },
14541            #[cfg(not(target_arch = "wasm32"))]
14542            "ui_slider"
14543            | "滑块"
14544            | "スライダー"
14545            | "슬라이더"
14546            | "แถบเลื่อน"
14547            | "لغزنده"
14548            | "شريط_انزلاق"
14549            | "מחוון_החלקה"
14550            | "سلائیڈر"
14551            | "curseur_ui"
14552            | "ui_schieberegler"
14553            | "ui_ползунок" => {
14554                let x = self.arg_num(&args, 0, 0.)? as f32;
14555                let y = self.arg_num(&args, 1, 0.)? as f32;
14556                let w0 = self.arg_num(&args, 2, 160.)? as f32;
14557                let mut val = self.arg_num(&args, 3, 0.)? as f32;
14558                let mn = self.arg_num(&args, 4, 0.)? as f32;
14559                let mx_ = self.arg_num(&args, 5, 1.)? as f32;
14560                let (mx, my, down) = self.mouse_now();
14561                let hover = ling_ui::holo::hit_rect(mx, my, x - 8.0, y - 10.0, w0 + 16.0, 20.0);
14562                if hover && down {
14563                    let frac = ((mx - x) / w0).clamp(0.0, 1.0);
14564                    val = mn + (mx_ - mn) * frac;
14565                }
14566                let frac = ((val - mn) / (mx_ - mn).abs().max(1e-6)).clamp(0.0, 1.0);
14567                let th = self.ui_theme;
14568                let fill = self.color_at(&args, 6, th.primary);
14569                self.draw_ui(&ling_ui::widgets::slider(
14570                    x, y, w0, frac, hover, fill, th.track,
14571                ));
14572                return Ok(Value::Number(val as f64));
14573            },
14574            #[cfg(not(target_arch = "wasm32"))]
14575            "ui_checkbox"
14576            | "复选框"
14577            | "チェックボックス"
14578            | "체크박스"
14579            | "ช่องเลือก"
14580            | "جعبه_علامت"
14581            | "مربع_اختيار"
14582            | "תיבת_סימון"
14583            | "چیک_باکس"
14584            | "case_cocher_ui"
14585            | "ui_kontrollkästchen"
14586            | "ui_флажок" => {
14587                let x = self.arg_num(&args, 0, 0.)? as f32;
14588                let y = self.arg_num(&args, 1, 0.)? as f32;
14589                let s = self.arg_num(&args, 2, 20.)? as f32;
14590                let mut checked = self.arg_num(&args, 3, 0.)? > 0.5;
14591                let (mx, my, down) = self.mouse_now();
14592                let hover = ling_ui::holo::hit_rect(mx, my, x, y, s, s);
14593                if hover && down && !self.mouse_was_down {
14594                    checked = !checked;
14595                }
14596                let th = self.ui_theme;
14597                let prim = self.color_at(&args, 4, th.primary);
14598                self.draw_ui(&ling_ui::widgets::checkbox(
14599                    x, y, s, checked, hover, prim, th.track,
14600                ));
14601                return Ok(Value::Number(if checked { 1.0 } else { 0.0 }));
14602            },
14603            #[cfg(not(target_arch = "wasm32"))]
14604            "ui_tabs"
14605            | "标签页"
14606            | "タブ"
14607            | "탭"
14608            | "แท็บ"
14609            | "برگه‌ها"
14610            | "ألسنة_الواجهة"
14611            | "לשוניות"
14612            | "ٹیبز"
14613            | "onglets_ui"
14614            | "ui_reiter"
14615            | "ui_вкладки" => {
14616                let x = self.arg_num(&args, 0, 0.)? as f32;
14617                let y = self.arg_num(&args, 1, 0.)? as f32;
14618                let w0 = self.arg_num(&args, 2, 240.)? as f32;
14619                let h0 = self.arg_num(&args, 3, 28.)? as f32;
14620                let count = self.arg_num(&args, 4, 3.)? as usize;
14621                let mut active = self.arg_num(&args, 5, 0.)? as i32;
14622                let (mx, my, down) = self.mouse_now();
14623                let mut hover = -1;
14624                if my >= y && my <= y + h0 && mx >= x && mx <= x + w0 && count > 0 {
14625                    hover = (((mx - x) / (w0 / count as f32)) as i32)
14626                        .max(0)
14627                        .min(count as i32 - 1);
14628                    if down && !self.mouse_was_down {
14629                        active = hover;
14630                    }
14631                }
14632                let th = self.ui_theme;
14633                let prim = self.color_at(&args, 6, th.primary);
14634                self.draw_ui(&ling_ui::widgets::tabs(
14635                    x,
14636                    y,
14637                    w0,
14638                    h0,
14639                    count,
14640                    active as usize,
14641                    hover,
14642                    prim,
14643                    th.track,
14644                ));
14645                return Ok(Value::Number(active as f64));
14646            },
14647            #[cfg(not(target_arch = "wasm32"))]
14648            "ui_progress"
14649            | "进度"
14650            | "プログレス"
14651            | "진행바"
14652            | "ความคืบหน้า"
14653            | "نوار_پیشرفت"
14654            | "شريط_التقدم"
14655            | "פס_התקדמות"
14656            | "پیش_رفت_بار"
14657            | "progression_ui"
14658            | "ui_fortschritt"
14659            | "ui_прогресс" => {
14660                let x = self.arg_num(&args, 0, 0.)? as f32;
14661                let y = self.arg_num(&args, 1, 0.)? as f32;
14662                let w0 = self.arg_num(&args, 2, 200.)? as f32;
14663                let h0 = self.arg_num(&args, 3, 12.)? as f32;
14664                let frac = self.arg_num(&args, 4, 0.)? as f32;
14665                let th = self.ui_theme;
14666                let fill = self.color_at(&args, 5, th.accent);
14667                self.draw_ui(&ling_ui::widgets::progress(
14668                    x, y, w0, h0, frac, fill, th.track,
14669                ));
14670                return Ok(Value::Unit);
14671            },
14672            #[cfg(not(target_arch = "wasm32"))]
14673            "ui_tooltip"
14674            | "提示框"
14675            | "ツールチップ"
14676            | "툴팁"
14677            | "คำแนะนำ"
14678            | "راهنمای_شناور"
14679            | "تلميح_الواجهة"
14680            | "חלונית_עזרה"
14681            | "ٹول_ٹپ"
14682            | "infobulle_ui"
14683            | "ui_подсказка" => {
14684                let x = self.arg_num(&args, 0, 0.)? as f32;
14685                let y = self.arg_num(&args, 1, 0.)? as f32;
14686                let w0 = self.arg_num(&args, 2, 120.)? as f32;
14687                let h0 = self.arg_num(&args, 3, 28.)? as f32;
14688                let th = self.ui_theme;
14689                let prim = self.color_at(&args, 4, th.primary);
14690                self.draw_ui(&ling_ui::widgets::tooltip(x, y, w0, h0, prim, th.bg));
14691                return Ok(Value::Unit);
14692            },
14693            #[cfg(not(target_arch = "wasm32"))]
14694            "ui_stepper"
14695            | "步进器"
14696            | "ステッパー"
14697            | "스테퍼"
14698            | "ตัวปรับค่า"
14699            | "پله‌گر"
14700            | "زر_خطوات"
14701            | "בורר_מדורג"
14702            | "اسٹیپر"
14703            | "pas_à_pas_ui"
14704            | "ui_schrittsteuerung"
14705            | "ui_степпер" => {
14706                let x = self.arg_num(&args, 0, 0.)? as f32;
14707                let y = self.arg_num(&args, 1, 0.)? as f32;
14708                let w0 = self.arg_num(&args, 2, 120.)? as f32;
14709                let h0 = self.arg_num(&args, 3, 28.)? as f32;
14710                let mut val = self.arg_num(&args, 4, 0.)? as f32;
14711                let step = self.arg_num(&args, 5, 1.)? as f32;
14712                let (mx, my, down) = self.mouse_now();
14713                let hm = ling_ui::holo::hit_rect(mx, my, x, y, h0, h0);
14714                let hp = ling_ui::holo::hit_rect(mx, my, x + w0 - h0, y, h0, h0);
14715                if down && !self.mouse_was_down {
14716                    if hm {
14717                        val -= step;
14718                    }
14719                    if hp {
14720                        val += step;
14721                    }
14722                }
14723                let th = self.ui_theme;
14724                let prim = self.color_at(&args, 6, th.primary);
14725                self.draw_ui(&ling_ui::widgets::stepper(
14726                    x, y, w0, h0, hm, hp, prim, th.track,
14727                ));
14728                return Ok(Value::Number(val as f64));
14729            },
14730
14731            // ── Game UI ──────────────────────────────────────────────────────
14732            #[cfg(not(target_arch = "wasm32"))]
14733            "ui_healthbar"
14734            | "血条"
14735            | "体力バー"
14736            | "체력바"
14737            | "แถบพลังชีวิต"
14738            | "نوار_سلامتی"
14739            | "شريط_الصحة"
14740            | "פס_בריאות"
14741            | "ہیلتھ_بار"
14742            | "barre_vie_ui"
14743            | "ui_lebensbalken"
14744            | "ui_полоса_здоровья" => {
14745                let x = self.arg_num(&args, 0, 0.)? as f32;
14746                let y = self.arg_num(&args, 1, 0.)? as f32;
14747                let w0 = self.arg_num(&args, 2, 180.)? as f32;
14748                let h0 = self.arg_num(&args, 3, 16.)? as f32;
14749                let val = self.arg_num(&args, 4, 1.)? as f32;
14750                let max = self.arg_num(&args, 5, 1.)? as f32;
14751                let pulse = self.arg_num(&args, 6, 0.)? as f32;
14752                let th = self.ui_theme;
14753                let full = self.color_at(&args, 7, th.accent);
14754                self.draw_ui(&ling_ui::widgets::healthbar(
14755                    x,
14756                    y,
14757                    w0,
14758                    h0,
14759                    val / max.max(1e-6),
14760                    pulse,
14761                    full,
14762                    th.warn,
14763                    th.track,
14764                ));
14765                return Ok(Value::Unit);
14766            },
14767            #[cfg(not(target_arch = "wasm32"))]
14768            "ui_cooldown"
14769            | "冷却"
14770            | "クールダウン"
14771            | "쿨다운"
14772            | "คูลดาวน์"
14773            | "زمان_خنک‌سازی"
14774            | "مؤقت_التهدئة"
14775            | "זמן_קירור"
14776            | "کول_ڈاؤن"
14777            | "recharge_ui"
14778            | "ui_abklingzeit"
14779            | "ui_перезарядка" => {
14780                let cx = self.arg_num(&args, 0, 0.)? as f32;
14781                let cy = self.arg_num(&args, 1, 0.)? as f32;
14782                let r = self.arg_num(&args, 2, 28.)? as f32;
14783                let frac = self.arg_num(&args, 3, 0.)? as f32;
14784                let th = self.ui_theme;
14785                let fill = self.color_at(&args, 4, th.primary);
14786                self.draw_ui(&ling_ui::widgets::cooldown(cx, cy, r, frac, fill, th.track));
14787                return Ok(Value::Unit);
14788            },
14789            #[cfg(not(target_arch = "wasm32"))]
14790            "ui_counter"
14791            | "计数器"
14792            | "カウンター"
14793            | "카운터"
14794            | "ตัวนับ"
14795            | "شمارشگر"
14796            | "عداد_الواجهة"
14797            | "מונה_ממשק"
14798            | "کاؤنٹر"
14799            | "compteur_ui"
14800            | "ui_zähler"
14801            | "ui_счётчик" => {
14802                let x = self.arg_num(&args, 0, 0.)? as f32;
14803                let y = self.arg_num(&args, 1, 0.)? as f32;
14804                let dw = self.arg_num(&args, 2, 14.)? as f32;
14805                let dh = self.arg_num(&args, 3, 24.)? as f32;
14806                let val = self.arg_num(&args, 4, 0.)? as i64;
14807                let digits = self.arg_num(&args, 5, 4.)? as usize;
14808                let th = self.ui_theme;
14809                let on = self.color_at(&args, 6, th.primary);
14810                let off = ling_ui::widgets::shade(th.track, 0.5);
14811                self.draw_ui(&ling_ui::widgets::counter(
14812                    x, y, dw, dh, val, digits, on, off,
14813                ));
14814                return Ok(Value::Unit);
14815            },
14816            #[cfg(not(target_arch = "wasm32"))]
14817            "ui_minimap"
14818            | "小地图"
14819            | "ミニマップ"
14820            | "미니맵"
14821            | "แผนที่ย่อ"
14822            | "نقشه_کوچک"
14823            | "خريطة_مصغرة"
14824            | "מפה_מוקטנת"
14825            | "منی_میپ"
14826            | "minicarte_ui"
14827            | "ui_minikarte"
14828            | "ui_миникарта" => {
14829                let x = self.arg_num(&args, 0, 0.)? as f32;
14830                let y = self.arg_num(&args, 1, 0.)? as f32;
14831                let w0 = self.arg_num(&args, 2, 140.)? as f32;
14832                let h0 = self.arg_num(&args, 3, 140.)? as f32;
14833                let th = self.ui_theme;
14834                let prim = self.color_at(&args, 4, th.primary);
14835                self.draw_ui(&ling_ui::widgets::minimap(x, y, w0, h0, prim, th.bg));
14836                return Ok(Value::Unit);
14837            },
14838            #[cfg(not(target_arch = "wasm32"))]
14839            "ui_dpad"
14840            | "方向键"
14841            | "方向パッド"
14842            | "방향패드"
14843            | "ปุ่มทิศทาง"
14844            | "دسته_جهت‌دار"
14845            | "لوحة_الاتجاهات"
14846            | "לוח_כיוונים"
14847            | "ڈی_پیڈ"
14848            | "croix_direction_ui"
14849            | "ui_steuerkreuz"
14850            | "ui_крестовина" => {
14851                let cx = self.arg_num(&args, 0, 0.)? as f32;
14852                let cy = self.arg_num(&args, 1, 0.)? as f32;
14853                let r = self.arg_num(&args, 2, 50.)? as f32;
14854                let (mx, my, down) = self.mouse_now();
14855                let mut dir = 0;
14856                if down {
14857                    let (dx, dy) = (mx - cx, my - cy);
14858                    if dx * dx + dy * dy <= r * r {
14859                        if dx.abs() > dy.abs() {
14860                            dir = if dx > 0.0 { 2 } else { 4 };
14861                        } else {
14862                            dir = if dy > 0.0 { 3 } else { 1 };
14863                        }
14864                    }
14865                }
14866                let th = self.ui_theme;
14867                let prim = self.color_at(&args, 3, th.primary);
14868                self.draw_ui(&ling_ui::widgets::dpad(cx, cy, r, dir, prim, th.track));
14869                return Ok(Value::Number(dir as f64));
14870            },
14871            #[cfg(not(target_arch = "wasm32"))]
14872            "ui_slotgrid"
14873            | "物品格"
14874            | "スロットグリッド"
14875            | "슬롯격자"
14876            | "ช่องไอเทม"
14877            | "شبکه_شیار"
14878            | "شبكة_الفتحات"
14879            | "רשת_חריצים"
14880            | "سلاٹ_گرڈ"
14881            | "grille_emplacements_ui"
14882            | "ui_slotraster"
14883            | "ui_сетка_слотов" => {
14884                let x = self.arg_num(&args, 0, 0.)? as f32;
14885                let y = self.arg_num(&args, 1, 0.)? as f32;
14886                let cols = self.arg_num(&args, 2, 4.)? as usize;
14887                let rows = self.arg_num(&args, 3, 1.)? as usize;
14888                let cell = self.arg_num(&args, 4, 36.)? as f32;
14889                let sel = self.arg_num(&args, 5, -1.)? as i32;
14890                let th = self.ui_theme;
14891                let prim = self.color_at(&args, 6, th.primary);
14892                self.draw_ui(&ling_ui::widgets::slotgrid(
14893                    x, y, cols, rows, cell, sel, prim, th.track,
14894                ));
14895                return Ok(Value::Unit);
14896            },
14897            #[cfg(not(target_arch = "wasm32"))]
14898            "ui_vignette"
14899            | "暗角"
14900            | "ビネット"
14901            | "비네트"
14902            | "ขอบมืด"
14903            | "سایه‌گرد_کادر"
14904            | "تظليل_الحواف"
14905            | "הצללת_מסגרת"
14906            | "ویگنیٹ"
14907            | "vignette_ui"
14908            | "ui_виньетка" => {
14909                let intensity = self.arg_num(&args, 0, 0.5)? as f32;
14910                let (w, h) = {
14911                    let g = self.gfx.borrow();
14912                    (g.width as f32, g.height as f32)
14913                };
14914                let th = self.ui_theme;
14915                let col = self.color_at(&args, 1, th.warn);
14916                self.draw_ui(&ling_ui::widgets::vignette(w, h, intensity, col));
14917                return Ok(Value::Unit);
14918            },
14919
14920            // ── Faux-3D in 2D space ──────────────────────────────────────────
14921            #[cfg(not(target_arch = "wasm32"))]
14922            "ui_gauge3d"
14923            | "立体仪表"
14924            | "立体ゲージ"
14925            | "입체게이지"
14926            | "มาตรวัด3มิติ"
14927            | "گیج_سه‌بعدی"
14928            | "مقياس_ثلاثي_الأبعاد"
14929            | "מד_תלת_ממדי"
14930            | "تھری_ڈی_گیج"
14931            | "jauge_3d_ui"
14932            | "ui_anzeige_3d"
14933            | "ui_индикатор_3d" => {
14934                let cx = self.arg_num(&args, 0, 0.)? as f32;
14935                let cy = self.arg_num(&args, 1, 0.)? as f32;
14936                let r = self.arg_num(&args, 2, 50.)? as f32;
14937                let val = self.arg_num(&args, 3, 0.)? as f32;
14938                let max = self.arg_num(&args, 4, 1.)? as f32;
14939                let spin = self.arg_num(&args, 5, 0.)? as f32;
14940                let th = self.ui_theme;
14941                let fill = self.color_at(&args, 6, th.primary);
14942                self.draw_ui(&ling_ui::widgets::gauge3d(
14943                    cx,
14944                    cy,
14945                    r,
14946                    val / max.max(1e-6),
14947                    spin,
14948                    fill,
14949                    th.track,
14950                ));
14951                return Ok(Value::Unit);
14952            },
14953            #[cfg(not(target_arch = "wasm32"))]
14954            "ui_panel3d"
14955            | "立体面板"
14956            | "立体パネル"
14957            | "입체패널"
14958            | "แผง3มิติ"
14959            | "پنل_سه‌بعدی"
14960            | "لوحة_ثلاثية_الأبعاد"
14961            | "לוח_תלת_ממדי"
14962            | "تھری_ڈی_پینل"
14963            | "panneau_3d_ui"
14964            | "ui_feld_3d"
14965            | "ui_панель_3d" => {
14966                let x = self.arg_num(&args, 0, 0.)? as f32;
14967                let y = self.arg_num(&args, 1, 0.)? as f32;
14968                let w0 = self.arg_num(&args, 2, 200.)? as f32;
14969                let h0 = self.arg_num(&args, 3, 120.)? as f32;
14970                let depth = self.arg_num(&args, 4, 14.)? as f32;
14971                let th = self.ui_theme;
14972                let prim = self.color_at(&args, 5, th.primary);
14973                self.draw_ui(&ling_ui::widgets::panel3d(x, y, w0, h0, depth, prim, th.bg));
14974                return Ok(Value::Unit);
14975            },
14976            #[cfg(not(target_arch = "wasm32"))]
14977            "ui_radar3d"
14978            | "立体雷达"
14979            | "立体レーダー"
14980            | "입체레이더"
14981            | "เรดาร์3มิติ"
14982            | "رادار_سه‌بعدی"
14983            | "رادار_ثلاثي_الأبعاد"
14984            | "מכ״ם_תלת_ממדי"
14985            | "تھری_ڈی_ریڈار"
14986            | "radar_3d_ui"
14987            | "ui_radar_3d"
14988            | "ui_радар_3d" => {
14989                let cx = self.arg_num(&args, 0, 0.)? as f32;
14990                let cy = self.arg_num(&args, 1, 0.)? as f32;
14991                let r = self.arg_num(&args, 2, 60.)? as f32;
14992                let tilt = self.arg_num(&args, 3, 0.9)? as f32;
14993                let sweep = self.arg_num(&args, 4, 0.)? as f32;
14994                let th = self.ui_theme;
14995                let prim = self.color_at(&args, 5, th.primary);
14996                self.draw_ui(&ling_ui::widgets::radar3d(
14997                    cx, cy, r, tilt, sweep, prim, th.track,
14998                ));
14999                return Ok(Value::Unit);
15000            },
15001
15002            // ── Interface sounds ─────────────────────────────────────────────
15003            #[cfg(not(target_arch = "wasm32"))]
15004            "audio_blip"
15005            | "提示音"
15006            | "ビープ音"
15007            | "효과음"
15008            | "เสียงบี๊บ"
15009            | "بوق_کوتاه"
15010            | "نغمة_قصيرة"
15011            | "ביפ"
15012            | "بلپ_آواز"
15013            | "bip_audio"
15014            | "звук_бип" => {
15015                let freq = self.arg_num(&args, 0, 660.)? as f32;
15016                let dur = self.arg_num(&args, 1, 0.08)? as f32;
15017                let wave = Wave::from_name(&self.arg_str(&args, 2, "sine"));
15018                let amp = self.arg_num(&args, 3, 0.25)? as f32;
15019                if let Some(audio) = &self.audio {
15020                    audio.blip(freq, amp, dur, wave);
15021                }
15022                return Ok(Value::Unit);
15023            },
15024            #[cfg(not(target_arch = "wasm32"))]
15025            "ui_sound"
15026            | "界面音"
15027            | "UI音"
15028            | "인터페이스음"
15029            | "เสียงปุ่ม"
15030            | "صدای_رابط"
15031            | "صوت_الواجهة"
15032            | "צליל_ממשק"
15033            | "یو_آئی_آواز"
15034            | "son_ui"
15035            | "ui_klang"
15036            | "ui_звук" => {
15037                let name = self.arg_str(&args, 0, "click");
15038                if let Some(audio) = &self.audio {
15039                    match name.as_str() {
15040                        "hover" => audio.blip(880.0, 0.10, 0.04, Wave::Sine),
15041                        "confirm" => {
15042                            audio.blip(660.0, 0.22, 0.07, Wave::Square);
15043                            audio.blip(990.0, 0.18, 0.10, Wave::Square);
15044                        },
15045                        "error" => {
15046                            audio.blip(180.0, 0.30, 0.16, Wave::Saw);
15047                            audio.blip(140.0, 0.30, 0.18, Wave::Saw);
15048                        },
15049                        "toggle" => audio.blip(520.0, 0.22, 0.05, Wave::Triangle),
15050                        "tick" => audio.blip(1500.0, 0.12, 0.02, Wave::Square),
15051                        _ => audio.blip(720.0, 0.26, 0.05, Wave::Square), // "click"
15052                    }
15053                }
15054                return Ok(Value::Unit);
15055            },
15056
15057            // ══════════════════════════════════════════════════════════════════
15058            // MUSIC TOOLKIT  (crates/ling-music) — decode · analysis · GM synth ·
15059            // rhythm · karaoke. Analysis/decoding need no audio device; playback
15060            // and synthesis lazily start a dedicated music engine.
15061            // ══════════════════════════════════════════════════════════════════
15062
15063            // music_load(path) -> track handle (decodes WAV/FLAC/OGG/MP3/AAC)
15064            #[cfg(not(target_arch = "wasm32"))]
15065            "music_load"
15066            | "载入音乐"
15067            | "音楽読込"
15068            | "음악로드"
15069            | "โหลดเพลง"
15070            | "بارگذاری_موسیقی"
15071            | "تحميل_الموسيقى"
15072            | "טעינת_מוזיקה"
15073            | "موسیقی_لوڈ"
15074            | "charger_musique"
15075            | "musik_laden"
15076            | "загрузить_музыку" => {
15077                let path = self.arg_str(&args, 0, "");
15078                let resolved = if std::path::Path::new(&path).exists() {
15079                    path.clone()
15080                } else if let Some(d) = &self.source_dir {
15081                    d.join(&path).to_string_lossy().into_owned()
15082                } else {
15083                    path.clone()
15084                };
15085                match ling_music::load(&resolved) {
15086                    Ok(t) => {
15087                        let id = self.tracks.len();
15088                        self.tracks.push(t);
15089                        return Ok(Value::Number(id as f64));
15090                    },
15091                    Err(e) => {
15092                        eprintln!("music_load failed ({path}): {e}");
15093                        return Ok(Value::Number(-1.0));
15094                    },
15095                }
15096            },
15097            #[cfg(not(target_arch = "wasm32"))]
15098            "music_duration"
15099            | "音乐时长"
15100            | "音楽長さ"
15101            | "음악길이"
15102            | "ความยาวเพลง"
15103            | "مدت_موسیقی"
15104            | "مدة_الموسيقى"
15105            | "משך_מוזיקה"
15106            | "موسیقی_دورانیہ"
15107            | "durée_musique"
15108            | "musik_dauer"
15109            | "длительность_музыки" => {
15110                let id = self.arg_num(&args, 0, 0.0)? as i64;
15111                let d = self
15112                    .tracks
15113                    .get(id as usize)
15114                    .map(|t| t.duration)
15115                    .unwrap_or(0.0);
15116                return Ok(Value::Number(d as f64));
15117            },
15118            #[cfg(not(target_arch = "wasm32"))]
15119            "music_bpm"
15120            | "节拍速度"
15121            | "テンポ"
15122            | "템포"
15123            | "จังหวะต่อนาที"
15124            | "ضربان_در_دقیقه"
15125            | "نبضات_بالدقيقة"
15126            | "פעימות_לדקה"
15127            | "بی_پی_ایم"
15128            | "bpm_musique"
15129            | "musik_bpm"
15130            | "музыка_bpm" => {
15131                let id = self.arg_num(&args, 0, 0.0)? as i64;
15132                let b = self
15133                    .tracks
15134                    .get(id as usize)
15135                    .map(|t| ling_music::analysis::bpm(&t.mono, t.rate))
15136                    .unwrap_or(0.0);
15137                return Ok(Value::Number(b as f64));
15138            },
15139            #[cfg(not(target_arch = "wasm32"))]
15140            "music_key"
15141            | "调性"
15142            | "調性"
15143            | "조성"
15144            | "คีย์เพลง"
15145            | "گام_موسیقی"
15146            | "مقام_الموسيقى"
15147            | "סולם_מוזיקלי"
15148            | "موسیقی_کلید"
15149            | "tonalité_musique"
15150            | "musik_tonart"
15151            | "тональность_музыки" => {
15152                let id = self.arg_num(&args, 0, 0.0)? as i64;
15153                let k = self
15154                    .tracks
15155                    .get(id as usize)
15156                    .map(|t| ling_music::analysis::key_name(&t.mono, t.rate))
15157                    .unwrap_or_default();
15158                return Ok(Value::Str(k));
15159            },
15160            #[cfg(not(target_arch = "wasm32"))]
15161            "music_onsets"
15162            | "音符起点"
15163            | "オンセット"
15164            | "온셋"
15165            | "จุดเริ่มเสียง"
15166            | "آغازهای_نت"
15167            | "بدايات_النغمات"
15168            | "התחלות_תווים"
15169            | "نوٹ_شروعات"
15170            | "attaques_musique"
15171            | "musik_einsätze"
15172            | "атаки_музыки" => {
15173                let id = self.arg_num(&args, 0, 0.0)? as i64;
15174                let v = self
15175                    .tracks
15176                    .get(id as usize)
15177                    .map(|t| ling_music::analysis::onsets(&t.mono, t.rate))
15178                    .unwrap_or_default();
15179                return Ok(Value::List(Rc::new(
15180                    v.into_iter().map(|x| Value::Number(x as f64)).collect(),
15181                )));
15182            },
15183            #[cfg(not(target_arch = "wasm32"))]
15184            "music_beat_grid"
15185            | "节拍网格"
15186            | "ビートグリッド"
15187            | "비트그리드"
15188            | "กริดจังหวะ"
15189            | "شبکه_ضرب"
15190            | "شبكة_الإيقاع"
15191            | "רשת_פעימות"
15192            | "بیٹ_گرڈ"
15193            | "grille_temps_musique"
15194            | "musik_taktraster"
15195            | "сетка_ритма_музыки" => {
15196                let id = self.arg_num(&args, 0, 0.0)? as i64;
15197                let beats = self
15198                    .tracks
15199                    .get(id as usize)
15200                    .map(|t| {
15201                        let b = ling_music::analysis::bpm(&t.mono, t.rate);
15202                        ling_music::analysis::beat_grid(&t.mono, t.rate, b)
15203                    })
15204                    .unwrap_or_default();
15205                return Ok(Value::List(Rc::new(
15206                    beats.into_iter().map(|x| Value::Number(x as f64)).collect(),
15207                )));
15208            },
15209
15210            // ── playback ──
15211            #[cfg(not(target_arch = "wasm32"))]
15212            "music_play"
15213            | "播放音乐"
15214            | "音楽再生"
15215            | "음악재생"
15216            | "เล่นเพลง"
15217            | "پخش_موسیقی"
15218            | "شغّل_الموسيقى"
15219            | "נגן_מוזיקה"
15220            | "موسیقی_چلاؤ"
15221            | "jouer_musique"
15222            | "musik_abspielen"
15223            | "играть_музыку" => {
15224                let id = self.arg_num(&args, 0, 0.0)? as i64;
15225                if self.ensure_music() {
15226                    let track = self
15227                        .tracks
15228                        .get(id as usize)
15229                        .map(|t| (t.stereo.clone(), t.rate));
15230                    if let (Some((st, rate)), Some(m)) = (track, &self.music) {
15231                        m.set_track(st, rate);
15232                        m.play();
15233                    } else if let Some(m) = &self.music {
15234                        m.play();
15235                    }
15236                }
15237                return Ok(Value::Unit);
15238            },
15239            #[cfg(not(target_arch = "wasm32"))]
15240            "music_pause"
15241            | "暂停音乐"
15242            | "音楽一時停止"
15243            | "음악일시정지"
15244            | "หยุดเพลงชั่วคราว"
15245            | "مکث_موسیقی"
15246            | "ألبث_الموسيقى"
15247            | "השהה_מוזיקה"
15248            | "موسیقی_روکو_مؤقت"
15249            | "pause_musique"
15250            | "musik_pausieren"
15251            | "пауза_музыки" => {
15252                if let Some(m) = &self.music {
15253                    m.pause();
15254                }
15255                return Ok(Value::Unit);
15256            },
15257            #[cfg(not(target_arch = "wasm32"))]
15258            "music_stop"
15259            | "停止音乐"
15260            | "音楽停止"
15261            | "음악정지"
15262            | "หยุดเพลง"
15263            | "توقف_موسیقی"
15264            | "أوقف_الموسيقى"
15265            | "עצור_מוזיקה"
15266            | "موسیقی_روکو"
15267            | "arrêter_musique"
15268            | "musik_stoppen"
15269            | "остановить_музыку" => {
15270                if let Some(m) = &self.music {
15271                    m.stop();
15272                }
15273                return Ok(Value::Unit);
15274            },
15275            #[cfg(not(target_arch = "wasm32"))]
15276            "music_seek"
15277            | "定位音乐"
15278            | "音楽シーク"
15279            | "음악탐색"
15280            | "ค้นหาเพลง"
15281            | "جستجوی_موسیقی"
15282            | "ابحث_في_الموسيقى"
15283            | "חפש_במוזיקה"
15284            | "موسیقی_تلاش"
15285            | "chercher_musique"
15286            | "musik_suchen"
15287            | "перемотать_музыку" => {
15288                let sec = self.arg_num(&args, 0, 0.0)? as f32;
15289                if let Some(m) = &self.music {
15290                    m.seek(sec);
15291                }
15292                return Ok(Value::Unit);
15293            },
15294            #[cfg(not(target_arch = "wasm32"))]
15295            "music_pos"
15296            | "音乐位置"
15297            | "音楽位置"
15298            | "음악위치"
15299            | "ตำแหน่งเพลง"
15300            | "موقعیت_موسیقی"
15301            | "موضع_الموسيقى"
15302            | "מיקום_מוזיקה"
15303            | "موسیقی_مقام"
15304            | "position_musique"
15305            | "musik_position"
15306            | "позиция_музыки" => {
15307                let p = self.music.as_ref().map(|m| m.position()).unwrap_or(0.0);
15308                return Ok(Value::Number(p as f64));
15309            },
15310            #[cfg(not(target_arch = "wasm32"))]
15311            "music_volume"
15312            | "音乐音量"
15313            | "音楽音量"
15314            | "음악음량"
15315            | "ระดับเพลง"
15316            | "بلندی_موسیقی"
15317            | "مستوى_الموسيقى"
15318            | "עוצמת_מוזיקה"
15319            | "موسیقی_شدت"
15320            | "volume_musique"
15321            | "musik_lautstärke"
15322            | "громкость_музыки" => {
15323                let v = self.arg_num(&args, 0, 0.8)? as f32;
15324                if self.ensure_music() {
15325                    if let Some(m) = &self.music {
15326                        m.set_volume(v);
15327                    }
15328                }
15329                return Ok(Value::Unit);
15330            },
15331
15332            // ── synthesis (GM-capable, patches from .ling files) ──
15333            #[cfg(not(target_arch = "wasm32"))]
15334            "music_patch"
15335            | "乐器音色"
15336            | "音色読込"
15337            | "악기패치"
15338            | "แพตช์เครื่องดนตรี"
15339            | "پچ_موسیقی"
15340            | "آلة_الموسيقى"
15341            | "תיקון_כלי_נגינה"
15342            | "میوزک_پیچ"
15343            | "patch_musique"
15344            | "musik_patch"
15345            | "патч_музыки" => {
15346                let path = self.arg_str(&args, 0, "");
15347                let resolved = if std::path::Path::new(&path).exists() {
15348                    path.clone()
15349                } else if let Some(d) = &self.source_dir {
15350                    d.join(&path).to_string_lossy().into_owned()
15351                } else {
15352                    path.clone()
15353                };
15354                if !self.ensure_music() {
15355                    return Ok(Value::Number(-1.0));
15356                }
15357                match ling_music::patch::from_path(&resolved) {
15358                    Ok(p) => {
15359                        let id = self.music.as_ref().unwrap().add_patch(p);
15360                        return Ok(Value::Number(id as f64));
15361                    },
15362                    Err(e) => {
15363                        eprintln!("music_patch failed ({path}): {e}");
15364                        return Ok(Value::Number(-1.0));
15365                    },
15366                }
15367            },
15368            #[cfg(not(target_arch = "wasm32"))]
15369            "music_note"
15370            | "弹音符"
15371            | "音符演奏"
15372            | "음표연주"
15373            | "เล่นโน้ต"
15374            | "نواختن_نت"
15375            | "عزف_نغمة"
15376            | "נגן_תו"
15377            | "نوٹ_بجاؤ"
15378            | "note_musique"
15379            | "musik_note"
15380            | "нота_музыки" => {
15381                let inst = self.arg_num(&args, 0, 0.0)? as usize;
15382                let midi = self.pitch_arg(&args, 1, 60);
15383                let dur = self.arg_num(&args, 2, 0.5)? as f32;
15384                let vel = self.arg_num(&args, 3, 0.9)? as f32;
15385                if self.ensure_music() {
15386                    if let Some(m) = &self.music {
15387                        m.note(inst, midi, vel, dur);
15388                    }
15389                }
15390                return Ok(Value::Unit);
15391            },
15392            #[cfg(not(target_arch = "wasm32"))]
15393            "music_note_on"
15394            | "音符开始"
15395            | "音符オン"
15396            | "음표켜기"
15397            | "โน้ตเริ่ม"
15398            | "شروع_نت"
15399            | "بدء_النغمة"
15400            | "התחלת_תו"
15401            | "نوٹ_شروع"
15402            | "note_musique_on"
15403            | "musik_note_an"
15404            | "нота_музыки_вкл" => {
15405                let inst = self.arg_num(&args, 0, 0.0)? as usize;
15406                let midi = self.pitch_arg(&args, 1, 60);
15407                let vel = self.arg_num(&args, 2, 0.9)? as f32;
15408                if self.ensure_music() {
15409                    if let Some(m) = &self.music {
15410                        m.note_on(inst, midi, vel);
15411                    }
15412                }
15413                return Ok(Value::Unit);
15414            },
15415            #[cfg(not(target_arch = "wasm32"))]
15416            "music_note_off"
15417            | "音符结束"
15418            | "音符オフ"
15419            | "음표끄기"
15420            | "โน้ตจบ"
15421            | "پایان_نت"
15422            | "إيقاف_النغمة"
15423            | "סיום_תו"
15424            | "نوٹ_ختم"
15425            | "note_musique_off"
15426            | "musik_note_aus"
15427            | "нота_музыки_выкл" => {
15428                let inst = self.arg_num(&args, 0, 0.0)? as usize;
15429                let midi = self.pitch_arg(&args, 1, 60);
15430                if let Some(m) = &self.music {
15431                    m.note_off(inst, midi);
15432                }
15433                return Ok(Value::Unit);
15434            },
15435
15436            // ── rhythm-game judging ──
15437            #[cfg(not(target_arch = "wasm32"))]
15438            "music_judge"
15439            | "判定"
15440            | "判定する"
15441            | "판정"
15442            | "ตัดสินจังหวะ"
15443            | "داوری_ضرب"
15444            | "حكم_الإيقاع"
15445            | "שיפוט_קצב"
15446            | "بیٹ_فیصلہ"
15447            | "juger_musique"
15448            | "musik_bewerten"
15449            | "оценить_музыку" => {
15450                let delta_ms = self.arg_num(&args, 0, 9999.0)? as f32;
15451                return Ok(Value::Number(
15452                    ling_music::Grade::judge(delta_ms).index() as f64
15453                ));
15454            },
15455            #[cfg(not(target_arch = "wasm32"))]
15456            "music_grade_name"
15457            | "判定名"
15458            | "判定名称"
15459            | "판정이름"
15460            | "ชื่อการตัดสิน"
15461            | "نام_رتبه"
15462            | "اسم_التقييم"
15463            | "שם_דירוג"
15464            | "گریڈ_نام"
15465            | "nom_grade_musique"
15466            | "musik_bewertungsname"
15467            | "имя_оценки_музыки" => {
15468                let idx = self.arg_num(&args, 0, 4.0)? as i32;
15469                return Ok(Value::Str(
15470                    ling_music::Grade::from_index(idx).name().to_string(),
15471                ));
15472            },
15473
15474            // ── karaoke ──
15475            #[cfg(not(target_arch = "wasm32"))]
15476            "music_lrc"
15477            | "载入歌词"
15478            | "歌詞読込"
15479            | "가사로드"
15480            | "โหลดเนื้อเพลง"
15481            | "بارگذاری_متن_ترانه"
15482            | "تحميل_كلمات_الأغنية"
15483            | "טעינת_מילות_שיר"
15484            | "گیت_متن_لوڈ"
15485            | "lrc_musique"
15486            | "musik_lrc"
15487            | "lrc_музыки" => {
15488                let path = self.arg_str(&args, 0, "");
15489                let resolved = if std::path::Path::new(&path).exists() {
15490                    path.clone()
15491                } else if let Some(d) = &self.source_dir {
15492                    d.join(&path).to_string_lossy().into_owned()
15493                } else {
15494                    path.clone()
15495                };
15496                match std::fs::read_to_string(&resolved) {
15497                    Ok(text) => {
15498                        let id = self.lyrics.len();
15499                        self.lyrics.push(ling_music::Lyrics::parse(&text));
15500                        return Ok(Value::Number(id as f64));
15501                    },
15502                    Err(e) => {
15503                        eprintln!("music_lrc failed ({path}): {e}");
15504                        return Ok(Value::Number(-1.0));
15505                    },
15506                }
15507            },
15508            #[cfg(not(target_arch = "wasm32"))]
15509            "music_lyric"
15510            | "当前歌词"
15511            | "現在歌詞"
15512            | "현재가사"
15513            | "เนื้อเพลงปัจจุบัน"
15514            | "متن_ترانه_فعلی"
15515            | "كلمات_الأغنية_الحالية"
15516            | "מילות_שיר_נוכחיות"
15517            | "موجودہ_گیت_متن"
15518            | "paroles_musique"
15519            | "musik_liedtext"
15520            | "текст_песни" => {
15521                let id = self.arg_num(&args, 0, 0.0)? as i64;
15522                let t = self.arg_num(&args, 1, 0.0)? as f32;
15523                let line = self
15524                    .lyrics
15525                    .get(id as usize)
15526                    .map(|l| l.line_at(t).to_string())
15527                    .unwrap_or_default();
15528                return Ok(Value::Str(line));
15529            },
15530            #[cfg(not(target_arch = "wasm32"))]
15531            "music_mic_pitch"
15532            | "麦克风音高"
15533            | "マイク音程"
15534            | "마이크음정"
15535            | "ระดับเสียงไมค์"
15536            | "زیروبمی_میکروفون"
15537            | "طبقة_صوت_الميكروفون"
15538            | "גובה_צליל_מיקרופון"
15539            | "مائیکروفون_پچ"
15540            | "hauteur_micro_musique"
15541            | "musik_mikrofon_tonhöhe"
15542            | "высота_тона_микрофона" => {
15543                let hz = if let Some(mic) = self.mic.as_ref() {
15544                    let s = mic.latest_samples();
15545                    let rate = mic.sample_rate();
15546                    ling_music::pitch::detect(&s, rate).unwrap_or(0.0)
15547                } else {
15548                    0.0
15549                };
15550                return Ok(Value::Number(hz as f64));
15551            },
15552            #[cfg(not(target_arch = "wasm32"))]
15553            "music_note_name"
15554            | "音名"
15555            | "音名称"
15556            | "음이름"
15557            | "ชื่อโน้ต"
15558            | "نام_نت"
15559            | "اسم_النغمة"
15560            | "שם_תו"
15561            | "نوٹ_نام"
15562            | "nom_note_musique"
15563            | "musik_notenname"
15564            | "имя_ноты_музыки" => {
15565                let hz = self.arg_num(&args, 0, 0.0)? as f32;
15566                return Ok(Value::Str(ling_music::note::hz_to_name(hz)));
15567            },
15568            #[cfg(not(target_arch = "wasm32"))]
15569            "music_hz"
15570            | "音符频率"
15571            | "音符周波数"
15572            | "음표주파수"
15573            | "ความถี่โน้ต"
15574            | "فرکانس_نت"
15575            | "تردد_النغمة"
15576            | "תדר_תו"
15577            | "نوٹ_ہرٹز"
15578            | "hz_musique"
15579            | "musik_hz"
15580            | "музыка_гц" => {
15581                let midi = self.pitch_arg(&args, 0, 69);
15582                return Ok(Value::Number(
15583                    ling_music::note::midi_to_hz(midi as f32) as f64
15584                ));
15585            },
15586            #[cfg(not(target_arch = "wasm32"))]
15587            "music_pitch_score"
15588            | "音准评分"
15589            | "音程スコア"
15590            | "음정점수"
15591            | "คะแนนเสียง"
15592            | "امتیاز_زیروبمی"
15593            | "درجة_طبقة_الصوت"
15594            | "ציון_גובה_צליל"
15595            | "پچ_اسکور"
15596            | "score_hauteur_musique"
15597            | "musik_tonhöhen_punktzahl"
15598            | "счёт_высоты_тона" => {
15599                let hz = self.arg_num(&args, 0, 0.0)? as f32;
15600                let target = self.arg_num(&args, 1, 0.0)? as f32;
15601                return Ok(Value::Number(
15602                    ling_music::karaoke::pitch_score(hz, target) as f64
15603                ));
15604            },
15605
15606            // ── MIDI (inaudible note source: drive coins, cues, etc.) ──
15607            #[cfg(not(target_arch = "wasm32"))]
15608            "music_midi_load"
15609            | "载入MIDI"
15610            | "MIDI読込"
15611            | "미디로드"
15612            | "โหลดมิดี"
15613            | "بارگذاری_MIDI"
15614            | "تحميل_MIDI"
15615            | "טעינת_MIDI"
15616            | "MIDI_لوڈ"
15617            | "charger_midi_musique"
15618            | "musik_midi_laden"
15619            | "загрузить_midi_музыки" => {
15620                let path = self.arg_str(&args, 0, "");
15621                let resolved = if std::path::Path::new(&path).exists() {
15622                    path.clone()
15623                } else if let Some(d) = &self.source_dir {
15624                    d.join(&path).to_string_lossy().into_owned()
15625                } else {
15626                    path.clone()
15627                };
15628                match ling_music::midi::load(&resolved) {
15629                    Ok(m) => {
15630                        let id = self.midis.len();
15631                        self.midis.push(m);
15632                        return Ok(Value::Number(id as f64));
15633                    },
15634                    Err(e) => {
15635                        eprintln!("music_midi_load failed ({path}): {e}");
15636                        return Ok(Value::Number(-1.0));
15637                    },
15638                }
15639            },
15640            #[cfg(not(target_arch = "wasm32"))]
15641            "music_midi_count"
15642            | "MIDI数量"
15643            | "MIDI数"
15644            | "미디수"
15645            | "จำนวนมิดี"
15646            | "تعداد_MIDI"
15647            | "عدد_MIDI"
15648            | "מספר_MIDI"
15649            | "MIDI_تعداد"
15650            | "nombre_midi_musique"
15651            | "musik_midi_anzahl"
15652            | "число_midi_музыки" => {
15653                let id = self.arg_num(&args, 0, 0.0)? as i64;
15654                let n = self
15655                    .midis
15656                    .get(id as usize)
15657                    .map(|m| m.notes.len())
15658                    .unwrap_or(0);
15659                return Ok(Value::Number(n as f64));
15660            },
15661            // music_midi_notes(id) -> flat [time, midi, time, midi, …]
15662            #[cfg(not(target_arch = "wasm32"))]
15663            "music_midi_notes"
15664            | "MIDI音符"
15665            | "MIDIノート"
15666            | "미디음표"
15667            | "โน้ตมิดี"
15668            | "نت‌های_MIDI"
15669            | "نغمات_MIDI"
15670            | "תווי_MIDI"
15671            | "MIDI_نوٹس"
15672            | "notes_midi_musique"
15673            | "musik_midi_noten"
15674            | "ноты_midi_музыки" => {
15675                let id = self.arg_num(&args, 0, 0.0)? as i64;
15676                let mut out = Vec::new();
15677                if let Some(m) = self.midis.get(id as usize) {
15678                    for n in &m.notes {
15679                        out.push(Value::Number(n.time as f64));
15680                        out.push(Value::Number(n.midi as f64));
15681                    }
15682                }
15683                return Ok(Value::List(Rc::new(out)));
15684            },
15685            // music_midi_bars(id) -> flat [time, midi, dur, …] (for karaoke note bars)
15686            #[cfg(not(target_arch = "wasm32"))]
15687            "music_midi_bars"
15688            | "MIDI音条"
15689            | "MIDIバー"
15690            | "미디바"
15691            | "แท่งมิดี"
15692            | "میله‌های_MIDI"
15693            | "أعمدة_MIDI"
15694            | "עמודות_MIDI"
15695            | "MIDI_بارز"
15696            | "mesures_midi_musique"
15697            | "musik_midi_takte"
15698            | "такты_midi_музыки" => {
15699                let id = self.arg_num(&args, 0, 0.0)? as i64;
15700                let mut out = Vec::new();
15701                if let Some(m) = self.midis.get(id as usize) {
15702                    for n in &m.notes {
15703                        out.push(Value::Number(n.time as f64));
15704                        out.push(Value::Number(n.midi as f64));
15705                        out.push(Value::Number(n.dur as f64));
15706                    }
15707                }
15708                return Ok(Value::List(Rc::new(out)));
15709            },
15710
15711            // music_fft(track_id, nbands) -> spectrum at the current playback position
15712            #[cfg(not(target_arch = "wasm32"))]
15713            "music_fft"
15714            | "音乐频谱"
15715            | "音楽スペクトル"
15716            | "음악스펙트럼"
15717            | "สเปกตรัมเพลง"
15718            | "طیف_موسیقی"
15719            | "طيف_الموسيقى"
15720            | "ספקטרום_מוזיקה"
15721            | "میوزک_اسپیکٹرم"
15722            | "fft_musique"
15723            | "musik_fft"
15724            | "fft_музыки" => {
15725                let id = self.arg_num(&args, 0, 0.0)? as i64;
15726                let nbands = self.arg_num(&args, 1, 16.0)? as usize;
15727                let pos = self.music.as_ref().map(|m| m.position()).unwrap_or(0.0);
15728                if let Some(t) = self.tracks.get(id as usize) {
15729                    let idx = (pos * t.rate as f32) as usize;
15730                    let end = (idx + 2048).min(t.mono.len());
15731                    if end > idx + 64 {
15732                        self.fft.borrow_mut().push_samples(&t.mono[idx..end]);
15733                    }
15734                }
15735                let bands = self.fft.borrow().freq_bands(nbands);
15736                return Ok(Value::List(Rc::new(
15737                    bands.into_iter().map(|x| Value::Number(x as f64)).collect(),
15738                )));
15739            },
15740
15741            // ── stop every one-shot SFX/morph/sample voice (scene cleanup) ──
15742            #[cfg(not(target_arch = "wasm32"))]
15743            "audio_stop_sfx"
15744            | "停止音效"
15745            | "効果音停止"
15746            | "효과음정지"
15747            | "หยุดเอฟเฟกต์ทั้งหมด"
15748            | "توقف_همه_جلوه‌ها"
15749            | "أوقف_كل_المؤثرات"
15750            | "עצור_כל_האפקטים"
15751            | "تمام_ایفیکٹ_روکو" => {
15752                if let Some(a) = &self.audio {
15753                    a.stop_all_sfx();
15754                }
15755                return Ok(Value::Unit);
15756            },
15757            // ── spatial (2D/3D/4D) one-shot SFX ──
15758            #[cfg(not(target_arch = "wasm32"))]
15759            "audio_sfx"
15760            | "音效"
15761            | "空間効果音"
15762            | "공간효과음"
15763            | "เสียงเอฟเฟกต์"
15764            | "جلوه_صوتی"
15765            | "مؤثرات_صوتية"
15766            | "אפקט_קול"
15767            | "آواز_ایفیکٹ"
15768            | "effet_sonore"
15769            | "klangeffekt"
15770            | "звуковой_эффект" => {
15771                let x = self.arg_num(&args, 0, 0.0)? as f32;
15772                let y = self.arg_num(&args, 1, 0.0)? as f32;
15773                let z = self.arg_num(&args, 2, 0.0)? as f32;
15774                let w = self.arg_num(&args, 3, 1.0)? as f32;
15775                let freq = self.arg_num(&args, 4, 440.0)? as f32;
15776                let amp = self.arg_num(&args, 5, 0.3)? as f32;
15777                let dur = self.arg_num(&args, 6, 0.15)? as f32;
15778                let wave = Wave::from_name(&self.arg_str(&args, 7, "sine"));
15779                if let Some(a) = &self.audio {
15780                    a.sfx(x, y, z, w, freq, amp, dur, wave);
15781                }
15782                return Ok(Value::Unit);
15783            },
15784            // ── YIN-YANG morph synth note: physical-model(light) ↔ FM/crush(dark) ──
15785            // โน้ตมอร์ฟ(x,y,z,w, freq, amp, dur, material, morph)
15786            //   material: 0 bowed-string · 1 plucked · 2 blown · 3 struck-metal
15787            //   morph:    0.0 light/acoustic .. 1.0 dark/digital
15788            #[cfg(not(target_arch = "wasm32"))]
15789            "morph_note"
15790            | "โน้ตมอร์ฟ"
15791            | "变形音"
15792            | "モーフ音"
15793            | "모프음"
15794            | "نت_مورف"
15795            | "نغمة_متحولة"
15796            | "תו_מורף"
15797            | "مورف_نوٹ" => {
15798                let x = self.arg_num(&args, 0, 0.0)? as f32;
15799                let y = self.arg_num(&args, 1, 0.0)? as f32;
15800                let z = self.arg_num(&args, 2, 0.0)? as f32;
15801                let w = self.arg_num(&args, 3, 1.0)? as f32;
15802                let freq = self.arg_num(&args, 4, 220.0)? as f32;
15803                let amp = self.arg_num(&args, 5, 0.3)? as f32;
15804                let dur = self.arg_num(&args, 6, 0.6)? as f32;
15805                let material = self.arg_num(&args, 7, 0.0)?.clamp(0.0, 3.0) as u8;
15806                let morph = self.arg_num(&args, 8, 0.0)? as f32;
15807                if let Some(a) = &self.audio {
15808                    a.morph_note(x, y, z, w, freq, amp, dur, material, morph);
15809                }
15810                return Ok(Value::Unit);
15811            },
15812            // ── sample load / positional play / loop / stop ──
15813            #[cfg(not(target_arch = "wasm32"))]
15814            "audio_sample_load"
15815            | "载入采样"
15816            | "サンプル読込"
15817            | "샘플로드"
15818            | "โหลดตัวอย่างเสียง"
15819            | "بارگذاری_نمونه_صدا"
15820            | "تحميل_عينة_صوتية"
15821            | "טעינת_דגימת_קול"
15822            | "آواز_نمونہ_لوڈ"
15823            | "charger_échantillon"
15824            | "sample_laden"
15825            | "загрузить_семпл" => {
15826                let path = self.arg_str(&args, 0, "");
15827                let resolved = if std::path::Path::new(&path).exists() {
15828                    path.clone()
15829                } else if let Some(d) = &self.source_dir {
15830                    d.join(&path).to_string_lossy().into_owned()
15831                } else {
15832                    path.clone()
15833                };
15834                match ling_music::load(&resolved) {
15835                    Ok(t) => {
15836                        if let Some(a) = &self.audio {
15837                            return Ok(Value::Number(a.add_sample(t.mono, t.rate) as f64));
15838                        }
15839                        return Ok(Value::Number(-1.0));
15840                    },
15841                    Err(e) => {
15842                        eprintln!("audio_sample_load failed ({path}): {e}");
15843                        return Ok(Value::Number(-1.0));
15844                    },
15845                }
15846            },
15847            #[cfg(not(target_arch = "wasm32"))]
15848            "audio_sample_play"
15849            | "播放采样"
15850            | "サンプル再生"
15851            | "샘플재생"
15852            | "เล่นตัวอย่างเสียง"
15853            | "پخش_نمونه_صدا"
15854            | "تشغيل_عينة_صوتية"
15855            | "נגינת_דגימת_קול"
15856            | "آواز_نمونہ_چلاؤ"
15857            | "jouer_échantillon"
15858            | "sample_abspielen"
15859            | "играть_семпл" => {
15860                let id = self.arg_num(&args, 0, 0.0)? as usize;
15861                let x = self.arg_num(&args, 1, 0.0)? as f32;
15862                let y = self.arg_num(&args, 2, 0.0)? as f32;
15863                let z = self.arg_num(&args, 3, 0.0)? as f32;
15864                let w = self.arg_num(&args, 4, 1.0)? as f32;
15865                let vol = self.arg_num(&args, 5, 1.0)? as f32;
15866                let looping = self.arg_num(&args, 6, 0.0)? > 0.5;
15867                let v = self
15868                    .audio
15869                    .as_ref()
15870                    .map(|a| a.play_sample(id, x, y, z, w, vol, looping))
15871                    .unwrap_or(0);
15872                return Ok(Value::Number(v as f64));
15873            },
15874            // Flat/non-positional sample playback — no distance falloff, no
15875            // stereo pan, same level regardless of listener position. For
15876            // VO/narration/UI cues, as opposed to audio_sample_play's
15877            // world-space sound effects (which fall off with camera distance).
15878            #[cfg(not(target_arch = "wasm32"))]
15879            "audio_sample_play_flat"
15880            | "播放采样平"
15881            | "サンプル再生平"
15882            | "샘플재생평"
15883            | "เล่นตัวอย่างเสียงเรียบ" => {
15884                let id = self.arg_num(&args, 0, 0.0)? as usize;
15885                let vol = self.arg_num(&args, 1, 1.0)? as f32;
15886                let looping = self.arg_num(&args, 2, 0.0)? > 0.5;
15887                let v = self
15888                    .audio
15889                    .as_ref()
15890                    .map(|a| a.play_sample_flat(id, vol, looping))
15891                    .unwrap_or(0);
15892                return Ok(Value::Number(v as f64));
15893            },
15894            #[cfg(not(target_arch = "wasm32"))]
15895            "audio_sample_stop"
15896            | "停止采样"
15897            | "サンプル停止"
15898            | "샘플정지"
15899            | "หยุดตัวอย่างเสียง"
15900            | "توقف_نمونه_صدا"
15901            | "إيقاف_عينة_صوتية"
15902            | "עצירת_דגימת_קול"
15903            | "آواز_نمونہ_روکو"
15904            | "arrêter_échantillon"
15905            | "sample_stoppen"
15906            | "остановить_семпл" => {
15907                let v = self.arg_num(&args, 0, 0.0)? as u32;
15908                if let Some(a) = &self.audio {
15909                    a.stop_sample(v);
15910                }
15911                return Ok(Value::Unit);
15912            },
15913            // Duration (seconds) of an already-loaded sample. 0.0 if the id
15914            // is invalid. Lets a cutscene pace itself off the real length of
15915            // whichever language's VO clip actually got loaded, instead of a
15916            // hand-measured constant that only covers one language.
15917            #[cfg(not(target_arch = "wasm32"))]
15918            "audio_sample_duration"
15919            | "采样时长"
15920            | "サンプル長さ"
15921            | "샘플길이"
15922            | "ความยาวตัวอย่างเสียง" => {
15923                let id = self.arg_num(&args, 0, 0.0)? as usize;
15924                let dur = self
15925                    .audio
15926                    .as_ref()
15927                    .map(|a| a.sample_duration(id))
15928                    .unwrap_or(0.0);
15929                return Ok(Value::Number(dur as f64));
15930            },
15931            // ── master FX: delay / reverb / low-pass (underwater) ──
15932            #[cfg(not(target_arch = "wasm32"))]
15933            "audio_fx_delay"
15934            | "回声"
15935            | "ディレイ効果"
15936            | "딜레이"
15937            | "เสียงสะท้อน"
15938            | "افکت_تاخیر"
15939            | "صدى_تأخير"
15940            | "אפקט_עיכוב"
15941            | "تاخیر_ایفیکٹ"
15942            | "délai_audio"
15943            | "audio_verzögerung"
15944            | "звук_задержка" => {
15945                let time = self.arg_num(&args, 0, 0.3)? as f32;
15946                let fb = self.arg_num(&args, 1, 0.3)? as f32;
15947                let mix = self.arg_num(&args, 2, 0.3)? as f32;
15948                if let Some(a) = &self.audio {
15949                    a.fx_delay(time, fb, mix);
15950                }
15951                return Ok(Value::Unit);
15952            },
15953            #[cfg(not(target_arch = "wasm32"))]
15954            "audio_fx_reverb"
15955            | "混响"
15956            | "リバーブ"
15957            | "리버브"
15958            | "เสียงก้อง"
15959            | "افکت_پژواک"
15960            | "صدى_ارتداد"
15961            | "אפקט_הדהוד"
15962            | "بازگشت_آواز_ایفیکٹ"
15963            | "réverbération_audio"
15964            | "audio_nachhall"
15965            | "звук_реверберация" => {
15966                let mix = self.arg_num(&args, 0, 0.3)? as f32;
15967                if let Some(a) = &self.audio {
15968                    a.fx_reverb(mix);
15969                }
15970                return Ok(Value::Unit);
15971            },
15972            #[cfg(not(target_arch = "wasm32"))]
15973            "audio_fx_lowpass"
15974            | "低通滤波"
15975            | "ローパス"
15976            | "저역통과"
15977            | "กรองความถี่ต่ำ"
15978            | "فیلتر_پایین‌گذر"
15979            | "مرشح_تمرير_منخفض"
15980            | "מסנן_תדר_נמוך"
15981            | "لو_پاس_فلٹر"
15982            | "passe_bas_audio"
15983            | "audio_tiefpass"
15984            | "звук_фнч" => {
15985                let cutoff = self.arg_num(&args, 0, 1.0)? as f32;
15986                if let Some(a) = &self.audio {
15987                    a.fx_lowpass(cutoff);
15988                }
15989                return Ok(Value::Unit);
15990            },
15991
15992            // ══════════════════════════════════════════════════════════════════
15993            // PHYSICS BUILTINS  (crates/ling-physics) — soft bodies, rigid+angular,
15994            // and a fast 2-D water/oil liquid sim mappable onto 3-D surfaces.
15995            // ══════════════════════════════════════════════════════════════════
15996
15997            // ── soft bodies (deformable bouncy balls) ──
15998            #[cfg(not(target_arch = "wasm32"))]
15999            "soft_ball"
16000            | "软球"
16001            | "ソフトボール"
16002            | "소프트볼"
16003            | "ลูกบอลนุ่ม"
16004            | "توپ_نرم"
16005            | "كرة_ناعمة"
16006            | "כדור_רך"
16007            | "نرم_گیند"
16008            | "balle_molle"
16009            | "weicher_ball"
16010            | "мягкий_шар" => {
16011                let x = self.arg_num(&args, 0, 0.)? as f32;
16012                let y = self.arg_num(&args, 1, 0.)? as f32;
16013                let z = self.arg_num(&args, 2, 0.)? as f32;
16014                let r = self.arg_num(&args, 3, 1.0)? as f32;
16015                let b = ling_physics::soft::SoftBody::sphere(
16016                    ling_physics::Vec3::new(x, y, z),
16017                    r,
16018                    8,
16019                    12,
16020                    1.0,
16021                );
16022                let id = self.soft_bodies.len();
16023                self.soft_bodies.push(b);
16024                return Ok(Value::Number(id as f64));
16025            },
16026            #[cfg(not(target_arch = "wasm32"))]
16027            "soft_step"
16028            | "软体步进"
16029            | "ソフト更新"
16030            | "소프트스텝"
16031            | "ก้าวนุ่ม"
16032            | "گام_نرم_جسم"
16033            | "خطوة_ناعمة"
16034            | "צעד_רך"
16035            | "نرم_قدم"
16036            | "pas_mou"
16037            | "weicher_schritt"
16038            | "мягкий_шаг" => {
16039                let id = self.arg_num(&args, 0, 0.)? as usize;
16040                let dt = self.arg_num(&args, 1, 0.016)? as f32;
16041                let gy = self.arg_num(&args, 2, 15.0)? as f32;
16042                if let Some(b) = self.soft_bodies.get_mut(id) {
16043                    b.integrate(dt, ling_physics::Vec3::new(0.0, gy, 0.0), 4);
16044                }
16045                return Ok(Value::Unit);
16046            },
16047            #[cfg(not(target_arch = "wasm32"))]
16048            "soft_bounce"
16049            | "软体落地"
16050            | "ソフト着地"
16051            | "소프트바운스"
16052            | "เด้งนุ่ม"
16053            | "جهش_نرم"
16054            | "ارتداد_ناعم"
16055            | "קפיצה_רכה"
16056            | "نرم_اچھال"
16057            | "rebond_mou"
16058            | "weicher_abprall"
16059            | "мягкий_отскок" => {
16060                let id = self.arg_num(&args, 0, 0.)? as usize;
16061                let fy = self.arg_num(&args, 1, 0.)? as f32;
16062                let rest = self.arg_num(&args, 2, 0.5)? as f32;
16063                if let Some(b) = self.soft_bodies.get_mut(id) {
16064                    b.floor_collision(fy, rest);
16065                }
16066                return Ok(Value::Unit);
16067            },
16068            #[cfg(not(target_arch = "wasm32"))]
16069            "soft_contain"
16070            | "软体边界"
16071            | "ソフト箱"
16072            | "소프트경계"
16073            | "กล่องนุ่ม"
16074            | "محفظه_نرم"
16075            | "احتواء_ناعم"
16076            | "הכלה_רכה"
16077            | "نرم_احاطہ"
16078            | "contenir_mou"
16079            | "weiche_eindämmung"
16080            | "мягкое_сдерживание" => {
16081                let id = self.arg_num(&args, 0, 0.)? as usize;
16082                let nx = self.arg_num(&args, 1, -5.)? as f32;
16083                let ny = self.arg_num(&args, 2, -5.)? as f32;
16084                let nz = self.arg_num(&args, 3, -5.)? as f32;
16085                let mx = self.arg_num(&args, 4, 5.)? as f32;
16086                let my = self.arg_num(&args, 5, 5.)? as f32;
16087                let mz = self.arg_num(&args, 6, 5.)? as f32;
16088                let rest = self.arg_num(&args, 7, 0.6)? as f32;
16089                if let Some(b) = self.soft_bodies.get_mut(id) {
16090                    b.contain(
16091                        ling_physics::Vec3::new(nx, ny, nz),
16092                        ling_physics::Vec3::new(mx, my, mz),
16093                        rest,
16094                    );
16095                }
16096                return Ok(Value::Unit);
16097            },
16098            #[cfg(not(target_arch = "wasm32"))]
16099            "soft_kick"
16100            | "软体踢"
16101            | "ソフト衝撃"
16102            | "소프트킥"
16103            | "เตะนุ่ม"
16104            | "ضربه_نرم"
16105            | "ركلة_ناعمة"
16106            | "בעיטה_רכה"
16107            | "نرم_ٹھوکر"
16108            | "coup_mou"
16109            | "weicher_stoß"
16110            | "мягкий_удар" => {
16111                let id = self.arg_num(&args, 0, 0.)? as usize;
16112                let dx = self.arg_num(&args, 1, 0.)? as f32;
16113                let dy = self.arg_num(&args, 2, 0.)? as f32;
16114                let dz = self.arg_num(&args, 3, 0.)? as f32;
16115                let s = self.arg_num(&args, 4, 0.1)? as f32;
16116                if let Some(b) = self.soft_bodies.get_mut(id) {
16117                    b.kick(ling_physics::Vec3::new(dx, dy, dz), s);
16118                }
16119                return Ok(Value::Unit);
16120            },
16121            // soft_spin(id, ax, ay, az, rate) — add angular velocity about the axis
16122            // through the centroid (rate = rad/step; ≈ surface_speed / radius to roll)
16123            #[cfg(not(target_arch = "wasm32"))]
16124            "soft_spin"
16125            | "软体自旋"
16126            | "ソフト回転"
16127            | "소프트회전"
16128            | "หมุนนุ่ม"
16129            | "چرخش_نرم"
16130            | "دوران_ناعم"
16131            | "סיבוב_רך"
16132            | "نرم_گھماؤ"
16133            | "rotation_molle"
16134            | "weicher_spin"
16135            | "мягкое_вращение" => {
16136                let id = self.arg_num(&args, 0, 0.)? as usize;
16137                let ax = self.arg_num(&args, 1, 0.)? as f32;
16138                let ay = self.arg_num(&args, 2, 0.)? as f32;
16139                let az = self.arg_num(&args, 3, 0.)? as f32;
16140                let rate = self.arg_num(&args, 4, 0.1)? as f32;
16141                if let Some(b) = self.soft_bodies.get_mut(id) {
16142                    b.spin(ling_physics::Vec3::new(ax, ay, az), rate);
16143                }
16144                return Ok(Value::Unit);
16145            },
16146            #[cfg(not(target_arch = "wasm32"))]
16147            "soft_deform"
16148            | "形变量"
16149            | "変形量"
16150            | "변형량"
16151            | "ความบิดเบี้ยว"
16152            | "تغییرشکل_نرم"
16153            | "تشوه_ناعم"
16154            | "עיוות_רך"
16155            | "نرم_بگاڑ"
16156            | "déformer_mou"
16157            | "weiches_verformen"
16158            | "мягкая_деформация" => {
16159                let id = self.arg_num(&args, 0, 0.)? as usize;
16160                let d = self
16161                    .soft_bodies
16162                    .get(id)
16163                    .map(|b| b.deformation())
16164                    .unwrap_or(0.0);
16165                return Ok(Value::Number(d as f64));
16166            },
16167            // soft_angular_speed(id) -> magnitude of the body's angular velocity
16168            // (how fast it is tumbling/rolling), derived from its node velocities.
16169            #[cfg(not(target_arch = "wasm32"))]
16170            "soft_angular_speed"
16171            | "软体角速"
16172            | "ソフト角速度"
16173            | "소프트각속도"
16174            | "ความเร็วเชิงมุมนุ่ม"
16175            | "سرعت_زاویه‌ای_نرم"
16176            | "سرعة_زاوية_ناعمة"
16177            | "מהירות_זוויתית_רכה"
16178            | "نرم_زاویائی_رفتار"
16179            | "vitesse_angulaire_molle"
16180            | "weiche_winkelgeschwindigkeit"
16181            | "мягкая_угловая_скорость" => {
16182                let id = self.arg_num(&args, 0, 0.)? as usize;
16183                let w = self
16184                    .soft_bodies
16185                    .get(id)
16186                    .map(|b| b.angular_speed())
16187                    .unwrap_or(0.0);
16188                return Ok(Value::Number(w as f64));
16189            },
16190            #[cfg(not(target_arch = "wasm32"))]
16191            "soft_centroid"
16192            | "软体质心"
16193            | "ソフト重心"
16194            | "소프트중심"
16195            | "จุดศูนย์กลางนุ่ม"
16196            | "مرکز_جرم_نرم"
16197            | "مركز_ثقل_ناعم"
16198            | "מרכז_כובד_רך"
16199            | "نرم_مرکز_ثقل"
16200            | "centroïde_mou"
16201            | "weicher_schwerpunkt"
16202            | "мягкий_центроид" => {
16203                let id = self.arg_num(&args, 0, 0.)? as usize;
16204                let c = self
16205                    .soft_bodies
16206                    .get(id)
16207                    .map(|b| b.centroid())
16208                    .unwrap_or(ling_physics::Vec3::ZERO);
16209                return Ok(Value::List(Rc::new(vec![
16210                    Value::Number(c.x as f64),
16211                    Value::Number(c.y as f64),
16212                    Value::Number(c.z as f64),
16213                ])));
16214            },
16215            // soft_nodes(id) -> flat [x,y,z, x,y,z, …] for rendering the deformed mesh
16216            #[cfg(not(target_arch = "wasm32"))]
16217            "soft_nodes"
16218            | "软体节点"
16219            | "ソフト節点"
16220            | "소프트노드"
16221            | "จุดนุ่ม"
16222            | "گره‌های_نرم"
16223            | "عقد_ناعمة"
16224            | "צמתי_רך"
16225            | "نرم_نوڈز"
16226            | "nœuds_mous"
16227            | "weiche_knoten"
16228            | "мягкие_узлы" => {
16229                let id = self.arg_num(&args, 0, 0.)? as usize;
16230                let mut out = Vec::new();
16231                if let Some(b) = self.soft_bodies.get(id) {
16232                    for n in &b.nodes {
16233                        out.push(Value::Number(n.pos.x as f64));
16234                        out.push(Value::Number(n.pos.y as f64));
16235                        out.push(Value::Number(n.pos.z as f64));
16236                    }
16237                }
16238                return Ok(Value::List(Rc::new(out)));
16239            },
16240
16241            // ── rigid bodies with angular dynamics ──
16242            #[cfg(not(target_arch = "wasm32"))]
16243            "rb_add"
16244            | "刚体添加"
16245            | "剛体追加"
16246            | "강체추가"
16247            | "เพิ่มวัตถุแข็ง"
16248            | "افزودن_جسم_صلب"
16249            | "أضف_جسما_صلبا"
16250            | "הוסף_גוף_קשיח"
16251            | "سخت_جسم_شامل_کرو"
16252            | "ajouter_corps_rigide"
16253            | "starrkörper_hinzufügen"
16254            | "добавить_твёрдое_тело" => {
16255                let x = self.arg_num(&args, 0, 0.)? as f32;
16256                let y = self.arg_num(&args, 1, 0.)? as f32;
16257                let z = self.arg_num(&args, 2, 0.)? as f32;
16258                let mass = self.arg_num(&args, 3, 1.0)? as f32;
16259                let mut b =
16260                    ling_physics::rigid::RigidBody::new(ling_physics::Vec3::new(x, y, z), mass);
16261                b.restitution = 0.6;
16262                return Ok(Value::Number(self.rigid_world.add(b) as f64));
16263            },
16264            #[cfg(not(target_arch = "wasm32"))]
16265            "rb_torque"
16266            | "扭矩"
16267            | "トルク"
16268            | "토크"
16269            | "แรงบิด"
16270            | "گشتاور"
16271            | "عزم_دوران"
16272            | "מומנט"
16273            | "ٹارک"
16274            | "couple_corps_rigide"
16275            | "starrkörper_drehmoment"
16276            | "крутящий_момент_твёрдого_тела" => {
16277                let i = self.arg_num(&args, 0, 0.)? as usize;
16278                let tx = self.arg_num(&args, 1, 0.)? as f32;
16279                let ty = self.arg_num(&args, 2, 0.)? as f32;
16280                let tz = self.arg_num(&args, 3, 0.)? as f32;
16281                if let Some(b) = self.rigid_world.bodies.get_mut(i) {
16282                    b.apply_torque(ling_physics::Vec3::new(tx, ty, tz));
16283                }
16284                return Ok(Value::Unit);
16285            },
16286            #[cfg(not(target_arch = "wasm32"))]
16287            "rb_spin"
16288            | "自旋"
16289            | "スピン"
16290            | "스핀"
16291            | "หมุน"
16292            | "چرخش_جسم_صلب"
16293            | "دوران_جسم_صلب"
16294            | "סיבוב_גוף_קשיח"
16295            | "سخت_جسم_گھماؤ"
16296            | "spin_corps_rigide"
16297            | "starrkörper_spin"
16298            | "вращение_твёрдого_тела" => {
16299                let i = self.arg_num(&args, 0, 0.)? as usize;
16300                let wx = self.arg_num(&args, 1, 0.)? as f32;
16301                let wy = self.arg_num(&args, 2, 0.)? as f32;
16302                let wz = self.arg_num(&args, 3, 0.)? as f32;
16303                if let Some(b) = self.rigid_world.bodies.get_mut(i) {
16304                    b.apply_spin(ling_physics::Vec3::new(wx, wy, wz));
16305                }
16306                return Ok(Value::Unit);
16307            },
16308            #[cfg(not(target_arch = "wasm32"))]
16309            "rb_impulse"
16310            | "刚体冲量"
16311            | "剛体インパルス"
16312            | "강체충격"
16313            | "แรงดลแข็ง"
16314            | "ضربه_جسم_صلب"
16315            | "دفعة_جسم_صلب"
16316            | "דחף_גוף_קשיח"
16317            | "سخت_جسم_دھکا"
16318            | "impulsion_corps_rigide"
16319            | "starrkörper_impuls"
16320            | "импульс_твёрдого_тела" => {
16321                let i = self.arg_num(&args, 0, 0.)? as usize;
16322                let ix = self.arg_num(&args, 1, 0.)? as f32;
16323                let iy = self.arg_num(&args, 2, 0.)? as f32;
16324                let iz = self.arg_num(&args, 3, 0.)? as f32;
16325                if let Some(b) = self.rigid_world.bodies.get_mut(i) {
16326                    b.apply_impulse(ling_physics::Vec3::new(ix, iy, iz));
16327                }
16328                return Ok(Value::Unit);
16329            },
16330            #[cfg(not(target_arch = "wasm32"))]
16331            "rb_floor"
16332            | "刚体落地"
16333            | "剛体着地"
16334            | "강체바닥"
16335            | "พื้นแข็ง"
16336            | "کف_جسم_صلب"
16337            | "أرضية_جسم_صلب"
16338            | "רצפת_גוף_קשיח"
16339            | "سخت_جسم_فرش"
16340            | "sol_corps_rigide"
16341            | "starrkörper_boden"
16342            | "пол_твёрдого_тела" => {
16343                let i = self.arg_num(&args, 0, 0.)? as usize;
16344                let fy = self.arg_num(&args, 1, 0.)? as f32;
16345                let rest = self.arg_num(&args, 2, 0.6)? as f32;
16346                let fric = self.arg_num(&args, 3, 0.6)? as f32;
16347                if let Some(b) = self.rigid_world.bodies.get_mut(i) {
16348                    b.bounce_floor(fy, rest, fric);
16349                }
16350                return Ok(Value::Unit);
16351            },
16352            #[cfg(not(target_arch = "wasm32"))]
16353            "rb_gravity"
16354            | "刚体重力"
16355            | "剛体重力"
16356            | "강체중력"
16357            | "แรงโน้มถ่วงแข็ง"
16358            | "گرانش_جسم_صلب"
16359            | "جاذبية_جسم_صلب"
16360            | "כבידת_גוף_קשיח"
16361            | "سخت_جسم_کشش_ثقل"
16362            | "gravité_corps_rigide"
16363            | "starrkörper_schwerkraft"
16364            | "гравитация_твёрдого_тела" => {
16365                let gx = self.arg_num(&args, 0, 0.)? as f32;
16366                let gy = self.arg_num(&args, 1, 9.81)? as f32;
16367                let gz = self.arg_num(&args, 2, 0.)? as f32;
16368                self.rigid_world.gravity = ling_physics::Vec3::new(gx, gy, gz);
16369                return Ok(Value::Unit);
16370            },
16371            #[cfg(not(target_arch = "wasm32"))]
16372            "rb_step"
16373            | "刚体步进"
16374            | "剛体更新"
16375            | "강체스텝"
16376            | "ก้าวแข็ง"
16377            | "گام_جسم_صلب"
16378            | "خطوة_جسم_صلب"
16379            | "צעד_גוף_קשיח"
16380            | "سخت_جسم_قدم"
16381            | "pas_corps_rigide"
16382            | "starrkörper_schritt"
16383            | "шаг_твёрдого_тела" => {
16384                let dt = self.arg_num(&args, 0, 0.016)? as f32;
16385                self.rigid_world.step(dt);
16386                return Ok(Value::Unit);
16387            },
16388            #[cfg(not(target_arch = "wasm32"))]
16389            "rb_pos"
16390            | "刚体位置"
16391            | "剛体位置"
16392            | "강체위치"
16393            | "ตำแหน่งแข็ง"
16394            | "موقعیت_جسم_صلب"
16395            | "موضع_جسم_صلب"
16396            | "מיקום_גוף_קשיח"
16397            | "سخت_جسم_مقام"
16398            | "position_corps_rigide"
16399            | "starrkörper_position"
16400            | "позиция_твёрдого_тела" => {
16401                let i = self.arg_num(&args, 0, 0.)? as usize;
16402                let p = self
16403                    .rigid_world
16404                    .bodies
16405                    .get(i)
16406                    .map(|b| b.pos)
16407                    .unwrap_or(ling_physics::Vec3::ZERO);
16408                return Ok(Value::List(Rc::new(vec![
16409                    Value::Number(p.x as f64),
16410                    Value::Number(p.y as f64),
16411                    Value::Number(p.z as f64),
16412                ])));
16413            },
16414            #[cfg(not(target_arch = "wasm32"))]
16415            "rb_rot"
16416            | "刚体旋转"
16417            | "剛体回転"
16418            | "강체회전"
16419            | "การหมุนแข็ง"
16420            | "چرخش_وضعية_جسم_صلب"
16421            | "دوران_وضعية_جسم_صلب"
16422            | "סיבוב_זווית_גוף_קשיח"
16423            | "سخت_جسم_گردش"
16424            | "rotation_corps_rigide"
16425            | "starrkörper_rotation"
16426            | "поворот_твёрдого_тела" => {
16427                let i = self.arg_num(&args, 0, 0.)? as usize;
16428                let q = self
16429                    .rigid_world
16430                    .bodies
16431                    .get(i)
16432                    .map(|b| b.orientation)
16433                    .unwrap_or(ling_physics::Quat::IDENTITY);
16434                return Ok(Value::List(Rc::new(vec![
16435                    Value::Number(q.x as f64),
16436                    Value::Number(q.y as f64),
16437                    Value::Number(q.z as f64),
16438                    Value::Number(q.w as f64),
16439                ])));
16440            },
16441
16442            // ── native-res mesh (.lmesh): load once, draw fast (unlit, per-tri colour) ──
16443            #[cfg(not(target_arch = "wasm32"))]
16444            "mesh_load"
16445            | "โหลดเมช"
16446            | "载入网格"
16447            | "メッシュ読込"
16448            | "메시로드"
16449            | "بارگذاری_مش"
16450            | "حمّل_شبكة"
16451            | "טען_מש"
16452            | "میش_لوڈ" => {
16453                let path = self.arg_str(&args, 0, "");
16454                let resolved = if std::path::Path::new(&path).exists() {
16455                    path.clone()
16456                } else if let Some(d) = &self.source_dir {
16457                    d.join(&path).to_string_lossy().into_owned()
16458                } else {
16459                    path.clone()
16460                };
16461                let bytes = match std::fs::read(&resolved) {
16462                    Ok(b) => b,
16463                    Err(e) => {
16464                        eprintln!("mesh_load failed ({path}): {e}");
16465                        return Ok(Value::Number(-1.0));
16466                    },
16467                };
16468                if bytes.len() < 16 || &bytes[0..4] != b"LMSH" {
16469                    eprintln!("mesh_load: bad header ({path})");
16470                    return Ok(Value::Number(-1.0));
16471                }
16472                let rd4 =
16473                    |o: usize| -> [u8; 4] { [bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]] };
16474                let height = f32::from_le_bytes(rd4(8));
16475                let ntri = u32::from_le_bytes(rd4(12)) as usize;
16476                let need = 16usize.saturating_add(ntri.saturating_mul(9 * 4 + 3));
16477                if bytes.len() < need {
16478                    eprintln!("mesh_load: truncated ({path})");
16479                    return Ok(Value::Number(-1.0));
16480                }
16481                let mut pos = Vec::with_capacity(ntri * 3);
16482                let mut col = Vec::with_capacity(ntri);
16483                let mut off = 16usize;
16484                for _ in 0..ntri {
16485                    for _k in 0..3 {
16486                        let x = f32::from_le_bytes(rd4(off));
16487                        let y = f32::from_le_bytes(rd4(off + 4));
16488                        let z = f32::from_le_bytes(rd4(off + 8));
16489                        off += 12;
16490                        pos.push([x, y, z]);
16491                    }
16492                    col.push([bytes[off], bytes[off + 1], bytes[off + 2]]);
16493                    off += 3;
16494                }
16495                eprintln!("mesh_load: {} ({} tris, h={:.2})", path, ntri, height);
16496                let id = self.meshes.len();
16497                self.meshes
16498                    .push(crate::gfx::shapes::ColorMesh { pos, col, height });
16499                return Ok(Value::Number(id as f64));
16500            },
16501            #[cfg(target_arch = "wasm32")]
16502            "mesh_load"
16503            | "โหลดเมช"
16504            | "载入网格"
16505            | "メッシュ読込"
16506            | "메시로드"
16507            | "بارگذاری_مش"
16508            | "حمّل_شبكة"
16509            | "טען_רשת"
16510            | "میش_لوڈ" => {
16511                // Native .lmesh loading is file-system based and not wired for wasm yet.
16512                // Return an invalid handle so scripts can choose a fallback path.
16513                return Ok(Value::Number(-1.0));
16514            },
16515            #[cfg(not(target_arch = "wasm32"))]
16516            "mesh_draw"
16517            | "วาดเมชสี"
16518            | "绘制网格"
16519            | "メッシュ描画"
16520            | "메시그리기"
16521            | "رسم_مش_رنگی"
16522            | "ارسم_شبكة_ملونة"
16523            | "צייר_רשת_צבעונית"
16524            | "رنگین_میش_کھینچو" => {
16525                // ('วาดเมช' is taken by draw_mesh — use a distinct Thai alias)
16526                let id = self.arg_num(&args, 0, 0.)? as usize;
16527                let cx = self.arg_num(&args, 1, 0.)? as f32;
16528                let cy = self.arg_num(&args, 2, 0.)? as f32;
16529                let cz = self.arg_num(&args, 3, 0.)? as f32;
16530                let sc = self.arg_num(&args, 4, 1.)? as f32;
16531                let yaw = self.arg_num(&args, 5, 0.)? as f32;
16532                let sway = self.arg_num(&args, 6, 0.)? as f32;
16533                let arm = self.arg_num(&args, 7, 0.)? as f32;
16534                let lean = self.arg_num(&args, 8, 0.)? as f32;
16535                let leg = self.arg_num(&args, 9, 0.)? as f32;
16536                let tuck = self.arg_num(&args, 10, 0.)? as f32;
16537                if id < self.meshes.len() {
16538                    let m = &self.meshes[id];
16539                    let mut gfx = self.gfx.borrow_mut();
16540                    gfx.draw_color_mesh(m, cx, cy, cz, sc, yaw, sway, arm, lean, leg, tuck);
16541                }
16542                return Ok(Value::Unit);
16543            },
16544            #[cfg(target_arch = "wasm32")]
16545            "mesh_draw"
16546            | "วาดเมชสี"
16547            | "绘制网格"
16548            | "メッシュ描画"
16549            | "메시그리기"
16550            | "رسم_مش_رنگی"
16551            | "ارسم_شبكة_ملونة"
16552            | "צייר_רשת_צבעונית"
16553            | "رنگین_میش_کھینچو" => {
16554                return Ok(Value::Unit);
16555            },
16556
16557            // ── liquid sim (water + oil, immiscible) ──
16558            "liquid_new"
16559            | "新建液体"
16560            | "液体新規"
16561            | "액체생성"
16562            | "สร้างของเหลว"
16563            | "مایع_جدید"
16564            | "سائل_جديد"
16565            | "נוזל_חדש"
16566            | "نیا_مائع"
16567            | "nouveau_liquide"
16568            | "neue_flüssigkeit"
16569            | "новая_жидкость" => {
16570                let w = self.arg_num(&args, 0, 64.)? as usize;
16571                let h = self.arg_num(&args, 1, 64.)? as usize;
16572                let id = self.liquids.len();
16573                self.liquids
16574                    .push(ling_physics::liquid::LiquidGrid::new(w, h));
16575                return Ok(Value::Number(id as f64));
16576            },
16577            "liquid_set_colors"
16578            | "液体颜色"
16579            | "液体配色"
16580            | "액체색상"
16581            | "สีของเหลว"
16582            | "تنظیم_رنگ_مایع"
16583            | "عيّن_ألوان_السائل"
16584            | "קבע_צבעי_נוזל"
16585            | "مائع_رنگ_مقرر_کرو" => {
16586                let id = self.arg_num(&args, 0, 0.)? as usize;
16587                let wr = self.arg_num(&args, 1, 40.)? as f32;
16588                let wg = self.arg_num(&args, 2, 110.)? as f32;
16589                let wb = self.arg_num(&args, 3, 235.)? as f32;
16590                let or_ = self.arg_num(&args, 4, 240.)? as f32;
16591                let og = self.arg_num(&args, 5, 175.)? as f32;
16592                let ob = self.arg_num(&args, 6, 45.)? as f32;
16593                if let Some(g) = self.liquids.get_mut(id) {
16594                    g.set_colors(wr, wg, wb, or_, og, ob);
16595                }
16596                return Ok(Value::Unit);
16597            },
16598            "liquid_splat"
16599            | "液体注入"
16600            | "液体追加"
16601            | "액체분사"
16602            | "หยดของเหลว"
16603            | "پاشش_مایع"
16604            | "بقعة_سائل"
16605            | "התזת_נוזל"
16606            | "مائع_چھینٹا"
16607            | "éclaboussure_liquide"
16608            | "flüssigkeit_spritzer"
16609            | "брызги_жидкости" => {
16610                let id = self.arg_num(&args, 0, 0.)? as usize;
16611                let x = self.arg_num(&args, 1, 0.)? as f32;
16612                let y = self.arg_num(&args, 2, 0.)? as f32;
16613                let kind = self.arg_num(&args, 3, 0.)? as i32;
16614                let amt = self.arg_num(&args, 4, 1.0)? as f32;
16615                let rad = self.arg_num(&args, 5, 4.0)? as f32;
16616                if let Some(g) = self.liquids.get_mut(id) {
16617                    g.splat(x, y, kind, amt, rad);
16618                }
16619                return Ok(Value::Unit);
16620            },
16621            "liquid_gravity"
16622            | "液体重力"
16623            | "液体重力ベクトル"
16624            | "액체중력"
16625            | "แรงโน้มถ่วงเหลว"
16626            | "گرانش_مایع"
16627            | "جاذبية_السائل"
16628            | "כבידת_נוזל"
16629            | "مائع_کشش_ثقل"
16630            | "gravité_liquide"
16631            | "flüssigkeit_schwerkraft"
16632            | "гравитация_жидкости" => {
16633                let id = self.arg_num(&args, 0, 0.)? as usize;
16634                let gx = self.arg_num(&args, 1, 0.)? as f32;
16635                let gy = self.arg_num(&args, 2, 60.)? as f32;
16636                if let Some(g) = self.liquids.get_mut(id) {
16637                    g.set_gravity(gx, gy);
16638                }
16639                return Ok(Value::Unit);
16640            },
16641            "liquid_step"
16642            | "液体步进"
16643            | "液体更新"
16644            | "액체스텝"
16645            | "ก้าวของเหลว"
16646            | "گام_مایع"
16647            | "خطوة_السائل"
16648            | "צעד_נוזל"
16649            | "مائع_قدم"
16650            | "pas_liquide"
16651            | "flüssigkeit_schritt"
16652            | "шаг_жидкости" => {
16653                let id = self.arg_num(&args, 0, 0.)? as usize;
16654                let dt = self.arg_num(&args, 1, 0.016)? as f32;
16655                if let Some(g) = self.liquids.get_mut(id) {
16656                    g.step(dt);
16657                }
16658                return Ok(Value::Unit);
16659            },
16660            // liquid_step_all(dt) — advance EVERY liquid grid one tick, in parallel
16661            // across instances (rayon). Independent grids share no state, so this is
16662            // an embarrassingly-parallel batch: a scene with many liquid surfaces
16663            // steps in one call that scales across cores instead of N serial
16664            // `liquid_step` calls.
16665            "liquid_step_all"
16666            | "液体全步进"
16667            | "液体全更新"
16668            | "전체액체스텝"
16669            | "ก้าวของเหลวทั้งหมด"
16670            | "گام_همه_مایعات"
16671            | "خطوة_كل_السوائل"
16672            | "צעד_כל_הנוזלים"
16673            | "تمام_مائع_قدم" => {
16674                let dt = self.arg_num(&args, 0, 0.016)? as f32;
16675                ling_physics::liquid::step_all(&mut self.liquids, dt);
16676                return Ok(Value::Unit);
16677            },
16678            // liquid_rainbow(id, on) — colour the fluid as a flowing ROYGBIV marble
16679            "liquid_rainbow"
16680            | "液体彩虹"
16681            | "液体虹"
16682            | "액체무지개"
16683            | "ของเหลวสายรุ้ง"
16684            | "مایع_رنگین‌کمان"
16685            | "سائل_قوس_قزح"
16686            | "נוזל_קשת"
16687            | "قوس_قزح_مائع"
16688            | "arc_en_ciel_liquide"
16689            | "flüssigkeit_regenbogen"
16690            | "радуга_жидкости" => {
16691                let id = self.arg_num(&args, 0, 0.)? as usize;
16692                let on = self.arg_num(&args, 1, 1.0)? > 0.5;
16693                if let Some(g) = self.liquids.get_mut(id) {
16694                    g.rainbow = on;
16695                }
16696                return Ok(Value::Unit);
16697            },
16698            // liquid_mix(id) -> 0 (oil/water separated) .. 1 (fully intermixed)
16699            "liquid_mix"
16700            | "液体混合"
16701            | "液体混合度"
16702            | "액체혼합"
16703            | "การผสมของเหลว"
16704            | "ترکیب_مایع"
16705            | "مزج_سائل"
16706            | "ערבוב_נוזל"
16707            | "مائع_ملاؤ"
16708            | "mélanger_liquide"
16709            | "flüssigkeit_mischen"
16710            | "смешать_жидкость" => {
16711                let id = self.arg_num(&args, 0, 0.)? as usize;
16712                let m = self.liquids.get(id).map(|g| g.mix_amount()).unwrap_or(0.0);
16713                return Ok(Value::Number(m as f64));
16714            },
16715            // liquid_draw(id, sx, sy, scale) — fast flat 2-D blit of the colour field
16716            #[cfg(not(target_arch = "wasm32"))]
16717            "liquid_draw"
16718            | "绘制液体"
16719            | "液体描画"
16720            | "액체그리기"
16721            | "วาดของเหลว"
16722            | "رسم_مایع"
16723            | "ارسم_سائلا"
16724            | "צייר_נוזל"
16725            | "مائع_کھینچو"
16726            | "dessiner_liquide"
16727            | "flüssigkeit_zeichnen"
16728            | "рисовать_жидкость" => {
16729                let id = self.arg_num(&args, 0, 0.)? as usize;
16730                let sx = self.arg_num(&args, 1, 0.)? as i32;
16731                let sy = self.arg_num(&args, 2, 0.)? as i32;
16732                let scale = (self.arg_num(&args, 3, 4.)? as i32).max(1);
16733                if id < self.liquids.len() {
16734                    let (gw, gh) = {
16735                        let g = &self.liquids[id];
16736                        (g.w, g.h)
16737                    };
16738                    let mut gfx = self.gfx.borrow_mut();
16739                    let (w, h) = (gfx.width as i32, gfx.height as i32);
16740                    let g = &self.liquids[id];
16741                    for cy in 0..gh {
16742                        for cx in 0..gw {
16743                            let col = g.sample_rgb(cx, cy);
16744                            let bx = sx + cx as i32 * scale;
16745                            let by = sy + cy as i32 * scale;
16746                            for dy in 0..scale {
16747                                for dx in 0..scale {
16748                                    let px = bx + dx;
16749                                    let py = by + dy;
16750                                    if px >= 0 && py >= 0 && px < w && py < h {
16751                                        gfx.buffer[(py * w + px) as usize] = col;
16752                                    }
16753                                }
16754                            }
16755                        }
16756                    }
16757                }
16758                return Ok(Value::Unit);
16759            },
16760            // liquid_draw_surface(id, kind, cx,cy,cz, radius, height)
16761            //   kind: 0 plane · 1 sphere · 2 cylinder · 3 cone · 4 dome
16762            "liquid_draw_surface"
16763            | "液体贴面"
16764            | "液体曲面"
16765            | "액체곡면"
16766            | "ของเหลวบนพื้นผิว"
16767            | "رسم_سطح_مایع"
16768            | "ارسم_سطح_السائل"
16769            | "צייר_משטח_נוזל"
16770            | "مائع_سطح_کھینچو"
16771            | "dessiner_surface_liquide"
16772            | "flüssigkeit_oberfläche_zeichnen"
16773            | "рисовать_поверхность_жидкости" => {
16774                #[cfg(not(target_arch = "wasm32"))]
16775                {
16776                    let id = self.arg_num(&args, 0, 0.)? as usize;
16777                    let kind = self.arg_num(&args, 1, 1.)? as i32;
16778                    let cx = self.arg_num(&args, 2, 0.)? as f32;
16779                    let cy = self.arg_num(&args, 3, 0.)? as f32;
16780                    let cz = self.arg_num(&args, 4, 0.)? as f32;
16781                    let radius = self.arg_num(&args, 5, 2.0)? as f32;
16782                    let height = self.arg_num(&args, 6, 3.0)? as f32;
16783                    if id < self.liquids.len() {
16784                        let (gw, gh) = {
16785                            let g = &self.liquids[id];
16786                            (g.w, g.h)
16787                        };
16788                        let mut gfx = self.gfx.borrow_mut();
16789                        let (w, h, add) = (gfx.width, gfx.height, gfx.blend == 1);
16790                        let cam = gfx.camera.clone();
16791                        let near = -cam.zdist + 0.05;
16792                        let g = &self.liquids[id];
16793                        let tau = std::f32::consts::TAU;
16794                        let pi = std::f32::consts::PI;
16795                        // surface point for a (u,v) in [0,1] on the chosen primitive
16796                        let sp = |u: f32, v: f32| -> [f32; 3] {
16797                            if kind == 0 {
16798                                [
16799                                    cx + (u - 0.5) * 2.0 * radius,
16800                                    cy,
16801                                    cz + (v - 0.5) * 2.0 * radius,
16802                                ]
16803                            } else if kind == 2 {
16804                                let th = u * tau;
16805                                [
16806                                    cx + th.cos() * radius,
16807                                    cy + (v - 0.5) * height,
16808                                    cz + th.sin() * radius,
16809                                ]
16810                            } else if kind == 3 {
16811                                let th = u * tau;
16812                                let rr = radius * (1.0 - v);
16813                                [
16814                                    cx + th.cos() * rr,
16815                                    cy + (v - 0.5) * height,
16816                                    cz + th.sin() * rr,
16817                                ]
16818                            } else if kind == 4 {
16819                                let th = u * tau;
16820                                let ph = v * pi * 0.5;
16821                                [
16822                                    cx + ph.sin() * th.cos() * radius,
16823                                    cy - ph.cos() * radius,
16824                                    cz + ph.sin() * th.sin() * radius,
16825                                ]
16826                            } else {
16827                                let th = u * tau;
16828                                let ph = v * pi;
16829                                [
16830                                    cx + ph.sin() * th.cos() * radius,
16831                                    cy + ph.cos() * radius,
16832                                    cz + ph.sin() * th.sin() * radius,
16833                                ]
16834                            }
16835                        };
16836                        let nrm = |u: f32, v: f32| -> [f32; 3] {
16837                            if kind == 0 {
16838                                [0.0, -1.0, 0.0]
16839                            } else if kind == 2 {
16840                                let th = u * tau;
16841                                [th.cos(), 0.0, th.sin()]
16842                            } else if kind == 3 {
16843                                let th = u * tau;
16844                                let s = (radius / height.max(0.01)).atan();
16845                                [th.cos() * s.cos(), s.sin(), th.sin() * s.cos()]
16846                            } else if kind == 4 {
16847                                let th = u * tau;
16848                                let ph = v * pi * 0.5;
16849                                [ph.sin() * th.cos(), -ph.cos(), ph.sin() * th.sin()]
16850                            } else {
16851                                let th = u * tau;
16852                                let ph = v * pi;
16853                                [ph.sin() * th.cos(), ph.cos(), ph.sin() * th.sin()]
16854                            }
16855                        };
16856                        let gwf = gw as f32;
16857                        let ghf = gh as f32;
16858                        let mut cyc = 0usize;
16859                        while cyc < gh {
16860                            let mut cxc = 0usize;
16861                            while cxc < gw {
16862                                // cull by the cell centre's outward normal
16863                                let uc = (cxc as f32 + 0.5) / gwf;
16864                                let vc = (cyc as f32 + 0.5) / ghf;
16865                                let c = sp(uc, vc);
16866                                let n = nrm(uc, vc);
16867                                let dc = cam.depth(c[0], c[1], c[2]);
16868                                if dc > near {
16869                                    let cull = kind != 0
16870                                        && cam.depth(
16871                                            c[0] + n[0] * 0.06,
16872                                            c[1] + n[1] * 0.06,
16873                                            c[2] + n[2] * 0.06,
16874                                        ) > dc;
16875                                    if !cull {
16876                                        // project the 4 cell corners → a filled AA vector quad
16877                                        let u0 = cxc as f32 / gwf;
16878                                        let u1 = (cxc + 1) as f32 / gwf;
16879                                        let v0 = cyc as f32 / ghf;
16880                                        let v1 = (cyc + 1) as f32 / ghf;
16881                                        let q = [sp(u0, v0), sp(u1, v0), sp(u1, v1), sp(u0, v1)];
16882                                        let mut poly: Vec<[f32; 2]> = Vec::with_capacity(5);
16883                                        let mut ok = true;
16884                                        for p in &q {
16885                                            if cam.depth(p[0], p[1], p[2]) <= near {
16886                                                ok = false;
16887                                                break;
16888                                            }
16889                                            let (sx, sy, _) = cam.project(p[0], p[1], p[2]);
16890                                            poly.push([sx, sy]);
16891                                        }
16892                                        if ok {
16893                                            let p0 = poly[0];
16894                                            poly.push(p0);
16895                                            let col = g.sample_rgb(cxc, cyc);
16896                                            crate::gfx::raster::fill_contours_aa(
16897                                                &mut gfx.buffer,
16898                                                w,
16899                                                h,
16900                                                col,
16901                                                add,
16902                                                std::slice::from_ref(&poly),
16903                                            );
16904                                        }
16905                                    }
16906                                }
16907                                cxc += 1;
16908                            }
16909                            cyc += 1;
16910                        }
16911                    }
16912                }
16913                #[cfg(target_arch = "wasm32")]
16914                {
16915                    // WASM: liquid_draw_surface is a no-op for now (would need WebGL shader)
16916                    // The liquid simulation still runs, just not rendered to 3D surfaces
16917                }
16918                return Ok(Value::Unit);
16919            },
16920            // sparkle(x, y, w, h, count [, t]) — scatter twinkling vector star-sparkles
16921            // in a rect (snowglobe effect) in the current colour + blend mode.
16922            #[cfg(not(target_arch = "wasm32"))]
16923            "sparkle" | "闪光" | "きらめき" | "반짝임" | "ประกาย" | "درخشش" | "بريق" | "נצנוץ"
16924            | "چمک" | "scintillement" | "funkeln" | "искриться" => {
16925                let x = self.arg_num(&args, 0, 0.)? as f32;
16926                let y = self.arg_num(&args, 1, 0.)? as f32;
16927                let ww = self.arg_num(&args, 2, 200.)? as f32;
16928                let hh = self.arg_num(&args, 3, 200.)? as f32;
16929                let count = self.arg_num(&args, 4, 40.)? as i32;
16930                let t = self.arg_num(&args, 5, 0.)? as f32;
16931                let mut gfx = self.gfx.borrow_mut();
16932                let (w, h, add, color) = (gfx.width, gfx.height, gfx.blend == 1, gfx.color);
16933                let (cr, cg, cb) = (
16934                    (color >> 16 & 0xFF) as f32,
16935                    (color >> 8 & 0xFF) as f32,
16936                    (color & 0xFF) as f32,
16937                );
16938                let mut n = 0i32;
16939                while n < count {
16940                    let hsh = (n as u32).wrapping_mul(2654435761).wrapping_add(0x9E3779B9);
16941                    let u = ((hsh >> 8) & 1023) as f32 / 1023.0;
16942                    let v = ((hsh >> 18) & 1023) as f32 / 1023.0;
16943                    let phase = (hsh & 255) as f32 / 255.0;
16944                    let tw = (t * 3.0 + phase * std::f32::consts::TAU + n as f32).sin() * 0.5 + 0.5;
16945                    let sz = 1.5 + tw * 5.0;
16946                    let px = x + u * ww;
16947                    let py = y + v * hh;
16948                    let b = tw * tw; // sharp twinkle
16949                    let col =
16950                        (((cr * b) as u32) << 16) | (((cg * b) as u32) << 8) | ((cb * b) as u32);
16951                    crate::gfx::raster::draw_line_aa(
16952                        &mut gfx.buffer,
16953                        w,
16954                        h,
16955                        col,
16956                        add,
16957                        px - sz,
16958                        py,
16959                        px + sz,
16960                        py,
16961                    );
16962                    crate::gfx::raster::draw_line_aa(
16963                        &mut gfx.buffer,
16964                        w,
16965                        h,
16966                        col,
16967                        add,
16968                        px,
16969                        py - sz,
16970                        px,
16971                        py + sz,
16972                    );
16973                    let d = sz * 0.55;
16974                    crate::gfx::raster::draw_line_aa(
16975                        &mut gfx.buffer,
16976                        w,
16977                        h,
16978                        col,
16979                        add,
16980                        px - d,
16981                        py - d,
16982                        px + d,
16983                        py + d,
16984                    );
16985                    crate::gfx::raster::draw_line_aa(
16986                        &mut gfx.buffer,
16987                        w,
16988                        h,
16989                        col,
16990                        add,
16991                        px - d,
16992                        py + d,
16993                        px + d,
16994                        py - d,
16995                    );
16996                    n += 1;
16997                }
16998                return Ok(Value::Unit);
16999            },
17000
17001            // ══════════════════════════════════════════════════════════════════
17002            // DIALOG BUILTINS  (crates/ling-game/src/dialog.rs) — cinematic,
17003            // typed-out, colour-coded text boxes. Markup: {n}name{/} {p}place{/}
17004            // {i}item{/}, \n newline, || page break.
17005            // ══════════════════════════════════════════════════════════════════
17006            #[cfg(not(target_arch = "wasm32"))]
17007            "dialog_show"
17008            | "对话显示"
17009            | "会話表示"
17010            | "대화표시"
17011            | "แสดงบทสนทนา"
17012            | "نمایش_گفتگو"
17013            | "اعرض_الحوار"
17014            | "הצג_דיאלוג"
17015            | "مکالمہ_دکھاؤ"
17016            | "afficher_dialogue"
17017            | "dialog_anzeigen"
17018            | "показать_диалог" => {
17019                let text = self.arg_str(&args, 0, "");
17020                let cps = self.arg_num(&args, 1, 32.0)? as f32;
17021                self.dialog = Some(ling_game::dialog::Dialog::new(&text, cps));
17022                return Ok(Value::Unit);
17023            },
17024            #[cfg(not(target_arch = "wasm32"))]
17025            "dialog_step"
17026            | "对话步进"
17027            | "会話更新"
17028            | "대화스텝"
17029            | "ก้าวบทสนทนา"
17030            | "گام_گفتگو"
17031            | "خطوة_الحوار"
17032            | "צעד_דיאלוג"
17033            | "مکالمہ_قدم"
17034            | "pas_dialogue"
17035            | "dialog_schritt"
17036            | "шаг_диалога" => {
17037                let dt = self.arg_num(&args, 0, 0.016)? as f32;
17038                if let Some(d) = self.dialog.as_mut() {
17039                    d.update(dt);
17040                }
17041                return Ok(Value::Unit);
17042            },
17043            #[cfg(not(target_arch = "wasm32"))]
17044            "dialog_advance"
17045            | "对话推进"
17046            | "会話送り"
17047            | "대화진행"
17048            | "เลื่อนบทสนทนา"
17049            | "پیشروی_گفتگو"
17050            | "تقدّم_الحوار"
17051            | "קדם_דיאלוג"
17052            | "مکالمہ_آگے_بڑھاؤ"
17053            | "avancer_dialogue"
17054            | "dialog_weiter"
17055            | "продолжить_диалог" => {
17056                if let Some(d) = self.dialog.as_mut() {
17057                    d.advance();
17058                }
17059                return Ok(Value::Unit);
17060            },
17061            #[cfg(not(target_arch = "wasm32"))]
17062            "dialog_active"
17063            | "对话激活"
17064            | "会話中"
17065            | "대화중"
17066            | "บทสนทนาทำงาน"
17067            | "گفتگو_فعال"
17068            | "الحوار_نشط"
17069            | "דיאלוג_פעיל"
17070            | "مکالمہ_فعال"
17071            | "dialogue_actif"
17072            | "dialog_aktiv"
17073            | "диалог_активен" => {
17074                let a = self
17075                    .dialog
17076                    .as_ref()
17077                    .map(|d| !d.is_closed())
17078                    .unwrap_or(false);
17079                return Ok(Value::Bool(a));
17080            },
17081            #[cfg(not(target_arch = "wasm32"))]
17082            "dialog_typing"
17083            | "对话打字"
17084            | "会話タイプ中"
17085            | "대화타이핑"
17086            | "กำลังพิมพ์บทสนทนา"
17087            | "گفتگو_در_حال_تایپ"
17088            | "الحوار_يكتب"
17089            | "דיאלוג_מקליד"
17090            | "مکالمہ_ٹائپنگ"
17091            | "dialogue_frappe"
17092            | "dialog_tippen"
17093            | "диалог_печатает" => {
17094                use ling_game::dialog::Dialog;
17095
17096                let a = self
17097                    .dialog
17098                    .as_ref()
17099                    .map(|d: &Dialog| !d.is_closed() && d.is_typing())
17100                    .unwrap_or(false);
17101                return Ok(Value::Bool(a));
17102            },
17103            #[cfg(not(target_arch = "wasm32"))]
17104            "dialog_close"
17105            | "对话关闭"
17106            | "会話閉じる"
17107            | "대화닫기"
17108            | "ปิดบทสนทนา"
17109            | "بستن_گفتگو"
17110            | "أغلق_الحوار"
17111            | "סגור_דיאלוג"
17112            | "مکالمہ_بند"
17113            | "fermer_dialogue"
17114            | "dialog_schließen"
17115            | "закрыть_диалог" => {
17116                self.dialog = None;
17117                return Ok(Value::Unit);
17118            },
17119            // dialog_color(role, r, g, b) — role: 0 text · 1 name · 2 place · 3 item
17120            #[cfg(not(target_arch = "wasm32"))]
17121            "dialog_color"
17122            | "对话颜色"
17123            | "会話色"
17124            | "대화색"
17125            | "สีบทสนทนา"
17126            | "رنگ_گفتگو"
17127            | "لون_الحوار"
17128            | "צבע_דיאלוג"
17129            | "مکالمہ_رنگ"
17130            | "couleur_dialogue"
17131            | "dialog_farbe"
17132            | "цвет_диалога" => {
17133                let role = (self.arg_num(&args, 0, 0.0)? as usize).min(3);
17134                let r = self.arg_num(&args, 1, 255.0)? as u32 & 0xFF;
17135                let g = self.arg_num(&args, 2, 255.0)? as u32 & 0xFF;
17136                let b = self.arg_num(&args, 3, 255.0)? as u32 & 0xFF;
17137                self.dialog_colors[role] = (r << 16) | (g << 8) | b;
17138                return Ok(Value::Unit);
17139            },
17140            // dialog_draw(x, y, w, h [, font_handle]) — draw the box + typed text
17141            #[cfg(not(target_arch = "wasm32"))]
17142            "dialog_draw"
17143            | "对话绘制"
17144            | "会話描画"
17145            | "대화그리기"
17146            | "วาดบทสนทนา"
17147            | "رسم_گفتگو"
17148            | "ارسم_الحوار"
17149            | "צייר_דיאלוג"
17150            | "مکالمہ_کھینچو"
17151            | "dessiner_dialogue"
17152            | "dialog_zeichnen"
17153            | "рисовать_диалог" => {
17154                let x = self.arg_num(&args, 0, 40.0)? as f32;
17155                let y = self.arg_num(&args, 1, 0.0)? as f32;
17156                let ww = self.arg_num(&args, 2, 720.0)? as f32;
17157                let hh = self.arg_num(&args, 3, 150.0)? as f32;
17158                let font = self.arg_num(&args, 4, -1.0)? as i64;
17159                let t = (crate::runtime::now_secs() - self.start_time_secs) as f32;
17160                self.render_dialog(x, y, ww, hh, font, t);
17161                return Ok(Value::Unit);
17162            },
17163
17164            // text_poll() — fold newly-typed keys into the input buffer, return it.
17165            // Repeat is enabled (KeyRepeat::Yes) so holding a key/Backspace behaves
17166            // like a normal text field; length is capped so a stuck key or a runaway
17167            // script can't grow the buffer without bound.
17168            #[cfg(not(target_arch = "wasm32"))]
17169            "text_poll" => {
17170                const TEXT_BUFFER_MAX: usize = 240;
17171                // See key_down/key_pressed: our topmost fullscreen window can
17172                // be visually in front without real Win32 keyboard focus, so
17173                // WM_KEYDOWN/WM_CHAR (what minifb's get_keys_pressed reads)
17174                // never arrive. Poll the OS key-state table directly instead
17175                // — no focus required — with our own repeat-aware edge
17176                // detection (key_repeat_fire) so holding a key behaves like
17177                // the KeyRepeat::Yes path below: one char on press, then
17178                // repeats after a short hold delay.
17179                #[cfg(windows)]
17180                {
17181                    let topmost = self.gfx.borrow().topmost_window;
17182                    if topmost {
17183                        if !window_is_foreground(self.gfx.borrow().hwnd) {
17184                            return Ok(Value::Str(self.text_buffer.clone()));
17185                        }
17186                        let shift = os_key_down(VK_SHIFT);
17187                        let now = crate::runtime::now_secs();
17188                        let mut gfx = self.gfx.borrow_mut();
17189                        let back_idx = VK_BACK as usize;
17190                        let back_down = os_key_down(VK_BACK);
17191                        let back_was = gfx.raw_keys_prev[back_idx];
17192                        let (mut back_since, mut back_fire) = (
17193                            gfx.raw_keys_down_since[back_idx],
17194                            gfx.raw_keys_last_fire[back_idx],
17195                        );
17196                        if key_repeat_fire(
17197                            now,
17198                            back_down,
17199                            back_was,
17200                            &mut back_since,
17201                            &mut back_fire,
17202                        ) {
17203                            self.text_buffer.pop();
17204                        }
17205                        gfx.raw_keys_down_since[back_idx] = back_since;
17206                        gfx.raw_keys_last_fire[back_idx] = back_fire;
17207                        gfx.raw_keys_prev[back_idx] = back_down;
17208                        for &vk in TEXT_POLL_VKS {
17209                            let idx = (vk as usize) & 0xFF;
17210                            let down = os_key_down(vk);
17211                            let was = gfx.raw_keys_prev[idx];
17212                            let (mut since, mut fire) =
17213                                (gfx.raw_keys_down_since[idx], gfx.raw_keys_last_fire[idx]);
17214                            if key_repeat_fire(now, down, was, &mut since, &mut fire) {
17215                                if let Some(c) = vk_char(vk, shift) {
17216                                    if self.text_buffer.chars().count() < TEXT_BUFFER_MAX {
17217                                        self.text_buffer.push(c);
17218                                    }
17219                                }
17220                            }
17221                            gfx.raw_keys_down_since[idx] = since;
17222                            gfx.raw_keys_last_fire[idx] = fire;
17223                            gfx.raw_keys_prev[idx] = down;
17224                        }
17225                        return Ok(Value::Str(self.text_buffer.clone()));
17226                    }
17227                }
17228                let (keys, shift) = {
17229                    let gfx = self.gfx.borrow();
17230                    match gfx.window.as_ref() {
17231                        Some(w) => (
17232                            w.get_keys_pressed(minifb::KeyRepeat::Yes),
17233                            w.is_key_down(minifb::Key::LeftShift)
17234                                || w.is_key_down(minifb::Key::RightShift),
17235                        ),
17236                        None => (Vec::new(), false),
17237                    }
17238                };
17239                for k in keys {
17240                    if k == minifb::Key::Backspace {
17241                        self.text_buffer.pop();
17242                    } else if let Some(c) = key_char(k, shift) {
17243                        if self.text_buffer.chars().count() < TEXT_BUFFER_MAX {
17244                            self.text_buffer.push(c);
17245                        }
17246                    }
17247                }
17248                return Ok(Value::Str(self.text_buffer.clone()));
17249            },
17250            #[cfg(target_arch = "wasm32")]
17251            "text_poll" => {
17252                return Ok(Value::Str(self.text_buffer.clone()));
17253            },
17254            "text_get" => return Ok(Value::Str(self.text_buffer.clone())),
17255            "text_set" => {
17256                self.text_buffer = self.arg_str(&args, 0, "");
17257                return Ok(Value::Unit);
17258            },
17259            "text_clear" => {
17260                self.text_buffer.clear();
17261                return Ok(Value::Unit);
17262            },
17263            // record_frame() — append the current framebuffer as a PPM, return frame #
17264            #[cfg(not(target_arch = "wasm32"))]
17265            "record_frame" => {
17266                let n = self.record_n;
17267                let (buf, w, h) = {
17268                    let gfx = self.gfx.borrow();
17269                    (gfx.buffer.clone(), gfx.width, gfx.height)
17270                };
17271                let _ = std::fs::create_dir_all("recordings");
17272                let mut out = Vec::with_capacity(w * h * 3 + 32);
17273                out.extend_from_slice(format!("P6\n{w} {h}\n255\n").as_bytes());
17274                for px in &buf {
17275                    let p = *px;
17276                    out.push((p >> 16) as u8);
17277                    out.push((p >> 8) as u8);
17278                    out.push(p as u8);
17279                }
17280                let _ = std::fs::write(format!("recordings/frame_{n:05}.ppm"), out);
17281                self.record_n += 1;
17282                return Ok(Value::Number(n as f64));
17283            },
17284            "record_count" => return Ok(Value::Number(self.record_n as f64)),
17285            // ── screenshot(mode) → PNG in ./screenshots/ with timestamp + mode + size ──
17286            #[cfg(not(target_arch = "wasm32"))]
17287            "screenshot" | "บันทึกภาพ" | "عکس‌صفحه" | "لقطة_شاشة" | "צילום_מסך" | "اسکرین_شاٹ" =>
17288            {
17289                let mode = self.arg_str(&args, 0, "game");
17290                let (buf, w, h) = {
17291                    let gfx = self.gfx.borrow();
17292                    (gfx.buffer.clone(), gfx.width, gfx.height)
17293                };
17294                let _ = std::fs::create_dir_all("screenshots");
17295                let ts = std::time::SystemTime::now()
17296                    .duration_since(std::time::UNIX_EPOCH)
17297                    .map(|d| d.as_secs())
17298                    .unwrap_or(0);
17299                let safe: String = mode
17300                    .chars()
17301                    .map(|c| if c.is_alphanumeric() { c } else { '_' })
17302                    .collect();
17303                let path = format!("screenshots/ss_{ts}_{safe}_{w}x{h}.png");
17304                let mut rgb = Vec::with_capacity(w * h * 3);
17305                for px in &buf {
17306                    let p = *px;
17307                    rgb.push((p >> 16) as u8);
17308                    rgb.push((p >> 8) as u8);
17309                    rgb.push(p as u8);
17310                }
17311                if let Some(img) = image::RgbImage::from_raw(w as u32, h as u32, rgb) {
17312                    let _ = img.save(&path);
17313                }
17314                return Ok(Value::Str(path));
17315            },
17316            // ── microphone → crypto donut ──
17317            // mic_capture() — append the latest mic samples to the record buffer
17318            // (call each frame while recording). Returns the buffer length.
17319            #[cfg(not(target_arch = "wasm32"))]
17320            "mic_capture" => {
17321                if let Some(mic) = self.mic.as_ref() {
17322                    let s = mic.latest_samples();
17323                    self.mic_buffer.extend_from_slice(&s);
17324                    let cap = 96_000usize; // ~2 s @ 48 kHz
17325                    if self.mic_buffer.len() > cap {
17326                        let drop = self.mic_buffer.len() - cap;
17327                        self.mic_buffer.drain(0..drop);
17328                    }
17329                }
17330                return Ok(Value::Number(self.mic_buffer.len() as f64));
17331            },
17332            // mic_seed() — SHA3-256 hex of the recorded audio, usable as a donut seed
17333            #[cfg(not(target_arch = "wasm32"))]
17334            "mic_seed" => {
17335                let mut bytes = Vec::with_capacity(self.mic_buffer.len() * 4);
17336                for f in &self.mic_buffer {
17337                    bytes.extend_from_slice(&f.to_le_bytes());
17338                }
17339                return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(&bytes))));
17340            },
17341            #[cfg(not(target_arch = "wasm32"))]
17342            "mic_clear" => {
17343                self.mic_buffer.clear();
17344                return Ok(Value::Number(0.0));
17345            },
17346            // flush the 3-D depth queue onto the framebuffer WITHOUT presenting,
17347            // so 2-D UI drawn afterwards overlays the 3-D scene.
17348            #[cfg(not(target_arch = "wasm32"))]
17349            "flush_3d" | "render_3d" => {
17350                let mut gfx = self.gfx.borrow_mut();
17351                if !gfx.depth_queue.is_empty() {
17352                    let w = gfx.width;
17353                    let h = gfx.height;
17354                    let dt = gfx.depth_test;
17355                    let reset_z = gfx.zbuf_needs_clear;
17356                    let (bm, ba) = (gfx.blend, gfx.alpha);
17357                    let aa = gfx.antialias;
17358                    let queue = std::mem::take(&mut gfx.depth_queue);
17359                    {
17360                        let g = &mut *gfx;
17361                        let z = if dt { Some(&mut g.depth_buf) } else { None };
17362                        queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
17363                    }
17364                    gfx.zbuf_needs_clear = false;
17365                    gfx.depth_queue.set_state(bm, ba); // keep active blend/alpha across the mid-frame flush
17366                }
17367                return Ok(Value::Unit);
17368            },
17369            #[cfg(target_arch = "wasm32")]
17370            "flush_3d" | "render_3d" => {
17371                let mut gfx = self.gfx.borrow_mut();
17372                if !gfx.depth_queue.is_empty() {
17373                    let w = gfx.width;
17374                    let h = gfx.height;
17375                    let dt = gfx.depth_test;
17376                    let reset_z = gfx.zbuf_needs_clear;
17377                    let (bm, ba) = (gfx.blend, gfx.alpha);
17378                    let aa = gfx.antialias;
17379                    let queue = std::mem::take(&mut gfx.depth_queue);
17380                    {
17381                        let g = &mut *gfx;
17382                        let z = if dt { Some(&mut g.depth_buf) } else { None };
17383                        queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
17384                    }
17385                    gfx.zbuf_needs_clear = false;
17386                    gfx.depth_queue.set_state(bm, ba);
17387                }
17388                return Ok(Value::Unit);
17389            },
17390
17391            // flush_post() — flush the 3-D queue like `flush_3d`, then run the
17392            // toon post-chain (SSAO → outlines → tone ramp → bloom → FXAA) over
17393            // the SCENE immediately. `present` skips the chain this frame, so
17394            // 2-D UI drawn after this call stays exact — no bloom/blur on HUDs.
17395            "flush_post" | "post_now" | "포스트플러시" | "后期冲刷" => {
17396                let mut gfx = self.gfx.borrow_mut();
17397                if !gfx.depth_queue.is_empty() {
17398                    let w = gfx.width;
17399                    let h = gfx.height;
17400                    let dt = gfx.depth_test;
17401                    let reset_z = gfx.zbuf_needs_clear;
17402                    let (bm, ba) = (gfx.blend, gfx.alpha);
17403                    let aa = gfx.antialias;
17404                    let queue = std::mem::take(&mut gfx.depth_queue);
17405                    {
17406                        let g = &mut *gfx;
17407                        let z = if dt { Some(&mut g.depth_buf) } else { None };
17408                        queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
17409                    }
17410                    gfx.zbuf_needs_clear = false;
17411                    gfx.depth_queue.set_state(bm, ba);
17412                }
17413                gfx.toon_post_process();
17414                gfx.post_done = true;
17415                return Ok(Value::Unit);
17416            },
17417
17418            // Viscous full-screen distortion (warp/pucker/bloat, edge-wrapped). Call
17419            // after the 3-D flush and before the UI so only the world layer warps.
17420            #[cfg(not(target_arch = "wasm32"))]
17421            "screen_distort"
17422            | "บิดจอ"
17423            | "屏幕扭曲"
17424            | "画面歪み"
17425            | "화면왜곡"
17426            | "اعوجاج_صفحه"
17427            | "شوّه_الشاشة"
17428            | "עוות_מסך"
17429            | "اسکرین_ڈسٹورٹ" => {
17430                let amount = self.arg_num(&args, 0, 8.0)? as f32;
17431                let t = self.arg_num(&args, 1, 0.0)? as f32;
17432                // optional `step` (default 1 = full res): 2 = half-res block warp
17433                // (~4× fewer warp computes, slightly softer — suits a liquid look).
17434                let step = self.arg_num(&args, 2, 1.0)?.max(1.0) as usize;
17435                let _d = std::time::Instant::now();
17436                self.gfx.borrow_mut().distort(amount, t, step);
17437                ling_phase_add(phase::DISTORT, _d.elapsed().as_nanos());
17438                return Ok(Value::Unit);
17439            },
17440
17441            "set_rim"
17442            | "设置边缘光"
17443            | "リム設定"
17444            | "림라이트"
17445            | "ตั้งขอบเรือง"
17446            | "تنظیم_نور_لبه"
17447            | "عيّن_إضاءة_الحافة"
17448            | "קבע_תאורת_קצה"
17449            | "رم_لائٹ_مقرر_کرو"
17450            | "définir_contour_lumineux"
17451            | "rimlicht_setzen"
17452            | "задать_контурный_свет" => {
17453                let s = self.arg_num(&args, 0, 0.6)? as f32;
17454                let r = self.arg_num(&args, 1, 115.)? as f32 / 255.0;
17455                let g = self.arg_num(&args, 2, 217.)? as f32 / 255.0;
17456                let b = self.arg_num(&args, 3, 255.)? as f32 / 255.0;
17457                let mut gfx = self.gfx.borrow_mut();
17458                gfx.shade.rim = s;
17459                gfx.shade.rim_color = [r, g, b];
17460                return Ok(Value::Unit);
17461            },
17462
17463            // ══════════════════════════════════════════════════════════════════
17464            // 3-D PRIMITIVES  (src/gfx/shapes.rs)  — "Inkscape for 3-D"
17465            //   shape(cx,cy,cz,  sx,sy,sz,  rx,ry,rz,  mode,  e0,e1,e2)
17466            //     centre (cx,cy,cz), per-axis scale, Euler rotation (radians),
17467            //     mode: 0 filled · 1 wireframe · 2 both,
17468            //     e0..e2: shape-specific (segments / sides / ratio …).
17469            //   Pen colour (set_color) drives fill lighting and wireframe colour.
17470            // ══════════════════════════════════════════════════════════════════
17471            n if crate::gfx::shapes::canon(n).is_some() => {
17472                let kind = crate::gfx::shapes::canon(n).unwrap();
17473                let cx = self.arg_num(&args, 0, 0.)? as f32;
17474                let cy = self.arg_num(&args, 1, 0.)? as f32;
17475                let cz = self.arg_num(&args, 2, 0.)? as f32;
17476                let sx = self.arg_num(&args, 3, 1.)? as f32;
17477                let sy = self.arg_num(&args, 4, 1.)? as f32;
17478                let sz = self.arg_num(&args, 5, 1.)? as f32;
17479                let rx = self.arg_num(&args, 6, 0.)? as f32;
17480                let ry = self.arg_num(&args, 7, 0.)? as f32;
17481                let rz = self.arg_num(&args, 8, 0.)? as f32;
17482                let mode = self.arg_num(&args, 9, 0.)? as i32;
17483                let e0 = self.arg_num(&args, 10, 0.)? as f32;
17484                let e1 = self.arg_num(&args, 11, 0.)? as f32;
17485                let e2 = self.arg_num(&args, 12, 0.)? as f32;
17486                if let Some(mesh) = crate::gfx::shapes::build(
17487                    kind,
17488                    [cx, cy, cz, sx, sy, sz, rx, ry, rz],
17489                    e0,
17490                    e1,
17491                    e2,
17492                ) {
17493                    let mut gfx = self.gfx.borrow_mut();
17494                    gfx.emit_mesh(&mesh, mode);
17495                }
17496                return Ok(Value::Unit);
17497            },
17498
17499            _ => {},
17500        }
17501
17502        // `form` struct constructor: positional `Name(v0, v1, ...)`.
17503        if let Some(field_names) = self.structs.get(name).cloned() {
17504            if args.len() != field_names.len() {
17505                return Err(EvalErr::from(format!(
17506                    "{name} expects {} field(s), got {}",
17507                    field_names.len(),
17508                    args.len()
17509                )));
17510            }
17511            let fields = field_names.into_iter().zip(args).collect();
17512            return Ok(Value::Struct { name: name.to_string(), fields });
17513        }
17514
17515        // `choose` enum variant constructor: `Variant(...)` or `Enum::Variant(...)`.
17516        if let Some((enum_name, arity)) = self.enum_variants.get(name).cloned() {
17517            if args.len() != arity {
17518                return Err(EvalErr::from(format!(
17519                    "{name} expects {arity} value(s), got {}",
17520                    args.len()
17521                )));
17522            }
17523            let variant = name.rsplit("::").next().unwrap_or(name).to_string();
17524            return Ok(Value::Variant { enum_name, variant, payload: args });
17525        }
17526
17527        #[cfg(target_arch = "wasm32")]
17528        if let Some(v) = wasm_unsupported_builtin(name) {
17529            return Ok(v);
17530        }
17531
17532        Err(EvalErr::from(format!("unknown function '{name}'")))
17533    }
17534
17535    fn call_value(&mut self, v: Value, args: Vec<Value>) -> EvalResult {
17536        match v {
17537            Value::Fn(params, body, mut captured) => {
17538                for (p, a) in params.iter().zip(args) {
17539                    captured.insert(p.clone(), a);
17540                }
17541                match self.framed("<closure>", |me| me.exec_block(&body, &mut captured)) {
17542                    Ok(v) => Ok(v.unwrap_or(Value::Unit)),
17543                    Err(EvalErr::Return(v)) => Ok(v),
17544                    Err(e) => Err(e),
17545                }
17546            },
17547            other => Err(EvalErr::from(format!("cannot call {:?}", other))),
17548        }
17549    }
17550
17551    fn call_method(&self, recv: Value, method: &str, args: Vec<Value>) -> EvalResult {
17552        match (&recv, method) {
17553            (Value::Str(s), "is_empty" | "是空") => Ok(Value::Bool(s.is_empty())),
17554            // All of `lingfu normalize`'s per-language spellings of len/push
17555            // (see ling-fu normalize.rs alias table), not just the Chinese
17556            // ones — normalize rewrites method calls into whichever language
17557            // the project is normalized to, and any spelling missing here
17558            // makes those calls un-callable post-normalize (first hit with
17559            // `.长度()`, then again with Thai `.ความยาว()`).
17560            (Value::Str(s), "len" | "长" | "长度" | "長さ" | "길이" | "ความยาว") => {
17561                Ok(Value::Number(s.len() as f64))
17562            },
17563            (Value::Str(s), "to_string" | "转文") => Ok(Value::Str(s.clone())),
17564            (Value::Str(s), "contains" | "包含") => {
17565                if let Some(Value::Str(sub)) = args.first() {
17566                    Ok(Value::Bool(s.contains(sub.as_str())))
17567                } else {
17568                    Ok(Value::Bool(false))
17569                }
17570            },
17571            (Value::Str(s), "push_str" | "推_文") => {
17572                let mut s2 = s.clone();
17573                if let Some(Value::Str(a)) = args.first() {
17574                    s2.push_str(a);
17575                }
17576                Ok(Value::Str(s2))
17577            },
17578            (Value::List(v), "len" | "长" | "长度" | "長さ" | "길이" | "ความยาว") => {
17579                Ok(Value::Number(v.len() as f64))
17580            },
17581            (Value::List(v), "push" | "推" | "添加" | "追加" | "추가" | "เพิ่ม") =>
17582            {
17583                let mut v2: Vec<Value> = (**v).clone();
17584                if let Some(a) = args.first() {
17585                    v2.push(a.clone());
17586                }
17587                Ok(Value::List(Rc::new(v2)))
17588            },
17589            // `form` field access: `point.x` (no-arg method == field read).
17590            (Value::Struct { fields, .. }, _) if args.is_empty() => fields
17591                .iter()
17592                .find(|(k, _)| k == method)
17593                .map(|(_, v)| v.clone())
17594                .ok_or_else(|| EvalErr::from(format!("no field '{method}' on {recv}"))),
17595            // Enum introspection: `.tag` → variant name, `.is(Name)` not needed for now.
17596            (Value::Variant { variant, .. }, "tag" | "标签" | "タグ" | "태그" | "ป้าย")
17597                if args.is_empty() =>
17598            {
17599                Ok(Value::Str(variant.clone()))
17600            },
17601            (Value::Ok(inner), _) | (Value::Err(inner), _) => Ok(*inner.clone()),
17602            _ => Err(EvalErr::from(format!("no method '{method}' on {recv}"))),
17603        }
17604    }
17605
17606    // ─── Pattern matching ─────────────────────────────────────────────────────
17607
17608    fn match_pattern(&self, pat: &Pattern, val: &Value) -> Option<Env> {
17609        match (pat, val) {
17610            (Pattern::Wildcard, _) => Some(new_env()),
17611            (Pattern::Str(s), Value::Str(v)) if s == v => Some(new_env()),
17612            (Pattern::Number(n), Value::Number(v)) if (n - v).abs() < 1e-12 => Some(new_env()),
17613            (Pattern::Bool(b), Value::Bool(v)) if b == v => Some(new_env()),
17614            (Pattern::Ident(name), _) => {
17615                let mut e = new_env();
17616                e.insert(name.clone(), val.clone());
17617                Some(e)
17618            },
17619            (Pattern::Constructor(ctor, inner_pat), _) => {
17620                let (matches, inner_val) = match (ctor.as_str(), val) {
17621                    ("ok" | "好", Value::Ok(v)) => (true, Some(v.as_ref().clone())),
17622                    ("bad" | "坏", Value::Err(v)) => (true, Some(v.as_ref().clone())),
17623                    ("ok" | "好", v) if !matches!(v, Value::Err(_)) => (true, Some(v.clone())),
17624                    _ => (false, None),
17625                };
17626                if !matches {
17627                    return None;
17628                }
17629                match (inner_pat, inner_val) {
17630                    (Some(p), Some(v)) => self.match_pattern(p, &v),
17631                    (None, _) => Some(new_env()),
17632                    (Some(p), None) => self.match_pattern(p, &Value::Unit),
17633                }
17634            },
17635            // User enum variant pattern: `Circle(r)`, `Pair(a, b)`, nullary `Origin()`.
17636            (Pattern::Variant(vname, sub_pats), Value::Variant { variant, payload, .. }) => {
17637                if vname != variant || sub_pats.len() != payload.len() {
17638                    return None;
17639                }
17640                let mut bindings = new_env();
17641                for (p, v) in sub_pats.iter().zip(payload.iter()) {
17642                    bindings.extend(self.match_pattern(p, v)?);
17643                }
17644                Some(bindings)
17645            },
17646            // A zero-payload variant pattern also matches the bare result-style `ok`/`bad`
17647            // values so `Ok()`-style patterns keep working uniformly.
17648            (Pattern::Variant(vname, sub), Value::Ok(v)) if (vname == "ok" || vname == "好") => {
17649                match sub.as_slice() {
17650                    [] => Some(new_env()),
17651                    [p] => self.match_pattern(p, v),
17652                    _ => None,
17653                }
17654            },
17655            (Pattern::Variant(vname, sub), Value::Err(v))
17656                if (vname == "bad" || vname == "坏" || vname == "err") =>
17657            {
17658                match sub.as_slice() {
17659                    [] => Some(new_env()),
17660                    [p] => self.match_pattern(p, v),
17661                    _ => None,
17662                }
17663            },
17664            _ => None,
17665        }
17666    }
17667
17668    // ─── Utilities ───────────────────────────────────────────────────────────
17669
17670    fn value_to_iter(&self, val: Value) -> Result<Vec<Value>, EvalErr> {
17671        match val {
17672            Value::List(v) => Ok(Rc::try_unwrap(v).unwrap_or_else(|rc| (*rc).clone())),
17673            Value::Str(s) => Ok(s.chars().map(|c| Value::Str(c.to_string())).collect()),
17674            Value::Number(n) => Ok((0..n as i64).map(|i| Value::Number(i as f64)).collect()),
17675            other => Err(EvalErr::from(format!("cannot iterate over {:?}", other))),
17676        }
17677    }
17678
17679    pub(crate) fn is_truthy(&self, val: &Value) -> bool {
17680        match val {
17681            Value::Bool(b) => *b,
17682            Value::Unit => false,
17683            Value::Number(n) => *n != 0.0,
17684            Value::Str(s) => !s.is_empty(),
17685            Value::List(v) => !v.is_empty(),
17686            Value::Ok(_) => true,
17687            Value::Err(_) => false,
17688            Value::Fn(_, _, _) => true,
17689            Value::Struct { .. } => true,
17690            Value::Variant { .. } => true,
17691        }
17692    }
17693
17694    fn to_number(&self, val: &Value) -> Result<f64, EvalErr> {
17695        match val {
17696            Value::Number(n) => Ok(*n),
17697            Value::Str(s) => s
17698                .parse()
17699                .map_err(|_| EvalErr::from(format!("cannot convert '{s}' to number"))),
17700            other => Err(EvalErr::from(format!("expected number, got {:?}", other))),
17701        }
17702    }
17703
17704    /// Get the n-th argument as f64, falling back to `default` if missing.
17705    fn arg_num(&self, args: &[Value], n: usize, default: f64) -> Result<f64, EvalErr> {
17706        match args.get(n) {
17707            Some(v) => self.to_number(v),
17708            None => Ok(default),
17709        }
17710    }
17711
17712    fn arg_str(&self, args: &[Value], n: usize, default: &str) -> String {
17713        args.get(n)
17714            .map(|v| v.to_string())
17715            .unwrap_or_else(|| default.to_string())
17716    }
17717
17718    /// Shared by `http_serve`/`http_serve_tls`/`http_serve_dev_tls`: drains
17719    /// requests off `rx` — blocking, on the interpreter's own thread — and
17720    /// dispatches each to whichever registered Ling route handler matches
17721    /// method+path, exactly the same regardless of whether the connection
17722    /// underneath is plain HTTP or TLS (that's all already decided by the
17723    /// `web::TlsMode` passed to `web::spawn_server` before this runs).
17724    #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
17725    fn drain_http_requests(
17726        &mut self,
17727        rx: std::sync::mpsc::Receiver<web::PendingRequest>,
17728        routes: Vec<(String, String, Value)>,
17729    ) -> Result<Value, EvalErr> {
17730        for pending in rx {
17731            let matched = routes.iter().find(|(m, p, _)| m == &pending.method && p == &pending.path);
17732            let response = match matched {
17733                Some((_, _, handler)) => {
17734                    let req_value = Value::Struct {
17735                        name: "Request".to_string(),
17736                        fields: vec![
17737                            ("method".to_string(), Value::Str(pending.method.clone())),
17738                            ("path".to_string(), Value::Str(pending.path.clone())),
17739                            ("query".to_string(), Value::Str(pending.query.clone())),
17740                            ("body".to_string(), Value::Str(pending.body.clone())),
17741                            ("cookie".to_string(), Value::Str(pending.cookie.clone())),
17742                            ("authorization".to_string(), Value::Str(pending.authorization.clone())),
17743                            ("client_ip".to_string(), Value::Str(pending.client_ip.clone())),
17744                        ],
17745                    };
17746                    match self.call_value(handler.clone(), vec![req_value]) {
17747                        Ok(v) => web::value_to_response(&v),
17748                        Err(e) => web::HttpResponse {
17749                            status: 500,
17750                            content_type: "text/plain; charset=utf-8".to_string(),
17751                            body: format!("handler error: {e:?}"),
17752                            set_cookie: None,
17753                            location: None,
17754                        },
17755                    }
17756                },
17757                None => web::HttpResponse {
17758                    status: 404,
17759                    content_type: "text/plain; charset=utf-8".to_string(),
17760                    body: "not found".to_string(),
17761                    set_cookie: None,
17762                    location: None,
17763                },
17764            };
17765            let _ = pending.respond_to.send(response);
17766        }
17767        Ok(Value::Unit)
17768    }
17769
17770    /// Read a list-of-strings argument as `Vec<String>` (empty if absent/not a list).
17771    /// Used by `run_process` for argv — never shell-interpreted, so no injection risk.
17772    fn arg_list_str(&self, args: &[Value], n: usize) -> Vec<String> {
17773        match args.get(n) {
17774            Some(Value::List(v)) => v.iter().map(|x| x.to_string()).collect(),
17775            _ => Vec::new(),
17776        }
17777    }
17778
17779    /// Read a list-of-numbers argument as `Vec<f32>` (empty if absent/not a list).
17780    #[allow(dead_code)]
17781    fn arg_list_f32(&self, args: &[Value], n: usize) -> Vec<f32> {
17782        match args.get(n) {
17783            Some(Value::List(v)) => v
17784                .iter()
17785                .filter_map(|x| match x {
17786                    Value::Number(n) => Some(*n as f32),
17787                    _ => None,
17788                })
17789                .collect(),
17790            _ => Vec::new(),
17791        }
17792    }
17793
17794    /// Optional `r,g,b` colour override starting at arg `i` → packed 0x00RRGGBB,
17795    /// or `default` if those three numeric args aren't present.
17796    #[cfg(not(target_arch = "wasm32"))]
17797    fn color_at(&self, args: &[Value], i: usize, default: u32) -> u32 {
17798        match (args.get(i), args.get(i + 1), args.get(i + 2)) {
17799            (Some(a), Some(b), Some(c)) => {
17800                match (self.to_number(a), self.to_number(b), self.to_number(c)) {
17801                    (Ok(r), Ok(g), Ok(bl)) => {
17802                        ((r as u32 & 0xFF) << 16) | ((g as u32 & 0xFF) << 8) | (bl as u32 & 0xFF)
17803                    },
17804                    _ => default,
17805                }
17806            },
17807            _ => default,
17808        }
17809    }
17810
17811    /// A pitch argument: a note-name string (`"C4"`, `"A#3"`) or a numeric MIDI value.
17812    #[cfg(not(target_arch = "wasm32"))]
17813    fn pitch_arg(&self, args: &[Value], i: usize, default: i32) -> i32 {
17814        match args.get(i) {
17815            Some(Value::Str(s)) => ling_music::note::parse_pitch(s).unwrap_or(default),
17816            Some(Value::Number(n)) => *n as i32,
17817            _ => default,
17818        }
17819    }
17820
17821    /// Current mouse position + left-button-down (native window only).
17822    #[cfg(not(target_arch = "wasm32"))]
17823    fn mouse_now(&self) -> (f32, f32, bool) {
17824        let gfx = self.gfx.borrow();
17825        let (mx, my) = gfx
17826            .window
17827            .as_ref()
17828            .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
17829            .unwrap_or((0.0, 0.0));
17830        let down = gfx
17831            .window
17832            .as_ref()
17833            .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
17834            .unwrap_or(false);
17835        (mx, my, down)
17836    }
17837
17838    /// Rasterize a UI [`ling_ui::widgets::Draw`] into the framebuffer: filled
17839    /// polygons via the AA scanline fill, polylines via AA lines, honouring the
17840    /// current blend mode.
17841    #[cfg(not(target_arch = "wasm32"))]
17842    fn draw_ui(&self, d: &ling_ui::widgets::Draw) {
17843        let mut gfx = self.gfx.borrow_mut();
17844        let (w, h, add) = (gfx.width, gfx.height, gfx.blend == 1);
17845        for (c, poly) in &d.fills {
17846            crate::gfx::raster::fill_contours_aa(
17847                &mut gfx.buffer,
17848                w,
17849                h,
17850                *c,
17851                add,
17852                std::slice::from_ref(poly),
17853            );
17854        }
17855        for (c, pl) in &d.strokes {
17856            for s in pl.windows(2) {
17857                crate::gfx::raster::draw_line_aa(
17858                    &mut gfx.buffer,
17859                    w,
17860                    h,
17861                    *c,
17862                    add,
17863                    s[0][0],
17864                    s[0][1],
17865                    s[1][0],
17866                    s[1][1],
17867                );
17868            }
17869        }
17870    }
17871
17872    /// Parse (dst_x, dst_y, width, height) from the first four args of a tex_* builtin.
17873    fn tex_rect(&self, args: &[Value]) -> Result<(usize, usize, usize, usize), EvalErr> {
17874        let tx = self.arg_num(args, 0, 0.0)? as usize;
17875        let ty = self.arg_num(args, 1, 0.0)? as usize;
17876        let tw = self.arg_num(args, 2, 256.0)? as usize;
17877        let th = self.arg_num(args, 3, 256.0)? as usize;
17878        Ok((tx, ty, tw.max(1), th.max(1)))
17879    }
17880
17881    pub(crate) fn apply_binop(&self, op: &BinOp, l: Value, r: Value) -> EvalResult {
17882        match op {
17883            BinOp::Add => match (l, r) {
17884                (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
17885                (Value::Str(a), Value::Str(b)) => Ok(Value::Str(a + &b)),
17886                (Value::Str(a), b) => Ok(Value::Str(a + &b.to_string())),
17887                (a, Value::Str(b)) => Ok(Value::Str(a.to_string() + &b)),
17888                (a, b) => Err(EvalErr::from(format!("cannot add {:?} and {:?}", a, b))),
17889            },
17890            BinOp::Sub => Ok(Value::Number(self.to_number(&l)? - self.to_number(&r)?)),
17891            BinOp::Mul => Ok(Value::Number(self.to_number(&l)? * self.to_number(&r)?)),
17892            BinOp::Div => Ok(Value::Number(self.to_number(&l)? / self.to_number(&r)?)),
17893            BinOp::Rem => Ok(Value::Number(self.to_number(&l)? % self.to_number(&r)?)),
17894            BinOp::Eq => Ok(Value::Bool(values_equal(&l, &r))),
17895            BinOp::Ne => Ok(Value::Bool(!values_equal(&l, &r))),
17896            BinOp::Lt => Ok(Value::Bool(self.to_number(&l)? < self.to_number(&r)?)),
17897            BinOp::Gt => Ok(Value::Bool(self.to_number(&l)? > self.to_number(&r)?)),
17898            BinOp::Le => Ok(Value::Bool(self.to_number(&l)? <= self.to_number(&r)?)),
17899            BinOp::Ge => Ok(Value::Bool(self.to_number(&l)? >= self.to_number(&r)?)),
17900            BinOp::And => Ok(Value::Bool(self.is_truthy(&l) && self.is_truthy(&r))),
17901            BinOp::Or => Ok(Value::Bool(self.is_truthy(&l) || self.is_truthy(&r))),
17902        }
17903    }
17904
17905    fn builtin_format(&self, args: &[Value]) -> Result<String, EvalErr> {
17906        if args.is_empty() {
17907            return Ok(String::new());
17908        }
17909        let fmt = match &args[0] {
17910            Value::Str(s) => s.clone(),
17911            other => return Ok(other.to_string()),
17912        };
17913
17914        let mut result = String::new();
17915        let mut arg_idx = 1usize;
17916        let mut chars = fmt.chars().peekable();
17917        while let Some(c) = chars.next() {
17918            if c == '{' {
17919                if chars.peek() == Some(&'}') {
17920                    chars.next();
17921                    if arg_idx < args.len() {
17922                        result.push_str(&args[arg_idx].to_string());
17923                        arg_idx += 1;
17924                    }
17925                } else {
17926                    let mut spec = String::new();
17927                    for ch in chars.by_ref() {
17928                        if ch == '}' {
17929                            break;
17930                        }
17931                        spec.push(ch);
17932                    }
17933                    if arg_idx < args.len() {
17934                        if let Some(suffix) = spec.strip_prefix(":.") {
17935                            if let Value::Number(n) = &args[arg_idx] {
17936                                let prec: usize = suffix.trim_end_matches('f').parse().unwrap_or(2);
17937                                result.push_str(&format!("{:.prec$}", n));
17938                                arg_idx += 1;
17939                                continue;
17940                            }
17941                        }
17942                        result.push_str(&args[arg_idx].to_string());
17943                        arg_idx += 1;
17944                    }
17945                }
17946            } else {
17947                result.push(c);
17948            }
17949        }
17950        Ok(result)
17951    }
17952}
17953
17954#[cfg(not(target_arch = "wasm32"))]
17955/// Map a friendly button name (any vendor / d-pad alias) to a gamepad button.
17956#[cfg(not(target_arch = "wasm32"))]
17957fn parse_pad_button(name: &str) -> Option<ling_input::GamepadButton> {
17958    use ling_input::GamepadButton as B;
17959    Some(match name.to_ascii_lowercase().as_str() {
17960        "a" | "south" | "cross" => B::South,
17961        "b" | "east" | "circle" => B::East,
17962        "x" | "west" | "square" => B::West,
17963        "y" | "north" | "triangle" => B::North,
17964        "lb" | "l1" | "left_shoulder" => B::LeftShoulder,
17965        "rb" | "r1" | "right_shoulder" => B::RightShoulder,
17966        "lt" | "l2" | "left_trigger" => B::LeftTrigger,
17967        "rt" | "r2" | "right_trigger" => B::RightTrigger,
17968        "start" | "menu" | "options" | "démarrer" | "начать" => B::Start,
17969        "select" | "back" | "share" | "view" => B::Select,
17970        "guide" | "home" => B::Guide,
17971        "l3" | "left_stick" => B::LeftStick,
17972        "r3" | "right_stick" => B::RightStick,
17973        "up" | "dpad_up" => B::DpadUp,
17974        "down" | "dpad_down" => B::DpadDown,
17975        "left" | "dpad_left" => B::DpadLeft,
17976        "right" | "dpad_right" => B::DpadRight,
17977        _ => return None,
17978    })
17979}
17980
17981#[cfg(not(target_arch = "wasm32"))]
17982fn str_to_minifb_key(name: &str) -> Option<minifb::Key> {
17983    use minifb::Key;
17984    Some(match name {
17985        "numpad0" | "kp0" => Key::NumPad0,
17986        "numpad1" | "kp1" => Key::NumPad1,
17987        "numpad2" | "kp2" => Key::NumPad2,
17988        "numpad3" | "kp3" => Key::NumPad3,
17989        "numpad4" | "kp4" => Key::NumPad4,
17990        "numpad5" | "kp5" => Key::NumPad5,
17991        "numpad6" | "kp6" => Key::NumPad6,
17992        "numpad7" | "kp7" => Key::NumPad7,
17993        "numpad8" | "kp8" => Key::NumPad8,
17994        "numpad9" | "kp9" => Key::NumPad9,
17995        "numpad+" | "kp+" => Key::NumPadPlus,
17996        "numpad-" | "kp-" => Key::NumPadMinus,
17997        "numpad*" | "kp*" => Key::NumPadAsterisk,
17998        "numpad/" | "kp/" => Key::NumPadSlash,
17999        "left" => Key::Left,
18000        "right" => Key::Right,
18001        "up" => Key::Up,
18002        "down" => Key::Down,
18003        "space" => Key::Space,
18004        "enter" => Key::Enter,
18005        "escape" => Key::Escape,
18006        "pageup" => Key::PageUp,
18007        "pagedown" => Key::PageDown,
18008        "lshift" | "leftshift" => Key::LeftShift,
18009        "rshift" | "rightshift" => Key::RightShift,
18010        "lctrl" | "leftctrl" => Key::LeftCtrl,
18011        "rctrl" | "rightctrl" => Key::RightCtrl,
18012        "lalt" | "leftalt" => Key::LeftAlt,
18013        "ralt" | "rightalt" => Key::RightAlt,
18014        "tab" => Key::Tab,
18015        "backspace" => Key::Backspace,
18016        "delete" => Key::Delete,
18017        "insert" => Key::Insert,
18018        "home" => Key::Home,
18019        "end" => Key::End,
18020        "a" => Key::A,
18021        "b" => Key::B,
18022        "c" => Key::C,
18023        "d" => Key::D,
18024        "e" => Key::E,
18025        "f" => Key::F,
18026        "g" => Key::G,
18027        "h" => Key::H,
18028        "i" => Key::I,
18029        "j" => Key::J,
18030        "k" => Key::K,
18031        "l" => Key::L,
18032        "m" => Key::M,
18033        "n" => Key::N,
18034        "o" => Key::O,
18035        "p" => Key::P,
18036        "q" => Key::Q,
18037        "r" => Key::R,
18038        "s" => Key::S,
18039        "t" => Key::T,
18040        "u" => Key::U,
18041        "v" => Key::V,
18042        "w" => Key::W,
18043        "x" => Key::X,
18044        "y" => Key::Y,
18045        "z" => Key::Z,
18046        "0" => Key::Key0,
18047        "1" => Key::Key1,
18048        "2" => Key::Key2,
18049        "3" => Key::Key3,
18050        "4" => Key::Key4,
18051        "5" => Key::Key5,
18052        "6" => Key::Key6,
18053        "7" => Key::Key7,
18054        "8" => Key::Key8,
18055        "9" => Key::Key9,
18056        _ => return None,
18057    })
18058}
18059
18060pub(crate) fn values_equal(a: &Value, b: &Value) -> bool {
18061    match (a, b) {
18062        (Value::Number(x), Value::Number(y)) => (x - y).abs() < 1e-12,
18063        (Value::Str(x), Value::Str(y)) => x == y,
18064        (Value::Bool(x), Value::Bool(y)) => x == y,
18065        (Value::Unit, Value::Unit) => true,
18066        _ => false,
18067    }
18068}
18069
18070// Rasteriser functions live in crate::gfx::raster — imported at top of file.
18071
18072// ── Window platform helpers ────────────────────────────────────────────────────
18073
18074/// Strip *all* window chrome from `hwnd` and make it cover the whole primary
18075/// monitor (0,0 → screen_w × screen_h), above the taskbar. This turns the
18076/// minifb window into a true borderless-fullscreen surface: no title bar, no
18077/// frame, no resize grips — there is no visible window "handle" left.
18078#[cfg(all(not(target_arch = "wasm32"), windows))]
18079fn make_borderless_fullscreen(hwnd: isize, screen_w: i32, screen_h: i32) {
18080    if hwnd == 0 {
18081        return;
18082    }
18083    unsafe {
18084        extern "system" {
18085            fn SetWindowLongPtrW(hwnd: isize, index: i32, new: isize) -> isize;
18086            fn SetWindowPos(
18087                hwnd: isize,
18088                insert_after: isize,
18089                x: i32,
18090                y: i32,
18091                cx: i32,
18092                cy: i32,
18093                flags: u32,
18094            ) -> i32;
18095            fn ShowWindow(hwnd: isize, cmd: i32) -> i32;
18096        }
18097        const GWL_STYLE: i32 = -16;
18098        const GWL_EXSTYLE: i32 = -20;
18099        // WS_POPUP (0x80000000) | WS_VISIBLE (0x10000000) — a bare top-level
18100        // window with no caption, border, or system menu.
18101        SetWindowLongPtrW(hwnd, GWL_STYLE, 0x9000_0000isize);
18102        // Clear extended edges (WS_EX_WINDOWEDGE / CLIENTEDGE / DLGMODALFRAME).
18103        SetWindowLongPtrW(hwnd, GWL_EXSTYLE, 0);
18104        // HWND_TOPMOST = -1; SWP_FRAMECHANGED (0x0020) | SWP_SHOWWINDOW (0x0040).
18105        SetWindowPos(hwnd, -1isize, 0, 0, screen_w, screen_h, 0x0020 | 0x0040);
18106        ShowWindow(hwnd, 3); // SW_MAXIMIZE-equivalent paint; 3 = SW_SHOWMAXIMIZED
18107    }
18108}
18109
18110/// Force real OS keyboard focus onto `hwnd`, not just Z-order prominence.
18111/// Windows' foreground-lock can leave a freshly-created window topmost — so
18112/// VISUALLY it covers everything — without actually handing it keyboard
18113/// focus, e.g. when launched from a terminal that still holds real focus:
18114/// clicks can nudge focus over (a more "user-driven" event) but typed keys
18115/// silently keep going to whatever app really has it, which looks exactly
18116/// like "clicking a text field doesn't focus it". AttachThreadInput is the
18117/// standard documented workaround — it lets SetForegroundWindow succeed even
18118/// under the lock by sharing input state with whichever thread currently
18119/// owns the foreground window. Call this LAST, after every other
18120/// window-visibility change for this launch (anything that shows/hides a
18121/// window afterward — e.g. hiding the launching console — can itself
18122/// reassign the foreground window and undo an earlier focus claim).
18123#[cfg(all(not(target_arch = "wasm32"), windows))]
18124fn force_window_focus(hwnd: isize) {
18125    if hwnd == 0 {
18126        return;
18127    }
18128    unsafe {
18129        extern "system" {
18130            fn GetForegroundWindow() -> isize;
18131            fn GetWindowThreadProcessId(hwnd: isize, pid: *mut u32) -> u32;
18132            fn GetCurrentThreadId() -> u32;
18133            fn AttachThreadInput(id_attach: u32, id_attach_to: u32, attach: i32) -> i32;
18134            fn SetForegroundWindow(hwnd: isize) -> i32;
18135            fn BringWindowToTop(hwnd: isize) -> i32;
18136            fn SetFocus(hwnd: isize) -> isize;
18137            fn SetActiveWindow(hwnd: isize) -> isize;
18138        }
18139        let fg = GetForegroundWindow();
18140        if fg != 0 && fg != hwnd {
18141            let mut fg_pid: u32 = 0;
18142            let fg_tid = GetWindowThreadProcessId(fg, &mut fg_pid);
18143            let my_tid = GetCurrentThreadId();
18144            if fg_tid != 0 && fg_tid != my_tid {
18145                AttachThreadInput(my_tid, fg_tid, 1);
18146                SetForegroundWindow(hwnd);
18147                BringWindowToTop(hwnd);
18148                SetFocus(hwnd);
18149                SetActiveWindow(hwnd);
18150                AttachThreadInput(my_tid, fg_tid, 0);
18151                return;
18152            }
18153        }
18154        SetForegroundWindow(hwnd);
18155        BringWindowToTop(hwnd);
18156        SetFocus(hwnd);
18157        SetActiveWindow(hwnd);
18158    }
18159}
18160
18161/// Toggle `hwnd`'s HWND_TOPMOST z-order style without moving/resizing/
18162/// activating it — used to drop the borderless-fullscreen window's topmost
18163/// flag on alt-tab (so it stops covering whatever the user switched to) and
18164/// restore it when the user switches back.
18165#[cfg(all(not(target_arch = "wasm32"), windows))]
18166fn set_window_topmost(hwnd: isize, topmost: bool) {
18167    if hwnd == 0 {
18168        return;
18169    }
18170    unsafe {
18171        extern "system" {
18172            fn SetWindowPos(
18173                hwnd: isize,
18174                insert_after: isize,
18175                x: i32,
18176                y: i32,
18177                cx: i32,
18178                cy: i32,
18179                flags: u32,
18180            ) -> i32;
18181        }
18182        let insert_after: isize = if topmost { -1 } else { -2 }; // HWND_TOPMOST / HWND_NOTOPMOST
18183                                                                 // SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE — pure z-order change,
18184                                                                 // must not steal focus back when restoring topmost on refocus.
18185        SetWindowPos(hwnd, insert_after, 0, 0, 0, 0, 0x0002 | 0x0001 | 0x0010);
18186    }
18187}
18188
18189/// Pace `win` to `vsync`'s target rate. `LING_FPS_CAP` (0 = uncapped, else an
18190/// explicit fps) always overrides; otherwise vsync-on paces to the monitor's
18191/// real refresh rate and vsync-off runs uncapped. minifb has no swap-interval
18192/// vsync (it owns no GPU present queue), so this is frame-rate pacing to the
18193/// refresh rate, not a tear-free guarantee.
18194#[cfg(not(target_arch = "wasm32"))]
18195fn apply_frame_pacing(win: &mut minifb::Window, vsync: bool) {
18196    match std::env::var("LING_FPS_CAP")
18197        .ok()
18198        .and_then(|v| v.parse::<usize>().ok())
18199    {
18200        Some(0) => win.set_target_fps(100_000),
18201        Some(cap) => win.set_target_fps(cap),
18202        None if vsync => win.set_target_fps(monitor_info().2.max(30) as usize),
18203        None => win.set_target_fps(100_000),
18204    }
18205}
18206
18207/// Primary-monitor resolution and refresh rate as `(width, height, hz)`.
18208/// `hz` falls back to 60 when the driver reports an unknown/`default` rate.
18209#[cfg(all(not(target_arch = "wasm32"), windows))]
18210fn monitor_info() -> (i32, i32, i32) {
18211    unsafe {
18212        extern "system" {
18213            fn GetSystemMetrics(index: i32) -> i32;
18214            fn GetDC(hwnd: isize) -> isize;
18215            fn ReleaseDC(hwnd: isize, hdc: isize) -> i32;
18216            fn GetDeviceCaps(hdc: isize, index: i32) -> i32;
18217        }
18218        let w = GetSystemMetrics(0).max(1); // SM_CXSCREEN
18219        let h = GetSystemMetrics(1).max(1); // SM_CYSCREEN
18220        let hdc = GetDC(0);
18221        let mut hz = if hdc != 0 { GetDeviceCaps(hdc, 116) } else { 0 }; // VREFRESH
18222        if hdc != 0 {
18223            ReleaseDC(0, hdc);
18224        }
18225        if hz <= 1 {
18226            hz = 60; // 0 or 1 means "device default" → assume 60 Hz
18227        }
18228        (w, h, hz)
18229    }
18230}
18231
18232/// Non-Windows native fallback: resolution from [`native_screen_size`]; refresh
18233/// from the active X11/RandR mode (so a 144 Hz panel drives the loop at 144),
18234/// falling back to 60 Hz when it can't be detected (Wayland, headless, macOS).
18235#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
18236fn monitor_info() -> (i32, i32, i32) {
18237    let (w, h) = native_screen_size();
18238    (w as i32, h as i32, linux_refresh_hz().unwrap_or(60))
18239}
18240
18241/// Active display refresh rate via `xrandr`. Each connected output's active
18242/// mode is the token flagged with `*` (e.g. `1920x1080 144.00*+`); we take the
18243/// max across all active outputs so a multi-monitor rig drives the loop at
18244/// its fastest panel.
18245#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
18246fn linux_refresh_hz() -> Option<i32> {
18247    let out = std::process::Command::new("xrandr")
18248        .arg("--current")
18249        .output()
18250        .ok()?;
18251    if !out.status.success() {
18252        return None;
18253    }
18254    parse_xrandr_max_hz(&String::from_utf8_lossy(&out.stdout))
18255}
18256
18257/// Pure parse used by [`linux_refresh_hz`]: the highest `*`-flagged refresh
18258/// rate across all active outputs in `xrandr --current` output.
18259#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
18260fn parse_xrandr_max_hz(text: &str) -> Option<i32> {
18261    text.split_whitespace()
18262        .filter(|tok| tok.contains('*'))
18263        .filter_map(|tok| {
18264            tok.trim_matches(|c: char| !c.is_ascii_digit() && c != '.')
18265                .parse::<f64>()
18266                .ok()
18267        })
18268        .map(|hz| hz.round() as i32)
18269        .filter(|&hz| (24..=1000).contains(&hz))
18270        .max()
18271}
18272
18273/// WASM fallback: the canvas is the display surface; assume 60 Hz.
18274#[cfg(target_arch = "wasm32")]
18275fn monitor_info() -> (i32, i32, i32) {
18276    let (w, h) = crate::gfx::webgl::canvas_size();
18277    (w as i32, h as i32, 60)
18278}
18279
18280#[cfg(all(test, not(target_arch = "wasm32"), not(windows)))]
18281mod xrandr_tests {
18282    use super::parse_xrandr_max_hz;
18283
18284    #[test]
18285    fn picks_highest_active_output() {
18286        let text = "\
18287eDP-1 connected primary 1920x1080+0+0
18288   1920x1080     60.00*+  59.94
18289DP-1 connected 2560x1440+1920+0
18290   2560x1440    144.00*+  120.00  60.00
18291";
18292        assert_eq!(parse_xrandr_max_hz(text), Some(144));
18293    }
18294
18295    #[test]
18296    fn single_output() {
18297        let text = "   1920x1080     75.00*+  60.00\n";
18298        assert_eq!(parse_xrandr_max_hz(text), Some(75));
18299    }
18300
18301    #[test]
18302    fn no_active_mode_returns_none() {
18303        let text = "eDP-1 disconnected\n";
18304        assert_eq!(parse_xrandr_max_hz(text), None);
18305    }
18306
18307    #[test]
18308    fn out_of_range_hz_filtered() {
18309        let text = "   1x1     5000.00*+\n";
18310        assert_eq!(parse_xrandr_max_hz(text), None);
18311    }
18312}
18313
18314/// Query the primary display resolution on non-Windows platforms.
18315/// Falls back to 1920×1080 if the size cannot be determined.
18316#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
18317fn native_screen_size() -> (f64, f64) {
18318    // On Linux/macOS we don't have an easy dependency-free call; return a
18319    // sensible default. Callers can always pass explicit dimensions.
18320    (1920.0, 1080.0)
18321}
18322
18323// ════════════════════════════════════════════════════════════════════════════
18324// Builtin call profiler  (env-gated, near-zero cost when off)
18325//
18326//   LING_PROFILE=1            enable per-builtin call-count + inclusive-time tally
18327//   LING_PROFILE_EVERY=N      print the report every N frames (default 240)
18328//
18329// Every builtin call funnels through `Interp::call_named` (JIT via `ling_builtin`,
18330// tree-walker directly), so this captures the full render/physics/audio builtin
18331// hot-path. Report is sorted by total time and prints calls, calls/frame,
18332// total_ms and ms/frame — the top-down "what's making so many calls" view.
18333// ════════════════════════════════════════════════════════════════════════════
18334struct LingProfileState {
18335    enabled: bool,
18336    every: u64,
18337    frames: u64,
18338    calls: std::collections::HashMap<String, (u64, u128)>, // name -> (count, nanos)
18339}
18340
18341thread_local! {
18342    static LING_PROFILE: std::cell::RefCell<LingProfileState> = std::cell::RefCell::new({
18343        let enabled = std::env::var("LING_PROFILE").map(|v| v != "0" && !v.is_empty()).unwrap_or(false);
18344        let every = std::env::var("LING_PROFILE_EVERY").ok()
18345            .and_then(|v| v.parse::<u64>().ok()).filter(|&n| n > 0).unwrap_or(240);
18346        if enabled {
18347            eprintln!("[ling-profile] ON — report every {every} frames (set LING_PROFILE_EVERY to change)");
18348        }
18349        LingProfileState { enabled, every, frames: 0, calls: std::collections::HashMap::new() }
18350    });
18351}
18352
18353#[inline]
18354fn ling_profile_enabled() -> bool {
18355    LING_PROFILE.with(|p| p.borrow().enabled)
18356}
18357
18358thread_local! {
18359    static LING_FPS: std::cell::RefCell<(bool, f64, u32, f64)> = std::cell::RefCell::new(
18360        (std::env::var("LING_FPS").map(|v| v != "0" && !v.is_empty()).unwrap_or(false), 0.0, 0, 0.0)
18361    );
18362}
18363
18364#[cfg(not(target_arch = "wasm32"))]
18365fn ling_fps_tick() {
18366    LING_FPS.with(|s| {
18367        let mut s = s.borrow_mut();
18368        if !s.0 {
18369            return;
18370        }
18371        let now = crate::runtime::now_secs();
18372        if s.1 > 0.0 {
18373            s.3 += now - s.1;
18374            s.2 += 1;
18375            if s.2 >= 120 {
18376                let avg = s.3 / s.2 as f64;
18377                eprintln!(
18378                    "[fps] {:.1} fps  ({:.2} ms/frame, wall, {} frames)",
18379                    1.0 / avg,
18380                    avg * 1000.0,
18381                    s.2
18382                );
18383                s.2 = 0;
18384                s.3 = 0.0;
18385            }
18386        }
18387        s.1 = now;
18388    });
18389}
18390
18391// Coarse render-pipeline timers (set LING_PHASE=1). Each accumulates wall-time
18392// per frame at flush/present granularity, so the cost is negligible. Reports the
18393// software-rasteriser breakdown the builtin profiler can't separate (the work is
18394// all inside the `present`/`flush_3d` builtins).
18395thread_local! {
18396    static LING_PHASE: std::cell::RefCell<(bool, u64, [u128; 5])> = std::cell::RefCell::new(
18397        (std::env::var_os("LING_PHASE").is_some(), 0, [0; 5])
18398    );
18399}
18400
18401/// Phase indices for [`ling_phase_add`].
18402pub mod phase {
18403    pub const FLUSH: usize = 0;
18404    pub const TOON: usize = 1;
18405    pub const BLIT: usize = 2;
18406    pub const DISTORT: usize = 3;
18407    pub const SORT: usize = 4;
18408}
18409
18410#[cfg(not(target_arch = "wasm32"))]
18411#[inline]
18412pub fn ling_phase_add(idx: usize, nanos: u128) {
18413    LING_PHASE.with(|p| {
18414        let mut p = p.borrow_mut();
18415        if p.0 {
18416            p.2[idx] += nanos;
18417        }
18418    });
18419}
18420
18421#[cfg(not(target_arch = "wasm32"))]
18422fn ling_phase_frame() {
18423    LING_PHASE.with(|p| {
18424        let mut p = p.borrow_mut();
18425        if !p.0 {
18426            return;
18427        }
18428        p.1 += 1;
18429        if p.1 >= 120 {
18430            let f = p.1 as f64;
18431            let ms = |i: usize| p.2[i] as f64 / 1e6 / f;
18432            eprintln!(
18433                "[phase] sort={:.2} flush={:.2} toon={:.2} blit={:.2} distort={:.2} ms/frame",
18434                ms(phase::SORT),
18435                ms(phase::FLUSH),
18436                ms(phase::TOON),
18437                ms(phase::BLIT),
18438                ms(phase::DISTORT)
18439            );
18440            p.1 = 0;
18441            p.2 = [0; 5];
18442        }
18443    });
18444}
18445
18446fn ling_profile_record(name: &str, nanos: u128) {
18447    // Frame boundary = a present() call.
18448    let is_frame = matches!(
18449        name,
18450        "present" | "แสดงผล" | "gfx_present" | "show" | "显" | "呈现" | "表示" | "표시"
18451    );
18452    LING_PROFILE.with(|p| {
18453        let mut p = p.borrow_mut();
18454        let e = p.calls.entry(name.to_string()).or_insert((0, 0));
18455        e.0 += 1;
18456        e.1 += nanos;
18457        if is_frame {
18458            p.frames += 1;
18459            if p.frames % p.every == 0 {
18460                ling_profile_print(&p);
18461            }
18462        }
18463    });
18464}
18465
18466fn ling_profile_print(p: &LingProfileState) {
18467    let mut rows: Vec<(&String, u64, u128)> =
18468        p.calls.iter().map(|(n, (c, ns))| (n, *c, *ns)).collect();
18469    use std::cmp::Reverse;
18470    rows.sort_by_key(|x| Reverse(x.2)); // by total time desc
18471    let total_ns: u128 = p.calls.values().map(|(_, ns)| *ns).sum();
18472    let total_calls: u64 = p.calls.values().map(|(c, _)| *c).sum();
18473    let fr = p.frames.max(1) as f64;
18474    eprintln!(
18475        "\n┌─ LING PROFILE ── frames={} ─ builtin calls by total inclusive time ─────────────",
18476        p.frames
18477    );
18478    eprintln!(
18479        "│ {:<24} {:>9} {:>9} {:>10} {:>9} {:>6}",
18480        "builtin", "calls", "calls/fr", "total_ms", "ms/frame", "%time"
18481    );
18482    eprintln!("├──────────────────────────────────────────────────────────────────────────────");
18483    for (name, count, ns) in rows.iter().take(30) {
18484        let ms = *ns as f64 / 1e6;
18485        let pct = if total_ns > 0 {
18486            *ns as f64 / total_ns as f64 * 100.0
18487        } else {
18488            0.0
18489        };
18490        eprintln!(
18491            "│ {:<24} {:>9} {:>9.1} {:>10.1} {:>9.3} {:>5.1}%",
18492            truncate_name(name),
18493            count,
18494            *count as f64 / fr,
18495            ms,
18496            ms / fr,
18497            pct
18498        );
18499    }
18500    eprintln!("├──────────────────────────────────────────────────────────────────────────────");
18501    eprintln!(
18502        "│ TOTAL {} builtin calls, {:.1} ms over {} frames  →  {:.0} calls/frame, {:.2} ms/frame in builtins",
18503        total_calls,
18504        total_ns as f64 / 1e6,
18505        p.frames,
18506        total_calls as f64 / fr,
18507        total_ns as f64 / 1e6 / fr
18508    );
18509    eprintln!("└──────────────────────────────────────────────────────────────────────────────");
18510}
18511
18512/// Trim a builtin name to fit the report column (counts chars, good enough for
18513/// the mixed-script names).
18514fn truncate_name(s: &str) -> String {
18515    let max = 24;
18516    if s.chars().count() <= max {
18517        s.to_string()
18518    } else {
18519        let mut t: String = s.chars().take(max - 1).collect();
18520        t.push('…');
18521        t
18522    }
18523}