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(all(not(target_arch = "wasm32"), feature = "web"))]
156pub mod web;
157use crate::gfx::{GfxState, Light};
158use crate::parser::ast::*;
159#[cfg(target_arch = "wasm32")]
160use js_sys;
161use std::cell::RefCell;
162use std::collections::HashMap;
163use std::rc::Rc;
164// `raster` is wasm-safe (pure CPU framebuffer), so `draw_line` is available on
165// web too; `fill_triangle` is only reached from native-gated 3-D fill paths.
166use crate::gfx::raster::draw_line;
167#[cfg(not(target_arch = "wasm32"))]
168use crate::gfx::raster::fill_triangle;
169#[cfg(not(target_arch = "wasm32"))]
170use ling_audio::{AudioEngine, ToneParams, Wave};
171
172#[cfg(not(target_arch = "wasm32"))]
173use ling_audio::FftAnalyzer;
174
175#[cfg(not(target_arch = "wasm32"))]
176use ling_mic;
177
178// ─── Values ──────────────────────────────────────────────────────────────────
179
180#[derive(Debug, Clone)]
181pub enum Value {
182    Str(String),
183    Number(f64),
184    Bool(bool),
185    Unit,
186    List(Rc<Vec<Value>>),
187    Ok(Box<Value>),
188    Err(Box<Value>),
189    Fn(Vec<String>, Vec<Stmt>, Env),
190    /// `form` record instance — ordered named fields.
191    Struct {
192        name: String,
193        fields: Vec<(String, Value)>,
194    },
195    /// `choose` enum instance — variant tag plus ordered payload.
196    Variant {
197        enum_name: String,
198        variant: String,
199        payload: Vec<Value>,
200    },
201}
202
203// Interpreter-hot maps use a fast non-crypto hasher: short Thai identifier keys
204// are hashed on every variable access and builtin dispatch, where SipHash dominates.
205use rustc_hash::FxHashMap;
206type Env = FxHashMap<String, Value>;
207
208#[inline]
209fn new_env() -> Env {
210    FxHashMap::default()
211}
212
213impl std::fmt::Display for Value {
214    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215        match self {
216            Value::Str(s) => write!(f, "{s}"),
217            Value::Number(n) => {
218                if n.fract() == 0.0 && n.abs() < 1e15 {
219                    write!(f, "{}", *n as i64)
220                } else {
221                    write!(f, "{n}")
222                }
223            },
224            Value::Bool(b) => write!(f, "{b}"),
225            Value::Unit => write!(f, "()"),
226            Value::List(v) => {
227                write!(f, "[")?;
228                for (i, x) in v.iter().enumerate() {
229                    if i > 0 {
230                        write!(f, ", ")?;
231                    }
232                    write!(f, "{x}")?;
233                }
234                write!(f, "]")
235            },
236            Value::Ok(v) => write!(f, "Ok({v})"),
237            Value::Err(v) => write!(f, "Err({v})"),
238            Value::Fn(_, _, _) => write!(f, "<fn>"),
239            Value::Struct { name, fields } => {
240                write!(f, "{name} {{ ")?;
241                for (i, (k, v)) in fields.iter().enumerate() {
242                    if i > 0 {
243                        write!(f, ", ")?;
244                    }
245                    write!(f, "{k}: {v}")?;
246                }
247                write!(f, " }}")
248            },
249            Value::Variant { variant, payload, .. } => {
250                write!(f, "{variant}")?;
251                if !payload.is_empty() {
252                    write!(f, "(")?;
253                    for (i, v) in payload.iter().enumerate() {
254                        if i > 0 {
255                            write!(f, ", ")?;
256                        }
257                        write!(f, "{v}")?;
258                    }
259                    write!(f, ")")?;
260                }
261                Ok(())
262            },
263        }
264    }
265}
266
267#[cfg(target_arch = "wasm32")]
268fn wasm_unsupported_builtin(name: &str) -> Option<Value> {
269    Some(match name {
270        // interface blips (native-only today)
271        "audio_blip"
272        | "提示音"
273        | "ビープ音"
274        | "효과음"
275        | "เสียงบี๊บ"
276        | "ui_sound"
277        | "界面音"
278        | "UI音"
279        | "인터페이스음"
280        | "เสียงปุ่ม"
281        | "audio_stop_sfx"
282        | "停止音效"
283        | "効果音停止"
284        | "효과음정지"
285        | "หยุดเอฟเฟกต์ทั้งหมด"
286        | "بوق_کوتاه"
287        | "نغمة_قصيرة"
288        | "ביפ"
289        | "بلپ_آواز" => Value::Unit,
290
291        // music loading / analysis / playback / midi (native-only today)
292        "music_load"
293        | "载入音乐"
294        | "音楽読込"
295        | "음악로드"
296        | "โหลดเพลง"
297        | "music_patch"
298        | "乐器音色"
299        | "音色読込"
300        | "악기패치"
301        | "แพตช์เครื่องดนตรี"
302        | "music_lrc"
303        | "载入歌词"
304        | "歌詞読込"
305        | "가사로드"
306        | "โหลดเนื้อเพลง"
307        | "music_midi_load"
308        | "载入MIDI"
309        | "MIDI読込"
310        | "미디로드"
311        | "โหลดมิดี"
312        | "بارگذاری_موسیقی"
313        | "تحميل_الموسيقى"
314        | "טעינת_מוזיקה"
315        | "موسیقی_لوڈ" => Value::Number(-1.0),
316
317        "music_duration"
318        | "音乐时长"
319        | "音楽長さ"
320        | "음악길이"
321        | "ความยาวเพลง"
322        | "music_bpm"
323        | "节拍速度"
324        | "テンポ"
325        | "템포"
326        | "จังหวะต่อนาที"
327        | "music_pos"
328        | "音乐位置"
329        | "音楽位置"
330        | "음악위치"
331        | "ตำแหน่งเพลง"
332        | "music_mic_pitch"
333        | "麦克风音高"
334        | "マイク音程"
335        | "마이크음정"
336        | "ระดับเสียงไมค์"
337        | "music_hz"
338        | "音符频率"
339        | "音符周波数"
340        | "음표주파수"
341        | "ความถี่โน้ต"
342        | "music_pitch_score"
343        | "音准评分"
344        | "音程スコア"
345        | "음정점수"
346        | "คะแนนเสียง"
347        | "music_judge"
348        | "判定"
349        | "判定する"
350        | "판정"
351        | "ตัดสินจังหวะ"
352        | "music_midi_count"
353        | "MIDI数量"
354        | "MIDI数"
355        | "미디수"
356        | "จำนวนมิดี"
357        | "مدت_موسیقی"
358        | "مدة_الموسيقى"
359        | "משך_מוזיקה"
360        | "موسیقی_دورانیہ" => Value::Number(0.0),
361
362        "music_key"
363        | "调性"
364        | "調性"
365        | "조성"
366        | "คีย์เพลง"
367        | "music_lyric"
368        | "当前歌词"
369        | "現在歌詞"
370        | "현재가사"
371        | "เนื้อเพลงปัจจุบัน"
372        | "music_note_name"
373        | "音名"
374        | "音名称"
375        | "음이름"
376        | "ชื่อโน้ต"
377        | "music_grade_name"
378        | "判定名"
379        | "判定名称"
380        | "판정이름"
381        | "ชื่อการตัดสิน"
382        | "گام_موسیقی"
383        | "مقام_الموسيقى"
384        | "סולם_מוזיקלי"
385        | "موسیقی_کلید" => Value::Str(String::new()),
386
387        "music_onsets"
388        | "音符起点"
389        | "オンセット"
390        | "온셋"
391        | "จุดเริ่มเสียง"
392        | "music_beat_grid"
393        | "节拍网格"
394        | "ビートグリッド"
395        | "비트그리드"
396        | "กริดจังหวะ"
397        | "music_midi_notes"
398        | "MIDI音符"
399        | "MIDIノート"
400        | "미디음표"
401        | "โน้ตมิดี"
402        | "music_midi_bars"
403        | "MIDI音条"
404        | "MIDIバー"
405        | "미디바"
406        | "แท่งมิดี"
407        | "music_fft"
408        | "音乐频谱"
409        | "音楽スペクトル"
410        | "음악스펙트럼"
411        | "สเปกตรัมเพลง"
412        | "آغازهای_نت"
413        | "بدايات_النغمات"
414        | "התחלות_תווים"
415        | "نوٹ_شروعات" => Value::List(Vec::new().into()),
416
417        "music_play"
418        | "播放音乐"
419        | "音楽再生"
420        | "음악재생"
421        | "เล่นเพลง"
422        | "music_pause"
423        | "暂停音乐"
424        | "音楽一時停止"
425        | "음악일시정지"
426        | "หยุดเพลงชั่วคราว"
427        | "music_stop"
428        | "停止音乐"
429        | "音楽停止"
430        | "음악정지"
431        | "หยุดเพลง"
432        | "music_seek"
433        | "定位音乐"
434        | "音楽シーク"
435        | "음악탐색"
436        | "ค้นหาเพลง"
437        | "music_volume"
438        | "音乐音量"
439        | "音楽音量"
440        | "음악음량"
441        | "ระดับเพลง"
442        | "music_note"
443        | "弹音符"
444        | "音符演奏"
445        | "음표연주"
446        | "เล่นโน้ต"
447        | "music_note_on"
448        | "音符开始"
449        | "音符オン"
450        | "음표켜기"
451        | "โน้ตเริ่ม"
452        | "music_note_off"
453        | "音符结束"
454        | "音符オフ"
455        | "음표끄기"
456        | "โน้ตจบ"
457        | "پخش_موسیقی"
458        | "شغّل_الموسيقى"
459        | "נגן_מוזיקה"
460        | "موسیقی_چلاؤ" => Value::Unit,
461
462        // liquid sim — return handle 0 for new, Unit for everything else
463        "liquid_new"
464        | "新建液体"
465        | "液体新規"
466        | "액체생성"
467        | "สร้างของเหลว"
468        | "مایع_جدید"
469        | "سائل_جديد"
470        | "נוזל_חדש"
471        | "نیا_مائع" => Value::Number(0.0),
472        "liquid_mix"
473        | "液体混合"
474        | "液体混合度"
475        | "액체혼합"
476        | "การผสมของเหลว"
477        | "ترکیب_مایع"
478        | "مزج_سائل"
479        | "ערבוב_נוזל"
480        | "مائع_ملاؤ" => Value::Number(0.0),
481        "liquid_set_colors"
482        | "液体颜色"
483        | "液体配色"
484        | "액체색상"
485        | "สีของเหลว"
486        | "liquid_splat"
487        | "液体注入"
488        | "液体追加"
489        | "액체분사"
490        | "หยดของเหลว"
491        | "liquid_gravity"
492        | "液体重力"
493        | "液体重力ベクトル"
494        | "액체중력"
495        | "แรงโน้มถ่วงเหลว"
496        | "liquid_step"
497        | "液体步进"
498        | "液体更新"
499        | "액체스텝"
500        | "ก้าวของเหลว"
501        | "liquid_step_all"
502        | "液体全步进"
503        | "液体全更新"
504        | "전체액체스텝"
505        | "ก้าวของเหลวทั้งหมด"
506        | "liquid_rainbow"
507        | "液体彩虹"
508        | "液体虹"
509        | "액체무지개"
510        | "ของเหลวสายรุ้ง"
511        | "liquid_draw"
512        | "绘制液体"
513        | "液体描画"
514        | "액체그리기"
515        | "วาดของเหลว"
516        | "liquid_draw_surface"
517        | "液体贴面"
518        | "液体曲面"
519        | "액체곡면"
520        | "ของเหลวบนพื้นผิว"
521        | "تنظیم_رنگ_مایع"
522        | "عيّن_ألوان_السائل"
523        | "קבע_צבעי_נוזל"
524        | "مائع_رنگ_مقرر_کرو" => Value::Unit,
525
526        // ── game AI: neural networks ─────────────────────────────────────────
527        "nn_new"
528        | "建神经网"
529        | "ニューラル作成"
530        | "신경망생성"
531        | "สร้างโครงข่าย"
532        | "nn_load"
533        | "载入网"
534        | "網読込"
535        | "신경망불러오기"
536        | "โหลดโครงข่าย"
537        | "شبکه_جدید"
538        | "شبكة_جديدة"
539        | "רשת_חדשה"
540        | "نئی_نیورل_نیٹ" => Value::Number(-1.0),
541        "nn_forward"
542        | "神经前向"
543        | "順伝播"
544        | "순전파"
545        | "ส่งต่อโครงข่าย"
546        | "پیش‌روی_شبکه"
547        | "تمرير_أمامي"
548        | "העברה_קדימה"
549        | "فارورڈ_پاس" => Value::List(Vec::new().into()),
550        "nn_train"
551        | "训练网"
552        | "ニューラル学習"
553        | "신경망학습"
554        | "ฝึกโครงข่าย"
555        | "nn_dense"
556        | "密集层"
557        | "密層追加"
558        | "밀집층"
559        | "ชั้นหนาแน่น"
560        | "آموزش_شبکه"
561        | "درّب_الشبكة"
562        | "אמן_רשת"
563        | "نیٹ_ٹریننگ" => Value::Number(0.0),
564        "nn_save"
565        | "保存网"
566        | "網保存"
567        | "신경망저장"
568        | "บันทึกโครงข่าย"
569        | "ذخیره_شبکه"
570        | "احفظ_الشبكة"
571        | "שמור_רשת"
572        | "نیٹ_محفوظ_کرو" => Value::Bool(false),
573
574        // ── game AI: behavior trees ─────────────────────────────────────────
575        "bt_build"
576        | "建行为树"
577        | "行動木構築"
578        | "행동트리구성"
579        | "สร้างต้นไม้พฤติกรรม"
580        | "ساخت_درخت_رفتار"
581        | "ابنِ_شجرة_السلوك"
582        | "בנה_עץ_התנהגות"
583        | "بی_ٹی_تعمیر" => Value::Number(-1.0),
584        "bt_tick"
585        | "行为树滴答"
586        | "行動木更新"
587        | "행동트리틱"
588        | "เดินต้นไม้พฤติกรรม"
589        | "تیک_درخت_رفتار"
590        | "نبضة_شجرة_السلوك"
591        | "טיק_עץ_התנהגות"
592        | "بی_ٹی_ٹک" => Value::Str(String::new()),
593        "bt_status"
594        | "行为树状态"
595        | "行動木状態"
596        | "행동트리상태"
597        | "สถานะต้นไม้พฤติกรรม"
598        | "وضعیت_درخت_رفتار"
599        | "حالة_شجرة_السلوك"
600        | "סטטוס_עץ_התנהגות"
601        | "بی_ٹی_حالت" => Value::Number(0.0),
602        "bt_set"
603        | "设事实"
604        | "事実設定"
605        | "사실설정"
606        | "ตั้งข้อเท็จจริง"
607        | "تنظیم_واقعیت"
608        | "عيّن_حقيقة"
609        | "קבע_עובדה"
610        | "بی_ٹی_سیٹ" => Value::Unit,
611
612        // ── game AI: dialog LLM ─────────────────────────────────────────────
613        "dialog_new"
614        | "建对话模型"
615        | "対話モデル作成"
616        | "대화모델생성"
617        | "สร้างโมเดลสนทนา"
618        | "dialog_load_model"
619        | "对话载模"
620        | "対話モデル読込"
621        | "대화모델불러오기"
622        | "โหลดโมเดลสนทนา"
623        | "dialog_train"
624        | "对话训练"
625        | "対話訓練"
626        | "대화훈련"
627        | "ฝึกสนทนา"
628        | "dialog_load"
629        | "对话载入"
630        | "対話読込"
631        | "대화불러오기"
632        | "โหลดชุดสนทนา"
633        | "مدل_گفتگوی_جدید"
634        | "نموذج_حوار_جديد"
635        | "מודל_דיאלוג_חדש"
636        | "نیا_مکالمہ_ماڈل" => Value::Number(-1.0),
637        "dialog_say"
638        | "对话生成"
639        | "対話生成"
640        | "대화생성"
641        | "พูดสนทนา"
642        | "بگو"
643        | "قل"
644        | "אמור"
645        | "کہو" => Value::Str(String::new()),
646        "dialog_save"
647        | "对话存模"
648        | "対話モデル保存"
649        | "대화모델저장"
650        | "บันทึกโมเดลสนทนา"
651        | "ذخیره_مدل_گفتگو"
652        | "احفظ_نموذج_الحوار"
653        | "שמור_מודל_דיאלוג"
654        | "مکالمہ_ماڈل_محفوظ" => Value::Bool(false),
655        "dialog_learn"
656        | "对话学习"
657        | "対話学習"
658        | "대화학습"
659        | "เรียนรู้สนทนา"
660        | "یادگیری_گفتگو"
661        | "تعلّم_الحوار"
662        | "למד_דיאלוג"
663        | "مکالمہ_سیکھو" => Value::Unit,
664
665        // ── networking ──────────────────────────────────────────────────────
666        "net_connect"
667        | "联网"
668        | "ネット接続"
669        | "네트연결"
670        | "เชื่อมเน็ต"
671        | "net_listen"
672        | "监听"
673        | "待機"
674        | "리슨"
675        | "รอรับ"
676        | "net_send"
677        | "发送"
678        | "送信"
679        | "전송"
680        | "ส่ง"
681        | "اتصال_شبکه"
682        | "اتصل_بالشبكة"
683        | "התחבר_לרשת"
684        | "نیٹ_کنیکٹ" => Value::Number(-1.0),
685        "net_recv"
686        | "接收"
687        | "受信"
688        | "수신"
689        | "รับ"
690        | "net_status"
691        | "连接状态"
692        | "接続状態"
693        | "연결상태"
694        | "สถานะการเชื่อม"
695        | "دریافت_شبکه"
696        | "استقبل_من_الشبكة"
697        | "קבל_מרשת"
698        | "نیٹ_وصول" => Value::Str(String::new()),
699        "net_discover"
700        | "发现"
701        | "探索"
702        | "검색"
703        | "ค้นหาเครือข่าย"
704        | "کشف_شبکه"
705        | "اكتشف_الشبكة"
706        | "גלה_רשת"
707        | "نیٹ_دریافت" => Value::List(Vec::new().into()),
708        "net_close"
709        | "断开"
710        | "切断"
711        | "연결종료"
712        | "ตัดเชื่อม"
713        | "net_test"
714        | "测连接"
715        | "接続テスト"
716        | "연결테스트"
717        | "ทดสอบเน็ต"
718        | "بستن_شبکه"
719        | "أغلق_الشبكة"
720        | "סגור_רשת"
721        | "نیٹ_بند" => Value::Number(0.0),
722
723        // catch-all: any other native-only builtin silently no-ops on wasm32
724        _ => Value::Unit,
725    })
726}
727
728// ─── Control flow ────────────────────────────────────────────────────────────
729
730#[derive(Debug)]
731pub(crate) enum EvalErr {
732    Runtime(String),
733    Return(Value),
734    #[allow(dead_code)] // reserved for future `break` statement support
735    Break,
736}
737
738impl From<String> for EvalErr {
739    fn from(s: String) -> Self {
740        EvalErr::Runtime(s)
741    }
742}
743
744type EvalResult = Result<Value, EvalErr>;
745
746/// RFC 4648 base32 encode (no padding) — used for TOTP secrets.
747#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
748fn base32_encode(data: &[u8]) -> String {
749    const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
750    let mut out = String::new();
751    let mut buffer: u32 = 0;
752    let mut bits = 0u32;
753    for &b in data {
754        buffer = (buffer << 8) | b as u32;
755        bits += 8;
756        while bits >= 5 {
757            bits -= 5;
758            out.push(ALPHABET[((buffer >> bits) & 0x1f) as usize] as char);
759        }
760    }
761    if bits > 0 {
762        out.push(ALPHABET[((buffer << (5 - bits)) & 0x1f) as usize] as char);
763    }
764    out
765}
766
767/// RFC 4648 base32 decode (case-insensitive, ignores padding/whitespace).
768#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
769fn base32_decode(s: &str) -> Option<Vec<u8>> {
770    let mut buffer: u32 = 0;
771    let mut bits = 0u32;
772    let mut out = Vec::new();
773    for c in s.chars() {
774        if c == '=' || c.is_whitespace() {
775            continue;
776        }
777        let v = match c.to_ascii_uppercase() {
778            'A'..='Z' => c.to_ascii_uppercase() as u32 - 'A' as u32,
779            '2'..='7' => c as u32 - '2' as u32 + 26,
780            _ => return None,
781        };
782        buffer = (buffer << 5) | v;
783        bits += 5;
784        if bits >= 8 {
785            bits -= 8;
786            out.push((buffer >> bits) as u8);
787        }
788    }
789    Some(out)
790}
791
792/// One RFC 6238 TOTP code (HMAC-SHA1, 6 digits) for the given 30s time step.
793#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
794fn totp_code(secret_b32: &str, step: u64) -> Option<String> {
795    let key = base32_decode(secret_b32)?;
796    let msg = step.to_be_bytes();
797    let mac = hmac_sha1(&key, &msg);
798    let offset = (mac[19] & 0x0f) as usize;
799    let bin = ((mac[offset] as u32 & 0x7f) << 24)
800        | ((mac[offset + 1] as u32) << 16)
801        | ((mac[offset + 2] as u32) << 8)
802        | (mac[offset + 3] as u32);
803    Some(format!("{:06}", bin % 1_000_000))
804}
805
806/// TOTP verify with a ±1 step window (tolerates minor clock skew).
807#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
808fn totp_check(secret_b32: &str, code: &str) -> bool {
809    if code.len() != 6 || !code.bytes().all(|b| b.is_ascii_digit()) {
810        return false;
811    }
812    let now_step = (crate::runtime::now_secs() as u64) / 30;
813    for delta in [-1i64, 0, 1] {
814        let step = (now_step as i64 + delta) as u64;
815        if let Some(expected) = totp_code(secret_b32, step) {
816            // constant-time-ish compare (fixed 6-char length)
817            if expected.as_bytes().ct_eq_str(code.as_bytes()) {
818                return true;
819            }
820        }
821    }
822    false
823}
824
825/// HMAC-SHA1 built on the `hmac`+`sha1` crates (both via the web feature).
826#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
827fn hmac_sha1(key: &[u8], msg: &[u8]) -> [u8; 20] {
828    use hmac::{Mac, SimpleHmac};
829    let mut mac = SimpleHmac::<sha1::Sha1>::new_from_slice(key).expect("hmac key");
830    mac.update(msg);
831    let out = mac.finalize().into_bytes();
832    let mut arr = [0u8; 20];
833    arr.copy_from_slice(&out);
834    arr
835}
836
837/// Tiny fixed-length constant-time byte compare helper for TOTP codes.
838#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
839trait CtEqStr {
840    fn ct_eq_str(&self, other: &[u8]) -> bool;
841}
842#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
843impl CtEqStr for [u8] {
844    fn ct_eq_str(&self, other: &[u8]) -> bool {
845        if self.len() != other.len() {
846            return false;
847        }
848        let mut diff = 0u8;
849        for (a, b) in self.iter().zip(other.iter()) {
850            diff |= a ^ b;
851        }
852        diff == 0
853    }
854}
855
856/// Percent-decodes a URL query component (`+` → space, `%41` → `A`).
857fn url_decode(s: &str) -> String {
858    let bytes = s.as_bytes();
859    let mut out = Vec::with_capacity(bytes.len());
860    let mut i = 0;
861    while i < bytes.len() {
862        match bytes[i] {
863            b'+' => {
864                out.push(b' ');
865                i += 1;
866            },
867            b'%' if i + 2 < bytes.len() => {
868                let hi = (bytes[i + 1] as char).to_digit(16);
869                let lo = (bytes[i + 2] as char).to_digit(16);
870                if let (Some(h), Some(l)) = (hi, lo) {
871                    out.push((h * 16 + l) as u8);
872                    i += 3;
873                } else {
874                    out.push(bytes[i]);
875                    i += 1;
876                }
877            },
878            b => {
879                out.push(b);
880                i += 1;
881            },
882        }
883    }
884    String::from_utf8_lossy(&out).into_owned()
885}
886
887/// Maps Ling values to owned rusqlite parameter values for `db_exec`/`db_query`.
888#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
889fn values_to_sql_params(args: &[Value]) -> Vec<ling_http::rusqlite::types::Value> {
890    use ling_http::rusqlite::types::Value as Sql;
891    args.iter()
892        .map(|v| match v {
893            Value::Number(n) if n.fract() == 0.0 && n.abs() < 9e15 => Sql::Integer(*n as i64),
894            Value::Number(n) => Sql::Real(*n),
895            Value::Bool(b) => Sql::Integer(*b as i64),
896            other => Sql::Text(other.to_string()),
897        })
898        .collect()
899}
900
901// GfxState is now defined in crate::gfx — see src/gfx/mod.rs.
902
903// ─── SVG writer ───────────────────────────────────────────────────────────────
904
905struct SvgWriter {
906    path: String,
907    width: f64,
908    height: f64,
909    elements: Vec<String>,
910}
911
912impl SvgWriter {
913    fn new(path: String, width: f64, height: f64) -> Self {
914        let bg = format!("<rect width=\"{width}\" height=\"{height}\" fill=\"#0a0a0a\"/>");
915        Self { path, width, height, elements: vec![bg] }
916    }
917
918    fn save(&self) -> std::io::Result<()> {
919        let w = self.width;
920        let h = self.height;
921        let mut out = format!(
922            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
923             <svg xmlns=\"http://www.w3.org/2000/svg\" \
924             width=\"{w}\" height=\"{h}\" viewBox=\"0 0 {w} {h}\">\n"
925        );
926        for elem in &self.elements {
927            out.push_str("  ");
928            out.push_str(elem);
929            out.push('\n');
930        }
931        out.push_str("</svg>\n");
932        // Create parent directory if it doesn't exist.
933        if let Some(parent) = std::path::Path::new(&self.path).parent() {
934            if !parent.as_os_str().is_empty() {
935                let _ = std::fs::create_dir_all(parent);
936            }
937        }
938        std::fs::write(&self.path, out.as_bytes())
939    }
940}
941
942fn hsl_to_hex(h: f64, s: f64, l: f64) -> String {
943    let s = s / 100.0;
944    let l = l / 100.0;
945    let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
946    let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
947    let m = l - c / 2.0;
948    let (r1, g1, b1) = if h < 60.0 {
949        (c, x, 0.0)
950    } else if h < 120.0 {
951        (x, c, 0.0)
952    } else if h < 180.0 {
953        (0.0, c, x)
954    } else if h < 240.0 {
955        (0.0, x, c)
956    } else if h < 300.0 {
957        (x, 0.0, c)
958    } else {
959        (c, 0.0, x)
960    };
961    let r = ((r1 + m) * 255.0).round() as u8;
962    let g = ((g1 + m) * 255.0).round() as u8;
963    let b = ((b1 + m) * 255.0).round() as u8;
964    format!("#{r:02x}{g:02x}{b:02x}")
965}
966
967// ─── Procedural texture helpers ───────────────────────────────────────────────
968
969fn tex_hash(x: i32, y: i32, seed: u32) -> f32 {
970    let mut h = (x as u32)
971        .wrapping_add((y as u32).wrapping_mul(2654435769))
972        .wrapping_add(seed.wrapping_mul(1234567891));
973    h ^= h >> 16;
974    h = h.wrapping_mul(0x45d9f3b);
975    h ^= h >> 16;
976    h as f32 / u32::MAX as f32
977}
978
979fn tex_vnoise(x: f32, y: f32, seed: u32) -> f32 {
980    let xi = x.floor() as i32;
981    let yi = y.floor() as i32;
982    let sm = |t: f32| t * t * (3.0 - 2.0 * t);
983    let xf = sm(x - xi as f32);
984    let yf = sm(y - yi as f32);
985    let a = tex_hash(xi, yi, seed);
986    let b = tex_hash(xi + 1, yi, seed);
987    let c = tex_hash(xi, yi + 1, seed);
988    let d = tex_hash(xi + 1, yi + 1, seed);
989    a + (b - a) * xf + (c - a) * yf + (a - b - c + d) * xf * yf
990}
991
992fn tex_fbm(x: f32, y: f32, octaves: u32, seed: u32) -> f32 {
993    let mut v = 0.0f32;
994    let mut amp = 0.5f32;
995    let mut f = 1.0f32;
996    for i in 0..octaves {
997        v += tex_vnoise(x * f, y * f, seed.wrapping_add(i * 7919)) * amp;
998        amp *= 0.5;
999        f *= 2.0;
1000    }
1001    v
1002}
1003
1004fn tex_palette(name: &str, t: f32) -> [f32; 3] {
1005    let (a, b, c, d): ([f32; 3], [f32; 3], [f32; 3], [f32; 3]) = match name {
1006        "fire" => (
1007            [0.8, 0.4, 0.1],
1008            [0.7, 0.3, 0.1],
1009            [1.0, 0.5, 0.3],
1010            [0.0, 0.5, 0.8],
1011        ),
1012        "ocean" => (
1013            [0.1, 0.4, 0.7],
1014            [0.3, 0.3, 0.4],
1015            [0.8, 1.0, 0.5],
1016            [0.3, 0.0, 0.6],
1017        ),
1018        "psychedelic" => (
1019            [0.5, 0.5, 0.5],
1020            [0.8, 0.8, 0.8],
1021            [1.0, 1.3, 0.7],
1022            [0.0, 0.15, 0.3],
1023        ),
1024        "neon" => (
1025            [0.5, 0.5, 0.5],
1026            [0.5, 0.5, 0.5],
1027            [2.0, 1.0, 0.0],
1028            [0.5, 0.2, 0.25],
1029        ),
1030        "forest" => (
1031            [0.3, 0.5, 0.2],
1032            [0.2, 0.3, 0.1],
1033            [1.0, 0.5, 0.8],
1034            [0.1, 0.3, 0.6],
1035        ),
1036        _ => (
1037            [0.5, 0.5, 0.5],
1038            [0.5, 0.5, 0.5],
1039            [1.0, 1.0, 1.0],
1040            [0.0, 0.333, 0.667],
1041        ),
1042    };
1043    [0, 1, 2]
1044        .map(|i| (a[i] + b[i] * (std::f32::consts::TAU * (c[i] * t + d[i])).cos()).clamp(0.0, 1.0))
1045}
1046
1047/// Map a physical key to a typed character for ling-ui text input (lowercase).
1048#[cfg(not(target_arch = "wasm32"))]
1049// Full US-QWERTY keyboard → char, shift-aware. `key_char` only ever emits
1050// printable ASCII (never Tab/Enter/control bytes — those keys have no case
1051// here at all) so anything read through `text_poll` is safe by construction
1052// to drop straight into the game's tab/comma/semicolon/pipe-framed wire
1053// protocols without needing per-keystroke sanitization.
1054fn key_char(k: minifb::Key, shift: bool) -> Option<char> {
1055    use minifb::Key::*;
1056    let base = match k {
1057        A => 'a',
1058        B => 'b',
1059        C => 'c',
1060        D => 'd',
1061        E => 'e',
1062        F => 'f',
1063        G => 'g',
1064        H => 'h',
1065        I => 'i',
1066        J => 'j',
1067        K => 'k',
1068        L => 'l',
1069        M => 'm',
1070        N => 'n',
1071        O => 'o',
1072        P => 'p',
1073        Q => 'q',
1074        R => 'r',
1075        S => 's',
1076        T => 't',
1077        U => 'u',
1078        V => 'v',
1079        W => 'w',
1080        X => 'x',
1081        Y => 'y',
1082        Z => 'z',
1083        Key0 => '0',
1084        Key1 => '1',
1085        Key2 => '2',
1086        Key3 => '3',
1087        Key4 => '4',
1088        Key5 => '5',
1089        Key6 => '6',
1090        Key7 => '7',
1091        Key8 => '8',
1092        Key9 => '9',
1093        Space => ' ',
1094        Minus => '-',
1095        Equal => '=',
1096        Period => '.',
1097        Comma => ',',
1098        Slash => '/',
1099        Backslash => '\\',
1100        Semicolon => ';',
1101        Apostrophe => '\'',
1102        LeftBracket => '[',
1103        RightBracket => ']',
1104        Backquote => '`',
1105        _ => return None,
1106    };
1107    if !shift {
1108        return Some(base);
1109    }
1110    Some(match base {
1111        'a'..='z' => base.to_ascii_uppercase(),
1112        '0' => ')',
1113        '1' => '!',
1114        '2' => '@',
1115        '3' => '#',
1116        '4' => '$',
1117        '5' => '%',
1118        '6' => '^',
1119        '7' => '&',
1120        '8' => '*',
1121        '9' => '(',
1122        '-' => '_',
1123        '=' => '+',
1124        '.' => '>',
1125        ',' => '<',
1126        '/' => '?',
1127        '\\' => '|',
1128        ';' => ':',
1129        '\'' => '"',
1130        '[' => '{',
1131        ']' => '}',
1132        '`' => '~',
1133        other => other,
1134    })
1135}
1136
1137/// Win32 virtual-key code → char, mirroring `key_char` exactly but keyed by
1138/// VK code instead of `minifb::Key` — used by the `GetAsyncKeyState` input
1139/// path (see `os_key_down` / the `topmost_window` fallback in `text_poll`).
1140#[cfg(all(not(target_arch = "wasm32"), windows))]
1141fn vk_char(vk: i32, shift: bool) -> Option<char> {
1142    let base = match vk {
1143        0x41..=0x5A => (b'a' + (vk - 0x41) as u8) as char, // 'A'..'Z'
1144        0x30..=0x39 => (b'0' + (vk - 0x30) as u8) as char, // '0'..'9'
1145        0x20 => ' ',                                       // VK_SPACE
1146        0xBD => '-',                                       // VK_OEM_MINUS
1147        0xBB => '=',                                       // VK_OEM_PLUS (unshifted '=')
1148        0xBE => '.',                                       // VK_OEM_PERIOD
1149        0xBC => ',',                                       // VK_OEM_COMMA
1150        0xBF => '/',                                       // VK_OEM_2
1151        0xDC => '\\',                                      // VK_OEM_5
1152        0xBA => ';',                                       // VK_OEM_1
1153        0xDE => '\'',                                      // VK_OEM_7
1154        0xDB => '[',                                       // VK_OEM_4
1155        0xDD => ']',                                       // VK_OEM_6
1156        0xC0 => '`',                                       // VK_OEM_3
1157        _ => return None,
1158    };
1159    if !shift {
1160        return Some(base);
1161    }
1162    Some(match base {
1163        'a'..='z' => base.to_ascii_uppercase(),
1164        '0' => ')',
1165        '1' => '!',
1166        '2' => '@',
1167        '3' => '#',
1168        '4' => '$',
1169        '5' => '%',
1170        '6' => '^',
1171        '7' => '&',
1172        '8' => '*',
1173        '9' => '(',
1174        '-' => '_',
1175        '=' => '+',
1176        '.' => '>',
1177        ',' => '<',
1178        '/' => '?',
1179        '\\' => '|',
1180        ';' => ':',
1181        '\'' => '"',
1182        '[' => '{',
1183        ']' => '}',
1184        '`' => '~',
1185        other => other,
1186    })
1187}
1188
1189/// The VK codes `text_poll`'s `GetAsyncKeyState` fallback scans each frame —
1190/// every key `vk_char` can turn into a character.
1191#[cfg(all(not(target_arch = "wasm32"), windows))]
1192const TEXT_POLL_VKS: &[i32] = &[
1193    0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
1194    0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
1195    0x36, 0x37, 0x38, 0x39, 0x20, 0xBD, 0xBB, 0xBE, 0xBC, 0xBF, 0xDC, 0xBA, 0xDE, 0xDB, 0xDD, 0xC0,
1196];
1197/// VK_BACK — polled separately from `TEXT_POLL_VKS` since it edits (pops)
1198/// rather than appends.
1199#[cfg(all(not(target_arch = "wasm32"), windows))]
1200const VK_BACK: i32 = 0x08;
1201#[cfg(all(not(target_arch = "wasm32"), windows))]
1202const VK_SHIFT: i32 = 0x10;
1203
1204/// Read the OS's live key-state table directly — unlike minifb's
1205/// `is_key_down`/`get_keys_pressed` (populated from `WM_KEYDOWN`, which only
1206/// arrives at a window holding real Win32 keyboard focus), this works even
1207/// when the borderless-fullscreen window is topmost/visually in front but
1208/// didn't actually win the OS focus fight (Windows' foreground-lock — see
1209/// `force_window_focus`). High bit of `GetAsyncKeyState` = currently down.
1210#[cfg(all(not(target_arch = "wasm32"), windows))]
1211fn os_key_down(vk: i32) -> bool {
1212    unsafe {
1213        extern "system" {
1214            fn GetAsyncKeyState(vkey: i32) -> i16;
1215        }
1216        (GetAsyncKeyState(vk) as u16 & 0x8000) != 0
1217    }
1218}
1219
1220/// True when `hwnd` is the actual OS foreground window. `GetAsyncKeyState`
1221/// (see `os_key_down`) reads the global key-state table regardless of which
1222/// window is focused, so the `topmost_window` input fallback must check this
1223/// before trusting it — otherwise alt-tabbing away from the topmost/
1224/// fullscreen window to type in some other app would still feed keystrokes
1225/// into the game sitting behind it, since nothing about GetAsyncKeyState
1226/// itself would notice the window lost focus.
1227#[cfg(all(not(target_arch = "wasm32"), windows))]
1228fn window_is_foreground(hwnd: isize) -> bool {
1229    unsafe {
1230        extern "system" {
1231            fn GetForegroundWindow() -> isize;
1232        }
1233        hwnd != 0 && GetForegroundWindow() == hwnd
1234    }
1235}
1236
1237/// Delay (seconds) a key must stay held before it starts auto-repeating —
1238/// matches the initial "hesitation" of a normal OS text field.
1239#[cfg(all(not(target_arch = "wasm32"), windows))]
1240const KEY_REPEAT_DELAY: f64 = 0.45;
1241/// Interval (seconds) between repeats once a held key is auto-repeating.
1242#[cfg(all(not(target_arch = "wasm32"), windows))]
1243const KEY_REPEAT_RATE: f64 = 0.045;
1244
1245/// Edge/repeat detector for the `GetAsyncKeyState` text-input fallback:
1246/// fires true on the initial press, then again every `KEY_REPEAT_RATE`
1247/// seconds once the key has been held past `KEY_REPEAT_DELAY` — the same
1248/// press-then-hold-to-repeat behavior minifb's `KeyRepeat::Yes` gives the
1249/// normal (focused-window) path, which this fallback otherwise lacks since
1250/// it does its own from-scratch edge detection per VK code.
1251#[cfg(all(not(target_arch = "wasm32"), windows))]
1252fn key_repeat_fire(
1253    now: f64,
1254    down: bool,
1255    was_down: bool,
1256    down_since: &mut f64,
1257    last_fire: &mut f64,
1258) -> bool {
1259    if !down {
1260        return false;
1261    }
1262    if !was_down {
1263        *down_since = now;
1264        *last_fire = now;
1265        return true;
1266    }
1267    if now - *down_since >= KEY_REPEAT_DELAY && now - *last_fire >= KEY_REPEAT_RATE {
1268        *last_fire = now;
1269        return true;
1270    }
1271    false
1272}
1273
1274/// Map the same key-name strings `key_down`/`key_pressed` already accept
1275/// (see `str_to_minifb_key`) to Win32 virtual-key codes, for the
1276/// `GetAsyncKeyState` fallback path.
1277#[cfg(all(not(target_arch = "wasm32"), windows))]
1278fn str_to_vk(name: &str) -> Option<i32> {
1279    Some(match name {
1280        "numpad0" | "kp0" => 0x60,
1281        "numpad1" | "kp1" => 0x61,
1282        "numpad2" | "kp2" => 0x62,
1283        "numpad3" | "kp3" => 0x63,
1284        "numpad4" | "kp4" => 0x64,
1285        "numpad5" | "kp5" => 0x65,
1286        "numpad6" | "kp6" => 0x66,
1287        "numpad7" | "kp7" => 0x67,
1288        "numpad8" | "kp8" => 0x68,
1289        "numpad9" | "kp9" => 0x69,
1290        "numpad+" | "kp+" => 0x6B,
1291        "numpad-" | "kp-" => 0x6D,
1292        "numpad*" | "kp*" => 0x6A,
1293        "numpad/" | "kp/" => 0x6F,
1294        "left" => 0x25,
1295        "up" => 0x26,
1296        "right" => 0x27,
1297        "down" => 0x28,
1298        "space" => 0x20,
1299        "enter" => 0x0D,
1300        "escape" => 0x1B,
1301        "pageup" => 0x21,
1302        "pagedown" => 0x22,
1303        "lshift" | "leftshift" => 0xA0,
1304        "rshift" | "rightshift" => 0xA1,
1305        "lctrl" | "leftctrl" => 0xA2,
1306        "rctrl" | "rightctrl" => 0xA3,
1307        "lalt" | "leftalt" => 0xA4,
1308        "ralt" | "rightalt" => 0xA5,
1309        "tab" => 0x09,
1310        "backspace" => VK_BACK,
1311        "delete" => 0x2E,
1312        "insert" => 0x2D,
1313        "home" => 0x24,
1314        "end" => 0x23,
1315        "a" => 0x41,
1316        "b" => 0x42,
1317        "c" => 0x43,
1318        "d" => 0x44,
1319        "e" => 0x45,
1320        "f" => 0x46,
1321        "g" => 0x47,
1322        "h" => 0x48,
1323        "i" => 0x49,
1324        "j" => 0x4A,
1325        "k" => 0x4B,
1326        "l" => 0x4C,
1327        "m" => 0x4D,
1328        "n" => 0x4E,
1329        "o" => 0x4F,
1330        "p" => 0x50,
1331        "q" => 0x51,
1332        "r" => 0x52,
1333        "s" => 0x53,
1334        "t" => 0x54,
1335        "u" => 0x55,
1336        "v" => 0x56,
1337        "w" => 0x57,
1338        "x" => 0x58,
1339        "y" => 0x59,
1340        "z" => 0x5A,
1341        "0" => 0x30,
1342        "1" => 0x31,
1343        "2" => 0x32,
1344        "3" => 0x33,
1345        "4" => 0x34,
1346        "5" => 0x35,
1347        "6" => 0x36,
1348        "7" => 0x37,
1349        "8" => 0x38,
1350        "9" => 0x39,
1351        _ => return None,
1352    })
1353}
1354
1355/// Lowercase-hex encode bytes (the wire format for crypto values in Ling).
1356fn hex_encode(bytes: &[u8]) -> String {
1357    let mut s = String::with_capacity(bytes.len() * 2);
1358    for b in bytes {
1359        s.push_str(&format!("{b:02x}"));
1360    }
1361    s
1362}
1363
1364/// Decode a `ling convert` blob: base64 → zlib-inflate → raw little-endian bytes.
1365#[cfg(not(target_arch = "wasm32"))]
1366fn decode_blob(s: &str) -> Result<Vec<u8>, String> {
1367    use base64::Engine as _;
1368    use std::io::Read as _;
1369    let comp = base64::engine::general_purpose::STANDARD
1370        .decode(s.trim())
1371        .map_err(|e| format!("base64: {e}"))?;
1372    let mut out = Vec::new();
1373    flate2::read::ZlibDecoder::new(&comp[..])
1374        .read_to_end(&mut out)
1375        .map_err(|e| format!("inflate: {e}"))?;
1376    Ok(out)
1377}
1378
1379/// Decode a lowercase/uppercase hex string to bytes (ignores malformed tail).
1380#[cfg(not(target_arch = "wasm32"))]
1381fn hex_decode(s: &str) -> Vec<u8> {
1382    let s = s.trim();
1383    (0..s.len() / 2)
1384        .filter_map(|i| u8::from_str_radix(s.get(i * 2..i * 2 + 2)?, 16).ok())
1385        .collect()
1386}
1387
1388/// Decode a hex string into a fixed 32-byte key (zero-padded / truncated).
1389#[cfg(not(target_arch = "wasm32"))]
1390fn hex_to_32(s: &str) -> [u8; 32] {
1391    let v = hex_decode(s);
1392    let mut out = [0u8; 32];
1393    let n = v.len().min(32);
1394    out[..n].copy_from_slice(&v[..n]);
1395    out
1396}
1397
1398/// Builds a minimal, valid PDF with one page per input image (decoded via
1399/// `image`, page sized to the image's own pixel dimensions), backing the
1400/// `pdf_from_images` builtin. No PDF crate: each page is three objects
1401/// (Page / Contents / Image XObject) hand-written directly, with the image
1402/// stream flate-compressed raw RGB8 (`/Filter /FlateDecode /ColorSpace
1403/// /DeviceRGB /BitsPerComponent 8`) — no separate re-encoding step beyond
1404/// what `flate2` (already a dependency) does for the stream itself.
1405#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1406fn build_pdf_from_images(
1407    paths: &[String],
1408    out_path: &str,
1409) -> Result<(), Box<dyn std::error::Error>> {
1410    use std::io::Write as _;
1411
1412    struct PageImg {
1413        w: u32,
1414        h: u32,
1415        compressed: Vec<u8>,
1416    }
1417
1418    let mut pages = Vec::with_capacity(paths.len());
1419    for p in paths {
1420        let img = image::open(p)?.to_rgb8();
1421        let (w, h) = (img.width(), img.height());
1422        let mut enc = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
1423        enc.write_all(img.as_raw())?;
1424        pages.push(PageImg { w, h, compressed: enc.finish()? });
1425    }
1426
1427    let n = pages.len();
1428    // Object numbers: 1=Catalog, 2=Pages, then per page i (0-indexed):
1429    // 3+3i=Page, 4+3i=Contents, 5+3i=Image XObject.
1430    let page_nums: Vec<u32> = (0..n).map(|i| 3 + (i as u32) * 3).collect();
1431    let total_objs = 2 + n * 3;
1432    let mut off = vec![0usize; total_objs + 1]; // 1-based; off[0] unused
1433
1434    let mut buf: Vec<u8> = Vec::new();
1435    buf.extend_from_slice(b"%PDF-1.4\n%\xE2\xE3\xCF\xD3\n");
1436
1437    off[1] = buf.len();
1438    buf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
1439
1440    off[2] = buf.len();
1441    let kids = page_nums
1442        .iter()
1443        .map(|n| format!("{n} 0 R"))
1444        .collect::<Vec<_>>()
1445        .join(" ");
1446    buf.extend_from_slice(
1447        format!("2 0 obj\n<< /Type /Pages /Kids [{kids}] /Count {n} >>\nendobj\n").as_bytes(),
1448    );
1449
1450    for (i, page) in pages.iter().enumerate() {
1451        let page_num = page_nums[i];
1452        let content_num = page_num + 1;
1453        let image_num = page_num + 2;
1454        let (w, h) = (page.w, page.h);
1455
1456        off[page_num as usize] = buf.len();
1457        buf.extend_from_slice(format!(
1458            "{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"
1459        ).as_bytes());
1460
1461        let content = format!("q {w} 0 0 {h} 0 0 cm /Im0 Do Q");
1462        off[content_num as usize] = buf.len();
1463        buf.extend_from_slice(
1464            format!(
1465                "{content_num} 0 obj\n<< /Length {} >>\nstream\n{content}\nendstream\nendobj\n",
1466                content.len()
1467            )
1468            .as_bytes(),
1469        );
1470
1471        off[image_num as usize] = buf.len();
1472        buf.extend_from_slice(format!(
1473            "{image_num} 0 obj\n<< /Type /XObject /Subtype /Image /Width {w} /Height {h} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /FlateDecode /Length {} >>\nstream\n",
1474            page.compressed.len()
1475        ).as_bytes());
1476        buf.extend_from_slice(&page.compressed);
1477        buf.extend_from_slice(b"\nendstream\nendobj\n");
1478    }
1479
1480    let xref_offset = buf.len();
1481    buf.extend_from_slice(format!("xref\n0 {}\n", total_objs + 1).as_bytes());
1482    buf.extend_from_slice(b"0000000000 65535 f \n");
1483    for entry in off.iter().skip(1) {
1484        buf.extend_from_slice(format!("{entry:010} 00000 n \n").as_bytes());
1485    }
1486    buf.extend_from_slice(
1487        format!(
1488            "trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref_offset}\n%%EOF",
1489            total_objs + 1
1490        )
1491        .as_bytes(),
1492    );
1493
1494    std::fs::write(out_path, buf)?;
1495    Ok(())
1496}
1497
1498fn tex_rgb(r: f32, g: f32, b: f32) -> u32 {
1499    ((r * 255.0) as u32) << 16 | ((g * 255.0) as u32) << 8 | (b * 255.0) as u32
1500}
1501
1502// ─── 3D Perlin Noise (Improved Perlin 2002) ───────────────────────────────────
1503
1504const PERM: [u8; 512] = [
1505    151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
1506    142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219,
1507    203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 35, 63, 189, 114, 56, 42, 123,
1508    165, 38, 72, 93, 69, 139, 138, 78, 149, 159, 56, 89, 152, 78, 61, 140, 63, 26, 142, 76, 124,
1509    132, 72, 11, 90, 44, 82, 59, 96, 41, 148, 126, 157, 13, 49, 27, 176, 33, 47, 14, 97, 78, 71,
1510    40, 87, 183, 4, 122, 92, 7, 72, 3, 246, 17, 225, 87, 91, 106, 203, 190, 57, 74, 76, 88, 207,
1511    208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146,
1512    157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196,
1513    167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94,
1514    11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55,
1515    109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186, 120, 37, 46, 28, 166, 180,
1516    198, 232, 221, 116, 31, 75, 189, 139, 138, 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185,
1517    134, 193, 29, 158, 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223,
1518    140, 161, 137, 13, 191, 230, 66, 104, 153, 199, 167, 147, 99, 179, 92,
1519    // Duplicate for wrap-around indexing
1520    151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
1521    142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219,
1522    203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 35, 63, 189, 114, 56, 42, 123,
1523    165, 38, 72, 93, 69, 139, 138, 78, 149, 159, 56, 89, 152, 78, 61, 140, 63, 26, 142, 76, 124,
1524    132, 72, 11, 90, 44, 82, 59, 96, 41, 148, 126, 157, 13, 49, 27, 176, 33, 47, 14, 97, 78, 71,
1525    40, 87, 183, 4, 122, 92, 7, 72, 3, 246, 17, 225, 87, 91, 106, 203, 190, 57, 74, 76, 88, 207,
1526    208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146,
1527    157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196,
1528    167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94,
1529    11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55,
1530    109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174,
1531];
1532
1533fn fade(t: f32) -> f32 {
1534    t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
1535}
1536
1537fn grad(hash: u8, x: f32, y: f32, z: f32) -> f32 {
1538    let h = hash & 15;
1539    let u = if h < 8 { x } else { y };
1540    let v = if h < 8 { y } else { z };
1541    (if (h & 1) == 0 { u } else { -u }) + (if (h & 2) == 0 { v } else { -v })
1542}
1543
1544fn perlin3(x: f32, y: f32, z: f32) -> f32 {
1545    let xi = (x.floor() as i32) & 255;
1546    let yi = (y.floor() as i32) & 255;
1547    let zi = (z.floor() as i32) & 255;
1548
1549    let xf = x - x.floor();
1550    let yf = y - y.floor();
1551    let zf = z - z.floor();
1552
1553    let u = fade(xf);
1554    let v = fade(yf);
1555    let w = fade(zf);
1556
1557    let p0 = PERM[xi as usize] as usize;
1558    let p1 = PERM[((xi + 1) & 255) as usize] as usize;
1559    let pa = PERM[(p0 + yi as usize) & 255] as usize;
1560    let pb = PERM[(p0 + ((yi + 1) & 255) as usize) & 255] as usize;
1561    let pc = PERM[(p1 + yi as usize) & 255] as usize;
1562    let pd = PERM[(p1 + ((yi + 1) & 255) as usize) & 255] as usize;
1563
1564    let g000 = grad(PERM[(pa + zi as usize) & 255], xf, yf, zf);
1565    let g001 = grad(
1566        PERM[(pa + ((zi + 1) & 255) as usize) & 255],
1567        xf,
1568        yf,
1569        zf - 1.0,
1570    );
1571    let g010 = grad(PERM[(pb + zi as usize) & 255], xf, yf - 1.0, zf);
1572    let g011 = grad(
1573        PERM[(pb + ((zi + 1) & 255) as usize) & 255],
1574        xf,
1575        yf - 1.0,
1576        zf - 1.0,
1577    );
1578    let g100 = grad(PERM[(pc + zi as usize) & 255], xf - 1.0, yf, zf);
1579    let g101 = grad(
1580        PERM[(pc + ((zi + 1) & 255) as usize) & 255],
1581        xf - 1.0,
1582        yf,
1583        zf - 1.0,
1584    );
1585    let g110 = grad(PERM[(pd + zi as usize) & 255], xf - 1.0, yf - 1.0, zf);
1586    let g111 = grad(
1587        PERM[(pd + ((zi + 1) & 255) as usize) & 255],
1588        xf - 1.0,
1589        yf - 1.0,
1590        zf - 1.0,
1591    );
1592
1593    let l00 = g000 + u * (g100 - g000);
1594    let l01 = g001 + u * (g101 - g001);
1595    let l10 = g010 + u * (g110 - g010);
1596    let l11 = g011 + u * (g111 - g011);
1597
1598    let l0 = l00 + v * (l10 - l00);
1599    let l1 = l01 + v * (l11 - l01);
1600
1601    l0 + w * (l1 - l0)
1602}
1603
1604fn fast_rand_f64(state: &mut u64) -> f64 {
1605    *state = state
1606        .wrapping_mul(6364136223846793005)
1607        .wrapping_add(1442695040888963407);
1608    ((*state >> 32) as u32) as f64 / 4294967296.0
1609}
1610
1611// ─── Circle Drawing Primitives ────────────────────────────────────────────────
1612
1613/// Write one pixel into the framebuffer (normal or additive blend).
1614#[inline]
1615fn put_px(buf: &mut [u32], idx: usize, color: u32, blend: u8) {
1616    if idx >= buf.len() {
1617        return;
1618    }
1619    if blend == 0 {
1620        buf[idx] = color;
1621    } else {
1622        let old = buf[idx];
1623        let r = (((old >> 16) & 255) + ((color >> 16) & 255)).min(255);
1624        let g = (((old >> 8) & 255) + ((color >> 8) & 255)).min(255);
1625        let b = ((old & 255) + (color & 255)).min(255);
1626        buf[idx] = (r << 16) | (g << 8) | b;
1627    }
1628}
1629
1630/// Pack three float colour channels (0..255) into a 0x00RRGGBB word, clamping.
1631#[inline]
1632fn rgb(r: f64, g: f64, b: f64) -> u32 {
1633    let r = (r as i64).clamp(0, 255) as u32;
1634    let g = (g as i64).clamp(0, 255) as u32;
1635    let b = (b as i64).clamp(0, 255) as u32;
1636    (r << 16) | (g << 8) | b
1637}
1638
1639#[allow(clippy::too_many_arguments)]
1640fn draw_circle_outline(
1641    buf: &mut [u32],
1642    w: i32,
1643    h: i32,
1644    cx: i32,
1645    cy: i32,
1646    r: i32,
1647    color: u32,
1648    blend: u8,
1649) {
1650    let r = r.clamp(0, 20000); // guard against overflow / runaway from tiny depths
1651    if r == 0 {
1652        return;
1653    }
1654    let mut x = 0;
1655    let mut y = r;
1656    let mut d = 3 - 2 * r;
1657    while x <= y {
1658        plot_circle_points(buf, w, h, cx, cy, x, y, color, blend);
1659        if d < 0 {
1660            d += 4 * x + 6;
1661        } else {
1662            d += 4 * (x - y) + 10;
1663            y -= 1;
1664        }
1665        x += 1;
1666    }
1667}
1668
1669#[allow(clippy::too_many_arguments)]
1670fn plot_circle_points(
1671    buf: &mut [u32],
1672    w: i32,
1673    h: i32,
1674    cx: i32,
1675    cy: i32,
1676    x: i32,
1677    y: i32,
1678    color: u32,
1679    blend: u8,
1680) {
1681    let points = [
1682        (cx + x, cy + y),
1683        (cx - x, cy + y),
1684        (cx + x, cy - y),
1685        (cx - x, cy - y),
1686        (cx + y, cy + x),
1687        (cx - y, cy + x),
1688        (cx + y, cy - x),
1689        (cx - y, cy - x),
1690    ];
1691    for &(px, py) in &points {
1692        if px >= 0 && px < w && py >= 0 && py < h {
1693            put_px(buf, (py * w + px) as usize, color, blend);
1694        }
1695    }
1696}
1697
1698#[allow(clippy::too_many_arguments)]
1699fn draw_circle_filled(
1700    buf: &mut [u32],
1701    w: i32,
1702    h: i32,
1703    cx: i32,
1704    cy: i32,
1705    r: i32,
1706    color: u32,
1707    blend: u8,
1708) {
1709    if r <= 0 {
1710        return;
1711    }
1712    for dy in -r..=r {
1713        let dx_max = ((r * r - dy * dy) as f64).sqrt() as i32;
1714        let py = cy + dy;
1715        if py < 0 || py >= h {
1716            continue;
1717        }
1718        for dx in -dx_max..=dx_max {
1719            let px = cx + dx;
1720            if px >= 0 && px < w {
1721                put_px(buf, (py * w + px) as usize, color, blend);
1722            }
1723        }
1724    }
1725}
1726
1727#[cfg(test)]
1728mod draw_tests {
1729    use super::*;
1730
1731    #[test]
1732    fn filled_circle_actually_writes_pixels() {
1733        let mut buf = vec![0u32; 100 * 100];
1734        draw_circle_filled(&mut buf, 100, 100, 50, 50, 10, 0xFF00FF, 0);
1735        assert_eq!(buf[50 * 100 + 50], 0xFF00FF, "centre pixel must be filled");
1736        assert_eq!(buf[0], 0, "far corner must stay clear");
1737        let n = buf.iter().filter(|&&p| p != 0).count();
1738        assert!(n > 200 && n < 500, "r=10 disc area ≈ 314, got {n}");
1739    }
1740
1741    #[test]
1742    fn circle_outline_writes_a_ring() {
1743        let mut buf = vec![0u32; 100 * 100];
1744        draw_circle_outline(&mut buf, 100, 100, 50, 50, 20, 0x00FF00, 0);
1745        assert_eq!(buf[50 * 100 + 50], 0, "outline must NOT fill the centre");
1746        assert!(
1747            buf.iter().any(|&p| p == 0x00FF00),
1748            "outline must draw a ring"
1749        );
1750    }
1751
1752    #[test]
1753    fn additive_blend_accumulates_channels() {
1754        let mut buf = vec![0x202020u32; 1];
1755        put_px(&mut buf, 0, 0x404040, 1);
1756        assert_eq!(buf[0], 0x606060);
1757    }
1758}
1759
1760// ─── Interpreter ─────────────────────────────────────────────────────────────
1761
1762/// Customizable colour palette for the vector UI toolkit (packed 0x00RRGGBB).
1763/// `ui_theme(...)` sets it; every widget falls back to these and accepts a
1764/// trailing `r,g,b` override.
1765#[derive(Clone, Copy)]
1766pub struct UiTheme {
1767    pub primary: u32,
1768    pub accent: u32,
1769    pub track: u32,
1770    pub warn: u32,
1771    pub text: u32,
1772    pub bg: u32,
1773}
1774
1775impl Default for UiTheme {
1776    fn default() -> Self {
1777        Self {
1778            primary: 0x00D2FF, // holographic cyan
1779            accent: 0x28FFB4,  // mint
1780            track: 0x2C3E64,   // dim slate
1781            warn: 0xFF5A5A,    // alert red
1782            text: 0xBEEBFF,    // pale cyan
1783            bg: 0x0A1018,      // near-black panel
1784        }
1785    }
1786}
1787
1788#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
1789pub struct Interpreter {
1790    globals: HashMap<String, Expr>,
1791    /// Globals evaluated ONCE at program start (immutable after load).
1792    /// call_named clones this instead of re-evaluating every global per call.
1793    global_seed: Env,
1794    functions: FxHashMap<String, Rc<FnDef>>,
1795    /// `form` definitions: struct name → ordered field names.
1796    pub(crate) structs: HashMap<String, Vec<String>>,
1797    /// `choose` variants: variant name (bare and `Enum::Variant`) → (enum name, arity).
1798    enum_variants: HashMap<String, (String, usize)>,
1799    _modules: HashMap<String, Vec<FnDef>>,
1800    gfx: RefCell<GfxState>,
1801    svg: RefCell<Option<SvgWriter>>,
1802    /// Directory of the primary source file, for relative `use` resolution.
1803    pub source_dir: Option<std::path::PathBuf>,
1804    /// Files already loaded — prevents circular imports.
1805    loaded_files: std::collections::HashSet<String>,
1806    /// Optional audio engine — `None` if no audio device is available.
1807    #[cfg(not(target_arch = "wasm32"))]
1808    audio: Option<AudioEngine>,
1809    #[cfg(not(target_arch = "wasm32"))]
1810    fft: RefCell<FftAnalyzer>,
1811    fft_bands_cache: RefCell<Vec<f32>>,
1812    /// Real-time clock — seconds since Unix epoch at startup (f64 works on both
1813    /// native and wasm32; Instant is not available on wasm32).
1814    start_time_secs: f64,
1815    /// Frame counter — incremented at each present()
1816    frame_num: u64,
1817    /// Target framerate used to pace `present()` on wasm32.
1818    #[cfg(target_arch = "wasm32")]
1819    wasm_target_fps: f64,
1820    /// Next frame deadline (ms since epoch) for wasm frame pacing.
1821    #[cfg(target_arch = "wasm32")]
1822    wasm_next_present_ms: f64,
1823    /// Random state for rand() builtin (xorshift)
1824    rand_state: u64,
1825    /// Microphone input (Phase 1 audio reactivity)
1826    #[cfg(not(target_arch = "wasm32"))]
1827    mic: Option<ling_mic::MicInput>,
1828    /// Persistent KEM keypairs (knot / hybrid identities), referenced by handle.
1829    #[cfg(not(target_arch = "wasm32"))]
1830    crypto_ids: Vec<ling_crypto::KnotIdentity>,
1831    /// Persistent Ed25519 signing keypairs, referenced by handle.
1832    #[cfg(not(target_arch = "wasm32"))]
1833    ed25519_ids: Vec<ling_crypto::Ed25519Keypair>,
1834    /// Persistent ML-DSA-65 (post-quantum) signing keypairs, referenced by handle.
1835    #[cfg(not(target_arch = "wasm32"))]
1836    mldsa_ids: Vec<ling_crypto::MlDsa65Keypair>,
1837    /// Editable text-input buffer (ling-ui text fields).
1838    text_buffer: String,
1839    /// Frame counter for record_frame().
1840    record_n: u32,
1841    /// Accumulated microphone samples (for turning sound into crypto donuts).
1842    #[cfg(not(target_arch = "wasm32"))]
1843    mic_buffer: Vec<f32>,
1844    /// Loaded vector UI fonts, referenced by handle (index) from `font_load`.
1845    #[cfg(not(target_arch = "wasm32"))]
1846    fonts: Vec<ling_graphics::VectorFont>,
1847    /// Loaded raster images (PNG/etc.), referenced by handle (index) from
1848    /// `image_load` — read pixel-by-pixel via `image_pixel_r/g/b/a`.
1849    images: Vec<image::RgbaImage>,
1850    /// Customizable UI colour palette (set via `ui_theme`).
1851    ui_theme: UiTheme,
1852    /// Left-mouse state on the previous frame — for widget click-edge detection.
1853    mouse_was_down: bool,
1854    /// Live music engine (decode playback + GM synth) — lazily initialised.
1855    #[cfg(not(target_arch = "wasm32"))]
1856    music: Option<ling_music::MusicEngine>,
1857    #[cfg(not(target_arch = "wasm32"))]
1858    music_init: bool,
1859    /// Decoded tracks (for analysis + playback), by `music_load` handle.
1860    tracks: Vec<ling_music::DecodedAudio>,
1861    /// Parsed `.lrc` lyrics, by `music_lrc` handle.
1862    lyrics: Vec<ling_music::Lyrics>,
1863    /// Parsed MIDI songs, by `music_midi_load` handle.
1864    midis: Vec<ling_music::MidiSong>,
1865    /// Soft bodies (deformable balls), by `soft_ball` handle.
1866    soft_bodies: Vec<ling_physics::soft::SoftBody>,
1867    /// Rigid-body world (angular dynamics), shared by `rb_*`.
1868    rigid_world: ling_physics::rigid::PhysicsWorld,
1869    /// Liquid grids (water/oil), by `liquid_new` handle.
1870    liquids: Vec<ling_physics::liquid::LiquidGrid>,
1871    meshes: Vec<crate::gfx::shapes::ColorMesh>,
1872    /// Loaded glTF models (skeleton + skin weights + animations), by `mesh_load` handle.
1873    gltf_models: std::cell::RefCell<Vec<ling_physics::gltf::GltfModel>>,
1874    /// Active cinematic dialog box (Ocarina/Majora-style), if any.
1875    dialog: Option<ling_game::dialog::Dialog>,
1876    /// Dialog highlight colours by role: text, name, place, item (0x00RRGGBB).
1877    dialog_colors: [u32; 4],
1878    /// Active user-function call frames (names), for error tracebacks.
1879    frames: Vec<String>,
1880    /// Snapshot of `frames` captured the moment a runtime error first arose
1881    /// (the deepest call). Consumed by `take_error_trace`.
1882    error_trace: Option<Vec<String>>,
1883    /// Unified input (gamepads/joysticks/VR/touch via the ling-input
1884    /// "Sensorium"). Lazily initialised on the first `pad_*` builtin call;
1885    /// `None` if no native input backend is available.
1886    #[cfg(not(target_arch = "wasm32"))]
1887    input: RefCell<Option<InputState>>,
1888    /// Routes registered by `http_route(method, path, handler)`, consumed
1889    /// by `http_serve`. Lives here (not a global, unlike `net`) because the
1890    /// handler is a `Value::Fn` closure — `Value` holds `Rc` and so can't
1891    /// safely live in a `static`.
1892    #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1893    http_routes: Vec<(String, String, Value)>,
1894    /// SQLite handle opened by `db_open` — plain rusqlite Connection, no
1895    /// pool: the interpreter is single-threaded, so one connection is both
1896    /// sufficient and contention-free.
1897    #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1898    db: Option<ling_http::rusqlite::Connection>,
1899    /// `(url_prefix, disk_dir)` pairs registered by `http_static`, consumed by
1900    /// `http_serve` — served as raw bytes, bypassing the String-only Value bridge.
1901    #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1902    http_static_dirs: Vec<(String, String)>,
1903    /// Background jobs started by `http_post_async`, polled by `http_job_poll`.
1904    /// A plain `Arc<Mutex<..>>` handle (not `Value`), so it's fine to touch from
1905    /// the background tokio task that fills in each job's result.
1906    #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1907    async_jobs: web::AsyncJobs,
1908    /// Google sign-in exchanges started by `oauth_google_start`, polled by
1909    /// `oauth_google_done`/`_error`/`_sub`/`_email`/`_name`. Separate from
1910    /// `async_jobs` because a login result has three fields, not one string.
1911    #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1912    oauth_jobs: web::OAuthJobs,
1913}
1914
1915/// Live gamepad input state: a ling-input hub fed by the native `gilrs` backend.
1916#[cfg(not(target_arch = "wasm32"))]
1917struct InputState {
1918    sensorium: ling_input::Sensorium,
1919    backend: ling_input::backend::GilrsBackend,
1920}
1921
1922impl Default for Interpreter {
1923    fn default() -> Self {
1924        Self::new()
1925    }
1926}
1927
1928impl Interpreter {
1929    pub fn new() -> Self {
1930        #[cfg(not(target_arch = "wasm32"))]
1931        let audio = AudioEngine::new()
1932            .map_err(|e| eprintln!("audio init failed (no sound): {e}"))
1933            .ok();
1934        Self {
1935            globals: HashMap::new(),
1936            global_seed: new_env(),
1937            functions: FxHashMap::default(),
1938            structs: HashMap::new(),
1939            enum_variants: HashMap::new(),
1940            _modules: HashMap::new(),
1941            gfx: RefCell::new(GfxState::new()),
1942            svg: RefCell::new(None),
1943            source_dir: None,
1944            loaded_files: std::collections::HashSet::new(),
1945            #[cfg(not(target_arch = "wasm32"))]
1946            audio,
1947            #[cfg(not(target_arch = "wasm32"))]
1948            fft: RefCell::new(FftAnalyzer::new(2048, 44100)),
1949            fft_bands_cache: RefCell::new(vec![]),
1950            start_time_secs: crate::runtime::now_secs(),
1951            frame_num: 0,
1952            #[cfg(target_arch = "wasm32")]
1953            wasm_target_fps: 60.0,
1954            #[cfg(target_arch = "wasm32")]
1955            wasm_next_present_ms: 0.0,
1956            rand_state: 0x123456789ABCDEF,
1957            #[cfg(not(target_arch = "wasm32"))]
1958            mic: None,
1959            #[cfg(not(target_arch = "wasm32"))]
1960            crypto_ids: Vec::new(),
1961            #[cfg(not(target_arch = "wasm32"))]
1962            ed25519_ids: Vec::new(),
1963            #[cfg(not(target_arch = "wasm32"))]
1964            mldsa_ids: Vec::new(),
1965            text_buffer: String::new(),
1966            record_n: 0,
1967            #[cfg(not(target_arch = "wasm32"))]
1968            mic_buffer: Vec::new(),
1969            #[cfg(not(target_arch = "wasm32"))]
1970            fonts: Vec::new(),
1971            images: Vec::new(),
1972            ui_theme: UiTheme::default(),
1973            mouse_was_down: false,
1974            #[cfg(not(target_arch = "wasm32"))]
1975            music: None,
1976            #[cfg(not(target_arch = "wasm32"))]
1977            music_init: false,
1978            tracks: Vec::new(),
1979            lyrics: Vec::new(),
1980            midis: Vec::new(),
1981            soft_bodies: Vec::new(),
1982            rigid_world: ling_physics::rigid::PhysicsWorld::new(),
1983            liquids: Vec::new(),
1984            meshes: Vec::new(),
1985            gltf_models: std::cell::RefCell::new(Vec::new()),
1986            dialog: None,
1987            dialog_colors: [0xE6F2FF, 0xFFD24A, 0x4AD2FF, 0x6CFF8C], // text · name · place · item
1988            frames: Vec::new(),
1989            error_trace: None,
1990            #[cfg(not(target_arch = "wasm32"))]
1991            input: RefCell::new(None),
1992            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1993            http_routes: Vec::new(),
1994            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1995            db: None,
1996            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1997            http_static_dirs: Vec::new(),
1998            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1999            async_jobs: web::AsyncJobs::new(),
2000            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
2001            oauth_jobs: web::OAuthJobs::new(),
2002        }
2003    }
2004
2005    /// Lazily initialise the input system and advance it one frame; returns the
2006    /// number of connected gamepads. Call once per game-loop iteration (like a
2007    /// window update) before reading `pad_*` state.
2008    #[cfg(not(target_arch = "wasm32"))]
2009    fn pad_poll(&self) -> usize {
2010        let mut slot = self.input.borrow_mut();
2011        if slot.is_none() {
2012            match ling_input::backend::GilrsBackend::new() {
2013                Ok(backend) => {
2014                    *slot = Some(InputState { sensorium: ling_input::Sensorium::new(4), backend });
2015                },
2016                Err(_) => return 0,
2017            }
2018        }
2019        let st = slot.as_mut().unwrap();
2020        st.sensorium.begin_frame();
2021        st.sensorium.pump(&mut st.backend);
2022        st.sensorium.update(1.0 / 60.0);
2023        st.sensorium.devices.count()
2024    }
2025
2026    /// Read player `slot`'s gamepad with `f`, or return `default` if there is no
2027    /// input system / no such pad.
2028    #[cfg(not(target_arch = "wasm32"))]
2029    fn with_pad<T>(&self, slot: usize, default: T, f: impl FnOnce(&ling_input::Gamepad) -> T) -> T {
2030        let inp = self.input.borrow();
2031        match inp.as_ref().and_then(|s| s.sensorium.player(slot)) {
2032            Some(p) => f(p),
2033            None => default,
2034        }
2035    }
2036
2037    /// Take the call-stack snapshot captured at the deepest runtime error, if any.
2038    /// Frames are ordered outermost-first (entry point first, failing call last).
2039    pub fn take_error_trace(&mut self) -> Vec<String> {
2040        self.error_trace.take().unwrap_or_default()
2041    }
2042
2043    #[cfg(target_arch = "wasm32")]
2044    fn wasm_pace_frame(&mut self) {
2045        let fps = self.wasm_target_fps.max(1.0);
2046        let frame_ms = 1000.0 / fps;
2047        let now = js_sys::Date::now();
2048        if self.wasm_next_present_ms <= 0.0 {
2049            self.wasm_next_present_ms = now + frame_ms;
2050            return;
2051        }
2052
2053        let wait_ms = (self.wasm_next_present_ms - now).floor() as i32;
2054        if wait_ms > 0 {
2055            wasm_sleep_ms(wait_ms);
2056        }
2057
2058        let after = js_sys::Date::now();
2059        if after > self.wasm_next_present_ms + frame_ms * 3.0 {
2060            self.wasm_next_present_ms = after + frame_ms;
2061        } else {
2062            self.wasm_next_present_ms += frame_ms;
2063        }
2064    }
2065
2066    /// Run `body`, recording `name` as a call frame and snapshotting the stack on
2067    /// the first runtime error so a traceback can be reported.
2068    fn framed<T, F>(&mut self, name: &str, body: F) -> Result<T, EvalErr>
2069    where
2070        F: FnOnce(&mut Self) -> Result<T, EvalErr>,
2071    {
2072        self.frames.push(name.to_string());
2073        let result = body(self);
2074        if matches!(result, Err(EvalErr::Runtime(_))) && self.error_trace.is_none() {
2075            self.error_trace = Some(self.frames.clone());
2076        }
2077        self.frames.pop();
2078        result
2079    }
2080
2081    /// Render the active dialog box: beveled frame + dark fill, then the visible
2082    /// (typewriter-revealed) text word-wrapped with colour-coded runs, plus a
2083    /// blinking advance arrow once the page is fully typed.
2084    #[cfg(not(target_arch = "wasm32"))]
2085    fn render_dialog(&mut self, x: f32, y: f32, w: f32, h: f32, font: i64, t: f32) {
2086        let (runs, typing) = match &self.dialog {
2087            Some(d) if !d.is_closed() => {
2088                let runs: Vec<(String, usize, bool)> = d
2089                    .visible_runs()
2090                    .into_iter()
2091                    .map(|r| (r.text, r.role.index(), r.newline_before))
2092                    .collect();
2093                (runs, d.is_typing())
2094            },
2095            _ => return,
2096        };
2097        let colors = self.dialog_colors;
2098        // ── frame + fill ──
2099        let b = 12.0;
2100        let corners: Vec<[f32; 2]> = vec![
2101            [x + b, y],
2102            [x + w - b, y],
2103            [x + w, y + b],
2104            [x + w, y + h - b],
2105            [x + w - b, y + h],
2106            [x + b, y + h],
2107            [x, y + h - b],
2108            [x, y + b],
2109            [x + b, y],
2110        ];
2111        {
2112            let mut gfx = self.gfx.borrow_mut();
2113            let (bw, bh) = (gfx.width, gfx.height);
2114            crate::gfx::raster::fill_contours_aa(
2115                &mut gfx.buffer,
2116                bw,
2117                bh,
2118                0x0A1018,
2119                false,
2120                std::slice::from_ref(&corners),
2121            );
2122            for seg in corners.windows(2) {
2123                crate::gfx::raster::draw_line_aa(
2124                    &mut gfx.buffer,
2125                    bw,
2126                    bh,
2127                    0x00D2FF,
2128                    false,
2129                    seg[0][0],
2130                    seg[0][1],
2131                    seg[1][0],
2132                    seg[1][1],
2133                );
2134            }
2135        }
2136        // ── word-wrapped, colour-coded text ──
2137        let px = 22.0f32;
2138        let pad = 20.0f32;
2139        let line_h = px * 1.45;
2140        let mut cx = x + pad;
2141        let mut cy = y + pad;
2142        let use_font = font >= 0 && (font as usize) < self.fonts.len();
2143        for (text, role, nl) in &runs {
2144            if *nl {
2145                cx = x + pad;
2146                cy += line_h;
2147            }
2148            for word in text.split_inclusive(' ') {
2149                let wpx = if use_font {
2150                    self.fonts[font as usize].measure(word, px)
2151                } else {
2152                    ling_ui::holo::text_width(word, px * 0.6, px * 0.24)
2153                };
2154                if cx + wpx > x + w - pad && cx > x + pad + 1.0 {
2155                    cx = x + pad;
2156                    cy += line_h;
2157                }
2158                if cy + line_h > y + h {
2159                    break;
2160                }
2161                let col = colors[(*role).min(3)];
2162                if use_font {
2163                    let glyphs = self.font_layout_2d_glyphs(font as usize, cx, cy, px, word);
2164                    let mut gfx = self.gfx.borrow_mut();
2165                    let (bw, bh, add) = (gfx.width, gfx.height, gfx.blend == 1);
2166                    for contours in &glyphs {
2167                        crate::gfx::raster::fill_contours_aa(
2168                            &mut gfx.buffer,
2169                            bw,
2170                            bh,
2171                            col,
2172                            add,
2173                            contours,
2174                        );
2175                    }
2176                } else {
2177                    let segs = ling_ui::holo::text_lines(word, cx, cy, px * 0.6, px, px * 0.24);
2178                    let mut gfx = self.gfx.borrow_mut();
2179                    let (bw, bh) = (gfx.width, gfx.height);
2180                    for s in segs {
2181                        draw_line(&mut gfx.buffer, bw, bh, col, s[0], s[1], s[2], s[3]);
2182                    }
2183                }
2184                cx += wpx;
2185            }
2186        }
2187        // ── blinking advance arrow when fully typed ──
2188        if !typing && (t * 3.0).sin() > 0.0 {
2189            let ax = x + w - 26.0;
2190            let ay = y + h - 22.0;
2191            let mut gfx = self.gfx.borrow_mut();
2192            let (bw, bh) = (gfx.width, gfx.height);
2193            crate::gfx::raster::fill_contours_aa(
2194                &mut gfx.buffer,
2195                bw,
2196                bh,
2197                0x00D2FF,
2198                false,
2199                std::slice::from_ref(&vec![
2200                    [ax - 7.0, ay],
2201                    [ax + 7.0, ay],
2202                    [ax, ay + 9.0],
2203                    [ax - 7.0, ay],
2204                ]),
2205            );
2206        }
2207    }
2208
2209    /// Lazily start the music engine on first use (playback/synth need a device;
2210    /// analysis/decoding do not). Returns `false` if no audio device is available.
2211    #[cfg(not(target_arch = "wasm32"))]
2212    fn ensure_music(&mut self) -> bool {
2213        if self.music.is_some() {
2214            return true;
2215        }
2216        if self.music_init {
2217            return false;
2218        }
2219        self.music_init = true;
2220        match ling_music::MusicEngine::new() {
2221            Ok(m) => {
2222                self.music = Some(m);
2223                true
2224            },
2225            Err(e) => {
2226                eprintln!("music engine init failed (no music playback): {e}");
2227                false
2228            },
2229        }
2230    }
2231
2232    #[cfg(target_arch = "wasm32")]
2233    fn wasm_resolve_source_path(&self, path: &str) -> String {
2234        let p = path.trim();
2235        if p.is_empty() {
2236            return String::new();
2237        }
2238        if p.contains("://") || p.starts_with('/') || p.starts_with("./") || p.starts_with("../") {
2239            return p.to_string();
2240        }
2241        if let Some(d) = &self.source_dir {
2242            let base = d.to_string_lossy().replace('\\', "/");
2243            if !base.is_empty() {
2244                return format!(
2245                    "{}/{}",
2246                    base.trim_end_matches('/'),
2247                    p.trim_start_matches("./")
2248                );
2249            }
2250        }
2251        p.to_string()
2252    }
2253
2254    #[cfg(target_arch = "wasm32")]
2255    fn wasm_music_builtin(&mut self, name: &str, args: &[Value]) -> Result<Option<Value>, EvalErr> {
2256        match name {
2257            // music_load(path) -> track handle (decode from fetched bytes)
2258            "music_load"
2259            | "载入音乐"
2260            | "音楽読込"
2261            | "음악로드"
2262            | "โหลดเพลง"
2263            | "بارگذاری_موسیقی"
2264            | "تحميل_الموسيقى"
2265            | "טעינת_מוזיקה"
2266            | "موسیقی_لوڈ"
2267            | "charger_musique"
2268            | "musik_laden"
2269            | "загрузить_музыку" => {
2270                let path = self.arg_str(args, 0, "");
2271                let resolved = self.wasm_resolve_source_path(&path);
2272                match wasm_fetch_bytes(&resolved)
2273                    .and_then(|bytes| ling_music::from_bytes(&bytes).map_err(|e| e.to_string()))
2274                {
2275                    Ok(t) => {
2276                        let id = self.tracks.len();
2277                        self.tracks.push(t);
2278                        return Ok(Some(Value::Number(id as f64)));
2279                    },
2280                    Err(e) => {
2281                        eprintln!("music_load failed ({path}): {e}");
2282                        return Ok(Some(Value::Number(-1.0)));
2283                    },
2284                }
2285            },
2286            "music_duration"
2287            | "音乐时长"
2288            | "音楽長さ"
2289            | "음악길이"
2290            | "ความยาวเพลง"
2291            | "مدت_موسیقی"
2292            | "مدة_الموسيقى"
2293            | "משך_מוזיקה"
2294            | "موسیقی_دورانیہ"
2295            | "durée_musique"
2296            | "musik_dauer"
2297            | "длительность_музыки" => {
2298                let id = self.arg_num(args, 0, 0.0)? as i64;
2299                let d = self
2300                    .tracks
2301                    .get(id as usize)
2302                    .map(|t| t.duration)
2303                    .unwrap_or(0.0);
2304                return Ok(Some(Value::Number(d as f64)));
2305            },
2306            "music_bpm"
2307            | "节拍速度"
2308            | "テンポ"
2309            | "템포"
2310            | "จังหวะต่อนาที"
2311            | "ضربان_در_دقیقه"
2312            | "نبضات_بالدقيقة"
2313            | "פעימות_לדקה"
2314            | "بی_پی_ایم"
2315            | "bpm_musique"
2316            | "musik_bpm"
2317            | "музыка_bpm" => {
2318                let id = self.arg_num(args, 0, 0.0)? as i64;
2319                let b = self
2320                    .tracks
2321                    .get(id as usize)
2322                    .map(|t| ling_music::analysis::bpm(&t.mono, t.rate))
2323                    .unwrap_or(0.0);
2324                return Ok(Some(Value::Number(b as f64)));
2325            },
2326            "music_key"
2327            | "调性"
2328            | "調性"
2329            | "조성"
2330            | "คีย์เพลง"
2331            | "گام_موسیقی"
2332            | "مقام_الموسيقى"
2333            | "סולם_מוזיקלי"
2334            | "موسیقی_کلید"
2335            | "tonalité_musique"
2336            | "musik_tonart"
2337            | "тональность_музыки" => {
2338                let id = self.arg_num(args, 0, 0.0)? as i64;
2339                let k = self
2340                    .tracks
2341                    .get(id as usize)
2342                    .map(|t| ling_music::analysis::key_name(&t.mono, t.rate))
2343                    .unwrap_or_default();
2344                return Ok(Some(Value::Str(k)));
2345            },
2346            "music_onsets"
2347            | "音符起点"
2348            | "オンセット"
2349            | "온셋"
2350            | "จุดเริ่มเสียง"
2351            | "آغازهای_نت"
2352            | "بدايات_النغمات"
2353            | "התחלות_תווים"
2354            | "نوٹ_شروعات"
2355            | "attaques_musique"
2356            | "musik_einsätze"
2357            | "атаки_музыки" => {
2358                let id = self.arg_num(args, 0, 0.0)? as i64;
2359                let v = self
2360                    .tracks
2361                    .get(id as usize)
2362                    .map(|t| ling_music::analysis::onsets(&t.mono, t.rate))
2363                    .unwrap_or_default();
2364                return Ok(Some(Value::List(
2365                    v.into_iter()
2366                        .map(|x| Value::Number(x as f64))
2367                        .collect::<Vec<_>>()
2368                        .into(),
2369                )));
2370            },
2371            "music_beat_grid"
2372            | "节拍网格"
2373            | "ビートグリッド"
2374            | "비트그리드"
2375            | "กริดจังหวะ"
2376            | "شبکه_ضرب"
2377            | "شبكة_الإيقاع"
2378            | "רשת_פעימות"
2379            | "بیٹ_گرڈ"
2380            | "grille_temps_musique"
2381            | "musik_taktraster"
2382            | "сетка_ритма_музыки" => {
2383                let id = self.arg_num(args, 0, 0.0)? as i64;
2384                let beats = self
2385                    .tracks
2386                    .get(id as usize)
2387                    .map(|t| {
2388                        let b = ling_music::analysis::bpm(&t.mono, t.rate);
2389                        ling_music::analysis::beat_grid(&t.mono, t.rate, b)
2390                    })
2391                    .unwrap_or_default();
2392                return Ok(Some(Value::List(
2393                    beats
2394                        .into_iter()
2395                        .map(|x| Value::Number(x as f64))
2396                        .collect::<Vec<_>>()
2397                        .into(),
2398                )));
2399            },
2400            "music_lrc"
2401            | "载入歌词"
2402            | "歌詞読込"
2403            | "가사로드"
2404            | "โหลดเนื้อเพลง"
2405            | "بارگذاری_متن_ترانه"
2406            | "تحميل_كلمات_الأغنية"
2407            | "טעינת_מילות_שיר"
2408            | "گیت_متن_لوڈ"
2409            | "lrc_musique"
2410            | "musik_lrc"
2411            | "lrc_музыки" => {
2412                let path = self.arg_str(args, 0, "");
2413                let resolved = self.wasm_resolve_source_path(&path);
2414                match wasm_fetch_text(&resolved) {
2415                    Ok(text) => {
2416                        let id = self.lyrics.len();
2417                        self.lyrics.push(ling_music::Lyrics::parse(&text));
2418                        return Ok(Some(Value::Number(id as f64)));
2419                    },
2420                    Err(e) => {
2421                        eprintln!("music_lrc failed ({path}): {e}");
2422                        return Ok(Some(Value::Number(-1.0)));
2423                    },
2424                }
2425            },
2426            "music_lyric"
2427            | "当前歌词"
2428            | "現在歌詞"
2429            | "현재가사"
2430            | "เนื้อเพลงปัจจุบัน"
2431            | "متن_ترانه_فعلی"
2432            | "كلمات_الأغنية_الحالية"
2433            | "מילות_שיר_נוכחיות"
2434            | "موجودہ_گیت_متن"
2435            | "paroles_musique"
2436            | "musik_liedtext"
2437            | "текст_песни" => {
2438                let id = self.arg_num(args, 0, 0.0)? as i64;
2439                let t = self.arg_num(args, 1, 0.0)? as f32;
2440                let line = self
2441                    .lyrics
2442                    .get(id as usize)
2443                    .map(|l| l.line_at(t).to_string())
2444                    .unwrap_or_default();
2445                return Ok(Some(Value::Str(line)));
2446            },
2447            "music_midi_load"
2448            | "载入MIDI"
2449            | "MIDI読込"
2450            | "미디로드"
2451            | "โหลดมิดี"
2452            | "بارگذاری_MIDI"
2453            | "تحميل_MIDI"
2454            | "טעינת_MIDI"
2455            | "MIDI_لوڈ"
2456            | "charger_midi_musique"
2457            | "musik_midi_laden"
2458            | "загрузить_midi_музыки" => {
2459                let path = self.arg_str(args, 0, "");
2460                let resolved = self.wasm_resolve_source_path(&path);
2461                match wasm_fetch_bytes(&resolved).and_then(|bytes| {
2462                    ling_music::midi::from_bytes(&bytes).map_err(|e| e.to_string())
2463                }) {
2464                    Ok(m) => {
2465                        let id = self.midis.len();
2466                        self.midis.push(m);
2467                        return Ok(Some(Value::Number(id as f64)));
2468                    },
2469                    Err(e) => {
2470                        eprintln!("music_midi_load failed ({path}): {e}");
2471                        return Ok(Some(Value::Number(-1.0)));
2472                    },
2473                }
2474            },
2475            "music_midi_count"
2476            | "MIDI数量"
2477            | "MIDI数"
2478            | "미디수"
2479            | "จำนวนมิดี"
2480            | "تعداد_MIDI"
2481            | "عدد_MIDI"
2482            | "מספר_MIDI"
2483            | "MIDI_تعداد"
2484            | "nombre_midi_musique"
2485            | "musik_midi_anzahl"
2486            | "число_midi_музыки" => {
2487                let id = self.arg_num(args, 0, 0.0)? as i64;
2488                let n = self
2489                    .midis
2490                    .get(id as usize)
2491                    .map(|m| m.notes.len())
2492                    .unwrap_or(0);
2493                return Ok(Some(Value::Number(n as f64)));
2494            },
2495            "music_midi_notes"
2496            | "MIDI音符"
2497            | "MIDIノート"
2498            | "미디음표"
2499            | "โน้ตมิดี"
2500            | "نت‌های_MIDI"
2501            | "نغمات_MIDI"
2502            | "תווי_MIDI"
2503            | "MIDI_نوٹس"
2504            | "notes_midi_musique"
2505            | "musik_midi_noten"
2506            | "ноты_midi_музыки" => {
2507                let id = self.arg_num(args, 0, 0.0)? as i64;
2508                let mut out = Vec::new();
2509                if let Some(m) = self.midis.get(id as usize) {
2510                    for n in &m.notes {
2511                        out.push(Value::Number(n.time as f64));
2512                        out.push(Value::Number(n.midi as f64));
2513                    }
2514                }
2515                return Ok(Some(Value::List(out.into())));
2516            },
2517            "music_midi_bars"
2518            | "MIDI音条"
2519            | "MIDIバー"
2520            | "미디바"
2521            | "แท่งมิดี"
2522            | "میله‌های_MIDI"
2523            | "أعمدة_MIDI"
2524            | "עמודות_MIDI"
2525            | "MIDI_بارز"
2526            | "mesures_midi_musique"
2527            | "musik_midi_takte"
2528            | "такты_midi_музыки" => {
2529                let id = self.arg_num(args, 0, 0.0)? as i64;
2530                let mut out = Vec::new();
2531                if let Some(m) = self.midis.get(id as usize) {
2532                    for n in &m.notes {
2533                        out.push(Value::Number(n.time as f64));
2534                        out.push(Value::Number(n.midi as f64));
2535                        out.push(Value::Number(n.dur as f64));
2536                    }
2537                }
2538                return Ok(Some(Value::List(out.into())));
2539            },
2540            "music_judge"
2541            | "判定"
2542            | "判定する"
2543            | "판정"
2544            | "ตัดสินจังหวะ"
2545            | "داوری_ضرب"
2546            | "حكم_الإيقاع"
2547            | "שיפוט_קצב"
2548            | "بیٹ_فیصلہ"
2549            | "juger_musique"
2550            | "musik_bewerten"
2551            | "оценить_музыку" => {
2552                let delta_ms = self.arg_num(args, 0, 9999.0)? as f32;
2553                return Ok(Some(Value::Number(
2554                    ling_music::Grade::judge(delta_ms).index() as f64,
2555                )));
2556            },
2557            "music_grade_name"
2558            | "判定名"
2559            | "判定名称"
2560            | "판정이름"
2561            | "ชื่อการตัดสิน"
2562            | "نام_رتبه"
2563            | "اسم_التقييم"
2564            | "שם_דירוג"
2565            | "گریڈ_نام"
2566            | "nom_grade_musique"
2567            | "musik_bewertungsname"
2568            | "имя_оценки_музыки" => {
2569                let idx = self.arg_num(args, 0, 4.0)? as i32;
2570                return Ok(Some(Value::Str(
2571                    ling_music::Grade::from_index(idx).name().to_string(),
2572                )));
2573            },
2574            "music_note_name"
2575            | "音名"
2576            | "音名称"
2577            | "음이름"
2578            | "ชื่อโน้ต"
2579            | "نام_نت"
2580            | "اسم_النغمة"
2581            | "שם_תו"
2582            | "نوٹ_نام"
2583            | "nom_note_musique"
2584            | "musik_notenname"
2585            | "имя_ноты_музыки" => {
2586                let hz = self.arg_num(args, 0, 0.0)? as f32;
2587                return Ok(Some(Value::Str(ling_music::note::hz_to_name(hz))));
2588            },
2589            "music_hz"
2590            | "音符频率"
2591            | "音符周波数"
2592            | "음표주파수"
2593            | "ความถี่โน้ต"
2594            | "فرکانس_نت"
2595            | "تردد_النغمة"
2596            | "תדר_תו"
2597            | "نوٹ_ہرٹز"
2598            | "hz_musique"
2599            | "musik_hz"
2600            | "музыка_гц" => {
2601                let midi = match args.get(0) {
2602                    Some(Value::Str(s)) => ling_music::note::parse_pitch(s).unwrap_or(69),
2603                    Some(Value::Number(n)) => *n as i32,
2604                    _ => 69,
2605                };
2606                return Ok(Some(Value::Number(
2607                    ling_music::note::midi_to_hz(midi as f32) as f64,
2608                )));
2609            },
2610            "music_pitch_score"
2611            | "音准评分"
2612            | "音程スコア"
2613            | "음정점수"
2614            | "คะแนนเสียง"
2615            | "امتیاز_زیروبمی"
2616            | "درجة_طبقة_الصوت"
2617            | "ציון_גובה_צליל"
2618            | "پچ_اسکور"
2619            | "score_hauteur_musique"
2620            | "musik_tonhöhen_punktzahl"
2621            | "счёт_высоты_тона" => {
2622                let hz = self.arg_num(args, 0, 0.0)? as f32;
2623                let target = self.arg_num(args, 1, 0.0)? as f32;
2624                return Ok(Some(Value::Number(
2625                    ling_music::karaoke::pitch_score(hz, target) as f64,
2626                )));
2627            },
2628
2629            // ── Playback ──────────────────────────────────────────────────────
2630            "music_play"
2631            | "播放音乐"
2632            | "音楽再生"
2633            | "음악재생"
2634            | "เล่นเพลง"
2635            | "پخش_موسیقی"
2636            | "شغّل_الموسيقى"
2637            | "נגן_מוזיקה"
2638            | "موسیقی_چلاؤ"
2639            | "jouer_musique"
2640            | "musik_abspielen"
2641            | "играть_музыку" => {
2642                let id = self.arg_num(args, 0, 0.0)? as usize;
2643                if let Some(t) = self.tracks.get(id) {
2644                    crate::gfx::audio_web::play_music(id, &t.stereo, t.channels, t.rate, 1.0);
2645                }
2646                return Ok(Some(Value::Unit));
2647            },
2648            "music_pause"
2649            | "暂停音乐"
2650            | "音楽一時停止"
2651            | "음악일시정지"
2652            | "หยุดเพลงชั่วคราว"
2653            | "music_stop"
2654            | "停止音乐"
2655            | "音楽停止"
2656            | "음악정지"
2657            | "หยุดเพลง"
2658            | "مکث_موسیقی"
2659            | "ألبث_الموسيقى"
2660            | "השהה_מוזיקה"
2661            | "موسیقی_روکو_مؤقت"
2662            | "pause_musique"
2663            | "musik_pausieren"
2664            | "пауза_музыки" => {
2665                let id = self.arg_num(args, 0, 0.0)? as usize;
2666                crate::gfx::audio_web::stop_music(id);
2667                return Ok(Some(Value::Unit));
2668            },
2669            "music_seek"
2670            | "定位音乐"
2671            | "音楽シーク"
2672            | "음악탐색"
2673            | "ค้นหาเพลง"
2674            | "جستجوی_موسیقی"
2675            | "ابحث_في_الموسيقى"
2676            | "חפש_במוזיקה"
2677            | "موسیقی_تلاش"
2678            | "chercher_musique"
2679            | "musik_suchen"
2680            | "перемотать_музыку" => {
2681                // Seek is not straightforward on AudioBufferSourceNode; no-op for now.
2682                return Ok(Some(Value::Unit));
2683            },
2684            "music_pos"
2685            | "音乐位置"
2686            | "音楽位置"
2687            | "음악위치"
2688            | "ตำแหน่งเพลง"
2689            | "موقعیت_موسیقی"
2690            | "موضع_الموسيقى"
2691            | "מיקום_מוזיקה"
2692            | "موسیقی_مقام"
2693            | "position_musique"
2694            | "musik_position"
2695            | "позиция_музыки" => {
2696                return Ok(Some(Value::Number(
2697                    crate::gfx::audio_web::current_music_position(),
2698                )));
2699            },
2700            "music_volume"
2701            | "音乐音量"
2702            | "音楽音量"
2703            | "음악음량"
2704            | "ระดับเพลง"
2705            | "بلندی_موسیقی"
2706            | "مستوى_الموسيقى"
2707            | "עוצמת_מוזיקה"
2708            | "موسیقی_شدت"
2709            | "volume_musique"
2710            | "musik_lautstärke"
2711            | "громкость_музыки" => {
2712                let vol = self.arg_num(args, 0, 0.8)? as f32;
2713                // Apply to the most-recently started slot (slot 0 is typical).
2714                crate::gfx::audio_web::set_music_volume(0, vol);
2715                return Ok(Some(Value::Unit));
2716            },
2717
2718            // ── FFT bands at current playback position ─────────────────────
2719            "music_fft"
2720            | "音乐频谱"
2721            | "音楽スペクトル"
2722            | "음악스펙트럼"
2723            | "สเปกตรัมเพลง"
2724            | "طیف_موسیقی"
2725            | "طيف_الموسيقى"
2726            | "ספקטרום_מוזיקה"
2727            | "میوزک_اسپیکٹرم"
2728            | "fft_musique"
2729            | "musik_fft"
2730            | "fft_музыки" => {
2731                let id = self.arg_num(args, 0, 0.0)? as usize;
2732                let nbands = self.arg_num(args, 1, 16.0)? as usize;
2733                let pos = crate::gfx::audio_web::current_music_position() as f32;
2734                let bands = if let Some(t) = self.tracks.get(id) {
2735                    ling_music::analysis::fft_bands_at_pos(&t.mono, t.rate, pos, nbands)
2736                } else {
2737                    vec![0.0f32; nbands]
2738                };
2739                return Ok(Some(Value::List(
2740                    bands
2741                        .into_iter()
2742                        .map(|x| Value::Number(x as f64))
2743                        .collect::<Vec<_>>()
2744                        .into(),
2745                )));
2746            },
2747
2748            _ => {},
2749        }
2750        Ok(None)
2751    }
2752
2753    /// Lay out `text` for font `id` at size `px`, returning every glyph contour as
2754    /// a screen-space polyline (x→right, y→down). `(x, y)` is the text box top-left;
2755    /// the baseline is placed `ascent*px` below it. Curves are flattened to 0.3 px.
2756    #[cfg(not(target_arch = "wasm32"))]
2757    fn font_layout_2d(
2758        &mut self,
2759        id: usize,
2760        x: f32,
2761        y: f32,
2762        px: f32,
2763        text: &str,
2764    ) -> Vec<Vec<[f32; 2]>> {
2765        let mut out = Vec::new();
2766        for g in self.font_layout_2d_glyphs(id, x, y, px, text) {
2767            out.extend(g);
2768        }
2769        out
2770    }
2771
2772    /// Same as [`font_layout_2d`] but grouped per glyph (so a fill can apply the
2773    /// non-zero winding rule within each glyph, preserving interior holes).
2774    #[cfg(not(target_arch = "wasm32"))]
2775    fn font_layout_2d_glyphs(
2776        &mut self,
2777        id: usize,
2778        x: f32,
2779        y: f32,
2780        px: f32,
2781        text: &str,
2782    ) -> Vec<Vec<Vec<[f32; 2]>>> {
2783        let font = &mut self.fonts[id];
2784        let asc = font.ascent();
2785        let tol = 0.3 / px;
2786        let mut pen = 0.0f32;
2787        let mut glyphs = Vec::new();
2788        for ch in text.chars() {
2789            let go = font.glyph_outline(ch, tol);
2790            let mut contours = Vec::with_capacity(go.polylines.len());
2791            for pl in &go.polylines {
2792                let mapped: Vec<[f32; 2]> = pl
2793                    .iter()
2794                    .map(|p| [x + (pen + p[0]) * px, y + (asc - p[1]) * px])
2795                    .collect();
2796                contours.push(mapped);
2797            }
2798            glyphs.push(contours);
2799            pen += go.advance;
2800        }
2801        glyphs
2802    }
2803
2804    /// Register every item (functions, structs, globals) and evaluate the
2805    /// non-`do` globals into `global_seed`, WITHOUT running the entry. Used to
2806    /// prime the JIT's fallback interpreter so cranelift-skipped (oversized)
2807    /// functions can still be interpreted with full access to globals + peers.
2808    pub fn register_program(&mut self, program: &Program) -> Result<(), String> {
2809        for item in &program.items {
2810            self.register_item("", item)?;
2811        }
2812        let mut env = new_env();
2813        let non_do: Vec<_> = self
2814            .globals
2815            .iter()
2816            .filter(|(_, e)| !matches!(e, Expr::Do(_)))
2817            .map(|(k, e)| (k.clone(), e.clone()))
2818            .collect();
2819        let mut pending: Vec<(String, Expr)> = Vec::new();
2820        for (k, expr) in &non_do {
2821            let mut tmp = new_env();
2822            if let Ok(v) = self.eval_expr(expr, &mut tmp) {
2823                env.insert(k.clone(), v);
2824            } else {
2825                pending.push((k.clone(), expr.clone()));
2826            }
2827        }
2828        for (k, expr) in &pending {
2829            let mut tmp = env.clone();
2830            if let Ok(v) = self.eval_expr(expr, &mut tmp) {
2831                env.insert(k.clone(), v);
2832            }
2833        }
2834        self.global_seed = env;
2835        Ok(())
2836    }
2837
2838    pub fn run_program(&mut self, program: &Program) -> Result<(), String> {
2839        self.register_program(program)?;
2840        let entry = self
2841            .find_entry()
2842            .ok_or("no entry point — need `bind start = do {...}` or `ผูก เริ่ม = ทำ {...}`")?;
2843        let mut env = self.global_seed.clone();
2844        self.framed("start", |me| me.eval_expr(&entry, &mut env))
2845            .map(|_| ())
2846            .map_err(|e| match e {
2847                EvalErr::Runtime(s) => s,
2848                EvalErr::Return(_) => "unexpected top-level return".to_string(),
2849                EvalErr::Break => "unexpected break at top level".to_string(),
2850            })
2851    }
2852
2853    fn register_item(&mut self, ns: &str, item: &Item) -> Result<(), String> {
2854        match item {
2855            Item::Bind(name, expr) => {
2856                let key = if ns.is_empty() {
2857                    name.clone()
2858                } else {
2859                    format!("{ns}::{name}")
2860                };
2861                self.globals.insert(key, expr.clone());
2862            },
2863            Item::Fn(def) => {
2864                let key = if ns.is_empty() {
2865                    def.name.clone()
2866                } else {
2867                    format!("{ns}::{}", def.name)
2868                };
2869                self.functions.insert(key, Rc::new(def.clone()));
2870            },
2871            Item::Mod(name, body) => {
2872                let child_ns = if ns.is_empty() {
2873                    name.clone()
2874                } else {
2875                    format!("{ns}::{name}")
2876                };
2877                for child in body {
2878                    self.register_item(&child_ns, child)?;
2879                }
2880            },
2881            Item::TypeAlias(_, _) => {},
2882            Item::Struct(name, fields) => {
2883                self.structs.insert(name.clone(), fields.clone());
2884                if !ns.is_empty() {
2885                    self.structs.insert(format!("{ns}::{name}"), fields.clone());
2886                }
2887            },
2888            Item::Enum(name, variants) => {
2889                for v in variants {
2890                    self.enum_variants
2891                        .insert(v.name.clone(), (name.clone(), v.arity));
2892                    self.enum_variants
2893                        .insert(format!("{name}::{}", v.name), (name.clone(), v.arity));
2894                    if !ns.is_empty() {
2895                        self.enum_variants
2896                            .insert(format!("{ns}::{name}::{}", v.name), (name.clone(), v.arity));
2897                    }
2898                }
2899            },
2900            Item::Use { path, alias } => {
2901                self.load_module(path, alias.as_deref(), ns)?;
2902            },
2903        }
2904        Ok(())
2905    }
2906
2907    /// Resolve `path` relative to `source_dir`, load and parse it, then
2908    /// register all its definitions.  If `alias` is given, every name is
2909    /// prefixed with `<parent_ns>::<alias>`.  Circular imports are silently
2910    /// skipped.
2911    fn load_module(
2912        &mut self,
2913        path: &str,
2914        alias: Option<&str>,
2915        parent_ns: &str,
2916    ) -> Result<(), String> {
2917        // ── Wasm32: no filesystem — use the pre-registered module registry ──
2918        #[cfg(target_arch = "wasm32")]
2919        let (source, sub_dir) = {
2920            // Skip if already loaded (circular import guard)
2921            if self.loaded_files.contains(path) {
2922                return Ok(());
2923            }
2924            self.loaded_files.insert(path.to_string());
2925
2926            let src = crate::runtime::get_wasm_module(path)
2927                .or_else(|| crate::runtime::get_wasm_module(&format!("{}.ling", path)))
2928                .ok_or_else(|| format!("use: cannot find module '{path}'"))?;
2929            (src, None::<std::path::PathBuf>)
2930        };
2931
2932        // ── Native: resolve against filesystem ──
2933        #[cfg(not(target_arch = "wasm32"))]
2934        let (source, sub_dir) = {
2935            let base_dir = self
2936                .source_dir
2937                .clone()
2938                .unwrap_or_else(|| std::path::PathBuf::from("."));
2939            let raw = std::path::Path::new(path);
2940            let candidates: Vec<std::path::PathBuf> = vec![
2941                base_dir.join(format!("{}.ling", path)),
2942                base_dir.join(format!("{}.灵", path)),
2943                base_dir.join(format!("{}.령", path)),
2944                base_dir.join(format!("{}.霊", path)),
2945                base_dir.join(format!("{}.ลิง", path)),
2946                base_dir.join(raw),
2947                std::path::PathBuf::from(format!("{}.ling", path)),
2948                std::path::PathBuf::from(path),
2949            ];
2950
2951            let resolved = candidates
2952                .into_iter()
2953                .find(|p| p.exists())
2954                .ok_or_else(|| format!("use: cannot find module '{path}'"))?;
2955
2956            let canonical = resolved
2957                .canonicalize()
2958                .unwrap_or_else(|_| resolved.clone())
2959                .to_string_lossy()
2960                .to_string();
2961
2962            // Skip if already loaded (circular import guard)
2963            if self.loaded_files.contains(&canonical) {
2964                return Ok(());
2965            }
2966            self.loaded_files.insert(canonical.clone());
2967
2968            let src = std::fs::read_to_string(&resolved)
2969                .map_err(|e| format!("use: failed to read '{path}': {e}"))?;
2970            let dir = resolved.parent().map(|p| p.to_path_buf());
2971            (src, dir)
2972        };
2973
2974        let program = crate::parser::parse(&source)
2975            .map_err(|e| format!("use: parse error in '{path}': {e}"))?;
2976
2977        // Compute target namespace: parent_ns :: alias (or just alias, or just parent_ns)
2978        let target_ns = match (parent_ns.is_empty(), alias) {
2979            (_, Some(a)) if !parent_ns.is_empty() => format!("{parent_ns}::{a}"),
2980            (_, Some(a)) => a.to_string(),
2981            (false, None) => parent_ns.to_string(),
2982            (true, None) => String::new(),
2983        };
2984
2985        // Save/restore source_dir for nested relative imports
2986        let prev_dir = self.source_dir.clone();
2987        self.source_dir = sub_dir;
2988
2989        for item in &program.items {
2990            self.register_item(&target_ns, item)?;
2991        }
2992
2993        self.source_dir = prev_dir;
2994        Ok(())
2995    }
2996
2997    fn find_entry(&self) -> Option<Expr> {
2998        // Known entry-point names across supported human languages.
2999        for key in crate::entry::ENTRY_NAMES {
3000            if let Some(e) = self.globals.get(*key) {
3001                return Some(e.clone());
3002            }
3003        }
3004        self.globals
3005            .values()
3006            .find(|e| matches!(e, Expr::Do(_)))
3007            .cloned()
3008    }
3009
3010    // ─── Expression evaluation ────────────────────────────────────────────────
3011
3012    fn eval_expr(&mut self, expr: &Expr, env: &mut Env) -> EvalResult {
3013        match expr {
3014            Expr::Str(s) => Ok(Value::Str(s.clone())),
3015            Expr::Number(n) => Ok(Value::Number(*n)),
3016            Expr::Bool(b) => Ok(Value::Bool(*b)),
3017            Expr::Unit => Ok(Value::Unit),
3018            Expr::Array(elems) => {
3019                let vs: Vec<_> = elems
3020                    .iter()
3021                    .map(|e| self.eval_expr(e, env))
3022                    .collect::<Result<_, _>>()?;
3023                Ok(Value::List(Rc::new(vs)))
3024            },
3025
3026            Expr::Ident(name) => self.lookup(name, env),
3027
3028            Expr::Path(segs) => {
3029                if segs.len() == 1 {
3030                    return self.lookup(&segs[0], env);
3031                }
3032                Ok(Value::Str(segs.join("::")))
3033            },
3034
3035            Expr::Ref(inner) => self.eval_expr(inner, env),
3036            Expr::Await(inner) => self.eval_expr(inner, env),
3037
3038            Expr::Do(stmts) => {
3039                let mut local = env.clone();
3040                Ok(self.exec_block(stmts, &mut local)?.unwrap_or(Value::Unit))
3041            },
3042
3043            Expr::BinOp(op, lhs, rhs) => {
3044                let l = self.eval_expr(lhs, env)?;
3045                let r = self.eval_expr(rhs, env)?;
3046                self.apply_binop(op, l, r)
3047            },
3048
3049            Expr::If { cond, then, elseifs, else_body } => {
3050                let cond_val = self.eval_expr(cond, env)?;
3051                if self.is_truthy(&cond_val) {
3052                    return Ok(self.exec_block(then, env)?.unwrap_or(Value::Unit));
3053                }
3054                for (ei_cond, ei_body) in elseifs {
3055                    let ei_cond_val = self.eval_expr(ei_cond, env)?;
3056                    if self.is_truthy(&ei_cond_val) {
3057                        return Ok(self.exec_block(ei_body, env)?.unwrap_or(Value::Unit));
3058                    }
3059                }
3060                if let Some(eb) = else_body {
3061                    return Ok(self.exec_block(eb, env)?.unwrap_or(Value::Unit));
3062                }
3063                Ok(Value::Unit)
3064            },
3065
3066            Expr::While { cond, body } => {
3067                // Run the body directly in the *outer* env so that
3068                // `bind counter = counter + 1` persists across iterations,
3069                // which is the expected behaviour in a scripting language.
3070                loop {
3071                    let cv = self.eval_expr(cond, env)?;
3072                    if !self.is_truthy(&cv) {
3073                        break;
3074                    }
3075                    match self.exec_block(body, env) {
3076                        Ok(_) => {},
3077                        Err(EvalErr::Break) => break,
3078                        Err(e) => return Err(e),
3079                    }
3080                }
3081                Ok(Value::Unit)
3082            },
3083
3084            Expr::For { var, iter, body } => {
3085                let iter_val = self.eval_expr(iter, env)?;
3086                let items = self.value_to_iter(iter_val)?;
3087                for item in items {
3088                    let mut local = env.clone();
3089                    local.insert(var.clone(), item);
3090                    match self.exec_block(body, &mut local) {
3091                        Ok(_) => {},
3092                        Err(EvalErr::Break) => break,
3093                        Err(e) => return Err(e),
3094                    }
3095                }
3096                Ok(Value::Unit)
3097            },
3098
3099            Expr::Match(subject, arms) => {
3100                let subj = self.eval_expr(subject, env)?;
3101                for arm in arms {
3102                    if let Some(bindings) = self.match_pattern(&arm.pattern, &subj) {
3103                        let mut local = env.clone();
3104                        local.extend(bindings);
3105                        return self.eval_expr(&arm.body, &mut local);
3106                    }
3107                }
3108                Ok(Value::Unit)
3109            },
3110
3111            Expr::Range(lo, hi) => {
3112                let lo_v = self.eval_expr(lo, env)?;
3113                let hi_v = self.eval_expr(hi, env)?;
3114                let lo_n = self.to_number(&lo_v)? as i64;
3115                let hi_n = self.to_number(&hi_v)? as i64;
3116                Ok(Value::List(Rc::new(
3117                    (lo_n..hi_n).map(|i| Value::Number(i as f64)).collect(),
3118                )))
3119            },
3120
3121            Expr::Index(base, idx) => {
3122                let b = self.eval_expr(base, env)?;
3123                let i = self.eval_expr(idx, env)?;
3124                let n = self.to_number(&i)? as usize;
3125                match b {
3126                    Value::List(v) => v
3127                        .get(n)
3128                        .cloned()
3129                        .ok_or_else(|| EvalErr::from(format!("index {n} out of bounds"))),
3130                    Value::Str(s) => s
3131                        .chars()
3132                        .nth(n)
3133                        .map(|c| Value::Str(c.to_string()))
3134                        .ok_or_else(|| EvalErr::from(format!("index {n} out of bounds"))),
3135                    other => Err(EvalErr::from(format!("cannot index {:?}", other))),
3136                }
3137            },
3138
3139            Expr::Call(callee, args) => {
3140                let arg_vals: Vec<Value> = args
3141                    .iter()
3142                    .map(|a| self.eval_expr(a, env))
3143                    .collect::<Result<_, _>>()?;
3144                match callee.as_ref() {
3145                    Expr::Ident(name) => self.call_named(name, arg_vals, env),
3146                    Expr::Path(segs) => self.call_named(&segs.join("::"), arg_vals, env),
3147                    _ => {
3148                        let v = self.eval_expr(callee, env)?;
3149                        self.call_value(v, arg_vals)
3150                    },
3151                }
3152            },
3153
3154            Expr::MethodCall { receiver, method, args } => {
3155                let recv = self.eval_expr(receiver, env)?;
3156                let arg_vals: Vec<Value> = args
3157                    .iter()
3158                    .map(|a| self.eval_expr(a, env))
3159                    .collect::<Result<_, _>>()?;
3160                self.call_method(recv, method, arg_vals)
3161            },
3162
3163            Expr::Closure(params, body) => Ok(Value::Fn(
3164                params.clone(),
3165                vec![Stmt::Expr(*body.clone())],
3166                env.clone(),
3167            )),
3168
3169            Expr::Asm(_) => Ok(Value::Unit),
3170        }
3171    }
3172
3173    // ─── Block execution ─────────────────────────────────────────────────────
3174
3175    fn exec_block(&mut self, stmts: &[Stmt], env: &mut Env) -> Result<Option<Value>, EvalErr> {
3176        let mut last: Option<Value> = None;
3177        for stmt in stmts {
3178            match stmt {
3179                Stmt::Bind(name, expr) => {
3180                    match self.try_inplace_list_update(name, expr, env)? {
3181                        Some(v) => env.insert(name.clone(), v),
3182                        None => {
3183                            let v = self.eval_expr(expr, env)?;
3184                            env.insert(name.clone(), v)
3185                        },
3186                    };
3187                    last = None;
3188                },
3189                Stmt::Return(expr) => {
3190                    let v = self.eval_expr(expr, env)?;
3191                    return Err(EvalErr::Return(v));
3192                },
3193                Stmt::Expr(expr) => {
3194                    last = Some(self.eval_expr(expr, env)?);
3195                },
3196            }
3197        }
3198        Ok(last)
3199    }
3200
3201    /// Fast path for `bind v = list_push(v, x)` / `bind v = list_set(v, i, x)`:
3202    /// the binding aliases the same list being rebuilt, so the env copy keeps the
3203    /// `Rc` shared and `make_mut` copies the whole vector every call. Taking the
3204    /// value out of env first leaves the `Rc` unique (unless truly aliased
3205    /// elsewhere, where copy-on-write still applies), turning O(n) into O(1).
3206    /// Returns `None` to fall back to normal evaluation.
3207    fn try_inplace_list_update(
3208        &mut self,
3209        name: &str,
3210        expr: &Expr,
3211        env: &mut Env,
3212    ) -> Result<Option<Value>, EvalErr> {
3213        let Expr::Call(callee, args) = expr else { return Ok(None) };
3214        let Expr::Ident(fname) = callee.as_ref() else { return Ok(None) };
3215        let is_push = matches!(
3216            fname.as_str(),
3217            "list_push" | "เพิ่มรายการ" | "列表添加" | "リスト追加" | "목록추가"
3218        );
3219        let is_set = matches!(
3220            fname.as_str(),
3221            "list_set" | "ตั้งรายการ" | "设元素" | "要素設定" | "요소설정"
3222        );
3223        if !is_push && !is_set {
3224            return Ok(None);
3225        }
3226        // First arg must be the same variable we are binding, and the builtin
3227        // must not be shadowed by a user function.
3228        match args.first() {
3229            Some(Expr::Ident(a0)) if a0 == name => {},
3230            _ => return Ok(None),
3231        }
3232        if self.functions.contains_key(fname.as_str()) {
3233            return Ok(None);
3234        }
3235        if is_push {
3236            if args.len() != 2 {
3237                return Ok(None);
3238            }
3239            let val = self.eval_expr(&args[1], env)?;
3240            match env.remove(name) {
3241                Some(Value::List(mut v)) => {
3242                    Rc::make_mut(&mut v).push(val);
3243                    Ok(Some(Value::List(v)))
3244                },
3245                other => {
3246                    if let Some(o) = other {
3247                        env.insert(name.to_string(), o);
3248                    }
3249                    Ok(None)
3250                },
3251            }
3252        } else {
3253            if args.len() != 3 {
3254                return Ok(None);
3255            }
3256            let idx_v = self.eval_expr(&args[1], env)?;
3257            let idx = self.to_number(&idx_v).unwrap_or(0.0) as usize;
3258            let val = self.eval_expr(&args[2], env)?;
3259            match env.remove(name) {
3260                Some(Value::List(mut v)) => {
3261                    if idx < v.len() {
3262                        Rc::make_mut(&mut v)[idx] = val;
3263                    }
3264                    Ok(Some(Value::List(v)))
3265                },
3266                other => {
3267                    if let Some(o) = other {
3268                        env.insert(name.to_string(), o);
3269                    }
3270                    Ok(None)
3271                },
3272            }
3273        }
3274    }
3275
3276    // ─── Dispatch helpers ─────────────────────────────────────────────────────
3277
3278    fn lookup(&self, name: &str, env: &Env) -> EvalResult {
3279        if let Some(v) = env.get(name) {
3280            return Ok(v.clone());
3281        }
3282        // Globals are an immutable load-time snapshot shared by every call frame;
3283        // a function reads them here instead of receiving a per-call clone.
3284        if let Some(v) = self.global_seed.get(name) {
3285            return Ok(v.clone());
3286        }
3287        if self.functions.contains_key(name) {
3288            let def = &self.functions[name];
3289            return Ok(Value::Fn(def.params.clone(), def.body.clone(), new_env()));
3290        }
3291        // Bare nullary enum variant used as a value (e.g. `bind p = Origin`).
3292        if let Some((enum_name, 0)) = self.enum_variants.get(name).cloned() {
3293            let variant = name.rsplit("::").next().unwrap_or(name).to_string();
3294            return Ok(Value::Variant { enum_name, variant, payload: Vec::new() });
3295        }
3296        // Math constants usable as plain identifiers (e.g. `sin(pi)`)
3297        match name {
3298            "pi" | "π" | "พาย" | "圆周率" | "円周率" | "파이" | "پی" | "باي" | "פאי" | "پائی"
3299            | "пи" => return Ok(Value::Number(std::f64::consts::PI)),
3300            "tau" | "τ" | "双周率" | "タウ" | "타우" | "ทาว" | "تاو" | "טאו" | "ٹاؤ" | "тау" => {
3301                return Ok(Value::Number(std::f64::consts::TAU))
3302            },
3303            _ => {},
3304        }
3305        Err(EvalErr::from(format!("undefined: '{name}'")))
3306    }
3307
3308    /// Profiling wrapper around the real dispatch. Zero overhead unless
3309    /// `LING_PROFILE` is set (one thread-local bool check per call). When on,
3310    /// it tallies per-name call count + inclusive time and, on each frame
3311    /// boundary (`present`), prints a sorted top-down report every
3312    /// `LING_PROFILE_EVERY` frames (default 240). Both the JIT (`ling_builtin` →
3313    /// here) and the tree-walker route through this, so it sees every builtin —
3314    /// in JIT mode user fns are native, so it's a clean builtin/render/physics
3315    /// profile with no nesting double-count.
3316    pub(crate) fn call_named(&mut self, name: &str, args: Vec<Value>, env: &Env) -> EvalResult {
3317        if !ling_profile_enabled() {
3318            return self.call_named_inner(name, args, env);
3319        }
3320        let t0 = crate::runtime::now_secs();
3321        let r = self.call_named_inner(name, args, env);
3322        ling_profile_record(
3323            name,
3324            ((crate::runtime::now_secs() - t0) * 1_000_000_000.0) as u128,
3325        );
3326        r
3327    }
3328
3329    fn call_named_inner(&mut self, name: &str, args: Vec<Value>, env: &Env) -> EvalResult {
3330        // A user-defined function shadows any builtin of the same name, matching
3331        // the JIT/AOT backends (which always resolve a defined function first).
3332        if let Some(def) = self.functions.get(name).cloned() {
3333            let mut call_env =
3334                FxHashMap::with_capacity_and_hasher(def.params.len(), Default::default());
3335            let _ = env; // call-site locals are intentionally NOT visible to fns
3336            for (param, arg) in def.params.iter().zip(args) {
3337                call_env.insert(param.clone(), arg);
3338            }
3339            return match self.framed(name, |me| me.exec_block(&def.body, &mut call_env)) {
3340                Ok(v) => Ok(v.unwrap_or(Value::Unit)),
3341                Err(EvalErr::Return(v)) => Ok(v),
3342                Err(e) => Err(e),
3343            };
3344        }
3345
3346        #[cfg(target_arch = "wasm32")]
3347        if let Some(v) = self.wasm_music_builtin(name, &args)? {
3348            return Ok(v);
3349        }
3350
3351        match name {
3352            // Module global read emitted by the MIR backend: resolve against the
3353            // evaluated global snapshot (functions see globals read-only).
3354            "__ling_global" => {
3355                if let Some(Value::Str(g)) = args.first() {
3356                    if let Some(v) = self.global_seed.get(g.as_str()) {
3357                        return Ok(v.clone());
3358                    }
3359                }
3360                return Ok(Value::Unit);
3361            },
3362            // ── Print ──
3363            "print" | "println" | "印" | "打印" | "印刷" | "พิมพ์" | "출력" | "вывести"
3364            | "imprimir" | "afficher" | "چاپ" | "اطبع" | "הדפס" | "چھاپو" | "drucken"
3365            | "печать" => {
3366                let s = args
3367                    .iter()
3368                    .map(|v| v.to_string())
3369                    .collect::<Vec<_>>()
3370                    .join("");
3371                println!("{s}");
3372                return Ok(Value::Unit);
3373            },
3374            // print_color(colorIdx, text...) — ANSI-coloured console line.
3375            //   colorIdx 0..7 → bright fg (90+idx): 1=red 2=green 3=yellow 4=blue 6=cyan 7=white.
3376            "print_color" | "พิมพ์สี" | "چاپ_رنگی" | "اطبع_بلون" | "הדפס_בצבע" | "رنگین_چھاپو" =>
3377            {
3378                #[cfg(windows)]
3379                {
3380                    use std::sync::Once;
3381                    static VT: Once = Once::new();
3382                    VT.call_once(|| {
3383                        extern "system" {
3384                            fn GetStdHandle(n: u32) -> *mut std::ffi::c_void;
3385                            fn GetConsoleMode(h: *mut std::ffi::c_void, m: *mut u32) -> i32;
3386                            fn SetConsoleMode(h: *mut std::ffi::c_void, m: u32) -> i32;
3387                        }
3388                        unsafe {
3389                            let h = GetStdHandle(0xFFFF_FFF5u32); // STD_OUTPUT_HANDLE (-11)
3390                            let mut mode = 0u32;
3391                            if GetConsoleMode(h, &mut mode) != 0 {
3392                                SetConsoleMode(h, mode | 0x0004); // ENABLE_VIRTUAL_TERMINAL_PROCESSING
3393                            }
3394                        }
3395                    });
3396                }
3397                let col = self.arg_num(&args, 0, 7.0)? as i64;
3398                let s = args
3399                    .iter()
3400                    .skip(1)
3401                    .map(|v| v.to_string())
3402                    .collect::<Vec<_>>()
3403                    .join("");
3404                let code = 90 + col.clamp(0, 7);
3405                println!("\x1b[1;{code}m{s}\x1b[0m");
3406                return Ok(Value::Unit);
3407            },
3408            // ── Format ──
3409            "format"
3410            | "格式"
3411            | "フォーマット"
3412            | "서식"
3413            | "รูปแบบ"
3414            | "форматировать"
3415            | "formatear"
3416            | "formater"
3417            | "قالب‌بندی"
3418            | "نسّق"
3419            | "פרמט"
3420            | "فارمیٹ"
3421            | "formatieren" => {
3422                return Ok(Value::Str(self.builtin_format(&args)?));
3423            },
3424            // ── String join / concatenation ──
3425            "格式::拼接" | "format::join" => match args.first() {
3426                Some(Value::List(items)) => {
3427                    return Ok(Value::Str(items.iter().map(|v| v.to_string()).collect()));
3428                },
3429                _ => return Ok(Value::Str(self.builtin_format(&args)?)),
3430            },
3431            // ── Result constructors ──
3432            "ok" | "好" | "良し" | "좋아" | "โอเค" | "تایید" | "تمام" | "בסדר" | "ٹھیک" | "bon"
3433            | "gut" | "хорошо" => {
3434                let val = args.into_iter().next().unwrap_or(Value::Unit);
3435                return Ok(Value::Ok(Box::new(val)));
3436            },
3437            "bad" | "坏" | "err" | "悪い" | "나쁨" | "ผิด" | "بد" | "سيء" | "רע" | "برا"
3438            | "mauvais" | "schlecht" | "плохо" => {
3439                let val = args.into_iter().next().unwrap_or(Value::Unit);
3440                return Ok(Value::Err(Box::new(val)));
3441            },
3442            // ── Vec constructors ──
3443            "向量::从" | "Vec::from" => {
3444                if let Some(Value::List(v)) = args.first() {
3445                    return Ok(Value::List(v.clone()));
3446                }
3447                return Ok(Value::List(Rc::new(args)));
3448            },
3449            "向量::有容量" | "Vec::with_capacity" => {
3450                return Ok(Value::List(Rc::new(Vec::new())))
3451            },
3452            // ── Timer stubs ──
3453            "计时::获取当前小时" | "Timer::hour" => return Ok(Value::Number(14.0)),
3454            "计时::现在" | "Timer::now" => return Ok(Value::Number(1000.0)),
3455            // ── Sleep ──
3456            "sleep" | "หยุด" | "นอน" | "sleep_ms" | "睡眠" | "眠る" | "スリープ" | "잠자기"
3457            | "잠" | "流水::睡眠" | "Flow::sleep" | "خواب" | "نم" | "שינה" | "سو_جاؤ"
3458            | "dormir" | "schlafen" | "спать" => {
3459                if let Some(ms_val) = args.first() {
3460                    if let Ok(ms) = self.to_number(ms_val) {
3461                        #[cfg(target_arch = "wasm32")]
3462                        wasm_sleep_ms(ms.max(0.0) as i32);
3463                        #[cfg(not(target_arch = "wasm32"))]
3464                        std::thread::sleep(std::time::Duration::from_millis(ms as u64));
3465                    }
3466                }
3467                return Ok(Value::Unit);
3468            },
3469            // ── Flow::parallel stub ──
3470            "流水::并行" | "Flow::parallel" => {
3471                if let Some(Value::Fn(params, body, mut cap)) = args.first().cloned() {
3472                    let _ = params;
3473                    match self.exec_block(&body, &mut cap) {
3474                        Ok(Some(v)) => return Ok(v),
3475                        Ok(None) => return Ok(Value::Unit),
3476                        Err(EvalErr::Return(v)) => return Ok(v),
3477                        Err(e) => return Err(e),
3478                    }
3479                }
3480                return Ok(Value::Unit);
3481            },
3482
3483            // ══════════════════════════════════════════════════════════════════
3484            // MATH BUILTINS  (all args and results are f64)
3485            // Thai aliases: ไซน์ โคไซน์ แทนเจนต์ รากที่สอง ค่าสัมบูรณ์
3486            //               ปัดลง ปัดขึ้น ปัดเศษ ตัดทศนิยม ต่ำสุด สูงสุด
3487            //               จำกัด ยกกำลัง ลอการิทึม พาย
3488            // ══════════════════════════════════════════════════════════════════
3489
3490            // ── Trigonometry (input in radians) ──
3491            "sin" | "ไซน์" | "正弦" | "サイン" | "사인" | "سینوس" | "جا" | "סינוס" | "سائن"
3492            | "sinus" | "синус" => {
3493                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.sin()));
3494            },
3495            "cos" | "โคไซน์" | "余弦" | "コサイン" | "코사인" | "کسینوس" | "جتا" | "קוסינוס"
3496            | "کوسائن" | "cosinus" | "kosinus" | "косинус" => {
3497                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.cos()));
3498            },
3499
3500            // ── Hyperbolic functions ──
3501            // Hyperbolic tangent
3502            "tanh" | "tanhf" | "双曲正切" | "双曲線正接" | "쌍곡탄젠트" => {
3503                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.tanh()));
3504            },
3505
3506            "tan"
3507            | "แทนเจนต์"
3508            | "正切"
3509            | "タンジェント"
3510            | "탄젠트"
3511            | "تانژانت"
3512            | "ظا"
3513            | "טנגנס"
3514            | "ٹینجنٹ"
3515            | "tangente"
3516            | "tangens"
3517            | "тангенс" => {
3518                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.tan()));
3519            },
3520            "asin"
3521            | "arcsin"
3522            | "反正弦"
3523            | "アークサイン"
3524            | "아크사인"
3525            | "อาร์กไซน์"
3526            | "آرک‌سینوس"
3527            | "قوس_جا"
3528            | "ארקסינוס"
3529            | "آرک_سائن"
3530            | "arcsinus"
3531            | "arkussinus"
3532            | "арксинус" => {
3533                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.asin()));
3534            },
3535            "acos"
3536            | "arccos"
3537            | "反余弦"
3538            | "アークコサイン"
3539            | "아크코사인"
3540            | "อาร์กโคไซน์"
3541            | "آرک‌کسینوس"
3542            | "قوس_جتا"
3543            | "ארקוקוסינוס"
3544            | "آرک_کوسائن"
3545            | "arccosinus"
3546            | "arkuskosinus"
3547            | "арккосинус" => {
3548                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.acos()));
3549            },
3550            "atan"
3551            | "arctan"
3552            | "反正切"
3553            | "アークタンジェント"
3554            | "아크탄젠트"
3555            | "อาร์กแทนเจนต์"
3556            | "آرک‌تانژانت"
3557            | "قوس_ظا"
3558            | "ארקטנגנס"
3559            | "آرک_ٹینجنٹ"
3560            | "arctangente"
3561            | "arkustangens"
3562            | "арктангенс" => {
3563                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.atan()));
3564            },
3565            "atan2" | "arctan2" | "反正切2" | "アークタンジェント2" | "아크탄젠트2" =>
3566            {
3567                let y = self.arg_num(&args, 0, 0.0)?;
3568                let x = self.arg_num(&args, 1, 1.0)?;
3569                return Ok(Value::Number(y.atan2(x)));
3570            },
3571
3572            // ── Roots / powers ──
3573            "sqrt"
3574            | "รากที่สอง"
3575            | "平方根"
3576            | "根"
3577            | "제곱근"
3578            | "جذر"
3579            | "שורש"
3580            | "racine_carrée"
3581            | "quadratwurzel"
3582            | "корень" => {
3583                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.sqrt()));
3584            },
3585            "cbrt"
3586            | "立方根"
3587            | "세제곱근"
3588            | "รากที่สาม"
3589            | "ریشه_سوم"
3590            | "جذر_تكعيبي"
3591            | "שורש_שלישי"
3592            | "مکعب_جذر"
3593            | "racine_cubique"
3594            | "kubikwurzel"
3595            | "кубический_корень" => {
3596                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.cbrt()));
3597            },
3598            "pow"
3599            | "ยกกำลัง"
3600            | "幂"
3601            | "べき乗"
3602            | "거듭제곱"
3603            | "توان"
3604            | "أس"
3605            | "חזקה"
3606            | "قوت"
3607            | "puissance"
3608            | "potenz"
3609            | "степень" => {
3610                let base = self.arg_num(&args, 0, 0.0)?;
3611                let exp = self.arg_num(&args, 1, 1.0)?;
3612                return Ok(Value::Number(base.powf(exp)));
3613            },
3614            "exp" | "指数" | "指数関数" | "지수" => {
3615                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.exp()));
3616            },
3617            "hypot" | "斜边" | "斜辺" | "빗변" => {
3618                let x = self.arg_num(&args, 0, 0.0)?;
3619                let y = self.arg_num(&args, 1, 0.0)?;
3620                return Ok(Value::Number(x.hypot(y)));
3621            },
3622
3623            // ── Logarithms ──
3624            "ln"
3625            | "log"
3626            | "ลอการิทึม"
3627            | "对数"
3628            | "対数"
3629            | "로그"
3630            | "لگاریتم_طبیعی"
3631            | "لوغاريتم_طبيعي"
3632            | "לוגריתם_טבעי"
3633            | "فطری_لوگ" => {
3634                return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.ln()));
3635            },
3636            "log2" | "对数2" | "対数2" | "로그2" => {
3637                return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.log2()));
3638            },
3639            "log10" | "对数10" | "対数10" | "로그10" => {
3640                return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.log10()));
3641            },
3642
3643            // ── Rounding / truncation ──
3644            "abs"
3645            | "ค่าสัมบูรณ์"
3646            | "绝对值"
3647            | "绝对"
3648            | "絶対値"
3649            | "절댓값"
3650            | "절대값"
3651            | "قدرمطلق"
3652            | "مطلق"
3653            | "ערך_מוחלט"
3654            | "مطلق_قدر"
3655            | "valeur_absolue"
3656            | "betrag"
3657            | "модуль_числа" => {
3658                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.abs()));
3659            },
3660            "floor" | "ปัดลง" | "向下取整" | "下整" | "床関数" | "내림" | "کف" | "أرضية"
3661            | "רצפה" | "فرش" | "plancher" | "abrunden" | "вниз" => {
3662                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.floor()));
3663            },
3664            "ceil"
3665            | "ปัดขึ้น"
3666            | "向上取整"
3667            | "上整"
3668            | "天井関数"
3669            | "올림"
3670            | "سقف"
3671            | "תקרה"
3672            | "چھت"
3673            | "plafond"
3674            | "aufrunden"
3675            | "вверх" => {
3676                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.ceil()));
3677            },
3678            "round" | "ปัดเศษ" | "四舍五入" | "四舍" | "四捨五入" | "반올림" | "گرد_کردن"
3679            | "تقريب" | "עיגול" | "گول" | "arrondir" | "runden" | "округлить" =>
3680            {
3681                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.round()));
3682            },
3683            "trunc"
3684            | "int"
3685            | "ตัดทศนิยม"
3686            | "取整"
3687            | "整数化"
3688            | "整数"
3689            | "截整"
3690            | "정수화"
3691            | "정수"
3692            | "切り捨て"
3693            | "버림"
3694            | "برش"
3695            | "اقتطاع"
3696            | "קיטום"
3697            | "کٹائی"
3698            | "tronquer"
3699            | "abschneiden"
3700            | "усечь" => {
3701                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.trunc()));
3702            },
3703            "fract" | "小数部分" | "小数部" | "소수부" => {
3704                return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.fract()));
3705            },
3706
3707            // ── min / max / clamp ──
3708            "min" | "ต่ำสุด" | "最小" | "최솟값" | "کمینه" | "أصغر" | "מינימום" | "کم_ترین"
3709            | "minimum" | "минимум" => {
3710                let a = self.arg_num(&args, 0, 0.0)?;
3711                let b = self.arg_num(&args, 1, 0.0)?;
3712                return Ok(Value::Number(a.min(b)));
3713            },
3714            "max"
3715            | "สูงสุด"
3716            | "最大"
3717            | "최댓값"
3718            | "بیشینه"
3719            | "أكبر"
3720            | "מקסימום"
3721            | "زیادہ_ترین"
3722            | "maximum"
3723            | "максимум" => {
3724                let a = self.arg_num(&args, 0, 0.0)?;
3725                let b = self.arg_num(&args, 1, 0.0)?;
3726                return Ok(Value::Number(a.max(b)));
3727            },
3728            "clamp"
3729            | "จำกัด"
3730            | "截取"
3731            | "範囲制限"
3732            | "범위제한"
3733            | "محدود"
3734            | "قيّد"
3735            | "הגבל"
3736            | "limiter"
3737            | "begrenzen"
3738            | "ограничить" => {
3739                let x = self.arg_num(&args, 0, 0.0)?;
3740                let lo = self.arg_num(&args, 1, 0.0)?;
3741                let hi = self.arg_num(&args, 2, 1.0)?;
3742                return Ok(Value::Number(x.clamp(lo, hi)));
3743            },
3744
3745            // ── Constants (also accessible as plain identifiers via lookup) ──
3746            "pi" | "π" | "พาย" | "圆周率" | "円周率" | "파이" | "پی" | "باي" | "פאי" | "پائی"
3747            | "пи" => return Ok(Value::Number(std::f64::consts::PI)),
3748            "tau" | "τ" | "双周率" | "タウ" | "타우" | "ทาว" | "تاو" | "טאו" | "ٹاؤ" | "тау" => {
3749                return Ok(Value::Number(std::f64::consts::TAU))
3750            },
3751
3752            // ══════════════════════════════════════════════════════════════════
3753            // PHASE 1: DMT TRIP CODER FEATURES
3754            // ══════════════════════════════════════════════════════════════════
3755
3756            // ── Step 1: Noise Functions ──
3757            "vnoise"
3758            | "noise2"
3759            | "นอยส์2ดี"
3760            | "柏林噪声2D"
3761            | "バリューノイズ2D"
3762            | "값노이즈2D"
3763            | "نویز_برداری"
3764            | "ضجيج_متجه"
3765            | "רעש_וקטורי"
3766            | "ویکٹر_نوائز"
3767            | "bruit_v"
3768            | "v_rauschen"
3769            | "шум_v" => {
3770                let x = self.arg_num(&args, 0, 0.0)? as f32;
3771                let y = self.arg_num(&args, 1, 0.0)? as f32;
3772                let seed = self.arg_num(&args, 2, 0.0)? as u32;
3773                return Ok(Value::Number(tex_vnoise(x, y, seed) as f64));
3774            },
3775
3776            "fbm"
3777            | "นอยส์ออร์แกนิก"
3778            | "分形噪声"
3779            | "フラクタルノイズ"
3780            | "프랙탈노이즈"
3781            | "نویز_فراکتالی"
3782            | "ضجيج_عضوي"
3783            | "רעש_פרקטלי"
3784            | "فریکٹل_نوائز" => {
3785                let x = self.arg_num(&args, 0, 0.0)? as f32;
3786                let y = self.arg_num(&args, 1, 0.0)? as f32;
3787                let octaves = self.arg_num(&args, 2, 4.0)? as u32;
3788                let seed = self.arg_num(&args, 3, 0.0)? as u32;
3789                return Ok(Value::Number(tex_fbm(x, y, octaves, seed) as f64));
3790            },
3791
3792            "perlin"
3793            | "perlin3"
3794            | "เพอร์ลิน3ดี"
3795            | "柏林噪声3D"
3796            | "パーリンノイズ3D"
3797            | "펄린노이즈3D"
3798            | "نویز_پرلین"
3799            | "بيرلين"
3800            | "רעש_פרלין"
3801            | "پرلن_نوائز"
3802            | "перлин" => {
3803                let x = self.arg_num(&args, 0, 0.0)? as f32;
3804                let y = self.arg_num(&args, 1, 0.0)? as f32;
3805                let z = self.arg_num(&args, 2, 0.0)? as f32;
3806                return Ok(Value::Number(perlin3(x, y, z) as f64));
3807            },
3808
3809            // ── Step 2: Math Ergonomics ──
3810            "lerp"
3811            | "ค่าระหว่าง"
3812            | "线性插值"
3813            | "線形補間"
3814            | "선형보간"
3815            | "میان‌یابی"
3816            | "استيفاء"
3817            | "אינטרפולציה"
3818            | "درمیانی_قدر"
3819            | "interpoler"
3820            | "interpolieren"
3821            | "интерполировать" => {
3822                let a = self.arg_num(&args, 0, 0.0)?;
3823                let b = self.arg_num(&args, 1, 1.0)?;
3824                let t = self.arg_num(&args, 2, 0.0)?;
3825                return Ok(Value::Number(a + (b - a) * t));
3826            },
3827
3828            "smoothstep"
3829            | "เปลี่ยนแบบนุ่ม"
3830            | "平滑步进"
3831            | "スムーズステップ"
3832            | "스무스스텝"
3833            | "گام_نرم"
3834            | "تدرج_ناعم"
3835            | "מדרגה_חלקה"
3836            | "ہموار_قدم"
3837            | "lissage"
3838            | "glättung"
3839            | "сглаживание" => {
3840                let lo = self.arg_num(&args, 0, 0.0)?;
3841                let hi = self.arg_num(&args, 1, 1.0)?;
3842                let x = self.arg_num(&args, 2, 0.5)?;
3843                let t = ((x - lo) / (hi - lo)).clamp(0.0, 1.0);
3844                return Ok(Value::Number(t * t * (3.0 - 2.0 * t)));
3845            },
3846
3847            "rand" | "สุ่ม" | "随机" | "乱数" | "난수" | "تصادفی" | "عشوائي" | "אקראי"
3848            | "بے_ترتیب" | "aléatoire" | "zufall" | "случайное" => {
3849                let val = fast_rand_f64(&mut self.rand_state);
3850                return Ok(Value::Number(val));
3851            },
3852
3853            "sign"
3854            | "เครื่องหมาย"
3855            | "符号"
3856            | "符号関数"
3857            | "부호"
3858            | "علامت"
3859            | "إشارة"
3860            | "סימן"
3861            | "نشان"
3862            | "signe"
3863            | "vorzeichen"
3864            | "знак" => {
3865                let x = self.arg_num(&args, 0, 0.0)?;
3866                return Ok(Value::Number(x.signum()));
3867            },
3868
3869            "hsv_to_rgb"
3870            | "เอชเอสวีเป็นRGB"
3871            | "HSV转RGB"
3872            | "HSV変換RGB"
3873            | "HSV변환RGB"
3874            | "HSV_به_RGB"
3875            | "HSV_إلى_RGB"
3876            | "HSV_ל_RGB"
3877            | "HSV_سے_RGB"
3878            | "hsv_vers_rgb"
3879            | "hsv_zu_rgb"
3880            | "hsv_в_rgb" => {
3881                let h = self.arg_num(&args, 0, 0.0)?; // 0-360
3882                let s = self.arg_num(&args, 1, 1.0)?; // 0-1
3883                let v = self.arg_num(&args, 2, 1.0)?; // 0-1
3884                let c = v * s;
3885                let x = c * (1.0 - (((h / 60.0) % 2.0) - 1.0).abs());
3886                let m = v - c;
3887                let (r1, g1, b1) = if h < 60.0 {
3888                    (c, x, 0.0)
3889                } else if h < 120.0 {
3890                    (x, c, 0.0)
3891                } else if h < 180.0 {
3892                    (0.0, c, x)
3893                } else if h < 240.0 {
3894                    (0.0, x, c)
3895                } else if h < 300.0 {
3896                    (x, 0.0, c)
3897                } else {
3898                    (c, 0.0, x)
3899                };
3900                let r = ((r1 + m) * 255.0).round();
3901                let g = ((g1 + m) * 255.0).round();
3902                let b = ((b1 + m) * 255.0).round();
3903                return Ok(Value::List(Rc::new(vec![
3904                    Value::Number(r),
3905                    Value::Number(g),
3906                    Value::Number(b),
3907                ])));
3908            },
3909
3910            "lerp_color"
3911            | "ไล่สี"
3912            | "颜色插值"
3913            | "色補間"
3914            | "색보간"
3915            | "میان‌یابی_رنگ"
3916            | "استيفاء_اللون"
3917            | "אינטרפולציית_צבע"
3918            | "رنگ_درمیانی_قدر"
3919            | "interpoler_couleur"
3920            | "farbe_interpolieren"
3921            | "интерполировать_цвет" => {
3922                let r1 = self.arg_num(&args, 0, 0.0)?;
3923                let g1 = self.arg_num(&args, 1, 0.0)?;
3924                let b1 = self.arg_num(&args, 2, 0.0)?;
3925                let r2 = self.arg_num(&args, 3, 255.0)?;
3926                let g2 = self.arg_num(&args, 4, 255.0)?;
3927                let b2 = self.arg_num(&args, 5, 255.0)?;
3928                let t = self.arg_num(&args, 6, 0.0)?;
3929                let r = r1 + (r2 - r1) * t;
3930                let g = g1 + (g2 - g1) * t;
3931                let b = b1 + (b2 - b1) * t;
3932                let c = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
3933                self.gfx.borrow_mut().color = c;
3934                return Ok(Value::Unit);
3935            },
3936
3937            // ── Step 3: Real-Time Clock ──
3938            "time_now"
3939            | "เวลาปัจจุบัน"
3940            | "当前时间"
3941            | "経過時間"
3942            | "현재시간"
3943            | "زمان_اکنون"
3944            | "الوقت_الآن"
3945            | "הזמן_עכשיו"
3946            | "ابھی_کا_وقت"
3947            | "temps_actuel"
3948            | "aktuelle_zeit"
3949            | "текущее_время" => {
3950                return Ok(Value::Number(
3951                    crate::runtime::now_secs() - self.start_time_secs,
3952                ));
3953            },
3954
3955            // Wall-clock seconds since the Unix epoch (real date/time). Lets a
3956            // program defer deterministic-yet-evolving generation to the actual
3957            // datetime — same clock → same world, advancing as real time passes.
3958            "epoch_now"
3959            | "เวลาโลก"
3960            | "datetime"
3961            | "现在时刻"
3962            | "現在時刻"
3963            | "현재시각"
3964            | "مهر_زمانی_اکنون"
3965            | "طابع_الوقت_الآن"
3966            | "חותמת_זמן_עכשיו"
3967            | "ایپاک_وقت" => {
3968                return Ok(Value::Number(crate::runtime::now_secs()));
3969            },
3970
3971            "frame_count"
3972            | "เฟรม"
3973            | "帧数"
3974            | "フレーム数"
3975            | "프레임수"
3976            | "شمار_فریم"
3977            | "عدد_الإطارات"
3978            | "ספירת_פריימים"
3979            | "فریم_شمار"
3980            | "nombre_images"
3981            | "bildanzahl"
3982            | "число_кадров" => {
3983                return Ok(Value::Number(self.frame_num as f64));
3984            },
3985
3986            // ── Step 4: Microphone Input ──
3987            "mic_open"
3988            | "เปิดไมค์"
3989            | "开麦克风"
3990            | "マイク開く"
3991            | "마이크열기"
3992            | "باز_کردن_میکروفون"
3993            | "افتح_الميكروفون"
3994            | "פתח_מיקרופון"
3995            | "مائیکروفون_کھولو"
3996            | "ouvrir_micro"
3997            | "mikrofon_öffnen"
3998            | "открыть_микрофон" => {
3999                #[cfg(not(target_arch = "wasm32"))]
4000                {
4001                    match ling_mic::MicInput::open(Default::default()) {
4002                        Ok(mic) => {
4003                            let _ = mic.start(|_samples: &[f32]| {}); // No-op callback
4004                            self.mic = Some(mic);
4005                            return Ok(Value::Number(1.0)); // opened
4006                        },
4007                        // No device / permission denied → graceful: don't crash the game loop.
4008                        // Returns 0.0; mic_rms/mic_peak return 0.0 while self.mic is None.
4009                        Err(_e) => {
4010                            self.mic = None;
4011                            return Ok(Value::Number(0.0));
4012                        },
4013                    }
4014                }
4015                #[cfg(target_arch = "wasm32")]
4016                return Ok(Value::Unit);
4017            },
4018
4019            "mic_rms"
4020            | "เสียงRMS"
4021            | "麦克风音量"
4022            | "マイクRMS"
4023            | "마이크RMS"
4024            | "RMS_میکروفون"
4025            | "RMS_الميكروفون"
4026            | "RMS_מיקרופון"
4027            | "مائیکروفون_RMS"
4028            | "rms_micro"
4029            | "mikrofon_rms"
4030            | "rms_микрофона" => {
4031                #[cfg(not(target_arch = "wasm32"))]
4032                {
4033                    let rms = self
4034                        .mic
4035                        .as_ref()
4036                        .map(|m: &ling_mic::MicInput| m.rms())
4037                        .unwrap_or(0.0);
4038                    return Ok(Value::Number(rms as f64));
4039                }
4040                #[cfg(target_arch = "wasm32")]
4041                return Ok(Value::Number(0.0));
4042            },
4043
4044            "mic_peak"
4045            | "เสียงพีค"
4046            | "麦克风峰值"
4047            | "マイクピーク"
4048            | "마이크피크"
4049            | "اوج_میکروفون"
4050            | "ذروة_الميكروفون"
4051            | "שיא_מיקרופון"
4052            | "مائیکروفون_چوٹی"
4053            | "crête_micro"
4054            | "mikrofon_spitze"
4055            | "пик_микрофона" => {
4056                #[cfg(not(target_arch = "wasm32"))]
4057                {
4058                    let peak = self
4059                        .mic
4060                        .as_ref()
4061                        .map(|m: &ling_mic::MicInput| m.peak())
4062                        .unwrap_or(0.0);
4063                    return Ok(Value::Number(peak as f64));
4064                }
4065                #[cfg(target_arch = "wasm32")]
4066                return Ok(Value::Number(0.0));
4067            },
4068
4069            "mic_fft"
4070            | "วิเคราะห์เสียงสด"
4071            | "实时频谱"
4072            | "リアルタイムFFT"
4073            | "실시간FFT"
4074            | "FFT_میکروفون"
4075            | "FFT_الميكروفون"
4076            | "FFT_מיקרופון"
4077            | "مائیکروفون_FFT"
4078            | "fft_micro"
4079            | "mikrofon_fft"
4080            | "fft_микрофона" => {
4081                #[cfg(not(target_arch = "wasm32"))]
4082                {
4083                    let n = self.arg_num(&args, 0, 8.0)? as usize;
4084                    if let Some(mic) = self.mic.as_ref() {
4085                        let samples = mic.latest_samples();
4086                        self.fft.borrow_mut().push_samples(&samples);
4087                    }
4088                    let bands = self.fft.borrow().freq_bands(n);
4089                    let result: Vec<Value> =
4090                        bands.iter().map(|&v| Value::Number(v as f64)).collect();
4091                    return Ok(Value::List(Rc::new(result)));
4092                }
4093                #[cfg(target_arch = "wasm32")]
4094                return Ok(Value::List(Vec::new().into()));
4095            },
4096
4097            // ── Step 5: Additive Blend Mode ──
4098            "set_blend"
4099            | "โหมดผสม"
4100            | "混合模式"
4101            | "ブレンドモード"
4102            | "블렌드모드"
4103            | "تنظیم_ترکیب"
4104            | "عيّن_المزج"
4105            | "קבע_מיזוג"
4106            | "بلینڈ_مقرر_کرو"
4107            | "définir_mélange"
4108            | "mischmodus_setzen"
4109            | "задать_смешивание" => {
4110                let mode = self.arg_num(&args, 0, 0.0)? as u8;
4111                let mut gfx = self.gfx.borrow_mut();
4112                gfx.blend = mode;
4113                let a = gfx.alpha;
4114                gfx.depth_queue.set_state(mode, a); // 3-D queue captures blend for subsequent pushes
4115                return Ok(Value::Unit);
4116            },
4117
4118            // set_antialias(on) — smooth wireframe strokes (lines / edges / arcs /
4119            // circle outlines) via Xiaolin-Wu coverage. Default OFF = crisp,
4120            // opaque, aliased pixels; pass 1 to opt into smooth edges.
4121            "set_antialias"
4122            | "ตั้งลบรอยหยัก"
4123            | "抗锯齿"
4124            | "アンチエイリアス"
4125            | "안티에일리어싱"
4126            | "تنظیم_ضدلبه‌دندانه"
4127            | "عيّن_مضاد_التسنن"
4128            | "קבע_החלקת_קצוות"
4129            | "اینٹی_الائیسنگ_مقرر_کرو" => {
4130                let on = self.arg_num(&args, 0, 1.0)? > 0.5;
4131                self.gfx.borrow_mut().antialias = on;
4132                return Ok(Value::Unit);
4133            },
4134            // get_antialias() -> bool — current wireframe anti-aliasing state.
4135            "get_antialias"
4136            | "อ่านลบรอยหยัก"
4137            | "读取抗锯齿"
4138            | "アンチエイリアス取得"
4139            | "안티에일리어싱상태"
4140            | "خواندن_ضدلبه‌دندانه"
4141            | "اقرأ_مضاد_التسنن"
4142            | "קרא_החלקת_קצוות"
4143            | "اینٹی_الائیسنگ_پڑھو" => {
4144                return Ok(Value::Bool(self.gfx.borrow().antialias));
4145            },
4146
4147            // set_font_antialias(on) — smooth `font_text`/`font_text_fill` glyph
4148            // edges, independent of `set_antialias` (which only covers wireframe
4149            // strokes). Default OFF = crisp, hard-edged text; pass 1 to opt in.
4150            "set_font_antialias" | "글꼴안티에일리어싱" => {
4151                let on = self.arg_num(&args, 0, 1.0)? > 0.5;
4152                self.gfx.borrow_mut().font_antialias = on;
4153                return Ok(Value::Unit);
4154            },
4155            // get_font_antialias() -> bool — current font anti-aliasing state.
4156            "get_font_antialias" | "글꼴안티에일리어싱상태" => {
4157                return Ok(Value::Bool(self.gfx.borrow().font_antialias));
4158            },
4159
4160            // ── Step 6: Circle Primitives ──
4161            "draw_circle"
4162            | "วาดวงกลม"
4163            | "画圆"
4164            | "円描画"
4165            | "원그리기"
4166            | "رسم_دایره"
4167            | "ارسم_دائرة"
4168            | "צייר_עיגול"
4169            | "دائرہ_کھینچو"
4170            | "dessiner_cercle"
4171            | "kreis_zeichnen"
4172            | "рисовать_круг" => {
4173                let cx = self.arg_num(&args, 0, 0.0)? as i32;
4174                let cy = self.arg_num(&args, 1, 0.0)? as i32;
4175                let r = self.arg_num(&args, 2, 10.0)? as i32;
4176                let mut gfx = self.gfx.borrow_mut();
4177                let (w, h, color, blend) =
4178                    (gfx.width as i32, gfx.height as i32, gfx.color, gfx.blend);
4179                if gfx.antialias {
4180                    let (uw, uh) = (gfx.width, gfx.height);
4181                    let segs = ((r.max(1) as u32) * 4).clamp(24, 512);
4182                    crate::gfx::raster::draw_arc(
4183                        &mut gfx.buffer,
4184                        uw,
4185                        uh,
4186                        color,
4187                        true,
4188                        blend == 1,
4189                        cx as f32,
4190                        cy as f32,
4191                        r as f32,
4192                        0.0,
4193                        std::f32::consts::TAU,
4194                        segs,
4195                    );
4196                } else {
4197                    draw_circle_outline(&mut gfx.buffer, w, h, cx, cy, r, color, blend);
4198                }
4199                return Ok(Value::Unit);
4200            },
4201
4202            "draw_filled_circle"
4203            | "draw_disc"
4204            | "วาดวงกลมทึบ"
4205            | "画实心圆"
4206            | "塗りつぶし円"
4207            | "원채우기"
4208            | "رسم_دایره_توپر"
4209            | "ارسم_دائرة_ممتلئة"
4210            | "צייר_עיגול_מלא"
4211            | "بھرا_دائرہ_کھینچو" => {
4212                let cx = self.arg_num(&args, 0, 0.0)? as i32;
4213                let cy = self.arg_num(&args, 1, 0.0)? as i32;
4214                let r = self.arg_num(&args, 2, 10.0)? as i32;
4215                let mut gfx = self.gfx.borrow_mut();
4216                let (w, h, color, blend) =
4217                    (gfx.width as i32, gfx.height as i32, gfx.color, gfx.blend);
4218                draw_circle_filled(&mut gfx.buffer, w, h, cx, cy, r, color, blend);
4219                return Ok(Value::Unit);
4220            },
4221
4222            // draw_arc(cx, cy, r, a0, a1 [, segments]) — stroke a circular arc in
4223            // the pen colour (full circle when a1-a0 = TAU). Honors the antialias
4224            // flag; opaque by default (additive when blend = 1).
4225            "draw_arc"
4226            | "arc"
4227            | "วาดส่วนโค้ง"
4228            | "画弧"
4229            | "円弧描画"
4230            | "호그리기"
4231            | "رسم_کمان"
4232            | "ارسم_قوسا"
4233            | "צייר_קשת"
4234            | "آرک_کھینچو" => {
4235                let cx = self.arg_num(&args, 0, 0.0)? as f32;
4236                let cy = self.arg_num(&args, 1, 0.0)? as f32;
4237                let r = self.arg_num(&args, 2, 10.0)? as f32;
4238                let a0 = self.arg_num(&args, 3, 0.0)? as f32;
4239                let a1 = self.arg_num(&args, 4, std::f64::consts::TAU)? as f32;
4240                let default_segs = ((r.abs() * (a1 - a0).abs()).ceil() as u32).clamp(8, 1024);
4241                let segs = self.arg_num(&args, 5, default_segs as f64)? as u32;
4242                let mut gfx = self.gfx.borrow_mut();
4243                let color = gfx.color;
4244                #[cfg(not(target_arch = "wasm32"))]
4245                {
4246                    let (uw, uh, aa, add) = (gfx.width, gfx.height, gfx.antialias, gfx.blend == 1);
4247                    crate::gfx::raster::draw_arc(
4248                        &mut gfx.buffer,
4249                        uw,
4250                        uh,
4251                        color,
4252                        aa,
4253                        add,
4254                        cx,
4255                        cy,
4256                        r,
4257                        a0,
4258                        a1,
4259                        segs,
4260                    );
4261                }
4262                #[cfg(target_arch = "wasm32")]
4263                {
4264                    let segs_f = segs.max(1);
4265                    let step = (a1 - a0) / segs_f as f32;
4266                    let mut px = cx + r * a0.cos();
4267                    let mut py = cy + r * a0.sin();
4268                    let mut i = 1u32;
4269                    while i <= segs_f {
4270                        let a = a0 + step * i as f32;
4271                        let nx = cx + r * a.cos();
4272                        let ny = cy + r * a.sin();
4273                        gfx.depth_queue.push_line(0.0, color, px, py, nx, ny);
4274                        px = nx;
4275                        py = ny;
4276                        i += 1;
4277                    }
4278                }
4279                return Ok(Value::Unit);
4280            },
4281
4282            // ── Step 7: Transparent fills, gradient surfaces & colored shadows ──
4283            // These all write straight into the software framebuffer (gfx.buffer)
4284            // on both native and web, so no target gating is needed.
4285
4286            // set_alpha(a) — pen opacity 0..1 for the alpha-blended fills below.
4287            "set_alpha"
4288            | "ตั้งความโปร่งใส"
4289            | "设透明"
4290            | "アルファ設定"
4291            | "투명도설정"
4292            | "تنظیم_شفافیت"
4293            | "عيّن_الشفافية"
4294            | "קבע_שקיפות"
4295            | "شفافیت_مقرر_کرو"
4296            | "définir_alpha"
4297            | "alpha_setzen"
4298            | "задать_альфа" => {
4299                let a = self.arg_num(&args, 0, 1.0)? as f32;
4300                let mut gfx = self.gfx.borrow_mut();
4301                gfx.alpha = a.clamp(0.0, 1.0);
4302                let (m, al) = (gfx.blend, gfx.alpha);
4303                gfx.depth_queue.set_state(m, al); // 3-D queue captures alpha for subsequent pushes
4304                return Ok(Value::Unit);
4305            },
4306
4307            // mesh_hue(radians) — hue-rotate the baked per-tri colours of every
4308            // subsequent mesh_draw (.lmesh). 0 resets. Cheap: one matrix per call.
4309            "mesh_hue" | "หมุนสีเมช" | "فام_مش" | "صبغة_الشبكة" | "גוון_רשת" | "میش_ہیو" =>
4310            {
4311                let h = self.arg_num(&args, 0, 0.0)? as f32;
4312                let g = self.arg_num(&args, 1, 1.0)? as f32;
4313                let mut gfx = self.gfx.borrow_mut();
4314                gfx.mesh_hue = h;
4315                gfx.mesh_hue_gain = g.max(0.0);
4316                return Ok(Value::Unit);
4317            },
4318
4319            // set_frame_blur(amount 0..0.95) — afterimage trails: blend the previous
4320            // presented frame into each new one. 0 = off (also frees the ghost buffer).
4321            "set_frame_blur"
4322            | "frame_blur"
4323            | "เบลอเฟรม"
4324            | "تنظیم_تاری_فریم"
4325            | "عيّن_ضبابية_الإطار"
4326            | "קבע_טשטוש_פריים"
4327            | "فریم_بلر_مقرر_کرو" => {
4328                let a = self.arg_num(&args, 0, 0.0)? as f32;
4329                let mut gfx = self.gfx.borrow_mut();
4330                gfx.frame_blur = a.clamp(0.0, 0.95);
4331                if gfx.frame_blur <= 0.0 {
4332                    gfx.prev_frame = Vec::new();
4333                }
4334                return Ok(Value::Unit);
4335            },
4336
4337            // set_line_hue_cycle(rate) — rapidly cycle the hue of ALL wireframe line
4338            // strokes (draw_line / draw_line_3d). `rate` in radians/sec; 0 = off.
4339            // Process-global so a single call covers every stroke, every frame.
4340            "set_line_hue_cycle"
4341            | "ตั้งวนสีเส้น"
4342            | "تنظیم_چرخه_فام_خط"
4343            | "عيّن_دورة_صبغة_الخط"
4344            | "קבע_מחזור_גוון_קו"
4345            | "لائن_ہیو_سائیکل_مقرر_کرو" => {
4346                let rate = self.arg_num(&args, 0, 0.0)?;
4347                crate::runtime::set_line_hue_rate(rate);
4348                return Ok(Value::Unit);
4349            },
4350
4351            // set_color_space(mode) — 0 = legacy sRGB compositing (default),
4352            // 1 = gamma-correct linear-light compositing (blend in linear, store
4353            // sRGB) so alpha and gradients don't darken/shift hue.
4354            "set_color_space"
4355            | "ปริภูมิสี"
4356            | "色彩空间"
4357            | "色空間"
4358            | "색공간"
4359            | "تنظیم_فضای_رنگ"
4360            | "عيّن_فضاء_اللون"
4361            | "קבע_מרחב_צבע"
4362            | "کلر_اسپیس_مقرر_کرو"
4363            | "définir_espace_couleur"
4364            | "farbraum_setzen"
4365            | "задать_цветовое_пространство" => {
4366                let m = self.arg_num(&args, 0, 0.0)? as i64;
4367                self.gfx.borrow_mut().linear_blend = m != 0;
4368                return Ok(Value::Unit);
4369            },
4370
4371            // set_gradient_space(mode) — 1 = perceptual OkLab gradient interp
4372            // (default), 0 = legacy sRGB. Affects grad_triangle / grad_rect.
4373            "set_gradient_space"
4374            | "ปริภูมิไล่สี"
4375            | "渐变空间"
4376            | "グラデ空間"
4377            | "그라데이션공간"
4378            | "تنظیم_فضای_گرادیان"
4379            | "عيّن_فضاء_التدرج"
4380            | "קבע_מרחב_גרדיאנט"
4381            | "گریڈینٹ_اسپیس_مقرر_کرو"
4382            | "définir_espace_dégradé"
4383            | "verlaufsraum_setzen"
4384            | "задать_пространство_градиента" => {
4385                let m = self.arg_num(&args, 0, 1.0)? as i64;
4386                self.gfx.borrow_mut().grad_oklab = m != 0;
4387                return Ok(Value::Unit);
4388            },
4389
4390            // mix_color(r0,g0,b0, r1,g1,b1, t) — set the pen colour to the
4391            // perceptual OkLab blend of two colours (t in 0..1). Far nicer
4392            // mid-tones than a raw RGB lerp.
4393            "mix_color"
4394            | "ผสมสี"
4395            | "混合颜色"
4396            | "色混合"
4397            | "색혼합"
4398            | "ترکیب_رنگ"
4399            | "امزج_اللون"
4400            | "ערבב_צבע"
4401            | "رنگ_ملاؤ"
4402            | "mélanger_couleur"
4403            | "farbe_mischen"
4404            | "смешать_цвет" => {
4405                let c0 = rgb(
4406                    self.arg_num(&args, 0, 0.0)?,
4407                    self.arg_num(&args, 1, 0.0)?,
4408                    self.arg_num(&args, 2, 0.0)?,
4409                );
4410                let c1 = rgb(
4411                    self.arg_num(&args, 3, 255.0)?,
4412                    self.arg_num(&args, 4, 255.0)?,
4413                    self.arg_num(&args, 5, 255.0)?,
4414                );
4415                let t = self.arg_num(&args, 6, 0.5)? as f32;
4416                self.gfx.borrow_mut().color = crate::gfx::color::mix_oklab(c0, c1, t);
4417                return Ok(Value::Unit);
4418            },
4419
4420            // set_depth_test(on) — enable the per-pixel z-buffer for the deferred
4421            // 3-D/queued draws (correct interpenetration) instead of painter's-
4422            // only sort. 0 = off (default), non-zero = on.
4423            "set_depth_test"
4424            | "ทดสอบความลึก"
4425            | "深度测试"
4426            | "深度テスト"
4427            | "깊이테스트"
4428            | "تنظیم_آزمون_عمق"
4429            | "عيّن_اختبار_العمق"
4430            | "קבע_בדיקת_עומק"
4431            | "ڈیپتھ_ٹیسٹ_مقرر_کرو"
4432            | "définir_test_profondeur"
4433            | "tiefentest_setzen"
4434            | "задать_тест_глубины" => {
4435                let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
4436                self.gfx.borrow_mut().depth_test = on;
4437                return Ok(Value::Unit);
4438            },
4439
4440            // set_flat_shade(on) / ตั้งแฟลตเชด — perf test: skip all per-triangle/mesh
4441            // lighting (compute_lit_color) and draw with the raw pen colour.
4442            "set_flat_shade"
4443            | "ตั้งแฟลตเชด"
4444            | "平面着色"
4445            | "フラット着色"
4446            | "평면음영"
4447            | "تنظیم_سایه‌پردازی_تخت"
4448            | "عيّن_تظليلا_مسطحا"
4449            | "קבע_הצללה_שטוחה"
4450            | "فلیٹ_شیڈ_مقرر_کرو" => {
4451                let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
4452                self.gfx.borrow_mut().flat_shade = on;
4453                return Ok(Value::Unit);
4454            },
4455
4456            // set_normal_override(x,y,z) - force subsequent triangle/mesh lighting
4457            // to use a stylized world-space normal until reset_normal_override().
4458            "set_normal_override" => {
4459                let x = self.arg_num(&args, 0, 0.0)? as f32;
4460                let y = self.arg_num(&args, 1, -1.0)? as f32;
4461                let z = self.arg_num(&args, 2, 0.0)? as f32;
4462                self.gfx.borrow_mut().normal_override = Some([x, y, z]);
4463                return Ok(Value::Unit);
4464            },
4465
4466            "reset_normal_override" => {
4467                self.gfx.borrow_mut().normal_override = None;
4468                return Ok(Value::Unit);
4469            },
4470
4471            // clear_depth() / ล้างความลึก — force the z-buffer to clear on the next
4472            // flush. `เติม` already does this; call explicitly to start a fresh
4473            // depth pass mid-frame (e.g. a separate overlay scene).
4474            "clear_depth"
4475            | "ล้างความลึก"
4476            | "清深度"
4477            | "深度クリア"
4478            | "깊이지우기"
4479            | "پاک‌کردن_عمق"
4480            | "امسح_العمق"
4481            | "נקה_עומק"
4482            | "گہرائی_صاف_کرو" => {
4483                self.gfx.borrow_mut().zbuf_needs_clear = true;
4484                return Ok(Value::Unit);
4485            },
4486
4487            // depth_blur(focus, range, radius) / เบลอความลึก — depth-of-field post
4488            // pass over the framebuffer using the z-buffer: sharp at camera-space
4489            // depth `focus`, blurred up to `radius` px as depth departs by `range`.
4490            // Background (no geometry) blurs fully. Call AFTER `flush_3d` (so the
4491            // z-buffer is populated) and BEFORE `present`. Needs `set_depth_test(1)`.
4492            "depth_blur"
4493            | "เบลอความลึก"
4494            | "dof"
4495            | "depth_of_field"
4496            | "景深"
4497            | "تاری_عمق"
4498            | "ضبابية_العمق"
4499            | "טשטוש_עומק"
4500            | "ڈیپتھ_بلر" => {
4501                let focus = self.arg_num(&args, 0, 30.0)? as f32;
4502                let range = self.arg_num(&args, 1, 60.0)? as f32;
4503                let radius = self.arg_num(&args, 2, 3.0)?.max(0.0) as usize;
4504                // oil [0..1] — oil-slick treatment of the blurred zone:
4505                // iridescent chroma fringe + hue swirl (water / heat haze).
4506                let oil = self.arg_num(&args, 3, 0.0)? as f32;
4507                let mut gfx = self.gfx.borrow_mut();
4508                let w = gfx.width;
4509                let h = gfx.height;
4510                if gfx.depth_buf.len() == w * h {
4511                    let g = &mut *gfx;
4512                    crate::gfx::raster::depth_of_field(
4513                        &mut g.buffer,
4514                        &g.depth_buf,
4515                        w,
4516                        h,
4517                        focus,
4518                        range,
4519                        radius,
4520                        oil,
4521                    );
4522                }
4523                return Ok(Value::Unit);
4524            },
4525
4526            // light_pool(x, y, z, radius, r, g, b, intensity) / แอ่งแสง —
4527            // volumetric light splash: a soft additive radial vector gradient on
4528            // the floor at height y — the coloured pool a light throws on the
4529            // ground (underwater-light look). Smooth transparent edge, distance-
4530            // fog aware. Colours 0-255; intensity ~0.2-1.5.
4531            "light_pool"
4532            | "แอ่งแสง"
4533            | "光池"
4534            | "ライトプール"
4535            | "빛웅덩이"
4536            | "برکه_نور"
4537            | "بركة_ضوء"
4538            | "בריכת_אור"
4539            | "روشنی_تالاب"
4540            | "bassin_lumière"
4541            | "lichtpfütze"
4542            | "лужа_света" => {
4543                let x = self.arg_num(&args, 0, 0.0)? as f32;
4544                let y = self.arg_num(&args, 1, 0.0)? as f32;
4545                let z = self.arg_num(&args, 2, 0.0)? as f32;
4546                let radius = self.arg_num(&args, 3, 20.0)? as f32;
4547                let r = self.arg_num(&args, 4, 255.0)? as f32 / 255.0;
4548                let g = self.arg_num(&args, 5, 255.0)? as f32 / 255.0;
4549                let b = self.arg_num(&args, 6, 255.0)? as f32 / 255.0;
4550                let inten = self.arg_num(&args, 7, 1.0)? as f32;
4551                self.gfx
4552                    .borrow_mut()
4553                    .emit_light_pool(x, y, z, radius, [r, g, b], inten);
4554                return Ok(Value::Unit);
4555            },
4556
4557            // light_beam(x, y, z, floor_y, radius, r, g, b, intensity) / ลำแสงไฟ —
4558            // volumetric god-ray shaft: a soft additive double-cone from the
4559            // light position down to the floor plane, spreading to `radius`.
4560            // Pair with light_pool at the base. Colours 0-255.
4561            "light_beam"
4562            | "ลำแสงไฟ"
4563            | "光柱"
4564            | "ライトビーム"
4565            | "빛기둥"
4566            | "پرتو_نور"
4567            | "شعاع_ضوء"
4568            | "קרן_אור"
4569            | "روشنی_شعاع"
4570            | "faisceau_lumière"
4571            | "lichtstrahl"
4572            | "луч_света" => {
4573                let x = self.arg_num(&args, 0, 0.0)? as f32;
4574                let y = self.arg_num(&args, 1, 0.0)? as f32;
4575                let z = self.arg_num(&args, 2, 0.0)? as f32;
4576                let fy = self.arg_num(&args, 3, 0.0)? as f32;
4577                let radius = self.arg_num(&args, 4, 14.0)? as f32;
4578                let r = self.arg_num(&args, 5, 255.0)? as f32 / 255.0;
4579                let g = self.arg_num(&args, 6, 255.0)? as f32 / 255.0;
4580                let b = self.arg_num(&args, 7, 255.0)? as f32 / 255.0;
4581                let inten = self.arg_num(&args, 8, 1.0)? as f32;
4582                self.gfx
4583                    .borrow_mut()
4584                    .emit_light_beam(x, y, z, fy, radius, [r, g, b], inten);
4585                return Ok(Value::Unit);
4586            },
4587
4588            // grad_triangle(x0,y0,r0,g0,b0, x1,y1,r1,g1,b1, x2,y2,r2,g2,b2)
4589            // Smooth per-vertex gradient triangle — a cheap lit surface: put the
4590            // bright colour on the vertex facing the light. Honours set_alpha.
4591            "grad_triangle"
4592            | "สามเหลี่ยมไล่สี"
4593            | "渐变三角"
4594            | "グラデ三角"
4595            | "그라데삼각"
4596            | "مثلث_گرادیان"
4597            | "مثلث_متدرج"
4598            | "משולש_גרדיאנט"
4599            | "گریڈینٹ_مثلث"
4600            | "triangle_dégradé"
4601            | "dreieck_verlauf"
4602            | "градиент_треугольник" => {
4603                let x0 = self.arg_num(&args, 0, 0.0)? as f32;
4604                let y0 = self.arg_num(&args, 1, 0.0)? as f32;
4605                let c0 = rgb(
4606                    self.arg_num(&args, 2, 255.0)?,
4607                    self.arg_num(&args, 3, 255.0)?,
4608                    self.arg_num(&args, 4, 255.0)?,
4609                );
4610                let x1 = self.arg_num(&args, 5, 0.0)? as f32;
4611                let y1 = self.arg_num(&args, 6, 0.0)? as f32;
4612                let c1 = rgb(
4613                    self.arg_num(&args, 7, 255.0)?,
4614                    self.arg_num(&args, 8, 255.0)?,
4615                    self.arg_num(&args, 9, 255.0)?,
4616                );
4617                let x2 = self.arg_num(&args, 10, 0.0)? as f32;
4618                let y2 = self.arg_num(&args, 11, 0.0)? as f32;
4619                let c2 = rgb(
4620                    self.arg_num(&args, 12, 255.0)?,
4621                    self.arg_num(&args, 13, 255.0)?,
4622                    self.arg_num(&args, 14, 255.0)?,
4623                );
4624                let mut gfx = self.gfx.borrow_mut();
4625                let (w, h, alpha, mode, lin, ok) = (
4626                    gfx.width,
4627                    gfx.height,
4628                    gfx.alpha,
4629                    gfx.blend,
4630                    gfx.linear_blend,
4631                    gfx.grad_oklab,
4632                );
4633                crate::gfx::raster::fill_triangle_grad(
4634                    &mut gfx.buffer,
4635                    w,
4636                    h,
4637                    alpha,
4638                    mode,
4639                    lin,
4640                    ok,
4641                    x0,
4642                    y0,
4643                    c0,
4644                    x1,
4645                    y1,
4646                    c1,
4647                    x2,
4648                    y2,
4649                    c2,
4650                );
4651                return Ok(Value::Unit);
4652            },
4653
4654            // grad_rect(x,y,w,h, r0,g0,b0, r1,g1,b1, dir) — linear-gradient rect.
4655            // dir 0 = horizontal (left→right), else vertical (top→bottom).
4656            "grad_rect"
4657            | "สี่เหลี่ยมไล่สี"
4658            | "渐变矩形"
4659            | "グラデ矩形"
4660            | "그라데사각"
4661            | "مستطیل_گرادیان"
4662            | "مستطيل_متدرج"
4663            | "מלבן_גרדיאנט"
4664            | "گریڈینٹ_مستطیل"
4665            | "rectangle_dégradé"
4666            | "rechteck_verlauf"
4667            | "градиент_прямоугольник" => {
4668                let x = self.arg_num(&args, 0, 0.0)? as f32;
4669                let y = self.arg_num(&args, 1, 0.0)? as f32;
4670                let rw = self.arg_num(&args, 2, 0.0)? as f32;
4671                let rh = self.arg_num(&args, 3, 0.0)? as f32;
4672                let c0 = rgb(
4673                    self.arg_num(&args, 4, 255.0)?,
4674                    self.arg_num(&args, 5, 255.0)?,
4675                    self.arg_num(&args, 6, 255.0)?,
4676                );
4677                let c1 = rgb(
4678                    self.arg_num(&args, 7, 0.0)?,
4679                    self.arg_num(&args, 8, 0.0)?,
4680                    self.arg_num(&args, 9, 0.0)?,
4681                );
4682                let dir = self.arg_num(&args, 10, 1.0)? as u8;
4683                let mut gfx = self.gfx.borrow_mut();
4684                let (w, h, alpha, mode, lin, ok) = (
4685                    gfx.width,
4686                    gfx.height,
4687                    gfx.alpha,
4688                    gfx.blend,
4689                    gfx.linear_blend,
4690                    gfx.grad_oklab,
4691                );
4692                crate::gfx::raster::fill_rect_grad(
4693                    &mut gfx.buffer,
4694                    w,
4695                    h,
4696                    alpha,
4697                    mode,
4698                    lin,
4699                    ok,
4700                    x,
4701                    y,
4702                    rw,
4703                    rh,
4704                    c0,
4705                    c1,
4706                    dir,
4707                );
4708                return Ok(Value::Unit);
4709            },
4710
4711            // shadow_blob(cx,cy, rx,ry, alpha) — soft colored shadow ellipse in
4712            // the current pen colour. Dark colour = normal shadow; any hue = a
4713            // tinted/coloured shadow. Edge softness comes from shadow_params.
4714            "shadow_blob"
4715            | "เงาวงรี"
4716            | "阴影斑"
4717            | "影ブロブ"
4718            | "그림자블롭"
4719            | "لکه_سایه"
4720            | "بقعة_ظل"
4721            | "כתם_צל"
4722            | "سایہ_دھبہ"
4723            | "tache_ombre"
4724            | "schattenklecks"
4725            | "пятно_тени" => {
4726                let cx = self.arg_num(&args, 0, 0.0)? as f32;
4727                let cy = self.arg_num(&args, 1, 0.0)? as f32;
4728                let rx = self.arg_num(&args, 2, 16.0)? as f32;
4729                let ry = self.arg_num(&args, 3, 8.0)? as f32;
4730                let a = self.arg_num(&args, 4, 0.5)? as f32;
4731                let mut gfx = self.gfx.borrow_mut();
4732                let (w, h, color, soft, mode, lin) = (
4733                    gfx.width,
4734                    gfx.height,
4735                    gfx.color,
4736                    gfx.shadow.soft,
4737                    gfx.blend,
4738                    gfx.linear_blend,
4739                );
4740                crate::gfx::raster::fill_disc_soft(
4741                    &mut gfx.buffer,
4742                    w,
4743                    h,
4744                    cx,
4745                    cy,
4746                    rx,
4747                    ry,
4748                    color,
4749                    a,
4750                    soft,
4751                    mode,
4752                    lin,
4753                );
4754                return Ok(Value::Unit);
4755            },
4756
4757            // cast_shadow(cx,cy, height) — height-driven contact shadow in the
4758            // current pen colour. Closer to the surface (small height) = smaller,
4759            // darker, sharper; farther (large height) = bigger, fainter, softer.
4760            // Tune the ramp with shadow_params.
4761            "cast_shadow"
4762            | "ทอดเงา"
4763            | "投射阴影"
4764            | "影を落とす"
4765            | "그림자드리우기"
4766            | "افکندن_سایه"
4767            | "ألقِ_ظلا"
4768            | "הטל_צל"
4769            | "سایہ_ڈالو"
4770            | "projeter_ombre"
4771            | "schatten_werfen"
4772            | "отбросить_тень" => {
4773                let cx = self.arg_num(&args, 0, 0.0)? as f32;
4774                let cy = self.arg_num(&args, 1, 0.0)? as f32;
4775                let height = (self.arg_num(&args, 2, 0.0)? as f32).max(0.0);
4776                let mut gfx = self.gfx.borrow_mut();
4777                let sp = gfx.shadow;
4778                let radius = (sp.base + sp.grow * height).max(0.5);
4779                let alpha = (sp.alpha - sp.fade * height).clamp(0.04, 1.0);
4780                let soft = (sp.soft + height * 0.004).clamp(0.0, 0.95);
4781                let (w, h, color, mode, lin) = (
4782                    gfx.width,
4783                    gfx.height,
4784                    gfx.color,
4785                    gfx.blend,
4786                    gfx.linear_blend,
4787                );
4788                crate::gfx::raster::fill_disc_soft(
4789                    &mut gfx.buffer,
4790                    w,
4791                    h,
4792                    cx,
4793                    cy,
4794                    radius,
4795                    radius * 0.62,
4796                    color,
4797                    alpha,
4798                    soft,
4799                    mode,
4800                    lin,
4801                );
4802                return Ok(Value::Unit);
4803            },
4804
4805            // shadow_params(base, grow, alpha, fade, soft) — tune cast_shadow.
4806            // Each arg defaults to the current value, so you can set just one.
4807            "shadow_params"
4808            | "ตั้งค่าเงา"
4809            | "阴影参数"
4810            | "影設定"
4811            | "그림자설정"
4812            | "پارامترهای_سایه"
4813            | "معاملات_الظل"
4814            | "פרמטרי_צל"
4815            | "سایہ_پیرامیٹرز"
4816            | "paramètres_ombre"
4817            | "schattenparameter"
4818            | "параметры_тени" => {
4819                let cur = self.gfx.borrow().shadow;
4820                let base = self.arg_num(&args, 0, cur.base as f64)? as f32;
4821                let grow = self.arg_num(&args, 1, cur.grow as f64)? as f32;
4822                let alpha = self.arg_num(&args, 2, cur.alpha as f64)? as f32;
4823                let fade = self.arg_num(&args, 3, cur.fade as f64)? as f32;
4824                let soft = self.arg_num(&args, 4, cur.soft as f64)? as f32;
4825                self.gfx.borrow_mut().shadow =
4826                    crate::gfx::ShadowParams { base, grow, alpha, fade, soft };
4827                return Ok(Value::Unit);
4828            },
4829
4830            // depth_triangle(x0,y0, x1,y1, x2,y2, z) — queue a depth-sorted tri in
4831            // the current colour. Drawn back-to-front (painter's algorithm) at
4832            // present(); larger z = farther away. Lets 2-D sprites/quads sort by
4833            // depth the same way 3-D faces do.
4834            "depth_triangle"
4835            | "สามเหลี่ยมเรียงลึก"
4836            | "深度三角"
4837            | "深度三角形"
4838            | "깊이삼각"
4839            | "مثلث_عمق"
4840            | "مثلث_العمق"
4841            | "משולש_עומק"
4842            | "گہرائی_مثلث"
4843            | "triangle_profondeur"
4844            | "tiefendreieck"
4845            | "глубина_треугольник" => {
4846                let x0 = self.arg_num(&args, 0, 0.0)? as f32;
4847                let y0 = self.arg_num(&args, 1, 0.0)? as f32;
4848                let x1 = self.arg_num(&args, 2, 0.0)? as f32;
4849                let y1 = self.arg_num(&args, 3, 0.0)? as f32;
4850                let x2 = self.arg_num(&args, 4, 0.0)? as f32;
4851                let y2 = self.arg_num(&args, 5, 0.0)? as f32;
4852                let z = self.arg_num(&args, 6, 0.0)? as f32;
4853                let mut gfx = self.gfx.borrow_mut();
4854                let color = gfx.color;
4855                gfx.depth_queue
4856                    .push_triangle(z, color, x0, y0, x1, y1, x2, y2);
4857                return Ok(Value::Unit);
4858            },
4859
4860            // depth_line(x0,y0, x1,y1, z) — queue a depth-sorted line in the
4861            // current colour (same painter's queue as depth_triangle).
4862            "depth_line"
4863            | "เส้นเรียงลึก"
4864            | "深度线"
4865            | "深度線"
4866            | "깊이선"
4867            | "خط_عمق"
4868            | "خط_العمق"
4869            | "קו_עומק"
4870            | "گہرائی_لکیر"
4871            | "ligne_profondeur"
4872            | "tiefenlinie"
4873            | "глубина_линия" => {
4874                let x0 = self.arg_num(&args, 0, 0.0)? as f32;
4875                let y0 = self.arg_num(&args, 1, 0.0)? as f32;
4876                let x1 = self.arg_num(&args, 2, 0.0)? as f32;
4877                let y1 = self.arg_num(&args, 3, 0.0)? as f32;
4878                let z = self.arg_num(&args, 4, 0.0)? as f32;
4879                let mut gfx = self.gfx.borrow_mut();
4880                let color = gfx.color;
4881                gfx.depth_queue.push_line(z, color, x0, y0, x1, y1);
4882                return Ok(Value::Unit);
4883            },
4884
4885            // ══════════════════════════════════════════════════════════════════
4886            // GRAPHICS BUILTINS
4887            // Thai names first, then English aliases.
4888            // ══════════════════════════════════════════════════════════════════
4889
4890            // ── เปิดหน้าต่าง(width, height, title) — open_window ──
4891            "เปิดหน้าต่าง"
4892            | "open_window"
4893            | "gfx_window"
4894            | "开窗"
4895            | "ウィンドウ開く"
4896            | "창열기"
4897            | "باز_کردن_پنجره"
4898            | "افتح_نافذة"
4899            | "פתח_חלון"
4900            | "ونڈو_کھولو" => {
4901                let w = self.arg_num(&args, 0, 800.0)? as usize;
4902                let h = self.arg_num(&args, 1, 600.0)? as usize;
4903                #[cfg(not(target_arch = "wasm32"))]
4904                {
4905                    let title = args
4906                        .get(2)
4907                        .map(|v| v.to_string())
4908                        .unwrap_or_else(|| "Ling".into());
4909                    let mut gfx = self.gfx.borrow_mut();
4910                    let mut win = minifb::Window::new(
4911                        &title,
4912                        w,
4913                        h,
4914                        minifb::WindowOptions {
4915                            resize: false,
4916                            scale: minifb::Scale::X1,
4917                            ..Default::default()
4918                        },
4919                    )
4920                    .map_err(|e| EvalErr::from(format!("cannot open window: {e}")))?;
4921                    apply_frame_pacing(&mut win, gfx.vsync);
4922                    gfx.buffer = vec![0u32; w * h];
4923                    gfx.width = w;
4924                    gfx.height = h;
4925                    gfx.window = Some(win);
4926                    gfx.topmost_window = false;
4927                    gfx.sync_projection();
4928                }
4929                #[cfg(target_arch = "wasm32")]
4930                {
4931                    let mut gfx = self.gfx.borrow_mut();
4932                    gfx.width = w;
4933                    gfx.height = h;
4934                    gfx.buffer.resize(w * h, 0); // keep the CPU framebuffer in sync
4935                    gfx.sync_projection();
4936                    crate::gfx::webgl::resize(w as u32, h as u32);
4937                }
4938                return Ok(Value::Unit);
4939            },
4940
4941            // ── เติม(r, g, b) — fill / clear screen with colour ──
4942            "เติม" | "fill" | "gfx_fill" | "clear" | "填" | "塗り潰し" | "채우기" | "清"
4943            | "消去" | "지우기" | "پر_کن" | "املأ" | "מלא" | "بھرو" => {
4944                let r = self.arg_num(&args, 0, 0.0)? as u32;
4945                let g = self.arg_num(&args, 1, 0.0)? as u32;
4946                let b = self.arg_num(&args, 2, 0.0)? as u32;
4947                #[cfg(not(target_arch = "wasm32"))]
4948                {
4949                    let c = (r << 16) | (g << 8) | b;
4950                    let mut gfx = self.gfx.borrow_mut();
4951                    gfx.buffer.fill(c);
4952                    gfx.zbuf_needs_clear = true; // clear color ⇒ clear depth next flush
4953                    gfx.edge_set.clear(); // reset shared-edge dedup for new frame
4954                }
4955                #[cfg(target_arch = "wasm32")]
4956                {
4957                    let mut gfx = self.gfx.borrow_mut();
4958                    gfx.fill_r = r as f32 / 255.0;
4959                    gfx.fill_g = g as f32 / 255.0;
4960                    gfx.fill_b = b as f32 / 255.0;
4961                    let c = (r << 16) | (g << 8) | b;
4962                    gfx.buffer.fill(c);
4963                    gfx.zbuf_needs_clear = true;
4964                    gfx.edge_set.clear();
4965                }
4966                return Ok(Value::Unit);
4967            },
4968
4969            // ── set_color_hsl(h, s, l) — set drawing colour from HSL ──
4970            // h: 0–360 degrees, s: 0–100 saturation, l: 0–100 lightness
4971            "set_color_hsl"
4972            | "颜色HSL"
4973            | "色相"
4974            | "HSL色"
4975            | "HSL색설정"
4976            | "สีHSLวาด"
4977            | "تنظیم_رنگ_HSL"
4978            | "عيّن_اللون_HSL"
4979            | "קבע_צבע_HSL"
4980            | "HSL_رنگ_مقرر_کرو"
4981            | "définir_couleur_hsl"
4982            | "farbe_hsl_setzen"
4983            | "задать_цвет_hsl" => {
4984                let h = self.arg_num(&args, 0, 0.0)?;
4985                let s = self.arg_num(&args, 1, 70.0)?;
4986                let l = self.arg_num(&args, 2, 50.0)?;
4987                let hex = hsl_to_hex(h, s, l);
4988                let r = u32::from_str_radix(&hex[1..3], 16).unwrap_or(255);
4989                let g = u32::from_str_radix(&hex[3..5], 16).unwrap_or(255);
4990                let b = u32::from_str_radix(&hex[5..7], 16).unwrap_or(255);
4991                self.gfx.borrow_mut().color = (r << 16) | (g << 8) | b;
4992                return Ok(Value::Unit);
4993            },
4994
4995            // ── สีดินสอ(r, g, b) — set drawing colour ──
4996            "สีดินสอ"
4997            | "set_color"
4998            | "gfx_color"
4999            | "color"
5000            | "设色"
5001            | "色設定"
5002            | "색설정"
5003            | "تنظیم_رنگ"
5004            | "عيّن_اللون"
5005            | "קבע_צבע"
5006            | "رنگ_مقرر_کرو" => {
5007                let r = self.arg_num(&args, 0, 255.0)? as u32;
5008                let g = self.arg_num(&args, 1, 255.0)? as u32;
5009                let b = self.arg_num(&args, 2, 255.0)? as u32;
5010                self.gfx.borrow_mut().color = (r << 16) | (g << 8) | b;
5011                return Ok(Value::Unit);
5012            },
5013
5014            // ── วาดสามเหลี่ยม(x1,y1, x2,y2, x3,y3) — draw filled triangle ──
5015            "วาดสามเหลี่ยม"
5016            | "draw_triangle"
5017            | "gfx_triangle"
5018            | "triangle"
5019            | "画三角"
5020            | "三角形描画"
5021            | "삼각형그리기"
5022            | "رسم_مثلث"
5023            | "ارسم_مثلثا"
5024            | "צייר_משולש"
5025            | "مثلث_کھینچو" => {
5026                let x0 = self.arg_num(&args, 0, 0.0)? as f32;
5027                let y0 = self.arg_num(&args, 1, 0.0)? as f32;
5028                let x1 = self.arg_num(&args, 2, 0.0)? as f32;
5029                let y1 = self.arg_num(&args, 3, 0.0)? as f32;
5030                let x2 = self.arg_num(&args, 4, 0.0)? as f32;
5031                let y2 = self.arg_num(&args, 5, 0.0)? as f32;
5032                let mut gfx = self.gfx.borrow_mut();
5033                let color = gfx.color;
5034                #[cfg(not(target_arch = "wasm32"))]
5035                {
5036                    let w = gfx.width;
5037                    let h = gfx.height;
5038                    fill_triangle(&mut gfx.buffer, w, h, color, x0, y0, x1, y1, x2, y2);
5039                }
5040                #[cfg(target_arch = "wasm32")]
5041                gfx.depth_queue
5042                    .push_triangle(0.0, color, x0, y0, x1, y1, x2, y2);
5043                return Ok(Value::Unit);
5044            },
5045
5046            // ── วาดเส้น(x1,y1, x2,y2) — draw line ──
5047            "วาดเส้น"
5048            | "draw_line"
5049            | "gfx_line"
5050            | "line"
5051            | "画线"
5052            | "線描く"
5053            | "선그리기"
5054            | "رسم_خط"
5055            | "ارسم_خط"
5056            | "צייר_קו"
5057            | "لکیر_کھینچو" => {
5058                let x0 = self.arg_num(&args, 0, 0.0)? as f32;
5059                let y0 = self.arg_num(&args, 1, 0.0)? as f32;
5060                let x1 = self.arg_num(&args, 2, 0.0)? as f32;
5061                let y1 = self.arg_num(&args, 3, 0.0)? as f32;
5062                let mut gfx = self.gfx.borrow_mut();
5063                let color = gfx.color;
5064                #[cfg(not(target_arch = "wasm32"))]
5065                {
5066                    let w = gfx.width;
5067                    let h = gfx.height;
5068                    let aa = gfx.antialias;
5069                    let add = gfx.blend == 1;
5070                    if aa {
5071                        crate::gfx::raster::draw_line_aa(
5072                            &mut gfx.buffer,
5073                            w,
5074                            h,
5075                            color,
5076                            add,
5077                            x0,
5078                            y0,
5079                            x1,
5080                            y1,
5081                        );
5082                    } else {
5083                        draw_line(&mut gfx.buffer, w, h, color, x0, y0, x1, y1);
5084                    }
5085                }
5086                #[cfg(target_arch = "wasm32")]
5087                gfx.depth_queue.push_line(0.0, color, x0, y0, x1, y1);
5088                return Ok(Value::Unit);
5089            },
5090
5091            // ── วาดจุด(x, y) — plot a single pixel ──
5092            "วาดจุด"
5093            | "draw_pixel"
5094            | "gfx_pixel"
5095            | "pixel"
5096            | "画点"
5097            | "点描く"
5098            | "점그리기"
5099            | "رسم_نقطه"
5100            | "ارسم_نقطة"
5101            | "צייר_פיקסל"
5102            | "پکسل_کھینچو" => {
5103                let px = self.arg_num(&args, 0, 0.0)? as i32;
5104                let py = self.arg_num(&args, 1, 0.0)? as i32;
5105                #[cfg(not(target_arch = "wasm32"))]
5106                {
5107                    let mut gfx = self.gfx.borrow_mut();
5108                    let color = gfx.color;
5109                    let w = gfx.width;
5110                    let h = gfx.height;
5111                    if px >= 0 && py >= 0 && (px as usize) < w && (py as usize) < h {
5112                        gfx.buffer[py as usize * w + px as usize] = color;
5113                    }
5114                }
5115                #[cfg(target_arch = "wasm32")]
5116                {
5117                    // Render pixel as a 1×1 square via two triangles.
5118                    let mut gfx = self.gfx.borrow_mut();
5119                    let color = gfx.color;
5120                    let x = px as f32;
5121                    let y = py as f32;
5122                    gfx.depth_queue
5123                        .push_triangle(0.0, color, x, y, x + 1.0, y, x + 1.0, y + 1.0);
5124                    gfx.depth_queue
5125                        .push_triangle(0.0, color, x, y, x + 1.0, y + 1.0, x, y + 1.0);
5126                }
5127                return Ok(Value::Unit);
5128            },
5129
5130            // ── แสดงผล() — flush depth queue, then present frame to screen ──
5131            "แสดงผล" | "present" | "gfx_present" | "show" | "显" | "呈现" | "表示" | "표시"
5132            | "نمایش" | "اعرض" | "הצג" | "دکھاؤ" => {
5133                // Click-edge widgets (ui_button etc.) compare THIS frame's
5134                // mouse_now() against `mouse_was_down` to detect a fresh
5135                // press. That comparison only works if mouse_was_down
5136                // reflects what the script itself observed this frame — i.e.
5137                // the state from BEFORE update_with_buffer below pulls in new
5138                // OS events. Capturing it after (as the "freshest" read)
5139                // would mean a just-arrived click is already baked into
5140                // mouse_was_down by the time next frame's ui_button compares
5141                // against it, so `down && !mouse_was_down` is never true and
5142                // clicks never register at all. Declared at the top of the
5143                // match arm (not inside the block below) so it survives past
5144                // the wasm32/non-wasm32 split further down.
5145                #[cfg(not(target_arch = "wasm32"))]
5146                let pre_update_mouse_down = self
5147                    .gfx
5148                    .borrow()
5149                    .window
5150                    .as_ref()
5151                    .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
5152                    .unwrap_or(false);
5153                #[cfg(not(target_arch = "wasm32"))]
5154                {
5155                    ling_fps_tick();
5156                    ling_phase_frame();
5157                    // Flush depth queue and present — release borrow before reading mouse.
5158                    {
5159                        let mut gfx = self.gfx.borrow_mut();
5160                        if !gfx.depth_queue.is_empty() {
5161                            let w = gfx.width;
5162                            let h = gfx.height;
5163                            let dt = gfx.depth_test;
5164                            let reset_z = gfx.zbuf_needs_clear;
5165                            let (bm, ba) = (gfx.blend, gfx.alpha);
5166                            let aa = gfx.antialias;
5167                            let queue = std::mem::take(&mut gfx.depth_queue);
5168                            {
5169                                let g = &mut *gfx;
5170                                let z = if dt { Some(&mut g.depth_buf) } else { None };
5171                                queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
5172                            }
5173                            gfx.depth_queue.set_state(bm, ba);
5174                            gfx.zbuf_needs_clear = false;
5175                        }
5176                        let _t = std::time::Instant::now();
5177                        if !gfx.post_done {
5178                            gfx.toon_post_process();
5179                        }
5180                        gfx.post_done = false;
5181                        ling_phase_add(phase::TOON, _t.elapsed().as_nanos());
5182                        let w = gfx.width;
5183                        let h = gfx.height;
5184                        let g = &mut *gfx;
5185                        if g.frame_blur > 0.0 {
5186                            // Afterimage trails: previous frame decays by `frame_blur`
5187                            // per frame and composites with MAX — fresh content stays
5188                            // full-brightness, ghosts fade out over time.
5189                            // retention 0.98 @60fps ≈ trails last ~2.6 s.
5190                            let a = (g.frame_blur.clamp(0.0, 0.995) * 256.0) as u32;
5191                            if g.prev_frame.len() != g.buffer.len() {
5192                                g.prev_frame = g.buffer.clone();
5193                            }
5194                            for (dst, prev) in g.buffer.iter_mut().zip(g.prev_frame.iter_mut()) {
5195                                let c = *dst;
5196                                let pv = *prev;
5197                                let pr = (((pv >> 16) & 0xFF) * a) >> 8;
5198                                let pg = (((pv >> 8) & 0xFF) * a) >> 8;
5199                                let pb = ((pv & 0xFF) * a) >> 8;
5200                                let cr = (c >> 16) & 0xFF;
5201                                let cg = (c >> 8) & 0xFF;
5202                                let cb = c & 0xFF;
5203                                let outp = (cr.max(pr) << 16) | (cg.max(pg) << 8) | cb.max(pb);
5204                                *dst = outp;
5205                                *prev = outp;
5206                            }
5207                        }
5208                        if let Some(win) = g.window.as_mut() {
5209                            let _b = std::time::Instant::now();
5210                            win.update_with_buffer(&g.buffer, w, h)
5211                                .map_err(|e| EvalErr::from(format!("present error: {e}")))?;
5212                            ling_phase_add(phase::BLIT, _b.elapsed().as_nanos());
5213                        }
5214                    }
5215                    // Read mouse AFTER update_with_buffer so events are processed.
5216                    let mouse_pos = {
5217                        let gfx = self.gfx.borrow();
5218                        gfx.window
5219                            .as_ref()
5220                            .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
5221                    };
5222                    let mut gfx = self.gfx.borrow_mut();
5223                    if gfx.mouse_captured {
5224                        let w = gfx.width as f32;
5225                        let h = gfx.height as f32;
5226                        if let Some((mx, my)) = mouse_pos {
5227                            if gfx.last_mx.is_nan() {
5228                                gfx.mouse_dx = 0.0;
5229                                gfx.mouse_dy = 0.0;
5230                                gfx.last_mx = mx;
5231                                gfx.last_my = my;
5232                            } else {
5233                                gfx.mouse_dx = mx - gfx.last_mx;
5234                                gfx.mouse_dy = my - gfx.last_my;
5235                                // Wrap the cursor at every edge (L/R/U/D) → infinite look
5236                                // on both axes, and the cursor is NOT trapped (alt-tab works).
5237                                let margin = 6.0;
5238                                let (mut nx, mut ny, mut warp) = (mx, my, false);
5239                                if mx < margin {
5240                                    nx = w - margin - 2.0;
5241                                    warp = true;
5242                                } else if mx > w - margin {
5243                                    nx = margin + 2.0;
5244                                    warp = true;
5245                                }
5246                                if my < margin {
5247                                    ny = h - margin - 2.0;
5248                                    warp = true;
5249                                } else if my > h - margin {
5250                                    ny = margin + 2.0;
5251                                    warp = true;
5252                                }
5253                                if warp {
5254                                    #[cfg(windows)]
5255                                    unsafe {
5256                                        #[repr(C)]
5257                                        struct RECT {
5258                                            left: i32,
5259                                            top: i32,
5260                                            right: i32,
5261                                            bottom: i32,
5262                                        }
5263                                        extern "system" {
5264                                            fn GetForegroundWindow() -> isize;
5265                                            fn GetWindowRect(hwnd: isize, lpRect: *mut RECT)
5266                                                -> i32;
5267                                            fn SetCursorPos(x: i32, y: i32) -> i32;
5268                                        }
5269                                        let hwnd = GetForegroundWindow();
5270                                        let mut rect =
5271                                            RECT { left: 0, top: 0, right: 0, bottom: 0 };
5272                                        if GetWindowRect(hwnd, &mut rect) != 0 {
5273                                            SetCursorPos(
5274                                                rect.left + nx as i32,
5275                                                rect.top + ny as i32,
5276                                            );
5277                                        }
5278                                    }
5279                                    gfx.last_mx = nx;
5280                                    gfx.last_my = ny;
5281                                } else {
5282                                    gfx.last_mx = mx;
5283                                    gfx.last_my = my;
5284                                }
5285                            }
5286                        } else {
5287                            gfx.mouse_dx = 0.0;
5288                            gfx.mouse_dy = 0.0;
5289                        }
5290                    } else if let Some((mx, my)) = mouse_pos {
5291                        if gfx.last_mx.is_nan() {
5292                            gfx.mouse_dx = 0.0;
5293                            gfx.mouse_dy = 0.0;
5294                        } else {
5295                            gfx.mouse_dx = mx - gfx.last_mx;
5296                            gfx.mouse_dy = my - gfx.last_my;
5297                        }
5298                        gfx.last_mx = mx;
5299                        gfx.last_my = my;
5300                    } else {
5301                        gfx.mouse_dx = 0.0;
5302                        gfx.mouse_dy = 0.0;
5303                    }
5304
5305                    // Alt-tab support: minifb has no WM_KILLFOCUS handler on Windows,
5306                    // so a key/button released while another window was focused can
5307                    // still read as "down" for one stale frame right after the user
5308                    // alt-tabs back. Detect the unfocused→focused transition and
5309                    // swallow raw input for a short grace window afterward instead of
5310                    // letting a phantom held key jerk the camera. See key_down /
5311                    // mouse_down* below (they early-out on gfx.input_suppressed()).
5312                    let is_active = gfx.window.as_mut().map(|w| w.is_active()).unwrap_or(true);
5313                    if is_active && !gfx.was_active {
5314                        gfx.focus_grace_frames = 5;
5315                        // Regained focus: restore HWND_TOPMOST so the borderless-
5316                        // fullscreen window covers the taskbar again.
5317                        #[cfg(windows)]
5318                        if gfx.topmost_window {
5319                            if let Some(w) = gfx.window.as_ref() {
5320                                set_window_topmost(w.get_window_handle() as isize, true);
5321                            }
5322                        }
5323                    } else if !is_active && gfx.was_active {
5324                        // Lost focus (alt-tab): drop topmost so the game stops
5325                        // covering whatever window the user just switched to —
5326                        // otherwise a topmost borderless window visually "wins"
5327                        // even though it's no longer focused, making alt-tab
5328                        // look broken.
5329                        #[cfg(windows)]
5330                        if gfx.topmost_window {
5331                            if let Some(w) = gfx.window.as_ref() {
5332                                set_window_topmost(w.get_window_handle() as isize, false);
5333                            }
5334                        }
5335                    }
5336                    gfx.was_active = is_active;
5337                    if gfx.focus_grace_frames > 0 {
5338                        gfx.focus_grace_frames -= 1;
5339                    }
5340                }
5341                #[cfg(target_arch = "wasm32")]
5342                {
5343                    {
5344                        // Software-render everything (3-D depth queue + 2-D vtex/ui that
5345                        // already wrote into the buffer) into the framebuffer, exactly
5346                        // like native, then upload that buffer to the canvas in one blit.
5347                        let mut gfx = self.gfx.borrow_mut();
5348                        let w = gfx.width;
5349                        let h = gfx.height;
5350                        if gfx.buffer.len() != w * h {
5351                            gfx.buffer.resize(w * h, 0);
5352                        }
5353                        if !gfx.depth_queue.is_empty() {
5354                            let dt = gfx.depth_test;
5355                            let reset_z = gfx.zbuf_needs_clear;
5356                            let aa = gfx.antialias;
5357                            let queue = std::mem::take(&mut gfx.depth_queue);
5358                            {
5359                                let g = &mut *gfx;
5360                                let z = if dt { Some(&mut g.depth_buf) } else { None };
5361                                queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
5362                            }
5363                            gfx.zbuf_needs_clear = false;
5364                        }
5365                        if !gfx.post_done {
5366                            gfx.toon_post_process();
5367                        }
5368                        gfx.post_done = false;
5369                        crate::gfx::webgl::blit_rgb(&gfx.buffer, w, h);
5370                    }
5371                    self.wasm_pace_frame();
5372                }
5373                // Update the click-edge latch for interactive UI widgets —
5374                // using the PRE-update_with_buffer snapshot captured at the
5375                // top of this function (see the comment there for why).
5376                #[cfg(not(target_arch = "wasm32"))]
5377                {
5378                    self.mouse_was_down = pre_update_mouse_down;
5379                }
5380                // Increment frame counter
5381                self.frame_num += 1;
5382                return Ok(Value::Unit);
5383            },
5384
5385            // ── เปิดหน้าต่างเต็มจอ(title) — true native-res fullscreen window ──
5386            "เปิดหน้าต่างเต็มจอ"
5387            | "open_fullscreen"
5388            | "fullscreen"
5389            | "全屏"
5390            | "全画面"
5391            | "전체화면"
5392            | "باز_کردن_تمام‌صفحه"
5393            | "افتح_ملء_الشاشة"
5394            | "פתח_מסך_מלא"
5395            | "فل_سکرین_کھولو" => {
5396                // In WASM the canvas defines the viewport; use its current size
5397                // as the default so the projection matches what's actually visible.
5398                #[cfg(target_arch = "wasm32")]
5399                let (default_w, default_h) = {
5400                    let (cw, ch) = crate::gfx::webgl::canvas_size();
5401                    (cw as f64, ch as f64)
5402                };
5403                // On native: query the actual primary monitor resolution.
5404                #[cfg(all(not(target_arch = "wasm32"), windows))]
5405                let (default_w, default_h) = unsafe {
5406                    extern "system" {
5407                        fn GetSystemMetrics(nIndex: i32) -> i32;
5408                    }
5409                    (GetSystemMetrics(0) as f64, GetSystemMetrics(1) as f64)
5410                };
5411                #[cfg(all(not(target_arch = "wasm32"), not(windows)))]
5412                let (default_w, default_h) = native_screen_size();
5413
5414                let w = args
5415                    .get(1)
5416                    .map(|v| self.to_number(v).unwrap_or(default_w) as usize)
5417                    .unwrap_or(default_w as usize);
5418                let h = args
5419                    .get(2)
5420                    .map(|v| self.to_number(v).unwrap_or(default_h) as usize)
5421                    .unwrap_or(default_h as usize);
5422                #[cfg(not(target_arch = "wasm32"))]
5423                {
5424                    let title = args
5425                        .first()
5426                        .map(|v| v.to_string())
5427                        .unwrap_or_else(|| "Ling".into());
5428                    let mut gfx = self.gfx.borrow_mut();
5429                    let mut win = minifb::Window::new(
5430                        &title,
5431                        w,
5432                        h,
5433                        minifb::WindowOptions {
5434                            borderless: true,
5435                            title: false,
5436                            resize: false,
5437                            topmost: true,
5438                            scale: minifb::Scale::X1,
5439                            ..Default::default()
5440                        },
5441                    )
5442                    .map_err(|e| EvalErr::from(format!("cannot open fullscreen: {e}")))?;
5443                    apply_frame_pacing(&mut win, gfx.vsync);
5444                    // Grab the native handle *before* moving the window into gfx.
5445                    #[cfg(windows)]
5446                    let hwnd = win.get_window_handle() as isize;
5447                    gfx.buffer = vec![0u32; w * h];
5448                    gfx.width = w;
5449                    gfx.height = h;
5450                    gfx.window = Some(win);
5451                    gfx.topmost_window = true;
5452                    #[cfg(windows)]
5453                    {
5454                        gfx.hwnd = hwnd;
5455                    }
5456                    gfx.sync_projection();
5457                    // Strip all chrome and cover the full screen, above the taskbar.
5458                    #[cfg(windows)]
5459                    make_borderless_fullscreen(hwnd, w as i32, h as i32);
5460                    #[cfg(windows)]
5461                    force_window_focus(hwnd);
5462                }
5463                #[cfg(target_arch = "wasm32")]
5464                {
5465                    let mut gfx = self.gfx.borrow_mut();
5466                    gfx.width = w;
5467                    gfx.height = h;
5468                    gfx.buffer.resize(w * h, 0); // keep the CPU framebuffer in sync
5469                    gfx.sync_projection();
5470                    crate::gfx::webgl::resize(w as u32, h as u32);
5471                }
5472                return Ok(Value::Unit);
5473            },
5474
5475            // ── ความกว้าง() / ความสูง() — current framebuffer size ──
5476            "get_width"
5477            | "ความกว้าง"
5478            | "宽"
5479            | "幅取得"
5480            | "너비"
5481            | "عرض"
5482            | "العرض"
5483            | "רוחב"
5484            | "چوڑائی"
5485            | "obtenir_largeur"
5486            | "breite_abrufen"
5487            | "получить_ширину" => {
5488                return Ok(Value::Number(self.gfx.borrow().width as f64));
5489            },
5490            "get_height"
5491            | "ความสูง"
5492            | "高"
5493            | "高取得"
5494            | "높이"
5495            | "ارتفاع"
5496            | "الارتفاع"
5497            | "גובה"
5498            | "اونچائی"
5499            | "obtenir_hauteur"
5500            | "höhe_abrufen"
5501            | "получить_высоту" => {
5502                return Ok(Value::Number(self.gfx.borrow().height as f64));
5503            },
5504
5505            // ── monitor detection: physical display, not the framebuffer ──────
5506            // monitor_width() → primary-monitor pixel width
5507            "monitor_width"
5508            | "screen_width"
5509            | "屏宽"
5510            | "画面幅"
5511            | "화면너비"
5512            | "ความกว้างจอ"
5513            | "عرض_مانیتور"
5514            | "عرض_الشاشة"
5515            | "רוחב_צג"
5516            | "مانیٹر_چوڑائی"
5517            | "largeur_moniteur"
5518            | "monitor_breite"
5519            | "ширина_монитора" => {
5520                return Ok(Value::Number(monitor_info().0 as f64));
5521            },
5522            // monitor_height() → primary-monitor pixel height
5523            "monitor_height"
5524            | "screen_height"
5525            | "屏高"
5526            | "画面高"
5527            | "화면높이"
5528            | "ความสูงจอ"
5529            | "ارتفاع_مانیتور"
5530            | "ارتفاع_الشاشة"
5531            | "גובה_צג"
5532            | "مانیٹر_اونچائی"
5533            | "hauteur_moniteur"
5534            | "monitor_höhe"
5535            | "высота_монитора" => {
5536                return Ok(Value::Number(monitor_info().1 as f64));
5537            },
5538            // monitor_refresh() → refresh rate in Hz (a.k.a. the monitor framerate)
5539            "monitor_refresh"
5540            | "monitor_hz"
5541            | "monitor_fps"
5542            | "refresh_rate"
5543            | "刷新率"
5544            | "リフレッシュレート"
5545            | "주사율"
5546            | "อัตรารีเฟรช"
5547            | "نرخ_بروزرسانی_مانیتور"
5548            | "معدل_تحديث_الشاشة"
5549            | "קצב_רענון_צג"
5550            | "مانیٹر_ریفریش_ریٹ"
5551            | "fréquence_moniteur"
5552            | "monitor_bildwiederholrate"
5553            | "частота_монитора" => {
5554                return Ok(Value::Number(monitor_info().2 as f64));
5555            },
5556            // monitor_info() → [width, height, refresh_hz]
5557            "monitor_info"
5558            | "screen_info"
5559            | "屏幕信息"
5560            | "画面情報"
5561            | "화면정보"
5562            | "ข้อมูลจอ"
5563            | "اطلاعات_مانیتور"
5564            | "معلومات_الشاشة"
5565            | "מידע_צג"
5566            | "مانیٹر_معلومات"
5567            | "info_moniteur"
5568            | "bildschirminfo"
5569            | "инфо_монитора" => {
5570                let (w, h, hz) = monitor_info();
5571                return Ok(Value::List(Rc::new(vec![
5572                    Value::Number(w as f64),
5573                    Value::Number(h as f64),
5574                    Value::Number(hz as f64),
5575                ])));
5576            },
5577            // set_fps(n) → cap the render loop at n frames per second
5578            "set_fps"
5579            | "set_target_fps"
5580            | "target_fps"
5581            | "设帧率"
5582            | "フレームレート設定"
5583            | "프레임설정"
5584            | "ตั้งเฟรมเรต"
5585            | "تنظیم_نرخ_فریم"
5586            | "عيّن_معدل_الإطارات"
5587            | "קבע_קצב_פריימים"
5588            | "ایف_پی_ایس_مقرر_کرو"
5589            | "définir_fps"
5590            | "fps_setzen"
5591            | "задать_fps" => {
5592                #[cfg(not(target_arch = "wasm32"))]
5593                {
5594                    let fps = self.arg_num(&args, 0, 60.0)?.max(1.0) as usize;
5595                    let mut gfx = self.gfx.borrow_mut();
5596                    if let Some(win) = gfx.window.as_mut() {
5597                        win.set_target_fps(fps);
5598                    }
5599                }
5600                #[cfg(target_arch = "wasm32")]
5601                {
5602                    self.wasm_target_fps = self.arg_num(&args, 0, 60.0)?.max(1.0);
5603                    self.wasm_next_present_ms = 0.0;
5604                }
5605                return Ok(Value::Unit);
5606            },
5607
5608            // set_vsync(on) → pace the window to the monitor's refresh rate.
5609            // Frame-rate pacing (minifb has no swap-interval), not tear-free
5610            // vsync; `LING_FPS_CAP` and an explicit `set_fps` call still win.
5611            "set_vsync"
5612            | "vsync"
5613            | "垂直同步"
5614            | "垂直同期"
5615            | "수직동기"
5616            | "ตั้งวีซิงก์"
5617            | "تنظیم_وی‌سینک"
5618            | "عيّن_تزامن_رأسي"
5619            | "קבע_וי_סינק"
5620            | "وی_سینک_مقرر_کرو"
5621            | "définir_vsync"
5622            | "vsync_setzen"
5623            | "задать_vsync" => {
5624                let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
5625                #[cfg(not(target_arch = "wasm32"))]
5626                {
5627                    let mut gfx = self.gfx.borrow_mut();
5628                    gfx.vsync = on;
5629                    if let Some(win) = gfx.window.as_mut() {
5630                        apply_frame_pacing(win, on);
5631                    }
5632                }
5633                #[cfg(target_arch = "wasm32")]
5634                {
5635                    self.wasm_target_fps = if on { monitor_info().2 as f64 } else { 240.0 };
5636                    self.wasm_next_present_ms = 0.0;
5637                }
5638                return Ok(Value::Unit);
5639            },
5640
5641            // ── หน้าต่างเปิดอยู่() → bool — is the window still open? ──
5642            "หน้าต่างเปิดอยู่"
5643            | "window_is_open"
5644            | "gfx_is_open"
5645            | "is_open"
5646            | "窗开"
5647            | "開いている"
5648            | "창열림"
5649            | "پنجره_باز_است"
5650            | "النافذة_مفتوحة"
5651            | "החלון_פתוח"
5652            | "ونڈو_کھلی_ہے" => {
5653                #[cfg(not(target_arch = "wasm32"))]
5654                {
5655                    let gfx = self.gfx.borrow();
5656                    if gfx.want_quit {
5657                        return Ok(Value::Bool(false));
5658                    }
5659                    // Escape-to-quit needs the same GetAsyncKeyState fallback
5660                    // as key_down/key_pressed/text_poll (see those) — raw
5661                    // w.is_key_down(Escape) is WM_KEYDOWN-based and silently
5662                    // never fires if this topmost window didn't actually win
5663                    // real Win32 keyboard focus.
5664                    #[cfg(windows)]
5665                    let escape_down = if gfx.topmost_window {
5666                        window_is_foreground(gfx.hwnd) && os_key_down(0x1B) // VK_ESCAPE
5667                    } else {
5668                        gfx.window
5669                            .as_ref()
5670                            .map(|w| w.is_key_down(minifb::Key::Escape))
5671                            .unwrap_or(false)
5672                    };
5673                    #[cfg(not(windows))]
5674                    let escape_down = gfx
5675                        .window
5676                        .as_ref()
5677                        .map(|w| w.is_key_down(minifb::Key::Escape))
5678                        .unwrap_or(false);
5679                    let open =
5680                        gfx.window.as_ref().map(|w| w.is_open()).unwrap_or(false) && !escape_down;
5681                    return Ok(Value::Bool(open));
5682                }
5683                #[cfg(target_arch = "wasm32")]
5684                return Ok(Value::Bool(true));
5685            },
5686
5687            // quit() — close the window the same way Escape does, for a
5688            // script-drawn UI element (an exit button) to call.
5689            "quit" | "exit_game" | "close_window" => {
5690                #[cfg(not(target_arch = "wasm32"))]
5691                {
5692                    self.gfx.borrow_mut().want_quit = true;
5693                }
5694                return Ok(Value::Unit);
5695            },
5696
5697            // ── key_down(name) → bool — is a key held? ──
5698            "key_down"
5699            | "กดค้าง"
5700            | "按键"
5701            | "キー押す"
5702            | "키누름"
5703            | "کلید_فشرده"
5704            | "المفتاح_مضغوط"
5705            | "מקש_לחוץ"
5706            | "بٹن_دبا_ہوا"
5707            | "touche_enfoncée"
5708            | "taste_gedrückt"
5709            | "клавиша_нажата" => {
5710                #[cfg(not(target_arch = "wasm32"))]
5711                {
5712                    let name = self.arg_str(&args, 0, "");
5713                    let mut gfx = self.gfx.borrow_mut();
5714                    // The borderless-fullscreen/topmost window can be
5715                    // visually in front without ever winning real Win32
5716                    // keyboard focus (Windows' foreground-lock) — minifb's
5717                    // is_key_down is populated from WM_KEYDOWN, which then
5718                    // never arrives. GetAsyncKeyState reads the OS key-state
5719                    // table directly and doesn't need focus, so use it
5720                    // whenever this is that window (see force_window_focus).
5721                    #[cfg(windows)]
5722                    if gfx.topmost_window {
5723                        if !window_is_foreground(gfx.hwnd) {
5724                            return Ok(Value::Bool(false));
5725                        }
5726                        return Ok(Value::Bool(
5727                            str_to_vk(&name).map(os_key_down).unwrap_or(false),
5728                        ));
5729                    }
5730                    if gfx.input_suppressed() {
5731                        return Ok(Value::Bool(false));
5732                    }
5733                    let down = gfx
5734                        .window
5735                        .as_ref()
5736                        .and_then(|w| str_to_minifb_key(&name).map(|k| w.is_key_down(k)))
5737                        .unwrap_or(false);
5738                    return Ok(Value::Bool(down));
5739                }
5740                #[cfg(target_arch = "wasm32")]
5741                {
5742                    let name = self.arg_str(&args, 0, "");
5743                    return Ok(Value::Bool(crate::gfx::wasm_is_key_down(&name)));
5744                }
5745            },
5746
5747            // ── key_pressed(name) → bool — was a key pressed this frame? ──
5748            "key_pressed"
5749            | "กดปุ่ม"
5750            | "键按"
5751            | "キー押した"
5752            | "키눌림"
5753            | "فشردن_کلید"
5754            | "ضغط_المفتاح"
5755            | "לחיצת_מקש"
5756            | "بٹن_دبانا"
5757            | "touche_appuyée"
5758            | "taste_getippt"
5759            | "клавиша_нажатие" => {
5760                #[cfg(not(target_arch = "wasm32"))]
5761                {
5762                    let name = self.arg_str(&args, 0, "");
5763                    let pressed = {
5764                        let mut gfx = self.gfx.borrow_mut();
5765                        #[cfg(windows)]
5766                        let topmost = gfx.topmost_window;
5767                        #[cfg(not(windows))]
5768                        let topmost = false;
5769                        if topmost {
5770                            #[cfg(windows)]
5771                            {
5772                                if !window_is_foreground(gfx.hwnd) {
5773                                    false
5774                                } else {
5775                                    match str_to_vk(&name) {
5776                                        Some(vk) => {
5777                                            let idx = (vk as usize) & 0xFF;
5778                                            let down = os_key_down(vk);
5779                                            let was = gfx.raw_keys_prev[idx];
5780                                            gfx.raw_keys_prev[idx] = down;
5781                                            down && !was
5782                                        },
5783                                        None => false,
5784                                    }
5785                                }
5786                            }
5787                            #[cfg(not(windows))]
5788                            {
5789                                false
5790                            }
5791                        } else if gfx.input_suppressed() {
5792                            false
5793                        } else {
5794                            gfx.window
5795                                .as_ref()
5796                                .and_then(|w| {
5797                                    str_to_minifb_key(&name)
5798                                        .map(|k| w.is_key_pressed(k, minifb::KeyRepeat::No))
5799                                })
5800                                .unwrap_or(false)
5801                        }
5802                    };
5803                    // gamepad Start behaves like Enter everywhere
5804                    let pressed =
5805                        pressed || ((name == "enter" || name == "return") && gamepad::start_edge());
5806                    return Ok(Value::Bool(pressed));
5807                }
5808                #[cfg(target_arch = "wasm32")]
5809                {
5810                    let name = self.arg_str(&args, 0, "");
5811                    let pressed = crate::gfx::wasm_is_key_pressed(&name);
5812                    return Ok(Value::Bool(pressed));
5813                }
5814            },
5815
5816            // ── mouse_dx() / mouse_dy() → f64 — delta since last frame ──
5817            "mouse_dx"
5818            | "เมาส์X"
5819            | "鼠ΔX"
5820            | "マウスΔX"
5821            | "마우스ΔX"
5822            | "دلتا_ماوس_ایکس"
5823            | "فارق_الفأرة_س"
5824            | "דלתא_עכבר_X"
5825            | "ماؤس_ڈیلٹا_ایکس"
5826            | "souris_dx"
5827            | "maus_dx"
5828            | "мышь_dx" => {
5829                #[cfg(not(target_arch = "wasm32"))]
5830                return Ok(Value::Number(self.gfx.borrow().mouse_dx as f64));
5831                #[cfg(target_arch = "wasm32")]
5832                return Ok(Value::Number(crate::gfx::wasm_mouse_dx() as f64));
5833            },
5834            // ── mouse_scroll() → f64 — vertical scroll-wheel delta this frame ──
5835            #[cfg(not(target_arch = "wasm32"))]
5836            "mouse_scroll"
5837            | "ล้อเมาส์"
5838            | "滚轮"
5839            | "ホイール"
5840            | "스크롤"
5841            | "غلتک_ماوس"
5842            | "عجلة_الفأرة"
5843            | "גלגלת_עכבר"
5844            | "ماؤس_اسکرول" => {
5845                let gfx = self.gfx.borrow();
5846                let s = gfx
5847                    .window
5848                    .as_ref()
5849                    .and_then(|w| w.get_scroll_wheel())
5850                    .map(|(_, y)| y as f64)
5851                    .unwrap_or(0.0);
5852                return Ok(Value::Number(s));
5853            },
5854            #[cfg(target_arch = "wasm32")]
5855            "mouse_scroll"
5856            | "ล้อเมาส์"
5857            | "滚轮"
5858            | "ホイール"
5859            | "스크롤"
5860            | "غلتک_ماوس"
5861            | "عجلة_الفأرة"
5862            | "גלגלת_עכבר"
5863            | "ماؤس_اسکرول" => {
5864                return Ok(Value::Number(0.0));
5865            },
5866            "mouse_dy"
5867            | "เมาส์Y"
5868            | "鼠ΔY"
5869            | "マウスΔY"
5870            | "마우스ΔY"
5871            | "دلتا_ماوس_ایگرگ"
5872            | "فارق_الفأرة_ص"
5873            | "דלתא_עכבר_Y"
5874            | "ماؤس_ڈیلٹا_وائی"
5875            | "souris_dy"
5876            | "maus_dy"
5877            | "мышь_dy" => {
5878                #[cfg(not(target_arch = "wasm32"))]
5879                return Ok(Value::Number(self.gfx.borrow().mouse_dy as f64));
5880                #[cfg(target_arch = "wasm32")]
5881                return Ok(Value::Number(crate::gfx::wasm_mouse_dy() as f64));
5882            },
5883
5884            // ── Gamepad / joystick input (ling-input "Sensorium" + gilrs) ──
5885            // pad_poll() → number — advance input one frame; returns # connected pads.
5886            "pad_poll"
5887            | "手柄轮询"
5888            | "パッド更新"
5889            | "패드폴링"
5890            | "อัปเดตแพด"
5891            | "بررسی_دسته"
5892            | "استطلع_اليد"
5893            | "בדוק_בקר"
5894            | "گیم_پیڈ_پول"
5895            | "interroger_manette"
5896            | "gamepad_abfragen"
5897            | "опросить_геймпад" => {
5898                #[cfg(not(target_arch = "wasm32"))]
5899                return Ok(Value::Number(self.pad_poll() as f64));
5900                #[cfg(target_arch = "wasm32")]
5901                return Ok(Value::Number(input_web::poll() as f64));
5902            },
5903            // pad_count() → number — connected gamepads.
5904            "pad_count"
5905            | "手柄数"
5906            | "パッド数"
5907            | "패드수"
5908            | "จำนวนแพด"
5909            | "تعداد_دسته"
5910            | "عدد_أيدي_التحكم"
5911            | "מספר_בקרים"
5912            | "گیم_پیڈ_تعداد"
5913            | "nombre_manettes"
5914            | "gamepad_anzahl"
5915            | "число_геймпадов" => {
5916                #[cfg(not(target_arch = "wasm32"))]
5917                {
5918                    let inp = self.input.borrow();
5919                    let n = inp.as_ref().map_or(0, |s| s.sensorium.devices.count());
5920                    return Ok(Value::Number(n as f64));
5921                }
5922                #[cfg(target_arch = "wasm32")]
5923                return Ok(Value::Number(input_web::count() as f64));
5924            },
5925            // pad_connected(i) → bool.
5926            "pad_connected"
5927            | "手柄连接"
5928            | "パッド接続"
5929            | "패드연결"
5930            | "แพดเชื่อม"
5931            | "دسته_متصل"
5932            | "يد_التحكم_متصلة"
5933            | "בקר_מחובר"
5934            | "گیم_پیڈ_منسلک"
5935            | "manette_connectée"
5936            | "gamepad_verbunden"
5937            | "геймпад_подключён" => {
5938                #[cfg(not(target_arch = "wasm32"))]
5939                {
5940                    let i = self.arg_num(&args, 0, 0.0)? as usize;
5941                    let inp = self.input.borrow();
5942                    let c = inp
5943                        .as_ref()
5944                        .is_some_and(|s| s.sensorium.devices.for_player(i as u8).is_some());
5945                    return Ok(Value::Bool(c));
5946                }
5947                #[cfg(target_arch = "wasm32")]
5948                {
5949                    let i = self.arg_num(&args, 0, 0.0)? as usize;
5950                    return Ok(Value::Bool(input_web::is_connected(i)));
5951                }
5952            },
5953            // pad_button(i, name) → bool — is the button held?
5954            "pad_button"
5955            | "手柄按键"
5956            | "パッドボタン"
5957            | "패드버튼"
5958            | "ปุ่มแพด"
5959            | "دکمه_دسته"
5960            | "زر_اليد"
5961            | "כפתור_בקר"
5962            | "گیم_پیڈ_بٹن"
5963            | "bouton_manette"
5964            | "gamepad_taste"
5965            | "кнопка_геймпада" => {
5966                #[cfg(not(target_arch = "wasm32"))]
5967                {
5968                    let i = self.arg_num(&args, 0, 0.0)? as usize;
5969                    let name = self.arg_str(&args, 1, "");
5970                    let down = parse_pad_button(&name)
5971                        .is_some_and(|b| self.with_pad(i, false, |p| p.is_down(b)));
5972                    return Ok(Value::Bool(down));
5973                }
5974                #[cfg(target_arch = "wasm32")]
5975                {
5976                    let i = self.arg_num(&args, 0, 0.0)? as usize;
5977                    let name = self.arg_str(&args, 1, "");
5978                    return Ok(Value::Bool(input_web::button_down(i, &name)));
5979                }
5980            },
5981            // pad_pressed(i, name) → bool — pressed this frame?
5982            // On WASM we only have the current snapshot, so treat as button_down.
5983            "pad_pressed"
5984            | "手柄按下"
5985            | "パッド押下"
5986            | "패드눌림"
5987            | "แพดกด"
5988            | "دکمه_دسته_فشرده"
5989            | "زر_اليد_مضغوط"
5990            | "כפתור_בקר_לחוץ"
5991            | "گیم_پیڈ_دبایا"
5992            | "manette_appuyée"
5993            | "gamepad_gedrückt"
5994            | "геймпад_нажат" => {
5995                #[cfg(not(target_arch = "wasm32"))]
5996                {
5997                    let i = self.arg_num(&args, 0, 0.0)? as usize;
5998                    let name = self.arg_str(&args, 1, "");
5999                    let p = parse_pad_button(&name)
6000                        .is_some_and(|b| self.with_pad(i, false, |g| g.just_pressed(b)));
6001                    return Ok(Value::Bool(p));
6002                }
6003                #[cfg(target_arch = "wasm32")]
6004                {
6005                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6006                    let name = self.arg_str(&args, 1, "");
6007                    return Ok(Value::Bool(input_web::button_down(i, &name)));
6008                }
6009            },
6010            // pad_lx(i)/pad_ly(i)/pad_rx(i)/pad_ry(i) → number — stick axes (−1..=1).
6011            "pad_lx"
6012            | "手柄左X"
6013            | "パッド左X"
6014            | "패드왼X"
6015            | "แพดซ้ายX"
6016            | "آنالوگ_چپ_ایکس"
6017            | "عصا_اليسرى_س"
6018            | "ג'ויסטיק_שמאל_X"
6019            | "بائیں_اسٹک_ایکس"
6020            | "manette_axe_gauche_x"
6021            | "gamepad_lx"
6022            | "геймпад_ось_лево_x" => {
6023                #[cfg(not(target_arch = "wasm32"))]
6024                {
6025                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6026                    return Ok(Value::Number(
6027                        self.with_pad(i, 0.0, |p| p.left_stick.x as f64),
6028                    ));
6029                }
6030                #[cfg(target_arch = "wasm32")]
6031                {
6032                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6033                    return Ok(Value::Number(input_web::axis_lx(i) as f64));
6034                }
6035            },
6036            "pad_ly"
6037            | "手柄左Y"
6038            | "パッド左Y"
6039            | "패드왼Y"
6040            | "แพดซ้ายY"
6041            | "آنالوگ_چپ_ایگرگ"
6042            | "عصا_اليسرى_ص"
6043            | "ג'ויסטיק_שמאל_Y"
6044            | "بائیں_اسٹک_وائی"
6045            | "manette_axe_gauche_y"
6046            | "gamepad_ly"
6047            | "геймпад_ось_лево_y" => {
6048                #[cfg(not(target_arch = "wasm32"))]
6049                {
6050                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6051                    return Ok(Value::Number(
6052                        self.with_pad(i, 0.0, |p| p.left_stick.y as f64),
6053                    ));
6054                }
6055                #[cfg(target_arch = "wasm32")]
6056                {
6057                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6058                    return Ok(Value::Number(input_web::axis_ly(i) as f64));
6059                }
6060            },
6061            "pad_rx"
6062            | "手柄右X"
6063            | "パッド右X"
6064            | "패드오X"
6065            | "แพดขวาX"
6066            | "آنالوگ_راست_ایکس"
6067            | "عصا_اليمنى_س"
6068            | "ג'ויסטיק_ימין_X"
6069            | "دائیں_اسٹک_ایکس"
6070            | "manette_axe_droit_x"
6071            | "gamepad_rx"
6072            | "геймпад_ось_право_x" => {
6073                #[cfg(not(target_arch = "wasm32"))]
6074                {
6075                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6076                    return Ok(Value::Number(
6077                        self.with_pad(i, 0.0, |p| p.right_stick.x as f64),
6078                    ));
6079                }
6080                #[cfg(target_arch = "wasm32")]
6081                {
6082                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6083                    return Ok(Value::Number(input_web::axis_rx(i) as f64));
6084                }
6085            },
6086            "pad_ry"
6087            | "手柄右Y"
6088            | "パッド右Y"
6089            | "패드오Y"
6090            | "แพดขวาY"
6091            | "آنالوگ_راست_ایگرگ"
6092            | "عصا_اليمنى_ص"
6093            | "ג'ויסטיק_ימין_Y"
6094            | "دائیں_اسٹک_وائی"
6095            | "manette_axe_droit_y"
6096            | "gamepad_ry"
6097            | "геймпад_ось_право_y" => {
6098                #[cfg(not(target_arch = "wasm32"))]
6099                {
6100                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6101                    return Ok(Value::Number(
6102                        self.with_pad(i, 0.0, |p| p.right_stick.y as f64),
6103                    ));
6104                }
6105                #[cfg(target_arch = "wasm32")]
6106                {
6107                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6108                    return Ok(Value::Number(input_web::axis_ry(i) as f64));
6109                }
6110            },
6111            // pad_lt(i)/pad_rt(i) → number — analog triggers (0..=1).
6112            "pad_lt"
6113            | "手柄左扳机"
6114            | "パッド左トリガー"
6115            | "패드왼트리거"
6116            | "ไกแพดซ้าย"
6117            | "ماشه_چپ"
6118            | "زناد_اليسار"
6119            | "הדק_שמאל"
6120            | "بائیں_ٹریگر"
6121            | "manette_gâchette_gauche"
6122            | "gamepad_lt"
6123            | "геймпад_триггер_лево" => {
6124                #[cfg(not(target_arch = "wasm32"))]
6125                {
6126                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6127                    return Ok(Value::Number(
6128                        self.with_pad(i, 0.0, |p| p.left_trigger as f64),
6129                    ));
6130                }
6131                #[cfg(target_arch = "wasm32")]
6132                {
6133                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6134                    return Ok(Value::Number(input_web::trigger_lt(i) as f64));
6135                }
6136            },
6137            "pad_rt"
6138            | "手柄右扳机"
6139            | "パッド右トリガー"
6140            | "패드오트리거"
6141            | "ไกแพดขวา"
6142            | "ماشه_راست"
6143            | "زناد_اليمين"
6144            | "הדק_ימין"
6145            | "دائیں_ٹریگر"
6146            | "manette_gâchette_droite"
6147            | "gamepad_rt"
6148            | "геймпад_триггер_право" => {
6149                #[cfg(not(target_arch = "wasm32"))]
6150                {
6151                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6152                    return Ok(Value::Number(
6153                        self.with_pad(i, 0.0, |p| p.right_trigger as f64),
6154                    ));
6155                }
6156                #[cfg(target_arch = "wasm32")]
6157                {
6158                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6159                    return Ok(Value::Number(input_web::trigger_rt(i) as f64));
6160                }
6161            },
6162            // pad_rumble(i, lo, hi) → unit — set rumble motor amplitudes (0..=1).
6163            "pad_rumble"
6164            | "手柄震动"
6165            | "パッド振動"
6166            | "패드진동"
6167            | "แพดสั่น"
6168            | "لرزش_دسته"
6169            | "اهتزاز_اليد"
6170            | "רטט_בקר"
6171            | "گیم_پیڈ_تھرتھراہٹ"
6172            | "vibration_manette"
6173            | "gamepad_vibration"
6174            | "вибрация_геймпада" => {
6175                #[cfg(not(target_arch = "wasm32"))]
6176                {
6177                    use ling_input::backend::InputBackend;
6178                    let i = self.arg_num(&args, 0, 0.0)? as usize;
6179                    let lo = self.arg_num(&args, 1, 0.0)? as f32;
6180                    let hi = self.arg_num(&args, 2, lo as f64)? as f32;
6181                    let mut inp = self.input.borrow_mut();
6182                    if let Some(s) = inp.as_mut() {
6183                        if let Some(dev) = s.sensorium.devices.for_player(i as u8).map(|d| d.id) {
6184                            s.backend.set_rumble(
6185                                dev,
6186                                ling_input::Rumble { low: lo, high: hi, ..Default::default() },
6187                            );
6188                        }
6189                    }
6190                    return Ok(Value::Unit);
6191                }
6192                #[cfg(target_arch = "wasm32")]
6193                return Ok(Value::Unit);
6194            },
6195
6196            // ── set_camera_pos(x, y, z) — move camera to world position ──
6197            "set_camera_pos"
6198            | "ตั้งตำแหน่งกล้อง"
6199            | "镜坐标"
6200            | "カメラ座標"
6201            | "카메라좌표"
6202            | "تنظیم_موقعیت_دوربین"
6203            | "عيّن_موضع_الكاميرا"
6204            | "קבע_מיקום_מצלמה"
6205            | "کیمرہ_مقام_مقرر_کرو"
6206            | "définir_position_caméra"
6207            | "kameraposition_setzen"
6208            | "задать_позицию_камеры" => {
6209                let x = self.arg_num(&args, 0, 0.0)? as f32;
6210                let y = self.arg_num(&args, 1, 0.0)? as f32;
6211                let z = self.arg_num(&args, 2, 0.0)? as f32;
6212                {
6213                    let mut gfx = self.gfx.borrow_mut();
6214                    gfx.camera.tx = x;
6215                    gfx.camera.ty = y;
6216                    gfx.camera.tz = z;
6217                }
6218                #[cfg(not(target_arch = "wasm32"))]
6219                if let Some(audio) = &self.audio {
6220                    audio.set_listener_pos(x, y, z);
6221                }
6222                return Ok(Value::Unit);
6223            },
6224
6225            // ── move_camera(dx, dy, dz) — translate camera by delta ──
6226            "move_camera" => {
6227                let dx = self.arg_num(&args, 0, 0.0)? as f32;
6228                let dy = self.arg_num(&args, 1, 0.0)? as f32;
6229                let dz = self.arg_num(&args, 2, 0.0)? as f32;
6230                let mut gfx = self.gfx.borrow_mut();
6231                gfx.camera.tx += dx;
6232                gfx.camera.ty += dy;
6233                gfx.camera.tz += dz;
6234                return Ok(Value::Unit);
6235            },
6236
6237            // ── set_zdist(d) — set perspective z-offset (field-of-view taper) ──
6238            "set_zdist"
6239            | "ตั้งระยะห่าง"
6240            | "镜距"
6241            | "Z距離設定"
6242            | "Z거리설정"
6243            | "تنظیم_فاصله_عمق"
6244            | "عيّن_مسافة_العمق"
6245            | "קבע_מרחק_עומק"
6246            | "گہرائی_فاصلہ_مقرر_کرو"
6247            | "définir_distance_z"
6248            | "z_abstand_setzen"
6249            | "задать_дистанцию_z" => {
6250                let d = self.arg_num(&args, 0, 5.0)? as f32;
6251                self.gfx.borrow_mut().camera.zdist = d;
6252                return Ok(Value::Unit);
6253            },
6254
6255            // ── capture_mouse() — hide cursor and warp to centre each frame ──
6256            "capture_mouse"
6257            | "จับเมาส์"
6258            | "捕鼠"
6259            | "マウス捕捉"
6260            | "마우스잡기"
6261            | "ضبط_ماوس"
6262            | "امسك_الفأرة"
6263            | "לכוד_עכבר"
6264            | "ماؤس_پکڑو"
6265            | "capturer_souris"
6266            | "maus_erfassen"
6267            | "захватить_мышь" => {
6268                #[cfg(not(target_arch = "wasm32"))]
6269                {
6270                    let mut gfx = self.gfx.borrow_mut();
6271                    gfx.mouse_captured = true;
6272                    gfx.last_mx = f32::NAN;
6273                    if let Some(win) = gfx.window.as_mut() {
6274                        win.set_cursor_visibility(false);
6275                    }
6276                }
6277                return Ok(Value::Unit);
6278            },
6279
6280            // ── release_mouse() — restore cursor and remove clip region ──
6281            "release_mouse" => {
6282                #[cfg(not(target_arch = "wasm32"))]
6283                {
6284                    let mut gfx = self.gfx.borrow_mut();
6285                    gfx.mouse_captured = false;
6286                    gfx.last_mx = f32::NAN;
6287                    if let Some(win) = gfx.window.as_mut() {
6288                        win.set_cursor_visibility(true);
6289                    }
6290                    #[cfg(windows)]
6291                    unsafe {
6292                        // Null releases the clip; reuse the RECT-typed declaration above.
6293                        extern "system" {
6294                            fn ClipCursor(lpRect: *const std::ffi::c_void) -> i32;
6295                        }
6296                        ClipCursor(std::ptr::null());
6297                    }
6298                }
6299                return Ok(Value::Unit);
6300            },
6301
6302            // ── cursor_hide() / cursor_show() — just the OS cursor's visibility,
6303            // no warp-to-centre or clip region (unlike capture_mouse/release_mouse,
6304            // which are for FPS-style look-around). For point-and-click play where
6305            // the cursor still needs to move freely and mouse_x()/mouse_y() still
6306            // need to track real position, just hide the system pointer glyph.
6307            "cursor_hide" => {
6308                #[cfg(not(target_arch = "wasm32"))]
6309                if let Some(win) = self.gfx.borrow_mut().window.as_mut() {
6310                    win.set_cursor_visibility(false);
6311                }
6312                return Ok(Value::Unit);
6313            },
6314            "cursor_show" => {
6315                #[cfg(not(target_arch = "wasm32"))]
6316                if let Some(win) = self.gfx.borrow_mut().window.as_mut() {
6317                    win.set_cursor_visibility(true);
6318                }
6319                return Ok(Value::Unit);
6320            },
6321
6322            // ══════════════════════════════════════════════════════════════════
6323            // 3-D / 4-D DRAWING — camera, lights, depth-sorted geometry
6324            // ══════════════════════════════════════════════════════════════════
6325
6326            // ── set_camera(cry, sry, crx, srx) — store precomputed camera trig ──
6327            // Call once per frame after computing cos/sin of your rotation angles.
6328            "set_camera"
6329            | "ตั้งกล้อง"
6330            | "设镜"
6331            | "设置摄像机"
6332            | "カメラ設定"
6333            | "카메라설정"
6334            | "تنظیم_دوربین"
6335            | "عيّن_الكاميرا"
6336            | "קבע_מצלמה"
6337            | "کیمرہ_مقرر_کرو"
6338            | "définir_caméra"
6339            | "kamera_setzen"
6340            | "задать_камеру" => {
6341                let cry = self.arg_num(&args, 0, 1.0)? as f32;
6342                let sry = self.arg_num(&args, 1, 0.0)? as f32;
6343                let crx = self.arg_num(&args, 2, 1.0)? as f32;
6344                let srx = self.arg_num(&args, 3, 0.0)? as f32;
6345                let mut gfx = self.gfx.borrow_mut();
6346                gfx.camera.cry = cry;
6347                gfx.camera.sry = sry;
6348                gfx.camera.crx = crx;
6349                gfx.camera.srx = srx;
6350                return Ok(Value::Unit);
6351            },
6352
6353            // ── set_projection(cx, cy, focal, zdist) — override projection params ──
6354            // Automatically set when the window opens; override only if needed.
6355            "set_projection"
6356            | "ตั้งโปรเจกชัน"
6357            | "投影"
6358            | "投影設定"
6359            | "투영설정"
6360            | "تنظیم_فرافکنی"
6361            | "عيّن_الإسقاط"
6362            | "קבע_הטלה"
6363            | "پروجیکشن_مقرر_کرو"
6364            | "définir_projection"
6365            | "projektion_setzen"
6366            | "задать_проекцию" => {
6367                let cx = self.arg_num(&args, 0, 960.0)? as f32;
6368                let cy = self.arg_num(&args, 1, 540.0)? as f32;
6369                let focal = self.arg_num(&args, 2, 1080.0)? as f32;
6370                let zdist = self.arg_num(&args, 3, 5.0)? as f32;
6371                let mut gfx = self.gfx.borrow_mut();
6372                gfx.camera.cx = cx;
6373                gfx.camera.cy = cy;
6374                gfx.camera.focal = focal;
6375                gfx.camera.zdist = zdist;
6376                return Ok(Value::Unit);
6377            },
6378
6379            // ── mesh_load(path) → handle · loads a glb/gltf (skeleton + skin + animation) ──
6380            "gltf_load" => {
6381                let path = self.arg_str(&args, 0, "");
6382                match ling_physics::gltf::GltfModel::load(&path) {
6383                    Ok(m) => {
6384                        self.gltf_models.borrow_mut().push(m);
6385                        let h = self.gltf_models.borrow().len() - 1;
6386                        return Ok(Value::Number(h as f64));
6387                    },
6388                    Err(e) => {
6389                        eprintln!("mesh_load failed ({path}): {e}");
6390                        return Ok(Value::Number(-1.0));
6391                    },
6392                }
6393            },
6394            // mesh_anim_count(handle) → number of animation clips
6395            "gltf_anim_count" => {
6396                let h = self.arg_num(&args, 0, -1.0)? as i64;
6397                let n = self
6398                    .gltf_models
6399                    .borrow()
6400                    .get(h as usize)
6401                    .map(|m| m.animations.len())
6402                    .unwrap_or(0);
6403                return Ok(Value::Number(n as f64));
6404            },
6405            // mesh_anim_name(handle, i) → clip name
6406            "gltf_anim_name" => {
6407                let h = self.arg_num(&args, 0, -1.0)? as i64;
6408                let i = self.arg_num(&args, 1, 0.0)? as usize;
6409                let s = self
6410                    .gltf_models
6411                    .borrow()
6412                    .get(h as usize)
6413                    .and_then(|m| m.animations.get(i))
6414                    .map(|a| a.name.clone())
6415                    .unwrap_or_default();
6416                return Ok(Value::Str(s));
6417            },
6418            // mesh_anim_dur(handle, i) → clip duration (seconds)
6419            "gltf_anim_dur" => {
6420                let h = self.arg_num(&args, 0, -1.0)? as i64;
6421                let i = self.arg_num(&args, 1, 0.0)? as usize;
6422                let d = self
6423                    .gltf_models
6424                    .borrow()
6425                    .get(h as usize)
6426                    .and_then(|m| m.animations.get(i))
6427                    .map(|a| a.duration)
6428                    .unwrap_or(0.0);
6429                return Ok(Value::Number(d as f64));
6430            },
6431            // mesh_tris(handle) → total triangle count (perf sanity check)
6432            "gltf_tris" => {
6433                let h = self.arg_num(&args, 0, -1.0)? as i64;
6434                let n: usize = self
6435                    .gltf_models
6436                    .borrow()
6437                    .get(h as usize)
6438                    .map(|m| m.meshes.iter().map(|mm| mm.indices.len()).sum::<usize>() / 3)
6439                    .unwrap_or(0);
6440                return Ok(Value::Number(n as f64));
6441            },
6442
6443            // gltf_joint_count(handle) → number of skin joints (bones)
6444            "gltf_joint_count" => {
6445                let h = self.arg_num(&args, 0, -1.0)? as i64;
6446                let n = self
6447                    .gltf_models
6448                    .borrow()
6449                    .get(h as usize)
6450                    .and_then(|m| m.skins.first())
6451                    .map(|s| s.joints.len())
6452                    .unwrap_or(0);
6453                return Ok(Value::Number(n as f64));
6454            },
6455            // gltf_joint_name(handle, j) → bone name (its node's name)
6456            "gltf_joint_name" => {
6457                let h = self.arg_num(&args, 0, -1.0)? as i64;
6458                let j = self.arg_num(&args, 1, 0.0)? as usize;
6459                let models = self.gltf_models.borrow();
6460                let s = models
6461                    .get(h as usize)
6462                    .and_then(|m| {
6463                        m.skins
6464                            .first()
6465                            .and_then(|sk| sk.joints.get(j))
6466                            .and_then(|jt| m.nodes.get(jt.node_idx))
6467                            .map(|n| n.name.clone())
6468                    })
6469                    .unwrap_or_default();
6470                return Ok(Value::Str(s));
6471            },
6472
6473            // ── gltf_draw(handle, ox,oy,oz, scale, yaw) — filled render of a loaded model ──
6474            //   glTF is Y-up / -Z-forward; the engine is Y-down, so we flip Y and Z, then
6475            //   yaw about Y, scale, translate. Per-part colour by mesh name. Lit + depth-queued
6476            //   exactly like draw_mesh, so it shares the camera + z-buffer.
6477            "gltf_draw" => {
6478                let hh = self.arg_num(&args, 0, -1.0)? as i64;
6479                let ox = self.arg_num(&args, 1, 0.0)? as f32;
6480                let oy = self.arg_num(&args, 2, 0.0)? as f32;
6481                let oz = self.arg_num(&args, 3, 0.0)? as f32;
6482                let scale = self.arg_num(&args, 4, 1.0)? as f32;
6483                let yaw = self.arg_num(&args, 5, 0.0)? as f32;
6484                let (sy, cyy) = yaw.sin_cos();
6485                let models = self.gltf_models.borrow();
6486                let model = match models.get(hh as usize) {
6487                    Some(m) => m,
6488                    None => return Ok(Value::Unit),
6489                };
6490                let mut gfx = self.gfx.borrow_mut();
6491                let cp = {
6492                    let c = &gfx.camera;
6493                    ling_gpu::CameraParams {
6494                        cry: c.cry,
6495                        sry: c.sry,
6496                        crx: c.crx,
6497                        srx: c.srx,
6498                        cx: c.cx,
6499                        cy: c.cy,
6500                        focal: c.focal,
6501                        zdist: c.zdist,
6502                        tx: c.tx,
6503                        ty: c.ty,
6504                        tz: c.tz,
6505                    }
6506                };
6507                let near = -gfx.camera.zdist + 0.02;
6508                let ambient = gfx.ambient;
6509                for mesh in &model.meshes {
6510                    let nlow = mesh.name.to_lowercase();
6511                    let base: u32 = if nlow.contains("hair") {
6512                        0x7a4a28
6513                    } else if nlow.contains("cloth") || nlow.contains("top") {
6514                        0x4a86e0
6515                    } else if nlow.contains("wing") {
6516                        0xe6ecf5
6517                    } else if nlow.contains("star") {
6518                        0xffd24d
6519                    } else {
6520                        0xf2d6b8
6521                    };
6522                    let nv = mesh.verts.len();
6523                    if nv == 0 {
6524                        continue;
6525                    }
6526                    let mut world = vec![0.0f32; nv * 3];
6527                    for (i, v) in mesh.verts.iter().enumerate() {
6528                        let gx = v.pos.x * scale;
6529                        let gy = -v.pos.y * scale;
6530                        let gz = -v.pos.z * scale;
6531                        let rx = gx * cyy + gz * sy;
6532                        let rz = -gx * sy + gz * cyy;
6533                        world[i * 3] = ox + rx;
6534                        world[i * 3 + 1] = oy + gy;
6535                        world[i * 3 + 2] = oz + rz;
6536                    }
6537                    let mut proj = vec![0.0f32; nv * 3];
6538                    ling_gpu::backend().project_points(&world, &cp, &mut proj);
6539                    let idx = &mesh.indices;
6540                    let nt = idx.len() / 3;
6541                    for t in 0..nt {
6542                        let ia = idx[t * 3] as usize;
6543                        let ib = idx[t * 3 + 1] as usize;
6544                        let ic = idx[t * 3 + 2] as usize;
6545                        if ia >= nv || ib >= nv || ic >= nv {
6546                            continue;
6547                        }
6548                        let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
6549                        if (da + db + dc) / 3.0 <= near {
6550                            continue;
6551                        }
6552                        let col = {
6553                            let (ax, ay, az) =
6554                                (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
6555                            let (bx, by, bz) =
6556                                (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
6557                            let (px, py, pz) =
6558                                (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
6559                            let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
6560                            let (vx, vy, vz) = (px - ax, py - ay, pz - az);
6561                            let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
6562                            let centroid = [
6563                                (ax + bx + px) / 3.0,
6564                                (ay + by + py) / 3.0,
6565                                (az + bz + pz) / 3.0,
6566                            ];
6567                            if gfx.flat_shade {
6568                                base
6569                            } else {
6570                                crate::gfx::light::compute_lit_color(
6571                                    base,
6572                                    normal,
6573                                    centroid,
6574                                    &gfx.lights,
6575                                    ambient,
6576                                )
6577                            }
6578                        };
6579                        let depth = (da + db + dc) / 3.0;
6580                        let col = gfx.fog_apply(col, depth);
6581                        gfx.depth_queue.push_triangle_zv(
6582                            col,
6583                            proj[ia * 3],
6584                            proj[ia * 3 + 1],
6585                            da,
6586                            proj[ib * 3],
6587                            proj[ib * 3 + 1],
6588                            db,
6589                            proj[ic * 3],
6590                            proj[ic * 3 + 1],
6591                            dc,
6592                        );
6593                    }
6594                }
6595                return Ok(Value::Unit);
6596            },
6597
6598            // ── gltf_autorig(handle) → synthesize a humanoid skeleton + skin weights ──
6599            "gltf_autorig" => {
6600                let hh = self.arg_num(&args, 0, -1.0)? as i64;
6601                let mut models = self.gltf_models.borrow_mut();
6602                if let Some(m) = models.get_mut(hh as usize) {
6603                    return Ok(Value::Number(m.autorig() as f64));
6604                }
6605                return Ok(Value::Number(0.0));
6606            },
6607
6608            // ── gltf_pose_draw(handle, ox,oy,oz, scale, yaw, poseList) ──
6609            //   Like gltf_draw, but linear-blend-skins the mesh by `poseList` first.
6610            //   poseList = flat XYZ-euler radians, 3 per bone (12 bones → 36 values).
6611            "gltf_pose_draw" => {
6612                let hh = self.arg_num(&args, 0, -1.0)? as i64;
6613                let ox = self.arg_num(&args, 1, 0.0)? as f32;
6614                let oy = self.arg_num(&args, 2, 0.0)? as f32;
6615                let oz = self.arg_num(&args, 3, 0.0)? as f32;
6616                let scale = self.arg_num(&args, 4, 1.0)? as f32;
6617                let yaw = self.arg_num(&args, 5, 0.0)? as f32;
6618                let euler: Vec<f32> = match args.get(6) {
6619                    Some(Value::List(v)) => v
6620                        .iter()
6621                        .map(|x| self.to_number(x).unwrap_or(0.0) as f32)
6622                        .collect(),
6623                    _ => Vec::new(),
6624                };
6625                let (sy, cyy) = yaw.sin_cos();
6626                let models = self.gltf_models.borrow();
6627                let model = match models.get(hh as usize) {
6628                    Some(m) => m,
6629                    None => return Ok(Value::Unit),
6630                };
6631                let skinned = model.skin_local(&euler);
6632                let mut gfx = self.gfx.borrow_mut();
6633                let cp = {
6634                    let c = &gfx.camera;
6635                    ling_gpu::CameraParams {
6636                        cry: c.cry,
6637                        sry: c.sry,
6638                        crx: c.crx,
6639                        srx: c.srx,
6640                        cx: c.cx,
6641                        cy: c.cy,
6642                        focal: c.focal,
6643                        zdist: c.zdist,
6644                        tx: c.tx,
6645                        ty: c.ty,
6646                        tz: c.tz,
6647                    }
6648                };
6649                let near = -gfx.camera.zdist + 0.02;
6650                let ambient = gfx.ambient;
6651                for (mi, mesh) in model.meshes.iter().enumerate() {
6652                    let nlow = mesh.name.to_lowercase();
6653                    let base: u32 = if nlow.contains("hair") {
6654                        0x7a4a28
6655                    } else if nlow.contains("cloth") || nlow.contains("top") {
6656                        0x4a86e0
6657                    } else if nlow.contains("wing") {
6658                        0xe6ecf5
6659                    } else if nlow.contains("star") {
6660                        0xffd24d
6661                    } else {
6662                        0xf2d6b8
6663                    };
6664                    let sk = match skinned.get(mi) {
6665                        Some(s) => s,
6666                        None => continue,
6667                    };
6668                    let nv = sk.len();
6669                    if nv == 0 {
6670                        continue;
6671                    }
6672                    let mut world = vec![0.0f32; nv * 3];
6673                    for i in 0..nv {
6674                        let gx = sk[i][0] * scale;
6675                        let gy = -sk[i][1] * scale;
6676                        let gz = -sk[i][2] * scale;
6677                        let rx = gx * cyy + gz * sy;
6678                        let rz = -gx * sy + gz * cyy;
6679                        world[i * 3] = ox + rx;
6680                        world[i * 3 + 1] = oy + gy;
6681                        world[i * 3 + 2] = oz + rz;
6682                    }
6683                    let mut proj = vec![0.0f32; nv * 3];
6684                    ling_gpu::backend().project_points(&world, &cp, &mut proj);
6685                    let idx = &mesh.indices;
6686                    let nt = idx.len() / 3;
6687                    for t in 0..nt {
6688                        let ia = idx[t * 3] as usize;
6689                        let ib = idx[t * 3 + 1] as usize;
6690                        let ic = idx[t * 3 + 2] as usize;
6691                        if ia >= nv || ib >= nv || ic >= nv {
6692                            continue;
6693                        }
6694                        let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
6695                        if (da + db + dc) / 3.0 <= near {
6696                            continue;
6697                        }
6698                        let col = {
6699                            let (ax, ay, az) =
6700                                (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
6701                            let (bx, by, bz) =
6702                                (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
6703                            let (px, py, pz) =
6704                                (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
6705                            let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
6706                            let (vx, vy, vz) = (px - ax, py - ay, pz - az);
6707                            let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
6708                            let centroid = [
6709                                (ax + bx + px) / 3.0,
6710                                (ay + by + py) / 3.0,
6711                                (az + bz + pz) / 3.0,
6712                            ];
6713                            if gfx.flat_shade {
6714                                base
6715                            } else {
6716                                crate::gfx::light::compute_lit_color(
6717                                    base,
6718                                    normal,
6719                                    centroid,
6720                                    &gfx.lights,
6721                                    ambient,
6722                                )
6723                            }
6724                        };
6725                        let depth = (da + db + dc) / 3.0;
6726                        let col = gfx.fog_apply(col, depth);
6727                        gfx.depth_queue.push_triangle_zv(
6728                            col,
6729                            proj[ia * 3],
6730                            proj[ia * 3 + 1],
6731                            da,
6732                            proj[ib * 3],
6733                            proj[ib * 3 + 1],
6734                            db,
6735                            proj[ic * 3],
6736                            proj[ic * 3 + 1],
6737                            dc,
6738                        );
6739                    }
6740                }
6741                return Ok(Value::Unit);
6742            },
6743
6744            // ── draw_mesh(pos, idx, ox, oy, oz, scale, mode) ──
6745            //   Native batched triangle mesh. pos = flat [x,y,z,…], idx = flat tri indices.
6746            //   mode 0 = lit with current pen colour; 1 = per-face hue cycle.
6747            //   Vertices are batch-projected via ling-gpu (CPU fallback, or CUDA when the
6748            //   `cuda` feature is on); the per-triangle loop runs natively (not in the
6749            //   interpreter) so dense meshes (imported glTF, grids) stay fast.
6750            "draw_mesh"
6751            | "วาดเมช"
6752            | "رسم_مش"
6753            | "ارسم_شبكة"
6754            | "צייר_רשת"
6755            | "میش_کھینچو"
6756            | "dessiner_maillage"
6757            | "netz_zeichnen"
6758            | "рисовать_меш" => {
6759                let pos = match args.first() {
6760                    Some(Value::List(v)) => v,
6761                    _ => return Ok(Value::Unit),
6762                };
6763                let idx = match args.get(1) {
6764                    Some(Value::List(v)) => v,
6765                    _ => return Ok(Value::Unit),
6766                };
6767                let ox = self.arg_num(&args, 2, 0.0)? as f32;
6768                let oy = self.arg_num(&args, 3, 0.0)? as f32;
6769                let oz = self.arg_num(&args, 4, 0.0)? as f32;
6770                let scale = self.arg_num(&args, 5, 1.0)? as f32;
6771                let mode = self.arg_num(&args, 6, 0.0)? as i64;
6772                let nv = pos.len() / 3;
6773                if nv == 0 {
6774                    return Ok(Value::Unit);
6775                }
6776                let mut world = vec![0.0f32; nv * 3];
6777                for i in 0..nv {
6778                    world[i * 3] = ox + self.to_number(&pos[i * 3]).unwrap_or(0.0) as f32 * scale;
6779                    world[i * 3 + 1] =
6780                        oy + self.to_number(&pos[i * 3 + 1]).unwrap_or(0.0) as f32 * scale;
6781                    world[i * 3 + 2] =
6782                        oz + self.to_number(&pos[i * 3 + 2]).unwrap_or(0.0) as f32 * scale;
6783                }
6784                let mut gfx = self.gfx.borrow_mut();
6785                let cp = {
6786                    let c = &gfx.camera;
6787                    ling_gpu::CameraParams {
6788                        cry: c.cry,
6789                        sry: c.sry,
6790                        crx: c.crx,
6791                        srx: c.srx,
6792                        cx: c.cx,
6793                        cy: c.cy,
6794                        focal: c.focal,
6795                        zdist: c.zdist,
6796                        tx: c.tx,
6797                        ty: c.ty,
6798                        tz: c.tz,
6799                    }
6800                };
6801                let near = -gfx.camera.zdist + 0.02;
6802                let base = gfx.color;
6803                let ambient = gfx.ambient;
6804                let mut proj = vec![0.0f32; nv * 3]; // (sx, sy, depth) per vertex
6805                ling_gpu::backend().project_points(&world, &cp, &mut proj);
6806                let nt = idx.len() / 3;
6807                for t in 0..nt {
6808                    let ia = self.to_number(&idx[t * 3]).unwrap_or(0.0) as usize;
6809                    let ib = self.to_number(&idx[t * 3 + 1]).unwrap_or(0.0) as usize;
6810                    let ic = self.to_number(&idx[t * 3 + 2]).unwrap_or(0.0) as usize;
6811                    if ia >= nv || ib >= nv || ic >= nv {
6812                        continue;
6813                    }
6814                    let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
6815                    if (da + db + dc) / 3.0 <= near {
6816                        continue;
6817                    } // near-plane cull (centroid)
6818                    let col = if mode == 1 {
6819                        let h = t as f32 * 0.6;
6820                        let r = ((h.sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
6821                        let g = (((h + 2.094).sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
6822                        let b = (((h + 4.189).sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
6823                        (r << 16) | (g << 8) | b
6824                    } else {
6825                        let (ax, ay, az) = (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
6826                        let (bx, by, bz) = (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
6827                        let (px, py, pz) = (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
6828                        let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
6829                        let (vx, vy, vz) = (px - ax, py - ay, pz - az);
6830                        let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
6831                        let centroid = [
6832                            (ax + bx + px) / 3.0,
6833                            (ay + by + py) / 3.0,
6834                            (az + bz + pz) / 3.0,
6835                        ];
6836                        if gfx.flat_shade {
6837                            base
6838                        } else {
6839                            crate::gfx::light::compute_lit_color(
6840                                base,
6841                                normal,
6842                                centroid,
6843                                &gfx.lights,
6844                                ambient,
6845                            )
6846                        }
6847                    };
6848                    let depth = (da + db + dc) / 3.0;
6849                    let col = gfx.fog_apply(col, depth);
6850                    // True per-vertex depth so the z-buffer resolves mesh
6851                    // self-occlusion (when depth_test is off, the flush ignores
6852                    // z and uses the screen x/y exactly as before).
6853                    gfx.depth_queue.push_triangle_zv(
6854                        col,
6855                        proj[ia * 3],
6856                        proj[ia * 3 + 1],
6857                        da,
6858                        proj[ib * 3],
6859                        proj[ib * 3 + 1],
6860                        db,
6861                        proj[ic * 3],
6862                        proj[ic * 3 + 1],
6863                        dc,
6864                    );
6865                }
6866                return Ok(Value::Unit);
6867            },
6868
6869            // ── add_light(x, y, z, r, g, b, intensity, radius) ──
6870            // Adds a point light in world space.  r/g/b in [0..1].
6871            // radius == 0 → no distance falloff.
6872            "add_light"
6873            | "เพิ่มแสง"
6874            | "加灯"
6875            | "ライト追加"
6876            | "조명추가"
6877            | "افزودن_نور"
6878            | "أضف_ضوء"
6879            | "הוסף_אור"
6880            | "روشنی_شامل_کرو"
6881            | "ajouter_lumière"
6882            | "licht_hinzufügen"
6883            | "добавить_свет" => {
6884                let x = self.arg_num(&args, 0, 0.0)? as f32;
6885                let y = self.arg_num(&args, 1, -3.0)? as f32;
6886                let z = self.arg_num(&args, 2, 3.0)? as f32;
6887                let mut r = self.arg_num(&args, 3, 1.0)? as f32;
6888                let mut g = self.arg_num(&args, 4, 1.0)? as f32;
6889                let mut b = self.arg_num(&args, 5, 1.0)? as f32;
6890                // Forgive 0-255 colour values: if any channel is clearly > 1,
6891                // treat the triple as 0-255 and normalise. Keeps 0-1 callers exact.
6892                if r > 1.5 || g > 1.5 || b > 1.5 {
6893                    r /= 255.0;
6894                    g /= 255.0;
6895                    b /= 255.0;
6896                }
6897                let intensity = self.arg_num(&args, 6, 1.0)? as f32;
6898                let radius = self.arg_num(&args, 7, 0.0)? as f32;
6899                self.gfx
6900                    .borrow_mut()
6901                    .lights
6902                    .push(Light { x, y, z, r, g, b, intensity, radius });
6903                return Ok(Value::Unit);
6904            },
6905
6906            // ── clear_lights() — remove all lights ──
6907            "clear_lights"
6908            | "ล้างแสง"
6909            | "清灯"
6910            | "ライト消去"
6911            | "조명초기화"
6912            | "پاک‌کردن_نورها"
6913            | "امسح_الأضواء"
6914            | "נקה_אורות"
6915            | "روشنیاں_صاف_کرو"
6916            | "effacer_lumières"
6917            | "lichter_löschen"
6918            | "очистить_свет" => {
6919                self.gfx.borrow_mut().lights.clear();
6920                return Ok(Value::Unit);
6921            },
6922
6923            // ── set_material(key, value) — configure LingMaterial field ──
6924            // Activates the material BSDF for subsequent polygon/triangle draws.
6925            // Keys (string): "albedo" "roughness" "metallic" "emission"
6926            //   "emission_strength" "specular" "specular_tint" "subsurface"
6927            //   "subsurface_color" "clearcoat" "clearcoat_roughness"
6928            //   "transmission" "ior" "iridescence" "sheen" "anisotropy"
6929            //   "anisotropy_angle" "toon_bands" "shadow_softness"
6930            //   "outline_px" "outline_color" "highlight_color"
6931            // Value: number (or packed 0xRRGGBB for colour fields)
6932            "set_material"
6933            | "ตั้งวัสดุ"
6934            | "设置材质"
6935            | "マテリアル設定"
6936            | "재질설정"
6937            | "تنظیم_متریال"
6938            | "عيّن_المادة"
6939            | "קבע_חומר"
6940            | "میٹریل_مقرر_کرو" => {
6941                let key = self.arg_str(&args, 0, "");
6942                let val = self.arg_num(&args, 1, 0.0)?;
6943                let mut gfx = self.gfx.borrow_mut();
6944                let mat = gfx
6945                    .material
6946                    .get_or_insert_with(crate::gfx::LingMaterial::default);
6947                match key.as_str() {
6948                    "albedo" => mat.albedo = val as u32,
6949                    "roughness" => mat.roughness = val as f32,
6950                    "metallic" => mat.metallic = val as f32,
6951                    "emission" => mat.emission = val as u32,
6952                    "emission_strength" => mat.emission_strength = val as f32,
6953                    "specular" => mat.specular = val as f32,
6954                    "specular_tint" => mat.specular_tint = val as f32,
6955                    "subsurface" => mat.subsurface = val as f32,
6956                    "subsurface_color" => mat.subsurface_color = val as u32,
6957                    "clearcoat" => mat.clearcoat = val as f32,
6958                    "clearcoat_roughness" => mat.clearcoat_roughness = val as f32,
6959                    "transmission" => mat.transmission = val as f32,
6960                    "ior" => mat.ior = val as f32,
6961                    "iridescence" => mat.iridescence = val as f32,
6962                    "sheen" => mat.sheen = val as f32,
6963                    "anisotropy" => mat.anisotropy = val as f32,
6964                    "anisotropy_angle" => mat.anisotropy_angle = val as f32,
6965                    "toon_bands" => mat.toon_bands = val as u32,
6966                    "shadow_softness" => mat.shadow_softness = val as f32,
6967                    "outline_px" => mat.outline_px = val as f32,
6968                    "outline_color" => mat.outline_color = val as u32,
6969                    "highlight_color" => mat.highlight_color = val as u32,
6970                    _ => {},
6971                }
6972                return Ok(Value::Unit);
6973            },
6974
6975            // ── reset_material() — disable material override ──
6976            // After this call, draws use the legacy compute_lit_color_linear path.
6977            "reset_material"
6978            | "รีเซ็ตวัสดุ"
6979            | "重置材质"
6980            | "マテリアルリセット"
6981            | "재질초기화"
6982            | "بازنشانی_متریال"
6983            | "أعد_ضبط_المادة"
6984            | "אפס_חומר"
6985            | "میٹریل_ری_سیٹ" => {
6986                self.gfx.borrow_mut().material = None;
6987                return Ok(Value::Unit);
6988            },
6989
6990            // ── toon_outlines(thickness, color, threshold) ──
6991            // Enable vector-smooth ink outlines on depth discontinuities.
6992            //   thickness  — ink-line half-width in pixels (0 = off, 1.5 = anime default)
6993            //   color      — 0xRRGGBB ink colour (default black = 0)
6994            //   threshold  — depth delta that triggers an edge (0.05 recommended)
6995            "toon_outlines"
6996            | "ตั้งเส้นขอบการ์ตูน"
6997            | "卡通轮廓"
6998            | "トゥーンアウトライン"
6999            | "툰아웃라인"
7000            | "خطوط_کارتونی"
7001            | "حدود_كرتونية"
7002            | "קווי_מתאר_מצוירים"
7003            | "ٹون_آؤٹ_لائنز" => {
7004                let px = self.arg_num(&args, 0, 0.0)? as f32;
7005                let color = self.arg_num(&args, 1, 0.0)? as u32;
7006                let thresh = self.arg_num(&args, 2, 0.05)? as f32;
7007                let mut gfx = self.gfx.borrow_mut();
7008                gfx.toon.outline_px = px;
7009                gfx.toon.outline_color = color;
7010                gfx.toon.outline_thresh = thresh;
7011                return Ok(Value::Unit);
7012            },
7013
7014            // ── tone_stop(t, value) ──
7015            // Add a stop to the tone ramp.
7016            //   t      — input luminance position [0..1]
7017            //   value  — output brightness [0..1]
7018            // Stops are automatically sorted; call tone_ramp_reset() first to clear.
7019            "tone_stop"
7020            | "ตั้งจุดโทน"
7021            | "色调停止"
7022            | "トーンストップ"
7023            | "톤스톱"
7024            | "نقطه_توقف_تن_رنگ"
7025            | "نقطة_توقف_اللون"
7026            | "נקודת_עצירת_גוון"
7027            | "ٹون_اسٹاپ" => {
7028                let t = self.arg_num(&args, 0, 0.0)? as f32;
7029                let val = self.arg_num(&args, 1, 1.0)? as f32;
7030                let mut gfx = self.gfx.borrow_mut();
7031                gfx.toon.ramp.stops.push(crate::gfx::toon::ToneStop {
7032                    t: t.clamp(0.0, 1.0),
7033                    value: val.clamp(0.0, 1.0),
7034                });
7035                gfx.toon
7036                    .ramp
7037                    .stops
7038                    .sort_by(|a, b| a.t.partial_cmp(&b.t).unwrap_or(std::cmp::Ordering::Equal));
7039                return Ok(Value::Unit);
7040            },
7041
7042            // ── tone_smooth(enabled) ──
7043            // 0 = hard cel snap between stops (default); 1 = smooth gradient lerp.
7044            "tone_smooth"
7045            | "ตั้งโทนนุ่ม"
7046            | "色调平滑"
7047            | "トーンスムーズ"
7048            | "톤스무스"
7049            | "تن_رنگ_نرم"
7050            | "تدرج_لون_ناعم"
7051            | "גוון_חלק"
7052            | "ٹون_ہموار" => {
7053                let v = self.arg_num(&args, 0, 0.0)? as f32;
7054                self.gfx.borrow_mut().toon.ramp.smooth = v > 0.5;
7055                return Ok(Value::Unit);
7056            },
7057
7058            // ── tone_bezier(y1, y2) ──
7059            // Apply a cubic Bézier remap to the input luminance before stop lookup.
7060            //   y1, y2 — control-point y-values (identity: y1=0.333 y2=0.667)
7061            //   0 args or tone_bezier(0, 0)  → ease-in (shadow-heavy)
7062            //   tone_bezier(1, 1)            → ease-out (highlight-heavy)
7063            //   tone_bezier(0.1, 0.9)        → S-curve (smooth both ends)
7064            //   tone_bezier_off()            → disable (back to linear)
7065            "tone_bezier"
7066            | "ตั้งโทนเบซิเยร์"
7067            | "色调贝塞尔"
7068            | "トーンベジェ"
7069            | "톤베지어"
7070            | "تن_رنگ_بزیه"
7071            | "تدرج_لون_بيزيه"
7072            | "גוון_בזייה"
7073            | "بیزیئر_ٹون" => {
7074                let y1 = self.arg_num(&args, 0, 1.0 / 3.0)? as f32;
7075                let y2 = self.arg_num(&args, 1, 2.0 / 3.0)? as f32;
7076                self.gfx.borrow_mut().toon.ramp.bezier = Some([y1, y2]);
7077                return Ok(Value::Unit);
7078            },
7079
7080            // ── tone_bezier_off() — disable Bézier remap ──
7081            "tone_bezier_off"
7082            | "ปิดโทนเบซิเยร์"
7083            | "关闭色调贝塞尔"
7084            | "トーンベジェオフ"
7085            | "톤베지어끄기"
7086            | "خاموش‌کردن_بزیه"
7087            | "إيقاف_تدرج_بيزيه"
7088            | "כבה_גוון_בזייה"
7089            | "بیزیئر_ٹون_بند" => {
7090                self.gfx.borrow_mut().toon.ramp.bezier = None;
7091                return Ok(Value::Unit);
7092            },
7093
7094            // ── tone_ramp_reset() — restore default 3-band cel ramp ──
7095            "tone_ramp_reset"
7096            | "รีเซ็ตการไล่โทน"
7097            | "重置色调渐变"
7098            | "トーンランプリセット"
7099            | "톤램프리셋"
7100            | "بازنشانی_شیب_تن_رنگ"
7101            | "أعد_ضبط_تدرج_اللون"
7102            | "אפס_שיפוע_גוון"
7103            | "ٹون_ریمپ_ری_سیٹ" => {
7104                self.gfx.borrow_mut().toon.ramp = crate::gfx::toon::ToneRamp::default();
7105                return Ok(Value::Unit);
7106            },
7107
7108            // ── tone_ramp_clear() — clear all stops (build your own ramp) ──
7109            "tone_ramp_clear"
7110            | "ล้างการไล่โทน"
7111            | "清除色调渐变"
7112            | "トーンランプクリア"
7113            | "톤램프클리어"
7114            | "پاک‌کردن_شیب_تن_رنگ"
7115            | "امسح_تدرج_اللون"
7116            | "נקה_שיפוע_גוון"
7117            | "ٹون_ریمپ_صاف" => {
7118                self.gfx.borrow_mut().toon.ramp.stops.clear();
7119                return Ok(Value::Unit);
7120            },
7121
7122            // ── tone_soft(soft, sheen) — band-edge softness + highlight sheen ──
7123            //   soft  [0..1] — fraction of each band gap that blends smoothly
7124            //                  across the boundary (0 = crisp cel, ~0.3 = soft
7125            //                  Wind Waker shadow edges). Default 0.32.
7126            //   sheen [0..1] — bright pixels keep their smooth gradient instead
7127            //                  of being quantised (clean specular/rim sheen
7128            //                  rather than scratchy banded highlights). 0.65.
7129            "tone_soft"
7130            | "โทนขอบนุ่ม"
7131            | "色调柔边"
7132            | "トーンソフト"
7133            | "톤소프트"
7134            | "تن_رنگ_لبه‌نرم"
7135            | "تدرج_ناعم_الحواف"
7136            | "גוון_קצה_רך"
7137            | "نرم_کنارہ_ٹون"
7138            | "tonalité_douce"
7139            | "weicher_ton"
7140            | "мягкий_тон" => {
7141                let s = self.arg_num(&args, 0, 0.32)? as f32;
7142                let sh = self.arg_num(&args, 1, 0.65)? as f32;
7143                let mut gfx = self.gfx.borrow_mut();
7144                gfx.toon.ramp.soft = s.clamp(0.0, 1.0);
7145                gfx.toon.ramp.sheen = sh.clamp(0.0, 1.0);
7146                return Ok(Value::Unit);
7147            },
7148
7149            // ── set_ssao(strength, radius_px, zrange) — ambient occlusion ──
7150            // Depth-buffer contact shading: soft darkening in corners/under
7151            // objects, computed half-res + smoothed (no grain). Needs
7152            // set_depth_test(1). strength 0 disables. Defaults (0.35, 6, 12).
7153            "set_ssao"
7154            | "ตั้งเงาสัมผัส"
7155            | "环境光遮蔽"
7156            | "アンビエントオクルージョン"
7157            | "앰비언트오클루전"
7158            | "تنظیم_انسداد_محیطی"
7159            | "عيّن_تظليل_محيطي"
7160            | "קבע_הצללה_סביבתית"
7161            | "ایس_ایس_اے_او_مقرر_کرو"
7162            | "définir_ssao"
7163            | "ssao_setzen"
7164            | "задать_ssao" => {
7165                let s = self.arg_num(&args, 0, 0.35)? as f32;
7166                let r = self.arg_num(&args, 1, 6.0)? as f32;
7167                let z = self.arg_num(&args, 2, 12.0)? as f32;
7168                let mut gfx = self.gfx.borrow_mut();
7169                gfx.toon.ao_strength = s.clamp(0.0, 1.0);
7170                gfx.toon.ao_radius = r.max(1.0);
7171                gfx.toon.ao_range = z.max(0.01);
7172                return Ok(Value::Unit);
7173            },
7174
7175            // ── set_fxaa(on) — FXAA-lite screen-space edge anti-aliasing ──
7176            // Softens polygon stair-steps and ink-line jaggies over the whole
7177            // frame; flat fills are untouched. Applied last in the present
7178            // post-chain. (set_antialias smooths wireframe STROKES; this pass
7179            // smooths the composited IMAGE.)
7180            "set_fxaa"
7181            | "ลบรอยหยัก"
7182            | "屏幕抗锯齿"
7183            | "画面アンチエイリアス"
7184            | "화면안티앨리어싱"
7185            | "تنظیم_ضدلبه‌دندانه_سریع"
7186            | "عيّن_مضاد_التسنن_السريع"
7187            | "קבע_החלקת_מסך"
7188            | "ایف_ایکس_اے_اے_مقرر_کرو"
7189            | "définir_fxaa"
7190            | "fxaa_setzen"
7191            | "задать_fxaa" => {
7192                let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
7193                self.gfx.borrow_mut().toon.fxaa = on;
7194                return Ok(Value::Unit);
7195            },
7196
7197            // ── set_bloom(strength, threshold) — soft HDR-style glow ──
7198            // Bright pixels (rim sheen, emissive, additive FX) bleed a soft
7199            // quarter-res glow — the "HDR material" feel for toon/vector art.
7200            // strength 0 disables; threshold = luminance cutoff [0..1].
7201            "set_bloom"
7202            | "ตั้งบลูม"
7203            | "泛光"
7204            | "ブルーム"
7205            | "블룸"
7206            | "تنظیم_درخشش"
7207            | "عيّن_التوهج"
7208            | "קבע_זוהר"
7209            | "بلوم_مقرر_کرو"
7210            | "définir_bloom"
7211            | "bloom_setzen"
7212            | "задать_блум" => {
7213                let s = self.arg_num(&args, 0, 0.45)? as f32;
7214                let t = self.arg_num(&args, 1, 0.74)? as f32;
7215                let mut gfx = self.gfx.borrow_mut();
7216                gfx.toon.bloom_strength = s.max(0.0);
7217                gfx.toon.bloom_thresh = t.clamp(0.0, 0.99);
7218                return Ok(Value::Unit);
7219            },
7220
7221            // ── shadow_smooth(softness) [compat] → tone_smooth + tone_bezier ──
7222            // Deprecated: use tone_smooth + tone_bezier instead.
7223            "shadow_smooth"
7224            | "ตั้งเงานุ่ม"
7225            | "柔化阴影"
7226            | "影ソフト"
7227            | "그림자부드럽게"
7228            | "سایه_نرم"
7229            | "ظل_ناعم"
7230            | "צל_חלק"
7231            | "نرم_سایہ" => {
7232                let s = self.arg_num(&args, 0, 0.0)? as f32;
7233                let mut gfx = self.gfx.borrow_mut();
7234                gfx.toon.ramp.smooth = s > 0.05;
7235                if s > 0.05 {
7236                    let y1 = (0.333 + s * 0.2).clamp(0.0, 1.0);
7237                    let y2 = (0.667 - s * 0.2).clamp(0.0, 1.0);
7238                    gfx.toon.ramp.bezier = Some([y1, y2]);
7239                } else {
7240                    gfx.toon.ramp.bezier = None;
7241                }
7242                return Ok(Value::Unit);
7243            },
7244
7245            // ── toon_highlight [compat] — no-op, use tone_stop instead ──
7246            "toon_highlight"
7247            | "ตั้งไฮไลท์การ์ตูน"
7248            | "卡通高光"
7249            | "トゥーンハイライト"
7250            | "툰하이라이트"
7251            | "هایلایت_کارتونی"
7252            | "إبراز_كرتوني"
7253            | "הדגשה_מצוירת"
7254            | "ٹون_ہائی_لائٹ" => {
7255                // Remap as a lit-band brightness boost: adds a stop near the highlight threshold.
7256                let _strength = self.arg_num(&args, 0, 0.0)? as f32;
7257                let _thresh = self.arg_num(&args, 2, 0.78)? as f32;
7258                // No-op: configure via tone_stop() for precise control.
7259                return Ok(Value::Unit);
7260            },
7261
7262            // ── set_ambient(v) — ambient light level [0..1] ──
7263            "set_ambient"
7264            | "ตั้งแสงรอบข้าง"
7265            | "环境光"
7266            | "環境光設定"
7267            | "환경광설정"
7268            | "تنظیم_نور_محیطی"
7269            | "عيّن_الإضاءة_المحيطة"
7270            | "קבע_תאורה_סביבתית"
7271            | "ماحولیاتی_روشنی_مقرر_کرو"
7272            | "définir_ambiante"
7273            | "umgebungslicht_setzen"
7274            | "задать_фон" => {
7275                let v = self.arg_num(&args, 0, 0.15)? as f32;
7276                self.gfx.borrow_mut().ambient = v;
7277                return Ok(Value::Unit);
7278            },
7279
7280            // ── set_fog(r,g,b, start, end) — distance fog toward (r,g,b).
7281            //    triangles/lines fade from `start`..`end` camera depth. end<=0 = off.
7282            "set_fog"
7283            | "ตั้งหมอก"
7284            | "雾"
7285            | "霧設定"
7286            | "안개설정"
7287            | "تنظیم_مه"
7288            | "عيّن_الضباب"
7289            | "קבע_ערפל"
7290            | "دھند_مقرر_کرو" => {
7291                let r = self.arg_num(&args, 0, 0.0)?.clamp(0.0, 255.0) as u32;
7292                let g = self.arg_num(&args, 1, 0.0)?.clamp(0.0, 255.0) as u32;
7293                let b = self.arg_num(&args, 2, 0.0)?.clamp(0.0, 255.0) as u32;
7294                let start = self.arg_num(&args, 3, 0.0)? as f32;
7295                let end = self.arg_num(&args, 4, 0.0)? as f32;
7296                let mut gfx = self.gfx.borrow_mut();
7297                gfx.fog_color = (r << 16) | (g << 8) | b;
7298                gfx.fog_start = start;
7299                gfx.fog_end = end;
7300                return Ok(Value::Unit);
7301            },
7302
7303            // ── วาดสามเหลี่ยม3มิติ(ax,ay,az, bx,by,bz, cx,cy,cz) ──
7304            // Computes lighting from world-space normal + active lights (cel shading),
7305            // projects via the stored camera, and pushes to the depth queue.
7306            "วาดสามเหลี่ยม3มิติ"
7307            | "draw_triangle_3d"
7308            | "triangle3d"
7309            | "رسم_مثلث_سه‌بعدی"
7310            | "ارسم_مثلثا_ثلاثي_الأبعاد"
7311            | "צייר_משולש_תלת_ממדי"
7312            | "تھری_ڈی_مثلث_کھینچو" => {
7313                let ax = self.arg_num(&args, 0, 0.0)? as f32;
7314                let ay = self.arg_num(&args, 1, 0.0)? as f32;
7315                let az = self.arg_num(&args, 2, 0.0)? as f32;
7316                let bx = self.arg_num(&args, 3, 0.0)? as f32;
7317                let by = self.arg_num(&args, 4, 0.0)? as f32;
7318                let bz = self.arg_num(&args, 5, 0.0)? as f32;
7319                let cx = self.arg_num(&args, 6, 0.0)? as f32;
7320                let cy = self.arg_num(&args, 7, 0.0)? as f32;
7321                let cz = self.arg_num(&args, 8, 0.0)? as f32;
7322
7323                let mut gfx = self.gfx.borrow_mut();
7324
7325                // Mesh capture: record raw local coords + pen colour, skip submit.
7326                if gfx.mesh_capture.is_some() {
7327                    let col = gfx.color;
7328                    gfx.mesh_capture
7329                        .as_mut()
7330                        .unwrap()
7331                        .push(([ax, ay, az, bx, by, bz, cx, cy, cz], col));
7332                    return Ok(Value::Unit);
7333                }
7334
7335                gfx.submit_triangle(ax, ay, az, bx, by, bz, cx, cy, cz);
7336                return Ok(Value::Unit);
7337            },
7338
7339            // ── เริ่มอบเมช() — begin capturing 3-D triangles into a display list ──
7340            "เริ่มอบเมช"
7341            | "mesh_bake_begin"
7342            | "شروع_پخت_مش"
7343            | "ابدأ_خبز_الشبكة"
7344            | "התחל_אפיית_רשת"
7345            | "میش_بیک_شروع" => {
7346                self.gfx.borrow_mut().mesh_capture = Some(Vec::new());
7347                return Ok(Value::Unit);
7348            },
7349
7350            // ── เมชแคชรับ(key) — keyed display-list cache lookup (-1 = miss) ──
7351            "เมชแคชรับ"
7352            | "mesh_cache_get"
7353            | "دریافت_کش_مش"
7354            | "اجلب_مخبأ_الشبكة"
7355            | "קבל_מטמון_רשת"
7356            | "میش_کیش_حاصل_کرو" => {
7357                let key = self.arg_num(&args, 0, 0.0)? as i64;
7358                let h = self.gfx.borrow().mesh_cache.get(&key).copied();
7359                return Ok(Value::Number(h.map(|x| x as f64).unwrap_or(-1.0)));
7360            },
7361
7362            // ── เมชแคชตั้ง(key, handle) — store a baked mesh under key (bounded) ──
7363            "เมชแคชตั้ง"
7364            | "mesh_cache_put"
7365            | "ذخیره_در_کش_مش"
7366            | "ضع_في_مخبأ_الشبكة"
7367            | "שמור_במטמון_רשת"
7368            | "میش_کیش_رکھو" => {
7369                let key = self.arg_num(&args, 0, 0.0)? as i64;
7370                let h = self.arg_num(&args, 1, 0.0)? as usize;
7371                let mut gfx = self.gfx.borrow_mut();
7372                const CAP: usize = 256;
7373                if gfx.mesh_cache.len() >= CAP {
7374                    let evict: Vec<usize> = gfx.mesh_cache.values().copied().collect();
7375                    gfx.mesh_cache.clear();
7376                    for id in evict {
7377                        if id < gfx.meshes.len() {
7378                            gfx.meshes[id].clear();
7379                            gfx.mesh_free.push(id);
7380                        }
7381                    }
7382                }
7383                gfx.mesh_cache.insert(key, h);
7384                return Ok(Value::Unit);
7385            },
7386
7387            // ── เมชแคชล้าง() — drop the keyed cache (e.g. on level change) ──
7388            "เมชแคชล้าง"
7389            | "mesh_cache_clear"
7390            | "پاک‌کردن_کش_مش"
7391            | "امسح_مخبأ_الشبكة"
7392            | "נקה_מטמון_רשת"
7393            | "میش_کیش_صاف" => {
7394                let mut gfx = self.gfx.borrow_mut();
7395                let evict: Vec<usize> = gfx.mesh_cache.values().copied().collect();
7396                gfx.mesh_cache.clear();
7397                for id in evict {
7398                    if id < gfx.meshes.len() {
7399                        gfx.meshes[id].clear();
7400                        gfx.mesh_free.push(id);
7401                    }
7402                }
7403                return Ok(Value::Unit);
7404            },
7405
7406            // ── จบอบเมช() — bake captured triangles, return mesh handle ──
7407            "จบอบเมช"
7408            | "mesh_bake_end"
7409            | "پایان_پخت_مش"
7410            | "أنهِ_خبز_الشبكة"
7411            | "סיים_אפיית_רשת"
7412            | "میش_بیک_ختم" => {
7413                let mut gfx = self.gfx.borrow_mut();
7414                let tris = gfx.mesh_capture.take().unwrap_or_default();
7415                let id = gfx.mesh_register(tris);
7416                return Ok(Value::Number(id as f64));
7417            },
7418
7419            // ── วาดอบเมช[สี](id, ox,oy,oz, rx,ry,rz, ux,uy,uz, s) — draw a baked mesh ──
7420            //   วาดอบเมช: current pen colour (tinted glyphs)
7421            //   วาดอบเมชสี: per-triangle baked colour (multi-colour models)
7422            "วาดอบเมช"
7423            | "mesh_bake_draw"
7424            | "วาดอบเมชสี"
7425            | "mesh_bake_draw_col"
7426            | "رسم_مش_پخته"
7427            | "ارسم_شبكة_مخبوزة"
7428            | "צייר_רשת_אפויה"
7429            | "بیکڈ_میش_کھینچو" => {
7430                let baked_col = matches!(name, "วาดอบเมชสี" | "mesh_bake_draw_col");
7431                let id = self.arg_num(&args, 0, 0.0)? as usize;
7432                let ox = self.arg_num(&args, 1, 0.0)? as f32;
7433                let oy = self.arg_num(&args, 2, 0.0)? as f32;
7434                let oz = self.arg_num(&args, 3, 0.0)? as f32;
7435                let rx = self.arg_num(&args, 4, 1.0)? as f32;
7436                let ry = self.arg_num(&args, 5, 0.0)? as f32;
7437                let rz = self.arg_num(&args, 6, 0.0)? as f32;
7438                let ux = self.arg_num(&args, 7, 0.0)? as f32;
7439                let uy = self.arg_num(&args, 8, 1.0)? as f32;
7440                let uz = self.arg_num(&args, 9, 0.0)? as f32;
7441                let s = self.arg_num(&args, 10, 1.0)? as f32;
7442                self.gfx
7443                    .borrow_mut()
7444                    .mesh_draw(id, ox, oy, oz, rx, ry, rz, ux, uy, uz, s, baked_col);
7445                return Ok(Value::Unit);
7446            },
7447
7448            // ── draw_quad_3d / draw_pent_3d / draw_hex_3d / draw_polygon_3d ──
7449            // Fan-triangulate convex n-gons.  Lighting, near-plane clip, fog, and
7450            // Gouraud shading all mirror draw_triangle_3d exactly.
7451            "draw_quad_3d"
7452            | "quad3d"
7453            | "วาดสี่เหลี่ยม3มิติ"
7454            | "draw_pent_3d"
7455            | "pent3d"
7456            | "วาดห้าเหลี่ยม3มิติ"
7457            | "draw_hex_3d"
7458            | "hex3d"
7459            | "วาดหกเหลี่ยม3มิติ"
7460            | "draw_polygon_3d"
7461            | "polygon3d"
7462            | "วาดรูปหลายเหลี่ยม3มิติ"
7463            | "رسم_چهارضلعی_سه‌بعدی"
7464            | "ارسم_رباعيا_ثلاثي_الأبعاد"
7465            | "צייר_מרובע_תלת_ממדי"
7466            | "تھری_ڈی_چوکور_کھینچو" => {
7467                // Collect (wx, wy, wz) triples from args or list
7468                let mut wxs: [f32; 8] = [0.0; 8];
7469                let mut wys: [f32; 8] = [0.0; 8];
7470                let mut wzs: [f32; 8] = [0.0; 8];
7471                let n_verts;
7472
7473                if args.len() == 1 {
7474                    // draw_polygon_3d([x0,y0,z0, x1,y1,z1, ...])
7475                    let list = match &args[0] {
7476                        Value::List(l) => l.clone(),
7477                        _ => {
7478                            return Err(EvalErr::from("draw_polygon_3d: expected list".to_string()))
7479                        },
7480                    };
7481                    let coords: Vec<f32> = list
7482                        .iter()
7483                        .map(|v| match v {
7484                            Value::Number(n) => *n as f32,
7485                            _ => 0.0,
7486                        })
7487                        .collect();
7488                    n_verts = (coords.len() / 3).min(8);
7489                    for i in 0..n_verts {
7490                        wxs[i] = coords[i * 3];
7491                        wys[i] = coords[i * 3 + 1];
7492                        wzs[i] = coords[i * 3 + 2];
7493                    }
7494                } else {
7495                    // draw_quad/pent/hex_3d(x0,y0,z0, x1,y1,z1, ...)
7496                    n_verts = (args.len() / 3).min(8);
7497                    for i in 0..n_verts {
7498                        wxs[i] = self.arg_num(&args, i * 3, 0.0)? as f32;
7499                        wys[i] = self.arg_num(&args, i * 3 + 1, 0.0)? as f32;
7500                        wzs[i] = self.arg_num(&args, i * 3 + 2, 0.0)? as f32;
7501                    }
7502                }
7503                if n_verts < 3 {
7504                    return Ok(Value::Unit);
7505                }
7506
7507                let mut gfx = self.gfx.borrow_mut();
7508
7509                // Mesh capture: fan-triangulate and record raw local coords +
7510                // pen colour, exactly like วาดสามเหลี่ยม3มิติ. Quads used to
7511                // fall through here and bake EMPTY display lists — the 3-D
7512                // glyph fonts (letter pickups) are built from draw_quad_3d,
7513                // which made every baked glyph invisible.
7514                if gfx.mesh_capture.is_some() {
7515                    let col = gfx.color;
7516                    let cap = gfx.mesh_capture.as_mut().unwrap();
7517                    for i in 1..n_verts - 1 {
7518                        cap.push((
7519                            [
7520                                wxs[0],
7521                                wys[0],
7522                                wzs[0],
7523                                wxs[i],
7524                                wys[i],
7525                                wzs[i],
7526                                wxs[i + 1],
7527                                wys[i + 1],
7528                                wzs[i + 1],
7529                            ],
7530                            col,
7531                        ));
7532                    }
7533                    return Ok(Value::Unit);
7534                }
7535
7536                // Face normal from first triangle of the fan
7537                let normal = crate::gfx::poly::face_normal(
7538                    wxs[0], wys[0], wzs[0], wxs[1], wys[1], wzs[1], wxs[2], wys[2], wzs[2],
7539                );
7540
7541                // Per-vertex lit colours
7542                let mut wcs: [u32; 8] = [0; 8];
7543                if gfx.flat_shade {
7544                    let c = gfx.color;
7545                    for wc in wcs.iter_mut().take(n_verts) {
7546                        *wc = c;
7547                    }
7548                } else if let Some(ref mat) = gfx.material.clone() {
7549                    let cam = [gfx.camera.cx, gfx.camera.cy, gfx.camera.zdist];
7550                    let lights: Vec<_> = gfx.lights.clone();
7551                    let ambient = gfx.ambient;
7552                    for i in 0..n_verts {
7553                        let v = [wxs[i], wys[i], wzs[i]];
7554                        let vd = [cam[0] - v[0], cam[1] - v[1], cam[2] - v[2]];
7555                        wcs[i] = crate::gfx::material::shade(mat, normal, vd, v, &lights, ambient);
7556                    }
7557                } else {
7558                    let base = gfx.color;
7559                    let lights: Vec<_> = gfx.lights.clone();
7560                    let ambient = gfx.ambient;
7561                    for i in 0..n_verts {
7562                        wcs[i] = crate::gfx::light::compute_lit_color_linear(
7563                            base,
7564                            normal,
7565                            [wxs[i], wys[i], wzs[i]],
7566                            &lights,
7567                            ambient,
7568                        );
7569                    }
7570                }
7571
7572                // Near-plane clip (Sutherland-Hodgman per vertex)
7573                let near = -gfx.camera.zdist + 0.05;
7574                let mut clip_in: [(f32, f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
7575                    [(0.0, 0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
7576                for i in 0..n_verts {
7577                    let d = gfx.camera.depth(wxs[i], wys[i], wzs[i]);
7578                    clip_in[i] = (wxs[i], wys[i], wzs[i], d, wcs[i]);
7579                }
7580                let mut clip_out: [(f32, f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
7581                    [(0.0, 0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
7582                let pn = crate::gfx::poly::clip_near(&clip_in, n_verts, near, &mut clip_out);
7583                if pn < 3 {
7584                    return Ok(Value::Unit);
7585                }
7586
7587                // Project + fog
7588                let mut proj: [(f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
7589                    [(0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
7590                for i in 0..pn {
7591                    let (sx, sy, sz) =
7592                        gfx.camera
7593                            .project(clip_out[i].0, clip_out[i].1, clip_out[i].2);
7594                    let fc = gfx.fog_apply(clip_out[i].4, sz);
7595                    proj[i] = (sx, sy, sz, fc);
7596                }
7597
7598                // Fan-triangulate and push
7599                let unlit = gfx.flat_shade;
7600                crate::gfx::poly::fan_emit_proj(
7601                    &proj,
7602                    pn,
7603                    |x0, y0, z0, c0, x1, y1, z1, c1, x2, y2, z2, c2| {
7604                        gfx.depth_queue.push_triangle_g_zv(
7605                            x0, y0, z0, c0, x1, y1, z1, c1, x2, y2, z2, c2, 3, unlit,
7606                        );
7607                    },
7608                );
7609                return Ok(Value::Unit);
7610            },
7611
7612            // ── วาดเส้น3มิติ(ax,ay,az, bx,by,bz) ──
7613            // Projects two world-space points via the stored camera and pushes
7614            // a line to the depth queue.
7615            "วาดเส้น3มิติ"
7616            | "draw_line_3d"
7617            | "line3d"
7618            | "画3D线"
7619            | "3D線描く"
7620            | "3D선그리기"
7621            | "رسم_خط_سه‌بعدی"
7622            | "ارسم_خطا_ثلاثي_الأبعاد"
7623            | "צייר_קו_תלת_ממדי"
7624            | "تھری_ڈی_لکیر_کھینچو" => {
7625                let ax = self.arg_num(&args, 0, 0.0)? as f32;
7626                let ay = self.arg_num(&args, 1, 0.0)? as f32;
7627                let az = self.arg_num(&args, 2, 0.0)? as f32;
7628                let bx = self.arg_num(&args, 3, 0.0)? as f32;
7629                let by = self.arg_num(&args, 4, 0.0)? as f32;
7630                let bz = self.arg_num(&args, 5, 0.0)? as f32;
7631
7632                let mut gfx = self.gfx.borrow_mut();
7633                let color = gfx.color;
7634                // Near-plane clip in 3-D before perspective divide
7635                let near = -gfx.camera.zdist + 0.05;
7636                let mut lax = ax;
7637                let mut lay = ay;
7638                let mut laz = az;
7639                let mut lbx = bx;
7640                let mut lby = by;
7641                let mut lbz = bz;
7642                let da_raw = gfx.camera.depth(lax, lay, laz);
7643                let db_raw = gfx.camera.depth(lbx, lby, lbz);
7644                if da_raw <= near && db_raw <= near {
7645                    return Ok(Value::Unit);
7646                }
7647                if da_raw <= near {
7648                    let t = (near - da_raw) / (db_raw - da_raw);
7649                    lax += t * (lbx - lax);
7650                    lay += t * (lby - lay);
7651                    laz += t * (lbz - laz);
7652                } else if db_raw <= near {
7653                    let t = (near - da_raw) / (db_raw - da_raw);
7654                    lbx = lax + t * (lbx - lax);
7655                    lby = lay + t * (lby - lay);
7656                    lbz = laz + t * (lbz - laz);
7657                }
7658                // Shared-edge dedup: skip if this world-space edge was already queued.
7659                if !gfx.edge_set.try_insert(lax, lay, laz, lbx, lby, lbz) {
7660                    return Ok(Value::Unit);
7661                }
7662                let (sax, say, da) = gfx.camera.project(lax, lay, laz);
7663                let (sbx, sby, db) = gfx.camera.project(lbx, lby, lbz);
7664                let depth = (da + db) / 2.0;
7665                let color = gfx.fog_apply(color, depth);
7666                gfx.depth_queue.push_line(depth, color, sax, say, sbx, sby);
7667                return Ok(Value::Unit);
7668            },
7669
7670            // orb_shell(cx,cy,cz, radius, rot_y, rot_x, density, r,g,b)
7671            //   A single trippy, grayscale, depth-faded vector pattern wound around
7672            //   a sphere — two families of interleaved spherical spirals (a guilloché
7673            //   weave), NOT a lat/long cage. Each segment's brightness follows its
7674            //   facing (front bright, back dim), so it reads as a translucent
7675            //   grayscale "texture" with alpha rather than a hard wireframe; the
7676            //   inner marble shows through. `rot_y`/`rot_x` roll the texture around
7677            //   the orb; `density` = spirals per winding direction. r,g,b tint it
7678            //   (pass a gray like 230,230,230 for pure grayscale).
7679            #[cfg(not(target_arch = "wasm32"))]
7680            "orb_shell"
7681            | "球壳"
7682            | "オーブ殻"
7683            | "오브껍질"
7684            | "เปลือกทรงกลม"
7685            | "پوسته_کروی"
7686            | "قشرة_كروية"
7687            | "קליפת_כדור"
7688            | "کروی_خول"
7689            | "coque_orbe"
7690            | "orb_hülle"
7691            | "оболочка_сферы" => {
7692                let cx = self.arg_num(&args, 0, 0.)? as f32;
7693                let cy = self.arg_num(&args, 1, 0.)? as f32;
7694                let cz = self.arg_num(&args, 2, 0.)? as f32;
7695                let radius = self.arg_num(&args, 3, 1.0)? as f32;
7696                let ry = self.arg_num(&args, 4, 0.)? as f32;
7697                let rx = self.arg_num(&args, 5, 0.)? as f32;
7698                let density = (self.arg_num(&args, 6, 10.)? as i32).clamp(1, 48);
7699                let tr = (self.arg_num(&args, 7, 230.)? as f32).clamp(0., 255.);
7700                let tg = (self.arg_num(&args, 8, 230.)? as f32).clamp(0., 255.);
7701                let tb = (self.arg_num(&args, 9, 235.)? as f32).clamp(0., 255.);
7702                let (cyr, syr) = (ry.cos(), ry.sin());
7703                let (cxr, sxr) = (rx.cos(), rx.sin());
7704                let tau = std::f32::consts::TAU;
7705                let pi = std::f32::consts::PI;
7706                let turns = 6.0_f32; // how many times each spiral wraps pole→pole
7707                let nseg = 96; // segments per spiral (smoothness)
7708                let inv_r = if radius.abs() > 1e-5 {
7709                    1.0 / radius
7710                } else {
7711                    0.0
7712                };
7713                // a point along a spiral (param u 0..1, start angle theta0, winding dir),
7714                // spun by ry/rx — returns (world point, facing 0..1 where 1 = toward camera)
7715                let pt = |u: f32, theta0: f32, dir: f32| -> ([f32; 3], f32) {
7716                    let phi = pi * u; // 0..pi  (north → south)
7717                    let th = dir * turns * tau * u + theta0;
7718                    let (mut x, y, mut z) = (
7719                        phi.sin() * th.cos() * radius,
7720                        phi.cos() * radius,
7721                        phi.sin() * th.sin() * radius,
7722                    );
7723                    let x1 = x * cyr + z * syr; // yaw about Y
7724                    let z1 = -x * syr + z * cyr;
7725                    x = x1;
7726                    z = z1;
7727                    let y2 = y * cxr - z * sxr; // pitch about X
7728                    let z2 = y * sxr + z * cxr;
7729                    // facing: camera sits at -zdist looking +z, so smaller z2 = nearer = brighter
7730                    let facing = (0.5 - 0.5 * z2 * inv_r).clamp(0.0, 1.0);
7731                    ([cx + x, cy + y2, cz + z2], facing)
7732                };
7733                let mut gfx = self.gfx.borrow_mut();
7734                let near = -gfx.camera.zdist + 0.05;
7735                // draw one segment (near-clipped) in a grayscale tint scaled by `lum`
7736                let seg = |gfx: &mut crate::gfx::GfxState, a: [f32; 3], b: [f32; 3], lum: f32| {
7737                    let (mut lax, mut lay, mut laz) = (a[0], a[1], a[2]);
7738                    let (mut lbx, mut lby, mut lbz) = (b[0], b[1], b[2]);
7739                    let da = gfx.camera.depth(lax, lay, laz);
7740                    let db = gfx.camera.depth(lbx, lby, lbz);
7741                    if da <= near && db <= near {
7742                        return;
7743                    }
7744                    if da <= near {
7745                        let t = (near - da) / (db - da);
7746                        lax += t * (lbx - lax);
7747                        lay += t * (lby - lay);
7748                        laz += t * (lbz - laz);
7749                    } else if db <= near {
7750                        let t = (near - da) / (db - da);
7751                        lbx = lax + t * (lbx - lax);
7752                        lby = lay + t * (lby - lay);
7753                        lbz = laz + t * (lbz - laz);
7754                    }
7755                    let (sax, say, da2) = gfx.camera.project(lax, lay, laz);
7756                    let (sbx, sby, db2) = gfx.camera.project(lbx, lby, lbz);
7757                    // grayscale-alpha: front-facing bright, back faded toward black
7758                    let l = (0.12 + 0.88 * lum).clamp(0.0, 1.0);
7759                    let cr = (tr * l) as u32;
7760                    let cg = (tg * l) as u32;
7761                    let cb = (tb * l) as u32;
7762                    let color = (cr << 16) | (cg << 8) | cb;
7763                    gfx.depth_queue
7764                        .push_line((da2 + db2) * 0.5, color, sax, say, sbx, sby);
7765                };
7766                // two opposite winding directions → a soft guilloché weave (not a cage)
7767                for &dir in &[1.0_f32, -1.0_f32] {
7768                    for s in 0..density {
7769                        let theta0 = s as f32 * tau / density as f32;
7770                        let mut prev = pt(0.0, theta0, dir);
7771                        for k in 1..=nseg {
7772                            let cur = pt(k as f32 / nseg as f32, theta0, dir);
7773                            seg(&mut gfx, prev.0, cur.0, (prev.1 + cur.1) * 0.5);
7774                            prev = cur;
7775                        }
7776                    }
7777                }
7778                return Ok(Value::Unit);
7779            },
7780
7781            // orb_particles(cx,cy,cz, radius, count, t, r,g,b)
7782            //   Fills the VOLUME of a sphere with `count` swirling vector points —
7783            //   like motes suspended inside a snow-globe orb. Points are distributed
7784            //   uniformly through the ball, slowly tumble as a cloud + wobble
7785            //   individually over time `t`, and are depth-shaded (near = bright,
7786            //   far = dim) so the cloud has real volume. Additive, so it layers under
7787            //   a shell / over a liquid marble.
7788            #[cfg(not(target_arch = "wasm32"))]
7789            "orb_particles"
7790            | "球内粒子"
7791            | "オーブ粒子"
7792            | "오브입자"
7793            | "อนุภาคทรงกลม"
7794            | "ذرات_کروی"
7795            | "جسيمات_كروية"
7796            | "חלקיקי_כדור"
7797            | "کروی_ذرات"
7798            | "particules_orbe"
7799            | "orb_partikel"
7800            | "частицы_сферы" => {
7801                let cx = self.arg_num(&args, 0, 0.)? as f32;
7802                let cy = self.arg_num(&args, 1, 0.)? as f32;
7803                let cz = self.arg_num(&args, 2, 0.)? as f32;
7804                let radius = self.arg_num(&args, 3, 1.0)? as f32;
7805                let count = (self.arg_num(&args, 4, 160.)? as i32).clamp(1, 4000);
7806                let t = self.arg_num(&args, 5, 0.)? as f32;
7807                let tr = (self.arg_num(&args, 6, 255.)? as f32).clamp(0., 255.);
7808                let tg = (self.arg_num(&args, 7, 255.)? as f32).clamp(0., 255.);
7809                let tb = (self.arg_num(&args, 8, 255.)? as f32).clamp(0., 255.);
7810                let inv_r = if radius.abs() > 1e-5 {
7811                    1.0 / radius
7812                } else {
7813                    0.0
7814                };
7815                // cheap deterministic hash → [0,1)
7816                let h = |mut x: u32| -> f32 {
7817                    x = x.wrapping_mul(747796405).wrapping_add(2891336453);
7818                    x = ((x >> ((x >> 28).wrapping_add(4))) ^ x).wrapping_mul(277803737);
7819                    (((x >> 22) ^ x) & 0xFFFFFF) as f32 / 16_777_216.0
7820                };
7821                let tau = std::f32::consts::TAU;
7822                // slow tumble of the whole cloud
7823                let (cyr, syr) = ((t * 0.5).cos(), (t * 0.5).sin());
7824                let (cxr, sxr) = ((t * 0.23).cos(), (t * 0.23).sin());
7825                let mut gfx = self.gfx.borrow_mut();
7826                let near = -gfx.camera.zdist + 0.05;
7827                let (sw, sh) = (gfx.width as i32, gfx.height as i32);
7828                for i in 0..count {
7829                    let i = i as u32;
7830                    // uniform-in-volume: r = cbrt(u) * radius; direction from two hashes
7831                    let u = h(i.wrapping_mul(3) + 1);
7832                    let rr = u.cbrt() * radius * (0.85 + 0.15 * (t * 1.3 + i as f32).sin()); // gentle pulse
7833                    let th = h(i.wrapping_mul(3) + 2) * tau + t * (0.3 + 0.5 * h(i * 7 + 5)); // per-mote orbit
7834                    let ph = (h(i.wrapping_mul(3) + 3) * 2.0 - 1.0).acos(); // uniform cos(phi)
7835                    let (mut x, y, mut z) = (
7836                        rr * ph.sin() * th.cos(),
7837                        rr * ph.cos(),
7838                        rr * ph.sin() * th.sin(),
7839                    );
7840                    // tumble the cloud (yaw then pitch)
7841                    let x1 = x * cyr + z * syr;
7842                    let z1 = -x * syr + z * cyr;
7843                    x = x1;
7844                    z = z1;
7845                    let y2 = y * cxr - z * sxr;
7846                    let z2 = y * sxr + z * cxr;
7847                    let (wx, wy, wz) = (cx + x, cy + y2, cz + z2);
7848                    if gfx.camera.depth(wx, wy, wz) <= near {
7849                        continue;
7850                    }
7851                    let (sx, sy, dep) = gfx.camera.project(wx, wy, wz);
7852                    let sxi = sx as i32;
7853                    let syi = sy as i32;
7854                    if sxi < 0 || syi < 0 || sxi >= sw || syi >= sh {
7855                        continue;
7856                    }
7857                    // depth-shade: nearer (smaller z2) = brighter
7858                    let facing = (0.5 - 0.5 * z2 * inv_r).clamp(0.15, 1.0);
7859                    let l = facing;
7860                    let cr = (tr * l) as u32;
7861                    let cg = (tg * l) as u32;
7862                    let cb = (tb * l) as u32;
7863                    let color = (cr << 16) | (cg << 8) | cb;
7864                    // a 1–2px dot (bigger when near) as a short segment in the depth queue
7865                    let len = if facing > 0.7 { 1.0 } else { 0.0 };
7866                    gfx.depth_queue.push_line(dep, color, sx, sy, sx + len, sy);
7867                }
7868                return Ok(Value::Unit);
7869            },
7870
7871            // project_3d(x,y,z) -> [screen_x, screen_y, depth]; behind the camera
7872            // returns a sentinel ([-99999,-99999, depth]) so scripts can skip it.
7873            // Lets scripts place 2-D overlays (e.g. filled teardrop flames) onto 3-D points.
7874            "project_3d"
7875            | "投影3D"
7876            | "3D投影"
7877            | "3D투영"
7878            | "ฉาย3มิติ"
7879            | "فرافکنی_سه‌بعدی"
7880            | "إسقاط_ثلاثي_الأبعاد"
7881            | "הטלה_תלת_ממדית"
7882            | "تھری_ڈی_پروجیکشن"
7883            | "projeter_3d"
7884            | "projizieren_3d"
7885            | "проекция_3d" => {
7886                let x = self.arg_num(&args, 0, 0.0)? as f32;
7887                let y = self.arg_num(&args, 1, 0.0)? as f32;
7888                let z = self.arg_num(&args, 2, 0.0)? as f32;
7889                let gfx = self.gfx.borrow();
7890                let near = -gfx.camera.zdist + 0.05;
7891                let d = gfx.camera.depth(x, y, z);
7892                if d <= near {
7893                    return Ok(Value::List(Rc::new(vec![
7894                        Value::Number(-99999.0),
7895                        Value::Number(-99999.0),
7896                        Value::Number(d as f64),
7897                    ])));
7898                }
7899                let (sx, sy, depth) = gfx.camera.project(x, y, z);
7900                return Ok(Value::List(Rc::new(vec![
7901                    Value::Number(sx as f64),
7902                    Value::Number(sy as f64),
7903                    Value::Number(depth as f64),
7904                ])));
7905            },
7906
7907            // mouse_ray() -> [ox,oy,oz, dx,dy,dz] — world-space ray from the eye
7908            // through the actual mouse cursor pixel, exact inverse of project_3d's
7909            // pipeline (translate → Y-rotate → X-rotate → perspective divide by
7910            // rz+zdist). Scripts previously marched a ray along the CENTRE-SCREEN
7911            // forward vector regardless of where the cursor was — accurate only by
7912            // coincidence when the cursor happened to sit near the crosshair.
7913            #[cfg(not(target_arch = "wasm32"))]
7914            "mouse_ray" => {
7915                let gfx = self.gfx.borrow();
7916                let (mx, my) = gfx
7917                    .window
7918                    .as_ref()
7919                    .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
7920                    .unwrap_or((gfx.camera.cx, gfx.camera.cy));
7921                let cam = &gfx.camera;
7922                // Eye-relative pinhole direction for this pixel, in rotation-space
7923                // (before undoing the Y-then-X rotation project() applied).
7924                let dcx = (mx - cam.cx) / cam.focal;
7925                let dcy = (my - cam.cy) / cam.focal;
7926                // Undo the X-rotation, then the Y-rotation (reverse of project()'s
7927                // forward order), on the direction vector (dcx, dcy, 1.0).
7928                let a_x = dcx;
7929                let a_y = cam.crx * dcy + cam.srx * 1.0;
7930                let a_z = 0.0 - cam.srx * dcy + cam.crx * 1.0;
7931                let dir_x = cam.cry * a_x + cam.sry * a_z;
7932                let dir_y = a_y;
7933                let dir_z = 0.0 - cam.sry * a_x + cam.cry * a_z;
7934                let dlen = (dir_x * dir_x + dir_y * dir_y + dir_z * dir_z)
7935                    .sqrt()
7936                    .max(1e-6);
7937                let (dir_x, dir_y, dir_z) = (dir_x / dlen, dir_y / dlen, dir_z / dlen);
7938                // Origin: the camera's rotation pivot (tx,ty,tz) — i.e. wherever
7939                // the script last put it with set_camera_pos, NOT the "true"
7940                // pinhole eye zdist further back. The pivot is what scripts
7941                // already keep clear of the ground (a ground-collision pull-in
7942                // loop is standard practice for orbit cameras); the true eye
7943                // would need its own separate ground clearance since zdist is
7944                // often large relative to a close-in camera distance, and
7945                // starting a ray underground makes it hit "ground" instantly
7946                // regardless of aim. The zdist offset only matters for the
7947                // near-field parallax, which a click-to-move ray (aimed at
7948                // terrain many units out) doesn't need.
7949                let ox = cam.tx;
7950                let oy = cam.ty;
7951                let oz = cam.tz;
7952                return Ok(Value::List(Rc::new(vec![
7953                    Value::Number(ox as f64),
7954                    Value::Number(oy as f64),
7955                    Value::Number(oz as f64),
7956                    Value::Number(dir_x as f64),
7957                    Value::Number(dir_y as f64),
7958                    Value::Number(dir_z as f64),
7959                ])));
7960            },
7961            #[cfg(target_arch = "wasm32")]
7962            "mouse_ray" => {
7963                let gfx = self.gfx.borrow();
7964                let mx = crate::gfx::wasm_mouse_x();
7965                let my = crate::gfx::wasm_mouse_y();
7966                let cam = &gfx.camera;
7967                let dcx = (mx - cam.cx) / cam.focal;
7968                let dcy = (my - cam.cy) / cam.focal;
7969                let a_x = dcx;
7970                let a_y = cam.crx * dcy + cam.srx * 1.0;
7971                let a_z = 0.0 - cam.srx * dcy + cam.crx * 1.0;
7972                let dir_x = cam.cry * a_x + cam.sry * a_z;
7973                let dir_y = a_y;
7974                let dir_z = 0.0 - cam.sry * a_x + cam.cry * a_z;
7975                let dlen = (dir_x * dir_x + dir_y * dir_y + dir_z * dir_z)
7976                    .sqrt()
7977                    .max(1e-6);
7978                let (dir_x, dir_y, dir_z) = (dir_x / dlen, dir_y / dlen, dir_z / dlen);
7979                let ox = cam.tx;
7980                let oy = cam.ty;
7981                let oz = cam.tz;
7982                return Ok(Value::List(Rc::new(vec![
7983                    Value::Number(ox as f64),
7984                    Value::Number(oy as f64),
7985                    Value::Number(oz as f64),
7986                    Value::Number(dir_x as f64),
7987                    Value::Number(dir_y as f64),
7988                    Value::Number(dir_z as f64),
7989                ])));
7990            },
7991            // draw_poly([x0,y0,x1,y1,…]) — filled 2-D polygon in the current colour,
7992            // honouring the blend mode (additive → translucent glow). Auto-closes.
7993            #[cfg(not(target_arch = "wasm32"))]
7994            "draw_poly"
7995            | "填充多边形"
7996            | "ポリゴン塗り"
7997            | "다각형채우기"
7998            | "เติมรูปหลายเหลี่ยม"
7999            | "رسم_چندضلعی"
8000            | "ارسم_مضلع"
8001            | "צייר_מצולע"
8002            | "کثیر_الاضلاع_کھینچو"
8003            | "dessiner_polygone"
8004            | "polygon_zeichnen"
8005            | "рисовать_полигон" => {
8006                let mut pts: Vec<[f32; 2]> = Vec::new();
8007                if let Some(Value::List(v)) = args.first() {
8008                    let mut i = 0;
8009                    while i + 1 < v.len() {
8010                        let x = self.to_number(&v[i]).unwrap_or(0.0) as f32;
8011                        let y = self.to_number(&v[i + 1]).unwrap_or(0.0) as f32;
8012                        pts.push([x, y]);
8013                        i += 2;
8014                    }
8015                }
8016                if pts.len() >= 3 {
8017                    if pts[0] != pts[pts.len() - 1] {
8018                        let p0 = pts[0];
8019                        pts.push(p0);
8020                    } // close
8021                    let mut gfx = self.gfx.borrow_mut();
8022                    let (w, h, color, add) = (gfx.width, gfx.height, gfx.color, gfx.blend == 1);
8023                    crate::gfx::raster::fill_contours_aa(
8024                        &mut gfx.buffer,
8025                        w,
8026                        h,
8027                        color,
8028                        add,
8029                        std::slice::from_ref(&pts),
8030                    );
8031                }
8032                return Ok(Value::Unit);
8033            },
8034
8035            // ══════════════════════════════════════════════════════════════════
8036            // VECTOR TEXTURE BUILTINS  (src/gfx/vtex.rs)
8037            // All patterns are depth-biased so they appear on top of surfaces.
8038            // Plane defined by: centre (cx,cy,cz) + U tangent + V tangent.
8039            // Last two args always: fr (frame f32), hue (phase offset f32).
8040            // ══════════════════════════════════════════════════════════════════
8041
8042            // vtex_grid(cx,cy,cz, ux,uy,uz, vx,vy,vz, cols,rows, cw,ch, fr,hue)
8043            "vtex_grid"
8044            | "ลายตาราง"
8045            | "纹格"
8046            | "格子模様"
8047            | "격자무늬"
8048            | "الگوی_شبکه"
8049            | "نقش_شبكة"
8050            | "דוגמת_רשת"
8051            | "نقش_جالی"
8052            | "motif_grille"
8053            | "muster_gitter"
8054            | "узор_сетка" => {
8055                let cx = self.arg_num(&args, 0, 0.)? as f32;
8056                let cy = self.arg_num(&args, 1, 0.)? as f32;
8057                let cz = self.arg_num(&args, 2, 0.)? as f32;
8058                let ux = self.arg_num(&args, 3, 1.)? as f32;
8059                let uy = self.arg_num(&args, 4, 0.)? as f32;
8060                let uz = self.arg_num(&args, 5, 0.)? as f32;
8061                let vx = self.arg_num(&args, 6, 0.)? as f32;
8062                let vy = self.arg_num(&args, 7, 0.)? as f32;
8063                let vz = self.arg_num(&args, 8, 1.)? as f32;
8064                let cols = self.arg_num(&args, 9, 10.)? as usize;
8065                let rows = self.arg_num(&args, 10, 10.)? as usize;
8066                let cw = self.arg_num(&args, 11, 1.)? as f32;
8067                let ch = self.arg_num(&args, 12, 1.)? as f32;
8068                let fr = self.arg_num(&args, 13, 0.)? as f32;
8069                let hue = self.arg_num(&args, 14, 0.)? as f32;
8070                let mut gfx = self.gfx.borrow_mut();
8071                let cam = gfx.camera.clone();
8072                crate::gfx::vtex::draw_grid(
8073                    &mut gfx.depth_queue,
8074                    &cam,
8075                    cx,
8076                    cy,
8077                    cz,
8078                    ux,
8079                    uy,
8080                    uz,
8081                    vx,
8082                    vy,
8083                    vz,
8084                    cols,
8085                    rows,
8086                    cw,
8087                    ch,
8088                    fr,
8089                    hue,
8090                );
8091                return Ok(Value::Unit);
8092            },
8093
8094            // vtex_rings(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_rings,n_sides, max_r,twist, fr,hue)
8095            "vtex_rings"
8096            | "ลายวงซ้อน"
8097            | "纹环"
8098            | "同心円"
8099            | "동심원"
8100            | "الگوی_حلقه"
8101            | "نقش_حلقات"
8102            | "דוגמת_טבעות"
8103            | "نقش_حلقے"
8104            | "motif_anneaux"
8105            | "muster_ringe"
8106            | "узор_кольца" => {
8107                let cx = self.arg_num(&args, 0, 0.)? as f32;
8108                let cy = self.arg_num(&args, 1, 0.)? as f32;
8109                let cz = self.arg_num(&args, 2, 0.)? as f32;
8110                let ux = self.arg_num(&args, 3, 1.)? as f32;
8111                let uy = self.arg_num(&args, 4, 0.)? as f32;
8112                let uz = self.arg_num(&args, 5, 0.)? as f32;
8113                let vx = self.arg_num(&args, 6, 0.)? as f32;
8114                let vy = self.arg_num(&args, 7, 0.)? as f32;
8115                let vz = self.arg_num(&args, 8, 1.)? as f32;
8116                let nr = self.arg_num(&args, 9, 6.)? as usize;
8117                let ns = self.arg_num(&args, 10, 6.)? as usize;
8118                let mr = self.arg_num(&args, 11, 3.)? as f32;
8119                let tw = self.arg_num(&args, 12, 0.)? as f32;
8120                let fr = self.arg_num(&args, 13, 0.)? as f32;
8121                let hue = self.arg_num(&args, 14, 0.)? as f32;
8122                let mut gfx = self.gfx.borrow_mut();
8123                let cam = gfx.camera.clone();
8124                crate::gfx::vtex::draw_rings(
8125                    &mut gfx.depth_queue,
8126                    &cam,
8127                    cx,
8128                    cy,
8129                    cz,
8130                    ux,
8131                    uy,
8132                    uz,
8133                    vx,
8134                    vy,
8135                    vz,
8136                    nr,
8137                    ns,
8138                    mr,
8139                    tw,
8140                    fr,
8141                    hue,
8142                );
8143                return Ok(Value::Unit);
8144            },
8145
8146            // vtex_star(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_pts,r_out,r_in, rot_speed, fr,hue)
8147            "vtex_star"
8148            | "ลายดาว"
8149            | "纹星"
8150            | "星模様"
8151            | "별무늬"
8152            | "الگوی_ستاره"
8153            | "نقش_نجمة"
8154            | "דוגמת_כוכב"
8155            | "نقش_ستارہ"
8156            | "motif_étoile"
8157            | "muster_stern"
8158            | "узор_звезда" => {
8159                let cx = self.arg_num(&args, 0, 0.)? as f32;
8160                let cy = self.arg_num(&args, 1, 0.)? as f32;
8161                let cz = self.arg_num(&args, 2, 0.)? as f32;
8162                let ux = self.arg_num(&args, 3, 1.)? as f32;
8163                let uy = self.arg_num(&args, 4, 0.)? as f32;
8164                let uz = self.arg_num(&args, 5, 0.)? as f32;
8165                let vx = self.arg_num(&args, 6, 0.)? as f32;
8166                let vy = self.arg_num(&args, 7, 0.)? as f32;
8167                let vz = self.arg_num(&args, 8, 1.)? as f32;
8168                let np = self.arg_num(&args, 9, 6.)? as usize;
8169                let ro = self.arg_num(&args, 10, 2.)? as f32;
8170                let ri = self.arg_num(&args, 11, 1.)? as f32;
8171                let rs = self.arg_num(&args, 12, 0.01)? as f32;
8172                let fr = self.arg_num(&args, 13, 0.)? as f32;
8173                let hue = self.arg_num(&args, 14, 0.)? as f32;
8174                let mut gfx = self.gfx.borrow_mut();
8175                let cam = gfx.camera.clone();
8176                crate::gfx::vtex::draw_star(
8177                    &mut gfx.depth_queue,
8178                    &cam,
8179                    cx,
8180                    cy,
8181                    cz,
8182                    ux,
8183                    uy,
8184                    uz,
8185                    vx,
8186                    vy,
8187                    vz,
8188                    np,
8189                    ro,
8190                    ri,
8191                    rs,
8192                    fr,
8193                    hue,
8194                );
8195                return Ok(Value::Unit);
8196            },
8197
8198            // vtex_spiral(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_turns,max_r,steps, fr,hue)
8199            "vtex_spiral"
8200            | "ลายเกลียว"
8201            | "纹螺"
8202            | "螺旋"
8203            | "나선"
8204            | "الگوی_مارپیچ"
8205            | "نقش_حلزوني"
8206            | "דוגמת_ספירלה"
8207            | "نقش_سرپیچ"
8208            | "motif_spirale"
8209            | "muster_spirale"
8210            | "узор_спираль" => {
8211                let cx = self.arg_num(&args, 0, 0.)? as f32;
8212                let cy = self.arg_num(&args, 1, 0.)? as f32;
8213                let cz = self.arg_num(&args, 2, 0.)? as f32;
8214                let ux = self.arg_num(&args, 3, 1.)? as f32;
8215                let uy = self.arg_num(&args, 4, 0.)? as f32;
8216                let uz = self.arg_num(&args, 5, 0.)? as f32;
8217                let vx = self.arg_num(&args, 6, 0.)? as f32;
8218                let vy = self.arg_num(&args, 7, 0.)? as f32;
8219                let vz = self.arg_num(&args, 8, 1.)? as f32;
8220                let nt = self.arg_num(&args, 9, 3.)? as f32;
8221                let mr = self.arg_num(&args, 10, 3.)? as f32;
8222                let st = self.arg_num(&args, 11, 120.)? as usize;
8223                let fr = self.arg_num(&args, 12, 0.)? as f32;
8224                let hue = self.arg_num(&args, 13, 0.)? as f32;
8225                let mut gfx = self.gfx.borrow_mut();
8226                let cam = gfx.camera.clone();
8227                crate::gfx::vtex::draw_spiral(
8228                    &mut gfx.depth_queue,
8229                    &cam,
8230                    cx,
8231                    cy,
8232                    cz,
8233                    ux,
8234                    uy,
8235                    uz,
8236                    vx,
8237                    vy,
8238                    vz,
8239                    nt,
8240                    mr,
8241                    st,
8242                    fr,
8243                    hue,
8244                );
8245                return Ok(Value::Unit);
8246            },
8247
8248            // vtex_flower(cx,cy,cz, ux,uy,uz, vx,vy,vz, radius,n_sides, fr,hue)
8249            "vtex_flower"
8250            | "ลายดอก"
8251            | "纹花"
8252            | "花模様"
8253            | "꽃무늬"
8254            | "الگوی_گل"
8255            | "نقش_زهرة"
8256            | "דוגמת_פרח"
8257            | "نقش_پھول"
8258            | "motif_fleur"
8259            | "muster_blume"
8260            | "узор_цветок" => {
8261                let cx = self.arg_num(&args, 0, 0.)? as f32;
8262                let cy = self.arg_num(&args, 1, 0.)? as f32;
8263                let cz = self.arg_num(&args, 2, 0.)? as f32;
8264                let ux = self.arg_num(&args, 3, 1.)? as f32;
8265                let uy = self.arg_num(&args, 4, 0.)? as f32;
8266                let uz = self.arg_num(&args, 5, 0.)? as f32;
8267                let vx = self.arg_num(&args, 6, 0.)? as f32;
8268                let vy = self.arg_num(&args, 7, 0.)? as f32;
8269                let vz = self.arg_num(&args, 8, 1.)? as f32;
8270                let r = self.arg_num(&args, 9, 1.)? as f32;
8271                let ns = self.arg_num(&args, 10, 24.)? as usize;
8272                let fr = self.arg_num(&args, 11, 0.)? as f32;
8273                let hue = self.arg_num(&args, 12, 0.)? as f32;
8274                let mut gfx = self.gfx.borrow_mut();
8275                let cam = gfx.camera.clone();
8276                crate::gfx::vtex::draw_flower(
8277                    &mut gfx.depth_queue,
8278                    &cam,
8279                    cx,
8280                    cy,
8281                    cz,
8282                    ux,
8283                    uy,
8284                    uz,
8285                    vx,
8286                    vy,
8287                    vz,
8288                    r,
8289                    ns,
8290                    fr,
8291                    hue,
8292                );
8293                return Ok(Value::Unit);
8294            },
8295
8296            // vtex_letter_rain(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_cols,n_vis, col_w,row_h, speed, fr,hue)
8297            "vtex_letter_rain"
8298            | "ลายอักษรไหล"
8299            | "纹字雨"
8300            | "文字雨"
8301            | "글자비"
8302            | "الگوی_باران_حروف"
8303            | "نقش_مطر_الحروف"
8304            | "דוגמת_גשם_אותיות"
8305            | "نقش_حروف_بارش"
8306            | "motif_pluie_lettres"
8307            | "muster_buchstabenregen"
8308            | "узор_дождь_букв" => {
8309                let cx = self.arg_num(&args, 0, 0.)? as f32;
8310                let cy = self.arg_num(&args, 1, 0.)? as f32;
8311                let cz = self.arg_num(&args, 2, 0.)? as f32;
8312                let ux = self.arg_num(&args, 3, 1.)? as f32;
8313                let uy = self.arg_num(&args, 4, 0.)? as f32;
8314                let uz = self.arg_num(&args, 5, 0.)? as f32;
8315                let vx = self.arg_num(&args, 6, 0.)? as f32;
8316                let vy = self.arg_num(&args, 7, 0.)? as f32;
8317                let vz = self.arg_num(&args, 8, 1.)? as f32;
8318                let nc = self.arg_num(&args, 9, 16.)? as usize;
8319                let nv = self.arg_num(&args, 10, 14.)? as usize;
8320                let cw = self.arg_num(&args, 11, 0.65)? as f32;
8321                let rh = self.arg_num(&args, 12, 0.60)? as f32;
8322                let sp = self.arg_num(&args, 13, 0.025)? as f32;
8323                let fr = self.arg_num(&args, 14, 0.)? as f32;
8324                let hue = self.arg_num(&args, 15, 0.)? as f32;
8325                let mut gfx = self.gfx.borrow_mut();
8326                let cam = gfx.camera.clone();
8327                crate::gfx::vtex::draw_letter_rain(
8328                    &mut gfx.depth_queue,
8329                    &cam,
8330                    cx,
8331                    cy,
8332                    cz,
8333                    ux,
8334                    uy,
8335                    uz,
8336                    vx,
8337                    vy,
8338                    vz,
8339                    nc,
8340                    nv,
8341                    cw,
8342                    rh,
8343                    sp,
8344                    fr,
8345                    hue,
8346                );
8347                return Ok(Value::Unit);
8348            },
8349
8350            // vtex_hyperbolic_uv(cx,cy,cz, ux,uy,uz, vx,vy,vz, max_r,n_circles,n_rays, fr,hue)
8351            "vtex_hyperbolic_uv"
8352            | "ลายไฮเพอร์โบลิก"
8353            | "纹曲面"
8354            | "双曲線"
8355            | "쌍곡선"
8356            | "الگوی_هذلولی"
8357            | "نقش_زائدي"
8358            | "דוגמת_היפרבולית"
8359            | "نقش_ہائپربولک"
8360            | "motif_uv_hyperbolique"
8361            | "muster_hyperbolische_uv"
8362            | "узор_гиперболический_uv" => {
8363                let cx = self.arg_num(&args, 0, 0.)? as f32;
8364                let cy = self.arg_num(&args, 1, 0.)? as f32;
8365                let cz = self.arg_num(&args, 2, 0.)? as f32;
8366                let ux = self.arg_num(&args, 3, 1.)? as f32;
8367                let uy = self.arg_num(&args, 4, 0.)? as f32;
8368                let uz = self.arg_num(&args, 5, 0.)? as f32;
8369                let vx = self.arg_num(&args, 6, 0.)? as f32;
8370                let vy = self.arg_num(&args, 7, 0.)? as f32;
8371                let vz = self.arg_num(&args, 8, 1.)? as f32;
8372                let mr = self.arg_num(&args, 9, 5.)? as f32;
8373                let nc = self.arg_num(&args, 10, 12.)? as usize;
8374                let nr = self.arg_num(&args, 11, 18.)? as usize;
8375                let fr = self.arg_num(&args, 12, 0.)? as f32;
8376                let hue = self.arg_num(&args, 13, 0.)? as f32;
8377                let mut gfx = self.gfx.borrow_mut();
8378                let cam = gfx.camera.clone();
8379                crate::gfx::vtex::draw_hyperbolic_uv(
8380                    &mut gfx.depth_queue,
8381                    &cam,
8382                    cx,
8383                    cy,
8384                    cz,
8385                    ux,
8386                    uy,
8387                    uz,
8388                    vx,
8389                    vy,
8390                    vz,
8391                    mr,
8392                    nc,
8393                    nr,
8394                    fr,
8395                    hue,
8396                );
8397                return Ok(Value::Unit);
8398            },
8399
8400            // vtex_halftone(cx,cy,cz, ux,uy,uz, vx,vy,vz, cols,rows, cell_w,cell_h, density, fr,hue)
8401            "vtex_halftone"
8402            | "ลายจุด"
8403            | "纹半调"
8404            | "網点模様"
8405            | "망점"
8406            | "الگوی_نیم‌تن"
8407            | "نقش_نصفي"
8408            | "דוגמת_חצי_גוון"
8409            | "نقش_ہاف_ٹون"
8410            | "motif_demi_ton"
8411            | "muster_halbton"
8412            | "узор_растр" => {
8413                let cx = self.arg_num(&args, 0, 0.)? as f32;
8414                let cy = self.arg_num(&args, 1, 0.)? as f32;
8415                let cz = self.arg_num(&args, 2, 0.)? as f32;
8416                let ux = self.arg_num(&args, 3, 1.)? as f32;
8417                let uy = self.arg_num(&args, 4, 0.)? as f32;
8418                let uz = self.arg_num(&args, 5, 0.)? as f32;
8419                let vx = self.arg_num(&args, 6, 0.)? as f32;
8420                let vy = self.arg_num(&args, 7, 0.)? as f32;
8421                let vz = self.arg_num(&args, 8, 1.)? as f32;
8422                let cols = self.arg_num(&args, 9, 16.)? as usize;
8423                let rows = self.arg_num(&args, 10, 12.)? as usize;
8424                let cw = self.arg_num(&args, 11, 0.5)? as f32;
8425                let ch = self.arg_num(&args, 12, 0.5)? as f32;
8426                let dens = self.arg_num(&args, 13, 0.4)? as f32;
8427                let fr = self.arg_num(&args, 14, 0.)? as f32;
8428                let hue = self.arg_num(&args, 15, 0.)? as f32;
8429                let mut gfx = self.gfx.borrow_mut();
8430                let cam = gfx.camera.clone();
8431                crate::gfx::vtex::draw_halftone(
8432                    &mut gfx.depth_queue,
8433                    &cam,
8434                    cx,
8435                    cy,
8436                    cz,
8437                    ux,
8438                    uy,
8439                    uz,
8440                    vx,
8441                    vy,
8442                    vz,
8443                    cols,
8444                    rows,
8445                    cw,
8446                    ch,
8447                    dens,
8448                    fr,
8449                    hue,
8450                );
8451                return Ok(Value::Unit);
8452            },
8453
8454            // vtex_tessellated(cx,cy,cz, ux,uy,uz, vx,vy,vz, cols,rows, cell, amplitude,freq, fr,hue)
8455            "vtex_tessellated"
8456            | "ลายตาข่าย"
8457            | "纹镶嵌"
8458            | "網目模様"
8459            | "격자망"
8460            | "الگوی_کاشی‌کاری"
8461            | "نقش_مرصوف_متكرر"
8462            | "דוגמת_ריצוף_חוזר"
8463            | "نقش_ٹائلنگ"
8464            | "motif_tesselle"
8465            | "muster_tessellation"
8466            | "узор_мозаика" => {
8467                let cx = self.arg_num(&args, 0, 0.)? as f32;
8468                let cy = self.arg_num(&args, 1, 0.)? as f32;
8469                let cz = self.arg_num(&args, 2, 0.)? as f32;
8470                let ux = self.arg_num(&args, 3, 1.)? as f32;
8471                let uy = self.arg_num(&args, 4, 0.)? as f32;
8472                let uz = self.arg_num(&args, 5, 0.)? as f32;
8473                let vx = self.arg_num(&args, 6, 0.)? as f32;
8474                let vy = self.arg_num(&args, 7, 0.)? as f32;
8475                let vz = self.arg_num(&args, 8, 1.)? as f32;
8476                let cols = self.arg_num(&args, 9, 14.)? as usize;
8477                let rows = self.arg_num(&args, 10, 10.)? as usize;
8478                let cell = self.arg_num(&args, 11, 0.6)? as f32;
8479                let amp = self.arg_num(&args, 12, 0.25)? as f32;
8480                let freq = self.arg_num(&args, 13, 4.)? as f32;
8481                let fr = self.arg_num(&args, 14, 0.)? as f32;
8482                let hue = self.arg_num(&args, 15, 0.)? as f32;
8483                let mut gfx = self.gfx.borrow_mut();
8484                let cam = gfx.camera.clone();
8485                crate::gfx::vtex::draw_tessellated(
8486                    &mut gfx.depth_queue,
8487                    &cam,
8488                    cx,
8489                    cy,
8490                    cz,
8491                    ux,
8492                    uy,
8493                    uz,
8494                    vx,
8495                    vy,
8496                    vz,
8497                    cols,
8498                    rows,
8499                    cell,
8500                    amp,
8501                    freq,
8502                    fr,
8503                    hue,
8504                );
8505                return Ok(Value::Unit);
8506            },
8507
8508            // vtex_lotus(cx,cy,cz, ux,uy,uz, vx,vy,vz, r_inner,r_outer,n_petals, fr,hue)
8509            "vtex_lotus"
8510            | "ลายดอกบัว"
8511            | "纹莲"
8512            | "蓮模様"
8513            | "연꽃무늬"
8514            | "الگوی_لوتوس"
8515            | "نقش_لوتس"
8516            | "דוגמת_לוטוס"
8517            | "نقش_کنول"
8518            | "motif_lotus"
8519            | "muster_lotus"
8520            | "узор_лотос" => {
8521                let cx = self.arg_num(&args, 0, 0.)? as f32;
8522                let cy = self.arg_num(&args, 1, 0.)? as f32;
8523                let cz = self.arg_num(&args, 2, 0.)? as f32;
8524                let ux = self.arg_num(&args, 3, 1.)? as f32;
8525                let uy = self.arg_num(&args, 4, 0.)? as f32;
8526                let uz = self.arg_num(&args, 5, 0.)? as f32;
8527                let vx = self.arg_num(&args, 6, 0.)? as f32;
8528                let vy = self.arg_num(&args, 7, 0.)? as f32;
8529                let vz = self.arg_num(&args, 8, 1.)? as f32;
8530                let ri = self.arg_num(&args, 9, 1.)? as f32;
8531                let ro = self.arg_num(&args, 10, 2.)? as f32;
8532                let np = self.arg_num(&args, 11, 12.)? as usize;
8533                let fr = self.arg_num(&args, 12, 0.)? as f32;
8534                let hue = self.arg_num(&args, 13, 0.)? as f32;
8535                let mut gfx = self.gfx.borrow_mut();
8536                let cam = gfx.camera.clone();
8537                crate::gfx::vtex::draw_lotus(
8538                    &mut gfx.depth_queue,
8539                    &cam,
8540                    cx,
8541                    cy,
8542                    cz,
8543                    ux,
8544                    uy,
8545                    uz,
8546                    vx,
8547                    vy,
8548                    vz,
8549                    ri,
8550                    ro,
8551                    np,
8552                    fr,
8553                    hue,
8554                );
8555                return Ok(Value::Unit);
8556            },
8557
8558            // vtex_chakra(cx,cy,cz, ux,uy,uz, vx,vy,vz, r,n_spokes, fr,hue)
8559            "vtex_chakra"
8560            | "ลายจักร"
8561            | "纹轮"
8562            | "輪模様"
8563            | "바퀴무늬"
8564            | "الگوی_چاکرا"
8565            | "نقش_تشاكرا"
8566            | "דוגמת_צ'אקרה"
8567            | "نقش_چکر"
8568            | "motif_chakra"
8569            | "muster_chakra"
8570            | "узор_чакра" => {
8571                let cx = self.arg_num(&args, 0, 0.)? as f32;
8572                let cy = self.arg_num(&args, 1, 0.)? as f32;
8573                let cz = self.arg_num(&args, 2, 0.)? as f32;
8574                let ux = self.arg_num(&args, 3, 1.)? as f32;
8575                let uy = self.arg_num(&args, 4, 0.)? as f32;
8576                let uz = self.arg_num(&args, 5, 0.)? as f32;
8577                let vx = self.arg_num(&args, 6, 0.)? as f32;
8578                let vy = self.arg_num(&args, 7, 0.)? as f32;
8579                let vz = self.arg_num(&args, 8, 1.)? as f32;
8580                let r = self.arg_num(&args, 9, 2.)? as f32;
8581                let ns = self.arg_num(&args, 10, 8.)? as usize;
8582                let fr = self.arg_num(&args, 11, 0.)? as f32;
8583                let hue = self.arg_num(&args, 12, 0.)? as f32;
8584                let mut gfx = self.gfx.borrow_mut();
8585                let cam = gfx.camera.clone();
8586                crate::gfx::vtex::draw_chakra(
8587                    &mut gfx.depth_queue,
8588                    &cam,
8589                    cx,
8590                    cy,
8591                    cz,
8592                    ux,
8593                    uy,
8594                    uz,
8595                    vx,
8596                    vy,
8597                    vz,
8598                    r,
8599                    ns,
8600                    fr,
8601                    hue,
8602                );
8603                return Ok(Value::Unit);
8604            },
8605
8606            // vtex_yantra(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_layers,max_r, fr,hue)
8607            "vtex_yantra"
8608            | "ลายยันต์"
8609            | "纹咒"
8610            | "護符模様"
8611            | "부적무늬"
8612            | "الگوی_یانترا"
8613            | "نقش_يانترا"
8614            | "דוגמת_יאנטרה"
8615            | "نقش_ینترا"
8616            | "motif_yantra"
8617            | "muster_yantra"
8618            | "узор_янтра" => {
8619                let cx = self.arg_num(&args, 0, 0.)? as f32;
8620                let cy = self.arg_num(&args, 1, 0.)? as f32;
8621                let cz = self.arg_num(&args, 2, 0.)? as f32;
8622                let ux = self.arg_num(&args, 3, 1.)? as f32;
8623                let uy = self.arg_num(&args, 4, 0.)? as f32;
8624                let uz = self.arg_num(&args, 5, 0.)? as f32;
8625                let vx = self.arg_num(&args, 6, 0.)? as f32;
8626                let vy = self.arg_num(&args, 7, 0.)? as f32;
8627                let vz = self.arg_num(&args, 8, 1.)? as f32;
8628                let nl = self.arg_num(&args, 9, 4.)? as usize;
8629                let mr = self.arg_num(&args, 10, 3.)? as f32;
8630                let fr = self.arg_num(&args, 11, 0.)? as f32;
8631                let hue = self.arg_num(&args, 12, 0.)? as f32;
8632                let mut gfx = self.gfx.borrow_mut();
8633                let cam = gfx.camera.clone();
8634                crate::gfx::vtex::draw_yantra(
8635                    &mut gfx.depth_queue,
8636                    &cam,
8637                    cx,
8638                    cy,
8639                    cz,
8640                    ux,
8641                    uy,
8642                    uz,
8643                    vx,
8644                    vy,
8645                    vz,
8646                    nl,
8647                    mr,
8648                    fr,
8649                    hue,
8650                );
8651                return Ok(Value::Unit);
8652            },
8653
8654            // vtex_spiked_cog(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_teeth,r_body,r_spike,r_hub,n_spokes, fr,hue)
8655            "vtex_spiked_cog"
8656            | "ฟันเฟืองหนาม"
8657            | "纹棘轮"
8658            | "歯車模様"
8659            | "톱니바퀴"
8660            | "الگوی_چرخ‌دنده_خاردار"
8661            | "نقش_ترس_شائك"
8662            | "דוגמת_גלגל_קוצני"
8663            | "نقش_خاردار_گیئر"
8664            | "motif_engrenage_pointes"
8665            | "muster_stachelzahnrad"
8666            | "узор_шестерня_шипы" => {
8667                let cx = self.arg_num(&args, 0, 0.)? as f32;
8668                let cy = self.arg_num(&args, 1, 0.)? as f32;
8669                let cz = self.arg_num(&args, 2, 0.)? as f32;
8670                let ux = self.arg_num(&args, 3, 1.)? as f32;
8671                let uy = self.arg_num(&args, 4, 0.)? as f32;
8672                let uz = self.arg_num(&args, 5, 0.)? as f32;
8673                let vx = self.arg_num(&args, 6, 0.)? as f32;
8674                let vy = self.arg_num(&args, 7, 0.)? as f32;
8675                let vz = self.arg_num(&args, 8, 1.)? as f32;
8676                let nt = self.arg_num(&args, 9, 12.)? as usize;
8677                let rb = self.arg_num(&args, 10, 1.)? as f32;
8678                let rs = self.arg_num(&args, 11, 1.3)? as f32;
8679                let rh = self.arg_num(&args, 12, 0.2)? as f32;
8680                let ns = self.arg_num(&args, 13, 6.)? as usize;
8681                let fr = self.arg_num(&args, 14, 0.)? as f32;
8682                let hue = self.arg_num(&args, 15, 0.)? as f32;
8683                let mut gfx = self.gfx.borrow_mut();
8684                let cam = gfx.camera.clone();
8685                crate::gfx::vtex::draw_spiked_cog(
8686                    &mut gfx.depth_queue,
8687                    &cam,
8688                    cx,
8689                    cy,
8690                    cz,
8691                    ux,
8692                    uy,
8693                    uz,
8694                    vx,
8695                    vy,
8696                    vz,
8697                    nt,
8698                    rb,
8699                    rs,
8700                    rh,
8701                    ns,
8702                    fr,
8703                    hue,
8704                );
8705                return Ok(Value::Unit);
8706            },
8707
8708            // vtex_torii(cx,cy,cz, ux,uy,uz, vx,vy,vz, width,height, fr,hue)
8709            "vtex_torii"
8710            | "ประตูโทริอิ"
8711            | "纹鸟居"
8712            | "鳥居"
8713            | "도리이"
8714            | "الگوی_توری_ژاپنی"
8715            | "نقش_توري"
8716            | "דוגמת_טוריי"
8717            | "نقش_توری_گیٹ"
8718            | "motif_torii"
8719            | "muster_torii"
8720            | "узор_тории" => {
8721                let cx = self.arg_num(&args, 0, 0.)? as f32;
8722                let cy = self.arg_num(&args, 1, 0.)? as f32;
8723                let cz = self.arg_num(&args, 2, 0.)? as f32;
8724                let ux = self.arg_num(&args, 3, 1.)? as f32;
8725                let uy = self.arg_num(&args, 4, 0.)? as f32;
8726                let uz = self.arg_num(&args, 5, 0.)? as f32;
8727                let vx = self.arg_num(&args, 6, 0.)? as f32;
8728                let vy = self.arg_num(&args, 7, 0.)? as f32;
8729                let vz = self.arg_num(&args, 8, 1.)? as f32;
8730                let w = self.arg_num(&args, 9, 4.)? as f32;
8731                let h = self.arg_num(&args, 10, 5.)? as f32;
8732                let fr = self.arg_num(&args, 11, 0.)? as f32;
8733                let hue = self.arg_num(&args, 12, 0.)? as f32;
8734                let mut gfx = self.gfx.borrow_mut();
8735                let cam = gfx.camera.clone();
8736                crate::gfx::vtex::draw_torii(
8737                    &mut gfx.depth_queue,
8738                    &cam,
8739                    cx,
8740                    cy,
8741                    cz,
8742                    ux,
8743                    uy,
8744                    uz,
8745                    vx,
8746                    vy,
8747                    vz,
8748                    w,
8749                    h,
8750                    fr,
8751                    hue,
8752                );
8753                return Ok(Value::Unit);
8754            },
8755
8756            // vtex_pagoda(cx,cy,cz, ux,uy,uz, vx,vy,vz, n_tiers,base_w,tier_h,taper,eave_out, fr,hue)
8757            "vtex_pagoda"
8758            | "เจดีย์"
8759            | "纹塔"
8760            | "塔"
8761            | "탑"
8762            | "الگوی_پاگودا"
8763            | "نقش_باغودا"
8764            | "דוגמת_פגודה"
8765            | "نقش_پگوڈا"
8766            | "motif_pagode"
8767            | "muster_pagode"
8768            | "узор_пагода" => {
8769                let cx = self.arg_num(&args, 0, 0.)? as f32;
8770                let cy = self.arg_num(&args, 1, 0.)? as f32;
8771                let cz = self.arg_num(&args, 2, 0.)? as f32;
8772                let ux = self.arg_num(&args, 3, 1.)? as f32;
8773                let uy = self.arg_num(&args, 4, 0.)? as f32;
8774                let uz = self.arg_num(&args, 5, 0.)? as f32;
8775                let vx = self.arg_num(&args, 6, 0.)? as f32;
8776                let vy = self.arg_num(&args, 7, 0.)? as f32;
8777                let vz = self.arg_num(&args, 8, 1.)? as f32;
8778                let nt = self.arg_num(&args, 9, 5.)? as usize;
8779                let bw = self.arg_num(&args, 10, 2.)? as f32;
8780                let th = self.arg_num(&args, 11, 1.)? as f32;
8781                let tp = self.arg_num(&args, 12, 0.72)? as f32;
8782                let eo = self.arg_num(&args, 13, 0.28)? as f32;
8783                let fr = self.arg_num(&args, 14, 0.)? as f32;
8784                let hue = self.arg_num(&args, 15, 0.)? as f32;
8785                let mut gfx = self.gfx.borrow_mut();
8786                let cam = gfx.camera.clone();
8787                crate::gfx::vtex::draw_pagoda(
8788                    &mut gfx.depth_queue,
8789                    &cam,
8790                    cx,
8791                    cy,
8792                    cz,
8793                    ux,
8794                    uy,
8795                    uz,
8796                    vx,
8797                    vy,
8798                    vz,
8799                    nt,
8800                    bw,
8801                    th,
8802                    tp,
8803                    eo,
8804                    fr,
8805                    hue,
8806                );
8807                return Ok(Value::Unit);
8808            },
8809
8810            // ══════════════════════════════════════════════════════════════════
8811            // AUDIO BUILTINS
8812            // ══════════════════════════════════════════════════════════════════
8813
8814            // audio_tone(idx, x, y, z, w, freq, amp, lfo_rate, lfo_depth)
8815            #[cfg(not(target_arch = "wasm32"))]
8816            "audio_tone"
8817            | "เสียงโทน"
8818            | "音调"
8819            | "音調"
8820            | "음조"
8821            | "空间音"
8822            | "空間音"
8823            | "공간음"
8824            | "تن_صدا"
8825            | "نغمة"
8826            | "צליל"
8827            | "آواز_کا_سر"
8828            | "tonalité_audio"
8829            | "audioton"
8830            | "звук_тон" => {
8831                let idx = self.arg_num(&args, 0, 0.0)? as usize;
8832                let x = self.arg_num(&args, 1, 0.0)? as f32;
8833                let y = self.arg_num(&args, 2, 0.0)? as f32;
8834                let z = self.arg_num(&args, 3, 0.0)? as f32;
8835                let w = self.arg_num(&args, 4, 1.0)? as f32;
8836                let freq = self.arg_num(&args, 5, 220.0)? as f32;
8837                let amp = self.arg_num(&args, 6, 0.15)? as f32;
8838                let lfo_rate = self.arg_num(&args, 7, 0.5)? as f32;
8839                let lfo_depth = self.arg_num(&args, 8, 0.02)? as f32;
8840                if let Some(audio) = &self.audio {
8841                    audio.set_tone(
8842                        idx,
8843                        ToneParams { x, y, z, w, freq, amp, lfo_rate, lfo_depth },
8844                    );
8845                }
8846                return Ok(Value::Unit);
8847            },
8848
8849            #[cfg(not(target_arch = "wasm32"))]
8850            "audio_listener"
8851            | "ผู้ฟัง"
8852            | "音频监听"
8853            | "音声リスナー"
8854            | "오디오리스너"
8855            | "شنونده_صدا"
8856            | "مستمع_الصوت"
8857            | "מאזין_קול"
8858            | "آواز_سننے_والا"
8859            | "auditeur_audio"
8860            | "audiohörer"
8861            | "звук_слушатель" => {
8862                let cry = self.arg_num(&args, 0, 1.0)? as f32;
8863                let sry = self.arg_num(&args, 1, 0.0)? as f32;
8864                let crx = self.arg_num(&args, 2, 1.0)? as f32;
8865                let srx = self.arg_num(&args, 3, 0.0)? as f32;
8866                if let Some(audio) = &self.audio {
8867                    audio.set_listener(cry, sry, crx, srx);
8868                }
8869                return Ok(Value::Unit);
8870            },
8871
8872            #[cfg(not(target_arch = "wasm32"))]
8873            "audio_bgm"
8874            | "เพลงพื้นหลัง"
8875            | "เพลงประกอบ"
8876            | "背景乐"
8877            | "BGM"
8878            | "배경음악"
8879            | "موسیقی_پس‌زمینه"
8880            | "موسيقى_خلفية"
8881            | "מוזיקת_רקע"
8882            | "پس_منظر_موسیقی"
8883            | "musique_fond"
8884            | "hintergrundmusik"
8885            | "фоновая_музыка" => {
8886                let path = match args.first() {
8887                    Some(Value::Str(s)) => s.clone(),
8888                    _ => return Ok(Value::Unit),
8889                };
8890                let vol = self.arg_num(&args, 1, 0.5)? as f32;
8891                if let Some(audio) = &self.audio {
8892                    audio.load_bgm(&path, vol);
8893                }
8894                return Ok(Value::Unit);
8895            },
8896
8897            #[cfg(not(target_arch = "wasm32"))]
8898            "audio_bgm_volume"
8899            | "ระดับเสียงพื้นหลัง"
8900            | "ระดับเพลงประกอบ"
8901            | "背景乐音量"
8902            | "BGM音量"
8903            | "배경음악음량"
8904            | "بلندی_موسیقی_پس‌زمینه"
8905            | "مستوى_موسيقى_الخلفية"
8906            | "עוצמת_מוזיקת_רקע"
8907            | "پس_منظر_موسیقی_شدت"
8908            | "volume_musique_fond"
8909            | "hintergrundmusiklautstärke"
8910            | "громкость_фоновой_музыки" => {
8911                let vol = self.arg_num(&args, 0, 0.5)? as f32;
8912                if let Some(audio) = &self.audio {
8913                    audio.set_bgm_volume(vol);
8914                }
8915                return Ok(Value::Unit);
8916            },
8917
8918            #[cfg(not(target_arch = "wasm32"))]
8919            "audio_volume"
8920            | "ระดับเสียง"
8921            | "音量"
8922            | "음량"
8923            | "بلندی_صدا"
8924            | "مستوى_الصوت"
8925            | "עוצמת_קול"
8926            | "آواز_کی_شدت"
8927            | "volume_audio"
8928            | "audiolautstärke"
8929            | "звук_громкость" => {
8930                let vol = self.arg_num(&args, 0, 0.7)? as f32;
8931                if let Some(audio) = &self.audio {
8932                    audio.set_master_volume(vol);
8933                }
8934                return Ok(Value::Unit);
8935            },
8936
8937            // WASM audio builtins — delegate to Web Audio API
8938            #[cfg(target_arch = "wasm32")]
8939            "audio_tone"
8940            | "เสียงโทน"
8941            | "音调"
8942            | "音調"
8943            | "음조"
8944            | "空间音"
8945            | "空間音"
8946            | "공간음"
8947            | "تن_صدا"
8948            | "نغمة"
8949            | "צליל"
8950            | "آواز_کا_سر"
8951            | "tonalité_audio"
8952            | "audioton"
8953            | "звук_тон" => {
8954                let idx = self.arg_num(&args, 0, 0.0)? as usize;
8955                let x = self.arg_num(&args, 1, 0.0)? as f32;
8956                let y = self.arg_num(&args, 2, 0.0)? as f32;
8957                let z = self.arg_num(&args, 3, 0.0)? as f32;
8958                let w = self.arg_num(&args, 4, 1.0)? as f32;
8959                let freq = self.arg_num(&args, 5, 220.0)? as f32;
8960                let amp = self.arg_num(&args, 6, 0.15)? as f32;
8961                let lfo_rate = self.arg_num(&args, 7, 0.5)? as f32;
8962                let lfo_depth = self.arg_num(&args, 8, 0.02)? as f32;
8963                crate::gfx::audio_web::set_tone(idx, x, y, z, w, freq, amp, lfo_rate, lfo_depth);
8964                return Ok(Value::Unit);
8965            },
8966
8967            #[cfg(target_arch = "wasm32")]
8968            "audio_listener"
8969            | "ผู้ฟัง"
8970            | "音频监听"
8971            | "音声リスナー"
8972            | "오디오리스너"
8973            | "شنونده_صدا"
8974            | "مستمع_الصوت"
8975            | "מאזין_קול"
8976            | "آواز_سننے_والا"
8977            | "auditeur_audio"
8978            | "audiohörer"
8979            | "звук_слушатель" => {
8980                let cry = self.arg_num(&args, 0, 1.0)? as f32;
8981                let sry = self.arg_num(&args, 1, 0.0)? as f32;
8982                let crx = self.arg_num(&args, 2, 1.0)? as f32;
8983                let srx = self.arg_num(&args, 3, 0.0)? as f32;
8984                crate::gfx::audio_web::set_listener(cry, sry, crx, srx);
8985                return Ok(Value::Unit);
8986            },
8987
8988            #[cfg(target_arch = "wasm32")]
8989            "audio_bgm"
8990            | "เพลงพื้นหลัง"
8991            | "เพลงประกอบ"
8992            | "背景乐"
8993            | "BGM"
8994            | "배경음악"
8995            | "موسیقی_پس‌زمینه"
8996            | "موسيقى_خلفية"
8997            | "מוזיקת_רקע"
8998            | "پس_منظر_موسیقی"
8999            | "musique_fond"
9000            | "hintergrundmusik"
9001            | "фоновая_музыка" => {
9002                let path = self.arg_str(&args, 0, "");
9003                let vol = self.arg_num(&args, 1, 0.5)? as f32;
9004                crate::gfx::audio_web::load_bgm(&path, vol);
9005                return Ok(Value::Unit);
9006            },
9007
9008            #[cfg(target_arch = "wasm32")]
9009            "audio_bgm_volume"
9010            | "ระดับเสียงพื้นหลัง"
9011            | "ระดับเพลงประกอบ"
9012            | "背景乐音量"
9013            | "BGM音量"
9014            | "배경음악음량"
9015            | "بلندی_موسیقی_پس‌زمینه"
9016            | "مستوى_موسيقى_الخلفية"
9017            | "עוצמת_מוזיקת_רקע"
9018            | "پس_منظر_موسیقی_شدت"
9019            | "volume_musique_fond"
9020            | "hintergrundmusiklautstärke"
9021            | "громкость_фоновой_музыки" => {
9022                let vol = self.arg_num(&args, 0, 0.5)? as f32;
9023                crate::gfx::audio_web::set_bgm_volume(vol);
9024                return Ok(Value::Unit);
9025            },
9026
9027            #[cfg(target_arch = "wasm32")]
9028            "audio_volume"
9029            | "ระดับเสียง"
9030            | "音量"
9031            | "음량"
9032            | "بلندی_صدا"
9033            | "مستوى_الصوت"
9034            | "עוצמת_קול"
9035            | "آواز_کی_شدت"
9036            | "volume_audio"
9037            | "audiolautstärke"
9038            | "звук_громкость" => {
9039                let vol = self.arg_num(&args, 0, 0.7)? as f32;
9040                crate::gfx::audio_web::set_master_volume(vol);
9041                return Ok(Value::Unit);
9042            },
9043
9044            // ── WASM sample load / play / stop / FX (Web Audio pool) ─────────
9045            #[cfg(target_arch = "wasm32")]
9046            "audio_sample_load"
9047            | "载入采样"
9048            | "サンプル読込"
9049            | "샘플로드"
9050            | "โหลดตัวอย่างเสียง"
9051            | "بارگذاری_نمونه_صدا"
9052            | "تحميل_عينة_صوتية"
9053            | "טעינת_דגימת_קול"
9054            | "آواز_نمونہ_لوڈ"
9055            | "charger_échantillon"
9056            | "sample_laden"
9057            | "загрузить_семпл" => {
9058                let path = self.arg_str(&args, 0, "");
9059                let resolved = self.wasm_resolve_source_path(&path);
9060                match wasm_fetch_bytes(&resolved)
9061                    .and_then(|bytes| ling_music::from_bytes(&bytes).map_err(|e| e.to_string()))
9062                {
9063                    Ok(t) => {
9064                        let id = crate::gfx::audio_web::add_sample(&t.stereo, t.channels, t.rate);
9065                        return Ok(Value::Number(id as f64));
9066                    },
9067                    Err(e) => {
9068                        eprintln!("audio_sample_load failed ({path}): {e}");
9069                        return Ok(Value::Number(-1.0));
9070                    },
9071                }
9072            },
9073            #[cfg(target_arch = "wasm32")]
9074            "audio_sample_play"
9075            | "播放采样"
9076            | "サンプル再生"
9077            | "샘플재생"
9078            | "เล่นตัวอย่างเสียง"
9079            | "پخش_نمونه_صدا"
9080            | "تشغيل_عينة_صوتية"
9081            | "נגינת_דגימת_קול"
9082            | "آواز_نمونہ_چلاؤ"
9083            | "jouer_échantillon"
9084            | "sample_abspielen"
9085            | "играть_семпл" => {
9086                let id = self.arg_num(&args, 0, 0.0)? as usize;
9087                let x = self.arg_num(&args, 1, 0.0)? as f32;
9088                let y = self.arg_num(&args, 2, 0.0)? as f32;
9089                let z = self.arg_num(&args, 3, 0.0)? as f32;
9090                // arg 4 is w (4th spatial dim) — ignored for 3-D panner
9091                let vol = self.arg_num(&args, 5, 1.0)? as f32;
9092                let looping = self.arg_num(&args, 6, 0.0)? > 0.5;
9093                crate::gfx::audio_web::play_sample(id, x, y, z, vol, looping);
9094                return Ok(Value::Number(0.0));
9095            },
9096            #[cfg(target_arch = "wasm32")]
9097            "audio_sample_stop"
9098            | "停止采样"
9099            | "サンプル停止"
9100            | "샘플정지"
9101            | "หยุดตัวอย่างเสียง"
9102            | "audio_fx_reverb"
9103            | "混响"
9104            | "リバーブ"
9105            | "리버브"
9106            | "เสียงก้อง"
9107            | "audio_fx_delay"
9108            | "回声"
9109            | "ディレイ効果"
9110            | "딜레이"
9111            | "เสียงสะท้อน"
9112            | "audio_fx_lowpass"
9113            | "低通滤波"
9114            | "ローパス"
9115            | "저역통과"
9116            | "กรองความถี่ต่ำ"
9117            | "توقف_نمونه_صدا"
9118            | "إيقاف_عينة_صوتية"
9119            | "עצירת_דגימת_קול"
9120            | "آواز_نمونہ_روکو"
9121            | "arrêter_échantillon"
9122            | "sample_stoppen"
9123            | "остановить_семпл" => {
9124                return Ok(Value::Unit);
9125            },
9126
9127            // ── รอหน้าต่าง() — block until window closed / Escape ──
9128            "รอหน้าต่าง"
9129            | "wait_window"
9130            | "gfx_wait"
9131            | "انتظار_پنجره"
9132            | "انتظر_النافذة"
9133            | "המתן_לחלון"
9134            | "ونڈو_انتظار" => {
9135                #[cfg(not(target_arch = "wasm32"))]
9136                loop {
9137                    let still_open = {
9138                        let gfx = self.gfx.borrow();
9139                        gfx.window
9140                            .as_ref()
9141                            .map(|w| w.is_open() && !w.is_key_down(minifb::Key::Escape))
9142                            .unwrap_or(false)
9143                    };
9144                    if !still_open {
9145                        break;
9146                    }
9147                    let (buf, w, h) = {
9148                        let gfx = self.gfx.borrow();
9149                        (gfx.buffer.clone(), gfx.width, gfx.height)
9150                    };
9151                    let mut gfx = self.gfx.borrow_mut();
9152                    if let Some(win) = gfx.window.as_mut() {
9153                        if win.update_with_buffer(&buf, w, h).is_err() {
9154                            break;
9155                        }
9156                    }
9157                }
9158                return Ok(Value::Unit);
9159            },
9160
9161            // ── File I/O ──────────────────────────────────────────────────────
9162            "read_file" | "อ่านไฟล์" | "خواندن_فایل" | "اقرأ_الملف" | "קרא_קובץ" | "فائل_پڑھو" =>
9163            {
9164                #[cfg(target_arch = "wasm32")]
9165                return Ok(Value::Str(String::new()));
9166                #[cfg(not(target_arch = "wasm32"))]
9167                {
9168                    let path = self.arg_str(&args, 0, "").replace('\\', "/");
9169                    return std::fs::read_to_string(&path)
9170                        .map(Value::Str)
9171                        .map_err(|e| EvalErr::from(format!("read_file '{path}': {e}")));
9172                }
9173            },
9174            // ── networking (TCP, 2-peer co-op) ───────────────────────────────
9175            #[cfg(not(target_arch = "wasm32"))]
9176            "net_host" | "เน็ตโฮสต์" | "میزبانی_شبکه" | "استضف_الشبكة" | "ארח_רשת" | "نیٹ_ہوسٹ" =>
9177            {
9178                let port = self.arg_num(&args, 0, 7777.0)? as u16;
9179                net::host(port);
9180                return Ok(Value::Unit);
9181            },
9182            #[cfg(not(target_arch = "wasm32"))]
9183            "net_join" | "เน็ตจอย" | "پیوستن_شبکه" | "انضم_للشبكة" | "הצטרף_לרשת" | "نیٹ_شمولیت" =>
9184            {
9185                let ip = self.arg_str(&args, 0, "127.0.0.1");
9186                let port = self.arg_num(&args, 1, 7777.0)? as u16;
9187                net::join(&ip, port);
9188                return Ok(Value::Unit);
9189            },
9190            #[cfg(not(target_arch = "wasm32"))]
9191            "net_send" | "เน็ตส่ง" | "ارسال_شبکه" | "أرسل_عبر_الشبكة" | "שלח_ברשת" | "نیٹ_بھیجو" =>
9192            {
9193                let s = self.arg_str(&args, 0, "");
9194                net::send(&s);
9195                return Ok(Value::Unit);
9196            },
9197            #[cfg(not(target_arch = "wasm32"))]
9198            "net_recv" | "เน็ตรับ" | "دریافت_شبکه" | "استقبل_من_الشبكة" | "קבל_מרשת" | "نیٹ_وصول" =>
9199            {
9200                return Ok(Value::Str(net::recv()));
9201            },
9202            #[cfg(not(target_arch = "wasm32"))]
9203            "net_status" | "เน็ตสถานะ" | "وضعیت_شبکه" | "حالة_الشبكة" | "סטטוס_רשת" | "نیٹ_حالت" =>
9204            {
9205                return Ok(Value::Number(net::status() as f64));
9206            },
9207            #[cfg(not(target_arch = "wasm32"))]
9208            "net_recv_from" => {
9209                return Ok(Value::Str(net::recv_from()));
9210            },
9211            #[cfg(not(target_arch = "wasm32"))]
9212            "net_send_to" => {
9213                let id = self.arg_num(&args, 0, 0.0)? as u64;
9214                let s = self.arg_str(&args, 1, "");
9215                net::send_to(id, &s);
9216                return Ok(Value::Unit);
9217            },
9218            #[cfg(not(target_arch = "wasm32"))]
9219            "net_close" | "연결종료" => {
9220                net::close();
9221                return Ok(Value::Unit);
9222            },
9223            // ── LAN lobby discovery (UDP broadcast) ──
9224            #[cfg(not(target_arch = "wasm32"))]
9225            "net_announce"
9226            | "เน็ตประกาศ"
9227            | "اعلام_شبکه"
9228            | "أعلن_في_الشبكة"
9229            | "הכרז_ברשת"
9230            | "نیٹ_اعلان" => {
9231                let port = self.arg_num(&args, 0, 7778.0)? as u16;
9232                let info = self.arg_str(&args, 1, "");
9233                net::announce(port, &info);
9234                return Ok(Value::Unit);
9235            },
9236            #[cfg(not(target_arch = "wasm32"))]
9237            "net_announce_stop"
9238            | "เน็ตหยุดประกาศ"
9239            | "توقف_اعلام"
9240            | "أوقف_الإعلان"
9241            | "עצור_הכרזה"
9242            | "اعلان_روکو" => {
9243                net::announce_stop();
9244                return Ok(Value::Unit);
9245            },
9246            #[cfg(not(target_arch = "wasm32"))]
9247            "net_discover" | "เน็ตค้นหา" | "کشف_شبکه" | "اكتشف_الشبكة" | "גלה_רשת" | "نیٹ_دریافت" =>
9248            {
9249                let port = self.arg_num(&args, 0, 7778.0)? as u16;
9250                return Ok(Value::Str(net::discover(port)));
9251            },
9252            #[cfg(not(target_arch = "wasm32"))]
9253            "net_test" | "เน็ตทดสอบ" | "آزمون_شبکه" | "اختبر_الشبكة" | "בדוק_רשת" | "نیٹ_ٹیسٹ" =>
9254            {
9255                let port = self.arg_num(&args, 0, 7777.0)? as u16;
9256                return Ok(Value::Str(net::test_bind(port)));
9257            },
9258            // ── HTTP server (interpreter <-> async bridge, see runtime::web) ──
9259            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9260            "http_route" | "เว็บเส้นทาง" | "مسیر_HTTP" | "مسار_HTTP" | "נתיב_HTTP" | "HTTP_روٹ" =>
9261            {
9262                let method = self.arg_str(&args, 0, "GET").to_uppercase();
9263                let path = self.arg_str(&args, 1, "/");
9264                let handler = args.get(2).cloned().unwrap_or(Value::Unit);
9265                self.http_routes.push((method, path, handler));
9266                return Ok(Value::Unit);
9267            },
9268            // Registers a directory to be served as raw bytes at `prefix` (fonts,
9269            // images, generated zips/PDFs) — bypasses the String-only Request/
9270            // Response bridge entirely, so binary files come through intact.
9271            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9272            "http_static"
9273            | "เว็บสแตติก"
9274            | "فایل_ایستای_HTTP"
9275            | "ملفات_HTTP_ثابتة"
9276            | "קבצים_סטטיים_HTTP"
9277            | "HTTP_مستقل_فائل" => {
9278                let prefix = self.arg_str(&args, 0, "/static");
9279                let dir = self.arg_str(&args, 1, "static");
9280                self.http_static_dirs.push((prefix, dir));
9281                return Ok(Value::Unit);
9282            },
9283            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9284            "http_serve" | "เว็บเสิร์ฟ" | "سرویس_HTTP" | "قدّم_HTTP" | "הגש_HTTP" | "HTTP_سرو" =>
9285            {
9286                let host = self.arg_str(&args, 0, "127.0.0.1");
9287                let port = self.arg_num(&args, 1, 8080.0)? as u16;
9288                let routes = std::mem::take(&mut self.http_routes);
9289                let static_dirs = std::mem::take(&mut self.http_static_dirs);
9290                // No premature "listening" print here: ling_http::serve_http
9291                // (called from spawn_server's background thread) now prints
9292                // its own banner, but only after the socket is actually
9293                // bound — a more honest signal than printing right after
9294                // requesting the background thread be spawned.
9295                let rx = web::spawn_server(host.clone(), port, static_dirs);
9296                for pending in rx {
9297                    let matched = routes
9298                        .iter()
9299                        .find(|(m, p, _)| m == &pending.method && p == &pending.path);
9300                    let response = match matched {
9301                        Some((_, _, handler)) => {
9302                            let req_value = Value::Struct {
9303                                name: "Request".to_string(),
9304                                fields: vec![
9305                                    ("method".to_string(), Value::Str(pending.method.clone())),
9306                                    ("path".to_string(), Value::Str(pending.path.clone())),
9307                                    ("query".to_string(), Value::Str(pending.query.clone())),
9308                                    ("body".to_string(), Value::Str(pending.body.clone())),
9309                                    ("cookie".to_string(), Value::Str(pending.cookie.clone())),
9310                                    (
9311                                        "authorization".to_string(),
9312                                        Value::Str(pending.authorization.clone()),
9313                                    ),
9314                                    (
9315                                        "client_ip".to_string(),
9316                                        Value::Str(pending.client_ip.clone()),
9317                                    ),
9318                                ],
9319                            };
9320                            match self.call_value(handler.clone(), vec![req_value]) {
9321                                Ok(v) => web::value_to_response(&v),
9322                                Err(e) => web::HttpResponse {
9323                                    status: 500,
9324                                    content_type: "text/plain; charset=utf-8".to_string(),
9325                                    body: format!("handler error: {e:?}"),
9326                                    set_cookie: None,
9327                                    location: None,
9328                                },
9329                            }
9330                        },
9331                        None => web::HttpResponse {
9332                            status: 404,
9333                            content_type: "text/plain; charset=utf-8".to_string(),
9334                            body: "not found".to_string(),
9335                            set_cookie: None,
9336                            location: None,
9337                        },
9338                    };
9339                    let _ = pending.respond_to.send(response);
9340                }
9341                return Ok(Value::Unit);
9342            },
9343            // Fires a POST request on a background async runtime and returns a job
9344            // id immediately — for slow external calls (e.g. local Stable Diffusion
9345            // generation) that must not block http_serve's single-threaded loop.
9346            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9347            "http_post_async"
9348            | "เว็บโพสต์ไม่บล็อก"
9349            | "ارسال_ناهمگام_HTTP"
9350            | "أرسل_HTTP_غير_متزامن"
9351            | "שלח_HTTP_אסינכרוני"
9352            | "HTTP_غیر_ہمزمان_بھیجو" => {
9353                let url = self.arg_str(&args, 0, "");
9354                let body = self.arg_str(&args, 1, "");
9355                let content_type = self.arg_str(&args, 2, "application/json");
9356                let id = self.async_jobs.start_post(url, content_type, body);
9357                return Ok(Value::Str(id));
9358            },
9359            // Non-blocking poll: "" while the job named by http_post_async (or
9360            // sdai_generate_start) is still running, the result once it
9361            // completes — same job table, same builtin polls both.
9362            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9363            "http_job_poll"
9364            | "เว็บงานสำรวจ"
9365            | "بررسی_وظیفه_HTTP"
9366            | "استطلع_مهمة_HTTP"
9367            | "בדוק_משימת_HTTP"
9368            | "HTTP_کام_پول" => {
9369                let id = self.arg_str(&args, 0, "");
9370                return Ok(Value::Str(self.async_jobs.poll(&id).unwrap_or_default()));
9371            },
9372            // Starts an AUTOMATIC1111-compatible txt2img generation in the
9373            // background against `base_url` (e.g. "http://127.0.0.1:1342").
9374            // Poll with http_job_poll: the result is the plain base64 PNG
9375            // once ready, or a string starting with "ERROR:" on failure —
9376            // the JSON response itself is parsed in Rust (see
9377            // AsyncJobs::start_sdai_txt2img), since `.ling` has no JSON parser.
9378            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9379            "sdai_generate_start"
9380            | "เอสดีเอไอเริ่มสร้าง"
9381            | "شروع_تولید_هوش"
9382            | "ابدأ_توليد_الذكاء"
9383            | "התחל_יצירת_בינה"
9384            | "اے_آئی_تخلیق_شروع" => {
9385                let base_url = self.arg_str(&args, 0, "http://127.0.0.1:1342");
9386                let prompt = self.arg_str(&args, 1, "");
9387                let width = self.arg_num(&args, 2, 512.0)? as u32;
9388                let height = self.arg_num(&args, 3, 512.0)? as u32;
9389                let id = self
9390                    .async_jobs
9391                    .start_sdai_txt2img(base_url, prompt, width, height);
9392                return Ok(Value::Str(id));
9393            },
9394            // ── query_param("q=a&page=2", "q", "") → "a" (URL-decoded) ──
9395            "query_param"
9396            | "พารามิเตอร์"
9397            | "پارامتر_پرسوجو"
9398            | "معامل_الاستعلام"
9399            | "פרמטר_שאילתה"
9400            | "کوئری_پیرامیٹر" => {
9401                let qs = self.arg_str(&args, 0, "");
9402                let name = self.arg_str(&args, 1, "");
9403                let default = self.arg_str(&args, 2, "");
9404                let mut found = default;
9405                for pair in qs.split('&') {
9406                    let mut it = pair.splitn(2, '=');
9407                    if it.next().unwrap_or("") == name {
9408                        found = url_decode(it.next().unwrap_or(""));
9409                        break;
9410                    }
9411                }
9412                return Ok(Value::Str(found));
9413            },
9414            // ── cookie_get("sid=abc; x=1", "sid", "") → "abc" ──
9415            "cookie_get"
9416            | "รับคุกกี้"
9417            | "دریافت_کوکی"
9418            | "اجلب_الكعكة"
9419            | "קבל_עוגייה"
9420            | "کوکی_حاصل_کرو" => {
9421                let header = self.arg_str(&args, 0, "");
9422                let name = self.arg_str(&args, 1, "");
9423                let default = self.arg_str(&args, 2, "");
9424                let mut found = default;
9425                for pair in header.split(';') {
9426                    let p = pair.trim();
9427                    let mut it = p.splitn(2, '=');
9428                    if it.next().unwrap_or("") == name {
9429                        found = it.next().unwrap_or("").to_string();
9430                        break;
9431                    }
9432                }
9433                return Ok(Value::Str(found));
9434            },
9435            // ── html_escape(s) — & < > " ' → entities, for echoing user input ──
9436            "html_escape"
9437            | "กันเอชทีเอ็มแอล"
9438            | "فرار_HTML"
9439            | "أفلت_HTML"
9440            | "בריחת_HTML"
9441            | "HTML_ایسکیپ" => {
9442                let s = self.arg_str(&args, 0, "");
9443                return Ok(Value::Str(
9444                    s.replace('&', "&amp;")
9445                        .replace('<', "&lt;")
9446                        .replace('>', "&gt;")
9447                        .replace('"', "&quot;")
9448                        .replace('\'', "&#39;"),
9449                ));
9450            },
9451            // json_escape(s) — escape a string for embedding inside a JSON
9452            // string literal (", \, and control chars). Needed because the
9453            // registry builds JSON API responses by concatenation; without
9454            // this a value containing " or \ breaks or injects into the JSON.
9455            "json_escape" | "หนีเจสัน" | "فرار_JSON" | "أفلت_JSON" | "בריחת_JSON" | "JSON_ایسکیپ" =>
9456            {
9457                let s = self.arg_str(&args, 0, "");
9458                let mut out = String::with_capacity(s.len() + 8);
9459                for c in s.chars() {
9460                    match c {
9461                        '"' => out.push_str("\\\""),
9462                        '\\' => out.push_str("\\\\"),
9463                        '\n' => out.push_str("\\n"),
9464                        '\r' => out.push_str("\\r"),
9465                        '\t' => out.push_str("\\t"),
9466                        c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
9467                        c => out.push(c),
9468                    }
9469                }
9470                return Ok(Value::Str(out));
9471            },
9472            // ── CLI arguments: cli_arg("port", "8080") reads `--port 6688` ──
9473            "cli_arg"
9474            | "อาร์กิวเมนต์"
9475            | "آرگومان_خط‌فرمان"
9476            | "معامل_سطر_الأوامر"
9477            | "ארגומנט_שורת_פקודה"
9478            | "سی_ایل_آئی_دلیل" => {
9479                let name = self.arg_str(&args, 0, "");
9480                let default = self.arg_str(&args, 1, "");
9481                let flag = format!("--{name}");
9482                let argv: Vec<String> = std::env::args().collect();
9483                let found = argv
9484                    .iter()
9485                    .position(|a| a == &flag)
9486                    .and_then(|i| argv.get(i + 1))
9487                    .cloned()
9488                    .unwrap_or(default);
9489                return Ok(Value::Str(found));
9490            },
9491            // ── SQLite (rusqlite, synchronous — matches the interpreter) ──
9492            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9493            "db_open"
9494            | "ฐานข้อมูลเปิด"
9495            | "باز_کردن_پایگاه_داده"
9496            | "افتح_قاعدة_البيانات"
9497            | "פתח_מסד_נתונים"
9498            | "ڈیٹا_بیس_کھولو" => {
9499                let path = self.arg_str(&args, 0, "app.db");
9500                let conn = ling_http::rusqlite::Connection::open(&path)
9501                    .map_err(|e| EvalErr::from(format!("db_open '{path}': {e}")))?;
9502                let _ = conn.execute_batch("PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL;");
9503                self.db = Some(conn);
9504                return Ok(Value::Unit);
9505            },
9506            // db_exec(sql, ...params) → rows affected. Params bind positionally
9507            // (?1, ?2, ...): numbers as REAL, bools as 0/1, everything else TEXT.
9508            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9509            "db_exec"
9510            | "ฐานข้อมูลรัน"
9511            | "اجرای_پایگاه_داده"
9512            | "نفّذ_في_قاعدة_البيانات"
9513            | "בצע_במסד_נתונים"
9514            | "ڈیٹا_بیس_عمل" => {
9515                let sql = self.arg_str(&args, 0, "");
9516                let params = values_to_sql_params(&args[1.min(args.len())..]);
9517                let conn = self
9518                    .db
9519                    .as_ref()
9520                    .ok_or_else(|| EvalErr::from("db_exec: call db_open first".to_string()))?;
9521                let n = conn
9522                    .execute(&sql, ling_http::rusqlite::params_from_iter(params.iter()))
9523                    .map_err(|e| EvalErr::from(format!("db_exec: {e}\n  sql: {sql}")))?;
9524                return Ok(Value::Number(n as f64));
9525            },
9526            // db_query(sql, ...params) → List of Row structs (row.column_name).
9527            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9528            "db_query"
9529            | "ฐานข้อมูลถาม"
9530            | "پرسوجوی_پایگاه_داده"
9531            | "استعلم_قاعدة_البيانات"
9532            | "שאילתת_מסד_נתונים"
9533            | "ڈیٹا_بیس_سوال" => {
9534                let sql = self.arg_str(&args, 0, "");
9535                let params = values_to_sql_params(&args[1.min(args.len())..]);
9536                let conn = self
9537                    .db
9538                    .as_ref()
9539                    .ok_or_else(|| EvalErr::from("db_query: call db_open first".to_string()))?;
9540                let mut stmt = conn
9541                    .prepare(&sql)
9542                    .map_err(|e| EvalErr::from(format!("db_query: {e}\n  sql: {sql}")))?;
9543                let col_names: Vec<String> =
9544                    stmt.column_names().iter().map(|s| s.to_string()).collect();
9545                let mut rows = stmt
9546                    .query(ling_http::rusqlite::params_from_iter(params.iter()))
9547                    .map_err(|e| EvalErr::from(format!("db_query: {e}")))?;
9548                let mut out = Vec::new();
9549                while let Some(row) = rows
9550                    .next()
9551                    .map_err(|e| EvalErr::from(format!("db_query row: {e}")))?
9552                {
9553                    let mut fields = Vec::with_capacity(col_names.len());
9554                    for (i, col) in col_names.iter().enumerate() {
9555                        use ling_http::rusqlite::types::ValueRef;
9556                        let v = match row.get_ref(i) {
9557                            Ok(ValueRef::Null) => Value::Str(String::new()),
9558                            Ok(ValueRef::Integer(n)) => Value::Number(n as f64),
9559                            Ok(ValueRef::Real(n)) => Value::Number(n),
9560                            Ok(ValueRef::Text(t)) => {
9561                                Value::Str(String::from_utf8_lossy(t).into_owned())
9562                            },
9563                            Ok(ValueRef::Blob(b)) => {
9564                                use base64::Engine as _;
9565                                Value::Str(base64::engine::general_purpose::STANDARD.encode(b))
9566                            },
9567                            Err(_) => Value::Str(String::new()),
9568                        };
9569                        fields.push((col.clone(), v));
9570                    }
9571                    out.push(Value::Struct { name: "Row".to_string(), fields });
9572                }
9573                return Ok(Value::List(Rc::new(out)));
9574            },
9575            // ── gamepad (gilrs) ──
9576            #[cfg(not(target_arch = "wasm32"))]
9577            "gamepad_poll"
9578            | "จอยโพล"
9579            | "بررسی_دسته_بازی"
9580            | "استطلع_يد_اللعب"
9581            | "בדוק_בקר_משחק"
9582            | "گیم_پیڈ_پول_کرو" => {
9583                gamepad::poll();
9584                return Ok(Value::Unit);
9585            },
9586            #[cfg(not(target_arch = "wasm32"))]
9587            "gamepad_button"
9588            | "จอยปุ่ม"
9589            | "دکمه_دسته_بازی"
9590            | "زر_يد_اللعب"
9591            | "כפתור_בקר_משחק"
9592            | "گیم_پیڈ_بٹن_دبایا" => {
9593                let name = self.arg_str(&args, 0, "");
9594                return Ok(Value::Number(if gamepad::button(&name) {
9595                    1.0
9596                } else {
9597                    0.0
9598                }));
9599            },
9600            #[cfg(not(target_arch = "wasm32"))]
9601            "gamepad_axis"
9602            | "จอยแกน"
9603            | "محور_دسته_بازی"
9604            | "محور_يد_اللعب"
9605            | "ציר_בקר_משחק"
9606            | "گیم_پیڈ_محور" => {
9607                let name = self.arg_str(&args, 0, "");
9608                return Ok(Value::Number(gamepad::axis(&name) as f64));
9609            },
9610            #[cfg(not(target_arch = "wasm32"))]
9611            "gamepad_rumble"
9612            | "จอยสั่น"
9613            | "لرزش_دسته_بازی"
9614            | "اهتزاز_يد_اللعب"
9615            | "רטט_בקר_משחק"
9616            | "گیم_پیڈ_لرزش" => {
9617                let low = self.arg_num(&args, 0, 0.0)? as f32;
9618                let high = self.arg_num(&args, 1, 0.0)? as f32;
9619                let ms = self.arg_num(&args, 2, 200.0)? as u32;
9620                gamepad::rumble(low, high, ms);
9621                return Ok(Value::Unit);
9622            },
9623            #[cfg(not(target_arch = "wasm32"))]
9624            "gamepad_list"
9625            | "จอยรายการ"
9626            | "فهرست_دسته‌های_بازی"
9627            | "قائمة_أيدي_اللعب"
9628            | "רשימת_בקרי_משחק"
9629            | "گیم_پیڈ_فہرست" => {
9630                return Ok(Value::Str(gamepad::list()));
9631            },
9632            #[cfg(not(target_arch = "wasm32"))]
9633            "gamepad_any"
9634            | "จอยใดๆ"
9635            | "هر_دسته_بازی"
9636            | "أي_يد_لعب"
9637            | "בקר_כלשהו"
9638            | "کوئی_بھی_گیم_پیڈ" => {
9639                return Ok(Value::Number(if gamepad::any_button() { 1.0 } else { 0.0 }));
9640            },
9641            // wasm32: gamepad not available — return safe no-op values
9642            #[cfg(target_arch = "wasm32")]
9643            "gamepad_poll"
9644            | "จอยโพล"
9645            | "gamepad_rumble"
9646            | "จอยสั่น"
9647            | "بررسی_دسته_بازی"
9648            | "استطلع_يد_اللعب"
9649            | "בדוק_בקר_משחק"
9650            | "گیم_پیڈ_پول_کرو" => {
9651                return Ok(Value::Unit);
9652            },
9653            #[cfg(target_arch = "wasm32")]
9654            "gamepad_button"
9655            | "จอยปุ่ม"
9656            | "gamepad_axis"
9657            | "จอยแกน"
9658            | "gamepad_any"
9659            | "จอยใดๆ"
9660            | "دکمه_دسته_بازی"
9661            | "زر_يد_اللعب"
9662            | "כפתור_בקר_משחק"
9663            | "گیم_پیڈ_بٹن" => {
9664                return Ok(Value::Number(0.0));
9665            },
9666            #[cfg(target_arch = "wasm32")]
9667            "gamepad_list"
9668            | "จอยรายการ"
9669            | "فهرست_دسته‌های_بازی"
9670            | "قائمة_أيدي_اللعب"
9671            | "רשימת_בקרי_משחק"
9672            | "گیم_پیڈ_فہرست" => {
9673                return Ok(Value::Str(String::new()));
9674            },
9675
9676            // ── game AI: neural networks ─────────────────────────────────────
9677            // nn_new(inputs[, seed]) → handle
9678            #[cfg(not(target_arch = "wasm32"))]
9679            "nn_new"
9680            | "建神经网"
9681            | "ニューラル作成"
9682            | "신경망생성"
9683            | "สร้างโครงข่าย"
9684            | "شبکه_جدید"
9685            | "شبكة_جديدة"
9686            | "רשת_חדשה"
9687            | "نئی_نیورل_نیٹ"
9688            | "nouveau_réseau"
9689            | "neues_netz"
9690            | "новая_сеть" => {
9691                let n_in = self.arg_num(&args, 0, 1.0)?.max(0.0) as usize;
9692                let seed = self.arg_num(&args, 1, 1.0)? as u64;
9693                return Ok(Value::Number(ai::nn_new(n_in, seed) as f64));
9694            },
9695            // nn_dense(handle, units[, activation]) — append a layer
9696            #[cfg(not(target_arch = "wasm32"))]
9697            "nn_dense"
9698            | "密集层"
9699            | "密層追加"
9700            | "밀집층"
9701            | "ชั้นหนาแน่น"
9702            | "لایه_متراکم"
9703            | "طبقة_كثيفة"
9704            | "שכבה_צפופה"
9705            | "ڈینس_لیئر"
9706            | "réseau_dense"
9707            | "netz_dicht"
9708            | "плотная_сеть" => {
9709                let id = self.arg_num(&args, 0, -1.0)? as i64;
9710                let units = self.arg_num(&args, 1, 1.0)?.max(1.0) as usize;
9711                let act = self.arg_str(&args, 2, "relu");
9712                ai::nn_dense(id, units, &act);
9713                return Ok(Value::Unit);
9714            },
9715            // nn_forward(handle, [inputs]) → [outputs]
9716            #[cfg(not(target_arch = "wasm32"))]
9717            "nn_forward"
9718            | "神经前向"
9719            | "順伝播"
9720            | "순전파"
9721            | "ส่งต่อโครงข่าย"
9722            | "پیش‌روی_شبکه"
9723            | "تمرير_أمامي"
9724            | "העברה_קדימה"
9725            | "فارورڈ_پاس"
9726            | "propager_réseau"
9727            | "netz_vorwärts"
9728            | "прямой_проход_сети" => {
9729                let id = self.arg_num(&args, 0, -1.0)? as i64;
9730                let input = self.arg_list_f32(&args, 1);
9731                let out = ai::nn_forward(id, &input);
9732                return Ok(Value::List(Rc::new(
9733                    out.into_iter().map(|v| Value::Number(v as f64)).collect(),
9734                )));
9735            },
9736            // nn_train(handle, [inputs], [targets][, lr]) → loss
9737            #[cfg(not(target_arch = "wasm32"))]
9738            "nn_train"
9739            | "训练网"
9740            | "ニューラル学習"
9741            | "신경망학습"
9742            | "ฝึกโครงข่าย"
9743            | "آموزش_شبکه"
9744            | "درّب_الشبكة"
9745            | "אמן_רשת"
9746            | "نیٹ_ٹریننگ"
9747            | "entraîner_réseau"
9748            | "netz_trainieren"
9749            | "обучить_сеть" => {
9750                let id = self.arg_num(&args, 0, -1.0)? as i64;
9751                let input = self.arg_list_f32(&args, 1);
9752                let target = self.arg_list_f32(&args, 2);
9753                let lr = self.arg_num(&args, 3, 0.01)? as f32;
9754                return Ok(Value::Number(ai::nn_train(id, &input, &target, lr) as f64));
9755            },
9756            // nn_save(handle, path) → bool
9757            #[cfg(not(target_arch = "wasm32"))]
9758            "nn_save"
9759            | "保存网"
9760            | "網保存"
9761            | "신경망저장"
9762            | "บันทึกโครงข่าย"
9763            | "ذخیره_شبکه"
9764            | "احفظ_الشبكة"
9765            | "שמור_רשת"
9766            | "نیٹ_محفوظ_کرو"
9767            | "sauvegarder_réseau"
9768            | "netz_speichern"
9769            | "сохранить_сеть" => {
9770                let id = self.arg_num(&args, 0, -1.0)? as i64;
9771                let path = self.arg_str(&args, 1, "model.lnn");
9772                return Ok(Value::Bool(ai::nn_save(id, &path)));
9773            },
9774            // nn_load(path) → handle (-1 on failure)
9775            #[cfg(not(target_arch = "wasm32"))]
9776            "nn_load"
9777            | "载入网"
9778            | "網読込"
9779            | "신경망불러오기"
9780            | "โหลดโครงข่าย"
9781            | "بارگذاری_شبکه"
9782            | "حمّل_الشبكة"
9783            | "טען_רשת"
9784            | "نیٹ_لوڈ"
9785            | "charger_réseau"
9786            | "netz_laden"
9787            | "загрузить_сеть" => {
9788                let path = self.arg_str(&args, 0, "model.lnn");
9789                return Ok(Value::Number(ai::nn_load(&path) as f64));
9790            },
9791
9792            // ── game AI: behavior trees ──────────────────────────────────────
9793            // bt_build(dsl_string) → handle
9794            #[cfg(not(target_arch = "wasm32"))]
9795            "bt_build"
9796            | "建行为树"
9797            | "行動木構築"
9798            | "행동트리구성"
9799            | "สร้างต้นไม้พฤติกรรม"
9800            | "ساخت_درخت_رفتار"
9801            | "ابنِ_شجرة_السلوك"
9802            | "בנה_עץ_התנהגות"
9803            | "بی_ٹی_تعمیر"
9804            | "construire_arbre_comportement"
9805            | "verhaltensbaum_bauen"
9806            | "построить_дерево_поведения" => {
9807                let spec = self.arg_str(&args, 0, "");
9808                return Ok(Value::Number(ai::bt_build(&spec) as f64));
9809            },
9810            // bt_set(handle, key, value) — set a blackboard fact
9811            #[cfg(not(target_arch = "wasm32"))]
9812            "bt_set"
9813            | "设事实"
9814            | "事実設定"
9815            | "사실설정"
9816            | "ตั้งข้อเท็จจริง"
9817            | "تنظیم_واقعیت"
9818            | "عيّن_حقيقة"
9819            | "קבע_עובדה"
9820            | "بی_ٹی_سیٹ"
9821            | "définir_arbre_comportement"
9822            | "verhaltensbaum_setzen"
9823            | "задать_дерево_поведения" => {
9824                let id = self.arg_num(&args, 0, -1.0)? as i64;
9825                let key = self.arg_str(&args, 1, "");
9826                let val = self.arg_num(&args, 2, 0.0)? as f32;
9827                ai::bt_set(id, &key, val);
9828                return Ok(Value::Unit);
9829            },
9830            // bt_tick(handle) → chosen action name ("" if none)
9831            #[cfg(not(target_arch = "wasm32"))]
9832            "bt_tick"
9833            | "行为树滴答"
9834            | "行動木更新"
9835            | "행동트리틱"
9836            | "เดินต้นไม้พฤติกรรม"
9837            | "تیک_درخت_رفتار"
9838            | "نبضة_شجرة_السلوك"
9839            | "טיק_עץ_התנהגות"
9840            | "بی_ٹی_ٹک"
9841            | "tick_arbre_comportement"
9842            | "verhaltensbaum_tick"
9843            | "тик_дерева_поведения" => {
9844                let id = self.arg_num(&args, 0, -1.0)? as i64;
9845                return Ok(Value::Str(ai::bt_tick(id)));
9846            },
9847            // bt_status(handle) → 0 fail / 1 success / 2 running
9848            #[cfg(not(target_arch = "wasm32"))]
9849            "bt_status"
9850            | "行为树状态"
9851            | "行動木状態"
9852            | "행동트리상태"
9853            | "สถานะต้นไม้พฤติกรรม"
9854            | "وضعیت_درخت_رفتار"
9855            | "حالة_شجرة_السلوك"
9856            | "סטטוס_עץ_התנהגות"
9857            | "بی_ٹی_حالت"
9858            | "statut_arbre_comportement"
9859            | "verhaltensbaum_status"
9860            | "статус_дерева_поведения" => {
9861                let id = self.arg_num(&args, 0, -1.0)? as i64;
9862                return Ok(Value::Number(ai::bt_status(id) as f64));
9863            },
9864
9865            // ── game AI: miniature dialog LLM ────────────────────────────────
9866            // dialog_new([ctx, embed, hidden, seed]) → handle
9867            #[cfg(not(target_arch = "wasm32"))]
9868            "dialog_new"
9869            | "建对话模型"
9870            | "対話モデル作成"
9871            | "대화모델생성"
9872            | "สร้างโมเดลสนทนา"
9873            | "مدل_گفتگوی_جدید"
9874            | "نموذج_حوار_جديد"
9875            | "מודל_דיאלוג_חדש"
9876            | "نیا_مکالمہ_ماڈل"
9877            | "nouveau_dialogue"
9878            | "neuer_dialog"
9879            | "новый_диалог" => {
9880                let ctx = self.arg_num(&args, 0, 3.0)?.max(1.0) as usize;
9881                let embed = self.arg_num(&args, 1, 32.0)?.max(1.0) as usize;
9882                let hidden = self.arg_num(&args, 2, 64.0)?.max(1.0) as usize;
9883                let seed = self.arg_num(&args, 3, 1.0)? as u64;
9884                return Ok(Value::Number(
9885                    ai::dialog_new(ctx, embed, hidden, seed) as f64
9886                ));
9887            },
9888            // dialog_learn(handle, text) — add one utterance to the corpus
9889            #[cfg(not(target_arch = "wasm32"))]
9890            "dialog_learn"
9891            | "对话学习"
9892            | "対話学習"
9893            | "대화학습"
9894            | "เรียนรู้สนทนา"
9895            | "یادگیری_گفتگو"
9896            | "تعلّم_الحوار"
9897            | "למד_דיאלוג"
9898            | "مکالمہ_سیکھو"
9899            | "apprendre_dialogue"
9900            | "dialog_lernen"
9901            | "обучить_диалог" => {
9902                let id = self.arg_num(&args, 0, -1.0)? as i64;
9903                let text = self.arg_str(&args, 1, "");
9904                ai::dialog_learn(id, &text);
9905                return Ok(Value::Unit);
9906            },
9907            // dialog_load(handle, path) → lines added (-1 on error)
9908            #[cfg(not(target_arch = "wasm32"))]
9909            "dialog_load"
9910            | "对话载入"
9911            | "対話読込"
9912            | "대화불러오기"
9913            | "โหลดชุดสนทนา"
9914            | "بارگذاری_مجموعه_گفتگو"
9915            | "حمّل_مجموعة_الحوار"
9916            | "טען_מערך_דיאלוג"
9917            | "مکالمہ_مجموعہ_لوڈ"
9918            | "charger_dialogue"
9919            | "dialog_laden"
9920            | "загрузить_диалог" => {
9921                let id = self.arg_num(&args, 0, -1.0)? as i64;
9922                let path = self.arg_str(&args, 1, "");
9923                return Ok(Value::Number(ai::dialog_load(id, &path) as f64));
9924            },
9925            // dialog_train(handle[, epochs, lr]) → loss
9926            #[cfg(not(target_arch = "wasm32"))]
9927            "dialog_train"
9928            | "对话训练"
9929            | "対話訓練"
9930            | "대화훈련"
9931            | "ฝึกสนทนา"
9932            | "آموزش_گفتگو"
9933            | "درّب_الحوار"
9934            | "אמן_דיאלוג"
9935            | "مکالمہ_ٹریننگ"
9936            | "entraîner_dialogue"
9937            | "dialog_trainieren"
9938            | "тренировать_диалог" => {
9939                let id = self.arg_num(&args, 0, -1.0)? as i64;
9940                let epochs = self.arg_num(&args, 1, 20.0)?.max(1.0) as usize;
9941                let lr = self.arg_num(&args, 2, 0.1)? as f32;
9942                return Ok(Value::Number(ai::dialog_train(id, epochs, lr) as f64));
9943            },
9944            // dialog_say(handle, prompt[, max_tokens, temperature]) → reply text
9945            #[cfg(not(target_arch = "wasm32"))]
9946            "dialog_say"
9947            | "对话生成"
9948            | "対話生成"
9949            | "대화생성"
9950            | "พูดสนทนา"
9951            | "بگو"
9952            | "قل"
9953            | "אמור"
9954            | "کہو"
9955            | "dire_dialogue"
9956            | "dialog_sagen"
9957            | "сказать_диалог" => {
9958                let id = self.arg_num(&args, 0, -1.0)? as i64;
9959                let prompt = self.arg_str(&args, 1, "");
9960                let max = self.arg_num(&args, 2, 24.0)?.max(1.0) as usize;
9961                let temp = self.arg_num(&args, 3, 0.8)? as f32;
9962                return Ok(Value::Str(ai::dialog_say(id, &prompt, max, temp)));
9963            },
9964            // dialog_save(handle, path) → bool
9965            #[cfg(not(target_arch = "wasm32"))]
9966            "dialog_save"
9967            | "对话存模"
9968            | "対話モデル保存"
9969            | "대화모델저장"
9970            | "บันทึกโมเดลสนทนา"
9971            | "ذخیره_مدل_گفتگو"
9972            | "احفظ_نموذج_الحوار"
9973            | "שמור_מודל_דיאלוג"
9974            | "مکالمہ_ماڈل_محفوظ"
9975            | "sauvegarder_dialogue"
9976            | "dialog_speichern"
9977            | "сохранить_диалог" => {
9978                let id = self.arg_num(&args, 0, -1.0)? as i64;
9979                let path = self.arg_str(&args, 1, "model.llm");
9980                return Ok(Value::Bool(ai::dialog_save(id, &path)));
9981            },
9982            // dialog_load_model(path) → handle (-1 on failure)
9983            #[cfg(not(target_arch = "wasm32"))]
9984            "dialog_load_model"
9985            | "对话载模"
9986            | "対話モデル読込"
9987            | "대화모델불러오기"
9988            | "โหลดโมเดลสนทนา"
9989            | "بارگذاری_مدل_گفتگو"
9990            | "حمّل_نموذج_الحوار"
9991            | "טען_מודל_דיאלוג"
9992            | "مکالمہ_ماڈل_لوڈ"
9993            | "charger_modèle_dialogue"
9994            | "dialog_modell_laden"
9995            | "загрузить_модель_диалога" => {
9996                let path = self.arg_str(&args, 0, "model.llm");
9997                return Ok(Value::Number(ai::dialog_load_model(&path) as f64));
9998            },
9999
10000            // ── in-process chat-completion (chat.ling-lang.org, Milestone 2) ──
10001            // Real transformer inference (candle-transformers, GGUF, CUDA) —
10002            // distinct from the miniature dialog_* LM above. Generation runs
10003            // on a background thread; these builtins only ever start/poll a
10004            // job, never block http_serve's single-threaded dispatch loop.
10005            // llm_load(gguf_path, tokenizer_path[, device_index]) → handle (-1 on failure)
10006            #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
10007            "llm_load" => {
10008                let gguf_path = self.arg_str(&args, 0, "");
10009                let tokenizer_path = self.arg_str(&args, 1, "");
10010                let device_index = self.arg_num(&args, 2, 0.0)? as i64;
10011                return Ok(Value::Number(
10012                    llm::llm_load(&gguf_path, &tokenizer_path, device_index) as f64,
10013                ));
10014            },
10015            // llm_generate_start(handle, system_prompt, history[, max_tokens, temperature, top_p]) → job_id ("" on failure)
10016            #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
10017            "llm_generate_start" => {
10018                let handle = self.arg_num(&args, 0, -1.0)? as i64;
10019                let system_prompt = self.arg_str(&args, 1, "");
10020                let history = self.arg_str(&args, 2, "");
10021                let max_tokens = self.arg_num(&args, 3, 512.0)? as i64;
10022                let temperature = self.arg_num(&args, 4, 0.7)?;
10023                let top_p = self.arg_num(&args, 5, 0.9)?;
10024                return Ok(Value::Str(llm::llm_generate_start(
10025                    handle,
10026                    &system_prompt,
10027                    &history,
10028                    max_tokens,
10029                    temperature,
10030                    top_p,
10031                )));
10032            },
10033            // llm_generate_poll(job_id) → text generated so far
10034            #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
10035            "llm_generate_poll" => {
10036                let job_id = self.arg_str(&args, 0, "");
10037                return Ok(Value::Str(llm::llm_generate_poll(&job_id)));
10038            },
10039            // llm_generate_done(job_id) → bool
10040            #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
10041            "llm_generate_done" => {
10042                let job_id = self.arg_str(&args, 0, "");
10043                return Ok(Value::Bool(llm::llm_generate_done(&job_id)));
10044            },
10045            // llm_job_status(job_id) → "pending" | "tool_call" | "done"
10046            #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
10047            "llm_job_status" => {
10048                let job_id = self.arg_str(&args, 0, "");
10049                return Ok(Value::Str(llm::llm_job_status(&job_id)));
10050            },
10051            // llm_job_tool_name(job_id) → "" until Milestone 5 lands
10052            #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
10053            "llm_job_tool_name" => {
10054                let job_id = self.arg_str(&args, 0, "");
10055                return Ok(Value::Str(llm::llm_job_tool_name(&job_id)));
10056            },
10057            // llm_job_provide_tool_result(job_id, result_text) → bool (always false until Milestone 5)
10058            #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
10059            "llm_job_provide_tool_result" => {
10060                let job_id = self.arg_str(&args, 0, "");
10061                let result_text = self.arg_str(&args, 1, "");
10062                return Ok(Value::Bool(llm::llm_job_provide_tool_result(
10063                    &job_id,
10064                    &result_text,
10065                )));
10066            },
10067            // llm_job_cancel(job_id)
10068            #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
10069            "llm_job_cancel" => {
10070                let job_id = self.arg_str(&args, 0, "");
10071                llm::llm_job_cancel(&job_id);
10072                return Ok(Value::Unit);
10073            },
10074
10075            // ── Google OAuth sign-in (chat.ling-lang.org) ──────────────────
10076            // Exchanging a code for a token and fetching the profile are both
10077            // outbound HTTPS calls, so this runs on a background thread and
10078            // is polled the same non-blocking way as http_post_async, above.
10079            // oauth_google_start(code, client_id, client_secret, redirect_uri) → job_id
10080            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10081            "oauth_google_start" => {
10082                let code = self.arg_str(&args, 0, "");
10083                let client_id = self.arg_str(&args, 1, "");
10084                let client_secret = self.arg_str(&args, 2, "");
10085                let redirect_uri = self.arg_str(&args, 3, "");
10086                let id = self.oauth_jobs.start_google_login(
10087                    code,
10088                    client_id,
10089                    client_secret,
10090                    redirect_uri,
10091                );
10092                return Ok(Value::Str(id));
10093            },
10094            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10095            "oauth_google_done" => {
10096                let id = self.arg_str(&args, 0, "");
10097                return Ok(Value::Bool(self.oauth_jobs.done(&id)));
10098            },
10099            // "" while pending or on success; the error message on failure.
10100            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10101            "oauth_google_error" => {
10102                let id = self.arg_str(&args, 0, "");
10103                return Ok(Value::Str(self.oauth_jobs.error(&id)));
10104            },
10105            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10106            "oauth_google_sub" => {
10107                let id = self.arg_str(&args, 0, "");
10108                return Ok(Value::Str(self.oauth_jobs.sub(&id)));
10109            },
10110            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10111            "oauth_google_email" => {
10112                let id = self.arg_str(&args, 0, "");
10113                return Ok(Value::Str(self.oauth_jobs.email(&id)));
10114            },
10115            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10116            "oauth_google_name" => {
10117                let id = self.arg_str(&args, 0, "");
10118                return Ok(Value::Str(self.oauth_jobs.name(&id)));
10119            },
10120
10121            // ── image understanding (chat.ling-lang.org attach-an-image) ──
10122            // Moondream2 (quantized). Same background-job pattern as llm_*.
10123            // vision_load(gguf_path, tokenizer_path[, device_index]) → handle (-1 on failure)
10124            #[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
10125            "vision_load" => {
10126                let gguf_path = self.arg_str(&args, 0, "");
10127                let tokenizer_path = self.arg_str(&args, 1, "");
10128                let device_index = self.arg_num(&args, 2, 0.0)? as i64;
10129                return Ok(Value::Number(vision::vision_load(
10130                    &gguf_path,
10131                    &tokenizer_path,
10132                    device_index,
10133                ) as f64));
10134            },
10135            // vision_analyze_start(handle, image_base64, question[, max_tokens, temperature, top_p]) → job_id ("" on failure)
10136            #[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
10137            "vision_analyze_start" => {
10138                let handle = self.arg_num(&args, 0, -1.0)? as i64;
10139                let image_base64 = self.arg_str(&args, 1, "");
10140                let question = self.arg_str(&args, 2, "Describe this image.");
10141                let max_tokens = self.arg_num(&args, 3, 256.0)? as i64;
10142                let temperature = self.arg_num(&args, 4, 0.7)?;
10143                let top_p = self.arg_num(&args, 5, 0.9)?;
10144                return Ok(Value::Str(vision::vision_analyze_start(
10145                    handle,
10146                    &image_base64,
10147                    &question,
10148                    max_tokens,
10149                    temperature,
10150                    top_p,
10151                )));
10152            },
10153            #[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
10154            "vision_analyze_poll" => {
10155                let job_id = self.arg_str(&args, 0, "");
10156                return Ok(Value::Str(vision::vision_analyze_poll(&job_id)));
10157            },
10158            #[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
10159            "vision_analyze_done" => {
10160                let job_id = self.arg_str(&args, 0, "");
10161                return Ok(Value::Bool(vision::vision_analyze_done(&job_id)));
10162            },
10163            // "" unless analysis failed (bad image data, decode error, etc).
10164            #[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
10165            "vision_analyze_error" => {
10166                let job_id = self.arg_str(&args, 0, "");
10167                return Ok(Value::Str(vision::vision_analyze_error(&job_id)));
10168            },
10169
10170            // Decodes `application/x-www-form-urlencoded` text: '+' -> space,
10171            // '%XX' -> byte. Needed to read plain HTML `<form>` POST bodies.
10172            "url_decode" | "网址解码" => {
10173                let s = self.arg_str(&args, 0, "");
10174                let bytes = s.as_bytes();
10175                let mut out = Vec::with_capacity(bytes.len());
10176                let mut i = 0;
10177                while i < bytes.len() {
10178                    match bytes[i] {
10179                        b'+' => {
10180                            out.push(b' ');
10181                            i += 1;
10182                        },
10183                        b'%' if i + 2 < bytes.len() => {
10184                            let hex = std::str::from_utf8(&bytes[i + 1..i + 3]).unwrap_or("");
10185                            match u8::from_str_radix(hex, 16) {
10186                                Ok(b) => {
10187                                    out.push(b);
10188                                    i += 3;
10189                                },
10190                                Err(_) => {
10191                                    out.push(bytes[i]);
10192                                    i += 1;
10193                                },
10194                            }
10195                        },
10196                        b => {
10197                            out.push(b);
10198                            i += 1;
10199                        },
10200                    }
10201                }
10202                return Ok(Value::Str(String::from_utf8_lossy(&out).into_owned()));
10203            },
10204            // Seconds since Unix epoch (float — sub-second precision). No ISO/date
10205            // formatting builtin exists yet; `.ling` code that wants a display
10206            // string currently just uses the raw number.
10207            "now_unix" | "现在时间" => {
10208                return Ok(Value::Number(now_secs()));
10209            },
10210            "file_exists" | "文件存在" => {
10211                #[cfg(target_arch = "wasm32")]
10212                return Ok(Value::Bool(false));
10213                #[cfg(not(target_arch = "wasm32"))]
10214                {
10215                    let path = self.arg_str(&args, 0, "").replace('\\', "/");
10216                    return Ok(Value::Bool(std::path::Path::new(&path).exists()));
10217                }
10218            },
10219            "write_file" | "เขียนไฟล์" | "نوشتن_فایل" | "اكتب_الملف" | "כתוב_קובץ" | "فائل_لکھو" =>
10220            {
10221                #[cfg(target_arch = "wasm32")]
10222                return Ok(Value::Unit);
10223                #[cfg(not(target_arch = "wasm32"))]
10224                {
10225                    let path = self.arg_str(&args, 0, "").replace('\\', "/");
10226                    let content = self.arg_str(&args, 1, "");
10227                    if let Some(parent) = std::path::Path::new(&path).parent() {
10228                        if !parent.as_os_str().is_empty() {
10229                            let _ = std::fs::create_dir_all(parent);
10230                        }
10231                    }
10232                    std::fs::write(&path, content.as_bytes())
10233                        .map_err(|e| EvalErr::from(format!("write_file '{path}': {e}")))?;
10234                    return Ok(Value::Unit);
10235                }
10236            },
10237            "print_file" | "พิมพ์ไฟล์" | "چاپ_فایل" | "اطبع_الملف" | "הדפס_קובץ" | "فائل_چھاپو" =>
10238            {
10239                let content = self.arg_str(&args, 0, "");
10240                print!("{content}");
10241                return Ok(Value::Unit);
10242            },
10243
10244            // ── CLI arguments ─────────────────────────────────────────────────
10245            "get_args"
10246            | "รับอาร์กิวเมนต์"
10247            | "دریافت_آرگومان‌ها"
10248            | "اجلب_المعاملات"
10249            | "קבל_ארגומנטים"
10250            | "دلائل_حاصل_کرو" => {
10251                let v: Vec<Value> = std::env::args().map(Value::Str).collect();
10252                return Ok(Value::List(Rc::new(v)));
10253            },
10254
10255            // ── Filesystem: directory walking, stat, content hashing (native) ──
10256            // These power headless batch tools (asset pipelines, indexers). Errors
10257            // degrade gracefully (empty list / 0 / "") so a walk never aborts on one
10258            // unreadable entry.
10259            #[cfg(not(target_arch = "wasm32"))]
10260            "list_dir"
10261            | "รายการไดเรกทอรี"
10262            | "فهرست_پوشه"
10263            | "اسرد_المجلد"
10264            | "רשום_תיקייה"
10265            | "فولڈر_فہرست" => {
10266                let path = self.arg_str(&args, 0, ".").replace('\\', "/");
10267                let mut paths: Vec<String> = Vec::new();
10268                if let Ok(rd) = std::fs::read_dir(&path) {
10269                    for e in rd.flatten() {
10270                        paths.push(e.path().to_string_lossy().replace('\\', "/"));
10271                    }
10272                }
10273                paths.sort();
10274                let out: Vec<Value> = paths.into_iter().map(Value::Str).collect();
10275                return Ok(Value::List(Rc::new(out)));
10276            },
10277            #[cfg(not(target_arch = "wasm32"))]
10278            "is_dir"
10279            | "เป็นไดเรกทอรี"
10280            | "آیا_پوشه_است"
10281            | "هل_مجلد"
10282            | "האם_תיקייה"
10283            | "کیا_فولڈر_ہے" => {
10284                let path = self.arg_str(&args, 0, "").replace('\\', "/");
10285                return Ok(Value::Bool(std::path::Path::new(&path).is_dir()));
10286            },
10287            #[cfg(not(target_arch = "wasm32"))]
10288            "is_file" | "เป็นไฟล์" | "آیا_فایل_است" | "هل_ملف" | "האם_קובץ" | "کیا_فائل_ہے" =>
10289            {
10290                let path = self.arg_str(&args, 0, "").replace('\\', "/");
10291                return Ok(Value::Bool(std::path::Path::new(&path).is_file()));
10292            },
10293            #[cfg(not(target_arch = "wasm32"))]
10294            "path_name" | "ชื่อไฟล์" | "نام_مسیر" | "اسم_المسار" | "שם_נתיב" | "پاتھ_نام" =>
10295            {
10296                let path = self.arg_str(&args, 0, "").replace('\\', "/");
10297                let name = std::path::Path::new(&path)
10298                    .file_name()
10299                    .map(|s| s.to_string_lossy().into_owned())
10300                    .unwrap_or_default();
10301                return Ok(Value::Str(name));
10302            },
10303            #[cfg(not(target_arch = "wasm32"))]
10304            "path_ext"
10305            | "นามสกุลไฟล์"
10306            | "پسوند_مسیر"
10307            | "امتداد_المسار"
10308            | "סיומת_נתיב"
10309            | "پاتھ_ایکسٹینشن" => {
10310                let path = self.arg_str(&args, 0, "").replace('\\', "/");
10311                let ext = std::path::Path::new(&path)
10312                    .extension()
10313                    .map(|s| s.to_string_lossy().to_lowercase())
10314                    .unwrap_or_default();
10315                return Ok(Value::Str(ext));
10316            },
10317            #[cfg(not(target_arch = "wasm32"))]
10318            "file_size" | "ขนาดไฟล์" | "اندازه_فایل" | "حجم_الملف" | "גודל_קובץ" | "فائل_سائز" =>
10319            {
10320                let path = self.arg_str(&args, 0, "");
10321                let sz = std::fs::metadata(&path).map(|m| m.len()).unwrap_or(0);
10322                return Ok(Value::Number(sz as f64));
10323            },
10324            #[cfg(not(target_arch = "wasm32"))]
10325            "file_modified"
10326            | "เวลาที่แก้ไข"
10327            | "زمان_تغییر_فایل"
10328            | "وقت_تعديل_الملف"
10329            | "זמן_עדכון_קובץ"
10330            | "فائل_ترمیم_وقت" => {
10331                let path = self.arg_str(&args, 0, "");
10332                let secs = std::fs::metadata(&path)
10333                    .and_then(|m| m.modified())
10334                    .ok()
10335                    .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
10336                    .map(|d| d.as_secs_f64())
10337                    .unwrap_or(0.0);
10338                return Ok(Value::Number(secs));
10339            },
10340            #[cfg(not(target_arch = "wasm32"))]
10341            "file_created"
10342            | "เวลาที่สร้าง"
10343            | "زمان_ایجاد_فایل"
10344            | "وقت_إنشاء_الملف"
10345            | "זמן_יצירת_קובץ"
10346            | "فائل_تخلیق_وقت" => {
10347                let path = self.arg_str(&args, 0, "");
10348                let secs = std::fs::metadata(&path)
10349                    .ok()
10350                    .and_then(|m| m.created().or_else(|_| m.modified()).ok())
10351                    .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
10352                    .map(|d| d.as_secs_f64())
10353                    .unwrap_or(0.0);
10354                return Ok(Value::Number(secs));
10355            },
10356            #[cfg(not(target_arch = "wasm32"))]
10357            "make_dir"
10358            | "สร้างไดเรกทอรี"
10359            | "ساخت_پوشه"
10360            | "أنشئ_مجلدا"
10361            | "צור_תיקייה"
10362            | "فولڈر_بناؤ" => {
10363                let path = self.arg_str(&args, 0, "");
10364                return Ok(Value::Bool(std::fs::create_dir_all(&path).is_ok()));
10365            },
10366            // str_strip_prefix("Bearer x", "Bearer ") → "x" (unchanged if absent).
10367            "str_strip_prefix"
10368            | "ตัดคำนำหน้า"
10369            | "حذف_پیشوند"
10370            | "أزل_البادئة"
10371            | "הסר_קידומת"
10372            | "سابقہ_ہٹاؤ" => {
10373                let s = self.arg_str(&args, 0, "");
10374                let prefix = self.arg_str(&args, 1, "");
10375                return Ok(Value::Str(
10376                    s.strip_prefix(&prefix).unwrap_or(&s).to_string(),
10377                ));
10378            },
10379            // Classify a file by magic bytes: "gzip" | "zip" | "other" | "missing".
10380            // The build-verification gate: only real built archives may publish.
10381            #[cfg(not(target_arch = "wasm32"))]
10382            "file_magic"
10383            | "มายาไฟล์"
10384            | "امضای_فایل"
10385            | "توقيع_الملف"
10386            | "חתימת_קובץ"
10387            | "فائل_میجک" => {
10388                let path = self.arg_str(&args, 0, "");
10389                let kind = match std::fs::File::open(&path) {
10390                    Ok(mut f) => {
10391                        use std::io::Read;
10392                        let mut buf = [0u8; 8];
10393                        let n = f.read(&mut buf).unwrap_or(0);
10394                        if n >= 2 && buf[0] == 0x1f && buf[1] == 0x8b {
10395                            "gzip"
10396                        } else if n >= 4 && &buf[0..2] == b"PK" {
10397                            "zip"
10398                        } else if n >= 8 && &buf[0..8] == b"\x89PNG\r\n\x1a\n" {
10399                            "png"
10400                        } else {
10401                            "other"
10402                        }
10403                    },
10404                    Err(_) => "missing",
10405                };
10406                return Ok(Value::Str(kind.to_string()));
10407            },
10408            // Binary-safe file copy (backups): copy_file(src, dst) → bool.
10409            #[cfg(not(target_arch = "wasm32"))]
10410            "copy_file" | "คัดลอกไฟล์" | "کپی_فایل" | "انسخ_الملف" | "העתק_קובץ" | "فائل_کاپی" =>
10411            {
10412                let src = self.arg_str(&args, 0, "");
10413                let dst = self.arg_str(&args, 1, "");
10414                if let Some(parent) = std::path::Path::new(&dst).parent() {
10415                    let _ = std::fs::create_dir_all(parent);
10416                }
10417                return Ok(Value::Bool(std::fs::copy(&src, &dst).is_ok()));
10418            },
10419            // ── Read a .tgz (gzip tarball): list file entries / read one file ──
10420            // Powers the GitHub-style "Code / Files" browser: tar_gz_list gives
10421            // the file tree, tar_gz_read pulls one file's text for the viewer.
10422            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10423            "tar_gz_list"
10424            | "รายการทาร์"
10425            | "فهرست_TAR_GZ"
10426            | "اسرد_TAR_GZ"
10427            | "רשום_TAR_GZ"
10428            | "TAR_GZ_فہرست" => {
10429                let path = self.arg_str(&args, 0, "");
10430                let mut names: Vec<String> = Vec::new();
10431                if let Ok(file) = std::fs::File::open(&path) {
10432                    let gz = flate2::read::GzDecoder::new(file);
10433                    let mut ar = tar::Archive::new(gz);
10434                    if let Ok(entries) = ar.entries() {
10435                        for entry in entries.flatten() {
10436                            if entry.header().entry_type().is_file() {
10437                                if let Ok(p) = entry.path() {
10438                                    names.push(
10439                                        p.to_string_lossy()
10440                                            .trim_start_matches("./")
10441                                            .replace('\\', "/"),
10442                                    );
10443                                }
10444                            }
10445                        }
10446                    }
10447                }
10448                names.sort();
10449                names.dedup();
10450                let out: Vec<Value> = names.into_iter().map(Value::Str).collect();
10451                return Ok(Value::List(Rc::new(out)));
10452            },
10453            // tar_gz_read(archive, entry) → that file's text (utf-8 lossy,
10454            // capped at 256 KiB). Only reads entries that exist in the archive,
10455            // so a caller can't traverse outside it. "" if not found/unreadable.
10456            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10457            "tar_gz_read"
10458            | "อ่านทาร์"
10459            | "خواندن_TAR_GZ"
10460            | "اقرأ_TAR_GZ"
10461            | "קרא_TAR_GZ"
10462            | "TAR_GZ_پڑھو" => {
10463                let path = self.arg_str(&args, 0, "");
10464                let want = self.arg_str(&args, 1, "");
10465                let want = want.trim_start_matches("./").replace('\\', "/");
10466                let mut content = String::new();
10467                if let Ok(file) = std::fs::File::open(&path) {
10468                    let gz = flate2::read::GzDecoder::new(file);
10469                    let mut ar = tar::Archive::new(gz);
10470                    if let Ok(entries) = ar.entries() {
10471                        for entry in entries.flatten() {
10472                            let mut entry = entry;
10473                            let name = match entry.path() {
10474                                Ok(p) => p
10475                                    .to_string_lossy()
10476                                    .trim_start_matches("./")
10477                                    .replace('\\', "/"),
10478                                Err(_) => continue,
10479                            };
10480                            if name == want {
10481                                use std::io::Read;
10482                                let mut buf = Vec::new();
10483                                let cap = 256 * 1024;
10484                                if entry.take(cap as u64 + 1).read_to_end(&mut buf).is_ok() {
10485                                    let slice = if buf.len() > cap {
10486                                        &buf[..cap]
10487                                    } else {
10488                                        &buf[..]
10489                                    };
10490                                    content = String::from_utf8_lossy(slice).into_owned();
10491                                }
10492                                break;
10493                            }
10494                        }
10495                    }
10496                }
10497                return Ok(Value::Str(content));
10498            },
10499            // run_process(cmd, args_list, timeout_secs, log_path) -> number (exit code;
10500            // -1 = spawn/log-open failure; -2 = killed after timeout). No shell involved
10501            // (Command::new + .args, never a shell string), so args can't be reinterpreted;
10502            // this exists solely for the registry's own first-party controllers to drive
10503            // `docker run` for sandboxed package builds — never exposed to uploaded content.
10504            // stdout+stderr both append to log_path (two independent opens of the same
10505            // file — an approximation of 2>&1, fine for a build log, not byte-interleaved).
10506            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10507            "run_process" | "รันโปรเซส" => {
10508                let cmd = self.arg_str(&args, 0, "");
10509                let argv = self.arg_list_str(&args, 1);
10510                let timeout_secs = self.arg_num(&args, 2, 60.0)?.max(1.0) as u64;
10511                let log_path = self.arg_str(&args, 3, "");
10512
10513                let make_stdio = || -> std::process::Stdio {
10514                    if log_path.is_empty() {
10515                        std::process::Stdio::null()
10516                    } else {
10517                        std::fs::OpenOptions::new()
10518                            .create(true)
10519                            .append(true)
10520                            .open(&log_path)
10521                            .map(std::process::Stdio::from)
10522                            .unwrap_or_else(|_| std::process::Stdio::null())
10523                    }
10524                };
10525
10526                let spawned = std::process::Command::new(&cmd)
10527                    .args(&argv)
10528                    .stdin(std::process::Stdio::null())
10529                    .stdout(make_stdio())
10530                    .stderr(make_stdio())
10531                    .spawn();
10532
10533                let mut child = match spawned {
10534                    Ok(c) => c,
10535                    Err(_) => return Ok(Value::Number(-1.0)),
10536                };
10537
10538                let start = std::time::Instant::now();
10539                let timeout = std::time::Duration::from_secs(timeout_secs);
10540                loop {
10541                    match child.try_wait() {
10542                        Ok(Some(status)) => {
10543                            return Ok(Value::Number(status.code().unwrap_or(-1) as f64));
10544                        },
10545                        Ok(None) => {
10546                            if start.elapsed() > timeout {
10547                                let _ = child.kill();
10548                                let _ = child.wait();
10549                                return Ok(Value::Number(-2.0));
10550                            }
10551                            std::thread::sleep(std::time::Duration::from_millis(300));
10552                        },
10553                        Err(_) => return Ok(Value::Number(-1.0)),
10554                    }
10555                }
10556            },
10557            // tar_gz_extract_to(tar_path, entry_name, out_path) -> bool. Byte-perfect
10558            // single-entry extraction straight to disk — unlike tar_gz_read, no
10559            // UTF-8 round-trip, so binary entries (icons, images) come through intact.
10560            // Same in-archive-name string match as tar_gz_read (not a filesystem path
10561            // join), so an entry name can't traverse outside the archive's own listing.
10562            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10563            "tar_gz_extract_to" | "แตกไฟล์ทาร์" => {
10564                let path = self.arg_str(&args, 0, "");
10565                let want = self.arg_str(&args, 1, "");
10566                let want = want.trim_start_matches("./").replace('\\', "/");
10567                let out_path = self.arg_str(&args, 2, "");
10568                let mut found = false;
10569                if let Ok(file) = std::fs::File::open(&path) {
10570                    let gz = flate2::read::GzDecoder::new(file);
10571                    let mut ar = tar::Archive::new(gz);
10572                    if let Ok(entries) = ar.entries() {
10573                        for entry in entries.flatten() {
10574                            let mut entry = entry;
10575                            let name = match entry.path() {
10576                                Ok(p) => p
10577                                    .to_string_lossy()
10578                                    .trim_start_matches("./")
10579                                    .replace('\\', "/"),
10580                                Err(_) => continue,
10581                            };
10582                            if name == want {
10583                                if let Some(parent) = std::path::Path::new(&out_path).parent() {
10584                                    let _ = std::fs::create_dir_all(parent);
10585                                }
10586                                if let Ok(mut out) = std::fs::File::create(&out_path) {
10587                                    use std::io::copy;
10588                                    found = copy(&mut entry, &mut out).is_ok();
10589                                }
10590                                break;
10591                            }
10592                        }
10593                    }
10594                }
10595                return Ok(Value::Bool(found));
10596            },
10597            // icon_convert_to_png(src_path, out_path, size) -> bool. Rasterizes any
10598            // ling-icon-supported source (.svg/.png/.jpg/.bmp/.ico) to a single
10599            // size×size PNG — used to turn a package manifest's icon into a web avatar.
10600            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10601            "icon_convert_to_png" | "แปลงไอคอนเป็นพีเอ็นจี" => {
10602                let src = self.arg_str(&args, 0, "");
10603                let out = self.arg_str(&args, 1, "");
10604                let size = self.arg_num(&args, 2, 300.0)? as u32;
10605                let ok = match ling_icon::png_bytes(std::path::Path::new(&src), size) {
10606                    Ok(bytes) => std::fs::write(&out, bytes).is_ok(),
10607                    Err(_) => false,
10608                };
10609                return Ok(Value::Bool(ok));
10610            },
10611            // ── TOTP (RFC 6238, HMAC-SHA1, 6 digits, 30s) for 2FA ──
10612            // Base32 secret compatible with Google Authenticator / Authy etc.
10613            // `base32_encode`/`totp_code`/`totp_check` only exist under this
10614            // same `feature = "web"` gate (see their definitions above) — a
10615            // build without it must skip these arms too, not just fail to
10616            // link; matches how `file_hash` etc. gate their own arms below.
10617            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10618            "totp_secret" | "โทเทนลับ" | "راز_TOTP" | "سر_TOTP" | "סוד_TOTP" | "TOTP_راز" =>
10619            {
10620                let mut bytes = [0u8; 20];
10621                rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut bytes);
10622                return Ok(Value::Str(base32_encode(&bytes)));
10623            },
10624            // otpauth:// URI to paste into an authenticator app (or make a QR of).
10625            // No cfg gate needed — pure string formatting, no dependency on
10626            // the web-only TOTP helpers.
10627            "totp_uri"
10628            | "โทเทนยูอาร์ไอ"
10629            | "آدرس_TOTP"
10630            | "رابط_TOTP"
10631            | "כתובת_TOTP"
10632            | "TOTP_یو_آر_آئی" => {
10633                let secret = self.arg_str(&args, 0, "");
10634                let account = self.arg_str(&args, 1, "user");
10635                let issuer = self.arg_str(&args, 2, "lingfu");
10636                return Ok(Value::Str(format!(
10637                    "otpauth://totp/{issuer}:{account}?secret={secret}&issuer={issuer}&algorithm=SHA1&digits=6&period=30"
10638                )));
10639            },
10640            // Verify a 6-digit code against the secret, allowing ±1 time step.
10641            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10642            "totp_verify"
10643            | "โทเทนตรวจ"
10644            | "تایید_TOTP"
10645            | "تحقق_TOTP"
10646            | "אמת_TOTP"
10647            | "TOTP_تصدیق" => {
10648                let secret = self.arg_str(&args, 0, "");
10649                let code = self.arg_str(&args, 1, "");
10650                let ok = totp_check(&secret, code.trim());
10651                return Ok(Value::Bool(ok));
10652            },
10653            // The current valid code, for tests/tools.
10654            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
10655            "totp_now" | "โทเทนตอนนี้" | "TOTP_اکنون" | "TOTP_الآن" | "TOTP_עכשיו" | "TOTP_ابھی" =>
10656            {
10657                let secret = self.arg_str(&args, 0, "");
10658                let step = (crate::runtime::now_secs() as u64) / 30;
10659                return Ok(Value::Str(totp_code(&secret, step).unwrap_or_default()));
10660            },
10661            // BLAKE3 hex of a file's bytes (binary-safe content fingerprint).
10662            #[cfg(not(target_arch = "wasm32"))]
10663            "file_hash" | "แฮชไฟล์" | "درهم_فایل" | "بصمة_الملف" | "גיבוב_קובץ" | "فائل_ہیش" =>
10664            {
10665                let path = self.arg_str(&args, 0, "");
10666                match std::fs::read(&path) {
10667                    Ok(bytes) => {
10668                        return Ok(Value::Str(hex_encode(&ling_crypto::Blake3::hash(&bytes))))
10669                    },
10670                    Err(_) => return Ok(Value::Str(String::new())),
10671                }
10672            },
10673            // BLAKE3 hex of an arbitrary string (deterministic id/colour/role seed).
10674            "hash_hex"
10675            | "แฮชสตริง"
10676            | "درهم_هگزادسیمال"
10677            | "بصمة_سداسية"
10678            | "גיבוב_הקסדצימלי"
10679            | "ہیکس_ہیش" => {
10680                let s = self.arg_str(&args, 0, "");
10681                return Ok(Value::Str(hex_encode(&ling_crypto::Blake3::hash(
10682                    s.as_bytes(),
10683                ))));
10684            },
10685            // Read an environment variable, falling back to a default.
10686            #[cfg(not(target_arch = "wasm32"))]
10687            "env_get"
10688            | "รับตัวแปรแวดล้อม"
10689            | "دریافت_متغیر_محیطی"
10690            | "اجلب_متغير_البيئة"
10691            | "קבל_משתנה_סביבה"
10692            | "ماحولیاتی_متغیر_حاصل_کرو" => {
10693                let name = self.arg_str(&args, 0, "");
10694                let dflt = self.arg_str(&args, 1, "");
10695                return Ok(Value::Str(std::env::var(&name).unwrap_or(dflt)));
10696            },
10697
10698            // ── String utilities ──────────────────────────────────────────────
10699            // Parses a string to a number (0 on failure — degrades gracefully,
10700            // like the other filesystem/parsing builtins in this file).
10701            "to_number" | "转数字" => {
10702                let s = self.arg_str(&args, 0, "");
10703                return Ok(Value::Number(s.trim().parse().unwrap_or(0.0)));
10704            },
10705            // Plain SHA-256 hex — matches the browser's native SubtleCrypto
10706            // digest("SHA-256", ...), which is what proof-of-work mining uses
10707            // client-side (Web Crypto has no Blake3/SHA-3, so this is the one
10708            // hash both sides can compute natively and fast).
10709            "sha256_hex" | "SHA256哈希" => {
10710                use sha2::Digest;
10711                let s = self.arg_str(&args, 0, "");
10712                let mut h = sha2::Sha256::new();
10713                h.update(s.as_bytes());
10714                return Ok(Value::Str(hex_encode(&h.finalize())));
10715            },
10716            // Parses a hex string (no "0x" prefix) to a number — `to_number`
10717            // uses Rust's plain f64 parser, which doesn't understand hex.
10718            "hex_to_number" | "十六进制转数字" => {
10719                let s = self.arg_str(&args, 0, "");
10720                let v = u64::from_str_radix(s.trim(), 16).unwrap_or(0);
10721                return Ok(Value::Number(v as f64));
10722            },
10723            "split" | "str_split" | "แยก" | "جداسازی" | "قسّم" | "פצל" | "تقسیم_کرو" =>
10724            {
10725                let s = self.arg_str(&args, 0, "");
10726                let sep = self.arg_str(&args, 1, "\n");
10727                let sep = if sep.is_empty() { "\n".into() } else { sep };
10728                let parts: Vec<Value> = s
10729                    .split(sep.as_str())
10730                    .map(|p| Value::Str(p.to_string()))
10731                    .collect();
10732                return Ok(Value::List(Rc::new(parts)));
10733            },
10734            "trim"
10735            | "str_trim"
10736            | "ตัดช่องว่าง"
10737            | "حذف_فاصله"
10738            | "اقتطع_الفراغات"
10739            | "חתוך_רווחים"
10740            | "خالی_جگہ_کاٹو" => {
10741                let s = self.arg_str(&args, 0, "");
10742                return Ok(Value::Str(s.trim().to_string()));
10743            },
10744            "starts_with"
10745            | "str_starts_with"
10746            | "เริ่มด้วย"
10747            | "شروع_می‌شود_با"
10748            | "يبدأ_بـ"
10749            | "מתחיל_ב"
10750            | "شروع_ہوتا_ہے" => {
10751                let s = self.arg_str(&args, 0, "");
10752                let prefix = self.arg_str(&args, 1, "");
10753                return Ok(Value::Bool(s.starts_with(prefix.as_str())));
10754            },
10755            "ends_with"
10756            | "str_ends_with"
10757            | "ลงท้ายด้วย"
10758            | "پایان_می‌یابد_با"
10759            | "ينتهي_بـ"
10760            | "מסתיים_ב"
10761            | "ختم_ہوتا_ہے" => {
10762                let s = self.arg_str(&args, 0, "");
10763                let suffix = self.arg_str(&args, 1, "");
10764                return Ok(Value::Bool(s.ends_with(suffix.as_str())));
10765            },
10766            "str_replace"
10767            | "แทนสตริง"
10768            | "جایگزینی_رشته"
10769            | "استبدل_النص"
10770            | "החלף_מחרוזת"
10771            | "اسٹرنگ_تبدیل" => {
10772                let s = self.arg_str(&args, 0, "");
10773                let from = self.arg_str(&args, 1, "");
10774                let to = self.arg_str(&args, 2, "");
10775                return Ok(Value::Str(s.replace(from.as_str(), to.as_str())));
10776            },
10777            "str_find"
10778            | "หาในสตริง"
10779            | "جستجوی_رشته"
10780            | "ابحث_في_النص"
10781            | "חפש_מחרוזת"
10782            | "اسٹرنگ_تلاش" => {
10783                let s = self.arg_str(&args, 0, "");
10784                let needle = self.arg_str(&args, 1, "");
10785                // Return char index (not byte index) for consistency with substr
10786                let pos = s
10787                    .find(needle.as_str())
10788                    .map(|byte_i| s[..byte_i].chars().count() as f64)
10789                    .unwrap_or(-1.0);
10790                return Ok(Value::Number(pos));
10791            },
10792            "substr"
10793            | "str_slice"
10794            | "ส่วนสตริง"
10795            | "زیررشته"
10796            | "جزء_النص"
10797            | "תת_מחרוזת"
10798            | "ذیلی_اسٹرنگ" => {
10799                let s = self.arg_str(&args, 0, "");
10800                let start = self.arg_num(&args, 1, 0.0)? as usize;
10801                let len = args
10802                    .get(2)
10803                    .map(|v| self.to_number(v).unwrap_or(999999.0) as usize)
10804                    .unwrap_or_else(|| s.chars().count().saturating_sub(start));
10805                let chars: Vec<char> = s.chars().collect();
10806                let end = (start + len).min(chars.len());
10807                let slice: String = chars.get(start..end).unwrap_or(&[]).iter().collect();
10808                return Ok(Value::Str(slice));
10809            },
10810            "to_str"
10811            | "str"
10812            | "num_str"
10813            | "แปลงสตริง"
10814            | "تبدیل_به_رشته"
10815            | "حوّل_لنص"
10816            | "המר_למחרוזת"
10817            | "اسٹرنگ_میں_بدلو" => {
10818                let v = args.into_iter().next().unwrap_or(Value::Unit);
10819                return Ok(Value::Str(v.to_string()));
10820            },
10821            "str_repeat"
10822            | "ทำซ้ำสตริง"
10823            | "تکرار_رشته"
10824            | "كرّر_النص"
10825            | "חזור_על_מחרוזת"
10826            | "اسٹرنگ_دہراؤ" => {
10827                let s = self.arg_str(&args, 0, "");
10828                let n = self.arg_num(&args, 1, 1.0)? as usize;
10829                return Ok(Value::Str(s.repeat(n)));
10830            },
10831            "str_upper" => {
10832                let s = self.arg_str(&args, 0, "");
10833                return Ok(Value::Str(s.to_uppercase()));
10834            },
10835            "str_lower" => {
10836                let s = self.arg_str(&args, 0, "");
10837                return Ok(Value::Str(s.to_lowercase()));
10838            },
10839            "str_len"
10840            | "len"
10841            | "ความยาว"
10842            | "长度"
10843            | "長さ"
10844            | "길이"
10845            | "طول_رشته"
10846            | "طول_النص"
10847            | "אורך_מחרוזת"
10848            | "اسٹرنگ_لمبائی" => match args.first() {
10849                Some(Value::Str(s)) => return Ok(Value::Number(s.chars().count() as f64)),
10850                Some(Value::List(v)) => return Ok(Value::Number(v.len() as f64)),
10851                _ => return Ok(Value::Number(0.0)),
10852            },
10853
10854            // ── FNV-1a hash (deterministic, normalized 0.0–1.0) ──────────────
10855            "hash_str" | "แฮช" | "درهم_رشته" | "بصمة_نص" | "גיבוב_מחרוזת" | "اسٹرنگ_ہیش" =>
10856            {
10857                let s = self.arg_str(&args, 0, "");
10858                let mut h: u64 = 14695981039346656037_u64;
10859                for b in s.bytes() {
10860                    h ^= b as u64;
10861                    h = h.wrapping_mul(1099511628211);
10862                }
10863                return Ok(Value::Number((h & 0xFFFFFF) as f64 / 16777215.0));
10864            },
10865            "hash_int" | "แฮชจำนวน" | "درهم_عدد" | "بصمة_عدد" | "גיבוב_מספר" | "نمبر_ہیش" =>
10866            {
10867                let s = self.arg_str(&args, 0, "");
10868                let n = self.arg_num(&args, 1, 100.0)? as u64;
10869                let mut h: u64 = 14695981039346656037_u64;
10870                for b in s.bytes() {
10871                    h ^= b as u64;
10872                    h = h.wrapping_mul(1099511628211);
10873                }
10874                return Ok(Value::Number((h % n.max(1)) as f64));
10875            },
10876
10877            // ── List utilities ────────────────────────────────────────────────
10878            "list_new"
10879            | "รายการใหม่"
10880            | "新建列表"
10881            | "新規リスト"
10882            | "새목록"
10883            | "فهرست_جدید"
10884            | "قائمة_جديدة"
10885            | "רשימה_חדשה"
10886            | "نئی_فہرست"
10887            | "nouvelle_liste"
10888            | "neue_liste"
10889            | "новый_список" => {
10890                return Ok(Value::List(Rc::new(Vec::new())));
10891            },
10892            "list_push"
10893            | "เพิ่มรายการ"
10894            | "列表添加"
10895            | "リスト追加"
10896            | "목록추가"
10897            | "افزودن_به_فهرست"
10898            | "أضف_للقائمة"
10899            | "הוסף_לרשימה"
10900            | "فہرست_میں_شامل_کرو"
10901            | "ajouter_liste"
10902            | "liste_anhängen"
10903            | "добавить_в_список" => {
10904                let lst = args
10905                    .first()
10906                    .cloned()
10907                    .unwrap_or(Value::List(Rc::new(vec![])));
10908                let val = args.get(1).cloned().unwrap_or(Value::Unit);
10909                if let Value::List(mut v) = lst {
10910                    Rc::make_mut(&mut v).push(val);
10911                    return Ok(Value::List(v));
10912                }
10913                return Ok(Value::List(Rc::new(vec![val])));
10914            },
10915            "list_get"
10916            | "รับรายการ"
10917            | "取元素"
10918            | "要素取得"
10919            | "요소가져오기"
10920            | "دریافت_از_فهرست"
10921            | "اجلب_من_القائمة"
10922            | "קבל_מרשימה"
10923            | "فہرست_سے_حاصل_کرو"
10924            | "obtenir_liste"
10925            | "liste_abrufen"
10926            | "получить_из_списка" => {
10927                // Borrow the list; clone only the element (was cloning the whole list).
10928                let i = self.arg_num(&args, 1, 0.0)? as usize;
10929                if let Some(Value::List(v)) = args.first() {
10930                    return Ok(v.get(i).cloned().unwrap_or(Value::Str(String::new())));
10931                }
10932                return Ok(Value::Str(String::new()));
10933            },
10934            // list_max(numbers, default) / list_min(numbers, default) — `default`
10935            // is returned for an empty list (there's no numeric identity element
10936            // to fall back to otherwise).
10937            "list_max" | "列表最大值" => {
10938                let lst = args
10939                    .first()
10940                    .cloned()
10941                    .unwrap_or(Value::List(Rc::new(vec![])));
10942                let default = self.arg_num(&args, 1, 0.0)?;
10943                if let Value::List(v) = lst {
10944                    let mut best = default;
10945                    let mut any = false;
10946                    for item in v.iter() {
10947                        if let Value::Number(n) = item {
10948                            if !any || *n > best {
10949                                best = *n;
10950                                any = true;
10951                            }
10952                        }
10953                    }
10954                    return Ok(Value::Number(best));
10955                }
10956                return Ok(Value::Number(default));
10957            },
10958            "list_min" | "列表最小值" => {
10959                let lst = args
10960                    .first()
10961                    .cloned()
10962                    .unwrap_or(Value::List(Rc::new(vec![])));
10963                let default = self.arg_num(&args, 1, 0.0)?;
10964                if let Value::List(v) = lst {
10965                    let mut best = default;
10966                    let mut any = false;
10967                    for item in v.iter() {
10968                        if let Value::Number(n) = item {
10969                            if !any || *n < best {
10970                                best = *n;
10971                                any = true;
10972                            }
10973                        }
10974                    }
10975                    return Ok(Value::Number(best));
10976                }
10977                return Ok(Value::Number(default));
10978            },
10979            // list_set(lst, idx, val) → new list with index replaced. Engine builtin
10980            // (O(n) one copy) to replace the O(n²) ling `ตั้งรายการ` that looped
10981            // list_push + list_get (each of which copied the whole list).
10982            "list_set"
10983            | "ตั้งรายการ"
10984            | "设元素"
10985            | "要素設定"
10986            | "요소설정"
10987            | "تنظیم_عنصر_فهرست"
10988            | "عيّن_عنصر_القائمة"
10989            | "קבע_איבר_רשימה"
10990            | "فہرست_سیٹ" => {
10991                let idx = self.arg_num(&args, 1, 0.0)? as usize;
10992                let mut ai = args.into_iter();
10993                let lst = ai.next().unwrap_or(Value::List(Rc::new(vec![])));
10994                ai.next(); // skip idx
10995                let val = ai.next().unwrap_or(Value::Unit);
10996                if let Value::List(mut v) = lst {
10997                    if idx < v.len() {
10998                        Rc::make_mut(&mut v)[idx] = val;
10999                    }
11000                    return Ok(Value::List(v));
11001                }
11002                return Ok(Value::List(Rc::new(vec![])));
11003            },
11004            "list_join"
11005            | "join"
11006            | "รวมรายการ"
11007            | "连接"
11008            | "連結"
11009            | "연결"
11010            | "پیوستن_فهرست"
11011            | "اربط_القائمة"
11012            | "חבר_רשימה"
11013            | "فہرست_جوڑو" => {
11014                let lst = args
11015                    .first()
11016                    .cloned()
11017                    .unwrap_or(Value::List(Rc::new(vec![])));
11018                let sep = args.get(1).map(|v| v.to_string()).unwrap_or_default();
11019                if let Value::List(v) = lst {
11020                    return Ok(Value::Str(
11021                        v.iter()
11022                            .map(|x| x.to_string())
11023                            .collect::<Vec<_>>()
11024                            .join(&sep),
11025                    ));
11026                }
11027                return Ok(Value::Str(String::new()));
11028            },
11029            // list_map/list_filter/list_find — take a closure. Necessary as real
11030            // builtins (not expressible in `.ling` itself): a bare-identifier call
11031            // `f(x)` where `f` is a local variable always resolves through
11032            // `call_named`, which only looks at top-level `fn` definitions by
11033            // design ("call-site locals are intentionally NOT visible to fns") —
11034            // so a closure held in a variable/parameter can't be invoked from
11035            // `.ling` source directly. These call it from the Rust side instead,
11036            // the same way `http_serve` already invokes route-handler closures.
11037            "list_map" | "映射列表" => {
11038                let lst = args
11039                    .first()
11040                    .cloned()
11041                    .unwrap_or(Value::List(Rc::new(vec![])));
11042                let f = args.get(1).cloned().unwrap_or(Value::Unit);
11043                if let Value::List(v) = lst {
11044                    let mut out = Vec::with_capacity(v.len());
11045                    for item in v.iter() {
11046                        out.push(self.call_value(f.clone(), vec![item.clone()])?);
11047                    }
11048                    return Ok(Value::List(Rc::new(out)));
11049                }
11050                return Ok(Value::List(Rc::new(vec![])));
11051            },
11052            "list_filter" | "过滤列表" => {
11053                let lst = args
11054                    .first()
11055                    .cloned()
11056                    .unwrap_or(Value::List(Rc::new(vec![])));
11057                let f = args.get(1).cloned().unwrap_or(Value::Unit);
11058                if let Value::List(v) = lst {
11059                    let mut out = Vec::new();
11060                    for item in v.iter() {
11061                        if matches!(
11062                            self.call_value(f.clone(), vec![item.clone()])?,
11063                            Value::Bool(true)
11064                        ) {
11065                            out.push(item.clone());
11066                        }
11067                    }
11068                    return Ok(Value::List(Rc::new(out)));
11069                }
11070                return Ok(Value::List(Rc::new(vec![])));
11071            },
11072            // First element for which `f` returns true, or Unit if none match.
11073            "list_find" | "查找列表" => {
11074                let lst = args
11075                    .first()
11076                    .cloned()
11077                    .unwrap_or(Value::List(Rc::new(vec![])));
11078                let f = args.get(1).cloned().unwrap_or(Value::Unit);
11079                if let Value::List(v) = lst {
11080                    for item in v.iter() {
11081                        if matches!(
11082                            self.call_value(f.clone(), vec![item.clone()])?,
11083                            Value::Bool(true)
11084                        ) {
11085                            return Ok(item.clone());
11086                        }
11087                    }
11088                }
11089                return Ok(Value::Unit);
11090            },
11091            // blob_f32("<deflate+base64>") / blob_i32(...) — decode an embedded,
11092            // losslessly-compressed numeric blob into a list. Produced by
11093            // `ling convert`; lets converted assets carry geometry/PCM/etc. compactly.
11094            #[cfg(not(target_arch = "wasm32"))]
11095            "blob_f32" | "blob_i32" => {
11096                let s = self.arg_str(&args, 0, "");
11097                let is_i32 = name == "blob_i32";
11098                match decode_blob(&s) {
11099                    Ok(bytes) => {
11100                        let mut out = Vec::with_capacity(bytes.len() / 4);
11101                        for ch in bytes.chunks_exact(4) {
11102                            let arr = [ch[0], ch[1], ch[2], ch[3]];
11103                            let n = if is_i32 {
11104                                i32::from_le_bytes(arr) as f64
11105                            } else {
11106                                f32::from_le_bytes(arr) as f64
11107                            };
11108                            out.push(Value::Number(n));
11109                        }
11110                        return Ok(Value::List(Rc::new(out)));
11111                    },
11112                    Err(e) => {
11113                        eprintln!("blob decode failed: {e}");
11114                        return Ok(Value::List(Rc::new(vec![])));
11115                    },
11116                }
11117            },
11118
11119            // ══════════════════════════════════════════════════════════════════
11120            // SVG EXPORT  (svg_begin / svg_rect / svg_circle / svg_line /
11121            //              svg_polyline / svg_text / svg_end / hsl_color)
11122            // Chinese aliases: 开始SVG 结束SVG SVG矩形 SVG圆形 SVG线段 SVG折线 SVG文本 HSL颜色
11123            // Thai aliases:    เริ่มSVG จบSVG SVGสี่เหลี่ยม SVGวงกลม SVGเส้น SVGเส้นหัก SVGข้อความ สีHSL
11124            // ══════════════════════════════════════════════════════════════════
11125            "svg_begin" | "开始SVG" | "เริ่มSVG" | "شروع_SVG" | "ابدأ_SVG" | "התחל_SVG"
11126            | "SVG_شروع" | "commencer_svg" | "svg_beginnen" | "начать_svg" => {
11127                let path = self.arg_str(&args, 0, "output.svg");
11128                let width = self.arg_num(&args, 1, 800.0)?;
11129                let height = self.arg_num(&args, 2, 600.0)?;
11130                *self.svg.borrow_mut() = Some(SvgWriter::new(path, width, height));
11131                return Ok(Value::Unit);
11132            },
11133
11134            "svg_rect"
11135            | "SVG矩形"
11136            | "SVGสี่เหลี่ยม"
11137            | "مستطیل_SVG"
11138            | "مستطيل_SVG"
11139            | "מלבן_SVG"
11140            | "SVG_مستطیل"
11141            | "rectangle_svg"
11142            | "svg_rechteck"
11143            | "прямоугольник_svg" => {
11144                let x = self.arg_num(&args, 0, 0.0)?;
11145                let y = self.arg_num(&args, 1, 0.0)?;
11146                let w = self.arg_num(&args, 2, 10.0)?;
11147                let h = self.arg_num(&args, 3, 10.0)?;
11148                let fill = self.arg_str(&args, 4, "#ffffff");
11149                if let Some(svg) = self.svg.borrow_mut().as_mut() {
11150                    svg.elements.push(format!(
11151                        "<rect x=\"{x:.1}\" y=\"{y:.1}\" width=\"{w:.1}\" \
11152                         height=\"{h:.1}\" fill=\"{fill}\"/>"
11153                    ));
11154                }
11155                return Ok(Value::Unit);
11156            },
11157
11158            "svg_circle" | "SVG圆形" | "SVGวงกลม" | "دایره_SVG" | "دائرة_SVG" | "עיגול_SVG"
11159            | "SVG_دائرہ" | "cercle_svg" | "svg_kreis" | "круг_svg" => {
11160                let cx = self.arg_num(&args, 0, 0.0)?;
11161                let cy = self.arg_num(&args, 1, 0.0)?;
11162                let r = self.arg_num(&args, 2, 5.0)?;
11163                let fill = self.arg_str(&args, 3, "#ffffff");
11164                if let Some(svg) = self.svg.borrow_mut().as_mut() {
11165                    svg.elements.push(format!(
11166                        "<circle cx=\"{cx:.1}\" cy=\"{cy:.1}\" r=\"{r:.1}\" fill=\"{fill}\"/>"
11167                    ));
11168                }
11169                return Ok(Value::Unit);
11170            },
11171
11172            "svg_line" | "SVG线段" | "SVGเส้น" | "خط_SVG" | "קו_SVG" | "SVG_لکیر" | "ligne_svg"
11173            | "svg_linie" | "линия_svg" => {
11174                let x1 = self.arg_num(&args, 0, 0.0)?;
11175                let y1 = self.arg_num(&args, 1, 0.0)?;
11176                let x2 = self.arg_num(&args, 2, 0.0)?;
11177                let y2 = self.arg_num(&args, 3, 0.0)?;
11178                let stroke = self.arg_str(&args, 4, "#ffffff");
11179                let sw = self.arg_num(&args, 5, 1.0)?;
11180                if let Some(svg) = self.svg.borrow_mut().as_mut() {
11181                    svg.elements.push(format!(
11182                        "<line x1=\"{x1:.1}\" y1=\"{y1:.1}\" x2=\"{x2:.1}\" y2=\"{y2:.1}\" \
11183                         stroke=\"{stroke}\" stroke-width=\"{sw:.1}\"/>"
11184                    ));
11185                }
11186                return Ok(Value::Unit);
11187            },
11188
11189            "svg_polyline"
11190            | "SVG折线"
11191            | "SVGเส้นหัก"
11192            | "چندخطی_SVG"
11193            | "خط_متعدد_SVG"
11194            | "קו_שבור_SVG"
11195            | "SVG_پولی_لائن"
11196            | "polyligne_svg"
11197            | "svg_polylinie"
11198            | "ломаная_svg" => {
11199                let pts = self.arg_str(&args, 0, "");
11200                let stroke = self.arg_str(&args, 1, "#ffffff");
11201                let sw = self.arg_num(&args, 2, 1.0)?;
11202                if let Some(svg) = self.svg.borrow_mut().as_mut() {
11203                    svg.elements.push(format!(
11204                        "<polyline points=\"{pts}\" fill=\"none\" \
11205                         stroke=\"{stroke}\" stroke-width=\"{sw:.1}\"/>"
11206                    ));
11207                }
11208                return Ok(Value::Unit);
11209            },
11210
11211            "svg_text"
11212            | "SVG文本"
11213            | "SVGข้อความ"
11214            | "متن_SVG"
11215            | "نص_SVG"
11216            | "טקסט_SVG"
11217            | "SVG_متن"
11218            | "texte_svg"
11219            | "текст_svg" => {
11220                let x = self.arg_num(&args, 0, 0.0)?;
11221                let y = self.arg_num(&args, 1, 0.0)?;
11222                let text = self.arg_str(&args, 2, "");
11223                let fill = self.arg_str(&args, 3, "#ffffff");
11224                let size = self.arg_num(&args, 4, 12.0)?;
11225                if let Some(svg) = self.svg.borrow_mut().as_mut() {
11226                    let safe = text
11227                        .replace('&', "&amp;")
11228                        .replace('<', "&lt;")
11229                        .replace('>', "&gt;");
11230                    svg.elements.push(format!(
11231                        "<text x=\"{x:.1}\" y=\"{y:.1}\" fill=\"{fill}\" \
11232                         font-family=\"monospace\" font-size=\"{size:.0}\">{safe}</text>"
11233                    ));
11234                }
11235                return Ok(Value::Unit);
11236            },
11237
11238            "svg_end"
11239            | "结束SVG"
11240            | "จบSVG"
11241            | "پایان_SVG"
11242            | "أنهِ_SVG"
11243            | "סיים_SVG"
11244            | "SVG_ختم"
11245            | "terminer_svg"
11246            | "svg_beenden"
11247            | "закончить_svg" => {
11248                {
11249                    let borrow = self.svg.borrow();
11250                    if let Some(svg) = borrow.as_ref() {
11251                        svg.save()
11252                            .map_err(|e| EvalErr::from(format!("svg_end: {e}")))?;
11253                    }
11254                }
11255                *self.svg.borrow_mut() = None;
11256                return Ok(Value::Unit);
11257            },
11258
11259            "hsl_color" | "HSL颜色" | "สีHSL" | "رنگ_HSL" | "لون_HSL" | "צבע_HSL" | "HSL_رنگ"
11260            | "couleur_hsl" | "hsl_farbe" | "цвет_hsl" => {
11261                let h = self.arg_num(&args, 0, 0.0)?;
11262                let s = self.arg_num(&args, 1, 70.0)?;
11263                let l = self.arg_num(&args, 2, 50.0)?;
11264                return Ok(Value::Str(hsl_to_hex(h, s, l)));
11265            },
11266
11267            // ══════════════════════════════════════════════════════════════════
11268            // FFT / AUDIO ANALYSIS BUILTINS  (native only)
11269            // ══════════════════════════════════════════════════════════════════
11270
11271            // fft_push(samples_list) — feed raw audio samples and run FFT
11272            #[cfg(not(target_arch = "wasm32"))]
11273            "fft_push"
11274            | "วิเคราะห์เสียง"
11275            | "频谱输入"
11276            | "FFT入力"
11277            | "FFT입력"
11278            | "ورودی_FFT"
11279            | "أدخل_FFT"
11280            | "הזנת_FFT"
11281            | "FFT_ان_پٹ"
11282            | "fft_entrée"
11283            | "fft_eingabe"
11284            | "fft_вход" => {
11285                if let Some(Value::List(v)) = args.first() {
11286                    let samples: Vec<f32> = v
11287                        .iter()
11288                        .filter_map(|x| {
11289                            if let Value::Number(n) = x {
11290                                Some(*n as f32)
11291                            } else {
11292                                None
11293                            }
11294                        })
11295                        .collect();
11296                    self.fft.borrow_mut().push_samples(&samples);
11297                }
11298                return Ok(Value::Unit);
11299            },
11300
11301            // fft_bands(n) → list of n log-spaced magnitude bands (0..1)
11302            #[cfg(not(target_arch = "wasm32"))]
11303            "fft_bands"
11304            | "แถบความถี่"
11305            | "频段"
11306            | "周波数帯"
11307            | "주파수대"
11308            | "باندهای_FFT"
11309            | "نطاقات_FFT"
11310            | "פסי_FFT"
11311            | "FFT_بینڈز"
11312            | "fft_bandes"
11313            | "fft_bänder"
11314            | "fft_полосы" => {
11315                let n = self.arg_num(&args, 0, 32.0)? as usize;
11316                let bands = self.fft.borrow().freq_bands(n);
11317                *self.fft_bands_cache.borrow_mut() = bands.clone();
11318                return Ok(Value::List(Rc::new(
11319                    bands.into_iter().map(|v| Value::Number(v as f64)).collect(),
11320                )));
11321            },
11322
11323            // fft_beat() → bool
11324            #[cfg(not(target_arch = "wasm32"))]
11325            "fft_beat"
11326            | "จังหวะเสียง"
11327            | "节拍检测"
11328            | "ビート検出"
11329            | "비트"
11330            | "ضرب_FFT"
11331            | "نبضة_FFT"
11332            | "פעימת_FFT"
11333            | "FFT_دھڑکن"
11334            | "fft_battement"
11335            | "fft_takt"
11336            | "fft_удар" => {
11337                return Ok(Value::Bool(self.fft.borrow().is_beat()));
11338            },
11339
11340            // fft_beat_ratio() → f64  (1.0 = at threshold, >1 = strong beat)
11341            #[cfg(not(target_arch = "wasm32"))]
11342            "fft_beat_ratio"
11343            | "อัตราจังหวะ"
11344            | "节拍比"
11345            | "ビート比"
11346            | "비트비율"
11347            | "نسبت_ضرب_FFT"
11348            | "نسبة_نبضة_FFT"
11349            | "יחס_פעימת_FFT"
11350            | "FFT_بیٹ_تناسب" => {
11351                return Ok(Value::Number(self.fft.borrow().beat_ratio() as f64));
11352            },
11353
11354            // fft_rms() → f64
11355            #[cfg(not(target_arch = "wasm32"))]
11356            "fft_rms"
11357            | "ระดับRMS"
11358            | "均方根"
11359            | "二乗平均"
11360            | "RMS레벨"
11361            | "RMS_صدا"
11362            | "جذر_متوسط_مربع_FFT"
11363            | "RMS_של_FFT"
11364            | "FFT_RMS" => {
11365                return Ok(Value::Number(self.fft.borrow().rms() as f64));
11366            },
11367
11368            // fft_dominant_freq() → f64  in Hz
11369            #[cfg(not(target_arch = "wasm32"))]
11370            "fft_dominant_freq"
11371            | "ความถี่หลัก"
11372            | "主频"
11373            | "主要周波数"
11374            | "주파수"
11375            | "فرکانس_غالب"
11376            | "التردد_السائد"
11377            | "תדר_דומיננטי"
11378            | "غالب_فریکوئنسی"
11379            | "fft_fréquence_dominante"
11380            | "fft_dominante_frequenz"
11381            | "fft_доминирующая_частота" => {
11382                return Ok(Value::Number(self.fft.borrow().dominant_freq() as f64));
11383            },
11384
11385            // ── wasm32 stubs: fft builtins are no-ops on web ───────────────
11386            #[cfg(target_arch = "wasm32")]
11387            "fft_push"
11388            | "วิเคราะห์เสียง"
11389            | "频谱输入"
11390            | "FFT入力"
11391            | "FFT입력"
11392            | "ورودی_FFT"
11393            | "أدخل_FFT"
11394            | "הזנת_FFT"
11395            | "FFT_ان_پٹ"
11396            | "fft_entrée"
11397            | "fft_eingabe"
11398            | "fft_вход" => {
11399                return Ok(Value::Unit);
11400            },
11401            #[cfg(target_arch = "wasm32")]
11402            "fft_bands"
11403            | "แถบความถี่"
11404            | "频段"
11405            | "周波数帯"
11406            | "주파수대"
11407            | "باندهای_FFT"
11408            | "نطاقات_FFT"
11409            | "פסי_FFT"
11410            | "FFT_بینڈز"
11411            | "fft_bandes"
11412            | "fft_bänder"
11413            | "fft_полосы" => {
11414                let n = self.arg_num(&args, 0, 32.0)? as usize;
11415                return Ok(Value::List(vec![Value::Number(0.0); n].into()));
11416            },
11417            #[cfg(target_arch = "wasm32")]
11418            "fft_beat"
11419            | "จังหวะเสียง"
11420            | "节拍检测"
11421            | "ビート検出"
11422            | "비트"
11423            | "ضرب_FFT"
11424            | "نبضة_FFT"
11425            | "פעימת_FFT"
11426            | "FFT_دھڑکن"
11427            | "fft_battement"
11428            | "fft_takt"
11429            | "fft_удар" => {
11430                return Ok(Value::Bool(false));
11431            },
11432            #[cfg(target_arch = "wasm32")]
11433            "fft_beat_ratio"
11434            | "อัตราจังหวะ"
11435            | "节拍比"
11436            | "ビート比"
11437            | "비트비율"
11438            | "نسبت_ضرب_FFT"
11439            | "نسبة_نبضة_FFT"
11440            | "יחס_פעימת_FFT"
11441            | "FFT_بیٹ_تناسب" => {
11442                return Ok(Value::Number(1.0));
11443            },
11444            #[cfg(target_arch = "wasm32")]
11445            "fft_rms"
11446            | "ระดับRMS"
11447            | "均方根"
11448            | "二乗平均"
11449            | "RMS레벨"
11450            | "RMS_صدا"
11451            | "جذر_متوسط_مربع_FFT"
11452            | "RMS_של_FFT"
11453            | "FFT_RMS" => {
11454                return Ok(Value::Number(0.0));
11455            },
11456            #[cfg(target_arch = "wasm32")]
11457            "fft_dominant_freq"
11458            | "ความถี่หลัก"
11459            | "主频"
11460            | "主要周波数"
11461            | "주파수"
11462            | "فرکانس_غالب"
11463            | "التردد_السائد"
11464            | "תדר_דומיננטי"
11465            | "غالب_فریکوئنسی"
11466            | "fft_fréquence_dominante"
11467            | "fft_dominante_frequenz"
11468            | "fft_доминирующая_частота" => {
11469                return Ok(Value::Number(0.0));
11470            },
11471
11472            // ══════════════════════════════════════════════════════════════════
11473            // PROCEDURAL TEXTURE BLIT BUILTINS  (screen-space)
11474            // All: name(dst_x, dst_y, width, height, ...params, palette)
11475            // palette: "rainbow" | "fire" | "ocean" | "psychedelic" | "neon" | "forest"
11476            // ══════════════════════════════════════════════════════════════════
11477
11478            // tex_checkerboard(x, y, w, h, tiles, r1,g1,b1, r2,g2,b2)
11479            "tex_checkerboard"
11480            | "ลายตารางหมากรุก"
11481            | "بافت_شطرنجی"
11482            | "نسيج_رقعة_الشطرنج"
11483            | "מרקם_שחמט"
11484            | "شطرنج_ٹیکسچر" => {
11485                let (tx, ty, tw, th) = self.tex_rect(&args)?;
11486                let tiles = self.arg_num(&args, 4, 8.0)? as u32;
11487                let (r1, g1, b1) = (
11488                    self.arg_num(&args, 5, 255.)? as u32,
11489                    self.arg_num(&args, 6, 255.)? as u32,
11490                    self.arg_num(&args, 7, 255.)? as u32,
11491                );
11492                let (r2, g2, b2) = (
11493                    self.arg_num(&args, 8, 0.)? as u32,
11494                    self.arg_num(&args, 9, 0.)? as u32,
11495                    self.arg_num(&args, 10, 0.)? as u32,
11496                );
11497                let c1 = (r1 << 16) | (g1 << 8) | b1;
11498                let c2 = (r2 << 16) | (g2 << 8) | b2;
11499                let mut gfx = self.gfx.borrow_mut();
11500                let (bw, bh) = (gfx.width, gfx.height);
11501                for row in 0..th {
11502                    for col in 0..tw {
11503                        let cx = col as u32 * tiles / tw as u32;
11504                        let cy = row as u32 * tiles / th as u32;
11505                        let (dx, dy) = (tx + col, ty + row);
11506                        if dx < bw && dy < bh {
11507                            gfx.buffer[dy * bw + dx] = if (cx + cy) % 2 == 0 { c1 } else { c2 };
11508                        }
11509                    }
11510                }
11511                return Ok(Value::Unit);
11512            },
11513
11514            // tex_gradient(x, y, w, h, angle_deg, r1,g1,b1, r2,g2,b2)
11515            "tex_gradient"
11516            | "ลายไล่สี"
11517            | "بافت_گرادیان"
11518            | "نسيج_متدرج"
11519            | "מרקם_גרדיאנט"
11520            | "گریڈینٹ_ٹیکسچر" => {
11521                let (tx, ty, tw, th) = self.tex_rect(&args)?;
11522                let angle = self.arg_num(&args, 4, 0.0)? as f32;
11523                let (r1, g1, b1) = (
11524                    self.arg_num(&args, 5, 0.)? as f32 / 255.,
11525                    self.arg_num(&args, 6, 0.)? as f32 / 255.,
11526                    self.arg_num(&args, 7, 0.)? as f32 / 255.,
11527                );
11528                let (r2, g2, b2) = (
11529                    self.arg_num(&args, 8, 255.)? as f32 / 255.,
11530                    self.arg_num(&args, 9, 255.)? as f32 / 255.,
11531                    self.arg_num(&args, 10, 255.)? as f32 / 255.,
11532                );
11533                let (ca, sa) = (angle.to_radians().cos(), angle.to_radians().sin());
11534                let mut gfx = self.gfx.borrow_mut();
11535                let (bw, bh) = (gfx.width, gfx.height);
11536                for row in 0..th {
11537                    for col in 0..tw {
11538                        let nx = col as f32 / tw as f32 - 0.5;
11539                        let ny = row as f32 / th as f32 - 0.5;
11540                        let t = ((nx * ca + ny * sa + 0.707) / 1.414).clamp(0., 1.);
11541                        let (dx, dy) = (tx + col, ty + row);
11542                        if dx < bw && dy < bh {
11543                            gfx.buffer[dy * bw + dx] =
11544                                tex_rgb(r1 + (r2 - r1) * t, g1 + (g2 - g1) * t, b1 + (b2 - b1) * t);
11545                        }
11546                    }
11547                }
11548                return Ok(Value::Unit);
11549            },
11550
11551            // tex_noise(x, y, w, h, scale, octaves, seed, palette)
11552            "tex_noise" | "ลายนอยส์" | "بافت_نویز" | "نسيج_ضجيج" | "מרקם_רעש" | "نوائز_ٹیکسچر" =>
11553            {
11554                let (tx, ty, tw, th) = self.tex_rect(&args)?;
11555                let scale = self.arg_num(&args, 4, 4.0)? as f32;
11556                let octaves = self.arg_num(&args, 5, 4.0)? as u32;
11557                let seed = self.arg_num(&args, 6, 0.0)? as u32;
11558                let palette = self.arg_str(&args, 7, "rainbow");
11559                let mut gfx = self.gfx.borrow_mut();
11560                let (bw, bh) = (gfx.width, gfx.height);
11561                for row in 0..th {
11562                    for col in 0..tw {
11563                        let v = tex_fbm(
11564                            col as f32 * scale / tw as f32,
11565                            row as f32 * scale / th as f32,
11566                            octaves,
11567                            seed,
11568                        );
11569                        let [r, g, b] = tex_palette(&palette, v);
11570                        let (dx, dy) = (tx + col, ty + row);
11571                        if dx < bw && dy < bh {
11572                            gfx.buffer[dy * bw + dx] = tex_rgb(r, g, b);
11573                        }
11574                    }
11575                }
11576                return Ok(Value::Unit);
11577            },
11578
11579            // tex_freq_map(x, y, w, h, time, speed, palette)
11580            // Uses bands written by the last fft_bands() call.
11581            "tex_freq_map"
11582            | "ลายความถี่"
11583            | "نقشه_فرکانس_بافت"
11584            | "خريطة_تردد_النسيج"
11585            | "מפת_תדר_מרקם"
11586            | "فریکوئنسی_میپ_ٹیکسچر" => {
11587                let (tx, ty, tw, th) = self.tex_rect(&args)?;
11588                let time = self.arg_num(&args, 4, 0.0)? as f32;
11589                let speed = self.arg_num(&args, 5, 0.3)? as f32;
11590                let palette = self.arg_str(&args, 6, "rainbow");
11591                let bands: Vec<f32> = {
11592                    let c = self.fft_bands_cache.borrow();
11593                    if c.is_empty() {
11594                        vec![0.0; 32]
11595                    } else {
11596                        c.clone()
11597                    }
11598                };
11599                let n = bands.len().max(1);
11600                let mut gfx = self.gfx.borrow_mut();
11601                let (bw, bh) = (gfx.width, gfx.height);
11602                for row in 0..th {
11603                    for col in 0..tw {
11604                        let band_idx = (col * n / tw.max(1)).min(n - 1);
11605                        let mag = bands[band_idx].clamp(0., 1.);
11606                        let fill_y = (mag * th as f32) as usize;
11607                        if row >= th.saturating_sub(fill_y) {
11608                            let t = (col as f32 / tw as f32 + time * speed) % 1.0;
11609                            let [r, g, b] = tex_palette(&palette, t);
11610                            let bright = mag * (1.0 - row as f32 / th as f32 * 0.5);
11611                            let (dx, dy) = (tx + col, ty + row);
11612                            if dx < bw && dy < bh {
11613                                gfx.buffer[dy * bw + dx] =
11614                                    tex_rgb(r * bright, g * bright, b * bright);
11615                            }
11616                        }
11617                    }
11618                }
11619                return Ok(Value::Unit);
11620            },
11621
11622            // tex_spiral(x, y, w, h, freq, bands, time, palette)
11623            "tex_spiral"
11624            | "ลายเกลียวหมุน"
11625            | "بافت_مارپیچ"
11626            | "نسيج_حلزوني"
11627            | "מרקם_ספירלה"
11628            | "سرپیچ_ٹیکسچر" => {
11629                let (tx, ty, tw, th) = self.tex_rect(&args)?;
11630                let freq = self.arg_num(&args, 4, 5.0)? as f32;
11631                let n_bands = self.arg_num(&args, 5, 8.0)? as f32;
11632                let time = self.arg_num(&args, 6, 0.0)? as f32;
11633                let palette = self.arg_str(&args, 7, "rainbow");
11634                let mut gfx = self.gfx.borrow_mut();
11635                let (bw, bh) = (gfx.width, gfx.height);
11636                for row in 0..th {
11637                    for col in 0..tw {
11638                        let nx = col as f32 / tw as f32 - 0.5;
11639                        let ny = row as f32 / th as f32 - 0.5;
11640                        let r = (nx * nx + ny * ny).sqrt();
11641                        let theta = ny.atan2(nx);
11642                        let t = ((r * freq - theta / std::f32::consts::TAU + time * 0.5) * n_bands
11643                            % 1.0)
11644                            .abs();
11645                        let [cr, cg, cb] = tex_palette(&palette, t);
11646                        let (dx, dy) = (tx + col, ty + row);
11647                        if dx < bw && dy < bh {
11648                            gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
11649                        }
11650                    }
11651                }
11652                return Ok(Value::Unit);
11653            },
11654
11655            // tex_ripple(x, y, w, h, freq, cx, cy, time, palette)
11656            "tex_ripple" | "ลายระลอก" | "بافت_موج" | "نسيج_تموج" | "מרקם_אדווה" | "ریپل_ٹیکسچر" =>
11657            {
11658                let (tx, ty, tw, th) = self.tex_rect(&args)?;
11659                let freq = self.arg_num(&args, 4, 10.0)? as f32;
11660                let rcx = self.arg_num(&args, 5, 0.5)? as f32;
11661                let rcy = self.arg_num(&args, 6, 0.5)? as f32;
11662                let time = self.arg_num(&args, 7, 0.0)? as f32;
11663                let palette = self.arg_str(&args, 8, "ocean");
11664                let mut gfx = self.gfx.borrow_mut();
11665                let (bw, bh) = (gfx.width, gfx.height);
11666                for row in 0..th {
11667                    for col in 0..tw {
11668                        let nx = col as f32 / tw as f32 - rcx;
11669                        let ny = row as f32 / th as f32 - rcy;
11670                        let r = (nx * nx + ny * ny).sqrt();
11671                        let t = ((r * freq - time) % 1.0).abs();
11672                        let [cr, cg, cb] = tex_palette(&palette, t);
11673                        let (dx, dy) = (tx + col, ty + row);
11674                        if dx < bw && dy < bh {
11675                            gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
11676                        }
11677                    }
11678                }
11679                return Ok(Value::Unit);
11680            },
11681
11682            // tex_mandelbrot(x, y, w, h, zoom, cx, cy, max_iter, palette)
11683            "tex_mandelbrot"
11684            | "ลายแมนเดลบรอต"
11685            | "بافت_ماندلبرو"
11686            | "نسيج_مانديلبروت"
11687            | "מרקם_מנדלברוט"
11688            | "مینڈل_بروٹ_ٹیکسچر" => {
11689                let (tx, ty, tw, th) = self.tex_rect(&args)?;
11690                let zoom = self.arg_num(&args, 4, 1.0)?;
11691                let mcx = self.arg_num(&args, 5, -0.5)?;
11692                let mcy = self.arg_num(&args, 6, 0.0)?;
11693                let max_iter = self.arg_num(&args, 7, 64.0)? as u32;
11694                let palette = self.arg_str(&args, 8, "psychedelic");
11695                let mut gfx = self.gfx.borrow_mut();
11696                let (bw, bh) = (gfx.width, gfx.height);
11697                for row in 0..th {
11698                    for col in 0..tw {
11699                        let zx0 = (col as f64 / tw as f64 - 0.5) / zoom + mcx;
11700                        let zy0 = (row as f64 / th as f64 - 0.5) / zoom + mcy;
11701                        let mut x = 0.0f64;
11702                        let mut y = 0.0f64;
11703                        let mut i = 0u32;
11704                        while i < max_iter && x * x + y * y < 4.0 {
11705                            let t = x * x - y * y + zx0;
11706                            y = 2.0 * x * y + zy0;
11707                            x = t;
11708                            i += 1;
11709                        }
11710                        let t = if i == max_iter {
11711                            0.0f32
11712                        } else {
11713                            (i as f32
11714                                - (x as f32 * x as f32 + y as f32 * y as f32).ln().ln()
11715                                    / 2.0f32.ln())
11716                                / max_iter as f32
11717                        };
11718                        let [cr, cg, cb] = tex_palette(&palette, t.clamp(0., 1.));
11719                        let (dx, dy) = (tx + col, ty + row);
11720                        if dx < bw && dy < bh {
11721                            gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
11722                        }
11723                    }
11724                }
11725                return Ok(Value::Unit);
11726            },
11727
11728            // tex_julia(x, y, w, h, c_re, c_im, max_iter, palette)
11729            "tex_julia"
11730            | "ลายจูเลีย"
11731            | "بافت_ژولیا"
11732            | "نسيج_جوليا"
11733            | "מרקם_ג'וליה"
11734            | "جولیا_ٹیکسچر" => {
11735                let (tx, ty, tw, th) = self.tex_rect(&args)?;
11736                let c_re = self.arg_num(&args, 4, -0.7)?;
11737                let c_im = self.arg_num(&args, 5, 0.27)?;
11738                let max_iter = self.arg_num(&args, 6, 64.0)? as u32;
11739                let palette = self.arg_str(&args, 7, "neon");
11740                let mut gfx = self.gfx.borrow_mut();
11741                let (bw, bh) = (gfx.width, gfx.height);
11742                for row in 0..th {
11743                    for col in 0..tw {
11744                        let mut zx = (col as f64 / tw as f64 - 0.5) * 3.5;
11745                        let mut zy = (row as f64 / th as f64 - 0.5) * 3.5;
11746                        let mut i = 0u32;
11747                        while i < max_iter && zx * zx + zy * zy < 4.0 {
11748                            let t = zx * zx - zy * zy + c_re;
11749                            zy = 2.0 * zx * zy + c_im;
11750                            zx = t;
11751                            i += 1;
11752                        }
11753                        let t = i as f32 / max_iter as f32;
11754                        let [cr, cg, cb] = tex_palette(&palette, t);
11755                        let (dx, dy) = (tx + col, ty + row);
11756                        if dx < bw && dy < bh {
11757                            gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
11758                        }
11759                    }
11760                }
11761                return Ok(Value::Unit);
11762            },
11763
11764            // tex_voronoi(x, y, w, h, cells, seed, palette)
11765            "tex_voronoi"
11766            | "ลายโวโรนอย"
11767            | "بافت_ورونوی"
11768            | "نسيج_فورونوي"
11769            | "מרקם_וורונוי"
11770            | "ورونوئی_ٹیکسچر" => {
11771                let (tx, ty, tw, th) = self.tex_rect(&args)?;
11772                let cells = self.arg_num(&args, 4, 16.0)? as u32;
11773                let seed = self.arg_num(&args, 5, 42.0)? as u32;
11774                let palette = self.arg_str(&args, 6, "rainbow");
11775                let pts: Vec<[f32; 2]> = (0..cells)
11776                    .map(|i| {
11777                        [
11778                            tex_hash(i as i32, 0, seed),
11779                            tex_hash(i as i32, 1, seed + 999),
11780                        ]
11781                    })
11782                    .collect();
11783                let mut gfx = self.gfx.borrow_mut();
11784                let (bw, bh) = (gfx.width, gfx.height);
11785                for row in 0..th {
11786                    for col in 0..tw {
11787                        let (fx, fy) = (col as f32 / tw as f32, row as f32 / th as f32);
11788                        let (min_d, nearest) = pts.iter().enumerate().fold(
11789                            (f32::MAX, 0usize),
11790                            |(d, idx), (i, &[cx, cy])| {
11791                                let dd = (fx - cx).powi(2) + (fy - cy).powi(2);
11792                                if dd < d {
11793                                    (dd, i)
11794                                } else {
11795                                    (d, idx)
11796                                }
11797                            },
11798                        );
11799                        let t = (nearest as f32 / cells as f32 + min_d * 4.0) % 1.0;
11800                        let [cr, cg, cb] = tex_palette(&palette, t);
11801                        let (dx, dy) = (tx + col, ty + row);
11802                        if dx < bw && dy < bh {
11803                            gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
11804                        }
11805                    }
11806                }
11807                return Ok(Value::Unit);
11808            },
11809
11810            // tex_halftone(x, y, w, h, dot_size, time, palette)
11811            "tex_halftone"
11812            | "ลายฮาล์ฟโทน"
11813            | "بافت_نیم‌تن"
11814            | "نسيج_نصفي"
11815            | "מרקם_חצי_גוון"
11816            | "ہاف_ٹون_ٹیکسچر" => {
11817                let (tx, ty, tw, th) = self.tex_rect(&args)?;
11818                let dot_size = self.arg_num(&args, 4, 0.05)? as f32;
11819                let time = self.arg_num(&args, 5, 0.0)? as f32;
11820                let palette = self.arg_str(&args, 6, "rainbow");
11821                let mut gfx = self.gfx.borrow_mut();
11822                let (bw, bh) = (gfx.width, gfx.height);
11823                for row in 0..th {
11824                    for col in 0..tw {
11825                        let (fx, fy) = (col as f32 / tw as f32, row as f32 / th as f32);
11826                        let gx = (fx / dot_size).floor();
11827                        let gy = (fy / dot_size).floor();
11828                        let lx = (fx / dot_size - gx - 0.5) * 2.0;
11829                        let ly = (fy / dot_size - gy - 0.5) * 2.0;
11830                        let r = (lx * lx + ly * ly).sqrt();
11831                        let t = (gx / (1.0 / dot_size) + time * 0.1) % 1.0;
11832                        let a = if r < 0.7 {
11833                            ((0.7 - r) / 0.7).clamp(0., 1.)
11834                        } else {
11835                            0.0
11836                        };
11837                        if a > 0.0 {
11838                            let [cr, cg, cb] = tex_palette(&palette, t);
11839                            let (dx, dy) = (tx + col, ty + row);
11840                            if dx < bw && dy < bh {
11841                                gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
11842                            }
11843                        }
11844                    }
11845                }
11846                return Ok(Value::Unit);
11847            },
11848
11849            // ══════════════════════════════════════════════════════════════════
11850            // RENDER / LIGHTING MODES  (holographic cel shading)
11851            // ══════════════════════════════════════════════════════════════════
11852            // set_shade_mode(m) — 0 flat · 1 cel · 2 holo (default)
11853            "set_shade_mode"
11854            | "设置着色"
11855            | "シェード設定"
11856            | "셰이드모드"
11857            | "ตั้งการแรเงา"
11858            | "تنظیم_حالت_سایه‌پردازی"
11859            | "عيّن_نمط_التظليل"
11860            | "קבע_מצב_הצללה"
11861            | "شیڈ_موڈ_مقرر_کرو"
11862            | "définir_mode_ombrage"
11863            | "schattierungsmodus_setzen"
11864            | "задать_режим_затенения" => {
11865                let m = self.arg_num(&args, 0, 2.0)? as u8;
11866                self.gfx.borrow_mut().shade_mode = m;
11867                return Ok(Value::Unit);
11868            },
11869            // set_cel_bands(n) — number of posterisation bands (>=2)
11870            "set_cel_bands"
11871            | "设置色阶"
11872            | "セル段数"
11873            | "셀밴드"
11874            | "ตั้งระดับสี"
11875            | "تنظیم_باندهای_سل"
11876            | "عيّن_نطاقات_التظليل"
11877            | "קבע_רצועות_הצללה"
11878            | "سیل_بینڈز_مقرر_کرو"
11879            | "définir_bandes_cel"
11880            | "cel_bänder_setzen"
11881            | "задать_полосы_cel" => {
11882                let n = (self.arg_num(&args, 0, 4.0)? as u32).max(2);
11883                self.gfx.borrow_mut().shade.bands = n;
11884                return Ok(Value::Unit);
11885            },
11886            // set_shadow_color(r,g,b) — coloured-shadow tint, 0-255
11887            "set_shadow_color"
11888            | "设置阴影色"
11889            | "影の色"
11890            | "그림자색"
11891            | "ตั้งสีเงา"
11892            | "تنظیم_رنگ_سایه"
11893            | "عيّن_لون_الظل"
11894            | "קבע_צבע_צל"
11895            | "سایہ_رنگ_مقرر_کرو"
11896            | "définir_couleur_ombre"
11897            | "schattenfarbe_setzen"
11898            | "задать_цвет_тени" => {
11899                let r = self.arg_num(&args, 0, 26.)? as f32 / 255.0;
11900                let g = self.arg_num(&args, 1, 33.)? as f32 / 255.0;
11901                let b = self.arg_num(&args, 2, 77.)? as f32 / 255.0;
11902                self.gfx.borrow_mut().shade.shadow = [r, g, b];
11903                return Ok(Value::Unit);
11904            },
11905            // set_rim(strength, r,g,b) — holographic fresnel edge glow
11906            // ══════════════════════════════════════════════════════════════════
11907            // CRYPTOGRAPHY (ling-crypto) — geo suite, hybrid PQ KEM, holographic
11908            // Bytes cross the language boundary as lowercase hex strings.
11909            // ══════════════════════════════════════════════════════════════════
11910            #[cfg(not(target_arch = "wasm32"))]
11911            "crypto_hash"
11912            | "แฮชเข้ารหัส"
11913            | "几何哈希"
11914            | "幾何ハッシュ"
11915            | "기하해시"
11916            | "درهم_رمزنگاری"
11917            | "بصمة_تشفير"
11918            | "גיבוב_הצפנה"
11919            | "خفیہ_ہیش"
11920            | "hachage_crypto"
11921            | "krypto_hash"
11922            | "крипто_хеш" => {
11923                let s = self.arg_str(&args, 0, "");
11924                return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(
11925                    s.as_bytes(),
11926                ))));
11927            },
11928            #[cfg(target_arch = "wasm32")]
11929            "crypto_hash"
11930            | "แฮชเข้ารหัส"
11931            | "几何哈希"
11932            | "幾何ハッシュ"
11933            | "기하해시"
11934            | "درهم_رمزنگاری"
11935            | "بصمة_تشفير"
11936            | "גיבוב_הצפנה"
11937            | "خفیہ_ہیش"
11938            | "hachage_crypto"
11939            | "krypto_hash"
11940            | "крипто_хеш" => {
11941                let s = self.arg_str(&args, 0, "");
11942                return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(
11943                    s.as_bytes(),
11944                ))));
11945            },
11946            // 3-D torus-knot fingerprint of any text/key → flat [x,y,z, x,y,z, …]
11947            #[cfg(not(target_arch = "wasm32"))]
11948            "knot_points"
11949            | "จุดปม"
11950            | "结点坐标"
11951            | "結び目点"
11952            | "매듭점"
11953            | "نقاط_گره"
11954            | "نقاط_العقدة"
11955            | "נקודות_קשר"
11956            | "گرہ_پوائنٹس"
11957            | "points_nœud"
11958            | "knotenpunkte"
11959            | "точки_узла" => {
11960                let s = self.arg_str(&args, 0, "");
11961                let shape = ling_crypto::geo::KnotShape::from_bytes(s.as_bytes());
11962                let mut out = Vec::with_capacity(shape.points.len() * 3);
11963                for p in &shape.points {
11964                    out.push(Value::Number(p[0] as f64));
11965                    out.push(Value::Number(p[1] as f64));
11966                    out.push(Value::Number(p[2] as f64));
11967                }
11968                return Ok(Value::List(Rc::new(out)));
11969            },
11970            #[cfg(target_arch = "wasm32")]
11971            "knot_points"
11972            | "จุดปม"
11973            | "结点坐标"
11974            | "結び目点"
11975            | "매듭점"
11976            | "نقاط_گره"
11977            | "نقاط_العقدة"
11978            | "נקודות_קשר"
11979            | "گرہ_پوائنٹس"
11980            | "points_nœud"
11981            | "knotenpunkte"
11982            | "точки_узла" => {
11983                let s = self.arg_str(&args, 0, "");
11984                let shape = ling_crypto::geo::KnotShape::from_bytes(s.as_bytes());
11985                let mut out = Vec::with_capacity(shape.points.len() * 3);
11986                for p in &shape.points {
11987                    out.push(Value::Number(p[0] as f64));
11988                    out.push(Value::Number(p[1] as f64));
11989                    out.push(Value::Number(p[2] as f64));
11990                }
11991                return Ok(Value::List(out.into()));
11992            },
11993            #[cfg(not(target_arch = "wasm32"))]
11994            "knot_label"
11995            | "ป้ายปม"
11996            | "结点标签"
11997            | "結び目ラベル"
11998            | "매듭라벨"
11999            | "برچسب_گره"
12000            | "تسمية_العقدة"
12001            | "תווית_קשר"
12002            | "گرہ_لیبل"
12003            | "étiquette_nœud"
12004            | "knotenbezeichnung"
12005            | "метка_узла" => {
12006                let s = self.arg_str(&args, 0, "");
12007                return Ok(Value::Str(
12008                    ling_crypto::geo::KnotShape::from_bytes(s.as_bytes()).label(),
12009                ));
12010            },
12011            #[cfg(target_arch = "wasm32")]
12012            "knot_label"
12013            | "ป้ายปม"
12014            | "结点标签"
12015            | "結び目ラベル"
12016            | "매듭라벨"
12017            | "برچسب_گره"
12018            | "تسمية_العقدة"
12019            | "תווית_קשר"
12020            | "گرہ_لیبل"
12021            | "étiquette_nœud"
12022            | "knotenbezeichnung"
12023            | "метка_узла" => {
12024                let s = self.arg_str(&args, 0, "");
12025                return Ok(Value::Str(
12026                    ling_crypto::geo::KnotShape::from_bytes(s.as_bytes()).label(),
12027                ));
12028            },
12029            // KEM keypair (hybrid X25519+ML-KEM-768) → integer handle
12030            #[cfg(not(target_arch = "wasm32"))]
12031            "knot_keygen"
12032            | "hybrid_keygen"
12033            | "สร้างกุญแจปม"
12034            | "生成密钥"
12035            | "鍵生成"
12036            | "키생성"
12037            | "تولید_کلید_گره"
12038            | "توليد_مفتاح_العقدة"
12039            | "יצירת_מפתח_קשר"
12040            | "گرہ_کلید_تخلیق"
12041            | "génération_clé_nœud"
12042            | "knotenschlüsselerzeugung"
12043            | "генерация_ключа_узла" => {
12044                self.crypto_ids.push(ling_crypto::KnotIdentity::generate());
12045                return Ok(Value::Number((self.crypto_ids.len() - 1) as f64));
12046            },
12047            #[cfg(not(target_arch = "wasm32"))]
12048            "knot_public"
12049            | "hybrid_public"
12050            | "กุญแจสาธารณะปม"
12051            | "公钥"
12052            | "公開鍵"
12053            | "공개키"
12054            | "کلید_عمومی_گره"
12055            | "مفتاح_العقدة_العام"
12056            | "מפתח_ציבורי_קשר"
12057            | "گرہ_عوامی_کلید"
12058            | "clé_publique_nœud"
12059            | "knoten_öffentlicher_schlüssel"
12060            | "публичный_ключ_узла" => {
12061                let h = self.arg_num(&args, 0, 0.0)? as usize;
12062                let pk = self
12063                    .crypto_ids
12064                    .get(h)
12065                    .map(|id| hex_encode(id.public_key()))
12066                    .unwrap_or_default();
12067                return Ok(Value::Str(pk));
12068            },
12069            // encapsulate(pubkey_hex) → [ciphertext_hex, shared_secret_hex]
12070            #[cfg(not(target_arch = "wasm32"))]
12071            "knot_encapsulate"
12072            | "hybrid_encapsulate"
12073            | "ห่อกุญแจปม"
12074            | "封装密钥"
12075            | "カプセル化"
12076            | "캡슐화"
12077            | "کپسوله‌سازی_گره"
12078            | "تغليف_مفتاح_العقدة"
12079            | "עטיפת_קשר"
12080            | "گرہ_احاطہ"
12081            | "encapsuler_nœud"
12082            | "knoten_kapseln"
12083            | "инкапсулировать_узел" => {
12084                let pk = hex_decode(&self.arg_str(&args, 0, ""));
12085                match ling_crypto::geo::knot_encapsulate(&pk) {
12086                    Ok((ct, ss)) => {
12087                        return Ok(Value::List(Rc::new(vec![
12088                            Value::Str(hex_encode(&ct)),
12089                            Value::Str(hex_encode(&ss)),
12090                        ])))
12091                    },
12092                    Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
12093                }
12094            },
12095            // decapsulate(handle, ciphertext_hex) → shared_secret_hex
12096            #[cfg(not(target_arch = "wasm32"))]
12097            "knot_decapsulate"
12098            | "hybrid_decapsulate"
12099            | "แกะกุญแจปม"
12100            | "解封装密钥"
12101            | "カプセル解除"
12102            | "캡슐해제"
12103            | "بازکردن_کپسوله_گره"
12104            | "فك_تغليف_مفتاح_العقدة"
12105            | "פתיחת_עטיפת_קשר"
12106            | "گرہ_احاطہ_کھولو"
12107            | "décapsuler_nœud"
12108            | "knoten_entkapseln"
12109            | "декапсулировать_узел" => {
12110                let h = self.arg_num(&args, 0, 0.0)? as usize;
12111                let ct = hex_decode(&self.arg_str(&args, 1, ""));
12112                let ss = self
12113                    .crypto_ids
12114                    .get(h)
12115                    .and_then(|id| id.decapsulate(&ct).ok())
12116                    .map(|s| hex_encode(&s))
12117                    .unwrap_or_default();
12118                return Ok(Value::Str(ss));
12119            },
12120            // Authenticated encryption (XChaCha20-Poly1305) — seal(key_hex, text) → ct_hex
12121            #[cfg(not(target_arch = "wasm32"))]
12122            "crypto_seal"
12123            | "ผนึก"
12124            | "封印"
12125            | "封印する"
12126            | "봉인"
12127            | "مهر_رمزنگاری"
12128            | "ختم_تشفير"
12129            | "חתימת_הצפנה"
12130            | "خفیہ_مہر"
12131            | "sceller_crypto"
12132            | "krypto_versiegeln"
12133            | "запечатать_крипто" => {
12134                let key = hex_to_32(&self.arg_str(&args, 0, ""));
12135                let pt = self.arg_str(&args, 1, "");
12136                match ling_crypto::geo::holo_seal(key, pt.as_bytes()) {
12137                    Ok(ct) => return Ok(Value::Str(hex_encode(&ct))),
12138                    Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
12139                }
12140            },
12141            #[cfg(not(target_arch = "wasm32"))]
12142            "crypto_open"
12143            | "เปิดผนึก"
12144            | "解封"
12145            | "封印解除"
12146            | "봉인해제"
12147            | "بازکردن_مهر"
12148            | "فتح_الختم"
12149            | "פתיחת_חתימה"
12150            | "مہر_کھولو"
12151            | "ouvrir_crypto"
12152            | "krypto_öffnen"
12153            | "открыть_крипто" => {
12154                let key = hex_to_32(&self.arg_str(&args, 0, ""));
12155                let ct = hex_decode(&self.arg_str(&args, 1, ""));
12156                match ling_crypto::geo::holo_open(key, &ct) {
12157                    Ok(pt) => return Ok(Value::Str(String::from_utf8_lossy(&pt).into_owned())),
12158                    Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
12159                }
12160            },
12161            // Holographic all-or-nothing transform — 4-D fragment coords [a,b,c,d, …]
12162            #[cfg(not(target_arch = "wasm32"))]
12163            "holo_points"
12164            | "จุดโฮโลแกรม"
12165            | "全息点"
12166            | "ホログラム点"
12167            | "홀로그램점"
12168            | "نقاط_هولوگرام"
12169            | "نقاط_الهولوغرام"
12170            | "נקודות_הולוגרמה"
12171            | "ہولوگرام_پوائنٹس"
12172            | "points_holo"
12173            | "holo_punkte"
12174            | "точки_голо" => {
12175                let s = self.arg_str(&args, 0, "");
12176                let frags = ling_crypto::geo::scatter(s.as_bytes());
12177                let mut out = Vec::with_capacity(frags.len() * 4);
12178                for f in &frags {
12179                    for c in f.coord {
12180                        out.push(Value::Number(c as f64));
12181                    }
12182                }
12183                return Ok(Value::List(Rc::new(out)));
12184            },
12185            #[cfg(not(target_arch = "wasm32"))]
12186            "holo_fragment_count"
12187            | "จำนวนชิ้นโฮโลแกรม"
12188            | "全息碎片数"
12189            | "ホログラム断片数"
12190            | "홀로그램조각수"
12191            | "تعداد_قطعات_هولوگرام"
12192            | "عدد_شظايا_الهولوغرام"
12193            | "מספר_שברי_הולוגרמה"
12194            | "ہولوگرام_ٹکڑے_تعداد"
12195            | "nombre_fragments_holo"
12196            | "holo_fragmentanzahl"
12197            | "число_фрагментов_голо" => {
12198                let s = self.arg_str(&args, 0, "");
12199                return Ok(Value::Number(
12200                    ling_crypto::geo::scatter(s.as_bytes()).len() as f64
12201                ));
12202            },
12203            // SHAKE-256 XOF, squeezed to an arbitrary output length in bytes
12204            // (`shake_hex(s, 128)` = a 1024-bit seal digest).
12205            #[cfg(not(target_arch = "wasm32"))]
12206            "shake_hex" | "SHAKE哈希" => {
12207                let s = self.arg_str(&args, 0, "");
12208                let len = self.arg_num(&args, 1, 32.0)?.max(0.0) as usize;
12209                return Ok(Value::Str(hex_encode(&ling_crypto::Shake256::hash(
12210                    s.as_bytes(),
12211                    len,
12212                ))));
12213            },
12214            // Ed25519 signing keypair (issuer identity) → integer handle.
12215            #[cfg(not(target_arch = "wasm32"))]
12216            "ed25519_keygen"
12217            | "생성서명키"
12218            | "สร้างกุญแจลายเซ็น"
12219            | "生成签名密钥"
12220            | "署名鍵生成"
12221            | "تولید_کلید_امضا"
12222            | "توليد_مفتاح_التوقيع"
12223            | "יצירת_מפתח_חתימה"
12224            | "دستخط_کلید_تخلیق"
12225            | "génération_clé_signature"
12226            | "signaturschlüsselerzeugung"
12227            | "генерация_ключа_подписи" => {
12228                self.ed25519_ids
12229                    .push(ling_crypto::Ed25519Keypair::generate());
12230                return Ok(Value::Number((self.ed25519_ids.len() - 1) as f64));
12231            },
12232            // Deterministic keypair from a 32-byte hex seed — the same seed
12233            // always yields the same keypair, so a program can persist just the
12234            // seed (e.g. a bank's issuer identity) and rederive identical keys
12235            // across restarts instead of every run minting a fresh, unrelated one.
12236            #[cfg(not(target_arch = "wasm32"))]
12237            "ed25519_keygen_from_seed"
12238            | "씨앗에서생성서명키"
12239            | "สร้างกุญแจลายเซ็นจากเมล็ด"
12240            | "从种子生成签名密钥"
12241            | "シードから署名鍵生成"
12242            | "تولید_کلید_امضا_از_دانه"
12243            | "توليد_مفتاح_التوقيع_من_البذرة"
12244            | "יצירת_מפתח_חתימה_מזרע"
12245            | "بیج_سے_دستخط_کلید_تخلیق"
12246            | "génération_clé_signature_depuis_graine"
12247            | "signaturschlüsselerzeugung_aus_saat"
12248            | "генерация_ключа_подписи_из_семени" => {
12249                let seed = hex_to_32(&self.arg_str(&args, 0, ""));
12250                self.ed25519_ids
12251                    .push(ling_crypto::Ed25519Keypair::from_seed(seed));
12252                return Ok(Value::Number((self.ed25519_ids.len() - 1) as f64));
12253            },
12254            #[cfg(not(target_arch = "wasm32"))]
12255            "ed25519_public"
12256            | "서명공개키"
12257            | "กุญแจสาธารณะลายเซ็น"
12258            | "签名公钥"
12259            | "署名公開鍵"
12260            | "کلید_عمومی_امضا"
12261            | "مفتاح_التوقيع_العام"
12262            | "מפתח_חתימה_ציבורי"
12263            | "دستخط_عوامی_کلید"
12264            | "clé_publique_signature"
12265            | "signatur_öffentlicher_schlüssel"
12266            | "публичный_ключ_подписи" => {
12267                let h = self.arg_num(&args, 0, 0.0)? as usize;
12268                let pk = self
12269                    .ed25519_ids
12270                    .get(h)
12271                    .map(|kp| hex_encode(&kp.public_key()))
12272                    .unwrap_or_default();
12273                return Ok(Value::Str(pk));
12274            },
12275            // ed25519_sign(handle, message) → signature hex (64 bytes)
12276            #[cfg(not(target_arch = "wasm32"))]
12277            "ed25519_sign"
12278            | "서명하다"
12279            | "เซ็นชื่อ"
12280            | "签名"
12281            | "署名する"
12282            | "امضا_کردن"
12283            | "توقيع"
12284            | "לחתום"
12285            | "دستخط_کریں"
12286            | "signer"
12287            | "signieren"
12288            | "подписать" => {
12289                let h = self.arg_num(&args, 0, 0.0)? as usize;
12290                let msg = self.arg_str(&args, 1, "");
12291                let sig = self
12292                    .ed25519_ids
12293                    .get(h)
12294                    .map(|kp| hex_encode(&kp.sign(msg.as_bytes())))
12295                    .unwrap_or_default();
12296                return Ok(Value::Str(sig));
12297            },
12298            // ed25519_verify(pubkey_hex, message, signature_hex) → bool
12299            #[cfg(not(target_arch = "wasm32"))]
12300            "ed25519_verify"
12301            | "서명확인"
12302            | "ยืนยันลายเซ็น"
12303            | "验证签名"
12304            | "署名検証"
12305            | "تایید_امضا"
12306            | "التحقق_من_التوقيع"
12307            | "אימות_חתימה"
12308            | "دستخط_تصدیق"
12309            | "vérifier_signature"
12310            | "signatur_verifizieren"
12311            | "проверить_подпись" => {
12312                let pk_hex = self.arg_str(&args, 0, "");
12313                let msg = self.arg_str(&args, 1, "");
12314                let sig_hex = self.arg_str(&args, 2, "");
12315                let pk_bytes = hex_decode(&pk_hex);
12316                let sig_bytes = hex_decode(&sig_hex);
12317                let ok = (|| {
12318                    let pk: [u8; 32] = pk_bytes.try_into().ok()?;
12319                    let sig: [u8; 64] = sig_bytes.try_into().ok()?;
12320                    Some(ling_crypto::Ed25519Keypair::verify(&pk, msg.as_bytes(), &sig).is_ok())
12321                })()
12322                .unwrap_or(false);
12323                return Ok(Value::Bool(ok));
12324            },
12325            // ML-DSA-65 (FIPS 204, post-quantum) signing keypair → integer
12326            // handle. Meant to be composed with ed25519_* by application code
12327            // into a hybrid signature (verify passes only if both check out),
12328            // not as a replacement for Ed25519 on its own. Aliases below mirror
12329            // the ed25519_* ones with each language's word for "quantum" folded
12330            // in, the same way knot_keygen/hybrid_keygen distinguish themselves.
12331            #[cfg(not(target_arch = "wasm32"))]
12332            "mldsa_keygen"
12333            | "양자서명키생성"
12334            | "สร้างกุญแจลายเซ็นควอนตัม"
12335            | "生成量子签名密钥"
12336            | "量子署名鍵生成"
12337            | "تولید_کلید_امضای_کوانتومی"
12338            | "توليد_مفتاح_التوقيع_الكمي"
12339            | "יצירת_מפתח_חתימה_קוונטי"
12340            | "کوانٹم_دستخط_کلید_تخلیق"
12341            | "génération_clé_signature_quantique"
12342            | "quantensignaturschlüsselerzeugung"
12343            | "генерация_ключа_квантовой_подписи" => {
12344                self.mldsa_ids.push(ling_crypto::MlDsa65Keypair::generate());
12345                return Ok(Value::Number((self.mldsa_ids.len() - 1) as f64));
12346            },
12347            // Deterministic keypair from a 32-byte hex seed — same rederive-
12348            // from-seed contract as ed25519_keygen_from_seed.
12349            #[cfg(not(target_arch = "wasm32"))]
12350            "mldsa_keygen_from_seed"
12351            | "씨앗에서양자서명키생성"
12352            | "สร้างกุญแจลายเซ็นควอนตัมจากเมล็ด"
12353            | "从种子生成量子签名密钥"
12354            | "シードから量子署名鍵生成"
12355            | "تولید_کلید_امضای_کوانتومی_از_دانه"
12356            | "توليد_مفتاح_التوقيع_الكمي_من_البذرة"
12357            | "יצירת_מפתח_חתימה_קוונטי_מזרע"
12358            | "بیج_سے_کوانٹم_دستخط_کلید_تخلیق"
12359            | "génération_clé_signature_quantique_depuis_graine"
12360            | "quantensignaturschlüsselerzeugung_aus_saat"
12361            | "генерация_ключа_квантовой_подписи_из_семени" =>
12362            {
12363                let seed = hex_to_32(&self.arg_str(&args, 0, ""));
12364                self.mldsa_ids
12365                    .push(ling_crypto::MlDsa65Keypair::from_seed(seed));
12366                return Ok(Value::Number((self.mldsa_ids.len() - 1) as f64));
12367            },
12368            #[cfg(not(target_arch = "wasm32"))]
12369            "mldsa_public"
12370            | "양자서명공개키"
12371            | "กุญแจสาธารณะลายเซ็นควอนตัม"
12372            | "量子签名公钥"
12373            | "量子署名公開鍵"
12374            | "کلید_عمومی_امضای_کوانتومی"
12375            | "مفتاح_التوقيع_الكمي_العام"
12376            | "מפתח_חתימה_קוונטי_ציבורי"
12377            | "کوانٹم_دستخط_عوامی_کلید"
12378            | "clé_publique_signature_quantique"
12379            | "quantensignatur_öffentlicher_schlüssel"
12380            | "публичный_ключ_квантовой_подписи" => {
12381                let h = self.arg_num(&args, 0, 0.0)? as usize;
12382                let pk = self
12383                    .mldsa_ids
12384                    .get(h)
12385                    .map(|kp| hex_encode(&kp.public_key()))
12386                    .unwrap_or_default();
12387                return Ok(Value::Str(pk));
12388            },
12389            // mldsa_sign(handle, message) → signature hex (~3309 bytes)
12390            #[cfg(not(target_arch = "wasm32"))]
12391            "mldsa_sign"
12392            | "양자서명하다"
12393            | "เซ็นชื่อควอนตัม"
12394            | "量子签名"
12395            | "量子署名する"
12396            | "امضای_کوانتومی_کردن"
12397            | "توقيع_كمي"
12398            | "לחתום_קוונטית"
12399            | "کوانٹم_دستخط_کریں"
12400            | "signer_quantique"
12401            | "quantensignieren"
12402            | "подписать_квантово" => {
12403                let h = self.arg_num(&args, 0, 0.0)? as usize;
12404                let msg = self.arg_str(&args, 1, "");
12405                let sig = self
12406                    .mldsa_ids
12407                    .get(h)
12408                    .map(|kp| hex_encode(&kp.sign(msg.as_bytes())))
12409                    .unwrap_or_default();
12410                return Ok(Value::Str(sig));
12411            },
12412            // mldsa_verify(pubkey_hex, message, signature_hex) → bool
12413            #[cfg(not(target_arch = "wasm32"))]
12414            "mldsa_verify"
12415            | "양자서명확인"
12416            | "ยืนยันลายเซ็นควอนตัม"
12417            | "验证量子签名"
12418            | "量子署名検証"
12419            | "تایید_امضای_کوانتومی"
12420            | "التحقق_من_التوقيع_الكمي"
12421            | "אימות_חתימה_קוונטית"
12422            | "کوانٹم_دستخط_تصدیق"
12423            | "vérifier_signature_quantique"
12424            | "quantensignatur_verifizieren"
12425            | "проверить_квантовую_подпись" => {
12426                let pk_hex = self.arg_str(&args, 0, "");
12427                let msg = self.arg_str(&args, 1, "");
12428                let sig_hex = self.arg_str(&args, 2, "");
12429                let pk_bytes = hex_decode(&pk_hex);
12430                let sig_bytes = hex_decode(&sig_hex);
12431                let ok = ling_crypto::MlDsa65Keypair::verify(&pk_bytes, msg.as_bytes(), &sig_bytes)
12432                    .is_ok();
12433                return Ok(Value::Bool(ok));
12434            },
12435            // Argon2id password hashing — password_hash(pw) → PHC string,
12436            // password_verify(pw, phc_string) → bool.
12437            #[cfg(not(target_arch = "wasm32"))]
12438            "password_hash" | "비밀번호해시" => {
12439                let pw = self.arg_str(&args, 0, "");
12440                let hash = ling_crypto::Argon2idParams::default()
12441                    .hash_password(pw.as_bytes())
12442                    .unwrap_or_default();
12443                return Ok(Value::Str(hash));
12444            },
12445            #[cfg(not(target_arch = "wasm32"))]
12446            "password_verify" | "비밀번호확인" => {
12447                let pw = self.arg_str(&args, 0, "");
12448                let hash = self.arg_str(&args, 1, "");
12449                let ok = ling_crypto::Argon2idParams::verify_password(pw.as_bytes(), &hash).is_ok();
12450                return Ok(Value::Bool(ok));
12451            },
12452            // OS-CSPRNG random bytes as hex — session ids / nonces (not the
12453            // xorshift `rand` builtin, which is for game logic, not security).
12454            #[cfg(not(target_arch = "wasm32"))]
12455            "random_hex" | "무작위16진수" => {
12456                use rand::RngCore;
12457                let n = self.arg_num(&args, 0, 16.0)?.max(0.0) as usize;
12458                let mut buf = vec![0u8; n];
12459                rand::rngs::OsRng.fill_bytes(&mut buf);
12460                return Ok(Value::Str(hex_encode(&buf)));
12461            },
12462            // base64_encode(s) — text -> base64 (matches what canvas.toDataURL()
12463            // already produces client-side, so PNG uploads never need a binary
12464            // request body).
12465            #[cfg(not(target_arch = "wasm32"))]
12466            "base64_encode" | "base64인코딩" => {
12467                use base64::Engine as _;
12468                let s = self.arg_str(&args, 0, "");
12469                return Ok(Value::Str(
12470                    base64::engine::general_purpose::STANDARD.encode(s.as_bytes()),
12471                ));
12472            },
12473            // base64_decode_to_file(b64, path) — writes decoded bytes straight to
12474            // disk; returns true on success. The only way binary data (an
12475            // uploaded/rendered PNG) reaches the filesystem from `.ling` source.
12476            #[cfg(not(target_arch = "wasm32"))]
12477            // remove_file(path) -> bool. Deletes a file. Used to clear a
12478            // streamed upload temp file when a publish is rejected, so a
12479            // failed run cannot leave gigabytes behind in storage/tmp.
12480            "remove_file" | "ลบไฟล์" | "删除文件" | "ファイル削除" | "파일삭제" =>
12481            {
12482                let path = self.arg_str(&args, 0, "");
12483                return Ok(Value::Bool(std::fs::remove_file(&path).is_ok()));
12484            },
12485            // rename_file(from, to) -> bool. Moves a file, falling back to
12486            // copy+delete when the two ends are on different filesystems.
12487            // Finalising a streamed upload is a rename, not a copy: the
12488            // artifact is already fully written and can be hundreds of MB.
12489            "rename_file" | "เปลี่ยนชื่อไฟล์" | "重命名文件" | "ファイル改名" | "파일이름변경" =>
12490            {
12491                let from = self.arg_str(&args, 0, "");
12492                let to = self.arg_str(&args, 1, "");
12493                if std::fs::rename(&from, &to).is_ok() {
12494                    return Ok(Value::Bool(true));
12495                }
12496                let ok = std::fs::copy(&from, &to).is_ok();
12497                if ok {
12498                    let _ = std::fs::remove_file(&from);
12499                }
12500                return Ok(Value::Bool(ok));
12501            },
12502            "base64_decode_to_file" | "base64파일로저장" => {
12503                use base64::Engine as _;
12504                let b64 = self.arg_str(&args, 0, "");
12505                let path = self.arg_str(&args, 1, "");
12506                let b64 = b64.split(',').next_back().unwrap_or(&b64); // tolerate a "data:image/png;base64,..." prefix
12507                let ok = base64::engine::general_purpose::STANDARD
12508                    .decode(b64)
12509                    .ok()
12510                    .and_then(|bytes| std::fs::write(&path, bytes).ok())
12511                    .is_some();
12512                return Ok(Value::Bool(ok));
12513            },
12514            // qr_svg(text) — a scannable QR code as an inline <svg>...</svg>
12515            // string (e.g. for an otpauth:// 2FA enrollment URI). Kept as SVG
12516            // rather than a rasterized image so it fits the same "everything
12517            // stays vector" theme as the banknote seal art.
12518            #[cfg(not(target_arch = "wasm32"))]
12519            "qr_svg" | "QR코드" => {
12520                let text = self.arg_str(&args, 0, "");
12521                let svg = qrcode::QrCode::new(text.as_bytes())
12522                    .map(|code| {
12523                        code.render::<qrcode::render::svg::Color>()
12524                            .min_dimensions(240, 240)
12525                            .dark_color(qrcode::render::svg::Color("#1a0f3d"))
12526                            .light_color(qrcode::render::svg::Color("#ffffff"))
12527                            .build()
12528                    })
12529                    .unwrap_or_default();
12530                return Ok(Value::Str(svg));
12531            },
12532            // zip_files(paths_list, out_path) — bundles files into a zip archive
12533            // (used by the "render" step to package a note's PNG/SVG/PDF).
12534            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
12535            "zip_files" | "압축파일" => {
12536                let paths = match args.first() {
12537                    Some(Value::List(l)) => l.iter().map(|v| v.to_string()).collect::<Vec<_>>(),
12538                    _ => Vec::new(),
12539                };
12540                let out_path = self.arg_str(&args, 1, "out.zip");
12541                let ok = (|| -> std::io::Result<()> {
12542                    let file = std::fs::File::create(&out_path)?;
12543                    let mut writer = zip::ZipWriter::new(file);
12544                    let options: zip::write::FileOptions<'_, ()> =
12545                        zip::write::FileOptions::default()
12546                            .compression_method(zip::CompressionMethod::Deflated);
12547                    for p in &paths {
12548                        let name = std::path::Path::new(p)
12549                            .file_name()
12550                            .map(|n| n.to_string_lossy().into_owned())
12551                            .unwrap_or_else(|| p.clone());
12552                        let bytes = std::fs::read(p)?;
12553                        writer.start_file(name, options)?;
12554                        std::io::Write::write_all(&mut writer, &bytes)?;
12555                    }
12556                    writer.finish()?;
12557                    Ok(())
12558                })()
12559                .is_ok();
12560                return Ok(Value::Bool(ok));
12561            },
12562            // pdf_from_images(png_paths_list, out_path) — one page per image,
12563            // sized to its pixel dimensions. No PDF crate dependency: `image`
12564            // (decode) and `flate2` (deflate the page's raw RGB stream) are
12565            // already unconditional deps, so this hand-writes the handful of
12566            // PDF objects (Catalog/Pages/Page/Contents/Image XObject) directly.
12567            // `build_pdf_from_images` itself only exists under this same gate.
12568            #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
12569            "pdf_from_images" | "PDF来自图片" => {
12570                let paths = match args.first() {
12571                    Some(Value::List(l)) => l.iter().map(|v| v.to_string()).collect::<Vec<_>>(),
12572                    _ => Vec::new(),
12573                };
12574                let out_path = self.arg_str(&args, 1, "out.pdf");
12575                let ok = build_pdf_from_images(&paths, &out_path).is_ok();
12576                return Ok(Value::Bool(ok));
12577            },
12578
12579            // ══════════════════════════════════════════════════════════════════
12580            // ling-ui — animation easings + holographic vector widgets + text I/O
12581            // ══════════════════════════════════════════════════════════════════
12582            "ease" => {
12583                let name = self.arg_str(&args, 0, "ease");
12584                let t = self.arg_num(&args, 1, 0.0)? as f32;
12585                return Ok(Value::Number(
12586                    ling_ui::Easing::from_name(&name).apply(t) as f64
12587                ));
12588            },
12589
12590            // ══════════════════════════════════════════════════════════════════
12591            // Anima — unified animation drivers (ling-animation). Organic 灵 +
12592            // mechanical 机 scalar drivers, callable per frame from a script.
12593            // ══════════════════════════════════════════════════════════════════
12594            "tween"
12595            | "补间"
12596            | "補間"
12597            | "트윈"
12598            | "แทรกค่า"
12599            | "میان‌فریم"
12600            | "تدرج_حركي"
12601            | "טווין"
12602            | "ٹوئین"
12603            | "твин" => {
12604                let a = self.arg_num(&args, 0, 0.0)?;
12605                let b = self.arg_num(&args, 1, 0.0)?;
12606                let t = self.arg_num(&args, 2, 0.0)?.clamp(0.0, 1.0);
12607                return Ok(Value::Number(a + (b - a) * t));
12608            },
12609            "tween_ease"
12610            | "缓动补间"
12611            | "緩和補間"
12612            | "이징트윈"
12613            | "แทรกนุ่ม"
12614            | "میان‌فریم_نرم"
12615            | "تدرج_ناعم_حركي"
12616            | "טווין_חלק"
12617            | "ٹوئین_ایز"
12618            | "tween_lisse"
12619            | "tween_glättung"
12620            | "твин_плавность" => {
12621                let a = self.arg_num(&args, 0, 0.0)? as f32;
12622                let b = self.arg_num(&args, 1, 0.0)? as f32;
12623                let t = self.arg_num(&args, 2, 0.0)? as f32;
12624                let kind = self.arg_str(&args, 3, "linear");
12625                let e = ling_animation::EaseFunction::from_name(&kind);
12626                return Ok(Value::Number(
12627                    ling_animation::ease::tween_ease(&a, &b, t, e) as f64,
12628                ));
12629            },
12630            // ── Organic 灵 ──
12631            "breathe" | "呼吸" | "호흡" | "หายใจ" | "تنفس" | "נשימה" | "سانس" | "respirer"
12632            | "atmen" | "дышать" => {
12633                let t = self.arg_num(&args, 0, 0.0)? as f32;
12634                let rate = self.arg_num(&args, 1, 1.0)? as f32;
12635                let depth = self.arg_num(&args, 2, 0.1)? as f32;
12636                return Ok(Value::Number(
12637                    ling_animation::scalar::breathe(t, rate, depth) as f64,
12638                ));
12639            },
12640            "wobble"
12641            | "摆动"
12642            | "揺れ"
12643            | "흔들림"
12644            | "โยก"
12645            | "نوسان"
12646            | "تذبذب"
12647            | "תנודה"
12648            | "لرزش"
12649            | "osciller"
12650            | "wackeln"
12651            | "покачивание" => {
12652                let t = self.arg_num(&args, 0, 0.0)? as f32;
12653                let freq = self.arg_num(&args, 1, 1.0)? as f32;
12654                let amp = self.arg_num(&args, 2, 1.0)? as f32;
12655                let phase = self.arg_num(&args, 3, 0.0)? as f32;
12656                return Ok(Value::Number(
12657                    ling_animation::scalar::wobble(t, freq, amp, phase) as f64,
12658                ));
12659            },
12660            "gait_phase"
12661            | "步相"
12662            | "歩相"
12663            | "걸음위상"
12664            | "เฟสก้าว"
12665            | "فاز_گام"
12666            | "طور_المشية"
12667            | "שלב_הליכה"
12668            | "چال_مرحلہ"
12669            | "phase_démarche"
12670            | "gangphase"
12671            | "фаза_походки" => {
12672                let t = self.arg_num(&args, 0, 0.0)? as f32;
12673                let speed = self.arg_num(&args, 1, 1.0)? as f32;
12674                return Ok(Value::Number(
12675                    ling_animation::scalar::gait_phase(t, speed) as f64
12676                ));
12677            },
12678            "gait_swing"
12679            | "步摆"
12680            | "歩振り"
12681            | "걸음흔들"
12682            | "ก้าวแกว่ง"
12683            | "نوسان_گام"
12684            | "أرجحة_المشية"
12685            | "נדנוד_הליכה"
12686            | "چال_جھولا"
12687            | "balancement_démarche"
12688            | "gangschwung"
12689            | "мах_походки" => {
12690                let t = self.arg_num(&args, 0, 0.0)? as f32;
12691                let speed = self.arg_num(&args, 1, 1.0)? as f32;
12692                let stride = self.arg_num(&args, 2, 1.0)? as f32;
12693                return Ok(Value::Number(
12694                    ling_animation::scalar::gait_swing(t, speed, stride) as f64,
12695                ));
12696            },
12697            "gait_lift"
12698            | "抬脚"
12699            | "足上げ"
12700            | "발들기"
12701            | "ยกเท้า"
12702            | "بلندشدن_گام"
12703            | "رفع_المشية"
12704            | "הרמת_הליכה"
12705            | "چال_اٹھاؤ"
12706            | "levée_démarche"
12707            | "ganghub"
12708            | "подъём_походки" => {
12709                let t = self.arg_num(&args, 0, 0.0)? as f32;
12710                let speed = self.arg_num(&args, 1, 1.0)? as f32;
12711                let height = self.arg_num(&args, 2, 1.0)? as f32;
12712                return Ok(Value::Number(
12713                    ling_animation::scalar::gait_lift(t, speed, height) as f64,
12714                ));
12715            },
12716            "spring_to"
12717            | "弹向"
12718            | "バネ寄せ"
12719            | "스프링이동"
12720            | "สปริงไป"
12721            | "فنر_به‌سوی"
12722            | "نابض_إلى"
12723            | "קפיץ_אל"
12724            | "اسپرنگ_تک"
12725            | "ressort_vers"
12726            | "feder_zu"
12727            | "пружина_к" => {
12728                let pos = self.arg_num(&args, 0, 0.0)? as f32;
12729                let vel = self.arg_num(&args, 1, 0.0)? as f32;
12730                let target = self.arg_num(&args, 2, 0.0)? as f32;
12731                let stiffness = self.arg_num(&args, 3, 120.0)? as f32;
12732                let damping = self.arg_num(&args, 4, 14.0)? as f32;
12733                let dt = self.arg_num(&args, 5, 1.0 / 60.0)? as f32;
12734                let (np, nv) =
12735                    ling_animation::scalar::spring_step(pos, vel, target, stiffness, damping, dt);
12736                return Ok(Value::List(Rc::new(vec![
12737                    Value::Number(np as f64),
12738                    Value::Number(nv as f64),
12739                ])));
12740            },
12741            "ik2"
12742            | "反解"
12743            | "逆運動"
12744            | "역운동"
12745            | "ไอเค2"
12746            | "سینماتیک_معکوس2"
12747            | "حركية_عكسية2"
12748            | "קינמטיקה_הפוכה2"
12749            | "آئی_کے2"
12750            | "cinématique_inverse2"
12751            | "inverse_kinematik2"
12752            | "обратная_кинематика2" => {
12753                let l1 = self.arg_num(&args, 0, 1.0)? as f32;
12754                let l2 = self.arg_num(&args, 1, 1.0)? as f32;
12755                let tx = self.arg_num(&args, 2, 0.0)? as f32;
12756                let ty = self.arg_num(&args, 3, 0.0)? as f32;
12757                let (sh, el) = ling_animation::scalar::two_bone_ik(l1, l2, tx, ty);
12758                return Ok(Value::List(Rc::new(vec![
12759                    Value::Number(sh as f64),
12760                    Value::Number(el as f64),
12761                ])));
12762            },
12763            // ── Mechanical 机 ──
12764            "gear_couple"
12765            | "齿轮联动"
12766            | "歯車連動"
12767            | "기어연동"
12768            | "เฟืองทด"
12769            | "جفت_چرخ‌دنده"
12770            | "اقتران_التروس"
12771            | "צימוד_גלגלי_שיניים"
12772            | "گیئر_جوڑا"
12773            | "accoupler_engrenage"
12774            | "zahnrad_koppeln"
12775            | "сцепить_шестерни" => {
12776                let angle = self.arg_num(&args, 0, 0.0)? as f32;
12777                let ti = self.arg_num(&args, 1, 1.0)? as f32;
12778                let to = self.arg_num(&args, 2, 1.0)? as f32;
12779                return Ok(Value::Number(
12780                    ling_animation::scalar::gear(angle, ti, to) as f64
12781                ));
12782            },
12783            "gear_train"
12784            | "齿轮组"
12785            | "歯車列"
12786            | "기어열"
12787            | "ชุดเฟือง"
12788            | "مجموعه_چرخ‌دنده"
12789            | "قطار_التروس"
12790            | "שרשרת_גלגלי_שיניים"
12791            | "گیئر_ٹرین"
12792            | "train_engrenages"
12793            | "zahnradgetriebe"
12794            | "передача_шестерён" => {
12795                let angle = self.arg_num(&args, 0, 0.0)? as f32;
12796                let teeth: Vec<f32> = match args.get(1) {
12797                    Some(Value::List(items)) => items
12798                        .iter()
12799                        .filter_map(|v| {
12800                            if let Value::Number(n) = v {
12801                                Some(*n as f32)
12802                            } else {
12803                                None
12804                            }
12805                        })
12806                        .collect(),
12807                    _ => Vec::new(),
12808                };
12809                let out = ling_animation::mechanism::gear_train(angle, &teeth);
12810                return Ok(Value::List(Rc::new(
12811                    out.into_iter().map(|a| Value::Number(a as f64)).collect(),
12812                )));
12813            },
12814            "cam_lift"
12815            | "凸轮升程"
12816            | "カム揚程"
12817            | "캠리프트"
12818            | "ยกลูกเบี้ยว"
12819            | "بلندشدن_بادامک"
12820            | "رفع_الكامة"
12821            | "הרמת_קאם"
12822            | "کیم_اٹھاؤ"
12823            | "levée_came"
12824            | "nockenhub"
12825            | "подъём_кулачка" => {
12826                let angle = self.arg_num(&args, 0, 0.0)? as f32;
12827                let lift = self.arg_num(&args, 1, 1.0)? as f32;
12828                return Ok(Value::Number(
12829                    ling_animation::scalar::cam_lift(angle, lift) as f64
12830                ));
12831            },
12832            "piston" | "活塞" | "ピストン" | "피스톤" | "ลูกสูบ" | "پیستون" | "مكبس" | "בוכנה"
12833            | "پسٹن" | "kolben" | "поршень" => {
12834                let angle = self.arg_num(&args, 0, 0.0)? as f32;
12835                let crank = self.arg_num(&args, 1, 1.0)? as f32;
12836                let rod = self.arg_num(&args, 2, 2.0)? as f32;
12837                return Ok(Value::Number(
12838                    ling_animation::scalar::piston(angle, crank, rod) as f64,
12839                ));
12840            },
12841            "rack"
12842            | "齿条"
12843            | "ラック"
12844            | "랙"
12845            | "แร็ค"
12846            | "زبانه‌دنده"
12847            | "سكة_مسننة"
12848            | "מוט_שיניים"
12849            | "ریک"
12850            | "crémaillère"
12851            | "zahnstange"
12852            | "рейка" => {
12853                let angle = self.arg_num(&args, 0, 0.0)? as f32;
12854                let radius = self.arg_num(&args, 1, 1.0)? as f32;
12855                return Ok(Value::Number(
12856                    ling_animation::scalar::rack(angle, radius) as f64
12857                ));
12858            },
12859            #[cfg(not(target_arch = "wasm32"))]
12860            "mouse_x" => {
12861                let gfx = self.gfx.borrow();
12862                let v = gfx
12863                    .window
12864                    .as_ref()
12865                    .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
12866                    .map(|p| p.0 as f64)
12867                    .unwrap_or(0.0);
12868                return Ok(Value::Number(v));
12869            },
12870            #[cfg(target_arch = "wasm32")]
12871            "mouse_x" => {
12872                return Ok(Value::Number(crate::gfx::wasm_mouse_x() as f64));
12873            },
12874            #[cfg(not(target_arch = "wasm32"))]
12875            "mouse_y" => {
12876                let gfx = self.gfx.borrow();
12877                let v = gfx
12878                    .window
12879                    .as_ref()
12880                    .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
12881                    .map(|p| p.1 as f64)
12882                    .unwrap_or(0.0);
12883                return Ok(Value::Number(v));
12884            },
12885            #[cfg(target_arch = "wasm32")]
12886            "mouse_y" => {
12887                return Ok(Value::Number(crate::gfx::wasm_mouse_y() as f64));
12888            },
12889            #[cfg(not(target_arch = "wasm32"))]
12890            "mouse_down" => {
12891                let mut gfx = self.gfx.borrow_mut();
12892                let d = !gfx.input_suppressed()
12893                    && gfx
12894                        .window
12895                        .as_ref()
12896                        .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
12897                        .unwrap_or(false);
12898                return Ok(Value::Bool(d));
12899            },
12900            #[cfg(target_arch = "wasm32")]
12901            "mouse_down" => {
12902                return Ok(Value::Bool(crate::gfx::wasm_mouse_down()));
12903            },
12904            #[cfg(not(target_arch = "wasm32"))]
12905            "mouse_down_right"
12906            | "เมาส์ขวา"
12907            | "ماوس_راست_فشرده"
12908            | "الفأرة_اليمنى_مضغوطة"
12909            | "עכבר_ימני_לחוץ"
12910            | "دایاں_ماؤس_دبا_ہوا" => {
12911                let mut gfx = self.gfx.borrow_mut();
12912                let d = !gfx.input_suppressed()
12913                    && gfx
12914                        .window
12915                        .as_ref()
12916                        .map(|w| w.get_mouse_down(minifb::MouseButton::Right))
12917                        .unwrap_or(false);
12918                return Ok(Value::Bool(d));
12919            },
12920            #[cfg(target_arch = "wasm32")]
12921            "mouse_down_right"
12922            | "เมาส์ขวา"
12923            | "ماوس_راست_فشرده"
12924            | "الفأرة_اليمنى_مضغوطة"
12925            | "עכבר_ימני_לחוץ"
12926            | "دایاں_ماؤس_دبا_ہوا" => {
12927                return Ok(Value::Bool(crate::gfx::wasm_mouse_down_right()));
12928            },
12929            #[cfg(not(target_arch = "wasm32"))]
12930            "mouse_down_middle"
12931            | "เมาส์กลาง"
12932            | "ماوس_وسط_فشرده"
12933            | "الفأرة_الوسطى_مضغوطة"
12934            | "עכבר_אמצעי_לחוץ"
12935            | "درمیانی_ماؤس_دبا_ہوا" => {
12936                let mut gfx = self.gfx.borrow_mut();
12937                let d = !gfx.input_suppressed()
12938                    && gfx
12939                        .window
12940                        .as_ref()
12941                        .map(|w| w.get_mouse_down(minifb::MouseButton::Middle))
12942                        .unwrap_or(false);
12943                return Ok(Value::Bool(d));
12944            },
12945            #[cfg(target_arch = "wasm32")]
12946            "mouse_down_middle"
12947            | "เมาส์กลาง"
12948            | "ماوس_وسط_فشرده"
12949            | "الفأرة_الوسطى_مضغوطة"
12950            | "עכבר_אמצעי_לחוץ"
12951            | "درمیانی_ماؤس_دبا_ہوا" => {
12952                return Ok(Value::Bool(crate::gfx::wasm_mouse_down_middle()));
12953            },
12954            #[cfg(not(target_arch = "wasm32"))]
12955            "ui_hot"
12956            | "热区"
12957            | "ホットエリア"
12958            | "핫존"
12959            | "พื้นที่สัมผัส"
12960            | "ناحیه_فعال"
12961            | "منطقة_ساخنة"
12962            | "אזור_חם"
12963            | "ہاٹ_زون"
12964            | "survol_ui"
12965            | "ui_hover"
12966            | "ui_наведение" => {
12967                let x = self.arg_num(&args, 0, 0.0)? as f32;
12968                let y = self.arg_num(&args, 1, 0.0)? as f32;
12969                let w = self.arg_num(&args, 2, 0.0)? as f32;
12970                let h = self.arg_num(&args, 3, 0.0)? as f32;
12971                let gfx = self.gfx.borrow();
12972                let (mx, my) = gfx
12973                    .window
12974                    .as_ref()
12975                    .and_then(|win| win.get_mouse_pos(minifb::MouseMode::Clamp))
12976                    .unwrap_or((0.0, 0.0));
12977                return Ok(Value::Bool(ling_ui::holo::hit_rect(mx, my, x, y, w, h)));
12978            },
12979            #[cfg(target_arch = "wasm32")]
12980            "ui_hot"
12981            | "热区"
12982            | "ホットエリア"
12983            | "핫존"
12984            | "พื้นที่สัมผัส"
12985            | "ناحیه_فعال"
12986            | "منطقة_ساخنة"
12987            | "אזור_חם"
12988            | "ہاٹ_زون"
12989            | "survol_ui"
12990            | "ui_hover"
12991            | "ui_наведение" => {
12992                return Ok(Value::Bool(false));
12993            },
12994            // ui_text(x, y, scale, "string") — holographic vector text
12995            "ui_text"
12996            | "界面文字"
12997            | "UI文字"
12998            | "UI텍스트"
12999            | "ข้อความหน้าจอ"
13000            | "متن_رابط"
13001            | "نص_الواجهة"
13002            | "טקסט_ממשק"
13003            | "یو_آئی_متن"
13004            | "texte_ui"
13005            | "ui_beschriftung"
13006            | "ui_текст" => {
13007                let x = self.arg_num(&args, 0, 0.0)? as f32;
13008                let y = self.arg_num(&args, 1, 0.0)? as f32;
13009                let scale = self.arg_num(&args, 2, 16.0)? as f32;
13010                let s = self.arg_str(&args, 3, "");
13011                let segs = ling_ui::holo::text_lines(&s, x, y, scale * 0.62, scale, scale * 0.24);
13012                let mut gfx = self.gfx.borrow_mut();
13013                let (w, h, color) = (gfx.width, gfx.height, gfx.color);
13014                for sg in segs {
13015                    draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
13016                }
13017                return Ok(Value::Unit);
13018            },
13019            // font_load("path.ttf") — load a vector font (outlines cached lazily as
13020            // cache/fonts/<stem>/<codepoint>.ling). Returns a handle, or -1 on failure.
13021            #[cfg(not(target_arch = "wasm32"))]
13022            "font_load"
13023            | "โหลดฟอนต์"
13024            | "加载字体"
13025            | "フォント読込"
13026            | "글꼴로드"
13027            | "بارگذاری_فونت"
13028            | "تحميل_الخط"
13029            | "טעינת_גופן"
13030            | "فونٹ_لوڈ"
13031            | "charger_police"
13032            | "schriftart_laden"
13033            | "загрузить_шрифт" => {
13034                let path = self.arg_str(&args, 0, "");
13035                // Optional 2nd arg: variable-font weight (e.g. 600 for a solid, bold UI).
13036                let weight = match self.arg_num(&args, 1, 0.0)? {
13037                    w if w > 0.0 => Some(w as f32),
13038                    _ => None,
13039                };
13040                // Try the path as given, then relative to the script's directory.
13041                let mut loaded = ling_graphics::VectorFont::from_path_weight(&path, weight);
13042                if loaded.is_err() {
13043                    if let Some(dir) = &self.source_dir {
13044                        let joined = dir.join(&path);
13045                        loaded = ling_graphics::VectorFont::from_path_weight(
13046                            &joined.to_string_lossy(),
13047                            weight,
13048                        );
13049                    }
13050                }
13051                match loaded {
13052                    Ok(f) => {
13053                        let id = self.fonts.len();
13054                        self.fonts.push(f);
13055                        return Ok(Value::Number(id as f64));
13056                    },
13057                    Err(e) => {
13058                        eprintln!("font_load failed ({path}): {e}");
13059                        return Ok(Value::Number(-1.0));
13060                    },
13061                }
13062            },
13063            #[cfg(target_arch = "wasm32")]
13064            "font_load"
13065            | "โหลดฟอนต์"
13066            | "加载字体"
13067            | "フォント読込"
13068            | "글꼴로드"
13069            | "بارگذاری_فونت"
13070            | "تحميل_الخط"
13071            | "טעינת_גופן"
13072            | "فونٹ_لوڈ"
13073            | "charger_police"
13074            | "schriftart_laden"
13075            | "загрузить_шрифт" => {
13076                // Web runtime does not load host TTF/OTF files yet.
13077                // Return -1 so scripts can fall back to ui_text.
13078                return Ok(Value::Number(-1.0));
13079            },
13080            // image_load("path.png") — decode a raster image (via the `image` crate)
13081            // for pixel sampling (image_width/image_height/image_pixel_r/g/b/a) —
13082            // used by the coin-stamp mosaic tool to read a source photo's
13083            // colour/darkness. Returns a handle, or -1 on failure.
13084            #[cfg(not(target_arch = "wasm32"))]
13085            "image_load" => {
13086                let path = self.arg_str(&args, 0, "").replace('\\', "/");
13087                let mut loaded = image::open(&path);
13088                if loaded.is_err() {
13089                    if let Some(dir) = &self.source_dir {
13090                        let joined = dir.join(&path);
13091                        loaded = image::open(&joined);
13092                    }
13093                }
13094                match loaded {
13095                    Ok(img) => {
13096                        let id = self.images.len();
13097                        self.images.push(img.to_rgba8());
13098                        return Ok(Value::Number(id as f64));
13099                    },
13100                    Err(e) => {
13101                        eprintln!("image_load failed ({path}): {e}");
13102                        return Ok(Value::Number(-1.0));
13103                    },
13104                }
13105            },
13106            #[cfg(target_arch = "wasm32")]
13107            "image_load" => {
13108                // Web runtime does not load host image files yet.
13109                return Ok(Value::Number(-1.0));
13110            },
13111            "image_width" => {
13112                let id = self.arg_num(&args, 0, -1.0)? as i64;
13113                if id >= 0 && (id as usize) < self.images.len() {
13114                    return Ok(Value::Number(self.images[id as usize].width() as f64));
13115                }
13116                return Ok(Value::Number(0.0));
13117            },
13118            "image_height" => {
13119                let id = self.arg_num(&args, 0, -1.0)? as i64;
13120                if id >= 0 && (id as usize) < self.images.len() {
13121                    return Ok(Value::Number(self.images[id as usize].height() as f64));
13122                }
13123                return Ok(Value::Number(0.0));
13124            },
13125            "image_pixel_r" | "image_pixel_g" | "image_pixel_b" | "image_pixel_a" => {
13126                let id = self.arg_num(&args, 0, -1.0)? as i64;
13127                let px = self.arg_num(&args, 1, 0.0)? as i64;
13128                let py = self.arg_num(&args, 2, 0.0)? as i64;
13129                if id >= 0 && (id as usize) < self.images.len() {
13130                    let img = &self.images[id as usize];
13131                    if px >= 0 && py >= 0 && (px as u32) < img.width() && (py as u32) < img.height()
13132                    {
13133                        let p = img.get_pixel(px as u32, py as u32);
13134                        let ch = match name {
13135                            "image_pixel_r" => p[0],
13136                            "image_pixel_g" => p[1],
13137                            "image_pixel_b" => p[2],
13138                            _ => p[3],
13139                        };
13140                        return Ok(Value::Number(ch as f64));
13141                    }
13142                }
13143                return Ok(Value::Number(0.0));
13144            },
13145            // image_new(w, h) — a new blank (fully transparent) RGBA image the
13146            // script can paint into with image_set_pixel and write out with
13147            // image_save. Lives in the same self.images table as image_load,
13148            // so image_width/image_height/image_pixel_* all work on it too.
13149            // Used by the coin-stamp tool to build cropped, physically-sized
13150            // (mm x DPI) PNG exports — something a raw window screenshot()
13151            // can't do, since it always captures the whole on-screen
13152            // framebuffer at whatever size the window happens to be.
13153            "image_new" => {
13154                let w = self.arg_num(&args, 0, 1.0)?.max(1.0) as u32;
13155                let h = self.arg_num(&args, 1, 1.0)?.max(1.0) as u32;
13156                let id = self.images.len();
13157                self.images.push(image::RgbaImage::new(w, h));
13158                return Ok(Value::Number(id as f64));
13159            },
13160            // image_set_pixel(id, x, y, r, g, b, a) — paint one pixel of an
13161            // image created with image_new (0..255 channels; out-of-bounds is
13162            // a silent no-op, matching image_pixel_*'s own out-of-bounds
13163            // behaviour).
13164            "image_set_pixel" => {
13165                let id = self.arg_num(&args, 0, -1.0)? as i64;
13166                let px = self.arg_num(&args, 1, 0.0)? as i64;
13167                let py = self.arg_num(&args, 2, 0.0)? as i64;
13168                let r = self.arg_num(&args, 3, 0.0)?.clamp(0.0, 255.0) as u8;
13169                let g = self.arg_num(&args, 4, 0.0)?.clamp(0.0, 255.0) as u8;
13170                let b = self.arg_num(&args, 5, 0.0)?.clamp(0.0, 255.0) as u8;
13171                let a = self.arg_num(&args, 6, 255.0)?.clamp(0.0, 255.0) as u8;
13172                if id >= 0 && (id as usize) < self.images.len() {
13173                    let img = &mut self.images[id as usize];
13174                    if px >= 0 && py >= 0 && (px as u32) < img.width() && (py as u32) < img.height()
13175                    {
13176                        img.put_pixel(px as u32, py as u32, image::Rgba([r, g, b, a]));
13177                    }
13178                }
13179                return Ok(Value::Unit);
13180            },
13181            // image_save(id, "path.png") — encode an image (from image_new or
13182            // image_load) to disk, alpha preserved. Returns 1 on success, -1
13183            // on failure (bad id or write error), mirroring image_load's own
13184            // -1-on-failure convention. Path resolves the same way
13185            // write_file/copy_file's outputs do: relative to the script's own
13186            // working directory (typically the app dir the launcher cd's
13187            // into), not source_dir.
13188            #[cfg(not(target_arch = "wasm32"))]
13189            "image_save" => {
13190                let id = self.arg_num(&args, 0, -1.0)? as i64;
13191                let path = self.arg_str(&args, 1, "");
13192                if id >= 0 && (id as usize) < self.images.len() {
13193                    if let Some(parent) = std::path::Path::new(&path).parent() {
13194                        if !parent.as_os_str().is_empty() {
13195                            let _ = std::fs::create_dir_all(parent);
13196                        }
13197                    }
13198                    if self.images[id as usize].save(&path).is_ok() {
13199                        return Ok(Value::Number(1.0));
13200                    }
13201                }
13202                return Ok(Value::Number(-1.0));
13203            },
13204            #[cfg(target_arch = "wasm32")]
13205            "image_save" => {
13206                return Ok(Value::Number(-1.0));
13207            },
13208            // image_draw(id, x, y, w, h) — blit an image (nearest-neighbour
13209            // scaled to w x h, alpha-blended against whatever's already in
13210            // the framebuffer) into the current frame. A native pixel loop,
13211            // not a .ling-level per-pixel image_pixel_*+pixel() loop: doing
13212            // this from script for even a modest thumbnail grid re-incurs
13213            // the exact per-frame interpreted-call-volume cost that made
13214            // small mosaic tiles hang the UI (see mosaic.ling's
13215            // xform_glyph_pts_fit fix) — this is the "read the framebuffer
13216            // out" direction's counterpart to screenshot().
13217            #[cfg(not(target_arch = "wasm32"))]
13218            "image_draw" => {
13219                let id = self.arg_num(&args, 0, -1.0)? as i64;
13220                let dx = self.arg_num(&args, 1, 0.0)? as i32;
13221                let dy = self.arg_num(&args, 2, 0.0)? as i32;
13222                let dw = self.arg_num(&args, 3, 0.0)?.max(0.0) as i32;
13223                let dh = self.arg_num(&args, 4, 0.0)?.max(0.0) as i32;
13224                if id >= 0 && (id as usize) < self.images.len() && dw > 0 && dh > 0 {
13225                    let img = &self.images[id as usize];
13226                    let sw = img.width() as i32;
13227                    let sh = img.height() as i32;
13228                    if sw > 0 && sh > 0 {
13229                        let mut gfx = self.gfx.borrow_mut();
13230                        let (fw, fh) = (gfx.width as i32, gfx.height as i32);
13231                        for py in 0..dh {
13232                            let ty = dy + py;
13233                            if ty < 0 || ty >= fh {
13234                                continue;
13235                            }
13236                            let sy = (py * sh / dh).clamp(0, sh - 1) as u32;
13237                            for px in 0..dw {
13238                                let tx = dx + px;
13239                                if tx < 0 || tx >= fw {
13240                                    continue;
13241                                }
13242                                let sx = (px * sw / dw).clamp(0, sw - 1) as u32;
13243                                let p = img.get_pixel(sx, sy);
13244                                let a = p[3] as u32;
13245                                if a == 0 {
13246                                    continue;
13247                                }
13248                                let idx = ty as usize * gfx.width + tx as usize;
13249                                if a >= 255 {
13250                                    gfx.buffer[idx] = ((p[0] as u32) << 16)
13251                                        | ((p[1] as u32) << 8)
13252                                        | (p[2] as u32);
13253                                } else {
13254                                    let bg = gfx.buffer[idx];
13255                                    let br = (bg >> 16) & 0xff;
13256                                    let bg_g = (bg >> 8) & 0xff;
13257                                    let bb = bg & 0xff;
13258                                    let r = (p[0] as u32 * a + br * (255 - a)) / 255;
13259                                    let g = (p[1] as u32 * a + bg_g * (255 - a)) / 255;
13260                                    let b = (p[2] as u32 * a + bb * (255 - a)) / 255;
13261                                    gfx.buffer[idx] = (r << 16) | (g << 8) | b;
13262                                }
13263                            }
13264                        }
13265                    }
13266                }
13267                return Ok(Value::Unit);
13268            },
13269            #[cfg(target_arch = "wasm32")]
13270            "image_draw" => {
13271                return Ok(Value::Unit);
13272            },
13273            // font_text(handle, x, y, px, "string") — anti-aliased *stroked* vector outline
13274            // in the current set_color / set_blend. (x,y) is the text box top-left.
13275            #[cfg(not(target_arch = "wasm32"))]
13276            "font_text"
13277            | "ข้อความฟอนต์"
13278            | "字体文本"
13279            | "フォント文字"
13280            | "글꼴텍스트"
13281            | "متن_فونت"
13282            | "نص_الخط"
13283            | "טקסט_גופן"
13284            | "فونٹ_متن"
13285            | "texte_police"
13286            | "schriftart_text"
13287            | "текст_шрифт" => {
13288                let id = self.arg_num(&args, 0, 0.0)? as i64;
13289                let x = self.arg_num(&args, 1, 0.0)? as f32;
13290                let y = self.arg_num(&args, 2, 0.0)? as f32;
13291                let px = self.arg_num(&args, 3, 16.0)? as f32;
13292                let s = self.arg_str(&args, 4, "");
13293                if id >= 0 && (id as usize) < self.fonts.len() && px > 0.0 {
13294                    let strokes = self.font_layout_2d(id as usize, x, y, px, &s);
13295                    let mut gfx = self.gfx.borrow_mut();
13296                    let (w, h, color, add, aa) = (
13297                        gfx.width,
13298                        gfx.height,
13299                        gfx.color,
13300                        gfx.blend == 1,
13301                        gfx.font_antialias,
13302                    );
13303                    for pl in &strokes {
13304                        for seg in pl.windows(2) {
13305                            if aa {
13306                                crate::gfx::raster::draw_line_aa(
13307                                    &mut gfx.buffer,
13308                                    w,
13309                                    h,
13310                                    color,
13311                                    add,
13312                                    seg[0][0],
13313                                    seg[0][1],
13314                                    seg[1][0],
13315                                    seg[1][1],
13316                                );
13317                            } else {
13318                                crate::gfx::raster::draw_line(
13319                                    &mut gfx.buffer,
13320                                    w,
13321                                    h,
13322                                    color,
13323                                    seg[0][0],
13324                                    seg[0][1],
13325                                    seg[1][0],
13326                                    seg[1][1],
13327                                );
13328                            }
13329                        }
13330                    }
13331                }
13332                return Ok(Value::Unit);
13333            },
13334            #[cfg(target_arch = "wasm32")]
13335            "font_text"
13336            | "ข้อความฟอนต์"
13337            | "字体文本"
13338            | "フォント文字"
13339            | "글꼴텍스트"
13340            | "متن_فونت"
13341            | "نص_الخط"
13342            | "טקסט_גופן"
13343            | "فونٹ_متن"
13344            | "texte_police"
13345            | "schriftart_text"
13346            | "текст_шрифт" => {
13347                return Ok(Value::Unit);
13348            },
13349            // font_text_fill(handle, x, y, px, "string") — filled vector glyphs;
13350            // anti-aliased when `set_font_antialias(1)` is on (default off = crisp).
13351            #[cfg(not(target_arch = "wasm32"))]
13352            "font_text_fill"
13353            | "เติมฟอนต์"
13354            | "填充字体"
13355            | "フォント塗り"
13356            | "글꼴채움"
13357            | "پرکردن_متن_فونت"
13358            | "تعبئة_نص_الخط"
13359            | "מילוי_טקסט_גופן"
13360            | "فونٹ_متن_بھرو"
13361            | "remplir_texte_police"
13362            | "schriftart_text_füllen"
13363            | "заполнить_текст_шрифт" => {
13364                let id = self.arg_num(&args, 0, 0.0)? as i64;
13365                let x = self.arg_num(&args, 1, 0.0)? as f32;
13366                let y = self.arg_num(&args, 2, 0.0)? as f32;
13367                let px = self.arg_num(&args, 3, 16.0)? as f32;
13368                let s = self.arg_str(&args, 4, "");
13369                if id >= 0 && (id as usize) < self.fonts.len() && px > 0.0 {
13370                    // fill each glyph independently so interior holes (winding) stay correct
13371                    let glyphs = self.font_layout_2d_glyphs(id as usize, x, y, px, &s);
13372                    let mut gfx = self.gfx.borrow_mut();
13373                    let (w, h, color, add, aa) = (
13374                        gfx.width,
13375                        gfx.height,
13376                        gfx.color,
13377                        gfx.blend == 1,
13378                        gfx.font_antialias,
13379                    );
13380                    for contours in &glyphs {
13381                        if aa {
13382                            crate::gfx::raster::fill_contours_aa(
13383                                &mut gfx.buffer,
13384                                w,
13385                                h,
13386                                color,
13387                                add,
13388                                contours,
13389                            );
13390                        } else {
13391                            crate::gfx::raster::fill_contours(
13392                                &mut gfx.buffer,
13393                                w,
13394                                h,
13395                                color,
13396                                add,
13397                                contours,
13398                            );
13399                        }
13400                    }
13401                }
13402                return Ok(Value::Unit);
13403            },
13404            #[cfg(target_arch = "wasm32")]
13405            "font_text_fill"
13406            | "เติมฟอนต์"
13407            | "填充字体"
13408            | "フォント塗り"
13409            | "글꼴채움"
13410            | "پرکردن_متن_فونت"
13411            | "تعبئة_نص_الخط"
13412            | "מילוי_טקסט_גופן"
13413            | "فونٹ_متن_بھرو"
13414            | "remplir_texte_police"
13415            | "schriftart_text_füllen"
13416            | "заполнить_текст_шрифт" => {
13417                return Ok(Value::Unit);
13418            },
13419            // font_text_3d(handle, cx,cy,cz, ux,uy,uz, vx,vy,vz, size, "string")
13420            // — stroked vector text on a 3D plane: u = advance dir, v = up dir, size = world/em.
13421            //   Flows through the depth-sorted line pipeline, so it rotates with the camera (and 4D).
13422            #[cfg(not(target_arch = "wasm32"))]
13423            "font_text_3d"
13424            | "ข้อความฟอนต์3มิติ"
13425            | "字体3D"
13426            | "フォント3D"
13427            | "글꼴3D"
13428            | "متن_فونت_سه‌بعدی"
13429            | "نص_خط_ثلاثي_الأبعاد"
13430            | "טקסט_גופן_תלת_ממדי"
13431            | "تھری_ڈی_فونٹ_متن"
13432            | "texte_police_3d"
13433            | "schriftart_text_3d"
13434            | "текст_шрифт_3d" => {
13435                let id = self.arg_num(&args, 0, 0.0)? as i64;
13436                let cx = self.arg_num(&args, 1, 0.0)? as f32;
13437                let cy = self.arg_num(&args, 2, 0.0)? as f32;
13438                let cz = self.arg_num(&args, 3, 0.0)? as f32;
13439                let ux = self.arg_num(&args, 4, 1.0)? as f32;
13440                let uy = self.arg_num(&args, 5, 0.0)? as f32;
13441                let uz = self.arg_num(&args, 6, 0.0)? as f32;
13442                let vx = self.arg_num(&args, 7, 0.0)? as f32;
13443                let vy = self.arg_num(&args, 8, 1.0)? as f32;
13444                let vz = self.arg_num(&args, 9, 0.0)? as f32;
13445                let size = self.arg_num(&args, 10, 1.0)? as f32;
13446                let s = self.arg_str(&args, 11, "");
13447                // Optional arg 12: fill_rows — when > 0, each glyph interior is
13448                // filled with that many even-odd scanline spans (true filled
13449                // letterforms, not a bounding box). 0/omitted = outline only.
13450                let fill_rows = self.arg_num(&args, 12, 0.0)? as i32;
13451                if id >= 0 && (id as usize) < self.fonts.len() && size > 0.0 {
13452                    // Build world-space polylines: world = C + (pen+ex)*size*U + ey*size*V
13453                    let font = &mut self.fonts[id as usize];
13454                    let asc = font.ascent();
13455                    let mut pen = 0.0f32;
13456                    let mut lines: Vec<[f32; 6]> = Vec::new();
13457                    for ch in s.chars() {
13458                        let go = font.glyph_outline(ch, 0.01);
13459                        let map = |p: [f32; 2], pen: f32| {
13460                            let a = pen + p[0];
13461                            let b = p[1] - asc; // shift so the top of the cap sits near C
13462                            [
13463                                cx + a * size * ux + b * size * vx,
13464                                cy + a * size * uy + b * size * vy,
13465                                cz + a * size * uz + b * size * vz,
13466                            ]
13467                        };
13468                        for pl in &go.polylines {
13469                            for seg in pl.windows(2) {
13470                                let p0 = map(seg[0], pen);
13471                                let p1 = map(seg[1], pen);
13472                                lines.push([p0[0], p0[1], p0[2], p1[0], p1[1], p1[2]]);
13473                            }
13474                        }
13475                        if fill_rows > 0 {
13476                            // Even-odd scanline fill in glyph space. Contours may
13477                            // omit their closing edge, so the implicit last→first
13478                            // segment is scanned too (skipped when degenerate).
13479                            let (mut ymin, mut ymax) = (f32::MAX, f32::MIN);
13480                            for pl in &go.polylines {
13481                                for p in pl {
13482                                    ymin = ymin.min(p[1]);
13483                                    ymax = ymax.max(p[1]);
13484                                }
13485                            }
13486                            if ymax > ymin {
13487                                for r in 0..fill_rows {
13488                                    let y =
13489                                        ymin + (r as f32 + 0.5) * (ymax - ymin) / fill_rows as f32;
13490                                    let mut xs: Vec<f32> = Vec::new();
13491                                    for pl in &go.polylines {
13492                                        let n = pl.len();
13493                                        if n < 2 {
13494                                            continue;
13495                                        }
13496                                        for k in 0..n {
13497                                            let p0 = pl[k];
13498                                            let p1 = pl[(k + 1) % n];
13499                                            if k + 1 == n
13500                                                && (p1[0] - p0[0]).abs() < 1e-6
13501                                                && (p1[1] - p0[1]).abs() < 1e-6
13502                                            {
13503                                                continue; // contour already closed
13504                                            }
13505                                            let (y0, y1) = (p0[1], p1[1]);
13506                                            if (y0 <= y && y1 > y) || (y1 <= y && y0 > y) {
13507                                                let t = (y - y0) / (y1 - y0);
13508                                                xs.push(p0[0] + t * (p1[0] - p0[0]));
13509                                            }
13510                                        }
13511                                    }
13512                                    xs.sort_by(|a, b| {
13513                                        a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
13514                                    });
13515                                    let mut k = 0;
13516                                    while k + 1 < xs.len() {
13517                                        let a = map([xs[k], y], pen);
13518                                        let b = map([xs[k + 1], y], pen);
13519                                        lines.push([a[0], a[1], a[2], b[0], b[1], b[2]]);
13520                                        k += 2;
13521                                    }
13522                                }
13523                            }
13524                        }
13525                        pen += go.advance;
13526                    }
13527                    let mut gfx = self.gfx.borrow_mut();
13528                    let color = gfx.color;
13529                    let near = -gfx.camera.zdist + 0.05;
13530                    for l in &lines {
13531                        let (mut ax, mut ay, mut az) = (l[0], l[1], l[2]);
13532                        let (mut bx, mut by, mut bz) = (l[3], l[4], l[5]);
13533                        let da = gfx.camera.depth(ax, ay, az);
13534                        let db = gfx.camera.depth(bx, by, bz);
13535                        if da <= near && db <= near {
13536                            continue;
13537                        }
13538                        if da <= near {
13539                            let t = (near - da) / (db - da);
13540                            ax += t * (bx - ax);
13541                            ay += t * (by - ay);
13542                            az += t * (bz - az);
13543                        } else if db <= near {
13544                            let t = (near - da) / (db - da);
13545                            bx = ax + t * (bx - ax);
13546                            by = ay + t * (by - ay);
13547                            bz = az + t * (bz - az);
13548                        }
13549                        let (sax, say, da2) = gfx.camera.project(ax, ay, az);
13550                        let (sbx, sby, db2) = gfx.camera.project(bx, by, bz);
13551                        let depth = (da2 + db2) / 2.0;
13552                        gfx.depth_queue.push_line(depth, color, sax, say, sbx, sby);
13553                    }
13554                }
13555                return Ok(Value::Unit);
13556            },
13557            #[cfg(target_arch = "wasm32")]
13558            "font_text_3d"
13559            | "ข้อความฟอนต์3มิติ"
13560            | "字体3D"
13561            | "フォント3D"
13562            | "글꼴3D"
13563            | "متن_فونت_سه‌بعدی"
13564            | "نص_خط_ثلاثي_الأبعاد"
13565            | "טקסט_גופן_תלת_ממדי"
13566            | "تھری_ڈی_فونٹ_متن"
13567            | "texte_police_3d"
13568            | "schriftart_text_3d"
13569            | "текст_шрифт_3d" => {
13570                return Ok(Value::Unit);
13571            },
13572            // font_width(handle, px, "string") — pixel width of a string in a loaded font.
13573            #[cfg(not(target_arch = "wasm32"))]
13574            "font_width"
13575            | "ความกว้างฟอนต์"
13576            | "字体宽度"
13577            | "フォント幅"
13578            | "글꼴너비"
13579            | "عرض_فونت"
13580            | "عرض_الخط"
13581            | "רוחב_גופן"
13582            | "فونٹ_چوڑائی"
13583            | "largeur_police"
13584            | "schriftart_breite"
13585            | "ширина_шрифта" => {
13586                let id = self.arg_num(&args, 0, 0.0)? as i64;
13587                let px = self.arg_num(&args, 1, 16.0)? as f32;
13588                let s = self.arg_str(&args, 2, "");
13589                if id >= 0 && (id as usize) < self.fonts.len() {
13590                    return Ok(Value::Number(self.fonts[id as usize].measure(&s, px) as f64));
13591                }
13592                return Ok(Value::Number(0.0));
13593            },
13594            #[cfg(target_arch = "wasm32")]
13595            "font_width"
13596            | "ความกว้างฟอนต์"
13597            | "字体宽度"
13598            | "フォント幅"
13599            | "글꼴너비"
13600            | "عرض_فونت"
13601            | "عرض_الخط"
13602            | "רוחב_גופן"
13603            | "فونٹ_چوڑائی"
13604            | "largeur_police"
13605            | "schriftart_breite"
13606            | "ширина_шрифта" => {
13607                return Ok(Value::Number(0.0));
13608            },
13609            // font_glyph_outline(handle, "char", tol_em) — flattened vector outline of
13610            // ONE glyph in normalized em space (x→right, y→up, baseline at 0). Returns a
13611            // list of contours; each contour is a flat list [x0,y0,x1,y1,…]. Curves are
13612            // subdivided so deviation stays under tol_em (default 0.01). Empty on failure.
13613            #[cfg(not(target_arch = "wasm32"))]
13614            "font_glyph_outline"
13615            | "font_outline"
13616            | "เส้นขอบฟอนต์"
13617            | "字体轮廓"
13618            | "フォント輪郭"
13619            | "글꼴윤곽"
13620            | "خط‌دور_نویسه_فونت"
13621            | "حدود_حرف_الخط"
13622            | "קו_מתאר_גליף"
13623            | "فونٹ_گلف_آؤٹ_لائن" => {
13624                let id = self.arg_num(&args, 0, 0.0)? as i64;
13625                let s = self.arg_str(&args, 1, "");
13626                let tol = self.arg_num(&args, 2, 0.01)? as f32;
13627                let ch = s.chars().next().unwrap_or(' ');
13628                if id >= 0 && (id as usize) < self.fonts.len() {
13629                    let go = self.fonts[id as usize].glyph_outline(ch, tol.max(1e-4));
13630                    let mut contours: Vec<Value> = Vec::with_capacity(go.polylines.len());
13631                    for pl in &go.polylines {
13632                        let mut flat: Vec<Value> = Vec::with_capacity(pl.len() * 2);
13633                        for p in pl {
13634                            flat.push(Value::Number(p[0] as f64));
13635                            flat.push(Value::Number(p[1] as f64));
13636                        }
13637                        contours.push(Value::List(Rc::new(flat)));
13638                    }
13639                    return Ok(Value::List(Rc::new(contours)));
13640                }
13641                return Ok(Value::List(Rc::new(vec![])));
13642            },
13643            #[cfg(target_arch = "wasm32")]
13644            "font_glyph_outline"
13645            | "font_outline"
13646            | "เส้นขอบฟอนต์"
13647            | "字体轮廓"
13648            | "フォント輪郭"
13649            | "글꼴윤곽"
13650            | "خط‌دور_نویسه_فونت"
13651            | "حدود_حرف_الخط"
13652            | "קו_מתאר_גליף"
13653            | "فونٹ_گلف_آؤٹ_لائن" => {
13654                return Ok(Value::List(Rc::new(vec![])));
13655            },
13656            // font_advance(handle, "char") — normalized em advance width of ONE glyph
13657            // (baseline metric, ignores side bearings). Multiply by px for pixels.
13658            #[cfg(not(target_arch = "wasm32"))]
13659            "font_advance"
13660            | "ระยะฟอนต์"
13661            | "字体步进"
13662            | "フォント送り"
13663            | "글꼴전진"
13664            | "پیشروی_فونت"
13665            | "تقدم_الخط"
13666            | "קידום_גופן"
13667            | "فونٹ_ایڈوانس" => {
13668                let id = self.arg_num(&args, 0, 0.0)? as i64;
13669                let s = self.arg_str(&args, 1, "");
13670                let ch = s.chars().next().unwrap_or(' ');
13671                if id >= 0 && (id as usize) < self.fonts.len() {
13672                    return Ok(Value::Number(self.fonts[id as usize].advance(ch) as f64));
13673                }
13674                return Ok(Value::Number(0.0));
13675            },
13676            #[cfg(target_arch = "wasm32")]
13677            "font_advance"
13678            | "ระยะฟอนต์"
13679            | "字体步进"
13680            | "フォント送り"
13681            | "글꼴전진"
13682            | "پیشروی_فونت"
13683            | "تقدم_الخط"
13684            | "קידום_גופן"
13685            | "فونٹ_ایڈوانس" => {
13686                return Ok(Value::Number(0.0));
13687            },
13688
13689            // ui_frame(x,y,w,h, bracketLen) — sci-fi corner brackets
13690            "ui_frame"
13691            | "边框"
13692            | "フレーム枠"
13693            | "프레임틀"
13694            | "กรอบ"
13695            | "قاب_رابط"
13696            | "إطار_الواجهة"
13697            | "מסגרת_ממשק"
13698            | "یو_آئی_فریم"
13699            | "cadre_ui"
13700            | "ui_rahmen"
13701            | "ui_рамка" => {
13702                let x = self.arg_num(&args, 0, 0.0)? as f32;
13703                let y = self.arg_num(&args, 1, 0.0)? as f32;
13704                let w0 = self.arg_num(&args, 2, 0.0)? as f32;
13705                let h0 = self.arg_num(&args, 3, 0.0)? as f32;
13706                let l = self.arg_num(&args, 4, 14.0)? as f32;
13707                let segs = ling_ui::holo::corner_brackets(x, y, w0, h0, l);
13708                let mut gfx = self.gfx.borrow_mut();
13709                let (w, h, color) = (gfx.width, gfx.height, gfx.color);
13710                for sg in segs {
13711                    draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
13712                }
13713                return Ok(Value::Unit);
13714            },
13715            // ui_bevel(x,y,w,h, bevel) — beveled holographic panel outline
13716            "ui_bevel"
13717            | "斜角框"
13718            | "ベベル枠"
13719            | "베벨틀"
13720            | "กรอบเฉียง"
13721            | "لبه_شیبدار"
13722            | "حافة_مشطوفة"
13723            | "מסגרת_משופעת"
13724            | "یو_آئی_بیول"
13725            | "biseau_ui"
13726            | "ui_fase"
13727            | "ui_фаска" => {
13728                let x = self.arg_num(&args, 0, 0.0)? as f32;
13729                let y = self.arg_num(&args, 1, 0.0)? as f32;
13730                let w0 = self.arg_num(&args, 2, 0.0)? as f32;
13731                let h0 = self.arg_num(&args, 3, 0.0)? as f32;
13732                let bv = self.arg_num(&args, 4, 10.0)? as f32;
13733                let segs = ling_ui::holo::beveled_rect(x, y, w0, h0, bv);
13734                let mut gfx = self.gfx.borrow_mut();
13735                let (w, h, color) = (gfx.width, gfx.height, gfx.color);
13736                for sg in segs {
13737                    draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
13738                }
13739                return Ok(Value::Unit);
13740            },
13741
13742            // ══════════════════════════════════════════════════════════════════
13743            // VECTOR UI TOOLKIT  (crates/ling-ui/src/widgets.rs)
13744            // All widgets are vector + theme-coloured with an optional trailing
13745            // r,g,b override; interactive ones read the mouse and return state.
13746            // ══════════════════════════════════════════════════════════════════
13747            #[cfg(not(target_arch = "wasm32"))]
13748            "ui_theme"
13749            | "界面主题"
13750            | "UIテーマ"
13751            | "인터페이스테마"
13752            | "ธีมส่วนติดต่อ"
13753            | "پوسته_رابط"
13754            | "سمة_الواجهة"
13755            | "ערכת_נושא"
13756            | "یو_آئی_تھیم"
13757            | "thème_ui"
13758            | "ui_thema"
13759            | "ui_тема" => {
13760                let cur = self.ui_theme;
13761                let primary = self.color_at(&args, 0, cur.primary);
13762                let accent = self.color_at(&args, 3, cur.accent);
13763                let track = self.color_at(&args, 6, cur.track);
13764                let warn = self.color_at(&args, 9, cur.warn);
13765                let text = self.color_at(&args, 12, cur.text);
13766                let bg = self.color_at(&args, 15, cur.bg);
13767                self.ui_theme = UiTheme { primary, accent, track, warn, text, bg };
13768                return Ok(Value::Unit);
13769            },
13770
13771            // ui_theme_colors() -> [pr,pg,pb, ar,ag,ab, tr,tg,tb, wr,wg,wb,
13772            // xr,xg,xb, br,bg,bb] — the live theme every ui_* widget already
13773            // draws from (primary/accent/track/warn/text/bg, each 0-255),
13774            // so script-drawn UI (e.g. a hand-rolled text field) can match it
13775            // instead of guessing its own colours.
13776            "ui_theme_colors" | "인터페이스테마색상" => {
13777                let th = self.ui_theme;
13778                let mut out = Vec::with_capacity(18);
13779                for c in [th.primary, th.accent, th.track, th.warn, th.text, th.bg] {
13780                    out.push(Value::Number(((c >> 16) & 0xFF) as f64));
13781                    out.push(Value::Number(((c >> 8) & 0xFF) as f64));
13782                    out.push(Value::Number((c & 0xFF) as f64));
13783                }
13784                return Ok(Value::List(Rc::new(out)));
13785            },
13786
13787            // ── HUD ──────────────────────────────────────────────────────────
13788            #[cfg(not(target_arch = "wasm32"))]
13789            "ui_radar"
13790            | "雷达"
13791            | "レーダー"
13792            | "레이더"
13793            | "เรดาร์"
13794            | "رادار_رابط"
13795            | "رادار_الواجهة"
13796            | "מכ״ם_ממשק"
13797            | "یو_آئی_ریڈار"
13798            | "radar_ui"
13799            | "ui_радар" => {
13800                let cx = self.arg_num(&args, 0, 0.)? as f32;
13801                let cy = self.arg_num(&args, 1, 0.)? as f32;
13802                let r = self.arg_num(&args, 2, 60.)? as f32;
13803                let sweep = self.arg_num(&args, 3, 0.)? as f32;
13804                let th = self.ui_theme;
13805                let prim = self.color_at(&args, 4, th.primary);
13806                self.draw_ui(&ling_ui::widgets::radar(
13807                    cx, cy, r, sweep, prim, th.accent, th.track,
13808                ));
13809                return Ok(Value::Unit);
13810            },
13811            #[cfg(not(target_arch = "wasm32"))]
13812            "ui_compass"
13813            | "罗盘"
13814            | "コンパス"
13815            | "나침반"
13816            | "เข็มทิศ"
13817            | "قطب‌نمای_رابط"
13818            | "بوصلة_الواجهة"
13819            | "מצפן_ממשק"
13820            | "یو_آئی_قطب_نما"
13821            | "boussole_ui"
13822            | "ui_kompass"
13823            | "ui_компас" => {
13824                let x = self.arg_num(&args, 0, 0.)? as f32;
13825                let y = self.arg_num(&args, 1, 0.)? as f32;
13826                let w0 = self.arg_num(&args, 2, 300.)? as f32;
13827                let h0 = self.arg_num(&args, 3, 24.)? as f32;
13828                let head = self.arg_num(&args, 4, 0.)? as f32;
13829                let th = self.ui_theme;
13830                let prim = self.color_at(&args, 5, th.primary);
13831                self.draw_ui(&ling_ui::widgets::compass(
13832                    x, y, w0, h0, head, prim, th.track,
13833                ));
13834                return Ok(Value::Unit);
13835            },
13836            #[cfg(not(target_arch = "wasm32"))]
13837            "ui_reticle"
13838            | "准星"
13839            | "照準"
13840            | "조준선"
13841            | "เป้าเล็ง"
13842            | "نشانه_رابط"
13843            | "علامة_تصويب"
13844            | "כוונת"
13845            | "نشانہ"
13846            | "réticule_ui"
13847            | "ui_fadenkreuz"
13848            | "ui_прицел" => {
13849                let cx = self.arg_num(&args, 0, 0.)? as f32;
13850                let cy = self.arg_num(&args, 1, 0.)? as f32;
13851                let r = self.arg_num(&args, 2, 30.)? as f32;
13852                let spread = self.arg_num(&args, 3, 0.)? as f32;
13853                let th = self.ui_theme;
13854                let prim = self.color_at(&args, 4, th.primary);
13855                self.draw_ui(&ling_ui::widgets::reticle(cx, cy, r, spread, prim));
13856                return Ok(Value::Unit);
13857            },
13858            #[cfg(not(target_arch = "wasm32"))]
13859            "ui_target"
13860            | "锁定框"
13861            | "ターゲット"
13862            | "표적"
13863            | "กรอบเป้า"
13864            | "قاب_هدف"
13865            | "إطار_الهدف"
13866            | "מסגרת_מטרה"
13867            | "ہدف_فریم"
13868            | "cible_ui"
13869            | "ui_ziel"
13870            | "ui_цель" => {
13871                let x = self.arg_num(&args, 0, 0.)? as f32;
13872                let y = self.arg_num(&args, 1, 0.)? as f32;
13873                let w0 = self.arg_num(&args, 2, 80.)? as f32;
13874                let h0 = self.arg_num(&args, 3, 80.)? as f32;
13875                let lock = self.arg_num(&args, 4, 0.)? as f32;
13876                let th = self.ui_theme;
13877                let prim = self.color_at(&args, 5, th.primary);
13878                self.draw_ui(&ling_ui::widgets::target(
13879                    x, y, w0, h0, lock, prim, th.accent,
13880                ));
13881                return Ok(Value::Unit);
13882            },
13883            #[cfg(not(target_arch = "wasm32"))]
13884            "ui_panel"
13885            | "面板"
13886            | "パネル"
13887            | "패널"
13888            | "แผง"
13889            | "پنل_رابط"
13890            | "لوحة_الواجهة"
13891            | "לוח_ממשק"
13892            | "یو_آئی_پینل"
13893            | "panneau_ui"
13894            | "ui_feld"
13895            | "ui_панель" => {
13896                let x = self.arg_num(&args, 0, 0.)? as f32;
13897                let y = self.arg_num(&args, 1, 0.)? as f32;
13898                let w0 = self.arg_num(&args, 2, 200.)? as f32;
13899                let h0 = self.arg_num(&args, 3, 120.)? as f32;
13900                let bv = self.arg_num(&args, 4, 12.)? as f32;
13901                let th = self.ui_theme;
13902                let prim = self.color_at(&args, 5, th.primary);
13903                self.draw_ui(&ling_ui::widgets::panel(x, y, w0, h0, bv, prim, th.bg));
13904                return Ok(Value::Unit);
13905            },
13906            #[cfg(not(target_arch = "wasm32"))]
13907            "ui_scanlines"
13908            | "扫描线"
13909            | "走査線"
13910            | "스캔라인"
13911            | "เส้นสแกน"
13912            | "خطوط_اسکن"
13913            | "خطوط_المسح"
13914            | "קווי_סריקה"
13915            | "اسکین_لائنز"
13916            | "lignes_balayage_ui"
13917            | "ui_abtastzeilen"
13918            | "ui_линии_развёртки" => {
13919                let x = self.arg_num(&args, 0, 0.)? as f32;
13920                let y = self.arg_num(&args, 1, 0.)? as f32;
13921                let w0 = self.arg_num(&args, 2, 200.)? as f32;
13922                let h0 = self.arg_num(&args, 3, 120.)? as f32;
13923                let dens = self.arg_num(&args, 4, 24.)? as usize;
13924                let th = self.ui_theme;
13925                let line = self.color_at(&args, 5, th.track);
13926                self.draw_ui(&ling_ui::widgets::scanlines(x, y, w0, h0, dens, line));
13927                return Ok(Value::Unit);
13928            },
13929
13930            // ── Meters ───────────────────────────────────────────────────────
13931            #[cfg(not(target_arch = "wasm32"))]
13932            "ui_bar"
13933            | "进度条"
13934            | "バー"
13935            | "막대"
13936            | "แถบ"
13937            | "نوار_رابط"
13938            | "شريط_الواجهة"
13939            | "סרגל_ממשק"
13940            | "یو_آئی_بار"
13941            | "barre_ui"
13942            | "ui_leiste"
13943            | "ui_полоса" => {
13944                let x = self.arg_num(&args, 0, 0.)? as f32;
13945                let y = self.arg_num(&args, 1, 0.)? as f32;
13946                let w0 = self.arg_num(&args, 2, 160.)? as f32;
13947                let h0 = self.arg_num(&args, 3, 16.)? as f32;
13948                let val = self.arg_num(&args, 4, 0.)? as f32;
13949                let max = self.arg_num(&args, 5, 1.)? as f32;
13950                let th = self.ui_theme;
13951                let fill = self.color_at(&args, 6, th.primary);
13952                self.draw_ui(&ling_ui::widgets::bar(
13953                    x,
13954                    y,
13955                    w0,
13956                    h0,
13957                    val / max.max(1e-6),
13958                    fill,
13959                    th.track,
13960                ));
13961                return Ok(Value::Unit);
13962            },
13963            #[cfg(not(target_arch = "wasm32"))]
13964            "ui_segbar"
13965            | "分段条"
13966            | "分割バー"
13967            | "분할막대"
13968            | "แถบแบ่ง"
13969            | "نوار_قطعه‌ای"
13970            | "شريط_مقسم"
13971            | "סרגל_מקוטע"
13972            | "سیگمنٹ_بار"
13973            | "barre_segmentée_ui"
13974            | "ui_segmentleiste"
13975            | "ui_сегментная_полоса" => {
13976                let x = self.arg_num(&args, 0, 0.)? as f32;
13977                let y = self.arg_num(&args, 1, 0.)? as f32;
13978                let w0 = self.arg_num(&args, 2, 160.)? as f32;
13979                let h0 = self.arg_num(&args, 3, 16.)? as f32;
13980                let val = self.arg_num(&args, 4, 0.)? as f32;
13981                let max = self.arg_num(&args, 5, 1.)? as f32;
13982                let segs = self.arg_num(&args, 6, 10.)? as usize;
13983                let th = self.ui_theme;
13984                let fill = self.color_at(&args, 7, th.primary);
13985                self.draw_ui(&ling_ui::widgets::segbar(
13986                    x,
13987                    y,
13988                    w0,
13989                    h0,
13990                    val / max.max(1e-6),
13991                    segs,
13992                    fill,
13993                    th.track,
13994                ));
13995                return Ok(Value::Unit);
13996            },
13997            #[cfg(not(target_arch = "wasm32"))]
13998            "ui_gauge"
13999            | "仪表"
14000            | "ゲージ"
14001            | "게이지"
14002            | "มาตรวัด"
14003            | "گیج_رابط"
14004            | "مقياس_الواجهة"
14005            | "מד_ממשק"
14006            | "یو_آئی_گیج"
14007            | "jauge_ui"
14008            | "ui_anzeige"
14009            | "ui_индикатор" => {
14010                let cx = self.arg_num(&args, 0, 0.)? as f32;
14011                let cy = self.arg_num(&args, 1, 0.)? as f32;
14012                let r = self.arg_num(&args, 2, 50.)? as f32;
14013                let val = self.arg_num(&args, 3, 0.)? as f32;
14014                let max = self.arg_num(&args, 4, 1.)? as f32;
14015                let th = self.ui_theme;
14016                let needle = self.color_at(&args, 5, th.warn);
14017                self.draw_ui(&ling_ui::widgets::gauge(
14018                    cx,
14019                    cy,
14020                    r,
14021                    val / max.max(1e-6),
14022                    needle,
14023                    th.accent,
14024                    th.track,
14025                ));
14026                return Ok(Value::Unit);
14027            },
14028            #[cfg(not(target_arch = "wasm32"))]
14029            "ui_ring"
14030            | "环表"
14031            | "リングメーター"
14032            | "링미터"
14033            | "วงแหวนวัด"
14034            | "حلقه_گیج"
14035            | "حلقة_قياس"
14036            | "טבעת_מד"
14037            | "رنگ_گیج"
14038            | "anneau_ui"
14039            | "ui_кольцо" => {
14040                let cx = self.arg_num(&args, 0, 0.)? as f32;
14041                let cy = self.arg_num(&args, 1, 0.)? as f32;
14042                let r = self.arg_num(&args, 2, 40.)? as f32;
14043                let val = self.arg_num(&args, 3, 0.)? as f32;
14044                let max = self.arg_num(&args, 4, 1.)? as f32;
14045                let th = self.ui_theme;
14046                let fill = self.color_at(&args, 5, th.primary);
14047                self.draw_ui(&ling_ui::widgets::ring(
14048                    cx,
14049                    cy,
14050                    r,
14051                    val / max.max(1e-6),
14052                    fill,
14053                    th.track,
14054                ));
14055                return Ok(Value::Unit);
14056            },
14057            #[cfg(not(target_arch = "wasm32"))]
14058            "ui_vu"
14059            | "音量条"
14060            | "VUメーター"
14061            | "음량막대"
14062            | "มาตรเสียง"
14063            | "گیج_صدا"
14064            | "مقياس_مستوى_الصوت"
14065            | "מד_עוצמה"
14066            | "وی_یو_میٹر"
14067            | "vumètre_ui"
14068            | "ui_vumeter"
14069            | "ui_вю_метр" => {
14070                let x = self.arg_num(&args, 0, 0.)? as f32;
14071                let y = self.arg_num(&args, 1, 0.)? as f32;
14072                let w0 = self.arg_num(&args, 2, 160.)? as f32;
14073                let h0 = self.arg_num(&args, 3, 60.)? as f32;
14074                let levels = self.arg_list_f32(&args, 4);
14075                let th = self.ui_theme;
14076                let fill = self.color_at(&args, 5, th.primary);
14077                self.draw_ui(&ling_ui::widgets::vu(x, y, w0, h0, &levels, fill, th.warn));
14078                return Ok(Value::Unit);
14079            },
14080            #[cfg(not(target_arch = "wasm32"))]
14081            "ui_spark"
14082            | "迷你图"
14083            | "スパークライン"
14084            | "스파크라인"
14085            | "กราฟจิ๋ว"
14086            | "نمودار_ریز"
14087            | "رسم_مصغر"
14088            | "גרף_זעיר"
14089            | "اسپارک_لائن"
14090            | "mini_graphe_ui"
14091            | "ui_sparkline"
14092            | "ui_мини_график" => {
14093                let x = self.arg_num(&args, 0, 0.)? as f32;
14094                let y = self.arg_num(&args, 1, 0.)? as f32;
14095                let w0 = self.arg_num(&args, 2, 160.)? as f32;
14096                let h0 = self.arg_num(&args, 3, 40.)? as f32;
14097                let vals = self.arg_list_f32(&args, 4);
14098                let th = self.ui_theme;
14099                let line = self.color_at(&args, 5, th.accent);
14100                self.draw_ui(&ling_ui::widgets::spark(x, y, w0, h0, &vals, line));
14101                return Ok(Value::Unit);
14102            },
14103            #[cfg(not(target_arch = "wasm32"))]
14104            "ui_battery"
14105            | "电池"
14106            | "バッテリー"
14107            | "배터리"
14108            | "แบตเตอรี่"
14109            | "نشانگر_باتری"
14110            | "مؤشر_البطارية"
14111            | "מחוון_סוללה"
14112            | "بیٹری_انڈیکیٹر"
14113            | "batterie_ui"
14114            | "ui_batterie"
14115            | "ui_батарея" => {
14116                let x = self.arg_num(&args, 0, 0.)? as f32;
14117                let y = self.arg_num(&args, 1, 0.)? as f32;
14118                let w0 = self.arg_num(&args, 2, 50.)? as f32;
14119                let h0 = self.arg_num(&args, 3, 22.)? as f32;
14120                let val = self.arg_num(&args, 4, 1.)? as f32;
14121                let max = self.arg_num(&args, 5, 1.)? as f32;
14122                let th = self.ui_theme;
14123                let fill = self.color_at(&args, 6, th.accent);
14124                self.draw_ui(&ling_ui::widgets::battery(
14125                    x,
14126                    y,
14127                    w0,
14128                    h0,
14129                    val / max.max(1e-6),
14130                    fill,
14131                    th.track,
14132                    th.warn,
14133                ));
14134                return Ok(Value::Unit);
14135            },
14136
14137            // ── Interface controls (interactive → return state) ──────────────
14138            #[cfg(not(target_arch = "wasm32"))]
14139            "ui_button"
14140            | "按钮"
14141            | "ボタン"
14142            | "버튼"
14143            | "ปุ่ม"
14144            | "دکمه_رابط"
14145            | "زر_الواجهة"
14146            | "כפתור_ממשק"
14147            | "یو_آئی_بٹن"
14148            | "bouton_ui"
14149            | "ui_knopf"
14150            | "ui_кнопка" => {
14151                let x = self.arg_num(&args, 0, 0.)? as f32;
14152                let y = self.arg_num(&args, 1, 0.)? as f32;
14153                let w0 = self.arg_num(&args, 2, 120.)? as f32;
14154                let h0 = self.arg_num(&args, 3, 40.)? as f32;
14155                let (mx, my, down) = self.mouse_now();
14156                let hover = ling_ui::holo::hit_rect(mx, my, x, y, w0, h0);
14157                let clicked = hover && down && !self.mouse_was_down;
14158                let th = self.ui_theme;
14159                let prim = self.color_at(&args, 4, th.primary);
14160                self.draw_ui(&ling_ui::widgets::button(
14161                    x,
14162                    y,
14163                    w0,
14164                    h0,
14165                    hover,
14166                    down && hover,
14167                    prim,
14168                    th.bg,
14169                ));
14170                return Ok(Value::Number(if clicked { 1.0 } else { 0.0 }));
14171            },
14172            #[cfg(not(target_arch = "wasm32"))]
14173            "ui_toggle"
14174            | "开关"
14175            | "トグル"
14176            | "토글"
14177            | "สวิตช์"
14178            | "کلید_ضامن"
14179            | "مفتاح_تبديل"
14180            | "מתג"
14181            | "ٹوگل"
14182            | "bascule_ui"
14183            | "ui_schalter"
14184            | "ui_переключатель" => {
14185                let x = self.arg_num(&args, 0, 0.)? as f32;
14186                let y = self.arg_num(&args, 1, 0.)? as f32;
14187                let w0 = self.arg_num(&args, 2, 52.)? as f32;
14188                let h0 = self.arg_num(&args, 3, 24.)? as f32;
14189                let mut state = self.arg_num(&args, 4, 0.)? > 0.5;
14190                let (mx, my, down) = self.mouse_now();
14191                let hover = ling_ui::holo::hit_rect(mx, my, x, y, w0, h0);
14192                if hover && down && !self.mouse_was_down {
14193                    state = !state;
14194                }
14195                let th = self.ui_theme;
14196                let on = self.color_at(&args, 5, th.accent);
14197                self.draw_ui(&ling_ui::widgets::toggle(x, y, w0, h0, state, on, th.track));
14198                return Ok(Value::Number(if state { 1.0 } else { 0.0 }));
14199            },
14200            #[cfg(not(target_arch = "wasm32"))]
14201            "ui_slider"
14202            | "滑块"
14203            | "スライダー"
14204            | "슬라이더"
14205            | "แถบเลื่อน"
14206            | "لغزنده"
14207            | "شريط_انزلاق"
14208            | "מחוון_החלקה"
14209            | "سلائیڈر"
14210            | "curseur_ui"
14211            | "ui_schieberegler"
14212            | "ui_ползунок" => {
14213                let x = self.arg_num(&args, 0, 0.)? as f32;
14214                let y = self.arg_num(&args, 1, 0.)? as f32;
14215                let w0 = self.arg_num(&args, 2, 160.)? as f32;
14216                let mut val = self.arg_num(&args, 3, 0.)? as f32;
14217                let mn = self.arg_num(&args, 4, 0.)? as f32;
14218                let mx_ = self.arg_num(&args, 5, 1.)? as f32;
14219                let (mx, my, down) = self.mouse_now();
14220                let hover = ling_ui::holo::hit_rect(mx, my, x - 8.0, y - 10.0, w0 + 16.0, 20.0);
14221                if hover && down {
14222                    let frac = ((mx - x) / w0).clamp(0.0, 1.0);
14223                    val = mn + (mx_ - mn) * frac;
14224                }
14225                let frac = ((val - mn) / (mx_ - mn).abs().max(1e-6)).clamp(0.0, 1.0);
14226                let th = self.ui_theme;
14227                let fill = self.color_at(&args, 6, th.primary);
14228                self.draw_ui(&ling_ui::widgets::slider(
14229                    x, y, w0, frac, hover, fill, th.track,
14230                ));
14231                return Ok(Value::Number(val as f64));
14232            },
14233            #[cfg(not(target_arch = "wasm32"))]
14234            "ui_checkbox"
14235            | "复选框"
14236            | "チェックボックス"
14237            | "체크박스"
14238            | "ช่องเลือก"
14239            | "جعبه_علامت"
14240            | "مربع_اختيار"
14241            | "תיבת_סימון"
14242            | "چیک_باکس"
14243            | "case_cocher_ui"
14244            | "ui_kontrollkästchen"
14245            | "ui_флажок" => {
14246                let x = self.arg_num(&args, 0, 0.)? as f32;
14247                let y = self.arg_num(&args, 1, 0.)? as f32;
14248                let s = self.arg_num(&args, 2, 20.)? as f32;
14249                let mut checked = self.arg_num(&args, 3, 0.)? > 0.5;
14250                let (mx, my, down) = self.mouse_now();
14251                let hover = ling_ui::holo::hit_rect(mx, my, x, y, s, s);
14252                if hover && down && !self.mouse_was_down {
14253                    checked = !checked;
14254                }
14255                let th = self.ui_theme;
14256                let prim = self.color_at(&args, 4, th.primary);
14257                self.draw_ui(&ling_ui::widgets::checkbox(
14258                    x, y, s, checked, hover, prim, th.track,
14259                ));
14260                return Ok(Value::Number(if checked { 1.0 } else { 0.0 }));
14261            },
14262            #[cfg(not(target_arch = "wasm32"))]
14263            "ui_tabs"
14264            | "标签页"
14265            | "タブ"
14266            | "탭"
14267            | "แท็บ"
14268            | "برگه‌ها"
14269            | "ألسنة_الواجهة"
14270            | "לשוניות"
14271            | "ٹیبز"
14272            | "onglets_ui"
14273            | "ui_reiter"
14274            | "ui_вкладки" => {
14275                let x = self.arg_num(&args, 0, 0.)? as f32;
14276                let y = self.arg_num(&args, 1, 0.)? as f32;
14277                let w0 = self.arg_num(&args, 2, 240.)? as f32;
14278                let h0 = self.arg_num(&args, 3, 28.)? as f32;
14279                let count = self.arg_num(&args, 4, 3.)? as usize;
14280                let mut active = self.arg_num(&args, 5, 0.)? as i32;
14281                let (mx, my, down) = self.mouse_now();
14282                let mut hover = -1;
14283                if my >= y && my <= y + h0 && mx >= x && mx <= x + w0 && count > 0 {
14284                    hover = (((mx - x) / (w0 / count as f32)) as i32)
14285                        .max(0)
14286                        .min(count as i32 - 1);
14287                    if down && !self.mouse_was_down {
14288                        active = hover;
14289                    }
14290                }
14291                let th = self.ui_theme;
14292                let prim = self.color_at(&args, 6, th.primary);
14293                self.draw_ui(&ling_ui::widgets::tabs(
14294                    x,
14295                    y,
14296                    w0,
14297                    h0,
14298                    count,
14299                    active as usize,
14300                    hover,
14301                    prim,
14302                    th.track,
14303                ));
14304                return Ok(Value::Number(active as f64));
14305            },
14306            #[cfg(not(target_arch = "wasm32"))]
14307            "ui_progress"
14308            | "进度"
14309            | "プログレス"
14310            | "진행바"
14311            | "ความคืบหน้า"
14312            | "نوار_پیشرفت"
14313            | "شريط_التقدم"
14314            | "פס_התקדמות"
14315            | "پیش_رفت_بار"
14316            | "progression_ui"
14317            | "ui_fortschritt"
14318            | "ui_прогресс" => {
14319                let x = self.arg_num(&args, 0, 0.)? as f32;
14320                let y = self.arg_num(&args, 1, 0.)? as f32;
14321                let w0 = self.arg_num(&args, 2, 200.)? as f32;
14322                let h0 = self.arg_num(&args, 3, 12.)? as f32;
14323                let frac = self.arg_num(&args, 4, 0.)? as f32;
14324                let th = self.ui_theme;
14325                let fill = self.color_at(&args, 5, th.accent);
14326                self.draw_ui(&ling_ui::widgets::progress(
14327                    x, y, w0, h0, frac, fill, th.track,
14328                ));
14329                return Ok(Value::Unit);
14330            },
14331            #[cfg(not(target_arch = "wasm32"))]
14332            "ui_tooltip"
14333            | "提示框"
14334            | "ツールチップ"
14335            | "툴팁"
14336            | "คำแนะนำ"
14337            | "راهنمای_شناور"
14338            | "تلميح_الواجهة"
14339            | "חלונית_עזרה"
14340            | "ٹول_ٹپ"
14341            | "infobulle_ui"
14342            | "ui_подсказка" => {
14343                let x = self.arg_num(&args, 0, 0.)? as f32;
14344                let y = self.arg_num(&args, 1, 0.)? as f32;
14345                let w0 = self.arg_num(&args, 2, 120.)? as f32;
14346                let h0 = self.arg_num(&args, 3, 28.)? as f32;
14347                let th = self.ui_theme;
14348                let prim = self.color_at(&args, 4, th.primary);
14349                self.draw_ui(&ling_ui::widgets::tooltip(x, y, w0, h0, prim, th.bg));
14350                return Ok(Value::Unit);
14351            },
14352            #[cfg(not(target_arch = "wasm32"))]
14353            "ui_stepper"
14354            | "步进器"
14355            | "ステッパー"
14356            | "스테퍼"
14357            | "ตัวปรับค่า"
14358            | "پله‌گر"
14359            | "زر_خطوات"
14360            | "בורר_מדורג"
14361            | "اسٹیپر"
14362            | "pas_à_pas_ui"
14363            | "ui_schrittsteuerung"
14364            | "ui_степпер" => {
14365                let x = self.arg_num(&args, 0, 0.)? as f32;
14366                let y = self.arg_num(&args, 1, 0.)? as f32;
14367                let w0 = self.arg_num(&args, 2, 120.)? as f32;
14368                let h0 = self.arg_num(&args, 3, 28.)? as f32;
14369                let mut val = self.arg_num(&args, 4, 0.)? as f32;
14370                let step = self.arg_num(&args, 5, 1.)? as f32;
14371                let (mx, my, down) = self.mouse_now();
14372                let hm = ling_ui::holo::hit_rect(mx, my, x, y, h0, h0);
14373                let hp = ling_ui::holo::hit_rect(mx, my, x + w0 - h0, y, h0, h0);
14374                if down && !self.mouse_was_down {
14375                    if hm {
14376                        val -= step;
14377                    }
14378                    if hp {
14379                        val += step;
14380                    }
14381                }
14382                let th = self.ui_theme;
14383                let prim = self.color_at(&args, 6, th.primary);
14384                self.draw_ui(&ling_ui::widgets::stepper(
14385                    x, y, w0, h0, hm, hp, prim, th.track,
14386                ));
14387                return Ok(Value::Number(val as f64));
14388            },
14389
14390            // ── Game UI ──────────────────────────────────────────────────────
14391            #[cfg(not(target_arch = "wasm32"))]
14392            "ui_healthbar"
14393            | "血条"
14394            | "体力バー"
14395            | "체력바"
14396            | "แถบพลังชีวิต"
14397            | "نوار_سلامتی"
14398            | "شريط_الصحة"
14399            | "פס_בריאות"
14400            | "ہیلتھ_بار"
14401            | "barre_vie_ui"
14402            | "ui_lebensbalken"
14403            | "ui_полоса_здоровья" => {
14404                let x = self.arg_num(&args, 0, 0.)? as f32;
14405                let y = self.arg_num(&args, 1, 0.)? as f32;
14406                let w0 = self.arg_num(&args, 2, 180.)? as f32;
14407                let h0 = self.arg_num(&args, 3, 16.)? as f32;
14408                let val = self.arg_num(&args, 4, 1.)? as f32;
14409                let max = self.arg_num(&args, 5, 1.)? as f32;
14410                let pulse = self.arg_num(&args, 6, 0.)? as f32;
14411                let th = self.ui_theme;
14412                let full = self.color_at(&args, 7, th.accent);
14413                self.draw_ui(&ling_ui::widgets::healthbar(
14414                    x,
14415                    y,
14416                    w0,
14417                    h0,
14418                    val / max.max(1e-6),
14419                    pulse,
14420                    full,
14421                    th.warn,
14422                    th.track,
14423                ));
14424                return Ok(Value::Unit);
14425            },
14426            #[cfg(not(target_arch = "wasm32"))]
14427            "ui_cooldown"
14428            | "冷却"
14429            | "クールダウン"
14430            | "쿨다운"
14431            | "คูลดาวน์"
14432            | "زمان_خنک‌سازی"
14433            | "مؤقت_التهدئة"
14434            | "זמן_קירור"
14435            | "کول_ڈاؤن"
14436            | "recharge_ui"
14437            | "ui_abklingzeit"
14438            | "ui_перезарядка" => {
14439                let cx = self.arg_num(&args, 0, 0.)? as f32;
14440                let cy = self.arg_num(&args, 1, 0.)? as f32;
14441                let r = self.arg_num(&args, 2, 28.)? as f32;
14442                let frac = self.arg_num(&args, 3, 0.)? as f32;
14443                let th = self.ui_theme;
14444                let fill = self.color_at(&args, 4, th.primary);
14445                self.draw_ui(&ling_ui::widgets::cooldown(cx, cy, r, frac, fill, th.track));
14446                return Ok(Value::Unit);
14447            },
14448            #[cfg(not(target_arch = "wasm32"))]
14449            "ui_counter"
14450            | "计数器"
14451            | "カウンター"
14452            | "카운터"
14453            | "ตัวนับ"
14454            | "شمارشگر"
14455            | "عداد_الواجهة"
14456            | "מונה_ממשק"
14457            | "کاؤنٹر"
14458            | "compteur_ui"
14459            | "ui_zähler"
14460            | "ui_счётчик" => {
14461                let x = self.arg_num(&args, 0, 0.)? as f32;
14462                let y = self.arg_num(&args, 1, 0.)? as f32;
14463                let dw = self.arg_num(&args, 2, 14.)? as f32;
14464                let dh = self.arg_num(&args, 3, 24.)? as f32;
14465                let val = self.arg_num(&args, 4, 0.)? as i64;
14466                let digits = self.arg_num(&args, 5, 4.)? as usize;
14467                let th = self.ui_theme;
14468                let on = self.color_at(&args, 6, th.primary);
14469                let off = ling_ui::widgets::shade(th.track, 0.5);
14470                self.draw_ui(&ling_ui::widgets::counter(
14471                    x, y, dw, dh, val, digits, on, off,
14472                ));
14473                return Ok(Value::Unit);
14474            },
14475            #[cfg(not(target_arch = "wasm32"))]
14476            "ui_minimap"
14477            | "小地图"
14478            | "ミニマップ"
14479            | "미니맵"
14480            | "แผนที่ย่อ"
14481            | "نقشه_کوچک"
14482            | "خريطة_مصغرة"
14483            | "מפה_מוקטנת"
14484            | "منی_میپ"
14485            | "minicarte_ui"
14486            | "ui_minikarte"
14487            | "ui_миникарта" => {
14488                let x = self.arg_num(&args, 0, 0.)? as f32;
14489                let y = self.arg_num(&args, 1, 0.)? as f32;
14490                let w0 = self.arg_num(&args, 2, 140.)? as f32;
14491                let h0 = self.arg_num(&args, 3, 140.)? as f32;
14492                let th = self.ui_theme;
14493                let prim = self.color_at(&args, 4, th.primary);
14494                self.draw_ui(&ling_ui::widgets::minimap(x, y, w0, h0, prim, th.bg));
14495                return Ok(Value::Unit);
14496            },
14497            #[cfg(not(target_arch = "wasm32"))]
14498            "ui_dpad"
14499            | "方向键"
14500            | "方向パッド"
14501            | "방향패드"
14502            | "ปุ่มทิศทาง"
14503            | "دسته_جهت‌دار"
14504            | "لوحة_الاتجاهات"
14505            | "לוח_כיוונים"
14506            | "ڈی_پیڈ"
14507            | "croix_direction_ui"
14508            | "ui_steuerkreuz"
14509            | "ui_крестовина" => {
14510                let cx = self.arg_num(&args, 0, 0.)? as f32;
14511                let cy = self.arg_num(&args, 1, 0.)? as f32;
14512                let r = self.arg_num(&args, 2, 50.)? as f32;
14513                let (mx, my, down) = self.mouse_now();
14514                let mut dir = 0;
14515                if down {
14516                    let (dx, dy) = (mx - cx, my - cy);
14517                    if dx * dx + dy * dy <= r * r {
14518                        if dx.abs() > dy.abs() {
14519                            dir = if dx > 0.0 { 2 } else { 4 };
14520                        } else {
14521                            dir = if dy > 0.0 { 3 } else { 1 };
14522                        }
14523                    }
14524                }
14525                let th = self.ui_theme;
14526                let prim = self.color_at(&args, 3, th.primary);
14527                self.draw_ui(&ling_ui::widgets::dpad(cx, cy, r, dir, prim, th.track));
14528                return Ok(Value::Number(dir as f64));
14529            },
14530            #[cfg(not(target_arch = "wasm32"))]
14531            "ui_slotgrid"
14532            | "物品格"
14533            | "スロットグリッド"
14534            | "슬롯격자"
14535            | "ช่องไอเทม"
14536            | "شبکه_شیار"
14537            | "شبكة_الفتحات"
14538            | "רשת_חריצים"
14539            | "سلاٹ_گرڈ"
14540            | "grille_emplacements_ui"
14541            | "ui_slotraster"
14542            | "ui_сетка_слотов" => {
14543                let x = self.arg_num(&args, 0, 0.)? as f32;
14544                let y = self.arg_num(&args, 1, 0.)? as f32;
14545                let cols = self.arg_num(&args, 2, 4.)? as usize;
14546                let rows = self.arg_num(&args, 3, 1.)? as usize;
14547                let cell = self.arg_num(&args, 4, 36.)? as f32;
14548                let sel = self.arg_num(&args, 5, -1.)? as i32;
14549                let th = self.ui_theme;
14550                let prim = self.color_at(&args, 6, th.primary);
14551                self.draw_ui(&ling_ui::widgets::slotgrid(
14552                    x, y, cols, rows, cell, sel, prim, th.track,
14553                ));
14554                return Ok(Value::Unit);
14555            },
14556            #[cfg(not(target_arch = "wasm32"))]
14557            "ui_vignette"
14558            | "暗角"
14559            | "ビネット"
14560            | "비네트"
14561            | "ขอบมืด"
14562            | "سایه‌گرد_کادر"
14563            | "تظليل_الحواف"
14564            | "הצללת_מסגרת"
14565            | "ویگنیٹ"
14566            | "vignette_ui"
14567            | "ui_виньетка" => {
14568                let intensity = self.arg_num(&args, 0, 0.5)? as f32;
14569                let (w, h) = {
14570                    let g = self.gfx.borrow();
14571                    (g.width as f32, g.height as f32)
14572                };
14573                let th = self.ui_theme;
14574                let col = self.color_at(&args, 1, th.warn);
14575                self.draw_ui(&ling_ui::widgets::vignette(w, h, intensity, col));
14576                return Ok(Value::Unit);
14577            },
14578
14579            // ── Faux-3D in 2D space ──────────────────────────────────────────
14580            #[cfg(not(target_arch = "wasm32"))]
14581            "ui_gauge3d"
14582            | "立体仪表"
14583            | "立体ゲージ"
14584            | "입체게이지"
14585            | "มาตรวัด3มิติ"
14586            | "گیج_سه‌بعدی"
14587            | "مقياس_ثلاثي_الأبعاد"
14588            | "מד_תלת_ממדי"
14589            | "تھری_ڈی_گیج"
14590            | "jauge_3d_ui"
14591            | "ui_anzeige_3d"
14592            | "ui_индикатор_3d" => {
14593                let cx = self.arg_num(&args, 0, 0.)? as f32;
14594                let cy = self.arg_num(&args, 1, 0.)? as f32;
14595                let r = self.arg_num(&args, 2, 50.)? as f32;
14596                let val = self.arg_num(&args, 3, 0.)? as f32;
14597                let max = self.arg_num(&args, 4, 1.)? as f32;
14598                let spin = self.arg_num(&args, 5, 0.)? as f32;
14599                let th = self.ui_theme;
14600                let fill = self.color_at(&args, 6, th.primary);
14601                self.draw_ui(&ling_ui::widgets::gauge3d(
14602                    cx,
14603                    cy,
14604                    r,
14605                    val / max.max(1e-6),
14606                    spin,
14607                    fill,
14608                    th.track,
14609                ));
14610                return Ok(Value::Unit);
14611            },
14612            #[cfg(not(target_arch = "wasm32"))]
14613            "ui_panel3d"
14614            | "立体面板"
14615            | "立体パネル"
14616            | "입체패널"
14617            | "แผง3มิติ"
14618            | "پنل_سه‌بعدی"
14619            | "لوحة_ثلاثية_الأبعاد"
14620            | "לוח_תלת_ממדי"
14621            | "تھری_ڈی_پینل"
14622            | "panneau_3d_ui"
14623            | "ui_feld_3d"
14624            | "ui_панель_3d" => {
14625                let x = self.arg_num(&args, 0, 0.)? as f32;
14626                let y = self.arg_num(&args, 1, 0.)? as f32;
14627                let w0 = self.arg_num(&args, 2, 200.)? as f32;
14628                let h0 = self.arg_num(&args, 3, 120.)? as f32;
14629                let depth = self.arg_num(&args, 4, 14.)? as f32;
14630                let th = self.ui_theme;
14631                let prim = self.color_at(&args, 5, th.primary);
14632                self.draw_ui(&ling_ui::widgets::panel3d(x, y, w0, h0, depth, prim, th.bg));
14633                return Ok(Value::Unit);
14634            },
14635            #[cfg(not(target_arch = "wasm32"))]
14636            "ui_radar3d"
14637            | "立体雷达"
14638            | "立体レーダー"
14639            | "입체레이더"
14640            | "เรดาร์3มิติ"
14641            | "رادار_سه‌بعدی"
14642            | "رادار_ثلاثي_الأبعاد"
14643            | "מכ״ם_תלת_ממדי"
14644            | "تھری_ڈی_ریڈار"
14645            | "radar_3d_ui"
14646            | "ui_radar_3d"
14647            | "ui_радар_3d" => {
14648                let cx = self.arg_num(&args, 0, 0.)? as f32;
14649                let cy = self.arg_num(&args, 1, 0.)? as f32;
14650                let r = self.arg_num(&args, 2, 60.)? as f32;
14651                let tilt = self.arg_num(&args, 3, 0.9)? as f32;
14652                let sweep = self.arg_num(&args, 4, 0.)? as f32;
14653                let th = self.ui_theme;
14654                let prim = self.color_at(&args, 5, th.primary);
14655                self.draw_ui(&ling_ui::widgets::radar3d(
14656                    cx, cy, r, tilt, sweep, prim, th.track,
14657                ));
14658                return Ok(Value::Unit);
14659            },
14660
14661            // ── Interface sounds ─────────────────────────────────────────────
14662            #[cfg(not(target_arch = "wasm32"))]
14663            "audio_blip"
14664            | "提示音"
14665            | "ビープ音"
14666            | "효과음"
14667            | "เสียงบี๊บ"
14668            | "بوق_کوتاه"
14669            | "نغمة_قصيرة"
14670            | "ביפ"
14671            | "بلپ_آواز"
14672            | "bip_audio"
14673            | "звук_бип" => {
14674                let freq = self.arg_num(&args, 0, 660.)? as f32;
14675                let dur = self.arg_num(&args, 1, 0.08)? as f32;
14676                let wave = Wave::from_name(&self.arg_str(&args, 2, "sine"));
14677                let amp = self.arg_num(&args, 3, 0.25)? as f32;
14678                if let Some(audio) = &self.audio {
14679                    audio.blip(freq, amp, dur, wave);
14680                }
14681                return Ok(Value::Unit);
14682            },
14683            #[cfg(not(target_arch = "wasm32"))]
14684            "ui_sound"
14685            | "界面音"
14686            | "UI音"
14687            | "인터페이스음"
14688            | "เสียงปุ่ม"
14689            | "صدای_رابط"
14690            | "صوت_الواجهة"
14691            | "צליל_ממשק"
14692            | "یو_آئی_آواز"
14693            | "son_ui"
14694            | "ui_klang"
14695            | "ui_звук" => {
14696                let name = self.arg_str(&args, 0, "click");
14697                if let Some(audio) = &self.audio {
14698                    match name.as_str() {
14699                        "hover" => audio.blip(880.0, 0.10, 0.04, Wave::Sine),
14700                        "confirm" => {
14701                            audio.blip(660.0, 0.22, 0.07, Wave::Square);
14702                            audio.blip(990.0, 0.18, 0.10, Wave::Square);
14703                        },
14704                        "error" => {
14705                            audio.blip(180.0, 0.30, 0.16, Wave::Saw);
14706                            audio.blip(140.0, 0.30, 0.18, Wave::Saw);
14707                        },
14708                        "toggle" => audio.blip(520.0, 0.22, 0.05, Wave::Triangle),
14709                        "tick" => audio.blip(1500.0, 0.12, 0.02, Wave::Square),
14710                        _ => audio.blip(720.0, 0.26, 0.05, Wave::Square), // "click"
14711                    }
14712                }
14713                return Ok(Value::Unit);
14714            },
14715
14716            // ══════════════════════════════════════════════════════════════════
14717            // MUSIC TOOLKIT  (crates/ling-music) — decode · analysis · GM synth ·
14718            // rhythm · karaoke. Analysis/decoding need no audio device; playback
14719            // and synthesis lazily start a dedicated music engine.
14720            // ══════════════════════════════════════════════════════════════════
14721
14722            // music_load(path) -> track handle (decodes WAV/FLAC/OGG/MP3/AAC)
14723            #[cfg(not(target_arch = "wasm32"))]
14724            "music_load"
14725            | "载入音乐"
14726            | "音楽読込"
14727            | "음악로드"
14728            | "โหลดเพลง"
14729            | "بارگذاری_موسیقی"
14730            | "تحميل_الموسيقى"
14731            | "טעינת_מוזיקה"
14732            | "موسیقی_لوڈ"
14733            | "charger_musique"
14734            | "musik_laden"
14735            | "загрузить_музыку" => {
14736                let path = self.arg_str(&args, 0, "");
14737                let resolved = if std::path::Path::new(&path).exists() {
14738                    path.clone()
14739                } else if let Some(d) = &self.source_dir {
14740                    d.join(&path).to_string_lossy().into_owned()
14741                } else {
14742                    path.clone()
14743                };
14744                match ling_music::load(&resolved) {
14745                    Ok(t) => {
14746                        let id = self.tracks.len();
14747                        self.tracks.push(t);
14748                        return Ok(Value::Number(id as f64));
14749                    },
14750                    Err(e) => {
14751                        eprintln!("music_load failed ({path}): {e}");
14752                        return Ok(Value::Number(-1.0));
14753                    },
14754                }
14755            },
14756            #[cfg(not(target_arch = "wasm32"))]
14757            "music_duration"
14758            | "音乐时长"
14759            | "音楽長さ"
14760            | "음악길이"
14761            | "ความยาวเพลง"
14762            | "مدت_موسیقی"
14763            | "مدة_الموسيقى"
14764            | "משך_מוזיקה"
14765            | "موسیقی_دورانیہ"
14766            | "durée_musique"
14767            | "musik_dauer"
14768            | "длительность_музыки" => {
14769                let id = self.arg_num(&args, 0, 0.0)? as i64;
14770                let d = self
14771                    .tracks
14772                    .get(id as usize)
14773                    .map(|t| t.duration)
14774                    .unwrap_or(0.0);
14775                return Ok(Value::Number(d as f64));
14776            },
14777            #[cfg(not(target_arch = "wasm32"))]
14778            "music_bpm"
14779            | "节拍速度"
14780            | "テンポ"
14781            | "템포"
14782            | "จังหวะต่อนาที"
14783            | "ضربان_در_دقیقه"
14784            | "نبضات_بالدقيقة"
14785            | "פעימות_לדקה"
14786            | "بی_پی_ایم"
14787            | "bpm_musique"
14788            | "musik_bpm"
14789            | "музыка_bpm" => {
14790                let id = self.arg_num(&args, 0, 0.0)? as i64;
14791                let b = self
14792                    .tracks
14793                    .get(id as usize)
14794                    .map(|t| ling_music::analysis::bpm(&t.mono, t.rate))
14795                    .unwrap_or(0.0);
14796                return Ok(Value::Number(b as f64));
14797            },
14798            #[cfg(not(target_arch = "wasm32"))]
14799            "music_key"
14800            | "调性"
14801            | "調性"
14802            | "조성"
14803            | "คีย์เพลง"
14804            | "گام_موسیقی"
14805            | "مقام_الموسيقى"
14806            | "סולם_מוזיקלי"
14807            | "موسیقی_کلید"
14808            | "tonalité_musique"
14809            | "musik_tonart"
14810            | "тональность_музыки" => {
14811                let id = self.arg_num(&args, 0, 0.0)? as i64;
14812                let k = self
14813                    .tracks
14814                    .get(id as usize)
14815                    .map(|t| ling_music::analysis::key_name(&t.mono, t.rate))
14816                    .unwrap_or_default();
14817                return Ok(Value::Str(k));
14818            },
14819            #[cfg(not(target_arch = "wasm32"))]
14820            "music_onsets"
14821            | "音符起点"
14822            | "オンセット"
14823            | "온셋"
14824            | "จุดเริ่มเสียง"
14825            | "آغازهای_نت"
14826            | "بدايات_النغمات"
14827            | "התחלות_תווים"
14828            | "نوٹ_شروعات"
14829            | "attaques_musique"
14830            | "musik_einsätze"
14831            | "атаки_музыки" => {
14832                let id = self.arg_num(&args, 0, 0.0)? as i64;
14833                let v = self
14834                    .tracks
14835                    .get(id as usize)
14836                    .map(|t| ling_music::analysis::onsets(&t.mono, t.rate))
14837                    .unwrap_or_default();
14838                return Ok(Value::List(Rc::new(
14839                    v.into_iter().map(|x| Value::Number(x as f64)).collect(),
14840                )));
14841            },
14842            #[cfg(not(target_arch = "wasm32"))]
14843            "music_beat_grid"
14844            | "节拍网格"
14845            | "ビートグリッド"
14846            | "비트그리드"
14847            | "กริดจังหวะ"
14848            | "شبکه_ضرب"
14849            | "شبكة_الإيقاع"
14850            | "רשת_פעימות"
14851            | "بیٹ_گرڈ"
14852            | "grille_temps_musique"
14853            | "musik_taktraster"
14854            | "сетка_ритма_музыки" => {
14855                let id = self.arg_num(&args, 0, 0.0)? as i64;
14856                let beats = self
14857                    .tracks
14858                    .get(id as usize)
14859                    .map(|t| {
14860                        let b = ling_music::analysis::bpm(&t.mono, t.rate);
14861                        ling_music::analysis::beat_grid(&t.mono, t.rate, b)
14862                    })
14863                    .unwrap_or_default();
14864                return Ok(Value::List(Rc::new(
14865                    beats.into_iter().map(|x| Value::Number(x as f64)).collect(),
14866                )));
14867            },
14868
14869            // ── playback ──
14870            #[cfg(not(target_arch = "wasm32"))]
14871            "music_play"
14872            | "播放音乐"
14873            | "音楽再生"
14874            | "음악재생"
14875            | "เล่นเพลง"
14876            | "پخش_موسیقی"
14877            | "شغّل_الموسيقى"
14878            | "נגן_מוזיקה"
14879            | "موسیقی_چلاؤ"
14880            | "jouer_musique"
14881            | "musik_abspielen"
14882            | "играть_музыку" => {
14883                let id = self.arg_num(&args, 0, 0.0)? as i64;
14884                if self.ensure_music() {
14885                    let track = self
14886                        .tracks
14887                        .get(id as usize)
14888                        .map(|t| (t.stereo.clone(), t.rate));
14889                    if let (Some((st, rate)), Some(m)) = (track, &self.music) {
14890                        m.set_track(st, rate);
14891                        m.play();
14892                    } else if let Some(m) = &self.music {
14893                        m.play();
14894                    }
14895                }
14896                return Ok(Value::Unit);
14897            },
14898            #[cfg(not(target_arch = "wasm32"))]
14899            "music_pause"
14900            | "暂停音乐"
14901            | "音楽一時停止"
14902            | "음악일시정지"
14903            | "หยุดเพลงชั่วคราว"
14904            | "مکث_موسیقی"
14905            | "ألبث_الموسيقى"
14906            | "השהה_מוזיקה"
14907            | "موسیقی_روکو_مؤقت"
14908            | "pause_musique"
14909            | "musik_pausieren"
14910            | "пауза_музыки" => {
14911                if let Some(m) = &self.music {
14912                    m.pause();
14913                }
14914                return Ok(Value::Unit);
14915            },
14916            #[cfg(not(target_arch = "wasm32"))]
14917            "music_stop"
14918            | "停止音乐"
14919            | "音楽停止"
14920            | "음악정지"
14921            | "หยุดเพลง"
14922            | "توقف_موسیقی"
14923            | "أوقف_الموسيقى"
14924            | "עצור_מוזיקה"
14925            | "موسیقی_روکو"
14926            | "arrêter_musique"
14927            | "musik_stoppen"
14928            | "остановить_музыку" => {
14929                if let Some(m) = &self.music {
14930                    m.stop();
14931                }
14932                return Ok(Value::Unit);
14933            },
14934            #[cfg(not(target_arch = "wasm32"))]
14935            "music_seek"
14936            | "定位音乐"
14937            | "音楽シーク"
14938            | "음악탐색"
14939            | "ค้นหาเพลง"
14940            | "جستجوی_موسیقی"
14941            | "ابحث_في_الموسيقى"
14942            | "חפש_במוזיקה"
14943            | "موسیقی_تلاش"
14944            | "chercher_musique"
14945            | "musik_suchen"
14946            | "перемотать_музыку" => {
14947                let sec = self.arg_num(&args, 0, 0.0)? as f32;
14948                if let Some(m) = &self.music {
14949                    m.seek(sec);
14950                }
14951                return Ok(Value::Unit);
14952            },
14953            #[cfg(not(target_arch = "wasm32"))]
14954            "music_pos"
14955            | "音乐位置"
14956            | "音楽位置"
14957            | "음악위치"
14958            | "ตำแหน่งเพลง"
14959            | "موقعیت_موسیقی"
14960            | "موضع_الموسيقى"
14961            | "מיקום_מוזיקה"
14962            | "موسیقی_مقام"
14963            | "position_musique"
14964            | "musik_position"
14965            | "позиция_музыки" => {
14966                let p = self.music.as_ref().map(|m| m.position()).unwrap_or(0.0);
14967                return Ok(Value::Number(p as f64));
14968            },
14969            #[cfg(not(target_arch = "wasm32"))]
14970            "music_volume"
14971            | "音乐音量"
14972            | "音楽音量"
14973            | "음악음량"
14974            | "ระดับเพลง"
14975            | "بلندی_موسیقی"
14976            | "مستوى_الموسيقى"
14977            | "עוצמת_מוזיקה"
14978            | "موسیقی_شدت"
14979            | "volume_musique"
14980            | "musik_lautstärke"
14981            | "громкость_музыки" => {
14982                let v = self.arg_num(&args, 0, 0.8)? as f32;
14983                if self.ensure_music() {
14984                    if let Some(m) = &self.music {
14985                        m.set_volume(v);
14986                    }
14987                }
14988                return Ok(Value::Unit);
14989            },
14990
14991            // ── synthesis (GM-capable, patches from .ling files) ──
14992            #[cfg(not(target_arch = "wasm32"))]
14993            "music_patch"
14994            | "乐器音色"
14995            | "音色読込"
14996            | "악기패치"
14997            | "แพตช์เครื่องดนตรี"
14998            | "پچ_موسیقی"
14999            | "آلة_الموسيقى"
15000            | "תיקון_כלי_נגינה"
15001            | "میوزک_پیچ"
15002            | "patch_musique"
15003            | "musik_patch"
15004            | "патч_музыки" => {
15005                let path = self.arg_str(&args, 0, "");
15006                let resolved = if std::path::Path::new(&path).exists() {
15007                    path.clone()
15008                } else if let Some(d) = &self.source_dir {
15009                    d.join(&path).to_string_lossy().into_owned()
15010                } else {
15011                    path.clone()
15012                };
15013                if !self.ensure_music() {
15014                    return Ok(Value::Number(-1.0));
15015                }
15016                match ling_music::patch::from_path(&resolved) {
15017                    Ok(p) => {
15018                        let id = self.music.as_ref().unwrap().add_patch(p);
15019                        return Ok(Value::Number(id as f64));
15020                    },
15021                    Err(e) => {
15022                        eprintln!("music_patch failed ({path}): {e}");
15023                        return Ok(Value::Number(-1.0));
15024                    },
15025                }
15026            },
15027            #[cfg(not(target_arch = "wasm32"))]
15028            "music_note"
15029            | "弹音符"
15030            | "音符演奏"
15031            | "음표연주"
15032            | "เล่นโน้ต"
15033            | "نواختن_نت"
15034            | "عزف_نغمة"
15035            | "נגן_תו"
15036            | "نوٹ_بجاؤ"
15037            | "note_musique"
15038            | "musik_note"
15039            | "нота_музыки" => {
15040                let inst = self.arg_num(&args, 0, 0.0)? as usize;
15041                let midi = self.pitch_arg(&args, 1, 60);
15042                let dur = self.arg_num(&args, 2, 0.5)? as f32;
15043                let vel = self.arg_num(&args, 3, 0.9)? as f32;
15044                if self.ensure_music() {
15045                    if let Some(m) = &self.music {
15046                        m.note(inst, midi, vel, dur);
15047                    }
15048                }
15049                return Ok(Value::Unit);
15050            },
15051            #[cfg(not(target_arch = "wasm32"))]
15052            "music_note_on"
15053            | "音符开始"
15054            | "音符オン"
15055            | "음표켜기"
15056            | "โน้ตเริ่ม"
15057            | "شروع_نت"
15058            | "بدء_النغمة"
15059            | "התחלת_תו"
15060            | "نوٹ_شروع"
15061            | "note_musique_on"
15062            | "musik_note_an"
15063            | "нота_музыки_вкл" => {
15064                let inst = self.arg_num(&args, 0, 0.0)? as usize;
15065                let midi = self.pitch_arg(&args, 1, 60);
15066                let vel = self.arg_num(&args, 2, 0.9)? as f32;
15067                if self.ensure_music() {
15068                    if let Some(m) = &self.music {
15069                        m.note_on(inst, midi, vel);
15070                    }
15071                }
15072                return Ok(Value::Unit);
15073            },
15074            #[cfg(not(target_arch = "wasm32"))]
15075            "music_note_off"
15076            | "音符结束"
15077            | "音符オフ"
15078            | "음표끄기"
15079            | "โน้ตจบ"
15080            | "پایان_نت"
15081            | "إيقاف_النغمة"
15082            | "סיום_תו"
15083            | "نوٹ_ختم"
15084            | "note_musique_off"
15085            | "musik_note_aus"
15086            | "нота_музыки_выкл" => {
15087                let inst = self.arg_num(&args, 0, 0.0)? as usize;
15088                let midi = self.pitch_arg(&args, 1, 60);
15089                if let Some(m) = &self.music {
15090                    m.note_off(inst, midi);
15091                }
15092                return Ok(Value::Unit);
15093            },
15094
15095            // ── rhythm-game judging ──
15096            #[cfg(not(target_arch = "wasm32"))]
15097            "music_judge"
15098            | "判定"
15099            | "判定する"
15100            | "판정"
15101            | "ตัดสินจังหวะ"
15102            | "داوری_ضرب"
15103            | "حكم_الإيقاع"
15104            | "שיפוט_קצב"
15105            | "بیٹ_فیصلہ"
15106            | "juger_musique"
15107            | "musik_bewerten"
15108            | "оценить_музыку" => {
15109                let delta_ms = self.arg_num(&args, 0, 9999.0)? as f32;
15110                return Ok(Value::Number(
15111                    ling_music::Grade::judge(delta_ms).index() as f64
15112                ));
15113            },
15114            #[cfg(not(target_arch = "wasm32"))]
15115            "music_grade_name"
15116            | "判定名"
15117            | "判定名称"
15118            | "판정이름"
15119            | "ชื่อการตัดสิน"
15120            | "نام_رتبه"
15121            | "اسم_التقييم"
15122            | "שם_דירוג"
15123            | "گریڈ_نام"
15124            | "nom_grade_musique"
15125            | "musik_bewertungsname"
15126            | "имя_оценки_музыки" => {
15127                let idx = self.arg_num(&args, 0, 4.0)? as i32;
15128                return Ok(Value::Str(
15129                    ling_music::Grade::from_index(idx).name().to_string(),
15130                ));
15131            },
15132
15133            // ── karaoke ──
15134            #[cfg(not(target_arch = "wasm32"))]
15135            "music_lrc"
15136            | "载入歌词"
15137            | "歌詞読込"
15138            | "가사로드"
15139            | "โหลดเนื้อเพลง"
15140            | "بارگذاری_متن_ترانه"
15141            | "تحميل_كلمات_الأغنية"
15142            | "טעינת_מילות_שיר"
15143            | "گیت_متن_لوڈ"
15144            | "lrc_musique"
15145            | "musik_lrc"
15146            | "lrc_музыки" => {
15147                let path = self.arg_str(&args, 0, "");
15148                let resolved = if std::path::Path::new(&path).exists() {
15149                    path.clone()
15150                } else if let Some(d) = &self.source_dir {
15151                    d.join(&path).to_string_lossy().into_owned()
15152                } else {
15153                    path.clone()
15154                };
15155                match std::fs::read_to_string(&resolved) {
15156                    Ok(text) => {
15157                        let id = self.lyrics.len();
15158                        self.lyrics.push(ling_music::Lyrics::parse(&text));
15159                        return Ok(Value::Number(id as f64));
15160                    },
15161                    Err(e) => {
15162                        eprintln!("music_lrc failed ({path}): {e}");
15163                        return Ok(Value::Number(-1.0));
15164                    },
15165                }
15166            },
15167            #[cfg(not(target_arch = "wasm32"))]
15168            "music_lyric"
15169            | "当前歌词"
15170            | "現在歌詞"
15171            | "현재가사"
15172            | "เนื้อเพลงปัจจุบัน"
15173            | "متن_ترانه_فعلی"
15174            | "كلمات_الأغنية_الحالية"
15175            | "מילות_שיר_נוכחיות"
15176            | "موجودہ_گیت_متن"
15177            | "paroles_musique"
15178            | "musik_liedtext"
15179            | "текст_песни" => {
15180                let id = self.arg_num(&args, 0, 0.0)? as i64;
15181                let t = self.arg_num(&args, 1, 0.0)? as f32;
15182                let line = self
15183                    .lyrics
15184                    .get(id as usize)
15185                    .map(|l| l.line_at(t).to_string())
15186                    .unwrap_or_default();
15187                return Ok(Value::Str(line));
15188            },
15189            #[cfg(not(target_arch = "wasm32"))]
15190            "music_mic_pitch"
15191            | "麦克风音高"
15192            | "マイク音程"
15193            | "마이크음정"
15194            | "ระดับเสียงไมค์"
15195            | "زیروبمی_میکروفون"
15196            | "طبقة_صوت_الميكروفون"
15197            | "גובה_צליל_מיקרופון"
15198            | "مائیکروفون_پچ"
15199            | "hauteur_micro_musique"
15200            | "musik_mikrofon_tonhöhe"
15201            | "высота_тона_микрофона" => {
15202                let hz = if let Some(mic) = self.mic.as_ref() {
15203                    let s = mic.latest_samples();
15204                    let rate = mic.sample_rate();
15205                    ling_music::pitch::detect(&s, rate).unwrap_or(0.0)
15206                } else {
15207                    0.0
15208                };
15209                return Ok(Value::Number(hz as f64));
15210            },
15211            #[cfg(not(target_arch = "wasm32"))]
15212            "music_note_name"
15213            | "音名"
15214            | "音名称"
15215            | "음이름"
15216            | "ชื่อโน้ต"
15217            | "نام_نت"
15218            | "اسم_النغمة"
15219            | "שם_תו"
15220            | "نوٹ_نام"
15221            | "nom_note_musique"
15222            | "musik_notenname"
15223            | "имя_ноты_музыки" => {
15224                let hz = self.arg_num(&args, 0, 0.0)? as f32;
15225                return Ok(Value::Str(ling_music::note::hz_to_name(hz)));
15226            },
15227            #[cfg(not(target_arch = "wasm32"))]
15228            "music_hz"
15229            | "音符频率"
15230            | "音符周波数"
15231            | "음표주파수"
15232            | "ความถี่โน้ต"
15233            | "فرکانس_نت"
15234            | "تردد_النغمة"
15235            | "תדר_תו"
15236            | "نوٹ_ہرٹز"
15237            | "hz_musique"
15238            | "musik_hz"
15239            | "музыка_гц" => {
15240                let midi = self.pitch_arg(&args, 0, 69);
15241                return Ok(Value::Number(
15242                    ling_music::note::midi_to_hz(midi as f32) as f64
15243                ));
15244            },
15245            #[cfg(not(target_arch = "wasm32"))]
15246            "music_pitch_score"
15247            | "音准评分"
15248            | "音程スコア"
15249            | "음정점수"
15250            | "คะแนนเสียง"
15251            | "امتیاز_زیروبمی"
15252            | "درجة_طبقة_الصوت"
15253            | "ציון_גובה_צליל"
15254            | "پچ_اسکور"
15255            | "score_hauteur_musique"
15256            | "musik_tonhöhen_punktzahl"
15257            | "счёт_высоты_тона" => {
15258                let hz = self.arg_num(&args, 0, 0.0)? as f32;
15259                let target = self.arg_num(&args, 1, 0.0)? as f32;
15260                return Ok(Value::Number(
15261                    ling_music::karaoke::pitch_score(hz, target) as f64
15262                ));
15263            },
15264
15265            // ── MIDI (inaudible note source: drive coins, cues, etc.) ──
15266            #[cfg(not(target_arch = "wasm32"))]
15267            "music_midi_load"
15268            | "载入MIDI"
15269            | "MIDI読込"
15270            | "미디로드"
15271            | "โหลดมิดี"
15272            | "بارگذاری_MIDI"
15273            | "تحميل_MIDI"
15274            | "טעינת_MIDI"
15275            | "MIDI_لوڈ"
15276            | "charger_midi_musique"
15277            | "musik_midi_laden"
15278            | "загрузить_midi_музыки" => {
15279                let path = self.arg_str(&args, 0, "");
15280                let resolved = if std::path::Path::new(&path).exists() {
15281                    path.clone()
15282                } else if let Some(d) = &self.source_dir {
15283                    d.join(&path).to_string_lossy().into_owned()
15284                } else {
15285                    path.clone()
15286                };
15287                match ling_music::midi::load(&resolved) {
15288                    Ok(m) => {
15289                        let id = self.midis.len();
15290                        self.midis.push(m);
15291                        return Ok(Value::Number(id as f64));
15292                    },
15293                    Err(e) => {
15294                        eprintln!("music_midi_load failed ({path}): {e}");
15295                        return Ok(Value::Number(-1.0));
15296                    },
15297                }
15298            },
15299            #[cfg(not(target_arch = "wasm32"))]
15300            "music_midi_count"
15301            | "MIDI数量"
15302            | "MIDI数"
15303            | "미디수"
15304            | "จำนวนมิดี"
15305            | "تعداد_MIDI"
15306            | "عدد_MIDI"
15307            | "מספר_MIDI"
15308            | "MIDI_تعداد"
15309            | "nombre_midi_musique"
15310            | "musik_midi_anzahl"
15311            | "число_midi_музыки" => {
15312                let id = self.arg_num(&args, 0, 0.0)? as i64;
15313                let n = self
15314                    .midis
15315                    .get(id as usize)
15316                    .map(|m| m.notes.len())
15317                    .unwrap_or(0);
15318                return Ok(Value::Number(n as f64));
15319            },
15320            // music_midi_notes(id) -> flat [time, midi, time, midi, …]
15321            #[cfg(not(target_arch = "wasm32"))]
15322            "music_midi_notes"
15323            | "MIDI音符"
15324            | "MIDIノート"
15325            | "미디음표"
15326            | "โน้ตมิดี"
15327            | "نت‌های_MIDI"
15328            | "نغمات_MIDI"
15329            | "תווי_MIDI"
15330            | "MIDI_نوٹس"
15331            | "notes_midi_musique"
15332            | "musik_midi_noten"
15333            | "ноты_midi_музыки" => {
15334                let id = self.arg_num(&args, 0, 0.0)? as i64;
15335                let mut out = Vec::new();
15336                if let Some(m) = self.midis.get(id as usize) {
15337                    for n in &m.notes {
15338                        out.push(Value::Number(n.time as f64));
15339                        out.push(Value::Number(n.midi as f64));
15340                    }
15341                }
15342                return Ok(Value::List(Rc::new(out)));
15343            },
15344            // music_midi_bars(id) -> flat [time, midi, dur, …] (for karaoke note bars)
15345            #[cfg(not(target_arch = "wasm32"))]
15346            "music_midi_bars"
15347            | "MIDI音条"
15348            | "MIDIバー"
15349            | "미디바"
15350            | "แท่งมิดี"
15351            | "میله‌های_MIDI"
15352            | "أعمدة_MIDI"
15353            | "עמודות_MIDI"
15354            | "MIDI_بارز"
15355            | "mesures_midi_musique"
15356            | "musik_midi_takte"
15357            | "такты_midi_музыки" => {
15358                let id = self.arg_num(&args, 0, 0.0)? as i64;
15359                let mut out = Vec::new();
15360                if let Some(m) = self.midis.get(id as usize) {
15361                    for n in &m.notes {
15362                        out.push(Value::Number(n.time as f64));
15363                        out.push(Value::Number(n.midi as f64));
15364                        out.push(Value::Number(n.dur as f64));
15365                    }
15366                }
15367                return Ok(Value::List(Rc::new(out)));
15368            },
15369
15370            // music_fft(track_id, nbands) -> spectrum at the current playback position
15371            #[cfg(not(target_arch = "wasm32"))]
15372            "music_fft"
15373            | "音乐频谱"
15374            | "音楽スペクトル"
15375            | "음악스펙트럼"
15376            | "สเปกตรัมเพลง"
15377            | "طیف_موسیقی"
15378            | "طيف_الموسيقى"
15379            | "ספקטרום_מוזיקה"
15380            | "میوزک_اسپیکٹرم"
15381            | "fft_musique"
15382            | "musik_fft"
15383            | "fft_музыки" => {
15384                let id = self.arg_num(&args, 0, 0.0)? as i64;
15385                let nbands = self.arg_num(&args, 1, 16.0)? as usize;
15386                let pos = self.music.as_ref().map(|m| m.position()).unwrap_or(0.0);
15387                if let Some(t) = self.tracks.get(id as usize) {
15388                    let idx = (pos * t.rate as f32) as usize;
15389                    let end = (idx + 2048).min(t.mono.len());
15390                    if end > idx + 64 {
15391                        self.fft.borrow_mut().push_samples(&t.mono[idx..end]);
15392                    }
15393                }
15394                let bands = self.fft.borrow().freq_bands(nbands);
15395                return Ok(Value::List(Rc::new(
15396                    bands.into_iter().map(|x| Value::Number(x as f64)).collect(),
15397                )));
15398            },
15399
15400            // ── stop every one-shot SFX/morph/sample voice (scene cleanup) ──
15401            #[cfg(not(target_arch = "wasm32"))]
15402            "audio_stop_sfx"
15403            | "停止音效"
15404            | "効果音停止"
15405            | "효과음정지"
15406            | "หยุดเอฟเฟกต์ทั้งหมด"
15407            | "توقف_همه_جلوه‌ها"
15408            | "أوقف_كل_المؤثرات"
15409            | "עצור_כל_האפקטים"
15410            | "تمام_ایفیکٹ_روکو" => {
15411                if let Some(a) = &self.audio {
15412                    a.stop_all_sfx();
15413                }
15414                return Ok(Value::Unit);
15415            },
15416            // ── spatial (2D/3D/4D) one-shot SFX ──
15417            #[cfg(not(target_arch = "wasm32"))]
15418            "audio_sfx"
15419            | "音效"
15420            | "空間効果音"
15421            | "공간효과음"
15422            | "เสียงเอฟเฟกต์"
15423            | "جلوه_صوتی"
15424            | "مؤثرات_صوتية"
15425            | "אפקט_קול"
15426            | "آواز_ایفیکٹ"
15427            | "effet_sonore"
15428            | "klangeffekt"
15429            | "звуковой_эффект" => {
15430                let x = self.arg_num(&args, 0, 0.0)? as f32;
15431                let y = self.arg_num(&args, 1, 0.0)? as f32;
15432                let z = self.arg_num(&args, 2, 0.0)? as f32;
15433                let w = self.arg_num(&args, 3, 1.0)? as f32;
15434                let freq = self.arg_num(&args, 4, 440.0)? as f32;
15435                let amp = self.arg_num(&args, 5, 0.3)? as f32;
15436                let dur = self.arg_num(&args, 6, 0.15)? as f32;
15437                let wave = Wave::from_name(&self.arg_str(&args, 7, "sine"));
15438                if let Some(a) = &self.audio {
15439                    a.sfx(x, y, z, w, freq, amp, dur, wave);
15440                }
15441                return Ok(Value::Unit);
15442            },
15443            // ── YIN-YANG morph synth note: physical-model(light) ↔ FM/crush(dark) ──
15444            // โน้ตมอร์ฟ(x,y,z,w, freq, amp, dur, material, morph)
15445            //   material: 0 bowed-string · 1 plucked · 2 blown · 3 struck-metal
15446            //   morph:    0.0 light/acoustic .. 1.0 dark/digital
15447            #[cfg(not(target_arch = "wasm32"))]
15448            "morph_note"
15449            | "โน้ตมอร์ฟ"
15450            | "变形音"
15451            | "モーフ音"
15452            | "모프음"
15453            | "نت_مورف"
15454            | "نغمة_متحولة"
15455            | "תו_מורף"
15456            | "مورف_نوٹ" => {
15457                let x = self.arg_num(&args, 0, 0.0)? as f32;
15458                let y = self.arg_num(&args, 1, 0.0)? as f32;
15459                let z = self.arg_num(&args, 2, 0.0)? as f32;
15460                let w = self.arg_num(&args, 3, 1.0)? as f32;
15461                let freq = self.arg_num(&args, 4, 220.0)? as f32;
15462                let amp = self.arg_num(&args, 5, 0.3)? as f32;
15463                let dur = self.arg_num(&args, 6, 0.6)? as f32;
15464                let material = self.arg_num(&args, 7, 0.0)?.clamp(0.0, 3.0) as u8;
15465                let morph = self.arg_num(&args, 8, 0.0)? as f32;
15466                if let Some(a) = &self.audio {
15467                    a.morph_note(x, y, z, w, freq, amp, dur, material, morph);
15468                }
15469                return Ok(Value::Unit);
15470            },
15471            // ── sample load / positional play / loop / stop ──
15472            #[cfg(not(target_arch = "wasm32"))]
15473            "audio_sample_load"
15474            | "载入采样"
15475            | "サンプル読込"
15476            | "샘플로드"
15477            | "โหลดตัวอย่างเสียง"
15478            | "بارگذاری_نمونه_صدا"
15479            | "تحميل_عينة_صوتية"
15480            | "טעינת_דגימת_קול"
15481            | "آواز_نمونہ_لوڈ"
15482            | "charger_échantillon"
15483            | "sample_laden"
15484            | "загрузить_семпл" => {
15485                let path = self.arg_str(&args, 0, "");
15486                let resolved = if std::path::Path::new(&path).exists() {
15487                    path.clone()
15488                } else if let Some(d) = &self.source_dir {
15489                    d.join(&path).to_string_lossy().into_owned()
15490                } else {
15491                    path.clone()
15492                };
15493                match ling_music::load(&resolved) {
15494                    Ok(t) => {
15495                        if let Some(a) = &self.audio {
15496                            return Ok(Value::Number(a.add_sample(t.mono, t.rate) as f64));
15497                        }
15498                        return Ok(Value::Number(-1.0));
15499                    },
15500                    Err(e) => {
15501                        eprintln!("audio_sample_load failed ({path}): {e}");
15502                        return Ok(Value::Number(-1.0));
15503                    },
15504                }
15505            },
15506            #[cfg(not(target_arch = "wasm32"))]
15507            "audio_sample_play"
15508            | "播放采样"
15509            | "サンプル再生"
15510            | "샘플재생"
15511            | "เล่นตัวอย่างเสียง"
15512            | "پخش_نمونه_صدا"
15513            | "تشغيل_عينة_صوتية"
15514            | "נגינת_דגימת_קול"
15515            | "آواز_نمونہ_چلاؤ"
15516            | "jouer_échantillon"
15517            | "sample_abspielen"
15518            | "играть_семпл" => {
15519                let id = self.arg_num(&args, 0, 0.0)? as usize;
15520                let x = self.arg_num(&args, 1, 0.0)? as f32;
15521                let y = self.arg_num(&args, 2, 0.0)? as f32;
15522                let z = self.arg_num(&args, 3, 0.0)? as f32;
15523                let w = self.arg_num(&args, 4, 1.0)? as f32;
15524                let vol = self.arg_num(&args, 5, 1.0)? as f32;
15525                let looping = self.arg_num(&args, 6, 0.0)? > 0.5;
15526                let v = self
15527                    .audio
15528                    .as_ref()
15529                    .map(|a| a.play_sample(id, x, y, z, w, vol, looping))
15530                    .unwrap_or(0);
15531                return Ok(Value::Number(v as f64));
15532            },
15533            // Flat/non-positional sample playback — no distance falloff, no
15534            // stereo pan, same level regardless of listener position. For
15535            // VO/narration/UI cues, as opposed to audio_sample_play's
15536            // world-space sound effects (which fall off with camera distance).
15537            #[cfg(not(target_arch = "wasm32"))]
15538            "audio_sample_play_flat"
15539            | "播放采样平"
15540            | "サンプル再生平"
15541            | "샘플재생평"
15542            | "เล่นตัวอย่างเสียงเรียบ" => {
15543                let id = self.arg_num(&args, 0, 0.0)? as usize;
15544                let vol = self.arg_num(&args, 1, 1.0)? as f32;
15545                let looping = self.arg_num(&args, 2, 0.0)? > 0.5;
15546                let v = self
15547                    .audio
15548                    .as_ref()
15549                    .map(|a| a.play_sample_flat(id, vol, looping))
15550                    .unwrap_or(0);
15551                return Ok(Value::Number(v as f64));
15552            },
15553            #[cfg(not(target_arch = "wasm32"))]
15554            "audio_sample_stop"
15555            | "停止采样"
15556            | "サンプル停止"
15557            | "샘플정지"
15558            | "หยุดตัวอย่างเสียง"
15559            | "توقف_نمونه_صدا"
15560            | "إيقاف_عينة_صوتية"
15561            | "עצירת_דגימת_קול"
15562            | "آواز_نمونہ_روکو"
15563            | "arrêter_échantillon"
15564            | "sample_stoppen"
15565            | "остановить_семпл" => {
15566                let v = self.arg_num(&args, 0, 0.0)? as u32;
15567                if let Some(a) = &self.audio {
15568                    a.stop_sample(v);
15569                }
15570                return Ok(Value::Unit);
15571            },
15572            // Duration (seconds) of an already-loaded sample. 0.0 if the id
15573            // is invalid. Lets a cutscene pace itself off the real length of
15574            // whichever language's VO clip actually got loaded, instead of a
15575            // hand-measured constant that only covers one language.
15576            #[cfg(not(target_arch = "wasm32"))]
15577            "audio_sample_duration"
15578            | "采样时长"
15579            | "サンプル長さ"
15580            | "샘플길이"
15581            | "ความยาวตัวอย่างเสียง" => {
15582                let id = self.arg_num(&args, 0, 0.0)? as usize;
15583                let dur = self
15584                    .audio
15585                    .as_ref()
15586                    .map(|a| a.sample_duration(id))
15587                    .unwrap_or(0.0);
15588                return Ok(Value::Number(dur as f64));
15589            },
15590            // ── master FX: delay / reverb / low-pass (underwater) ──
15591            #[cfg(not(target_arch = "wasm32"))]
15592            "audio_fx_delay"
15593            | "回声"
15594            | "ディレイ効果"
15595            | "딜레이"
15596            | "เสียงสะท้อน"
15597            | "افکت_تاخیر"
15598            | "صدى_تأخير"
15599            | "אפקט_עיכוב"
15600            | "تاخیر_ایفیکٹ"
15601            | "délai_audio"
15602            | "audio_verzögerung"
15603            | "звук_задержка" => {
15604                let time = self.arg_num(&args, 0, 0.3)? as f32;
15605                let fb = self.arg_num(&args, 1, 0.3)? as f32;
15606                let mix = self.arg_num(&args, 2, 0.3)? as f32;
15607                if let Some(a) = &self.audio {
15608                    a.fx_delay(time, fb, mix);
15609                }
15610                return Ok(Value::Unit);
15611            },
15612            #[cfg(not(target_arch = "wasm32"))]
15613            "audio_fx_reverb"
15614            | "混响"
15615            | "リバーブ"
15616            | "리버브"
15617            | "เสียงก้อง"
15618            | "افکت_پژواک"
15619            | "صدى_ارتداد"
15620            | "אפקט_הדהוד"
15621            | "بازگشت_آواز_ایفیکٹ"
15622            | "réverbération_audio"
15623            | "audio_nachhall"
15624            | "звук_реверберация" => {
15625                let mix = self.arg_num(&args, 0, 0.3)? as f32;
15626                if let Some(a) = &self.audio {
15627                    a.fx_reverb(mix);
15628                }
15629                return Ok(Value::Unit);
15630            },
15631            #[cfg(not(target_arch = "wasm32"))]
15632            "audio_fx_lowpass"
15633            | "低通滤波"
15634            | "ローパス"
15635            | "저역통과"
15636            | "กรองความถี่ต่ำ"
15637            | "فیلتر_پایین‌گذر"
15638            | "مرشح_تمرير_منخفض"
15639            | "מסנן_תדר_נמוך"
15640            | "لو_پاس_فلٹر"
15641            | "passe_bas_audio"
15642            | "audio_tiefpass"
15643            | "звук_фнч" => {
15644                let cutoff = self.arg_num(&args, 0, 1.0)? as f32;
15645                if let Some(a) = &self.audio {
15646                    a.fx_lowpass(cutoff);
15647                }
15648                return Ok(Value::Unit);
15649            },
15650
15651            // ══════════════════════════════════════════════════════════════════
15652            // PHYSICS BUILTINS  (crates/ling-physics) — soft bodies, rigid+angular,
15653            // and a fast 2-D water/oil liquid sim mappable onto 3-D surfaces.
15654            // ══════════════════════════════════════════════════════════════════
15655
15656            // ── soft bodies (deformable bouncy balls) ──
15657            #[cfg(not(target_arch = "wasm32"))]
15658            "soft_ball"
15659            | "软球"
15660            | "ソフトボール"
15661            | "소프트볼"
15662            | "ลูกบอลนุ่ม"
15663            | "توپ_نرم"
15664            | "كرة_ناعمة"
15665            | "כדור_רך"
15666            | "نرم_گیند"
15667            | "balle_molle"
15668            | "weicher_ball"
15669            | "мягкий_шар" => {
15670                let x = self.arg_num(&args, 0, 0.)? as f32;
15671                let y = self.arg_num(&args, 1, 0.)? as f32;
15672                let z = self.arg_num(&args, 2, 0.)? as f32;
15673                let r = self.arg_num(&args, 3, 1.0)? as f32;
15674                let b = ling_physics::soft::SoftBody::sphere(
15675                    ling_physics::Vec3::new(x, y, z),
15676                    r,
15677                    8,
15678                    12,
15679                    1.0,
15680                );
15681                let id = self.soft_bodies.len();
15682                self.soft_bodies.push(b);
15683                return Ok(Value::Number(id as f64));
15684            },
15685            #[cfg(not(target_arch = "wasm32"))]
15686            "soft_step"
15687            | "软体步进"
15688            | "ソフト更新"
15689            | "소프트스텝"
15690            | "ก้าวนุ่ม"
15691            | "گام_نرم_جسم"
15692            | "خطوة_ناعمة"
15693            | "צעד_רך"
15694            | "نرم_قدم"
15695            | "pas_mou"
15696            | "weicher_schritt"
15697            | "мягкий_шаг" => {
15698                let id = self.arg_num(&args, 0, 0.)? as usize;
15699                let dt = self.arg_num(&args, 1, 0.016)? as f32;
15700                let gy = self.arg_num(&args, 2, 15.0)? as f32;
15701                if let Some(b) = self.soft_bodies.get_mut(id) {
15702                    b.integrate(dt, ling_physics::Vec3::new(0.0, gy, 0.0), 4);
15703                }
15704                return Ok(Value::Unit);
15705            },
15706            #[cfg(not(target_arch = "wasm32"))]
15707            "soft_bounce"
15708            | "软体落地"
15709            | "ソフト着地"
15710            | "소프트바운스"
15711            | "เด้งนุ่ม"
15712            | "جهش_نرم"
15713            | "ارتداد_ناعم"
15714            | "קפיצה_רכה"
15715            | "نرم_اچھال"
15716            | "rebond_mou"
15717            | "weicher_abprall"
15718            | "мягкий_отскок" => {
15719                let id = self.arg_num(&args, 0, 0.)? as usize;
15720                let fy = self.arg_num(&args, 1, 0.)? as f32;
15721                let rest = self.arg_num(&args, 2, 0.5)? as f32;
15722                if let Some(b) = self.soft_bodies.get_mut(id) {
15723                    b.floor_collision(fy, rest);
15724                }
15725                return Ok(Value::Unit);
15726            },
15727            #[cfg(not(target_arch = "wasm32"))]
15728            "soft_contain"
15729            | "软体边界"
15730            | "ソフト箱"
15731            | "소프트경계"
15732            | "กล่องนุ่ม"
15733            | "محفظه_نرم"
15734            | "احتواء_ناعم"
15735            | "הכלה_רכה"
15736            | "نرم_احاطہ"
15737            | "contenir_mou"
15738            | "weiche_eindämmung"
15739            | "мягкое_сдерживание" => {
15740                let id = self.arg_num(&args, 0, 0.)? as usize;
15741                let nx = self.arg_num(&args, 1, -5.)? as f32;
15742                let ny = self.arg_num(&args, 2, -5.)? as f32;
15743                let nz = self.arg_num(&args, 3, -5.)? as f32;
15744                let mx = self.arg_num(&args, 4, 5.)? as f32;
15745                let my = self.arg_num(&args, 5, 5.)? as f32;
15746                let mz = self.arg_num(&args, 6, 5.)? as f32;
15747                let rest = self.arg_num(&args, 7, 0.6)? as f32;
15748                if let Some(b) = self.soft_bodies.get_mut(id) {
15749                    b.contain(
15750                        ling_physics::Vec3::new(nx, ny, nz),
15751                        ling_physics::Vec3::new(mx, my, mz),
15752                        rest,
15753                    );
15754                }
15755                return Ok(Value::Unit);
15756            },
15757            #[cfg(not(target_arch = "wasm32"))]
15758            "soft_kick"
15759            | "软体踢"
15760            | "ソフト衝撃"
15761            | "소프트킥"
15762            | "เตะนุ่ม"
15763            | "ضربه_نرم"
15764            | "ركلة_ناعمة"
15765            | "בעיטה_רכה"
15766            | "نرم_ٹھوکر"
15767            | "coup_mou"
15768            | "weicher_stoß"
15769            | "мягкий_удар" => {
15770                let id = self.arg_num(&args, 0, 0.)? as usize;
15771                let dx = self.arg_num(&args, 1, 0.)? as f32;
15772                let dy = self.arg_num(&args, 2, 0.)? as f32;
15773                let dz = self.arg_num(&args, 3, 0.)? as f32;
15774                let s = self.arg_num(&args, 4, 0.1)? as f32;
15775                if let Some(b) = self.soft_bodies.get_mut(id) {
15776                    b.kick(ling_physics::Vec3::new(dx, dy, dz), s);
15777                }
15778                return Ok(Value::Unit);
15779            },
15780            // soft_spin(id, ax, ay, az, rate) — add angular velocity about the axis
15781            // through the centroid (rate = rad/step; ≈ surface_speed / radius to roll)
15782            #[cfg(not(target_arch = "wasm32"))]
15783            "soft_spin"
15784            | "软体自旋"
15785            | "ソフト回転"
15786            | "소프트회전"
15787            | "หมุนนุ่ม"
15788            | "چرخش_نرم"
15789            | "دوران_ناعم"
15790            | "סיבוב_רך"
15791            | "نرم_گھماؤ"
15792            | "rotation_molle"
15793            | "weicher_spin"
15794            | "мягкое_вращение" => {
15795                let id = self.arg_num(&args, 0, 0.)? as usize;
15796                let ax = self.arg_num(&args, 1, 0.)? as f32;
15797                let ay = self.arg_num(&args, 2, 0.)? as f32;
15798                let az = self.arg_num(&args, 3, 0.)? as f32;
15799                let rate = self.arg_num(&args, 4, 0.1)? as f32;
15800                if let Some(b) = self.soft_bodies.get_mut(id) {
15801                    b.spin(ling_physics::Vec3::new(ax, ay, az), rate);
15802                }
15803                return Ok(Value::Unit);
15804            },
15805            #[cfg(not(target_arch = "wasm32"))]
15806            "soft_deform"
15807            | "形变量"
15808            | "変形量"
15809            | "변형량"
15810            | "ความบิดเบี้ยว"
15811            | "تغییرشکل_نرم"
15812            | "تشوه_ناعم"
15813            | "עיוות_רך"
15814            | "نرم_بگاڑ"
15815            | "déformer_mou"
15816            | "weiches_verformen"
15817            | "мягкая_деформация" => {
15818                let id = self.arg_num(&args, 0, 0.)? as usize;
15819                let d = self
15820                    .soft_bodies
15821                    .get(id)
15822                    .map(|b| b.deformation())
15823                    .unwrap_or(0.0);
15824                return Ok(Value::Number(d as f64));
15825            },
15826            // soft_angular_speed(id) -> magnitude of the body's angular velocity
15827            // (how fast it is tumbling/rolling), derived from its node velocities.
15828            #[cfg(not(target_arch = "wasm32"))]
15829            "soft_angular_speed"
15830            | "软体角速"
15831            | "ソフト角速度"
15832            | "소프트각속도"
15833            | "ความเร็วเชิงมุมนุ่ม"
15834            | "سرعت_زاویه‌ای_نرم"
15835            | "سرعة_زاوية_ناعمة"
15836            | "מהירות_זוויתית_רכה"
15837            | "نرم_زاویائی_رفتار"
15838            | "vitesse_angulaire_molle"
15839            | "weiche_winkelgeschwindigkeit"
15840            | "мягкая_угловая_скорость" => {
15841                let id = self.arg_num(&args, 0, 0.)? as usize;
15842                let w = self
15843                    .soft_bodies
15844                    .get(id)
15845                    .map(|b| b.angular_speed())
15846                    .unwrap_or(0.0);
15847                return Ok(Value::Number(w as f64));
15848            },
15849            #[cfg(not(target_arch = "wasm32"))]
15850            "soft_centroid"
15851            | "软体质心"
15852            | "ソフト重心"
15853            | "소프트중심"
15854            | "จุดศูนย์กลางนุ่ม"
15855            | "مرکز_جرم_نرم"
15856            | "مركز_ثقل_ناعم"
15857            | "מרכז_כובד_רך"
15858            | "نرم_مرکز_ثقل"
15859            | "centroïde_mou"
15860            | "weicher_schwerpunkt"
15861            | "мягкий_центроид" => {
15862                let id = self.arg_num(&args, 0, 0.)? as usize;
15863                let c = self
15864                    .soft_bodies
15865                    .get(id)
15866                    .map(|b| b.centroid())
15867                    .unwrap_or(ling_physics::Vec3::ZERO);
15868                return Ok(Value::List(Rc::new(vec![
15869                    Value::Number(c.x as f64),
15870                    Value::Number(c.y as f64),
15871                    Value::Number(c.z as f64),
15872                ])));
15873            },
15874            // soft_nodes(id) -> flat [x,y,z, x,y,z, …] for rendering the deformed mesh
15875            #[cfg(not(target_arch = "wasm32"))]
15876            "soft_nodes"
15877            | "软体节点"
15878            | "ソフト節点"
15879            | "소프트노드"
15880            | "จุดนุ่ม"
15881            | "گره‌های_نرم"
15882            | "عقد_ناعمة"
15883            | "צמתי_רך"
15884            | "نرم_نوڈز"
15885            | "nœuds_mous"
15886            | "weiche_knoten"
15887            | "мягкие_узлы" => {
15888                let id = self.arg_num(&args, 0, 0.)? as usize;
15889                let mut out = Vec::new();
15890                if let Some(b) = self.soft_bodies.get(id) {
15891                    for n in &b.nodes {
15892                        out.push(Value::Number(n.pos.x as f64));
15893                        out.push(Value::Number(n.pos.y as f64));
15894                        out.push(Value::Number(n.pos.z as f64));
15895                    }
15896                }
15897                return Ok(Value::List(Rc::new(out)));
15898            },
15899
15900            // ── rigid bodies with angular dynamics ──
15901            #[cfg(not(target_arch = "wasm32"))]
15902            "rb_add"
15903            | "刚体添加"
15904            | "剛体追加"
15905            | "강체추가"
15906            | "เพิ่มวัตถุแข็ง"
15907            | "افزودن_جسم_صلب"
15908            | "أضف_جسما_صلبا"
15909            | "הוסף_גוף_קשיח"
15910            | "سخت_جسم_شامل_کرو"
15911            | "ajouter_corps_rigide"
15912            | "starrkörper_hinzufügen"
15913            | "добавить_твёрдое_тело" => {
15914                let x = self.arg_num(&args, 0, 0.)? as f32;
15915                let y = self.arg_num(&args, 1, 0.)? as f32;
15916                let z = self.arg_num(&args, 2, 0.)? as f32;
15917                let mass = self.arg_num(&args, 3, 1.0)? as f32;
15918                let mut b =
15919                    ling_physics::rigid::RigidBody::new(ling_physics::Vec3::new(x, y, z), mass);
15920                b.restitution = 0.6;
15921                return Ok(Value::Number(self.rigid_world.add(b) as f64));
15922            },
15923            #[cfg(not(target_arch = "wasm32"))]
15924            "rb_torque"
15925            | "扭矩"
15926            | "トルク"
15927            | "토크"
15928            | "แรงบิด"
15929            | "گشتاور"
15930            | "عزم_دوران"
15931            | "מומנט"
15932            | "ٹارک"
15933            | "couple_corps_rigide"
15934            | "starrkörper_drehmoment"
15935            | "крутящий_момент_твёрдого_тела" => {
15936                let i = self.arg_num(&args, 0, 0.)? as usize;
15937                let tx = self.arg_num(&args, 1, 0.)? as f32;
15938                let ty = self.arg_num(&args, 2, 0.)? as f32;
15939                let tz = self.arg_num(&args, 3, 0.)? as f32;
15940                if let Some(b) = self.rigid_world.bodies.get_mut(i) {
15941                    b.apply_torque(ling_physics::Vec3::new(tx, ty, tz));
15942                }
15943                return Ok(Value::Unit);
15944            },
15945            #[cfg(not(target_arch = "wasm32"))]
15946            "rb_spin"
15947            | "自旋"
15948            | "スピン"
15949            | "스핀"
15950            | "หมุน"
15951            | "چرخش_جسم_صلب"
15952            | "دوران_جسم_صلب"
15953            | "סיבוב_גוף_קשיח"
15954            | "سخت_جسم_گھماؤ"
15955            | "spin_corps_rigide"
15956            | "starrkörper_spin"
15957            | "вращение_твёрдого_тела" => {
15958                let i = self.arg_num(&args, 0, 0.)? as usize;
15959                let wx = self.arg_num(&args, 1, 0.)? as f32;
15960                let wy = self.arg_num(&args, 2, 0.)? as f32;
15961                let wz = self.arg_num(&args, 3, 0.)? as f32;
15962                if let Some(b) = self.rigid_world.bodies.get_mut(i) {
15963                    b.apply_spin(ling_physics::Vec3::new(wx, wy, wz));
15964                }
15965                return Ok(Value::Unit);
15966            },
15967            #[cfg(not(target_arch = "wasm32"))]
15968            "rb_impulse"
15969            | "刚体冲量"
15970            | "剛体インパルス"
15971            | "강체충격"
15972            | "แรงดลแข็ง"
15973            | "ضربه_جسم_صلب"
15974            | "دفعة_جسم_صلب"
15975            | "דחף_גוף_קשיח"
15976            | "سخت_جسم_دھکا"
15977            | "impulsion_corps_rigide"
15978            | "starrkörper_impuls"
15979            | "импульс_твёрдого_тела" => {
15980                let i = self.arg_num(&args, 0, 0.)? as usize;
15981                let ix = self.arg_num(&args, 1, 0.)? as f32;
15982                let iy = self.arg_num(&args, 2, 0.)? as f32;
15983                let iz = self.arg_num(&args, 3, 0.)? as f32;
15984                if let Some(b) = self.rigid_world.bodies.get_mut(i) {
15985                    b.apply_impulse(ling_physics::Vec3::new(ix, iy, iz));
15986                }
15987                return Ok(Value::Unit);
15988            },
15989            #[cfg(not(target_arch = "wasm32"))]
15990            "rb_floor"
15991            | "刚体落地"
15992            | "剛体着地"
15993            | "강체바닥"
15994            | "พื้นแข็ง"
15995            | "کف_جسم_صلب"
15996            | "أرضية_جسم_صلب"
15997            | "רצפת_גוף_קשיח"
15998            | "سخت_جسم_فرش"
15999            | "sol_corps_rigide"
16000            | "starrkörper_boden"
16001            | "пол_твёрдого_тела" => {
16002                let i = self.arg_num(&args, 0, 0.)? as usize;
16003                let fy = self.arg_num(&args, 1, 0.)? as f32;
16004                let rest = self.arg_num(&args, 2, 0.6)? as f32;
16005                let fric = self.arg_num(&args, 3, 0.6)? as f32;
16006                if let Some(b) = self.rigid_world.bodies.get_mut(i) {
16007                    b.bounce_floor(fy, rest, fric);
16008                }
16009                return Ok(Value::Unit);
16010            },
16011            #[cfg(not(target_arch = "wasm32"))]
16012            "rb_gravity"
16013            | "刚体重力"
16014            | "剛体重力"
16015            | "강체중력"
16016            | "แรงโน้มถ่วงแข็ง"
16017            | "گرانش_جسم_صلب"
16018            | "جاذبية_جسم_صلب"
16019            | "כבידת_גוף_קשיח"
16020            | "سخت_جسم_کشش_ثقل"
16021            | "gravité_corps_rigide"
16022            | "starrkörper_schwerkraft"
16023            | "гравитация_твёрдого_тела" => {
16024                let gx = self.arg_num(&args, 0, 0.)? as f32;
16025                let gy = self.arg_num(&args, 1, 9.81)? as f32;
16026                let gz = self.arg_num(&args, 2, 0.)? as f32;
16027                self.rigid_world.gravity = ling_physics::Vec3::new(gx, gy, gz);
16028                return Ok(Value::Unit);
16029            },
16030            #[cfg(not(target_arch = "wasm32"))]
16031            "rb_step"
16032            | "刚体步进"
16033            | "剛体更新"
16034            | "강체스텝"
16035            | "ก้าวแข็ง"
16036            | "گام_جسم_صلب"
16037            | "خطوة_جسم_صلب"
16038            | "צעד_גוף_קשיח"
16039            | "سخت_جسم_قدم"
16040            | "pas_corps_rigide"
16041            | "starrkörper_schritt"
16042            | "шаг_твёрдого_тела" => {
16043                let dt = self.arg_num(&args, 0, 0.016)? as f32;
16044                self.rigid_world.step(dt);
16045                return Ok(Value::Unit);
16046            },
16047            #[cfg(not(target_arch = "wasm32"))]
16048            "rb_pos"
16049            | "刚体位置"
16050            | "剛体位置"
16051            | "강체위치"
16052            | "ตำแหน่งแข็ง"
16053            | "موقعیت_جسم_صلب"
16054            | "موضع_جسم_صلب"
16055            | "מיקום_גוף_קשיח"
16056            | "سخت_جسم_مقام"
16057            | "position_corps_rigide"
16058            | "starrkörper_position"
16059            | "позиция_твёрдого_тела" => {
16060                let i = self.arg_num(&args, 0, 0.)? as usize;
16061                let p = self
16062                    .rigid_world
16063                    .bodies
16064                    .get(i)
16065                    .map(|b| b.pos)
16066                    .unwrap_or(ling_physics::Vec3::ZERO);
16067                return Ok(Value::List(Rc::new(vec![
16068                    Value::Number(p.x as f64),
16069                    Value::Number(p.y as f64),
16070                    Value::Number(p.z as f64),
16071                ])));
16072            },
16073            #[cfg(not(target_arch = "wasm32"))]
16074            "rb_rot"
16075            | "刚体旋转"
16076            | "剛体回転"
16077            | "강체회전"
16078            | "การหมุนแข็ง"
16079            | "چرخش_وضعية_جسم_صلب"
16080            | "دوران_وضعية_جسم_صلب"
16081            | "סיבוב_זווית_גוף_קשיח"
16082            | "سخت_جسم_گردش"
16083            | "rotation_corps_rigide"
16084            | "starrkörper_rotation"
16085            | "поворот_твёрдого_тела" => {
16086                let i = self.arg_num(&args, 0, 0.)? as usize;
16087                let q = self
16088                    .rigid_world
16089                    .bodies
16090                    .get(i)
16091                    .map(|b| b.orientation)
16092                    .unwrap_or(ling_physics::Quat::IDENTITY);
16093                return Ok(Value::List(Rc::new(vec![
16094                    Value::Number(q.x as f64),
16095                    Value::Number(q.y as f64),
16096                    Value::Number(q.z as f64),
16097                    Value::Number(q.w as f64),
16098                ])));
16099            },
16100
16101            // ── native-res mesh (.lmesh): load once, draw fast (unlit, per-tri colour) ──
16102            #[cfg(not(target_arch = "wasm32"))]
16103            "mesh_load"
16104            | "โหลดเมช"
16105            | "载入网格"
16106            | "メッシュ読込"
16107            | "메시로드"
16108            | "بارگذاری_مش"
16109            | "حمّل_شبكة"
16110            | "טען_מש"
16111            | "میش_لوڈ" => {
16112                let path = self.arg_str(&args, 0, "");
16113                let resolved = if std::path::Path::new(&path).exists() {
16114                    path.clone()
16115                } else if let Some(d) = &self.source_dir {
16116                    d.join(&path).to_string_lossy().into_owned()
16117                } else {
16118                    path.clone()
16119                };
16120                let bytes = match std::fs::read(&resolved) {
16121                    Ok(b) => b,
16122                    Err(e) => {
16123                        eprintln!("mesh_load failed ({path}): {e}");
16124                        return Ok(Value::Number(-1.0));
16125                    },
16126                };
16127                if bytes.len() < 16 || &bytes[0..4] != b"LMSH" {
16128                    eprintln!("mesh_load: bad header ({path})");
16129                    return Ok(Value::Number(-1.0));
16130                }
16131                let rd4 =
16132                    |o: usize| -> [u8; 4] { [bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]] };
16133                let height = f32::from_le_bytes(rd4(8));
16134                let ntri = u32::from_le_bytes(rd4(12)) as usize;
16135                let need = 16usize.saturating_add(ntri.saturating_mul(9 * 4 + 3));
16136                if bytes.len() < need {
16137                    eprintln!("mesh_load: truncated ({path})");
16138                    return Ok(Value::Number(-1.0));
16139                }
16140                let mut pos = Vec::with_capacity(ntri * 3);
16141                let mut col = Vec::with_capacity(ntri);
16142                let mut off = 16usize;
16143                for _ in 0..ntri {
16144                    for _k in 0..3 {
16145                        let x = f32::from_le_bytes(rd4(off));
16146                        let y = f32::from_le_bytes(rd4(off + 4));
16147                        let z = f32::from_le_bytes(rd4(off + 8));
16148                        off += 12;
16149                        pos.push([x, y, z]);
16150                    }
16151                    col.push([bytes[off], bytes[off + 1], bytes[off + 2]]);
16152                    off += 3;
16153                }
16154                eprintln!("mesh_load: {} ({} tris, h={:.2})", path, ntri, height);
16155                let id = self.meshes.len();
16156                self.meshes
16157                    .push(crate::gfx::shapes::ColorMesh { pos, col, height });
16158                return Ok(Value::Number(id as f64));
16159            },
16160            #[cfg(target_arch = "wasm32")]
16161            "mesh_load"
16162            | "โหลดเมช"
16163            | "载入网格"
16164            | "メッシュ読込"
16165            | "메시로드"
16166            | "بارگذاری_مش"
16167            | "حمّل_شبكة"
16168            | "טען_רשת"
16169            | "میش_لوڈ" => {
16170                // Native .lmesh loading is file-system based and not wired for wasm yet.
16171                // Return an invalid handle so scripts can choose a fallback path.
16172                return Ok(Value::Number(-1.0));
16173            },
16174            #[cfg(not(target_arch = "wasm32"))]
16175            "mesh_draw"
16176            | "วาดเมชสี"
16177            | "绘制网格"
16178            | "メッシュ描画"
16179            | "메시그리기"
16180            | "رسم_مش_رنگی"
16181            | "ارسم_شبكة_ملونة"
16182            | "צייר_רשת_צבעונית"
16183            | "رنگین_میش_کھینچو" => {
16184                // ('วาดเมช' is taken by draw_mesh — use a distinct Thai alias)
16185                let id = self.arg_num(&args, 0, 0.)? as usize;
16186                let cx = self.arg_num(&args, 1, 0.)? as f32;
16187                let cy = self.arg_num(&args, 2, 0.)? as f32;
16188                let cz = self.arg_num(&args, 3, 0.)? as f32;
16189                let sc = self.arg_num(&args, 4, 1.)? as f32;
16190                let yaw = self.arg_num(&args, 5, 0.)? as f32;
16191                let sway = self.arg_num(&args, 6, 0.)? as f32;
16192                let arm = self.arg_num(&args, 7, 0.)? as f32;
16193                let lean = self.arg_num(&args, 8, 0.)? as f32;
16194                let leg = self.arg_num(&args, 9, 0.)? as f32;
16195                let tuck = self.arg_num(&args, 10, 0.)? as f32;
16196                if id < self.meshes.len() {
16197                    let m = &self.meshes[id];
16198                    let mut gfx = self.gfx.borrow_mut();
16199                    gfx.draw_color_mesh(m, cx, cy, cz, sc, yaw, sway, arm, lean, leg, tuck);
16200                }
16201                return Ok(Value::Unit);
16202            },
16203            #[cfg(target_arch = "wasm32")]
16204            "mesh_draw"
16205            | "วาดเมชสี"
16206            | "绘制网格"
16207            | "メッシュ描画"
16208            | "메시그리기"
16209            | "رسم_مش_رنگی"
16210            | "ارسم_شبكة_ملونة"
16211            | "צייר_רשת_צבעונית"
16212            | "رنگین_میش_کھینچو" => {
16213                return Ok(Value::Unit);
16214            },
16215
16216            // ── liquid sim (water + oil, immiscible) ──
16217            "liquid_new"
16218            | "新建液体"
16219            | "液体新規"
16220            | "액체생성"
16221            | "สร้างของเหลว"
16222            | "مایع_جدید"
16223            | "سائل_جديد"
16224            | "נוזל_חדש"
16225            | "نیا_مائع"
16226            | "nouveau_liquide"
16227            | "neue_flüssigkeit"
16228            | "новая_жидкость" => {
16229                let w = self.arg_num(&args, 0, 64.)? as usize;
16230                let h = self.arg_num(&args, 1, 64.)? as usize;
16231                let id = self.liquids.len();
16232                self.liquids
16233                    .push(ling_physics::liquid::LiquidGrid::new(w, h));
16234                return Ok(Value::Number(id as f64));
16235            },
16236            "liquid_set_colors"
16237            | "液体颜色"
16238            | "液体配色"
16239            | "액체색상"
16240            | "สีของเหลว"
16241            | "تنظیم_رنگ_مایع"
16242            | "عيّن_ألوان_السائل"
16243            | "קבע_צבעי_נוזל"
16244            | "مائع_رنگ_مقرر_کرو" => {
16245                let id = self.arg_num(&args, 0, 0.)? as usize;
16246                let wr = self.arg_num(&args, 1, 40.)? as f32;
16247                let wg = self.arg_num(&args, 2, 110.)? as f32;
16248                let wb = self.arg_num(&args, 3, 235.)? as f32;
16249                let or_ = self.arg_num(&args, 4, 240.)? as f32;
16250                let og = self.arg_num(&args, 5, 175.)? as f32;
16251                let ob = self.arg_num(&args, 6, 45.)? as f32;
16252                if let Some(g) = self.liquids.get_mut(id) {
16253                    g.set_colors(wr, wg, wb, or_, og, ob);
16254                }
16255                return Ok(Value::Unit);
16256            },
16257            "liquid_splat"
16258            | "液体注入"
16259            | "液体追加"
16260            | "액체분사"
16261            | "หยดของเหลว"
16262            | "پاشش_مایع"
16263            | "بقعة_سائل"
16264            | "התזת_נוזל"
16265            | "مائع_چھینٹا"
16266            | "éclaboussure_liquide"
16267            | "flüssigkeit_spritzer"
16268            | "брызги_жидкости" => {
16269                let id = self.arg_num(&args, 0, 0.)? as usize;
16270                let x = self.arg_num(&args, 1, 0.)? as f32;
16271                let y = self.arg_num(&args, 2, 0.)? as f32;
16272                let kind = self.arg_num(&args, 3, 0.)? as i32;
16273                let amt = self.arg_num(&args, 4, 1.0)? as f32;
16274                let rad = self.arg_num(&args, 5, 4.0)? as f32;
16275                if let Some(g) = self.liquids.get_mut(id) {
16276                    g.splat(x, y, kind, amt, rad);
16277                }
16278                return Ok(Value::Unit);
16279            },
16280            "liquid_gravity"
16281            | "液体重力"
16282            | "液体重力ベクトル"
16283            | "액체중력"
16284            | "แรงโน้มถ่วงเหลว"
16285            | "گرانش_مایع"
16286            | "جاذبية_السائل"
16287            | "כבידת_נוזל"
16288            | "مائع_کشش_ثقل"
16289            | "gravité_liquide"
16290            | "flüssigkeit_schwerkraft"
16291            | "гравитация_жидкости" => {
16292                let id = self.arg_num(&args, 0, 0.)? as usize;
16293                let gx = self.arg_num(&args, 1, 0.)? as f32;
16294                let gy = self.arg_num(&args, 2, 60.)? as f32;
16295                if let Some(g) = self.liquids.get_mut(id) {
16296                    g.set_gravity(gx, gy);
16297                }
16298                return Ok(Value::Unit);
16299            },
16300            "liquid_step"
16301            | "液体步进"
16302            | "液体更新"
16303            | "액체스텝"
16304            | "ก้าวของเหลว"
16305            | "گام_مایع"
16306            | "خطوة_السائل"
16307            | "צעד_נוזל"
16308            | "مائع_قدم"
16309            | "pas_liquide"
16310            | "flüssigkeit_schritt"
16311            | "шаг_жидкости" => {
16312                let id = self.arg_num(&args, 0, 0.)? as usize;
16313                let dt = self.arg_num(&args, 1, 0.016)? as f32;
16314                if let Some(g) = self.liquids.get_mut(id) {
16315                    g.step(dt);
16316                }
16317                return Ok(Value::Unit);
16318            },
16319            // liquid_step_all(dt) — advance EVERY liquid grid one tick, in parallel
16320            // across instances (rayon). Independent grids share no state, so this is
16321            // an embarrassingly-parallel batch: a scene with many liquid surfaces
16322            // steps in one call that scales across cores instead of N serial
16323            // `liquid_step` calls.
16324            "liquid_step_all"
16325            | "液体全步进"
16326            | "液体全更新"
16327            | "전체액체스텝"
16328            | "ก้าวของเหลวทั้งหมด"
16329            | "گام_همه_مایعات"
16330            | "خطوة_كل_السوائل"
16331            | "צעד_כל_הנוזלים"
16332            | "تمام_مائع_قدم" => {
16333                let dt = self.arg_num(&args, 0, 0.016)? as f32;
16334                ling_physics::liquid::step_all(&mut self.liquids, dt);
16335                return Ok(Value::Unit);
16336            },
16337            // liquid_rainbow(id, on) — colour the fluid as a flowing ROYGBIV marble
16338            "liquid_rainbow"
16339            | "液体彩虹"
16340            | "液体虹"
16341            | "액체무지개"
16342            | "ของเหลวสายรุ้ง"
16343            | "مایع_رنگین‌کمان"
16344            | "سائل_قوس_قزح"
16345            | "נוזל_קשת"
16346            | "قوس_قزح_مائع"
16347            | "arc_en_ciel_liquide"
16348            | "flüssigkeit_regenbogen"
16349            | "радуга_жидкости" => {
16350                let id = self.arg_num(&args, 0, 0.)? as usize;
16351                let on = self.arg_num(&args, 1, 1.0)? > 0.5;
16352                if let Some(g) = self.liquids.get_mut(id) {
16353                    g.rainbow = on;
16354                }
16355                return Ok(Value::Unit);
16356            },
16357            // liquid_mix(id) -> 0 (oil/water separated) .. 1 (fully intermixed)
16358            "liquid_mix"
16359            | "液体混合"
16360            | "液体混合度"
16361            | "액체혼합"
16362            | "การผสมของเหลว"
16363            | "ترکیب_مایع"
16364            | "مزج_سائل"
16365            | "ערבוב_נוזל"
16366            | "مائع_ملاؤ"
16367            | "mélanger_liquide"
16368            | "flüssigkeit_mischen"
16369            | "смешать_жидкость" => {
16370                let id = self.arg_num(&args, 0, 0.)? as usize;
16371                let m = self.liquids.get(id).map(|g| g.mix_amount()).unwrap_or(0.0);
16372                return Ok(Value::Number(m as f64));
16373            },
16374            // liquid_draw(id, sx, sy, scale) — fast flat 2-D blit of the colour field
16375            #[cfg(not(target_arch = "wasm32"))]
16376            "liquid_draw"
16377            | "绘制液体"
16378            | "液体描画"
16379            | "액체그리기"
16380            | "วาดของเหลว"
16381            | "رسم_مایع"
16382            | "ارسم_سائلا"
16383            | "צייר_נוזל"
16384            | "مائع_کھینچو"
16385            | "dessiner_liquide"
16386            | "flüssigkeit_zeichnen"
16387            | "рисовать_жидкость" => {
16388                let id = self.arg_num(&args, 0, 0.)? as usize;
16389                let sx = self.arg_num(&args, 1, 0.)? as i32;
16390                let sy = self.arg_num(&args, 2, 0.)? as i32;
16391                let scale = (self.arg_num(&args, 3, 4.)? as i32).max(1);
16392                if id < self.liquids.len() {
16393                    let (gw, gh) = {
16394                        let g = &self.liquids[id];
16395                        (g.w, g.h)
16396                    };
16397                    let mut gfx = self.gfx.borrow_mut();
16398                    let (w, h) = (gfx.width as i32, gfx.height as i32);
16399                    let g = &self.liquids[id];
16400                    for cy in 0..gh {
16401                        for cx in 0..gw {
16402                            let col = g.sample_rgb(cx, cy);
16403                            let bx = sx + cx as i32 * scale;
16404                            let by = sy + cy as i32 * scale;
16405                            for dy in 0..scale {
16406                                for dx in 0..scale {
16407                                    let px = bx + dx;
16408                                    let py = by + dy;
16409                                    if px >= 0 && py >= 0 && px < w && py < h {
16410                                        gfx.buffer[(py * w + px) as usize] = col;
16411                                    }
16412                                }
16413                            }
16414                        }
16415                    }
16416                }
16417                return Ok(Value::Unit);
16418            },
16419            // liquid_draw_surface(id, kind, cx,cy,cz, radius, height)
16420            //   kind: 0 plane · 1 sphere · 2 cylinder · 3 cone · 4 dome
16421            "liquid_draw_surface"
16422            | "液体贴面"
16423            | "液体曲面"
16424            | "액체곡면"
16425            | "ของเหลวบนพื้นผิว"
16426            | "رسم_سطح_مایع"
16427            | "ارسم_سطح_السائل"
16428            | "צייר_משטח_נוזל"
16429            | "مائع_سطح_کھینچو"
16430            | "dessiner_surface_liquide"
16431            | "flüssigkeit_oberfläche_zeichnen"
16432            | "рисовать_поверхность_жидкости" => {
16433                #[cfg(not(target_arch = "wasm32"))]
16434                {
16435                    let id = self.arg_num(&args, 0, 0.)? as usize;
16436                    let kind = self.arg_num(&args, 1, 1.)? as i32;
16437                    let cx = self.arg_num(&args, 2, 0.)? as f32;
16438                    let cy = self.arg_num(&args, 3, 0.)? as f32;
16439                    let cz = self.arg_num(&args, 4, 0.)? as f32;
16440                    let radius = self.arg_num(&args, 5, 2.0)? as f32;
16441                    let height = self.arg_num(&args, 6, 3.0)? as f32;
16442                    if id < self.liquids.len() {
16443                        let (gw, gh) = {
16444                            let g = &self.liquids[id];
16445                            (g.w, g.h)
16446                        };
16447                        let mut gfx = self.gfx.borrow_mut();
16448                        let (w, h, add) = (gfx.width, gfx.height, gfx.blend == 1);
16449                        let cam = gfx.camera.clone();
16450                        let near = -cam.zdist + 0.05;
16451                        let g = &self.liquids[id];
16452                        let tau = std::f32::consts::TAU;
16453                        let pi = std::f32::consts::PI;
16454                        // surface point for a (u,v) in [0,1] on the chosen primitive
16455                        let sp = |u: f32, v: f32| -> [f32; 3] {
16456                            if kind == 0 {
16457                                [
16458                                    cx + (u - 0.5) * 2.0 * radius,
16459                                    cy,
16460                                    cz + (v - 0.5) * 2.0 * radius,
16461                                ]
16462                            } else if kind == 2 {
16463                                let th = u * tau;
16464                                [
16465                                    cx + th.cos() * radius,
16466                                    cy + (v - 0.5) * height,
16467                                    cz + th.sin() * radius,
16468                                ]
16469                            } else if kind == 3 {
16470                                let th = u * tau;
16471                                let rr = radius * (1.0 - v);
16472                                [
16473                                    cx + th.cos() * rr,
16474                                    cy + (v - 0.5) * height,
16475                                    cz + th.sin() * rr,
16476                                ]
16477                            } else if kind == 4 {
16478                                let th = u * tau;
16479                                let ph = v * pi * 0.5;
16480                                [
16481                                    cx + ph.sin() * th.cos() * radius,
16482                                    cy - ph.cos() * radius,
16483                                    cz + ph.sin() * th.sin() * radius,
16484                                ]
16485                            } else {
16486                                let th = u * tau;
16487                                let ph = v * pi;
16488                                [
16489                                    cx + ph.sin() * th.cos() * radius,
16490                                    cy + ph.cos() * radius,
16491                                    cz + ph.sin() * th.sin() * radius,
16492                                ]
16493                            }
16494                        };
16495                        let nrm = |u: f32, v: f32| -> [f32; 3] {
16496                            if kind == 0 {
16497                                [0.0, -1.0, 0.0]
16498                            } else if kind == 2 {
16499                                let th = u * tau;
16500                                [th.cos(), 0.0, th.sin()]
16501                            } else if kind == 3 {
16502                                let th = u * tau;
16503                                let s = (radius / height.max(0.01)).atan();
16504                                [th.cos() * s.cos(), s.sin(), th.sin() * s.cos()]
16505                            } else if kind == 4 {
16506                                let th = u * tau;
16507                                let ph = v * pi * 0.5;
16508                                [ph.sin() * th.cos(), -ph.cos(), ph.sin() * th.sin()]
16509                            } else {
16510                                let th = u * tau;
16511                                let ph = v * pi;
16512                                [ph.sin() * th.cos(), ph.cos(), ph.sin() * th.sin()]
16513                            }
16514                        };
16515                        let gwf = gw as f32;
16516                        let ghf = gh as f32;
16517                        let mut cyc = 0usize;
16518                        while cyc < gh {
16519                            let mut cxc = 0usize;
16520                            while cxc < gw {
16521                                // cull by the cell centre's outward normal
16522                                let uc = (cxc as f32 + 0.5) / gwf;
16523                                let vc = (cyc as f32 + 0.5) / ghf;
16524                                let c = sp(uc, vc);
16525                                let n = nrm(uc, vc);
16526                                let dc = cam.depth(c[0], c[1], c[2]);
16527                                if dc > near {
16528                                    let cull = kind != 0
16529                                        && cam.depth(
16530                                            c[0] + n[0] * 0.06,
16531                                            c[1] + n[1] * 0.06,
16532                                            c[2] + n[2] * 0.06,
16533                                        ) > dc;
16534                                    if !cull {
16535                                        // project the 4 cell corners → a filled AA vector quad
16536                                        let u0 = cxc as f32 / gwf;
16537                                        let u1 = (cxc + 1) as f32 / gwf;
16538                                        let v0 = cyc as f32 / ghf;
16539                                        let v1 = (cyc + 1) as f32 / ghf;
16540                                        let q = [sp(u0, v0), sp(u1, v0), sp(u1, v1), sp(u0, v1)];
16541                                        let mut poly: Vec<[f32; 2]> = Vec::with_capacity(5);
16542                                        let mut ok = true;
16543                                        for p in &q {
16544                                            if cam.depth(p[0], p[1], p[2]) <= near {
16545                                                ok = false;
16546                                                break;
16547                                            }
16548                                            let (sx, sy, _) = cam.project(p[0], p[1], p[2]);
16549                                            poly.push([sx, sy]);
16550                                        }
16551                                        if ok {
16552                                            let p0 = poly[0];
16553                                            poly.push(p0);
16554                                            let col = g.sample_rgb(cxc, cyc);
16555                                            crate::gfx::raster::fill_contours_aa(
16556                                                &mut gfx.buffer,
16557                                                w,
16558                                                h,
16559                                                col,
16560                                                add,
16561                                                std::slice::from_ref(&poly),
16562                                            );
16563                                        }
16564                                    }
16565                                }
16566                                cxc += 1;
16567                            }
16568                            cyc += 1;
16569                        }
16570                    }
16571                }
16572                #[cfg(target_arch = "wasm32")]
16573                {
16574                    // WASM: liquid_draw_surface is a no-op for now (would need WebGL shader)
16575                    // The liquid simulation still runs, just not rendered to 3D surfaces
16576                }
16577                return Ok(Value::Unit);
16578            },
16579            // sparkle(x, y, w, h, count [, t]) — scatter twinkling vector star-sparkles
16580            // in a rect (snowglobe effect) in the current colour + blend mode.
16581            #[cfg(not(target_arch = "wasm32"))]
16582            "sparkle" | "闪光" | "きらめき" | "반짝임" | "ประกาย" | "درخشش" | "بريق" | "נצנוץ"
16583            | "چمک" | "scintillement" | "funkeln" | "искриться" => {
16584                let x = self.arg_num(&args, 0, 0.)? as f32;
16585                let y = self.arg_num(&args, 1, 0.)? as f32;
16586                let ww = self.arg_num(&args, 2, 200.)? as f32;
16587                let hh = self.arg_num(&args, 3, 200.)? as f32;
16588                let count = self.arg_num(&args, 4, 40.)? as i32;
16589                let t = self.arg_num(&args, 5, 0.)? as f32;
16590                let mut gfx = self.gfx.borrow_mut();
16591                let (w, h, add, color) = (gfx.width, gfx.height, gfx.blend == 1, gfx.color);
16592                let (cr, cg, cb) = (
16593                    (color >> 16 & 0xFF) as f32,
16594                    (color >> 8 & 0xFF) as f32,
16595                    (color & 0xFF) as f32,
16596                );
16597                let mut n = 0i32;
16598                while n < count {
16599                    let hsh = (n as u32).wrapping_mul(2654435761).wrapping_add(0x9E3779B9);
16600                    let u = ((hsh >> 8) & 1023) as f32 / 1023.0;
16601                    let v = ((hsh >> 18) & 1023) as f32 / 1023.0;
16602                    let phase = (hsh & 255) as f32 / 255.0;
16603                    let tw = (t * 3.0 + phase * std::f32::consts::TAU + n as f32).sin() * 0.5 + 0.5;
16604                    let sz = 1.5 + tw * 5.0;
16605                    let px = x + u * ww;
16606                    let py = y + v * hh;
16607                    let b = tw * tw; // sharp twinkle
16608                    let col =
16609                        (((cr * b) as u32) << 16) | (((cg * b) as u32) << 8) | ((cb * b) as u32);
16610                    crate::gfx::raster::draw_line_aa(
16611                        &mut gfx.buffer,
16612                        w,
16613                        h,
16614                        col,
16615                        add,
16616                        px - sz,
16617                        py,
16618                        px + sz,
16619                        py,
16620                    );
16621                    crate::gfx::raster::draw_line_aa(
16622                        &mut gfx.buffer,
16623                        w,
16624                        h,
16625                        col,
16626                        add,
16627                        px,
16628                        py - sz,
16629                        px,
16630                        py + sz,
16631                    );
16632                    let d = sz * 0.55;
16633                    crate::gfx::raster::draw_line_aa(
16634                        &mut gfx.buffer,
16635                        w,
16636                        h,
16637                        col,
16638                        add,
16639                        px - d,
16640                        py - d,
16641                        px + d,
16642                        py + d,
16643                    );
16644                    crate::gfx::raster::draw_line_aa(
16645                        &mut gfx.buffer,
16646                        w,
16647                        h,
16648                        col,
16649                        add,
16650                        px - d,
16651                        py + d,
16652                        px + d,
16653                        py - d,
16654                    );
16655                    n += 1;
16656                }
16657                return Ok(Value::Unit);
16658            },
16659
16660            // ══════════════════════════════════════════════════════════════════
16661            // DIALOG BUILTINS  (crates/ling-game/src/dialog.rs) — cinematic,
16662            // typed-out, colour-coded text boxes. Markup: {n}name{/} {p}place{/}
16663            // {i}item{/}, \n newline, || page break.
16664            // ══════════════════════════════════════════════════════════════════
16665            #[cfg(not(target_arch = "wasm32"))]
16666            "dialog_show"
16667            | "对话显示"
16668            | "会話表示"
16669            | "대화표시"
16670            | "แสดงบทสนทนา"
16671            | "نمایش_گفتگو"
16672            | "اعرض_الحوار"
16673            | "הצג_דיאלוג"
16674            | "مکالمہ_دکھاؤ"
16675            | "afficher_dialogue"
16676            | "dialog_anzeigen"
16677            | "показать_диалог" => {
16678                let text = self.arg_str(&args, 0, "");
16679                let cps = self.arg_num(&args, 1, 32.0)? as f32;
16680                self.dialog = Some(ling_game::dialog::Dialog::new(&text, cps));
16681                return Ok(Value::Unit);
16682            },
16683            #[cfg(not(target_arch = "wasm32"))]
16684            "dialog_step"
16685            | "对话步进"
16686            | "会話更新"
16687            | "대화스텝"
16688            | "ก้าวบทสนทนา"
16689            | "گام_گفتگو"
16690            | "خطوة_الحوار"
16691            | "צעד_דיאלוג"
16692            | "مکالمہ_قدم"
16693            | "pas_dialogue"
16694            | "dialog_schritt"
16695            | "шаг_диалога" => {
16696                let dt = self.arg_num(&args, 0, 0.016)? as f32;
16697                if let Some(d) = self.dialog.as_mut() {
16698                    d.update(dt);
16699                }
16700                return Ok(Value::Unit);
16701            },
16702            #[cfg(not(target_arch = "wasm32"))]
16703            "dialog_advance"
16704            | "对话推进"
16705            | "会話送り"
16706            | "대화진행"
16707            | "เลื่อนบทสนทนา"
16708            | "پیشروی_گفتگو"
16709            | "تقدّم_الحوار"
16710            | "קדם_דיאלוג"
16711            | "مکالمہ_آگے_بڑھاؤ"
16712            | "avancer_dialogue"
16713            | "dialog_weiter"
16714            | "продолжить_диалог" => {
16715                if let Some(d) = self.dialog.as_mut() {
16716                    d.advance();
16717                }
16718                return Ok(Value::Unit);
16719            },
16720            #[cfg(not(target_arch = "wasm32"))]
16721            "dialog_active"
16722            | "对话激活"
16723            | "会話中"
16724            | "대화중"
16725            | "บทสนทนาทำงาน"
16726            | "گفتگو_فعال"
16727            | "الحوار_نشط"
16728            | "דיאלוג_פעיל"
16729            | "مکالمہ_فعال"
16730            | "dialogue_actif"
16731            | "dialog_aktiv"
16732            | "диалог_активен" => {
16733                let a = self
16734                    .dialog
16735                    .as_ref()
16736                    .map(|d| !d.is_closed())
16737                    .unwrap_or(false);
16738                return Ok(Value::Bool(a));
16739            },
16740            #[cfg(not(target_arch = "wasm32"))]
16741            "dialog_typing"
16742            | "对话打字"
16743            | "会話タイプ中"
16744            | "대화타이핑"
16745            | "กำลังพิมพ์บทสนทนา"
16746            | "گفتگو_در_حال_تایپ"
16747            | "الحوار_يكتب"
16748            | "דיאלוג_מקליד"
16749            | "مکالمہ_ٹائپنگ"
16750            | "dialogue_frappe"
16751            | "dialog_tippen"
16752            | "диалог_печатает" => {
16753                use ling_game::dialog::Dialog;
16754
16755                let a = self
16756                    .dialog
16757                    .as_ref()
16758                    .map(|d: &Dialog| !d.is_closed() && d.is_typing())
16759                    .unwrap_or(false);
16760                return Ok(Value::Bool(a));
16761            },
16762            #[cfg(not(target_arch = "wasm32"))]
16763            "dialog_close"
16764            | "对话关闭"
16765            | "会話閉じる"
16766            | "대화닫기"
16767            | "ปิดบทสนทนา"
16768            | "بستن_گفتگو"
16769            | "أغلق_الحوار"
16770            | "סגור_דיאלוג"
16771            | "مکالمہ_بند"
16772            | "fermer_dialogue"
16773            | "dialog_schließen"
16774            | "закрыть_диалог" => {
16775                self.dialog = None;
16776                return Ok(Value::Unit);
16777            },
16778            // dialog_color(role, r, g, b) — role: 0 text · 1 name · 2 place · 3 item
16779            #[cfg(not(target_arch = "wasm32"))]
16780            "dialog_color"
16781            | "对话颜色"
16782            | "会話色"
16783            | "대화색"
16784            | "สีบทสนทนา"
16785            | "رنگ_گفتگو"
16786            | "لون_الحوار"
16787            | "צבע_דיאלוג"
16788            | "مکالمہ_رنگ"
16789            | "couleur_dialogue"
16790            | "dialog_farbe"
16791            | "цвет_диалога" => {
16792                let role = (self.arg_num(&args, 0, 0.0)? as usize).min(3);
16793                let r = self.arg_num(&args, 1, 255.0)? as u32 & 0xFF;
16794                let g = self.arg_num(&args, 2, 255.0)? as u32 & 0xFF;
16795                let b = self.arg_num(&args, 3, 255.0)? as u32 & 0xFF;
16796                self.dialog_colors[role] = (r << 16) | (g << 8) | b;
16797                return Ok(Value::Unit);
16798            },
16799            // dialog_draw(x, y, w, h [, font_handle]) — draw the box + typed text
16800            #[cfg(not(target_arch = "wasm32"))]
16801            "dialog_draw"
16802            | "对话绘制"
16803            | "会話描画"
16804            | "대화그리기"
16805            | "วาดบทสนทนา"
16806            | "رسم_گفتگو"
16807            | "ارسم_الحوار"
16808            | "צייר_דיאלוג"
16809            | "مکالمہ_کھینچو"
16810            | "dessiner_dialogue"
16811            | "dialog_zeichnen"
16812            | "рисовать_диалог" => {
16813                let x = self.arg_num(&args, 0, 40.0)? as f32;
16814                let y = self.arg_num(&args, 1, 0.0)? as f32;
16815                let ww = self.arg_num(&args, 2, 720.0)? as f32;
16816                let hh = self.arg_num(&args, 3, 150.0)? as f32;
16817                let font = self.arg_num(&args, 4, -1.0)? as i64;
16818                let t = (crate::runtime::now_secs() - self.start_time_secs) as f32;
16819                self.render_dialog(x, y, ww, hh, font, t);
16820                return Ok(Value::Unit);
16821            },
16822
16823            // text_poll() — fold newly-typed keys into the input buffer, return it.
16824            // Repeat is enabled (KeyRepeat::Yes) so holding a key/Backspace behaves
16825            // like a normal text field; length is capped so a stuck key or a runaway
16826            // script can't grow the buffer without bound.
16827            #[cfg(not(target_arch = "wasm32"))]
16828            "text_poll" => {
16829                const TEXT_BUFFER_MAX: usize = 240;
16830                // See key_down/key_pressed: our topmost fullscreen window can
16831                // be visually in front without real Win32 keyboard focus, so
16832                // WM_KEYDOWN/WM_CHAR (what minifb's get_keys_pressed reads)
16833                // never arrive. Poll the OS key-state table directly instead
16834                // — no focus required — with our own repeat-aware edge
16835                // detection (key_repeat_fire) so holding a key behaves like
16836                // the KeyRepeat::Yes path below: one char on press, then
16837                // repeats after a short hold delay.
16838                #[cfg(windows)]
16839                {
16840                    let topmost = self.gfx.borrow().topmost_window;
16841                    if topmost {
16842                        if !window_is_foreground(self.gfx.borrow().hwnd) {
16843                            return Ok(Value::Str(self.text_buffer.clone()));
16844                        }
16845                        let shift = os_key_down(VK_SHIFT);
16846                        let now = crate::runtime::now_secs();
16847                        let mut gfx = self.gfx.borrow_mut();
16848                        let back_idx = VK_BACK as usize;
16849                        let back_down = os_key_down(VK_BACK);
16850                        let back_was = gfx.raw_keys_prev[back_idx];
16851                        let (mut back_since, mut back_fire) = (
16852                            gfx.raw_keys_down_since[back_idx],
16853                            gfx.raw_keys_last_fire[back_idx],
16854                        );
16855                        if key_repeat_fire(
16856                            now,
16857                            back_down,
16858                            back_was,
16859                            &mut back_since,
16860                            &mut back_fire,
16861                        ) {
16862                            self.text_buffer.pop();
16863                        }
16864                        gfx.raw_keys_down_since[back_idx] = back_since;
16865                        gfx.raw_keys_last_fire[back_idx] = back_fire;
16866                        gfx.raw_keys_prev[back_idx] = back_down;
16867                        for &vk in TEXT_POLL_VKS {
16868                            let idx = (vk as usize) & 0xFF;
16869                            let down = os_key_down(vk);
16870                            let was = gfx.raw_keys_prev[idx];
16871                            let (mut since, mut fire) =
16872                                (gfx.raw_keys_down_since[idx], gfx.raw_keys_last_fire[idx]);
16873                            if key_repeat_fire(now, down, was, &mut since, &mut fire) {
16874                                if let Some(c) = vk_char(vk, shift) {
16875                                    if self.text_buffer.chars().count() < TEXT_BUFFER_MAX {
16876                                        self.text_buffer.push(c);
16877                                    }
16878                                }
16879                            }
16880                            gfx.raw_keys_down_since[idx] = since;
16881                            gfx.raw_keys_last_fire[idx] = fire;
16882                            gfx.raw_keys_prev[idx] = down;
16883                        }
16884                        return Ok(Value::Str(self.text_buffer.clone()));
16885                    }
16886                }
16887                let (keys, shift) = {
16888                    let gfx = self.gfx.borrow();
16889                    match gfx.window.as_ref() {
16890                        Some(w) => (
16891                            w.get_keys_pressed(minifb::KeyRepeat::Yes),
16892                            w.is_key_down(minifb::Key::LeftShift)
16893                                || w.is_key_down(minifb::Key::RightShift),
16894                        ),
16895                        None => (Vec::new(), false),
16896                    }
16897                };
16898                for k in keys {
16899                    if k == minifb::Key::Backspace {
16900                        self.text_buffer.pop();
16901                    } else if let Some(c) = key_char(k, shift) {
16902                        if self.text_buffer.chars().count() < TEXT_BUFFER_MAX {
16903                            self.text_buffer.push(c);
16904                        }
16905                    }
16906                }
16907                return Ok(Value::Str(self.text_buffer.clone()));
16908            },
16909            #[cfg(target_arch = "wasm32")]
16910            "text_poll" => {
16911                return Ok(Value::Str(self.text_buffer.clone()));
16912            },
16913            "text_get" => return Ok(Value::Str(self.text_buffer.clone())),
16914            "text_set" => {
16915                self.text_buffer = self.arg_str(&args, 0, "");
16916                return Ok(Value::Unit);
16917            },
16918            "text_clear" => {
16919                self.text_buffer.clear();
16920                return Ok(Value::Unit);
16921            },
16922            // record_frame() — append the current framebuffer as a PPM, return frame #
16923            #[cfg(not(target_arch = "wasm32"))]
16924            "record_frame" => {
16925                let n = self.record_n;
16926                let (buf, w, h) = {
16927                    let gfx = self.gfx.borrow();
16928                    (gfx.buffer.clone(), gfx.width, gfx.height)
16929                };
16930                let _ = std::fs::create_dir_all("recordings");
16931                let mut out = Vec::with_capacity(w * h * 3 + 32);
16932                out.extend_from_slice(format!("P6\n{w} {h}\n255\n").as_bytes());
16933                for px in &buf {
16934                    let p = *px;
16935                    out.push((p >> 16) as u8);
16936                    out.push((p >> 8) as u8);
16937                    out.push(p as u8);
16938                }
16939                let _ = std::fs::write(format!("recordings/frame_{n:05}.ppm"), out);
16940                self.record_n += 1;
16941                return Ok(Value::Number(n as f64));
16942            },
16943            "record_count" => return Ok(Value::Number(self.record_n as f64)),
16944            // ── screenshot(mode) → PNG in ./screenshots/ with timestamp + mode + size ──
16945            #[cfg(not(target_arch = "wasm32"))]
16946            "screenshot" | "บันทึกภาพ" | "عکس‌صفحه" | "لقطة_شاشة" | "צילום_מסך" | "اسکرین_شاٹ" =>
16947            {
16948                let mode = self.arg_str(&args, 0, "game");
16949                let (buf, w, h) = {
16950                    let gfx = self.gfx.borrow();
16951                    (gfx.buffer.clone(), gfx.width, gfx.height)
16952                };
16953                let _ = std::fs::create_dir_all("screenshots");
16954                let ts = std::time::SystemTime::now()
16955                    .duration_since(std::time::UNIX_EPOCH)
16956                    .map(|d| d.as_secs())
16957                    .unwrap_or(0);
16958                let safe: String = mode
16959                    .chars()
16960                    .map(|c| if c.is_alphanumeric() { c } else { '_' })
16961                    .collect();
16962                let path = format!("screenshots/ss_{ts}_{safe}_{w}x{h}.png");
16963                let mut rgb = Vec::with_capacity(w * h * 3);
16964                for px in &buf {
16965                    let p = *px;
16966                    rgb.push((p >> 16) as u8);
16967                    rgb.push((p >> 8) as u8);
16968                    rgb.push(p as u8);
16969                }
16970                if let Some(img) = image::RgbImage::from_raw(w as u32, h as u32, rgb) {
16971                    let _ = img.save(&path);
16972                }
16973                return Ok(Value::Str(path));
16974            },
16975            // ── microphone → crypto donut ──
16976            // mic_capture() — append the latest mic samples to the record buffer
16977            // (call each frame while recording). Returns the buffer length.
16978            #[cfg(not(target_arch = "wasm32"))]
16979            "mic_capture" => {
16980                if let Some(mic) = self.mic.as_ref() {
16981                    let s = mic.latest_samples();
16982                    self.mic_buffer.extend_from_slice(&s);
16983                    let cap = 96_000usize; // ~2 s @ 48 kHz
16984                    if self.mic_buffer.len() > cap {
16985                        let drop = self.mic_buffer.len() - cap;
16986                        self.mic_buffer.drain(0..drop);
16987                    }
16988                }
16989                return Ok(Value::Number(self.mic_buffer.len() as f64));
16990            },
16991            // mic_seed() — SHA3-256 hex of the recorded audio, usable as a donut seed
16992            #[cfg(not(target_arch = "wasm32"))]
16993            "mic_seed" => {
16994                let mut bytes = Vec::with_capacity(self.mic_buffer.len() * 4);
16995                for f in &self.mic_buffer {
16996                    bytes.extend_from_slice(&f.to_le_bytes());
16997                }
16998                return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(&bytes))));
16999            },
17000            #[cfg(not(target_arch = "wasm32"))]
17001            "mic_clear" => {
17002                self.mic_buffer.clear();
17003                return Ok(Value::Number(0.0));
17004            },
17005            // flush the 3-D depth queue onto the framebuffer WITHOUT presenting,
17006            // so 2-D UI drawn afterwards overlays the 3-D scene.
17007            #[cfg(not(target_arch = "wasm32"))]
17008            "flush_3d" | "render_3d" => {
17009                let mut gfx = self.gfx.borrow_mut();
17010                if !gfx.depth_queue.is_empty() {
17011                    let w = gfx.width;
17012                    let h = gfx.height;
17013                    let dt = gfx.depth_test;
17014                    let reset_z = gfx.zbuf_needs_clear;
17015                    let (bm, ba) = (gfx.blend, gfx.alpha);
17016                    let aa = gfx.antialias;
17017                    let queue = std::mem::take(&mut gfx.depth_queue);
17018                    {
17019                        let g = &mut *gfx;
17020                        let z = if dt { Some(&mut g.depth_buf) } else { None };
17021                        queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
17022                    }
17023                    gfx.zbuf_needs_clear = false;
17024                    gfx.depth_queue.set_state(bm, ba); // keep active blend/alpha across the mid-frame flush
17025                }
17026                return Ok(Value::Unit);
17027            },
17028            #[cfg(target_arch = "wasm32")]
17029            "flush_3d" | "render_3d" => {
17030                let mut gfx = self.gfx.borrow_mut();
17031                if !gfx.depth_queue.is_empty() {
17032                    let w = gfx.width;
17033                    let h = gfx.height;
17034                    let dt = gfx.depth_test;
17035                    let reset_z = gfx.zbuf_needs_clear;
17036                    let (bm, ba) = (gfx.blend, gfx.alpha);
17037                    let aa = gfx.antialias;
17038                    let queue = std::mem::take(&mut gfx.depth_queue);
17039                    {
17040                        let g = &mut *gfx;
17041                        let z = if dt { Some(&mut g.depth_buf) } else { None };
17042                        queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
17043                    }
17044                    gfx.zbuf_needs_clear = false;
17045                    gfx.depth_queue.set_state(bm, ba);
17046                }
17047                return Ok(Value::Unit);
17048            },
17049
17050            // flush_post() — flush the 3-D queue like `flush_3d`, then run the
17051            // toon post-chain (SSAO → outlines → tone ramp → bloom → FXAA) over
17052            // the SCENE immediately. `present` skips the chain this frame, so
17053            // 2-D UI drawn after this call stays exact — no bloom/blur on HUDs.
17054            "flush_post" | "post_now" | "포스트플러시" | "后期冲刷" => {
17055                let mut gfx = self.gfx.borrow_mut();
17056                if !gfx.depth_queue.is_empty() {
17057                    let w = gfx.width;
17058                    let h = gfx.height;
17059                    let dt = gfx.depth_test;
17060                    let reset_z = gfx.zbuf_needs_clear;
17061                    let (bm, ba) = (gfx.blend, gfx.alpha);
17062                    let aa = gfx.antialias;
17063                    let queue = std::mem::take(&mut gfx.depth_queue);
17064                    {
17065                        let g = &mut *gfx;
17066                        let z = if dt { Some(&mut g.depth_buf) } else { None };
17067                        queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
17068                    }
17069                    gfx.zbuf_needs_clear = false;
17070                    gfx.depth_queue.set_state(bm, ba);
17071                }
17072                gfx.toon_post_process();
17073                gfx.post_done = true;
17074                return Ok(Value::Unit);
17075            },
17076
17077            // Viscous full-screen distortion (warp/pucker/bloat, edge-wrapped). Call
17078            // after the 3-D flush and before the UI so only the world layer warps.
17079            #[cfg(not(target_arch = "wasm32"))]
17080            "screen_distort"
17081            | "บิดจอ"
17082            | "屏幕扭曲"
17083            | "画面歪み"
17084            | "화면왜곡"
17085            | "اعوجاج_صفحه"
17086            | "شوّه_الشاشة"
17087            | "עוות_מסך"
17088            | "اسکرین_ڈسٹورٹ" => {
17089                let amount = self.arg_num(&args, 0, 8.0)? as f32;
17090                let t = self.arg_num(&args, 1, 0.0)? as f32;
17091                // optional `step` (default 1 = full res): 2 = half-res block warp
17092                // (~4× fewer warp computes, slightly softer — suits a liquid look).
17093                let step = self.arg_num(&args, 2, 1.0)?.max(1.0) as usize;
17094                let _d = std::time::Instant::now();
17095                self.gfx.borrow_mut().distort(amount, t, step);
17096                ling_phase_add(phase::DISTORT, _d.elapsed().as_nanos());
17097                return Ok(Value::Unit);
17098            },
17099
17100            "set_rim"
17101            | "设置边缘光"
17102            | "リム設定"
17103            | "림라이트"
17104            | "ตั้งขอบเรือง"
17105            | "تنظیم_نور_لبه"
17106            | "عيّن_إضاءة_الحافة"
17107            | "קבע_תאורת_קצה"
17108            | "رم_لائٹ_مقرر_کرو"
17109            | "définir_contour_lumineux"
17110            | "rimlicht_setzen"
17111            | "задать_контурный_свет" => {
17112                let s = self.arg_num(&args, 0, 0.6)? as f32;
17113                let r = self.arg_num(&args, 1, 115.)? as f32 / 255.0;
17114                let g = self.arg_num(&args, 2, 217.)? as f32 / 255.0;
17115                let b = self.arg_num(&args, 3, 255.)? as f32 / 255.0;
17116                let mut gfx = self.gfx.borrow_mut();
17117                gfx.shade.rim = s;
17118                gfx.shade.rim_color = [r, g, b];
17119                return Ok(Value::Unit);
17120            },
17121
17122            // ══════════════════════════════════════════════════════════════════
17123            // 3-D PRIMITIVES  (src/gfx/shapes.rs)  — "Inkscape for 3-D"
17124            //   shape(cx,cy,cz,  sx,sy,sz,  rx,ry,rz,  mode,  e0,e1,e2)
17125            //     centre (cx,cy,cz), per-axis scale, Euler rotation (radians),
17126            //     mode: 0 filled · 1 wireframe · 2 both,
17127            //     e0..e2: shape-specific (segments / sides / ratio …).
17128            //   Pen colour (set_color) drives fill lighting and wireframe colour.
17129            // ══════════════════════════════════════════════════════════════════
17130            n if crate::gfx::shapes::canon(n).is_some() => {
17131                let kind = crate::gfx::shapes::canon(n).unwrap();
17132                let cx = self.arg_num(&args, 0, 0.)? as f32;
17133                let cy = self.arg_num(&args, 1, 0.)? as f32;
17134                let cz = self.arg_num(&args, 2, 0.)? as f32;
17135                let sx = self.arg_num(&args, 3, 1.)? as f32;
17136                let sy = self.arg_num(&args, 4, 1.)? as f32;
17137                let sz = self.arg_num(&args, 5, 1.)? as f32;
17138                let rx = self.arg_num(&args, 6, 0.)? as f32;
17139                let ry = self.arg_num(&args, 7, 0.)? as f32;
17140                let rz = self.arg_num(&args, 8, 0.)? as f32;
17141                let mode = self.arg_num(&args, 9, 0.)? as i32;
17142                let e0 = self.arg_num(&args, 10, 0.)? as f32;
17143                let e1 = self.arg_num(&args, 11, 0.)? as f32;
17144                let e2 = self.arg_num(&args, 12, 0.)? as f32;
17145                if let Some(mesh) = crate::gfx::shapes::build(
17146                    kind,
17147                    [cx, cy, cz, sx, sy, sz, rx, ry, rz],
17148                    e0,
17149                    e1,
17150                    e2,
17151                ) {
17152                    let mut gfx = self.gfx.borrow_mut();
17153                    gfx.emit_mesh(&mesh, mode);
17154                }
17155                return Ok(Value::Unit);
17156            },
17157
17158            _ => {},
17159        }
17160
17161        // `form` struct constructor: positional `Name(v0, v1, ...)`.
17162        if let Some(field_names) = self.structs.get(name).cloned() {
17163            if args.len() != field_names.len() {
17164                return Err(EvalErr::from(format!(
17165                    "{name} expects {} field(s), got {}",
17166                    field_names.len(),
17167                    args.len()
17168                )));
17169            }
17170            let fields = field_names.into_iter().zip(args).collect();
17171            return Ok(Value::Struct { name: name.to_string(), fields });
17172        }
17173
17174        // `choose` enum variant constructor: `Variant(...)` or `Enum::Variant(...)`.
17175        if let Some((enum_name, arity)) = self.enum_variants.get(name).cloned() {
17176            if args.len() != arity {
17177                return Err(EvalErr::from(format!(
17178                    "{name} expects {arity} value(s), got {}",
17179                    args.len()
17180                )));
17181            }
17182            let variant = name.rsplit("::").next().unwrap_or(name).to_string();
17183            return Ok(Value::Variant { enum_name, variant, payload: args });
17184        }
17185
17186        #[cfg(target_arch = "wasm32")]
17187        if let Some(v) = wasm_unsupported_builtin(name) {
17188            return Ok(v);
17189        }
17190
17191        Err(EvalErr::from(format!("unknown function '{name}'")))
17192    }
17193
17194    fn call_value(&mut self, v: Value, args: Vec<Value>) -> EvalResult {
17195        match v {
17196            Value::Fn(params, body, mut captured) => {
17197                for (p, a) in params.iter().zip(args) {
17198                    captured.insert(p.clone(), a);
17199                }
17200                match self.framed("<closure>", |me| me.exec_block(&body, &mut captured)) {
17201                    Ok(v) => Ok(v.unwrap_or(Value::Unit)),
17202                    Err(EvalErr::Return(v)) => Ok(v),
17203                    Err(e) => Err(e),
17204                }
17205            },
17206            other => Err(EvalErr::from(format!("cannot call {:?}", other))),
17207        }
17208    }
17209
17210    fn call_method(&self, recv: Value, method: &str, args: Vec<Value>) -> EvalResult {
17211        match (&recv, method) {
17212            (Value::Str(s), "is_empty" | "是空") => Ok(Value::Bool(s.is_empty())),
17213            // All of `lingfu normalize`'s per-language spellings of len/push
17214            // (see ling-fu normalize.rs alias table), not just the Chinese
17215            // ones — normalize rewrites method calls into whichever language
17216            // the project is normalized to, and any spelling missing here
17217            // makes those calls un-callable post-normalize (first hit with
17218            // `.长度()`, then again with Thai `.ความยาว()`).
17219            (Value::Str(s), "len" | "长" | "长度" | "長さ" | "길이" | "ความยาว") => {
17220                Ok(Value::Number(s.len() as f64))
17221            },
17222            (Value::Str(s), "to_string" | "转文") => Ok(Value::Str(s.clone())),
17223            (Value::Str(s), "contains" | "包含") => {
17224                if let Some(Value::Str(sub)) = args.first() {
17225                    Ok(Value::Bool(s.contains(sub.as_str())))
17226                } else {
17227                    Ok(Value::Bool(false))
17228                }
17229            },
17230            (Value::Str(s), "push_str" | "推_文") => {
17231                let mut s2 = s.clone();
17232                if let Some(Value::Str(a)) = args.first() {
17233                    s2.push_str(a);
17234                }
17235                Ok(Value::Str(s2))
17236            },
17237            (Value::List(v), "len" | "长" | "长度" | "長さ" | "길이" | "ความยาว") => {
17238                Ok(Value::Number(v.len() as f64))
17239            },
17240            (Value::List(v), "push" | "推" | "添加" | "追加" | "추가" | "เพิ่ม") =>
17241            {
17242                let mut v2: Vec<Value> = (**v).clone();
17243                if let Some(a) = args.first() {
17244                    v2.push(a.clone());
17245                }
17246                Ok(Value::List(Rc::new(v2)))
17247            },
17248            // `form` field access: `point.x` (no-arg method == field read).
17249            (Value::Struct { fields, .. }, _) if args.is_empty() => fields
17250                .iter()
17251                .find(|(k, _)| k == method)
17252                .map(|(_, v)| v.clone())
17253                .ok_or_else(|| EvalErr::from(format!("no field '{method}' on {recv}"))),
17254            // Enum introspection: `.tag` → variant name, `.is(Name)` not needed for now.
17255            (Value::Variant { variant, .. }, "tag" | "标签" | "タグ" | "태그" | "ป้าย")
17256                if args.is_empty() =>
17257            {
17258                Ok(Value::Str(variant.clone()))
17259            },
17260            (Value::Ok(inner), _) | (Value::Err(inner), _) => Ok(*inner.clone()),
17261            _ => Err(EvalErr::from(format!("no method '{method}' on {recv}"))),
17262        }
17263    }
17264
17265    // ─── Pattern matching ─────────────────────────────────────────────────────
17266
17267    fn match_pattern(&self, pat: &Pattern, val: &Value) -> Option<Env> {
17268        match (pat, val) {
17269            (Pattern::Wildcard, _) => Some(new_env()),
17270            (Pattern::Str(s), Value::Str(v)) if s == v => Some(new_env()),
17271            (Pattern::Number(n), Value::Number(v)) if (n - v).abs() < 1e-12 => Some(new_env()),
17272            (Pattern::Bool(b), Value::Bool(v)) if b == v => Some(new_env()),
17273            (Pattern::Ident(name), _) => {
17274                let mut e = new_env();
17275                e.insert(name.clone(), val.clone());
17276                Some(e)
17277            },
17278            (Pattern::Constructor(ctor, inner_pat), _) => {
17279                let (matches, inner_val) = match (ctor.as_str(), val) {
17280                    ("ok" | "好", Value::Ok(v)) => (true, Some(v.as_ref().clone())),
17281                    ("bad" | "坏", Value::Err(v)) => (true, Some(v.as_ref().clone())),
17282                    ("ok" | "好", v) if !matches!(v, Value::Err(_)) => (true, Some(v.clone())),
17283                    _ => (false, None),
17284                };
17285                if !matches {
17286                    return None;
17287                }
17288                match (inner_pat, inner_val) {
17289                    (Some(p), Some(v)) => self.match_pattern(p, &v),
17290                    (None, _) => Some(new_env()),
17291                    (Some(p), None) => self.match_pattern(p, &Value::Unit),
17292                }
17293            },
17294            // User enum variant pattern: `Circle(r)`, `Pair(a, b)`, nullary `Origin()`.
17295            (Pattern::Variant(vname, sub_pats), Value::Variant { variant, payload, .. }) => {
17296                if vname != variant || sub_pats.len() != payload.len() {
17297                    return None;
17298                }
17299                let mut bindings = new_env();
17300                for (p, v) in sub_pats.iter().zip(payload.iter()) {
17301                    bindings.extend(self.match_pattern(p, v)?);
17302                }
17303                Some(bindings)
17304            },
17305            // A zero-payload variant pattern also matches the bare result-style `ok`/`bad`
17306            // values so `Ok()`-style patterns keep working uniformly.
17307            (Pattern::Variant(vname, sub), Value::Ok(v)) if (vname == "ok" || vname == "好") => {
17308                match sub.as_slice() {
17309                    [] => Some(new_env()),
17310                    [p] => self.match_pattern(p, v),
17311                    _ => None,
17312                }
17313            },
17314            (Pattern::Variant(vname, sub), Value::Err(v))
17315                if (vname == "bad" || vname == "坏" || vname == "err") =>
17316            {
17317                match sub.as_slice() {
17318                    [] => Some(new_env()),
17319                    [p] => self.match_pattern(p, v),
17320                    _ => None,
17321                }
17322            },
17323            _ => None,
17324        }
17325    }
17326
17327    // ─── Utilities ───────────────────────────────────────────────────────────
17328
17329    fn value_to_iter(&self, val: Value) -> Result<Vec<Value>, EvalErr> {
17330        match val {
17331            Value::List(v) => Ok(Rc::try_unwrap(v).unwrap_or_else(|rc| (*rc).clone())),
17332            Value::Str(s) => Ok(s.chars().map(|c| Value::Str(c.to_string())).collect()),
17333            Value::Number(n) => Ok((0..n as i64).map(|i| Value::Number(i as f64)).collect()),
17334            other => Err(EvalErr::from(format!("cannot iterate over {:?}", other))),
17335        }
17336    }
17337
17338    pub(crate) fn is_truthy(&self, val: &Value) -> bool {
17339        match val {
17340            Value::Bool(b) => *b,
17341            Value::Unit => false,
17342            Value::Number(n) => *n != 0.0,
17343            Value::Str(s) => !s.is_empty(),
17344            Value::List(v) => !v.is_empty(),
17345            Value::Ok(_) => true,
17346            Value::Err(_) => false,
17347            Value::Fn(_, _, _) => true,
17348            Value::Struct { .. } => true,
17349            Value::Variant { .. } => true,
17350        }
17351    }
17352
17353    fn to_number(&self, val: &Value) -> Result<f64, EvalErr> {
17354        match val {
17355            Value::Number(n) => Ok(*n),
17356            Value::Str(s) => s
17357                .parse()
17358                .map_err(|_| EvalErr::from(format!("cannot convert '{s}' to number"))),
17359            other => Err(EvalErr::from(format!("expected number, got {:?}", other))),
17360        }
17361    }
17362
17363    /// Get the n-th argument as f64, falling back to `default` if missing.
17364    fn arg_num(&self, args: &[Value], n: usize, default: f64) -> Result<f64, EvalErr> {
17365        match args.get(n) {
17366            Some(v) => self.to_number(v),
17367            None => Ok(default),
17368        }
17369    }
17370
17371    fn arg_str(&self, args: &[Value], n: usize, default: &str) -> String {
17372        args.get(n)
17373            .map(|v| v.to_string())
17374            .unwrap_or_else(|| default.to_string())
17375    }
17376
17377    /// Read a list-of-strings argument as `Vec<String>` (empty if absent/not a list).
17378    /// Used by `run_process` for argv — never shell-interpreted, so no injection risk.
17379    fn arg_list_str(&self, args: &[Value], n: usize) -> Vec<String> {
17380        match args.get(n) {
17381            Some(Value::List(v)) => v.iter().map(|x| x.to_string()).collect(),
17382            _ => Vec::new(),
17383        }
17384    }
17385
17386    /// Read a list-of-numbers argument as `Vec<f32>` (empty if absent/not a list).
17387    #[allow(dead_code)]
17388    fn arg_list_f32(&self, args: &[Value], n: usize) -> Vec<f32> {
17389        match args.get(n) {
17390            Some(Value::List(v)) => v
17391                .iter()
17392                .filter_map(|x| match x {
17393                    Value::Number(n) => Some(*n as f32),
17394                    _ => None,
17395                })
17396                .collect(),
17397            _ => Vec::new(),
17398        }
17399    }
17400
17401    /// Optional `r,g,b` colour override starting at arg `i` → packed 0x00RRGGBB,
17402    /// or `default` if those three numeric args aren't present.
17403    #[cfg(not(target_arch = "wasm32"))]
17404    fn color_at(&self, args: &[Value], i: usize, default: u32) -> u32 {
17405        match (args.get(i), args.get(i + 1), args.get(i + 2)) {
17406            (Some(a), Some(b), Some(c)) => {
17407                match (self.to_number(a), self.to_number(b), self.to_number(c)) {
17408                    (Ok(r), Ok(g), Ok(bl)) => {
17409                        ((r as u32 & 0xFF) << 16) | ((g as u32 & 0xFF) << 8) | (bl as u32 & 0xFF)
17410                    },
17411                    _ => default,
17412                }
17413            },
17414            _ => default,
17415        }
17416    }
17417
17418    /// A pitch argument: a note-name string (`"C4"`, `"A#3"`) or a numeric MIDI value.
17419    #[cfg(not(target_arch = "wasm32"))]
17420    fn pitch_arg(&self, args: &[Value], i: usize, default: i32) -> i32 {
17421        match args.get(i) {
17422            Some(Value::Str(s)) => ling_music::note::parse_pitch(s).unwrap_or(default),
17423            Some(Value::Number(n)) => *n as i32,
17424            _ => default,
17425        }
17426    }
17427
17428    /// Current mouse position + left-button-down (native window only).
17429    #[cfg(not(target_arch = "wasm32"))]
17430    fn mouse_now(&self) -> (f32, f32, bool) {
17431        let gfx = self.gfx.borrow();
17432        let (mx, my) = gfx
17433            .window
17434            .as_ref()
17435            .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
17436            .unwrap_or((0.0, 0.0));
17437        let down = gfx
17438            .window
17439            .as_ref()
17440            .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
17441            .unwrap_or(false);
17442        (mx, my, down)
17443    }
17444
17445    /// Rasterize a UI [`ling_ui::widgets::Draw`] into the framebuffer: filled
17446    /// polygons via the AA scanline fill, polylines via AA lines, honouring the
17447    /// current blend mode.
17448    #[cfg(not(target_arch = "wasm32"))]
17449    fn draw_ui(&self, d: &ling_ui::widgets::Draw) {
17450        let mut gfx = self.gfx.borrow_mut();
17451        let (w, h, add) = (gfx.width, gfx.height, gfx.blend == 1);
17452        for (c, poly) in &d.fills {
17453            crate::gfx::raster::fill_contours_aa(
17454                &mut gfx.buffer,
17455                w,
17456                h,
17457                *c,
17458                add,
17459                std::slice::from_ref(poly),
17460            );
17461        }
17462        for (c, pl) in &d.strokes {
17463            for s in pl.windows(2) {
17464                crate::gfx::raster::draw_line_aa(
17465                    &mut gfx.buffer,
17466                    w,
17467                    h,
17468                    *c,
17469                    add,
17470                    s[0][0],
17471                    s[0][1],
17472                    s[1][0],
17473                    s[1][1],
17474                );
17475            }
17476        }
17477    }
17478
17479    /// Parse (dst_x, dst_y, width, height) from the first four args of a tex_* builtin.
17480    fn tex_rect(&self, args: &[Value]) -> Result<(usize, usize, usize, usize), EvalErr> {
17481        let tx = self.arg_num(args, 0, 0.0)? as usize;
17482        let ty = self.arg_num(args, 1, 0.0)? as usize;
17483        let tw = self.arg_num(args, 2, 256.0)? as usize;
17484        let th = self.arg_num(args, 3, 256.0)? as usize;
17485        Ok((tx, ty, tw.max(1), th.max(1)))
17486    }
17487
17488    pub(crate) fn apply_binop(&self, op: &BinOp, l: Value, r: Value) -> EvalResult {
17489        match op {
17490            BinOp::Add => match (l, r) {
17491                (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
17492                (Value::Str(a), Value::Str(b)) => Ok(Value::Str(a + &b)),
17493                (Value::Str(a), b) => Ok(Value::Str(a + &b.to_string())),
17494                (a, Value::Str(b)) => Ok(Value::Str(a.to_string() + &b)),
17495                (a, b) => Err(EvalErr::from(format!("cannot add {:?} and {:?}", a, b))),
17496            },
17497            BinOp::Sub => Ok(Value::Number(self.to_number(&l)? - self.to_number(&r)?)),
17498            BinOp::Mul => Ok(Value::Number(self.to_number(&l)? * self.to_number(&r)?)),
17499            BinOp::Div => Ok(Value::Number(self.to_number(&l)? / self.to_number(&r)?)),
17500            BinOp::Rem => Ok(Value::Number(self.to_number(&l)? % self.to_number(&r)?)),
17501            BinOp::Eq => Ok(Value::Bool(values_equal(&l, &r))),
17502            BinOp::Ne => Ok(Value::Bool(!values_equal(&l, &r))),
17503            BinOp::Lt => Ok(Value::Bool(self.to_number(&l)? < self.to_number(&r)?)),
17504            BinOp::Gt => Ok(Value::Bool(self.to_number(&l)? > self.to_number(&r)?)),
17505            BinOp::Le => Ok(Value::Bool(self.to_number(&l)? <= self.to_number(&r)?)),
17506            BinOp::Ge => Ok(Value::Bool(self.to_number(&l)? >= self.to_number(&r)?)),
17507            BinOp::And => Ok(Value::Bool(self.is_truthy(&l) && self.is_truthy(&r))),
17508            BinOp::Or => Ok(Value::Bool(self.is_truthy(&l) || self.is_truthy(&r))),
17509        }
17510    }
17511
17512    fn builtin_format(&self, args: &[Value]) -> Result<String, EvalErr> {
17513        if args.is_empty() {
17514            return Ok(String::new());
17515        }
17516        let fmt = match &args[0] {
17517            Value::Str(s) => s.clone(),
17518            other => return Ok(other.to_string()),
17519        };
17520
17521        let mut result = String::new();
17522        let mut arg_idx = 1usize;
17523        let mut chars = fmt.chars().peekable();
17524        while let Some(c) = chars.next() {
17525            if c == '{' {
17526                if chars.peek() == Some(&'}') {
17527                    chars.next();
17528                    if arg_idx < args.len() {
17529                        result.push_str(&args[arg_idx].to_string());
17530                        arg_idx += 1;
17531                    }
17532                } else {
17533                    let mut spec = String::new();
17534                    for ch in chars.by_ref() {
17535                        if ch == '}' {
17536                            break;
17537                        }
17538                        spec.push(ch);
17539                    }
17540                    if arg_idx < args.len() {
17541                        if let Some(suffix) = spec.strip_prefix(":.") {
17542                            if let Value::Number(n) = &args[arg_idx] {
17543                                let prec: usize = suffix.trim_end_matches('f').parse().unwrap_or(2);
17544                                result.push_str(&format!("{:.prec$}", n));
17545                                arg_idx += 1;
17546                                continue;
17547                            }
17548                        }
17549                        result.push_str(&args[arg_idx].to_string());
17550                        arg_idx += 1;
17551                    }
17552                }
17553            } else {
17554                result.push(c);
17555            }
17556        }
17557        Ok(result)
17558    }
17559}
17560
17561#[cfg(not(target_arch = "wasm32"))]
17562/// Map a friendly button name (any vendor / d-pad alias) to a gamepad button.
17563#[cfg(not(target_arch = "wasm32"))]
17564fn parse_pad_button(name: &str) -> Option<ling_input::GamepadButton> {
17565    use ling_input::GamepadButton as B;
17566    Some(match name.to_ascii_lowercase().as_str() {
17567        "a" | "south" | "cross" => B::South,
17568        "b" | "east" | "circle" => B::East,
17569        "x" | "west" | "square" => B::West,
17570        "y" | "north" | "triangle" => B::North,
17571        "lb" | "l1" | "left_shoulder" => B::LeftShoulder,
17572        "rb" | "r1" | "right_shoulder" => B::RightShoulder,
17573        "lt" | "l2" | "left_trigger" => B::LeftTrigger,
17574        "rt" | "r2" | "right_trigger" => B::RightTrigger,
17575        "start" | "menu" | "options" | "démarrer" | "начать" => B::Start,
17576        "select" | "back" | "share" | "view" => B::Select,
17577        "guide" | "home" => B::Guide,
17578        "l3" | "left_stick" => B::LeftStick,
17579        "r3" | "right_stick" => B::RightStick,
17580        "up" | "dpad_up" => B::DpadUp,
17581        "down" | "dpad_down" => B::DpadDown,
17582        "left" | "dpad_left" => B::DpadLeft,
17583        "right" | "dpad_right" => B::DpadRight,
17584        _ => return None,
17585    })
17586}
17587
17588#[cfg(not(target_arch = "wasm32"))]
17589fn str_to_minifb_key(name: &str) -> Option<minifb::Key> {
17590    use minifb::Key;
17591    Some(match name {
17592        "numpad0" | "kp0" => Key::NumPad0,
17593        "numpad1" | "kp1" => Key::NumPad1,
17594        "numpad2" | "kp2" => Key::NumPad2,
17595        "numpad3" | "kp3" => Key::NumPad3,
17596        "numpad4" | "kp4" => Key::NumPad4,
17597        "numpad5" | "kp5" => Key::NumPad5,
17598        "numpad6" | "kp6" => Key::NumPad6,
17599        "numpad7" | "kp7" => Key::NumPad7,
17600        "numpad8" | "kp8" => Key::NumPad8,
17601        "numpad9" | "kp9" => Key::NumPad9,
17602        "numpad+" | "kp+" => Key::NumPadPlus,
17603        "numpad-" | "kp-" => Key::NumPadMinus,
17604        "numpad*" | "kp*" => Key::NumPadAsterisk,
17605        "numpad/" | "kp/" => Key::NumPadSlash,
17606        "left" => Key::Left,
17607        "right" => Key::Right,
17608        "up" => Key::Up,
17609        "down" => Key::Down,
17610        "space" => Key::Space,
17611        "enter" => Key::Enter,
17612        "escape" => Key::Escape,
17613        "pageup" => Key::PageUp,
17614        "pagedown" => Key::PageDown,
17615        "lshift" | "leftshift" => Key::LeftShift,
17616        "rshift" | "rightshift" => Key::RightShift,
17617        "lctrl" | "leftctrl" => Key::LeftCtrl,
17618        "rctrl" | "rightctrl" => Key::RightCtrl,
17619        "lalt" | "leftalt" => Key::LeftAlt,
17620        "ralt" | "rightalt" => Key::RightAlt,
17621        "tab" => Key::Tab,
17622        "backspace" => Key::Backspace,
17623        "delete" => Key::Delete,
17624        "insert" => Key::Insert,
17625        "home" => Key::Home,
17626        "end" => Key::End,
17627        "a" => Key::A,
17628        "b" => Key::B,
17629        "c" => Key::C,
17630        "d" => Key::D,
17631        "e" => Key::E,
17632        "f" => Key::F,
17633        "g" => Key::G,
17634        "h" => Key::H,
17635        "i" => Key::I,
17636        "j" => Key::J,
17637        "k" => Key::K,
17638        "l" => Key::L,
17639        "m" => Key::M,
17640        "n" => Key::N,
17641        "o" => Key::O,
17642        "p" => Key::P,
17643        "q" => Key::Q,
17644        "r" => Key::R,
17645        "s" => Key::S,
17646        "t" => Key::T,
17647        "u" => Key::U,
17648        "v" => Key::V,
17649        "w" => Key::W,
17650        "x" => Key::X,
17651        "y" => Key::Y,
17652        "z" => Key::Z,
17653        "0" => Key::Key0,
17654        "1" => Key::Key1,
17655        "2" => Key::Key2,
17656        "3" => Key::Key3,
17657        "4" => Key::Key4,
17658        "5" => Key::Key5,
17659        "6" => Key::Key6,
17660        "7" => Key::Key7,
17661        "8" => Key::Key8,
17662        "9" => Key::Key9,
17663        _ => return None,
17664    })
17665}
17666
17667pub(crate) fn values_equal(a: &Value, b: &Value) -> bool {
17668    match (a, b) {
17669        (Value::Number(x), Value::Number(y)) => (x - y).abs() < 1e-12,
17670        (Value::Str(x), Value::Str(y)) => x == y,
17671        (Value::Bool(x), Value::Bool(y)) => x == y,
17672        (Value::Unit, Value::Unit) => true,
17673        _ => false,
17674    }
17675}
17676
17677// Rasteriser functions live in crate::gfx::raster — imported at top of file.
17678
17679// ── Window platform helpers ────────────────────────────────────────────────────
17680
17681/// Strip *all* window chrome from `hwnd` and make it cover the whole primary
17682/// monitor (0,0 → screen_w × screen_h), above the taskbar. This turns the
17683/// minifb window into a true borderless-fullscreen surface: no title bar, no
17684/// frame, no resize grips — there is no visible window "handle" left.
17685#[cfg(all(not(target_arch = "wasm32"), windows))]
17686fn make_borderless_fullscreen(hwnd: isize, screen_w: i32, screen_h: i32) {
17687    if hwnd == 0 {
17688        return;
17689    }
17690    unsafe {
17691        extern "system" {
17692            fn SetWindowLongPtrW(hwnd: isize, index: i32, new: isize) -> isize;
17693            fn SetWindowPos(
17694                hwnd: isize,
17695                insert_after: isize,
17696                x: i32,
17697                y: i32,
17698                cx: i32,
17699                cy: i32,
17700                flags: u32,
17701            ) -> i32;
17702            fn ShowWindow(hwnd: isize, cmd: i32) -> i32;
17703        }
17704        const GWL_STYLE: i32 = -16;
17705        const GWL_EXSTYLE: i32 = -20;
17706        // WS_POPUP (0x80000000) | WS_VISIBLE (0x10000000) — a bare top-level
17707        // window with no caption, border, or system menu.
17708        SetWindowLongPtrW(hwnd, GWL_STYLE, 0x9000_0000isize);
17709        // Clear extended edges (WS_EX_WINDOWEDGE / CLIENTEDGE / DLGMODALFRAME).
17710        SetWindowLongPtrW(hwnd, GWL_EXSTYLE, 0);
17711        // HWND_TOPMOST = -1; SWP_FRAMECHANGED (0x0020) | SWP_SHOWWINDOW (0x0040).
17712        SetWindowPos(hwnd, -1isize, 0, 0, screen_w, screen_h, 0x0020 | 0x0040);
17713        ShowWindow(hwnd, 3); // SW_MAXIMIZE-equivalent paint; 3 = SW_SHOWMAXIMIZED
17714    }
17715}
17716
17717/// Force real OS keyboard focus onto `hwnd`, not just Z-order prominence.
17718/// Windows' foreground-lock can leave a freshly-created window topmost — so
17719/// VISUALLY it covers everything — without actually handing it keyboard
17720/// focus, e.g. when launched from a terminal that still holds real focus:
17721/// clicks can nudge focus over (a more "user-driven" event) but typed keys
17722/// silently keep going to whatever app really has it, which looks exactly
17723/// like "clicking a text field doesn't focus it". AttachThreadInput is the
17724/// standard documented workaround — it lets SetForegroundWindow succeed even
17725/// under the lock by sharing input state with whichever thread currently
17726/// owns the foreground window. Call this LAST, after every other
17727/// window-visibility change for this launch (anything that shows/hides a
17728/// window afterward — e.g. hiding the launching console — can itself
17729/// reassign the foreground window and undo an earlier focus claim).
17730#[cfg(all(not(target_arch = "wasm32"), windows))]
17731fn force_window_focus(hwnd: isize) {
17732    if hwnd == 0 {
17733        return;
17734    }
17735    unsafe {
17736        extern "system" {
17737            fn GetForegroundWindow() -> isize;
17738            fn GetWindowThreadProcessId(hwnd: isize, pid: *mut u32) -> u32;
17739            fn GetCurrentThreadId() -> u32;
17740            fn AttachThreadInput(id_attach: u32, id_attach_to: u32, attach: i32) -> i32;
17741            fn SetForegroundWindow(hwnd: isize) -> i32;
17742            fn BringWindowToTop(hwnd: isize) -> i32;
17743            fn SetFocus(hwnd: isize) -> isize;
17744            fn SetActiveWindow(hwnd: isize) -> isize;
17745        }
17746        let fg = GetForegroundWindow();
17747        if fg != 0 && fg != hwnd {
17748            let mut fg_pid: u32 = 0;
17749            let fg_tid = GetWindowThreadProcessId(fg, &mut fg_pid);
17750            let my_tid = GetCurrentThreadId();
17751            if fg_tid != 0 && fg_tid != my_tid {
17752                AttachThreadInput(my_tid, fg_tid, 1);
17753                SetForegroundWindow(hwnd);
17754                BringWindowToTop(hwnd);
17755                SetFocus(hwnd);
17756                SetActiveWindow(hwnd);
17757                AttachThreadInput(my_tid, fg_tid, 0);
17758                return;
17759            }
17760        }
17761        SetForegroundWindow(hwnd);
17762        BringWindowToTop(hwnd);
17763        SetFocus(hwnd);
17764        SetActiveWindow(hwnd);
17765    }
17766}
17767
17768/// Toggle `hwnd`'s HWND_TOPMOST z-order style without moving/resizing/
17769/// activating it — used to drop the borderless-fullscreen window's topmost
17770/// flag on alt-tab (so it stops covering whatever the user switched to) and
17771/// restore it when the user switches back.
17772#[cfg(all(not(target_arch = "wasm32"), windows))]
17773fn set_window_topmost(hwnd: isize, topmost: bool) {
17774    if hwnd == 0 {
17775        return;
17776    }
17777    unsafe {
17778        extern "system" {
17779            fn SetWindowPos(
17780                hwnd: isize,
17781                insert_after: isize,
17782                x: i32,
17783                y: i32,
17784                cx: i32,
17785                cy: i32,
17786                flags: u32,
17787            ) -> i32;
17788        }
17789        let insert_after: isize = if topmost { -1 } else { -2 }; // HWND_TOPMOST / HWND_NOTOPMOST
17790                                                                 // SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE — pure z-order change,
17791                                                                 // must not steal focus back when restoring topmost on refocus.
17792        SetWindowPos(hwnd, insert_after, 0, 0, 0, 0, 0x0002 | 0x0001 | 0x0010);
17793    }
17794}
17795
17796/// Pace `win` to `vsync`'s target rate. `LING_FPS_CAP` (0 = uncapped, else an
17797/// explicit fps) always overrides; otherwise vsync-on paces to the monitor's
17798/// real refresh rate and vsync-off runs uncapped. minifb has no swap-interval
17799/// vsync (it owns no GPU present queue), so this is frame-rate pacing to the
17800/// refresh rate, not a tear-free guarantee.
17801#[cfg(not(target_arch = "wasm32"))]
17802fn apply_frame_pacing(win: &mut minifb::Window, vsync: bool) {
17803    match std::env::var("LING_FPS_CAP")
17804        .ok()
17805        .and_then(|v| v.parse::<usize>().ok())
17806    {
17807        Some(0) => win.set_target_fps(100_000),
17808        Some(cap) => win.set_target_fps(cap),
17809        None if vsync => win.set_target_fps(monitor_info().2.max(30) as usize),
17810        None => win.set_target_fps(100_000),
17811    }
17812}
17813
17814/// Primary-monitor resolution and refresh rate as `(width, height, hz)`.
17815/// `hz` falls back to 60 when the driver reports an unknown/`default` rate.
17816#[cfg(all(not(target_arch = "wasm32"), windows))]
17817fn monitor_info() -> (i32, i32, i32) {
17818    unsafe {
17819        extern "system" {
17820            fn GetSystemMetrics(index: i32) -> i32;
17821            fn GetDC(hwnd: isize) -> isize;
17822            fn ReleaseDC(hwnd: isize, hdc: isize) -> i32;
17823            fn GetDeviceCaps(hdc: isize, index: i32) -> i32;
17824        }
17825        let w = GetSystemMetrics(0).max(1); // SM_CXSCREEN
17826        let h = GetSystemMetrics(1).max(1); // SM_CYSCREEN
17827        let hdc = GetDC(0);
17828        let mut hz = if hdc != 0 { GetDeviceCaps(hdc, 116) } else { 0 }; // VREFRESH
17829        if hdc != 0 {
17830            ReleaseDC(0, hdc);
17831        }
17832        if hz <= 1 {
17833            hz = 60; // 0 or 1 means "device default" → assume 60 Hz
17834        }
17835        (w, h, hz)
17836    }
17837}
17838
17839/// Non-Windows native fallback: resolution from [`native_screen_size`]; refresh
17840/// from the active X11/RandR mode (so a 144 Hz panel drives the loop at 144),
17841/// falling back to 60 Hz when it can't be detected (Wayland, headless, macOS).
17842#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
17843fn monitor_info() -> (i32, i32, i32) {
17844    let (w, h) = native_screen_size();
17845    (w as i32, h as i32, linux_refresh_hz().unwrap_or(60))
17846}
17847
17848/// Active display refresh rate via `xrandr`. Each connected output's active
17849/// mode is the token flagged with `*` (e.g. `1920x1080 144.00*+`); we take the
17850/// max across all active outputs so a multi-monitor rig drives the loop at
17851/// its fastest panel.
17852#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
17853fn linux_refresh_hz() -> Option<i32> {
17854    let out = std::process::Command::new("xrandr")
17855        .arg("--current")
17856        .output()
17857        .ok()?;
17858    if !out.status.success() {
17859        return None;
17860    }
17861    parse_xrandr_max_hz(&String::from_utf8_lossy(&out.stdout))
17862}
17863
17864/// Pure parse used by [`linux_refresh_hz`]: the highest `*`-flagged refresh
17865/// rate across all active outputs in `xrandr --current` output.
17866#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
17867fn parse_xrandr_max_hz(text: &str) -> Option<i32> {
17868    text.split_whitespace()
17869        .filter(|tok| tok.contains('*'))
17870        .filter_map(|tok| {
17871            tok.trim_matches(|c: char| !c.is_ascii_digit() && c != '.')
17872                .parse::<f64>()
17873                .ok()
17874        })
17875        .map(|hz| hz.round() as i32)
17876        .filter(|&hz| (24..=1000).contains(&hz))
17877        .max()
17878}
17879
17880/// WASM fallback: the canvas is the display surface; assume 60 Hz.
17881#[cfg(target_arch = "wasm32")]
17882fn monitor_info() -> (i32, i32, i32) {
17883    let (w, h) = crate::gfx::webgl::canvas_size();
17884    (w as i32, h as i32, 60)
17885}
17886
17887#[cfg(all(test, not(target_arch = "wasm32"), not(windows)))]
17888mod xrandr_tests {
17889    use super::parse_xrandr_max_hz;
17890
17891    #[test]
17892    fn picks_highest_active_output() {
17893        let text = "\
17894eDP-1 connected primary 1920x1080+0+0
17895   1920x1080     60.00*+  59.94
17896DP-1 connected 2560x1440+1920+0
17897   2560x1440    144.00*+  120.00  60.00
17898";
17899        assert_eq!(parse_xrandr_max_hz(text), Some(144));
17900    }
17901
17902    #[test]
17903    fn single_output() {
17904        let text = "   1920x1080     75.00*+  60.00\n";
17905        assert_eq!(parse_xrandr_max_hz(text), Some(75));
17906    }
17907
17908    #[test]
17909    fn no_active_mode_returns_none() {
17910        let text = "eDP-1 disconnected\n";
17911        assert_eq!(parse_xrandr_max_hz(text), None);
17912    }
17913
17914    #[test]
17915    fn out_of_range_hz_filtered() {
17916        let text = "   1x1     5000.00*+\n";
17917        assert_eq!(parse_xrandr_max_hz(text), None);
17918    }
17919}
17920
17921/// Query the primary display resolution on non-Windows platforms.
17922/// Falls back to 1920×1080 if the size cannot be determined.
17923#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
17924fn native_screen_size() -> (f64, f64) {
17925    // On Linux/macOS we don't have an easy dependency-free call; return a
17926    // sensible default. Callers can always pass explicit dimensions.
17927    (1920.0, 1080.0)
17928}
17929
17930// ════════════════════════════════════════════════════════════════════════════
17931// Builtin call profiler  (env-gated, near-zero cost when off)
17932//
17933//   LING_PROFILE=1            enable per-builtin call-count + inclusive-time tally
17934//   LING_PROFILE_EVERY=N      print the report every N frames (default 240)
17935//
17936// Every builtin call funnels through `Interp::call_named` (JIT via `ling_builtin`,
17937// tree-walker directly), so this captures the full render/physics/audio builtin
17938// hot-path. Report is sorted by total time and prints calls, calls/frame,
17939// total_ms and ms/frame — the top-down "what's making so many calls" view.
17940// ════════════════════════════════════════════════════════════════════════════
17941struct LingProfileState {
17942    enabled: bool,
17943    every: u64,
17944    frames: u64,
17945    calls: std::collections::HashMap<String, (u64, u128)>, // name -> (count, nanos)
17946}
17947
17948thread_local! {
17949    static LING_PROFILE: std::cell::RefCell<LingProfileState> = std::cell::RefCell::new({
17950        let enabled = std::env::var("LING_PROFILE").map(|v| v != "0" && !v.is_empty()).unwrap_or(false);
17951        let every = std::env::var("LING_PROFILE_EVERY").ok()
17952            .and_then(|v| v.parse::<u64>().ok()).filter(|&n| n > 0).unwrap_or(240);
17953        if enabled {
17954            eprintln!("[ling-profile] ON — report every {every} frames (set LING_PROFILE_EVERY to change)");
17955        }
17956        LingProfileState { enabled, every, frames: 0, calls: std::collections::HashMap::new() }
17957    });
17958}
17959
17960#[inline]
17961fn ling_profile_enabled() -> bool {
17962    LING_PROFILE.with(|p| p.borrow().enabled)
17963}
17964
17965thread_local! {
17966    static LING_FPS: std::cell::RefCell<(bool, f64, u32, f64)> = std::cell::RefCell::new(
17967        (std::env::var("LING_FPS").map(|v| v != "0" && !v.is_empty()).unwrap_or(false), 0.0, 0, 0.0)
17968    );
17969}
17970
17971#[cfg(not(target_arch = "wasm32"))]
17972fn ling_fps_tick() {
17973    LING_FPS.with(|s| {
17974        let mut s = s.borrow_mut();
17975        if !s.0 {
17976            return;
17977        }
17978        let now = crate::runtime::now_secs();
17979        if s.1 > 0.0 {
17980            s.3 += now - s.1;
17981            s.2 += 1;
17982            if s.2 >= 120 {
17983                let avg = s.3 / s.2 as f64;
17984                eprintln!(
17985                    "[fps] {:.1} fps  ({:.2} ms/frame, wall, {} frames)",
17986                    1.0 / avg,
17987                    avg * 1000.0,
17988                    s.2
17989                );
17990                s.2 = 0;
17991                s.3 = 0.0;
17992            }
17993        }
17994        s.1 = now;
17995    });
17996}
17997
17998// Coarse render-pipeline timers (set LING_PHASE=1). Each accumulates wall-time
17999// per frame at flush/present granularity, so the cost is negligible. Reports the
18000// software-rasteriser breakdown the builtin profiler can't separate (the work is
18001// all inside the `present`/`flush_3d` builtins).
18002thread_local! {
18003    static LING_PHASE: std::cell::RefCell<(bool, u64, [u128; 5])> = std::cell::RefCell::new(
18004        (std::env::var_os("LING_PHASE").is_some(), 0, [0; 5])
18005    );
18006}
18007
18008/// Phase indices for [`ling_phase_add`].
18009pub mod phase {
18010    pub const FLUSH: usize = 0;
18011    pub const TOON: usize = 1;
18012    pub const BLIT: usize = 2;
18013    pub const DISTORT: usize = 3;
18014    pub const SORT: usize = 4;
18015}
18016
18017#[cfg(not(target_arch = "wasm32"))]
18018#[inline]
18019pub fn ling_phase_add(idx: usize, nanos: u128) {
18020    LING_PHASE.with(|p| {
18021        let mut p = p.borrow_mut();
18022        if p.0 {
18023            p.2[idx] += nanos;
18024        }
18025    });
18026}
18027
18028#[cfg(not(target_arch = "wasm32"))]
18029fn ling_phase_frame() {
18030    LING_PHASE.with(|p| {
18031        let mut p = p.borrow_mut();
18032        if !p.0 {
18033            return;
18034        }
18035        p.1 += 1;
18036        if p.1 >= 120 {
18037            let f = p.1 as f64;
18038            let ms = |i: usize| p.2[i] as f64 / 1e6 / f;
18039            eprintln!(
18040                "[phase] sort={:.2} flush={:.2} toon={:.2} blit={:.2} distort={:.2} ms/frame",
18041                ms(phase::SORT),
18042                ms(phase::FLUSH),
18043                ms(phase::TOON),
18044                ms(phase::BLIT),
18045                ms(phase::DISTORT)
18046            );
18047            p.1 = 0;
18048            p.2 = [0; 5];
18049        }
18050    });
18051}
18052
18053fn ling_profile_record(name: &str, nanos: u128) {
18054    // Frame boundary = a present() call.
18055    let is_frame = matches!(
18056        name,
18057        "present" | "แสดงผล" | "gfx_present" | "show" | "显" | "呈现" | "表示" | "표시"
18058    );
18059    LING_PROFILE.with(|p| {
18060        let mut p = p.borrow_mut();
18061        let e = p.calls.entry(name.to_string()).or_insert((0, 0));
18062        e.0 += 1;
18063        e.1 += nanos;
18064        if is_frame {
18065            p.frames += 1;
18066            if p.frames % p.every == 0 {
18067                ling_profile_print(&p);
18068            }
18069        }
18070    });
18071}
18072
18073fn ling_profile_print(p: &LingProfileState) {
18074    let mut rows: Vec<(&String, u64, u128)> =
18075        p.calls.iter().map(|(n, (c, ns))| (n, *c, *ns)).collect();
18076    use std::cmp::Reverse;
18077    rows.sort_by_key(|x| Reverse(x.2)); // by total time desc
18078    let total_ns: u128 = p.calls.values().map(|(_, ns)| *ns).sum();
18079    let total_calls: u64 = p.calls.values().map(|(c, _)| *c).sum();
18080    let fr = p.frames.max(1) as f64;
18081    eprintln!(
18082        "\n┌─ LING PROFILE ── frames={} ─ builtin calls by total inclusive time ─────────────",
18083        p.frames
18084    );
18085    eprintln!(
18086        "│ {:<24} {:>9} {:>9} {:>10} {:>9} {:>6}",
18087        "builtin", "calls", "calls/fr", "total_ms", "ms/frame", "%time"
18088    );
18089    eprintln!("├──────────────────────────────────────────────────────────────────────────────");
18090    for (name, count, ns) in rows.iter().take(30) {
18091        let ms = *ns as f64 / 1e6;
18092        let pct = if total_ns > 0 {
18093            *ns as f64 / total_ns as f64 * 100.0
18094        } else {
18095            0.0
18096        };
18097        eprintln!(
18098            "│ {:<24} {:>9} {:>9.1} {:>10.1} {:>9.3} {:>5.1}%",
18099            truncate_name(name),
18100            count,
18101            *count as f64 / fr,
18102            ms,
18103            ms / fr,
18104            pct
18105        );
18106    }
18107    eprintln!("├──────────────────────────────────────────────────────────────────────────────");
18108    eprintln!(
18109        "│ TOTAL {} builtin calls, {:.1} ms over {} frames  →  {:.0} calls/frame, {:.2} ms/frame in builtins",
18110        total_calls,
18111        total_ns as f64 / 1e6,
18112        p.frames,
18113        total_calls as f64 / fr,
18114        total_ns as f64 / 1e6 / fr
18115    );
18116    eprintln!("└──────────────────────────────────────────────────────────────────────────────");
18117}
18118
18119/// Trim a builtin name to fit the report column (counts chars, good enough for
18120/// the mixed-script names).
18121fn truncate_name(s: &str) -> String {
18122    let max = 24;
18123    if s.chars().count() <= max {
18124        s.to_string()
18125    } else {
18126        let mut t: String = s.chars().take(max - 1).collect();
18127        t.push('…');
18128        t
18129    }
18130}