灵符lingfu

soul-symphony-ling

↓ 0 downloads

Soul Symphony — Ling Edition. A spiritual 3D adventure in the soul realm: collect the letters of the world-song, clear the monster lattice, and duel in the arcade arena. Wind Waker-style toon renderer with volumetric light pools, SSAO, bloom and oil-slick tilt-shift — all ling-lang vectors.

Keywords game, 3d, toon, music, soul-symphony
lingfu install soul-symphony-ling
AuthorsLing Lin <taellinglin@gmail.com>
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (1947)

game/title.ling
# ────────────────────────────────────────────────────────────────────────────
# title.ling — บูตโลโก้ + ชื่อเรื่อง + ตัวช่วยเมนู 3D + ระบบฟอนต์/ภาษา
# ────────────────────────────────────────────────────────────────────────────

ใช้ "../font/Daemon"
ใช้ "../font/English"
ใช้ "../font/Daemon3D"
ใช้ "../font/English3D"
ใช้ "../font/Thai"
ใช้ "../font/Korean"
ใช้ "../font/Japanese"
ใช้ "../font/Chinese"
ใช้ "../font/MenuTH"
ใช้ "../font/MenuKO"
ใช้ "../font/MenuJA"
ใช้ "../font/MenuZH"
ใช้ "../font/ArcadeTH"
ใช้ "../font/ArcadeKO"
ใช้ "../font/ArcadeJA"
ใช้ "../font/ArcadeZH"
ใช้ "../font/SS2EN"
ใช้ "../font/SS2TH"
ใช้ "../font/SS2KO"
ใช้ "../font/SS2JA"
ใช้ "../font/SS2ZH"
ใช้ "../font/OnlineTH"
ใช้ "../font/OnlineKO"
ใช้ "../font/OnlineJA"
ใช้ "../font/OnlineZH"
ใช้ "../game/lattice"
bind glyph = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
bind ฝูงInit = 0
bind ฝูงN = 12
bind ฝูงX = []
bind ฝูงY = []
bind ฝูงZ = []
bind ฝูงVx = []
bind ฝูงVy = []
bind ฝูงVz = []
bind หมึกRB = liquid_new(48.0, 48.0)
bind _inkRB = สีของเหลว(หมึกRB, 220, 34, 52, 44, 96, 220)
bind หมึกGold = liquid_new(48.0, 48.0)
bind _inkGold = สีของเหลว(หมึกGold, 235, 182, 58, 148, 76, 20)
# ปราสาท 5 ชิ้น (เฟรมร่วม) นั่งบนขอบฟ้าหมึกไตเติล — หมุนสีคนละเฟส (หมุนสีเมช)
bind _วังไต0 = mesh_load("game/palace_p0.lmesh")
bind _วังไต1 = mesh_load("game/palace_p1.lmesh")
bind _วังไต2 = mesh_load("game/palace_p2.lmesh")
bind _วังไต3 = mesh_load("game/palace_p3.lmesh")
bind _วังไต4 = mesh_load("game/palace_p4.lmesh")
bind คำSS2 = [18,14,20,11,0.0-1.0,18,24,12,15,7,14,13,24,0.0-1.0,28]

# ── คำ (ดัชนีกลิฟ a=0..z=25, -1 = ช่องว่าง) ─────────────────────────────────
bind คำ_บูต     = [12,0,3,4,-1,8,13,-1,11,8,13,6,-1,2,14,20,13,19,17,24]  # made in ling country
bind คำ_ปุ่ม    = [4,13,19,4,17,-1,19,14,-1,18,19,0,17,19]                 # enter to start
bind คำ_โหลด    = [11,14,0,3,-1,18,0,21,4]                                 # load save
bind คำ_เกมใหม่ = [13,4,22,-1,6,0,12,4]                                    # new game
bind คำ_มัลติ   = [12,20,11,19,8,15,11,0,24,4,17]                          # multiplayer
bind คำ_ตัวเลือก= [14,15,19,8,14,13,18]                                    # options
bind คำ_ออก     = [4,23,8,19]                                              # exit
bind คำ_เมนูหลัก= [12,0,8,13,-1,12,4,13,20]                                # main menu
bind คำ_เล่นต่อ = [17,4,18,20,12,4]                                        # resume
bind คำ_พัก     = [15,0,20,18,4]                                           # pause
bind คำ_โฮสต์   = [7,14,18,19,-1,6,0,12,4]                                 # host game
bind คำ_จอยน์   = [9,14,8,13,-1,6,0,12,4]                                  # join game
bind คำ_โคออป   = [2,14,14,15,-1,15,19,22,14,-1,0,17,17,14,22,18]          # coop p two arrows
bind คำ_โลคัล   = [11,14,2,0,11,-1,2,14,14,15]                             # local coop
bind คำ_วีเอส   = [21,18,-1,12,14,3,4]                                     # vs mode
bind คำ_โหมดโคออป = [2,14,14,15,-1,12,14,3,4]                               # coop mode
bind คำ_เชื่อม  = [2,14,13,13,4,2,19,8,13,6]                                # connecting
bind คำ_พร้อม   = [2,14,13,13,4,2,19,4,3]                                  # connected
bind คำ_เซฟ     = [18,0,21,4]                                              # save
bind คำ_ยกเลิก  = [2,0,13,2,4,11]                                          # cancel
bind คำ_เพลง    = [12,20,18,8,2]                                           # music
bind คำ_ภาษาคำ  = [11,0,13,6,20,0,6,4]                                     # language
bind คำ_ช่อง    = [18,11,14,19]                                            # slot
bind คำ_ว่าง    = [4,12,15,19,24]                                          # empty
bind คำ_vsync   = [21,18,24,13,2]                                          # vsync
bind คำ_บันทึก  = [18,0,21,4,3]                                            # saved
bind คำ_บทหนึ่ง = [2,7,0,15,19,4,17,-1,14,13,4]                            # chapter one
bind คำ_โหลดดิ้ง = [11,14,0,3,8,13,6]                                       # loading
bind คำ_sfx = [18,5,23]                                                    # sfx
bind คำ_bgm = [1,6,12]                                                     # bgm
bind คำ_บทพูด = [18,20,1,19,8,19,11,4,18]                                  # subtitles
bind คำ_เลือกเลเวล = [11,4,21,4,11,-1,18,4,11,4,2,19]                       # level select
bind คำ_อาร์เคด = [0,17,2,0,3,4]                                            # arcade
# บรรทัดบูต (train-track): Daemon = โรมาจิ, English = อังกฤษ
bind คำ_โรมาจิ = [17,8,13,6,20,-1,10,20,13,8,-1,10,0,17,0,-1,19,18,20,10,20,19,19,0]  # ringu kuni kara tsukutta
bind คำ_บรร3 = [15,17,4,18,18,-1,4,13,19,4,17]                              # press enter
bind ไชม์ = [523.3, 587.3, 659.3, 784.0, 880.0, 1046.5, 1174.7, 1318.5, 1568.0]  # pentatonic ขึ้น

# ── ชื่อภาษา ────────────────────────────────────────────────────────────────
bind คำ_เดมอน   = [3,0,4,12,14,13]                                         # daemon
bind คำ_อังกฤษ  = [4,13,6,11,8,18,7]                                       # english
bind คำ_ไทย     = [19,7,0,8]                                               # thai
bind คำ_เกาหลี  = [10,14,17,4,0,13]                                        # korean
bind คำ_ญี่ปุ่น  = [9,0,15,0,13,4,18,4]                                     # japanese
bind คำ_จีน     = [2,7,8,13,4,18,4]                                        # chinese

# เมนู
bind เมนูหลักรายการ = [คำ_โหลด, คำ_เกมใหม่, คำ_เลือกเลเวล, คำ_มัลติ, คำ_ตัวเลือก, คำ_ออก]
bind เมนูพักรายการ  = [คำ_เล่นต่อ, คำ_ตัวเลือก, คำ_เมนูหลัก, คำ_ออก]
bind เมนูตัวเลือก    = [คำ_เพลง, คำ_ภาษาคำ, คำ_เซฟ, คำ_ยกเลิก]
bind เมนูมัลติ      = [คำ_โฮสต์, คำ_จอยน์]
bind เมนูภาษา       = [คำ_เดมอน, คำ_อังกฤษ, คำ_ไทย, คำ_เกาหลี, คำ_ญี่ปุ่น, คำ_จีน]
bind เมนูเซฟ        = [คำ_ช่อง, คำ_ช่อง, คำ_ช่อง]

# ── เมนูหลักหลายภาษา: ดัชนีตรงกับ MenuTH/KO/JA/ZH item 0-5 ──
#   Daemon = โรมาจิ (ตามคู่มือ), English = a-z, อื่น ๆ = ฟอนต์วลี
bind คำRM_โหลด   = [17,14,3,14]                   # rodo
bind คำRM_เกมใหม่ = [13,24,20,-1,6,4,12,20]         # nyu gemu
bind คำRM_เลเวล  = [17,4,1,4,17,20]               # reberu
bind คำRM_มัลติ  = [12,0,17,20,2,7,8]             # maruchi
bind คำRM_ตัวเลือก = [14,15,20,18,7,14,13]          # opushon
bind คำRM_ออก    = [18,7,20,20,17,24,14,20]        # shuuryou
bind คำRM_ช่อง   = [18,20,17,14,19,19,14]          # surotto
bind คำRM_ว่าง   = [10,0,17,0]                     # kara
bind คำRM_บันทึก = [7,14,25,14,13]                 # hozon
bind คำRM_เล่นต่อ = [18,0,8,10,0,8]                 # saikai
bind คำRM_เมนูหลัก = [12,4,8,13,-1,12,4,13,24,20]    # mein menyu
bind คำRM_เพลง   = [14,13,6,0,10,20]              # ongaku
bind คำRM_ภาษา   = [6,4,13,6,14]                  # gengo
bind คำRM_ยกเลิก = [10,24,0,13,18,4,17,20]         # kyanseru
bind คำRM_พัก    = [15,14,25,20]                  # pozu
# 18 รายการ — ดัชนีตรงกับ MenuTH/KO/JA/ZH
# item 19 = "online" (special-cased in วาดป้าย, never reads this slot) · item 20 = vsync
# (item > 18 always renders via the English word list regardless of `lang` —
#  the existing phrasebake fallback for labels not yet baked per-language.)
bind เมนูEN = [คำ_โหลด, คำ_เกมใหม่, คำ_เลือกเลเวล, คำ_มัลติ, คำ_ตัวเลือก, คำ_ออก, คำ_อังกฤษ, คำ_โหลด, คำ_ช่อง, คำ_ว่าง, คำ_บันทึก, คำ_เล่นต่อ, คำ_เมนูหลัก, คำ_เพลง, คำ_ภาษาคำ, คำ_เซฟ, คำ_ยกเลิก, คำ_พัก, คำ_อาร์เคด, [], คำ_vsync]
bind เมนูRM = [คำRM_โหลด, คำRM_เกมใหม่, คำRM_เลเวล, คำRM_มัลติ, คำRM_ตัวเลือก, คำRM_ออก, คำ_เดมอน, คำRM_โหลด, คำRM_ช่อง, คำRM_ว่าง, คำRM_บันทึก, คำRM_เล่นต่อ, คำRM_เมนูหลัก, คำRM_เพลง, คำRM_ภาษา, คำRM_บันทึก, คำRM_ยกเลิก, คำRM_พัก, คำ_อาร์เคด]

# ── ฟอนต์ดิสแพตช์: 0 = Daemon, อื่นๆ = English (ไทย/เกา/ญี่/จีน fallback) ─────
#   up = (0,-1,0) เพราะ Y+ = ลง → ตัวอักษรตั้งตรง (ไม่กลับหัว)
# ข้อความทั่วไป (เมนู/บูต/บทกวี) ใช้ฟอนต์แบน (เบา) — กลิฟ 3D เก็บไว้ที่
# วงแหวนชื่อเรื่อง + ตัวเก็บในโลกเท่านั้น
# อบกลิฟทั้งฟอนต์ลงดิสเพลย์ลิสต์ครั้งเดียวตอนเริ่ม → เล่นซ้ำแบบเนทีฟทุกเฟรม
# (เลี่ยงคำนวณเวกเตอร์กลิฟใหม่ในล่ามทุกครั้ง)
ฟังก์ชัน อบกลิฟ(ฟอนต์, n) {
    bind cache = []
    bind i = 0
    ขณะที่ i < n {
        เริ่มอบเมช()
        ถ้า ฟอนต์ == 0 { วาดอักขระDaemon(i, 0.0,0.0,0.0, 1.0,0.0,0.0, 0.0,1.0,0.0, 1.0) }
        มิฉะนั้น { วาดอักขระEnglish(i, 0.0,0.0,0.0, 1.0,0.0,0.0, 0.0,1.0,0.0, 1.0) }
        bind cache = list_push(cache, จบอบเมช())
        bind i = i + 1
    }
    คืน cache
}
ฟังก์ชัน อบกลิฟ3มิติ(n) {
    bind cache = []
    bind i = 0
    ขณะที่ i < n {
        เริ่มอบเมช()
        วาดอักขระDaemon3D(i, 0.0,0.0,0.0, 1.0,0.0,0.0, 0.0,1.0,0.0, 1.0)
        bind cache = list_push(cache, จบอบเมช())
        bind i = i + 1
    }
    คืน cache
}
bind _กลิฟเดมอน = อบกลิฟ(0, 26)
bind _กลิฟอังกฤษ = อบกลิฟ(1, 48)
bind _กลิฟเดมอน3มิติ = อบกลิฟ3มิติ(26)

# UI/boot/menu text is 2D-facing ink, not lit 3D geometry — flat_shade skips
# both the lighting compute and the toon cel-ramp so cycling hues stay exact.
ฟังก์ชัน boot_text_style() {
    set_depth_test(0)
    set_flat_shade(1.0)
    ตั้งแสงรอบข้าง(0.58)
}

ฟังก์ชัน ui_text_color(idx, selected, t) {
    bind h = t*1.45 + idx*0.72
    ถ้า selected > 0.5 {
        สีดินสอ(floor((ไซน์(h)*0.5+0.5)*80+175), floor((ไซน์(h+2.094)*0.5+0.5)*80+175), floor((ไซน์(h+4.189)*0.5+0.5)*80+175))
    }
    มิฉะนั้น {
        สีดินสอ(floor((ไซน์(h)*0.5+0.5)*95+85), floor((ไซน์(h+2.094)*0.5+0.5)*95+85), floor((ไซน์(h+4.189)*0.5+0.5)*95+85))
    }
}

ฟังก์ชัน วาดกลิฟ(lang, code, x, y, z, s) {
    boot_text_style()
    ถ้า lang == 0 {
        ถ้า code < 26 { วาดอบเมช(list_get(_กลิฟเดมอน, code), x,y,z, 1,0,0, 0,0.0-1.0,0, s) }
    }
    มิฉะนั้น {
        ถ้า code < 48 { วาดอบเมช(list_get(_กลิฟอังกฤษ, code), x,y,z, 1,0,0, 0,0.0-1.0,0, s) }
    }
    ตั้งแฟลตเชด(0)
}

# ── กล้องมองเป้า ─────────────────────────────────────────────────────────────
ฟังก์ชัน ตั้งกล้องมอง(cpx, cpy, cpz, tx, ty, tz) {
    bind ldx = tx - cpx;  bind ldy = ty - cpy;  bind ldz = tz - cpz
    bind ll  = รากที่สอง(ldx*ldx + ldy*ldy + ldz*ldz)
    bind ry  = atan2(ldx / ll, ldz / ll)
    bind rx  = asin(จำกัด(ldy / ll, 0.0-0.98, 0.98))
    ตั้งกล้อง(โคไซน์(ry), ไซน์(ry), โคไซน์(rx), ไซน์(rx))
    ตั้งตำแหน่งกล้อง(cpx, cpy, cpz)
    ผู้ฟัง(โคไซน์(ry), ไซน์(ry), โคไซน์(rx), ไซน์(rx))
}

# ── วาดข้อความ: กลิฟจากรายการดัชนี จัดกึ่งกลาง (lang เลือกฟอนต์) ────────────
ฟังก์ชัน วาดข้อความ(row, ox, oy, oz, scale, lang) {
    boot_text_style()
    bind n = ความยาว(row)
    bind adv = scale * 0.66
    bind startx = ox - (n - 1) * adv * 0.5
    # ── ฮาโลเรืองแสง (emission): ผ่านบวกกลิฟใหญ่ขึ้น → แกนทึบทับ ──
    โหมดผสม(1)
    bind ig = 0
    ขณะที่ ig < n {
        bind gc = list_get(row, ig)
        ถ้า gc >= 0 { วาดกลิฟ(lang, gc, startx + ig * adv, oy, oz + 0.15, scale*1.16) }
        bind ig = ig + 1
    }
    โหมดผสม(0)
    bind i = 0
    ขณะที่ i < n {
        bind code = list_get(row, i)
        ถ้า code >= 0 {
            วาดกลิฟ(lang, code, startx + i * adv, oy, oz, scale)
        }
        bind i = i + 1
    }
}

# ── ลูกศร ">" ───────────────────────────────────────────────────────────────
ฟังก์ชัน วาดลูกศร(x, y, z, s) {
    วาดเส้น3มิติ(x - s, y - s, z,  x + s*0.6, y, z)
    วาดเส้น3มิติ(x + s*0.6, y, z,  x - s, y + s, z)
}

# ── เคอร์เซอร์หมึก 3D: ลิ่มหมึก (พีระมิด 4 หน้าชี้ขวา รับแสง = มีมิติ) + ออร์บของเหลววาว ──
#   ใช้ liquid ink shader จริง (liquid_draw_surface) ที่โคนลิ่ม → เปียกวาวหมุนวน
bind เคอร์เซอร์ลิค = liquid_new(36.0, 36.0)
bind _curInk = สีของเหลว(เคอร์เซอร์ลิค, 95, 130, 215, 14, 12, 26)   # หมึก: น้ำเงินวาว + ดำสนิท
ฟังก์ชัน เคอร์เซอร์หมึก(x, y, z, s, t) {
    liquid_step(เคอร์เซอร์ลิค, 0.016)
    liquid_splat(เคอร์เซอร์ลิค, 18.0+ไซน์(t*2.3)*7.0, 18.0+โคไซน์(t*1.9)*7.0, 0.0, 0.22, 4.0)
    liquid_splat(เคอร์เซอร์ลิค, 18.0+โคไซน์(t*1.6)*7.0, 18.0+ไซน์(t*2.1)*7.0, 1.0, 0.18, 3.5)
    bind bob = ไซน์(t*3.0)*0.10*s
    bind d = s*0.55;  bind bx = x - s*0.7;  bind ax = x + s*1.0;  bind ay = y + bob
    # ลิ่มหมึก 3D (พีระมิดชี้ +x): 4 หน้าด้านข้าง → รับแสงต่างมุม = ดูเป็น 3 มิติ
    โหมดผสม(0);  สีดินสอ(24, 22, 40)
    วาดสามเหลี่ยม3มิติ(ax, ay, z,  bx, y-s+bob, z-d,  bx, y+s+bob, z-d)
    วาดสามเหลี่ยม3มิติ(ax, ay, z,  bx, y+s+bob, z-d,  bx, y+s+bob, z+d)
    วาดสามเหลี่ยม3มิติ(ax, ay, z,  bx, y+s+bob, z+d,  bx, y-s+bob, z+d)
    วาดสามเหลี่ยม3มิติ(ax, ay, z,  bx, y-s+bob, z+d,  bx, y-s+bob, z-d)
    flush_3d()
    # ออร์บหมึกเหลววาว (liquid ink shader) ที่โคนลิ่ม — ทิ้งโหมดผสมไว้ที่ 1 (เมนูวาดข้อความแบบบวกแสง)
    โหมดผสม(1)
    liquid_draw_surface(เคอร์เซอร์ลิค, 1.0, bx, y+bob, z, s*0.95, s*0.95)
    flush_3d()
}

# วงแหวนเติม: แอนนูลัสเติม (rIn..rOut, seg ส่วน) บนระนาบ XZ ที่ y
ฟังก์ชัน วงแหวนเติม(x, y, z, rOut, rIn, seg, r, g, b) {
    สีดินสอ(r, g, b)
    bind k = 0
    ขณะที่ k < seg {
        bind a0 = (k / seg) * 6.28318;  bind a1 = ((k + 1) / seg) * 6.28318
        bind c0 = โคไซน์(a0);  bind s0 = ไซน์(a0);  bind c1 = โคไซน์(a1);  bind s1 = ไซน์(a1)
        วาดสามเหลี่ยม3มิติ(x+c0*rIn, y, z+s0*rIn,  x+c0*rOut, y, z+s0*rOut,  x+c1*rOut, y, z+s1*rOut)
        วาดสามเหลี่ยม3มิติ(x+c0*rIn, y, z+s0*rIn,  x+c1*rOut, y, z+s1*rOut,  x+c1*rIn, y, z+s1*rIn)
        bind k = k + 1
    }
}

# ── เอฟเฟกต์อาวุธ AoE: คลื่นกระแทกเติมทึบ + สำเนียงต่างกันต่อชนิด (3-9) ──
ฟังก์ชัน วาดเอฟเฟกต์อาวุธ(typ, prog, rad, x, y, z) {
    bind ex = (1.0 - prog) * rad + 4.0
    bind h = typ * 0.9
    bind br = prog
    bind r = floor((ไซน์(h)*0.5+0.5)*205*br+30)
    bind g = floor((ไซน์(h+2.094)*0.5+0.5)*205*br+30)
    bind b = floor((ไซน์(h+4.189)*0.5+0.5)*205*br+30)
    วงแหวนเติม(x, y, z, ex, ex*0.74, 20, r, g, b)
    ถ้า typ == 3.0 { วาดสามเหลี่ยม3มิติ(x-3.0, y, z,  x+3.0, y, z,  x, y, z+ex*1.35) }   # Torpedo: ลิ่มพุ่ง
    ถ้า typ == 5.0 { วงแหวนเติม(x, y, z, ex*0.55, ex*0.34, 4, r, g, b) }                   # DualSword: กากบาท
    ถ้า typ == 6.0 {                                                                       # Burn: เปลวรอบวง
        bind fk = 0
        ขณะที่ fk < 8 { bind fa = (fk/8.0)*6.28318
            วาดสามเหลี่ยม3มิติ(x+โคไซน์(fa)*ex*0.8, y, z+ไซน์(fa)*ex*0.8,  x+โคไซน์(fa+0.25)*ex, y, z+ไซน์(fa+0.25)*ex,  x+โคไซน์(fa)*ex*0.85, y-5.0, z+ไซน์(fa)*ex*0.85)
            bind fk = fk + 1 }
    }
    ถ้า typ == 7.0 { วงแหวนเติม(x, y, z, ex*0.5, ex*0.18, 6, floor(r*0.5+120), floor(g*0.5+120), 255) }   # Freeze: หกเหลี่ยมน้ำแข็ง
    ถ้า typ == 8.0 { วงแหวนเติม(x, y, z, ex*0.56, ex*0.42, 24, floor(r*0.6+90), floor(g*0.6+90), floor(b*0.6+90)) }  # Nova: วงซ้อน
    ถ้า typ == 9.0 {                                                                       # Quake: หนามแตกรัศมี
        bind qk = 0
        ขณะที่ qk < 10 { bind qa = (qk/10.0)*6.28318
            วาดสามเหลี่ยม3มิติ(x+โคไซน์(qa-0.14)*ex*0.4, y, z+ไซน์(qa-0.14)*ex*0.4,  x+โคไซน์(qa+0.14)*ex*0.4, y, z+ไซน์(qa+0.14)*ex*0.4,  x+โคไซน์(qa)*ex*1.18, y, z+ไซน์(qa)*ex*1.18)
            bind qk = qk + 1 }
    }
}

# ── แถบปริมาณเสียง (สไลเดอร์ 0..1) ที่แถว yy: พื้นเทา + เติมฟ้า ──
ฟังก์ชัน แถบปริมาณ(vol, yy) {
    สีดินสอ(45, 45, 62)
    วาดสามเหลี่ยม3มิติ(2.0, yy-0.35, 0.0,  8.0, yy-0.35, 0.0,  8.0, yy+0.35, 0.0)
    วาดสามเหลี่ยม3มิติ(2.0, yy-0.35, 0.0,  8.0, yy+0.35, 0.0,  2.0, yy+0.35, 0.0)
    bind fw = 6.0 * vol
    สีดินสอ(80, 200, 255)
    วาดสามเหลี่ยม3มิติ(2.0, yy-0.35, 0.0,  2.0+fw, yy-0.35, 0.0,  2.0+fw, yy+0.35, 0.0)
    วาดสามเหลี่ยม3มิติ(2.0, yy-0.35, 0.0,  2.0+fw, yy+0.35, 0.0,  2.0, yy+0.35, 0.0)
}

# ── วาดเมนูแนวตั้ง ────────────────────────────────────────────────────────────
ฟังก์ชัน วาดเมนู(items, sel, scale, spacing, t, lang) {
    bind n = ความยาว(items)
    bind oy = 0.0 - (n - 1) * spacing * 0.5
    bind i = 0
    ขณะที่ i < n {
        bind row = list_get(items, i)
        bind yy = oy + i * spacing
        ถ้า i == sel {
            ui_text_color(i, 1.0, t)
            bind aw = (ความยาว(row) - 1) * scale * 0.66 * 0.5
            เคอร์เซอร์หมึก(0.0 - aw - 1.4, yy, 0.0, 0.7, t)
            ui_text_color(i, 1.0, t)
        }
        มิฉะนั้น {
            ui_text_color(i, 0.0, t)
        }
        วาดข้อความ(row, 0.0, yy, 0.0, scale, lang)
        bind i = i + 1
    }
    flush_3d()
}

# ── เมนูหลายภาษา ────────────────────────────────────────────────────────────
# วลี CJK (lang 2-5) item ที่ x กึ่งกลาง (สีตั้งไว้ก่อนเรียก)
ฟังก์ชัน วาดวลีเมนู(lang, item, x, y, sc) {
    boot_text_style()
    bind n = 0;  bind adv = []
    ถ้า lang == 2 { bind n = จำนวนอักขระ_MenuTH(item);  bind adv = กว้าง_MenuTH(item) }
    ถ้า lang == 3 { bind n = จำนวนอักขระ_MenuKO(item);  bind adv = กว้าง_MenuKO(item) }
    ถ้า lang == 4 { bind n = จำนวนอักขระ_MenuJA(item);  bind adv = กว้าง_MenuJA(item) }
    ถ้า lang == 5 { bind n = จำนวนอักขระ_MenuZH(item);  bind adv = กว้าง_MenuZH(item) }
    bind tw = 0.0;  bind a0 = 0
    ขณะที่ a0 < n { bind tw = tw + list_get(adv, a0) * sc;  bind a0 = a0 + 1 }
    bind cur = x - tw * 0.5;  bind j = 0
    ขณะที่ j < n {
        bind w = list_get(adv, j) * sc
        bind cxp = cur + w * 0.5
        ถ้า lang == 2 { วาดอักขระ_MenuTH(item, j, cxp, y, 0.0, 1,0,0, 0,0.0-1.0,0, sc) }
        ถ้า lang == 3 { วาดอักขระ_MenuKO(item, j, cxp, y, 0.0, 1,0,0, 0,0.0-1.0,0, sc) }
        ถ้า lang == 4 { วาดอักขระ_MenuJA(item, j, cxp, y, 0.0, 1,0,0, 0,0.0-1.0,0, sc) }
        ถ้า lang == 5 { วาดอักขระ_MenuZH(item, j, cxp, y, 0.0, 1,0,0, 0,0.0-1.0,0, sc) }
        bind cur = cur + w;  bind j = j + 1
    }
    ตั้งแฟลตเชด(0)
}
# ป้าย: รายการ item ในภาษา lang ที่ (x,y) (สีตั้งไว้ก่อนเรียก)
# นำ(ปุ่มคีย์, ขอบจอย): คีย์บอร์ด(Bool) หรือ จอย(ขอบ ≥0) → Bool — ใช้ทุกเมนูให้จอยเลื่อนได้
ฟังก์ชัน นำ(kb, gp) {
    bind r = 0.0
    ถ้า กดปุ่ม(kb) { bind r = 1.0 }     # กดปุ่ม คืน Bool → ใช้ใน ถ้า ตรง ๆ
    ถ้า gp > 0.5 { bind r = 1.0 }
    คืน r > 0.5                          # คืน Bool (ให้ ถ้า นำ(...) ใช้ได้)
}

# หมายเหตุ: ตั้งแฟลตเชด(1) ครอบทั้งฟังก์ชัน — ป้ายเมนู 2D ไม่ควรผ่านแสง/toon ramp
# จึงต้องไม่มี คืน กลางฟังก์ชัน (จะข้าม ตั้งแฟลตเชด(0) ปิดท้าย ทำให้ค้างเปิด)
ฟังก์ชัน วาดป้าย(lang, item, x, y, sc) {
    boot_text_style()
    ถ้า item == 19 {
        ถ้า lang == 0 { วาดสตริง("online", x, y, 0.0, sc, 0) }
        ถ้า lang == 1 { วาดสตริง("online", x, y, 0.0, sc, 1) }
        ถ้า lang == 2 { วาดOnlineTH(x, y, sc*0.85, เวลาปัจจุบัน()) }
        ถ้า lang == 3 { วาดOnlineKO(x, y, sc*0.85, เวลาปัจจุบัน()) }
        ถ้า lang == 4 { วาดOnlineJA(x, y, sc*0.85, เวลาปัจจุบัน()) }
        ถ้า lang == 5 { วาดOnlineZH(x, y, sc*0.85, เวลาปัจจุบัน()) }
    }
    มิฉะนั้น { ถ้า item == 18 {                    # อาร์เคด: เบคแล้วทุกภาษา (phrasebake)
        ถ้า lang == 0 { วาดข้อความ(list_get(เมนูRM, item), x, y, 0.0, sc, 0) }
        ถ้า lang == 1 { วาดข้อความ(list_get(เมนูEN, item), x, y, 0.0, sc, 1) }
        ถ้า lang == 2 { วาดArcadeTH(x, y, sc*0.85) }
        ถ้า lang == 3 { วาดArcadeKO(x, y, sc*0.85) }
        ถ้า lang == 4 { วาดArcadeJA(x, y, sc*0.85) }
        ถ้า lang == 5 { วาดArcadeZH(x, y, sc*0.85) }
    }
    มิฉะนั้น { ถ้า item > 18 { วาดข้อความ(list_get(เมนูEN, item), x, y, 0.0, sc, 1) }
    มิฉะนั้น {
        ถ้า lang == 0 { วาดข้อความ(list_get(เมนูRM, item), x, y, 0.0, sc, 0) }
        ถ้า lang == 1 { วาดข้อความ(list_get(เมนูEN, item), x, y, 0.0, sc, 1) }
        ถ้า lang >= 2 { วาดวลีเมนู(lang, item, x, y, sc) }
    } } }
    ตั้งแฟลตเชด(0)
}
# เมนูแนวตั้งหลายภาษา: ids = รายการดัชนี item; ไฮไลต์ที่ sel
ฟังก์ชัน วาดเมนูภาษา(ids, sel, lang, y0, dy, sc, t) {
    bind n = ความยาว(ids)
    bind i = 0
    ขณะที่ i < n {
        bind yy = y0 + i * dy
        ถ้า i == sel {
            เคอร์เซอร์หมึก(0.0-9.0, yy, 0.0, 0.7, t)
            ui_text_color(i, 1.0, t)
        }
        มิฉะนั้น { ui_text_color(i, 0.0, t) }
        วาดป้าย(lang, list_get(ids, i), 0.0, yy, sc)
        bind i = i + 1
    }
    flush_3d()
}
# ดัชนี item สำหรับแต่ละเมนู
bind idsหลัก     = [0, 1, 18, 2, 3, 19, 4, 14, 5]
bind idsพัก      = [11, 4, 12, 5]            # resume,options,mainmenu,exit
bind idsตัวเลือก  = [13, 15, 16]              # music,save,cancel (language ย้ายไปเมนูหลัก)
# เลขโรมัน i..xii (a-z: i=8 v=21 x=23) — ใช้ Daemon glyphs บนหน้าเต๋า
bind โรมัน = [[8],[8,8],[8,8,8],[8,21],[21],[21,8],[21,8,8],[21,8,8,8],[8,23],[23],[23,8],[23,8,8]]

# หน้าเลือกด่าน 10 ช่อง (tutorial=i + 9 levels) — ตาราง 2×5, เลขโรมัน, หัวข้อหลายภาษา
ฟังก์ชัน วาดเลือกด่าน(sel, lang, t, ปลดล็อก) {
    สีดินสอ(200, 210, 245)
    วาดป้าย(lang, 2, 0.0, 0.0-7.8, 1.0)       # "level select" (localized)
    bind k = 0
    ขณะที่ k < 10 {
        bind col = k - ปัดลง(k/5.0)*5.0
        bind row = ปัดลง(k/5.0)
        bind x = 0.0 - 10.0 + col*5.0
        bind y = 0.0 - 1.5 + row*4.0
        bind locked = 0;  ถ้า k > ปลดล็อก { bind locked = 1 }     # ด่านที่ยังไม่เล่นผ่าน = ล็อก
        ถ้า k == sel {
            bind h = t*3.0
            ถ้า locked == 1 { สีดินสอ(120, 60, 60) }              # เลือกด่านล็อก = แดงหรี่
            มิฉะนั้น { สีดินสอ(floor((ไซน์(h)*0.5+0.5)*155+100), floor((ไซน์(h+2.094)*0.5+0.5)*155+100), floor((ไซน์(h+4.189)*0.5+0.5)*155+100)) }
            วงเส้น(x, y, 2.0)
        }
        มิฉะนั้น {
            ถ้า locked == 1 { สีดินสอ(55, 45, 55) }  มิฉะนั้น { สีดินสอ(90, 100, 130) }
            วงเส้น(x, y, 1.6)
        }
        ถ้า locked == 1 { สีดินสอ(70, 55, 70) }                    # เลขด่านล็อก = หรี่ + กุญแจ
        วาดข้อความ(list_get(โรมัน, k), x, y, 0.0, 0.7, 1)
        ถ้า locked == 1 { สีดินสอ(150, 90, 90);  วงเส้น(x, y+0.55, 0.4);  วาดเส้น3มิติ(x-0.4, y+0.55, 0.0, x+0.4, y+0.05, 0.0) }   # ไอคอนกุญแจง่าย ๆ
        bind k = k + 1
    }
    flush_3d()
}

# ════════════════════════════════════════════════════════════════════════════
# ลูกเต๋า 12 หน้า (dodecahedron) เลือกด่าน — ตัวอักษร A–L หมุนรอบแกนหน้า (UV หมุน)
#   หน้า i (0–11) = ด่าน i+1 :  1–9 เนื้อเรื่อง · 10–12 โบนัส (หน้าทอง)
# ════════════════════════════════════════════════════════════════════════════
# 12 ทิศศูนย์กลางหน้า = จุดยอด icosahedron (perms ของ {0,±1,±φ})
ฟังก์ชัน ทิศหน้าเต๋า(i) {
    bind P = 1.618034;  bind N = 0.0 - 1.618034
    bind d = [0.0,1.0,P,  0.0,1.0,N,  0.0,0.0-1.0,P,  0.0,0.0-1.0,N,
              1.0,P,0.0,  1.0,N,0.0,  0.0-1.0,P,0.0,  0.0-1.0,N,0.0,
              P,0.0,1.0,  P,0.0,0.0-1.0,  N,0.0,1.0,  N,0.0,0.0-1.0]
    bind b = i*3;  bind m = 1.902113                     # |(1,φ,0)| = √(1+φ²)
    คืน [list_get(d,b)/m, list_get(d,b+1)/m, list_get(d,b+2)/m]
}
# หมุนเวกเตอร์รอบ Y(yaw) แล้ว X(pitch)
ฟังก์ชัน หมุนเต๋า(ax, ay, az, yaw, pit) {
    bind cy = โคไซน์(yaw);  bind sy = ไซน์(yaw)
    bind rx = ax*cy + az*sy;  bind rz = 0.0 - ax*sy + az*cy
    bind cp = โคไซน์(pit);  bind sp = ไซน์(pit)
    bind ny = ay*cp - rz*sp;  bind nz = ay*sp + rz*cp
    คืน [rx, ny, nz]
}
# (yaw,pitch) ที่ทำให้หน้า target หันเข้ากล้อง (−Z) → ใช้หมุนเต๋าไปเลือกด่านปัจจุบัน
ฟังก์ชัน เป้าหมุนเต๋า(target) {
    bind d = ทิศหน้าเต๋า(target)
    bind dx = list_get(d,0);  bind dy = list_get(d,1);  bind dz = list_get(d,2)
    bind yaw = atan2(0.0 - dx, dz)                       # หมุน Y ให้ x→0
    bind zc = 0.0 - dx*ไซน์(yaw) + dz*โคไซน์(yaw)
    ถ้า zc > 0.0 { bind yaw = yaw + 3.14159265 }         # ให้หันด้าน −Z
    bind zc2 = 0.0 - dx*ไซน์(yaw) + dz*โคไซน์(yaw)
    bind pitch = atan2(0.0 - dy, 0.0 - zc2)              # หมุน X ให้ (dy,zc2)→(0,−1)
    คืน [yaw, pitch]
}
# บิตที่ b (b=1,2,4,…) ของ m
ฟังก์ชัน บิตเลข(m, b) { คืน floor(m/b) - floor(m/(b*2.0))*2.0 }
# เส้นเซกเมนต์ใน (u,v) กึ่งกลาง → โลก ตามฐาน right×up (s=สเกล)
ฟังก์ชัน เส้นเลข(cx,cy,cz, rx,ry,rz, ux,uy,uz, s, u0,v0, u1,v1) {
    วาดเส้น3มิติ(cx+rx*u0*s+ux*v0*s, cy+ry*u0*s+uy*v0*s, cz+rz*u0*s+uz*v0*s,  cx+rx*u1*s+ux*v1*s, cy+ry*u1*s+uy*v1*s, cz+rz*u1*s+uz*v1*s)
}
# ตัวเลข 7-seg d (0-9) — กึ่งกลางตัวเลขอยู่ที่ (cx,cy,cz), หมุนรอบกึ่งกลางตามฐาน right×up
ฟังก์ชัน วาดเลข3มิติ(d, cx,cy,cz, rx,ry,rz, ux,uy,uz, s) {
    bind masks = [63,6,91,79,102,109,125,7,127,111]      # a..g bitmask ต่อหลัก
    bind m = list_get(masks, floor(d));  bind hw = 0.30;  bind hh = 0.52
    ถ้า บิตเลข(m,1)  > 0.5 { เส้นเลข(cx,cy,cz, rx,ry,rz, ux,uy,uz, s, 0.0-hw,hh,  hw,hh) }        # a
    ถ้า บิตเลข(m,2)  > 0.5 { เส้นเลข(cx,cy,cz, rx,ry,rz, ux,uy,uz, s, hw,hh,  hw,0.0) }            # b
    ถ้า บิตเลข(m,4)  > 0.5 { เส้นเลข(cx,cy,cz, rx,ry,rz, ux,uy,uz, s, hw,0.0,  hw,0.0-hh) }        # c
    ถ้า บิตเลข(m,8)  > 0.5 { เส้นเลข(cx,cy,cz, rx,ry,rz, ux,uy,uz, s, 0.0-hw,0.0-hh,  hw,0.0-hh) } # d
    ถ้า บิตเลข(m,16) > 0.5 { เส้นเลข(cx,cy,cz, rx,ry,rz, ux,uy,uz, s, 0.0-hw,0.0,  0.0-hw,0.0-hh) }# e
    ถ้า บิตเลข(m,32) > 0.5 { เส้นเลข(cx,cy,cz, rx,ry,rz, ux,uy,uz, s, 0.0-hw,hh,  0.0-hw,0.0) }    # f
    ถ้า บิตเลข(m,64) > 0.5 { เส้นเลข(cx,cy,cz, rx,ry,rz, ux,uy,uz, s, 0.0-hw,0.0,  hw,0.0) }        # g
    คืน 0
}
# เลขด่าน 1-12 จัดกึ่งกลางหน้า (1 หรือ 2 หลัก) — กึ่งกลางกลุ่มอยู่ที่ (cx,cy,cz)
ฟังก์ชัน วาดเลขเต๋า(num, cx,cy,cz, rx,ry,rz, ux,uy,uz, s) {
    ถ้า num < 10 { วาดเลข3มิติ(num, cx,cy,cz, rx,ry,rz, ux,uy,uz, s);  คืน 0 }
    bind o = 0.42*s
    วาดเลข3มิติ(1,       cx - rx*o, cy - ry*o, cz - rz*o, rx,ry,rz, ux,uy,uz, s*0.9)
    วาดเลข3มิติ(num-10,  cx + rx*o, cy + ry*o, cz + rz*o, rx,ry,rz, ux,uy,uz, s*0.9)
    คืน 0
}

ฟังก์ชัน วาดเลขเต๋าเดมอน(num, cx,cy,cz, rx,ry,rz, ux,uy,uz, s) {
    boot_text_style()
    bind row = list_get(โรมัน, floor(num)-1)
    bind n = ความยาว(row)
    bind adv = s * 0.48
    bind start = 0.0 - (n - 1.0) * adv * 0.5
    bind j = 0
    ขณะที่ j < n {
        bind code = list_get(row, j)
        bind off = start + j*adv
        วาดอบเมช(list_get(_กลิฟเดมอน, code), cx+rx*off, cy+ry*off, cz+rz*off, rx,ry,rz, ux,uy,uz, s)
        bind j = j + 1
    }
    ตั้งแฟลตเชด(0)
    คืน 0
}
#   คืน index หน้าที่หันเข้ากล้องมากสุด ( = ด่านที่เลือก )
#   selFace = หน้าที่เลือก (ไฮไลต์ + เต๋าหมุนมาหา) ; คืน selFace
ฟังก์ชัน วาดลูกเต๋าด่าน(dieYaw, diePitch, t, ปลดล็อก, selFace) {
    bind R = 4.6;  bind dcx = 0.0;  bind dcy = 0.0 - 0.5;  bind dcz = 0.0
    bind front = floor(selFace)                          # หน้าที่เลือก (เต๋าเอียงมาให้หันหน้า)
    ตั้งแสงรอบข้าง(0.95)                                  # (หน้าเต๋าเป็น วาดสามเหลี่ยม3มิติ = ไม่กินไฟเอนจิน — คุมสว่างเองด้านล่าง)
    # ── ไฟส่องหน้าเต๋า: ดวงไฟอยู่ "ข้างหน้า" ลูกเต๋า (ฝั่งกล้อง -z) เยื้องซ้าย-บน ──
    # เดิมความสว่างเป็น 0.62 + (-nz)*0.38 = แลมเบิร์ตของไฟที่ทับตำแหน่งกล้องพอดี
    # ทุกหน้าจึงสว่างตามความ "ตรงหน้า" อย่างเดียว ไม่มีการโมเดลรูปทรง — ย้ายไฟออก
    # มาเยื้องแล้วคิดแลมเบิร์ตจากตำแหน่งจริงต่อหน้า หน้าที่หันเข้าไฟจึงสว่างไล่ระดับ
    bind ไฟx = 0.0 - 6.0;  bind ไฟy = 0.0 - 7.5;  bind ไฟz = 0.0 - 13.0
    # หน้าเต๋าวาดด้วย วาดสามเหลี่ยม3มิติ ซึ่ง "กินแสงฉาก" จริง ไม่ใช่สีล้วน —
    # ก่อนหน้านี้ตั้งแต่แอมเบียนต์อย่างเดียวโดยไม่เคยมี เพิ่มแสง เลย (และหน้าเมนู
    # ก่อนหน้าอาจ ล้างแสง ทิ้งไว้) ทุกหน้าจึงออกมาเกือบดำสนิท เห็นแค่เสี้ยวที่บังเอิญ
    # โดนแสงค้างจากฉากก่อน ใส่ไฟจริงที่ตำแหน่งเดียวกับที่ใช้คิดแลมเบิร์ตด้านล่าง
    ล้างแสง()
    เพิ่มแสง(ไฟx, ไฟy, ไฟz, 1.0, 0.97, 0.92, 1.55, 90.0)          # คีย์หน้าเต๋า
    เพิ่มแสง(9.0, 4.0, 0.0 - 16.0, 0.55, 0.65, 1.0, 0.75, 80.0)   # ฟิลเย็นอีกฝั่ง กันหน้าข้างดำสนิท
    # ── วาดหน้า (z-buffer = ทึบถูกต้อง) ──
    set_depth_test(1)
    bind i = 0
    ขณะที่ i < 12 {
        bind dd = ทิศหน้าเต๋า(i)
        bind nn = หมุนเต๋า(list_get(dd,0), list_get(dd,1), list_get(dd,2), dieYaw, diePitch)
        bind nx = list_get(nn,0);  bind ny = list_get(nn,1);  bind nz = list_get(nn,2)
        ถ้า nz < 0.06 {                                   # เฉพาะหน้าหน้า (cull หลัง)
            bind cx = dcx + nx*R;  bind cy = dcy + ny*R;  bind cz = dcz + nz*R
            # ฐานสัมผัสในระนาบหน้า: T = norm(up × N), B = N × T
            bind tx = nz;  bind ty = 0.0;  bind tz = 0.0 - nx
            bind tl = รากที่สอง(tx*tx + tz*tz);  ถ้า tl < 0.001 { bind tx = 1.0;  bind tz = 0.0;  bind tl = 1.0 }
            bind tx = tx/tl;  bind tz = tz/tl
            bind bx = ny*tz - nz*ty;  bind by = nz*tx - nx*tz;  bind bz = nx*ty - ny*tx
            bind lvl = i + 1
            bind locked = 0;  ถ้า lvl > ปลดล็อก { bind locked = 1 }
            # ── สีหน้า: ROYGBIV ประจำหน้า · ล็อก=หม่น · หน้าหน้า=สว่างเต้น ──
            # เดิมหน้า 10-12 ถูกทาทองรวดเป็น "โบนัส" — ตอนนี้ 10-12 คือไฟนอล 1-3
            # และโบนัสย้ายไปเป็นเลเวล 13 (หลังตอนจบ) ซึ่งไม่มีหน้าบนทรงสิบสองหน้า
            # ทุกหน้าจึงได้สีประจำตัวตามลำดับสีรุ้งเหมือนกันหมด
            bind hue = i * 0.5236
            bind fr = floor((ไซน์(hue)*0.5+0.5)*165+85);  bind fg = floor((ไซน์(hue+2.094)*0.5+0.5)*165+85);  bind fb = floor((ไซน์(hue+4.189)*0.5+0.5)*165+85)
            # ปลดล็อกโบนัส (เลเวล 13) แล้ว: หน้าสุดท้าย (เลเวล 12 = จบเกม) ไล่สีวน
            # เป็นสัญญาณว่ายังมีรอบพิเศษต่อจากตอนจบ
            ถ้า ปลดล็อก > 12.0 { ถ้า i == 11 {
                bind bh = t * 2.2
                bind fr = floor((ไซน์(bh)*0.5+0.5)*160+95)
                bind fg = floor((ไซน์(bh+2.094)*0.5+0.5)*160+95)
                bind fb = floor((ไซน์(bh+4.189)*0.5+0.5)*160+95)
            } }
            # แลมเบิร์ตจากดวงไฟหน้าเต๋า + แอมเบียนต์พื้น (ยังคุมพื้นไว้ไม่ให้หน้าข้างดำจนอ่านเลขไม่ออก)
            bind lvx = ไฟx - cx;  bind lvy = ไฟy - cy;  bind lvz = ไฟz - cz
            bind lvd = รากที่สอง(lvx*lvx + lvy*lvy + lvz*lvz);  ถ้า lvd < 0.001 { bind lvd = 0.001 }
            bind ndl = (nx*lvx + ny*lvy + nz*lvz) / lvd
            ถ้า ndl < 0.0 { bind ndl = 0.0 }
            bind sh = 0.58 + ndl*0.62                       # พื้น 0.58: หน้าที่หันหนีไฟก็ยังอ่านเลขออก ไม่ดำสนิท
            ถ้า sh > 1.18 { bind sh = 1.18 }
            ถ้า i == front { bind sh = sh*0.45 + (0.94 + ไซน์(t*5.0)*0.08) }   # หน้าที่เลือกเด่นไว้เหมือนเดิม
            ถ้า locked == 1 { bind fr = floor(fr*0.28);  bind fg = floor(fg*0.28);  bind fb = floor(fb*0.30) }
            สีดินสอ(floor(fr*sh), floor(fg*sh), floor(fb*sh))
            # ── เพนตากอนหน้า (พัด 5 สามเหลี่ยมจากศูนย์กลาง) ──
            bind pr = 1.95;  bind fa = t*0.25 + i
            bind k = 0
            ขณะที่ k < 5 {
                bind a0 = k*1.2566 + fa;  bind a1 = (k+1)*1.2566 + fa
                bind p0x = cx + (โคไซน์(a0)*tx + ไซน์(a0)*bx)*pr;  bind p0y = cy + (โคไซน์(a0)*ty + ไซน์(a0)*by)*pr;  bind p0z = cz + (โคไซน์(a0)*tz + ไซน์(a0)*bz)*pr
                bind p1x = cx + (โคไซน์(a1)*tx + ไซน์(a1)*bx)*pr;  bind p1y = cy + (โคไซน์(a1)*ty + ไซน์(a1)*by)*pr;  bind p1z = cz + (โคไซน์(a1)*tz + ไซน์(a1)*bz)*pr
                วาดสามเหลี่ยม3มิติ(cx, cy, cz, p0x, p0y, p0z, p1x, p1y, p1z)
                bind k = k + 1
            }
            # ── เลขด่าน: Daemon roman glyphs จัดกึ่งกลางบน face tangent plane ──
            bind rxL = tx;  bind ryL = ty;  bind rzL = tz
            bind uxL = bx;  bind uyL = by;  bind uzL = bz
            bind ls = 1.00;  ถ้า i == front { bind ls = 1.25 }
            ถ้า locked == 1 { สีดินสอ(135, 105, 110) }
            มิฉะนั้น {
                ถ้า i == front { bind hg = t*3.0;  สีดินสอ(floor((ไซน์(hg)*0.5+0.5)*95+160), floor((ไซน์(hg+2.094)*0.5+0.5)*95+160), floor((ไซน์(hg+4.189)*0.5+0.5)*95+160)) }
                มิฉะนั้น { สีดินสอ(245, 248, 255) }
            }
            bind lo = 0.45
            วาดเลขเต๋าเดมอน(lvl, cx+nx*lo, cy+ny*lo, cz+nz*lo,  rxL,ryL,rzL,  uxL,uyL,uzL,  ls)
            set_depth_test(1)
            set_flat_shade(0.0)
        }
        bind i = i + 1
    }
    flush_3d()
    set_depth_test(0)
    # ── หางเรืองแสงของเลขไวร์เฟรม (พาสเสริม additive, painter's — หางไม่ขาดเพราะไม่มี z-reject) ──
    โหมดผสม(1)
    bind i2 = 0
    ขณะที่ i2 < 12 {
        bind dd2 = ทิศหน้าเต๋า(i2)
        bind nn2 = หมุนเต๋า(list_get(dd2,0), list_get(dd2,1), list_get(dd2,2), dieYaw, diePitch)
        bind nx2 = list_get(nn2,0);  bind ny2 = list_get(nn2,1);  bind nz2 = list_get(nn2,2)
        ถ้า nz2 < 0.02 {
            bind cx2 = dcx + nx2*R;  bind cy2 = dcy + ny2*R;  bind cz2 = dcz + nz2*R
            bind tx2 = nz2;  bind ty2 = 0.0;  bind tz2 = 0.0 - nx2
            bind tl2 = รากที่สอง(tx2*tx2 + tz2*tz2);  ถ้า tl2 < 0.001 { bind tx2 = 1.0;  bind tz2 = 0.0;  bind tl2 = 1.0 }
            bind tx2 = tx2/tl2;  bind tz2 = tz2/tl2
            bind bx2 = ny2*tz2 - nz2*ty2;  bind by2 = nz2*tx2 - nx2*tz2;  bind bz2 = nx2*ty2 - ny2*tx2
            bind lvl2 = i2 + 1
            bind lck2 = 0;  ถ้า lvl2 > ปลดล็อก { bind lck2 = 1 }
            bind hue2 = i2*0.5236 + t
            bind gr = ไซน์(hue2)*0.5+0.5;  bind gg = ไซน์(hue2+2.094)*0.5+0.5;  bind gb = ไซน์(hue2+4.189)*0.5+0.5
            ถ้า lck2 == 1 { bind gr = 0.35;  bind gg = 0.18;  bind gb = 0.2 }
            bind lo2 = 0.45;  bind lsG = 1.00;  ถ้า i2 == front { bind lsG = 1.25 }
            bind td2 = 5
            ขณะที่ td2 >= 1 {
                bind gf = (1.0 - td2/6.0) * 65.0
                สีดินสอ(floor(gr*gf), floor(gg*gf), floor(gb*gf))
                วาดเลขเต๋าเดมอน(lvl2, cx2+nx2*lo2, cy2+ny2*lo2, cz2+nz2*lo2,  tx2,ty2,tz2,  bx2,by2,bz2,  lsG)
                bind td2 = td2 - 1
            }
        }
        bind i2 = i2 + 1
    }
    flush_3d()
    โหมดผสม(0)
    # ── ป้าย 2D: ด่านที่เลือก + สถานะล็อก ──
    bind fl = front + 1
    ถ้า fl > ปลดล็อก { สีดินสอ(190, 110, 110);  วาดสตริง("locked - clear the level before", 0.0, 7.6, 0.0, 0.5, 1) }
    มิฉะนั้น {
        ถ้า front > 8 { สีดินสอ(255, 210, 110);  วาดสตริง("bonus level " + to_str(fl), 0.0, 7.6, 0.0, 0.6, 1) }
        มิฉะนั้น { สีดินสอ(170, 220, 200);  วาดสตริง("level " + to_str(fl), 0.0, 7.6, 0.0, 0.6, 1) }
    }
    สีดินสอ(150, 160, 195);  วาดสตริง("[<-/->] spin   [enter] play   [esc] back", 0.0, 8.6, 0.0, 0.42, 1)
    flush_3d()
    คืน front
}
# เมนูเลือกภาษา: แต่ละตัวเลือกเป็นชื่อภาษาของตัวเอง (endonym, ฟอนต์ของภาษานั้น)
# ชื่อภาษาแต่ละภาษา เขียนด้วยสคริปต์ของภาษานั้นเอง (ใช้ทั้งจอเลือกภาษาและ
# แถวสรุปค่าปัจจุบันในหน้าตัวเลือก)
ฟังก์ชัน วาดชื่อภาษา(lang, x, y, sc) {
    ถ้า lang == 0 { วาดข้อความ(คำ_เดมอน, x, y, 0.0, sc, 0) }
    ถ้า lang == 1 { วาดข้อความ(คำ_อังกฤษ, x, y, 0.0, sc, 1) }
    ถ้า lang == 2 { วาดวลีเมนู(2, 6, x, y, sc*1.53) }
    ถ้า lang == 3 { วาดวลีเมนู(3, 6, x, y, sc*1.76) }
    ถ้า lang == 4 { วาดวลีเมนู(4, 6, x, y, sc*1.76) }
    ถ้า lang == 5 { วาดวลีเมนู(5, 6, x, y, sc*1.76) }
}

# หัวข้อจอเลือกภาษา — บอกว่ากำลังตั้งค่าอันไหนอยู่
#   0 = ตั้งภาษารวมทุกอย่าง (เมนูหลัก → Language)
#   1 = ภาษา UI/เมนู · 2 = ภาษาพากย์ · 3 = ภาษาซับ  (ทั้งสามมาจากหน้า Options)
ฟังก์ชัน วาดหัวข้อเลือกภาษา(หัวข้อ, y) {
    สีดินสอ(245, 232, 170)
    ถ้า หัวข้อ == 0 { วาดสตริง("set global language", 0.0, y, 0.0, 0.62, 1) }
    ถ้า หัวข้อ == 1 { วาดสตริง("ui / menu language", 0.0, y, 0.0, 0.62, 1) }
    ถ้า หัวข้อ == 2 { วาดสตริง("voiceover language", 0.0, y, 0.0, 0.62, 1) }
    ถ้า หัวข้อ == 3 { วาดสตริง("subtitle language", 0.0, y, 0.0, 0.62, 1) }
    สีดินสอ(120, 125, 155)
    ถ้า หัวข้อ == 0 { วาดสตริง("sets dub, subtitles and menus", 0.0, y + 1.05, 0.0, 0.34, 1) }
    ถ้า หัวข้อ == 2 { วาดสตริง("spoken dialogue audio", 0.0, y + 1.05, 0.0, 0.34, 1) }
    ถ้า หัวข้อ == 3 { วาดสตริง("on-screen dialogue text", 0.0, y + 1.05, 0.0, 0.34, 1) }
    ถ้า หัวข้อ == 1 { วาดสตริง("menus and interface text", 0.0, y + 1.05, 0.0, 0.34, 1) }
    flush_3d()
}

ฟังก์ชัน วาดเมนูเลือกภาษา(sel, t, หัวข้อ) {
    วาดหัวข้อเลือกภาษา(หัวข้อ, 0.0 - 7.6)
    bind i = 0
    ขณะที่ i < 6 {
        bind yy = 0.0 - 4.2 + i * 1.92
        ถ้า i == sel {
            เคอร์เซอร์หมึก(0.0-7.0, yy, 0.0, 0.7, t)
            สีดินสอ(235, 220, 145)
        }
        มิฉะนั้น { สีดินสอ(110, 110, 140) }
        วาดชื่อภาษา(i, 0.0, yy, 0.85)
        bind i = i + 1
    }
    flush_3d()
}

# ── พื้นหลังเมนู ──────────────────────────────────────────────────────────────
ฟังก์ชัน วาดพื้นหลังเมนู(กรอบ) {
    เติม(2, 2, 9)
    ตั้งหมอก(0, 0, 0, 0.0, 0.0)        # ปิดหมอก (กันหมอกน้ำเงินจากไตเติลรั่วเข้าเมนู)
    set_flat_shade(0.0)                 # LIT (เซล): เมนูรับแสง → มีมิติ/สี (ไม่แบนตาย/unlit)
    ล้างแสง()                           # เคลียร์ไฟทุกเฟรม (ไฟไม่สะสม/ไม่ลากค้าง) แล้วตั้งใหม่ด้านล่าง
    ตั้งแสงรอบข้าง(0.62)
    เพิ่มแสง(0.0, 0.0-5.0, 0.0-13.0, 1.0, 0.92, 0.82, 1.25, 280.0)   # ไฟนุ่มหน้าฉาก (เมนูสว่าง+มีมิติ)
    # ── โซ่ไฟ 3 ดวงวิ่งตามเส้นปมเซลติก (ส่องเมนูจริง — เส้นทางเดียวกับ วาดมณฑล) ──
    bind lt = กรอบ * 0.01
    bind li = 0
    ขณะที่ li < 3 {
        bind lf = lt*0.06 + li/3.0;  bind lf = lf - floor(lf)
        bind ls2 = 6.2832 * lf
        bind lx = 6.0*โคไซน์(ls2) + 2.4*โคไซน์(5.0*ls2)
        bind ly = 6.0*ไซน์(ls2) - 2.4*ไซน์(5.0*ls2)
        bind lz = 2.2*ไซน์(10.0*ls2) - 4.0
        bind lh = lf*6.2832 + lt
        เพิ่มแสง(lx, ly, lz, ไซน์(lh)*0.5+0.5, ไซน์(lh+2.094)*0.5+0.5, ไซน์(lh+4.189)*0.5+0.5, 1.2, 120.0)
        bind li = li + 1
    }
    ตั้งกล้องมอง(0.0, 0.0, -22.0, 0.0, 0.0, 0.0)
    ตั้งระยะห่าง(2.8)
    โหมดผสม(1)
    วาดมณฑล(กรอบ)
    flush_3d()
    โหมดผสม(0)
}

# ── พรีโหลด: หน้าจอโหลด (วงแหวนลูกบาศก์สีรุ้ง + "loading") นาน dur วินาที ──
ฟังก์ชัน พรีโหลด(dur) {
    bind t0 = เวลาปัจจุบัน()
    bind go = 1
    ขณะที่ go == 1 {
        bind tt = เวลาปัจจุบัน() - t0
        ถ้า tt >= dur { bind go = 0 }
        เติม(3, 2, 10)
        ตั้งแสงรอบข้าง(0.5)
        ตั้งกล้องมอง(0.0, 0.0, -22.0, 0.0, 0.0, 0.0)
        ตั้งระยะห่าง(2.8)
        โหมดผสม(1)
        bind k = 0
        ขณะที่ k < 16 {
            bind h = tt*1.6 + k*0.3
            # ── หางดาวหาง: สำเนาอดีต 7 ตำแหน่งตามวงโคจร จางลงไปทางหาง (additive) ──
            bind td = 6
            ขณะที่ td >= 0 {
                bind aa = tt*3.5 + k*0.3927 - td*0.09
                bind rr = 4.5 + ไซน์(tt*4.0 + k*0.5 - td*0.09)*0.7
                bind fade = 1.0 - td/7.0
                สีดินสอ(floor(((ไซน์(h)*0.5+0.5)*200+40)*fade), floor(((ไซน์(h+2.094)*0.5+0.5)*200+40)*fade), floor(((ไซน์(h+4.189)*0.5+0.5)*200+40)*fade))
                วาดลูกบาศก์(โคไซน์(aa)*rr, ไซน์(aa)*rr, 0.0, 0.45*(0.35+0.65*fade))
                bind td = td - 1
            }
            bind k = k + 1
        }
        สีดินสอ(220, 225, 255)
        วาดข้อความ(คำ_โหลดดิ้ง, 0.0, 9.0, 0.0, 0.8, 1)
        flush_3d()
        โหมดผสม(0)
        แสดงผล()
    }
}

# ── เสียงชนพื้น/ผนัง: ระฆังจูนกับโน้ตเพลงปัจจุบัน; ดัง+สูงขึ้นตามแรงปะทะ ──
#   freqs = เก็บความถี่ (โน้ต MIDI ของเพลงเลเวล), n = จำนวนโน้ต, impact = ความเร็วเข้าผิว
ฟังก์ชัน เสียงชน(freqs, n, impact, vol, x, y, z) {
    ถ้า impact > 0.08 { ถ้า n > 0.5 {
        bind ni = ปัดลง(impact * 26.0);  bind ni = ni - ปัดลง(ni / n) * n   # แรงขึ้น = โน้ตสูงขึ้น
        bind v = impact * 5.0;  ถ้า v > 1.0 { bind v = 1.0 }                # ดังขึ้นมาก ๆ ตามแรง
        bind v = v * vol                                                    # คูณระดับ SFX
        เสียงเอฟเฟกต์(x, y, z, 1.0, list_get(freqs, ni) * 4.0, v, 0.32 + impact*0.9, "sine")   # ×4 = สูงขึ้น 2 ออกเทฟ (เสียงเด้งลูก)
    } }
}

# ════════════════════════════════════════════════════════════════════════════
# บูตโลโก้ "made in ling country" (Daemon font)
# ════════════════════════════════════════════════════════════════════════════
# บูต: 4 บรรทัด "made / in ling / country / press start" ไถลเข้าตามรางรถไฟ 3D
#   (ฟอนต์อังกฤษ legible). NOTE: เวอร์ชัน 5 ภาษา (ไทย/เกา/ญี่/จีน) ต้องมีฟอนต์ CJK
#   ซึ่งดาวน์โหลด/เบคไม่ได้ที่นี่ (glyph baker = a-z) — ใช้ละติน
#   (ฟอนต์ CJK เบคแล้ว: Thai/Korean/Japanese/Chinese — แต่ละบรรทัดเป็นวลีแยก)
# ride-in ต่อตัวอักษร: ตัว j เข้าจากซ้าย(คู่)/ขวา(คี่) ไปยังตำแหน่ง, ease-out
# แทร็กละติน: row = อาเรย์ดัชนีกลิฟ (English/Daemon); dim = ความสว่าง (เฟดจบ)
ฟังก์ชัน แทร็กละติน(row, y, lang, sc, prog, t, dim) {
    bind n = ความยาว(row)
    bind adv = sc * 0.66
    bind sx = 0.0 - (n - 1) * adv * 0.5
    bind j = 0
    ขณะที่ j < n {
        bind code = list_get(row, j)
        ถ้า code >= 0 {
            bind lp = (prog - j*0.02) / 0.3
            ถ้า lp < 0.0 { bind lp = 0.0 }
            ถ้า lp > 1.0 { bind lp = 1.0 }
            bind ease = 1.0 - (1.0-lp)*(1.0-lp)
            bind side = 1.0
            ถ้า j - ปัดลง(j/2.0)*2.0 == 1.0 { bind side = 0.0-1.0 }
            bind lx = (sx + j*adv) + side*46.0*(1.0-ease)
            bind hu = t*1.6 + j*0.5
            สีดินสอ(floor(((ไซน์(hu)*0.5+0.5)*185+70)*dim), floor(((ไซน์(hu+2.094)*0.5+0.5)*185+70)*dim), floor(((ไซน์(hu+4.189)*0.5+0.5)*185+70)*dim))
            วาดกลิฟ(lang, code, lx, y, 0.0, sc)
        }
        bind j = j + 1
    }
}
# แทร็กวลี (CJK): lid 2=Thai 3=Korean 4=Japanese 5=Chinese
ฟังก์ชัน แทร็กวลี(lid, y, sc, prog, t, dim) {
    ตั้งแฟลตเชด(1)
    bind n = 0;  bind adv = []
    ถ้า lid == 2 { bind n = จำนวนอักขระ_Thai();     bind adv = กว้าง_Thai() }
    ถ้า lid == 3 { bind n = จำนวนอักขระ_Korean();   bind adv = กว้าง_Korean() }
    ถ้า lid == 4 { bind n = จำนวนอักขระ_Japanese(); bind adv = กว้าง_Japanese() }
    ถ้า lid == 5 { bind n = จำนวนอักขระ_Chinese();  bind adv = กว้าง_Chinese() }
    bind tw = 0.0;  bind a0 = 0
    ขณะที่ a0 < n { bind tw = tw + list_get(adv, a0) * sc;  bind a0 = a0 + 1 }
    bind cur = 0.0 - tw * 0.5
    bind j = 0
    ขณะที่ j < n {
        bind w = list_get(adv, j) * sc
        bind lp = (prog - j*0.03) / 0.3
        ถ้า lp < 0.0 { bind lp = 0.0 }
        ถ้า lp > 1.0 { bind lp = 1.0 }
        bind ease = 1.0 - (1.0-lp)*(1.0-lp)
        bind side = 1.0
        ถ้า j - ปัดลง(j/2.0)*2.0 == 1.0 { bind side = 0.0-1.0 }
        bind lx = (cur + w*0.5) + side*46.0*(1.0-ease)
        bind hu = t*1.6 + j*0.6 + lid
        สีดินสอ(floor(((ไซน์(hu)*0.5+0.5)*185+70)*dim), floor(((ไซน์(hu+2.094)*0.5+0.5)*185+70)*dim), floor(((ไซน์(hu+4.189)*0.5+0.5)*185+70)*dim))
        ถ้า lid == 2 { วาดอักขระ_Thai(j, lx, y, 0.0, 1,0,0, 0,0.0-1.0,0, sc) }
        ถ้า lid == 3 { วาดอักขระ_Korean(j, lx, y, 0.0, 1,0,0, 0,0.0-1.0,0, sc) }
        ถ้า lid == 4 { วาดอักขระ_Japanese(j, lx, y, 0.0, 1,0,0, 0,0.0-1.0,0, sc) }
        ถ้า lid == 5 { วาดอักขระ_Chinese(j, lx, y, 0.0, 1,0,0, 0,0.0-1.0,0, sc) }
        bind cur = cur + w
        bind j = j + 1
    }
    ตั้งแฟลตเชด(0)
}
ฟังก์ชัน วาดบูต(prog, t) {
    # ตัวอักษรลงตัว ~0.62 → ค้างไว้ → eased fade to black; no grey flash frame
    bind fadeIn = prog / 0.16;  ถ้า fadeIn > 1.0 { bind fadeIn = 1.0 };  ถ้า fadeIn < 0.0 { bind fadeIn = 0.0 }
    bind fadeOut = 1.0
    ถ้า prog > 0.78 { bind fadeOut = 1.0 - (prog-0.78)/0.22;  ถ้า fadeOut < 0.0 { bind fadeOut = 0.0 } }
    bind fadeIn = fadeIn*fadeIn*(3.0 - 2.0*fadeIn)
    bind fadeOut = fadeOut*fadeOut*(3.0 - 2.0*fadeOut)
    bind dim = fadeIn * fadeOut
    เติม(0, 0, 0)
    ตั้งกล้องมอง(0.0, 0.0, -30.0, 0.0, 0.0, 0.0)
    ตั้งระยะห่าง(3.0)
    # ── ตัวอักษร LIT (เซล): รับแสง → สี + มิติ; painter's + stable sort กันกะพริบ ──
    set_flat_shade(0.0)                                    # 0 = รับแสง (เซล) → กลิฟมีสี+มิติ (ไม่แบนตาย)
    set_depth_test(0)                                      # painter's (stable sort กันไตรฯ โคเพลนาร์กะพริบ)
    ล้างแสง()
    ตั้งแสงรอบข้าง(0.58)                                    # ฟิลสว่างพอ (ตัวอักษรไม่มืด)
    bind lp = t*0.6
    เพิ่มแสง(โคไซน์(lp)*16.0,     0.0-9.0, 0.0-14.0, 1.0, 0.85, 0.7, 1.25, 300.0)   # คีย์อุ่น หมุนช้า
    เพิ่มแสง(โคไซน์(lp+3.14)*16.0, 9.0,    0.0-14.0, 0.7, 0.85, 1.0, 1.15, 300.0)   # ฟิลเย็น
    # 6 ราง — English, Daemon, Thai, Korean, Japanese, Chinese (เส้นสีรุ้งไล่ แบนล้วน ไม่ลิต)
    โหมดผสม(0)
    bind r = 0
    ขณะที่ r < 6 {
        bind ry = 0.0 - 7.5 + r * 3.0
        bind rh = t*0.6 + r*0.5
        สีดินสอ(floor(((ไซน์(rh)*0.5+0.5)*200+55)*dim), floor(((ไซน์(rh+2.094)*0.5+0.5)*200+55)*dim), floor(((ไซน์(rh+4.189)*0.5+0.5)*205+50)*dim))
        วาดเส้น3มิติ(0.0-22.0, ry + 1.3, 0.0,  22.0, ry + 1.3, 0.0)
        bind r = r + 1
    }
    flush_3d()
    # ── ตัวอักษร 3D: โหมดปกติ + รับแสง (เซล) → สีคมชัด มีมิติ ไม่กะพริบ ──
    โหมดผสม(0)
    # ทยอยเข้า (settle ~prog 0.6) → ถือไว้ → smooth fade to black
    แทร็กละติน(คำ_บูต,    0.0-7.5, 1, 0.95, prog - 0.00, t, dim)   # English
    แทร็กละติน(คำ_โรมาจิ, 0.0-4.5, 0, 0.85, prog - 0.04, t, dim)   # Daemon โรมาจิ
    แทร็กวลี(2, 0.0-1.5, 1.5, prog - 0.08, t, dim)                 # Thai
    แทร็กวลี(3, 1.5,     1.7, prog - 0.12, t, dim)                 # Korean
    แทร็กวลี(4, 4.5,     1.7, prog - 0.16, t, dim)                 # Japanese
    แทร็กวลี(5, 7.5,     1.7, prog - 0.20, t, dim)                 # Chinese
    flush_3d()
    # ── คืนสถานะ: ปิดพาสบวก + แบนดิบ + ล้างไฟบูต (ไม่รั่วเข้าไตเติล) ──
    โหมดผสม(0)
    set_flat_shade(1.0)
    ล้างแสง()
}

# ════════════════════════════════════════════════════════════════════════════
# หน้าชื่อเรื่อง: มณฑล + วงแหวนกลิฟ 8 วง (Daemon art)
# ════════════════════════════════════════════════════════════════════════════
ฟังก์ชัน วาดลูกบาศก์(cx, cy, cz, a) {
    วาดเส้น3มิติ(cx-a,cy-a,cz-a, cx+a,cy-a,cz-a)
    วาดเส้น3มิติ(cx+a,cy-a,cz-a, cx+a,cy-a,cz+a)
    วาดเส้น3มิติ(cx+a,cy-a,cz+a, cx-a,cy-a,cz+a)
    วาดเส้น3มิติ(cx-a,cy-a,cz+a, cx-a,cy-a,cz-a)
    วาดเส้น3มิติ(cx-a,cy+a,cz-a, cx+a,cy+a,cz-a)
    วาดเส้น3มิติ(cx+a,cy+a,cz-a, cx+a,cy+a,cz+a)
    วาดเส้น3มิติ(cx+a,cy+a,cz+a, cx-a,cy+a,cz+a)
    วาดเส้น3มิติ(cx-a,cy+a,cz+a, cx-a,cy+a,cz-a)
    วาดเส้น3มิติ(cx-a,cy-a,cz-a, cx-a,cy+a,cz-a)
    วาดเส้น3มิติ(cx+a,cy-a,cz-a, cx+a,cy+a,cz-a)
    วาดเส้น3มิติ(cx+a,cy-a,cz+a, cx+a,cy+a,cz+a)
    วาดเส้น3มิติ(cx-a,cy-a,cz+a, cx-a,cy+a,cz+a)
}

ฟังก์ชัน วาดมณฑล(กรอบ) {
    # ── โซ่แสงไหลไปตามเส้นปมเซลติก (knotwork rosette) — เรืองแสงแบบ additive ──
    #   เส้นทางปิด: x = a·cos s + b·cos(k·s) ,  y = a·sin s − b·sin(k·s)  (k=5 → 6 กลีบปม)
    bind tt = กรอบ * 0.01
    bind N = 30.0
    bind i = 0
    ขณะที่ i < N {
        bind f = tt*0.06 + i/N;  bind f = f - floor(f)      # เฟสไหลของแต่ละดวง (0..1)
        bind s = 6.2832 * f
        bind ex = 6.0*โคไซน์(s) + 2.4*โคไซน์(5.0*s)
        bind ey = 6.0*ไซน์(s) - 2.4*ไซน์(5.0*s)
        bind ez = 2.2*ไซน์(10.0*s)                          # สานขึ้น/ลง (ปมทับกัน)
        bind br = 0.55 + 0.45*ไซน์(s*2.5 + tt*2.0)          # กะพริบนุ่มไล่ไปตามโซ่
        bind ขนาด = 0.26 + 0.16*(ไซน์(f*12.566 + tt*3.0)*0.5+0.5)
        bind h = f*6.2832 + tt                              # ROYGBIV ตามตำแหน่งบนเส้น
        สีดินสอ(
            floor((ไซน์(h)         * 0.5 + 0.5) * 205 * br + 15),
            floor((ไซน์(h + 2.094) * 0.5 + 0.5) * 205 * br + 15),
            floor((ไซน์(h + 4.189) * 0.5 + 0.5) * 205 * br + 15)
        )
        วาดลูกบาศก์(ex, ey, ez, ขนาด)
        bind i = i + 1
    }
}

ฟังก์ชัน เริ่มฝูง() {
    ถ้า ฝูงInit == 1 { คืน 0 }
    bind i = 0
    bind nx = []
    bind ny = []
    bind nz = []
    bind nvx = []
    bind nvy = []
    bind nvz = []
    ขณะที่ i < ฝูงN {
        bind a = i * 2.39996
        bind r = 2.8 + (i - ปัดลง(i/5.0)*5.0) * 0.7
        bind px = โคไซน์(a) * r
        bind py = (i - ปัดลง(i/4.0)*4.0 - 1.5) * 0.75
        bind pz = ไซน์(a) * r
        bind vx = ไซน์(i*1.7) * 0.08
        bind vy = โคไซน์(i*1.3) * 0.05
        bind vz = โคไซน์(i*1.9) * 0.08
        bind nx = list_push(nx, px)
        bind ny = list_push(ny, py)
        bind nz = list_push(nz, pz)
        bind nvx = list_push(nvx, vx)
        bind nvy = list_push(nvy, vy)
        bind nvz = list_push(nvz, vz)
        bind i = i + 1
    }
    bind ฝูงX = nx;  bind ฝูงY = ny;  bind ฝูงZ = nz
    bind ฝูงVx = nvx;  bind ฝูงVy = nvy;  bind ฝูงVz = nvz
    bind ฝูงInit = 1
    คืน 0
}

ฟังก์ชัน อัปเดตฝูง(t) {
    เริ่มฝูง()
    bind nx = []
    bind ny = []
    bind nz = []
    bind nvx = []
    bind nvy = []
    bind nvz = []
    bind i = 0
    ขณะที่ i < ฝูงN {
        bind xi = list_get(ฝูงX, i);  bind yi = list_get(ฝูงY, i);  bind zi = list_get(ฝูงZ, i)
        bind vxi = list_get(ฝูงVx, i);  bind vyi = list_get(ฝูงVy, i);  bind vzi = list_get(ฝูงVz, i)
        bind cx = 0.0; bind cy = 0.0; bind cz = 0.0
        bind avx = 0.0; bind avy = 0.0; bind avz = 0.0
        bind sx = 0.0; bind sy = 0.0; bind sz = 0.0
        bind c = 0.0
        bind j = 0
        ขณะที่ j < ฝูงN {
            ถ้า j != i {
                bind dx = list_get(ฝูงX, j) - xi
                bind dy = list_get(ฝูงY, j) - yi
                bind dz = list_get(ฝูงZ, j) - zi
                bind d2 = dx*dx + dy*dy + dz*dz
                ถ้า d2 < 56.25 {
                    bind c = c + 1.0
                    bind cx = cx + list_get(ฝูงX, j)
                    bind cy = cy + list_get(ฝูงY, j)
                    bind cz = cz + list_get(ฝูงZ, j)
                    bind avx = avx + list_get(ฝูงVx, j)
                    bind avy = avy + list_get(ฝูงVy, j)
                    bind avz = avz + list_get(ฝูงVz, j)
                    ถ้า d2 < 4.0 {
                        bind dd = รากที่สอง(d2) + 0.001
                        bind sx = sx - dx / dd
                        bind sy = sy - dy / dd
                        bind sz = sz - dz / dd
                    }
                }
            }
            bind j = j + 1
        }
        bind ax = 0.0; bind ay = 0.0; bind az = 0.0
        ถ้า c > 0.5 {
            bind cx = cx / c;  bind cy = cy / c;  bind cz = cz / c
            bind avx = avx / c; bind avy = avy / c; bind avz = avz / c
            bind ax = ax + (cx - xi) * 0.018 + (avx - vxi) * 0.08 + sx * 0.11
            bind ay = ay + (cy - yi) * 0.018 + (avy - vyi) * 0.08 + sy * 0.11
            bind az = az + (cz - zi) * 0.018 + (avz - vzi) * 0.08 + sz * 0.11
        }
        # หมุนวนรอบแกน Y + โยกขึ้นลงเล็กน้อย
        bind ax = ax + (0.0 - zi) * 0.012 + ไซน์(t*2.6 + i*0.7) * 0.006
        bind ay = ay + ไซน์(t*4.7 + i*0.9) * 0.009
        bind az = az + xi * 0.012 + โคไซน์(t*2.2 + i*0.8) * 0.006
        # ขอบโดมลูกหมึก
        bind rr = รากที่สอง(xi*xi + yi*yi + zi*zi)
        ถ้า rr > 8.4 {
            bind ax = ax + (0.0 - xi) * 0.05
            bind ay = ay + (0.0 - yi) * 0.05
            bind az = az + (0.0 - zi) * 0.05
        }
        bind vxi = (vxi + ax) * 0.985
        bind vyi = (vyi + ay) * 0.985
        bind vzi = (vzi + az) * 0.985
        bind sp = รากที่สอง(vxi*vxi + vyi*vyi + vzi*vzi) + 0.0001
        ถ้า sp > 0.55 { bind vxi = vxi / sp * 0.55; bind vyi = vyi / sp * 0.55; bind vzi = vzi / sp * 0.55 }
        ถ้า sp < 0.05 { bind vxi = vxi / sp * 0.05; bind vyi = vyi / sp * 0.05; bind vzi = vzi / sp * 0.05 }
        bind xi = xi + vxi
        bind yi = yi + vyi
        bind zi = zi + vzi
        bind nx = list_push(nx, xi);  bind ny = list_push(ny, yi);  bind nz = list_push(nz, zi)
        bind nvx = list_push(nvx, vxi); bind nvy = list_push(nvy, vyi); bind nvz = list_push(nvz, vzi)
        bind i = i + 1
    }
    bind ฝูงX = nx;  bind ฝูงY = ny;  bind ฝูงZ = nz
    bind ฝูงVx = nvx;  bind ฝูงVy = nvy;  bind ฝูงVz = nvz
    คืน 0
}

# ── ตำแหน่งฝูงตัวอักษร (procedural) — ling ฟังก์ชันแก้ตัวแปรโกลบอลไม่ได้ จึงคำนวณจาก t ──
#   โคจรมุมทอง + รัศมีหายใจ + ส่ายเล็กน้อย → ดูเป็นฝูงหมุนวน (stateless, ทำงานทุกเฟรม)
ฟังก์ชัน ตำแหน่งฝูง(i, t) {
    bind a = i * 2.39996 + t * 0.55
    bind rad = 4.6 + ไซน์(t*0.5 + i*0.7) * 2.6
    bind px = โคไซน์(a) * rad + ไซน์(t*1.3 + i*1.1) * 0.6
    bind pz = ไซน์(a) * rad + โคไซน์(t*1.1 + i*0.9) * 0.6
    bind py = ไซน์(t*0.8 + i*0.9) * 3.2 + โคไซน์(t*1.7 + i*0.5) * 0.8
    คืน [px, py, pz]
}

ฟังก์ชัน วาดวงแหวนตัวอักษร(กรอบ, pulse) {
    bind t = กรอบ * 0.01
    ถ้า pulse < 0.0 { bind pulse = 0.0 }
    ถ้า pulse > 1.0 { bind pulse = 1.0 }

    liquid_step(หมึกRB, 0.016)
    liquid_step(หมึกGold, 0.016)
    liquid_splat(หมึกRB, 24.0 + ไซน์(t*1.7)*11.0, 24.0 + โคไซน์(t*1.3)*9.0, 0.0, 0.22, 3.1)
    liquid_splat(หมึกRB, 24.0 + โคไซน์(t*1.1)*10.5, 24.0 + ไซน์(t*1.9)*8.0, 1.0, 0.20, 2.9)
    liquid_splat(หมึกGold, 24.0 + ไซน์(t*1.5+1.2)*9.5, 24.0 + โคไซน์(t*1.8+0.7)*7.5, 0.0, 0.20, 2.8)
    liquid_splat(หมึกGold, 24.0 + โคไซน์(t*1.4+2.0)*9.0, 24.0 + ไซน์(t*1.6+0.3)*8.5, 1.0, 0.20, 2.8)
    ถ้า pulse > 0.04 {
        bind ba = t * 1.7
        bind bx = 24.0 + โคไซน์(ba) * 17.0
        bind by = 24.0 + ไซน์(ba * 1.31) * 17.0
        liquid_splat(หมึกRB, bx, by, 0.0, 0.16 + pulse*0.34, 2.2 + pulse*4.8)
        liquid_splat(หมึกGold, 48.0-bx, 48.0-by, 1.0, 0.14 + pulse*0.30, 2.0 + pulse*4.2)
    }

    bind i = 0
    ขณะที่ i < ฝูงN {
        bind p = ตำแหน่งฝูง(i, t)
        bind px = list_get(p, 0)
        bind py = list_get(p, 1)
        bind pz = list_get(p, 2)
        bind u = 24.0 + px * 1.95
        bind v = 24.0 + pz * 1.95
        bind w = 24.0 + py * 1.8
        liquid_splat(หมึกRB, u, v, i - ปัดลง(i/2.0)*2.0, 0.08, 1.1)
        liquid_splat(หมึกGold, u, w, (i+1) - ปัดลง((i+1)/2.0)*2.0, 0.07, 1.0)
        bind i = i + 1
    }

    # ระนาบหมึกใหญ่ (kind 0) ที่ระดับฉาก = พื้นหลังหมึกเต็มจอ (เดิมแผ่นเล็กลอยกลาง)
    bind inkPulse = 1.0 + pulse * 0.16
    set_flat_shade(0.0)
    liquid_draw_surface(หมึกRB, 0.0, 0.0, 1.0, 0.0, 42.0*inkPulse, 42.0*inkPulse)
    liquid_draw_surface(หมึกGold, 0.0, 0.0, 1.5, 0.0, 36.0*inkPulse, 36.0*inkPulse)
    set_flat_shade(0.0)

    bind j = 0
    ขณะที่ j < ฝูงN {
        bind p2 = ตำแหน่งฝูง(j, t)
        bind px2 = list_get(p2, 0)
        bind py2 = list_get(p2, 1)
        bind pz2 = list_get(p2, 2)
        bind hh = t * 10.5 + j * 0.9
        สีดินสอ(
            floor((ไซน์(hh)         * 0.5 + 0.5) * 200 + 55),
            floor((ไซน์(hh + 2.094) * 0.5 + 0.5) * 200 + 55),
            floor((ไซน์(hh + 4.189) * 0.5 + 0.5) * 200 + 55)
        )
        bind code = (j * 7 + ปัดลง(t * 9.0)) % 26
        bind s = 0.43 + (j - ปัดลง(j/3.0)*3.0) * 0.06
        วาดกลิฟ3มิติ(code, px2, py2 + ไซน์(t*6.0 + j) * 0.22, pz2, t*2.4 + j*0.35, s)
        bind j = j + 1
    }
}

# ── เบิร์สต์ลูกบาศก์ 6 ใบ ตามจังหวะ MIDI (burst 1→0 = เพิ่งโดนโน้ต) ──
#   ท่าเต้นวิวัฒน์ตามเวลา: 4 ฟอร์เมชัน วนทีละ ~9 วิ มอร์ฟต่อเนื่อง
#   0 ระเบิด 6 ทิศ · 1 วงโคจรแหวน · 2 เกลียวเสาตั้ง · 3 เลขแปดลิสซาจู
bind ทิศ6 = [1.0,0.0,0.0,  0.0-1.0,0.0,0.0,  0.0,1.0,0.0,  0.0,0.0-1.0,0.0,  0.0,0.0,1.0,  0.0,0.0,0.0-1.0]
ฟังก์ชัน ตำแหน่งเบิร์สต์(m, k, t) {
    ถ้า m == 1 {                                                  # วงโคจรแหวนเอียง
        bind a1 = t*1.4 + k*1.047
        bind r1 = 7.4 + ไซน์(t*0.9 + k)*1.3
        คืน [โคไซน์(a1)*r1, ไซน์(t*1.8 + k*2.1)*2.4 + ไซน์(a1*2.0)*1.1, ไซน์(a1)*r1]
    }
    ถ้า m == 2 {                                                  # เกลียวเสาตั้ง (ไหลขึ้นวนซ้ำ)
        bind a2 = t*2.1 + k*1.047
        bind hy = t*3.1 + k*2.33
        bind hy2 = (hy - ปัดลง(hy/14.0)*14.0) - 7.0
        คืน [โคไซน์(a2)*4.4, hy2, ไซน์(a2)*4.4]
    }
    ถ้า m == 3 {                                                  # เลขแปดลิสซาจู 3 มิติ
        คืน [ไซน์(t*1.3 + k*1.047)*7.2, ไซน์(t*2.6 + k*2.094)*3.4, ไซน์(t*1.7 + k*0.7 + 1.5708)*5.2]
    }
    bind dx = list_get(ทิศ6, k*3);  bind dy = list_get(ทิศ6, k*3+1);  bind dz = list_get(ทิศ6, k*3+2)
    bind d0 = 4.6 + ไซน์(t*1.1 + k*0.9)*1.7                       # ระเบิด 6 ทิศ หายใจเข้าออก
    คืน [dx*d0, dy*d0, dz*d0]
}
ฟังก์ชัน วาดเบิร์สต์(burst, t) {
    bind ph = t / 9.0
    bind ph = ph - ปัดลง(ph/4.0)*4.0                              # เฟส 0..4 วน
    bind m0 = ปัดลง(ph)
    bind mf = ph - m0
    bind mf = mf*mf*(3.0 - 2.0*mf)                                # smoothstep มอร์ฟทั้งช่วง
    bind m1 = m0 + 1;  ถ้า m1 > 3 { bind m1 = 0 }
    bind sz = 0.5 + burst * 2.4
    bind k = 0
    ขณะที่ k < 6 {
        bind pA = ตำแหน่งเบิร์สต์(m0, k, t)
        bind pB = ตำแหน่งเบิร์สต์(m1, k, t)
        bind px = list_get(pA,0) + (list_get(pB,0) - list_get(pA,0))*mf
        bind py = list_get(pA,1) + (list_get(pB,1) - list_get(pA,1))*mf
        bind pz = list_get(pA,2) + (list_get(pB,2) - list_get(pA,2))*mf
        bind dx = list_get(ทิศ6, k*3);  bind dy = list_get(ทิศ6, k*3+1);  bind dz = list_get(ทิศ6, k*3+2)
        bind px = px + dx*burst*3.6;  bind py = py + dy*burst*3.6;  bind pz = pz + dz*burst*3.6   # โน้ต MIDI ยังเตะกล่องออกทุกฟอร์เมชัน
        bind h = t*1.5 + k*1.047
        bind br = burst*0.8 + 0.2
        สีดินสอ(floor(((ไซน์(h)*0.5+0.5)*200+30)*br), floor(((ไซน์(h+2.094)*0.5+0.5)*200+30)*br), floor(((ไซน์(h+4.189)*0.5+0.5)*200+30)*br))
        วาดลูกบาศก์(px, py, pz, sz)
        # เงาตาม (echo) — ลูกเล็กไล่หลังบนเส้นทางเดียวกัน = หางเคลื่อนไหว
        bind pE = ตำแหน่งเบิร์สต์(m0, k, t - 0.16)
        สีดินสอ(floor(((ไซน์(h)*0.5+0.5)*110+15)*br), floor(((ไซน์(h+2.094)*0.5+0.5)*110+15)*br), floor(((ไซน์(h+4.189)*0.5+0.5)*110+15)*br))
        วาดลูกบาศก์(list_get(pE,0), list_get(pE,1), list_get(pE,2), sz*0.5)
        bind k = k + 1
    }
}

# ── ชื่อเรื่องใหญ่ "Soul Symphony 2" หลายภาษา (วลีเบคจาก phrasebake) ──
#   lang 0/1=Latin · 2=ไทย · 3=เกาหลี · 4=ญี่ปุ่น · 5=จีน
#   targetW = ความกว้างเป้าหมายบนจอ → ปรับ sc ตามความกว้าง em ของแต่ละภาษา (จัดกึ่งกลาง)
ฟังก์ชัน วาดSS2(lang, ox, oy, targetW) {
    ถ้า lang == 2 { วาดSS2TH(ox, oy, targetW/6.46);  คืน 0 }
    ถ้า lang == 3 { วาดSS2KO(ox, oy, targetW/5.36);  คืน 0 }
    ถ้า lang == 4 { วาดSS2JA(ox, oy, targetW/9.84);  คืน 0 }
    ถ้า lang == 5 { วาดSS2ZH(ox, oy, targetW/5.65);  คืน 0 }
    วาดSS2EN(ox, oy, targetW/7.18)                                   # 0,1 = โรมัน/อังกฤษ
    คืน 0
}

ฟังก์ชัน วาดSS2รายตัว(ox, oy, targetW, t) {
    bind n = ความยาว(คำSS2)
    bind sc = targetW / 7.9
    bind adv = sc * 0.70
    bind sx = ox - (n - 1) * adv * 0.5
    bind i = 0
    ขณะที่ i < n {
        bind code = list_get(คำSS2, i)
        ถ้า code >= 0 {
            bind hh = t * 12.0 + i * 1.15
            สีดินสอ(
                floor((ไซน์(hh)         * 0.5 + 0.5) * 205 + 50),
                floor((ไซน์(hh + 2.094) * 0.5 + 0.5) * 205 + 50),
                floor((ไซน์(hh + 4.189) * 0.5 + 0.5) * 205 + 50)
            )
            bind lx = sx + i * adv + ไซน์(t*3.8 + i*1.3) * sc * 0.18
            bind ly = oy + ไซน์(t*5.7 + i*0.95) * sc * 0.36
            วาดกลิฟ3มิติหน้า(code, lx, ly, 0.0, t*2.2 + i*0.45, sc, t + i*0.11)
        }
        bind i = i + 1
    }
    คืน 0
}

ฟังก์ชัน วาดSS2ไดนามิก(lang, ox, oy, targetW, t) {
    ถ้า lang == 0 { วาดSS2รายตัว(ox, oy, targetW, t); คืน 0 }
    bind h = t * 10.0
    สีดินสอ(
        floor((ไซน์(h)         * 0.5 + 0.5) * 180 + 70),
        floor((ไซน์(h + 2.094) * 0.5 + 0.5) * 180 + 70),
        floor((ไซน์(h + 4.189) * 0.5 + 0.5) * 180 + 70)
    )
    วาดSS2(lang, ox, oy + ไซน์(t*5.2) * 0.22, targetW)
    คืน 0
}

ฟังก์ชัน วาดฉากชื่อเรื่อง(กรอบ, burst, lang) {
    bind t = กรอบ*0.01
    bind pulse = burst
    ถ้า pulse < 0.0 { bind pulse = 0.0 }
    ถ้า pulse > 1.0 { bind pulse = 1.0 }
    bind bgH = floor(ไซน์(กรอบ * 0.022) * 3.0 + 4.0)
    เติม(bgH, 0, 8)
    ตั้งกล้องมอง(0.0, 0.0, -22.0, 0.0, 0.0, 0.0)
    ตั้งระยะห่าง(2.8 + pulse*0.12)
    set_depth_test(1)
    ตั้งแสงรอบข้าง(0.4)
    ตั้งหมอก(30, 72, 158, 8.0, 34.0)        # หมอกน้ำเงิน: ตัวอักษร/ลูกบาศก์ไกล จางเป็นน้ำเงิน (เข้มขึ้น)
    โหมดผสม(1)

    ล้างแสง()
    เพิ่มแสง(ไซน์(กรอบ*0.011)*5.0,  4.0, โคไซน์(กรอบ*0.011)*3.0,  1.0,0.88,0.65, 3.0 + pulse*1.2,14.0)
    เพิ่มแสง(0.0-4.0, 2.5, 2.0,  0.30,0.80,1.0,  1.5 + pulse*0.8,10.0)

    # ── ปราสาทบนขอบฟ้าหมึก (วาด "ก่อน" กล่อง/วงแหวน → ของ additive ทับหน้าได้เสมอ) ──
    โหมดผสม(0)                                       # ทึบ — ไม่ใช่ additive แบบวงแหวน
    ตั้งหมอก(0, 0, 0, 0.0, 0.0)                     # ปิดหมอกเฉพาะพาสนี้ — สีวนต้องสด
    bind วยอ = 3.1416 + ไซน์(t*0.21)*0.05
    bind วเด่น = ปัดลง(t*4.3) - ปัดลง(ปัดลง(t*4.3)/5.0)*5.0      # ชิ้นเด่นวนสลับ
    bind วชิ้น = 0
    ขณะที่ วชิ้น < 5 {
        bind วม = _วังไต0
        ถ้า วชิ้น == 1 { bind วม = _วังไต1 };  ถ้า วชิ้น == 2 { bind วม = _วังไต2 }
        ถ้า วชิ้น == 3 { bind วม = _วังไต3 };  ถ้า วชิ้น == 4 { bind วม = _วังไต4 }
        bind วเกน = 1.05 + pulse*0.6                             # ทุกชิ้นวูบตามโน้ต MIDI
        ถ้า วชิ้น == วเด่น { bind วเกน = 1.15 + pulse*1.6 }      # ชิ้นเด่นแฟลชแรงสุด
        หมุนสีเมช(t*0.85 + วชิ้น*1.257, วเกน)       # วนครบวงคนละเฟสต่อชิ้น + เกนสว่างตามโน้ต
        mesh_draw(วม, 0.0, 1.02, 6.0, 12.0, วยอ, ไซน์(t*0.33)*0.008, 0.0, 0.0, 0.0, 0.0)
        bind วชิ้น = วชิ้น + 1
    }
    หมุนสีเมช(0.0, 1.0)
    flush_3d()
    แอ่งแสง(0.0, 1.0, 6.0, 11.0, 185, 205, 255, 0.45 + pulse*0.25)  # แสงจันทร์นวลสะท้อนหมึกใต้ปราสาท
    # ── ไฟวังเต้นตามโน้ต MIDI: ออร่าเสริมชิ้นเด่น + แอ่งแฟลร์ (เกนเมชคือแสงหลัก) ──
    โหมดผสม(1)
    bind วp = 0
    ขณะที่ วp < 5 {
        bind วGlow = 0.25 + pulse*0.7
        ถ้า วp == วเด่น { bind วGlow = 0.50 + pulse*1.5 }
        bind วh = t*0.85 + วp*1.257
        bind วr = floor(((ไซน์(วh)*0.5+0.5)*205+50)*วGlow);  ถ้า วr > 255 { bind วr = 255 }
        bind วg = floor(((ไซน์(วh+2.094)*0.5+0.5)*205+50)*วGlow);  ถ้า วg > 255 { bind วg = 255 }
        bind วb = floor(((ไซน์(วh+4.189)*0.5+0.5)*205+50)*วGlow);  ถ้า วb > 255 { bind วb = 255 }
        อนุภาคทรงกลม(0.0, 1.0 - 2.2 - วp*1.35, 6.0, 2.5 + วp*0.4, 8, t*2.0 + วp, วr, วg, วb)
        ถ้า วp == วเด่น { แอ่งแสง(0.0, 1.0, 6.0, 6.0 + pulse*5.0, วr, วg, วb, 0.25 + pulse*0.45) }
        bind วp = วp + 1
    }
    flush_3d()
    ตั้งหมอก(30, 72, 158, 8.0, 34.0)

    วาดวงแหวนตัวอักษร(กรอบ, pulse)
    วาดเบิร์สต์(pulse, t)
    flush_3d()

    set_depth_test(0)
    set_flat_shade(1.0)
    ตั้งหมอก(0, 0, 0, 0.0, 0.0)
    โหมดผสม(1)
    วาดSS2ไดนามิก(lang, 0.0, 0.0-4.8 + pulse*0.22, 15.8 + pulse*1.1, t + pulse*0.3)

    ถ้า (floor(กรอบ * 0.04) % 2) == 0 {
        สีดินสอ(230, 230, 255)
        วาดข้อความ(คำ_ปุ่ม, 0.0, 11.0, 0.0, 0.7, 0)
    }
    flush_3d()
    โหมดผสม(0)
    set_depth_test(0)
    แสดงผล()
}

# ════════════════════════════════════════════════════════════════════════════
# ข้อความจากสตริง (a-z) + สโนว์โกลบดาว สำหรับคัตซีน
# ════════════════════════════════════════════════════════════════════════════
ฟังก์ชัน ตัวอักษรเป็นดัชนี(ch) {
    ถ้า ch == "a" { คืน 0 }
    ถ้า ch == "b" { คืน 1 }
    ถ้า ch == "c" { คืน 2 }
    ถ้า ch == "d" { คืน 3 }
    ถ้า ch == "e" { คืน 4 }
    ถ้า ch == "f" { คืน 5 }
    ถ้า ch == "g" { คืน 6 }
    ถ้า ch == "h" { คืน 7 }
    ถ้า ch == "i" { คืน 8 }
    ถ้า ch == "j" { คืน 9 }
    ถ้า ch == "k" { คืน 10 }
    ถ้า ch == "l" { คืน 11 }
    ถ้า ch == "m" { คืน 12 }
    ถ้า ch == "n" { คืน 13 }
    ถ้า ch == "o" { คืน 14 }
    ถ้า ch == "p" { คืน 15 }
    ถ้า ch == "q" { คืน 16 }
    ถ้า ch == "r" { คืน 17 }
    ถ้า ch == "s" { คืน 18 }
    ถ้า ch == "t" { คืน 19 }
    ถ้า ch == "u" { คืน 20 }
    ถ้า ch == "v" { คืน 21 }
    ถ้า ch == "w" { คืน 22 }
    ถ้า ch == "x" { คืน 23 }
    ถ้า ch == "y" { คืน 24 }
    ถ้า ch == "z" { คืน 25 }
    ถ้า ch == "0" { คืน 26 }
    ถ้า ch == "1" { คืน 27 }
    ถ้า ch == "2" { คืน 28 }
    ถ้า ch == "3" { คืน 29 }
    ถ้า ch == "4" { คืน 30 }
    ถ้า ch == "5" { คืน 31 }
    ถ้า ch == "6" { คืน 32 }
    ถ้า ch == "7" { คืน 33 }
    ถ้า ch == "8" { คืน 34 }
    ถ้า ch == "9" { คืน 35 }
    ถ้า ch == "." { คืน 36 }
    ถ้า ch == "," { คืน 37 }
    ถ้า ch == "!" { คืน 38 }
    ถ้า ch == "?" { คืน 39 }
    ถ้า ch == "'" { คืน 40 }
    ถ้า ch == "-" { คืน 41 }
    ถ้า ch == ":" { คืน 42 }
    ถ้า ch == ";" { คืน 43 }
    ถ้า ch == "(" { คืน 44 }
    ถ้า ch == ")" { คืน 45 }
    ถ้า ch == "/" { คืน 46 }
    ถ้า ch == "+" { คืน 47 }
    คืน 0.0 - 1.0
}

# ── ขอบดำบทพูด (เรียกก่อน วาดสตริง ที่ตำแหน่ง/สเกลเดียวกัน): วาดซ้ำ 8 ทิศเป็นสีดำ
#   ล้วน offset ~1-2px (ประมาณจากความกว้างตัวอักษร adv=scale*0.66 ต่อความกว้างจอ
#   ทั่วไป ~1920px) ก่อนพื้นผิวสีจริงทับด้านบน — ไม่มีฮาโลเรืองแสง (ขอบต้องคมชัด) ──
ฟังก์ชัน วาดสตริงขอบ(s, ox, oy, oz, scale, lang) {
    boot_text_style()
    bind n = ความยาว(s)
    bind adv = scale * 0.66
    bind startx = ox - (n - 1) * adv * 0.5
    bind d = 0.013                                    # ~1-2px ที่ระยะกล้อง HUD ปกติ
    สีดินสอ(0, 0, 0)
    bind oi2 = 0
    ขณะที่ oi2 < 8 {
        bind ang = oi2 * 0.7854                       # 8 ทิศรอบตัว (ทุก 45°)
        bind dx = โคไซน์(ang) * d;  bind dy = ไซน์(ang) * d
        bind i = 0
        ขณะที่ i < n {
            bind ch = ส่วนสตริง(s, i, 1)
            bind code = ตัวอักษรเป็นดัชนี(ch)
            ถ้า code >= 0 {
                วาดกลิฟ(lang, code, startx + i * adv + dx, oy + dy, oz + 0.02, scale)
            }
            bind i = i + 1
        }
        bind oi2 = oi2 + 1
    }
}

ฟังก์ชัน วาดสตริง(s, ox, oy, oz, scale, lang) {
    boot_text_style()
    bind n = ความยาว(s)
    bind adv = scale * 0.66
    bind startx = ox - (n - 1) * adv * 0.5
    # ── ฮาโลเรืองแสง (emission): ผ่านบวก กลิฟใหญ่ขึ้น → แกนทึบทับ (ตัวอักษรเรืองแสงในตัว) ──
    โหมดผสม(1)
    bind ig = 0
    ขณะที่ ig < n {
        bind gch = ส่วนสตริง(s, ig, 1)
        bind gcode = ตัวอักษรเป็นดัชนี(gch)
        ถ้า gcode >= 0 { วาดกลิฟ(lang, gcode, startx + ig * adv, oy, oz + 0.15, scale*1.16) }
        bind ig = ig + 1
    }
    โหมดผสม(0)
    bind i = 0
    ขณะที่ i < n {
        bind ch = ส่วนสตริง(s, i, 1)
        bind code = ตัวอักษรเป็นดัชนี(ch)
        ถ้า code >= 0 {
            วาดกลิฟ(lang, code, startx + i * adv, oy, oz, scale)
        }
        bind i = i + 1
    }
}

# สโนว์โกลบดาว: ทรงกลมอนุภาคหนาแน่น + สัญลักษณ์เครื่องดนตรี + ตัวอักษรเดี่ยวลอย
#   gather ∈ [0,1] → ดึงสัญลักษณ์/ตัวอักษรเข้าหาดาวเคราะห์ (0,-14,0) ช่วงท้าย
# ── วนสีรอบเฉดเดิม ────────────────────────────────────────────────────────────
# หมุนเฉดสีของสีฐาน (r,g,b) ไปตาม h โดยคุมความแรงด้วย แรง (0..1)
# ใช้กับอนุภาค/เส้นที่ "สีมีความหมาย" อยู่แล้ว (น้ำเงิน=ราชา, ชมพู=ราชินี,
# ขาว=แสงเย็น, อุ่น=โคมไฟ) — วนสีเต็มสเกลจะทำลายความหมายนั้น จึงเบลนด์สีฐาน
# เข้ากับสายรุ้งที่ความสว่างเท่ากัน สีเลยยังอ่านออกว่าเป็นสีเดิม แต่ไล่เฉดตลอด
# ทำให้หางที่ เบลอเฟรม ค้างไว้มีสีเคลื่อน (เรียกตอนวาด = ก่อนพาสเบลอเฟรมเสมอ)
ฟังก์ชัน วนสีอ่อน(r, g, b, h, แรง) {
    bind สว่าง = (r + g + b) / 3.0
    bind cr = (ไซน์(h)         * 0.5 + 0.5) * สว่าง * 1.55
    bind cg = (ไซน์(h + 2.094) * 0.5 + 0.5) * สว่าง * 1.55
    bind cb = (ไซน์(h + 4.189) * 0.5 + 0.5) * สว่าง * 1.55
    bind orr = r*(1.0 - แรง) + cr*แรง
    bind og  = g*(1.0 - แรง) + cg*แรง
    bind ob  = b*(1.0 - แรง) + cb*แรง
    ถ้า orr > 255.0 { bind orr = 255.0 }
    ถ้า og  > 255.0 { bind og  = 255.0 }
    ถ้า ob  > 255.0 { bind ob  = 255.0 }
    ถ้า orr < 0.0 { bind orr = 0.0 }
    ถ้า og  < 0.0 { bind og  = 0.0 }
    ถ้า ob  < 0.0 { bind ob  = 0.0 }
    คืน [floor(orr), floor(og), floor(ob)]
}

# สีหมุนเวียนหนึ่งเฉด (h เรเดียน) → [r,g,b] คูณ fade แล้ว
# ใช้ร่วมกันทั้งอนุภาคและไวร์เฟรมของฉากอินโทร ให้ทุกชั้นวนสีด้วยสูตรเดียวกัน
ฟังก์ชัน เมฆสีวน(h, fade, ฐาน, ช่วง) {
    คืน [
        floor(((ไซน์(h)         * 0.5 + 0.5) * ช่วง + ฐาน) * fade),
        floor(((ไซน์(h + 2.094) * 0.5 + 0.5) * ช่วง + ฐาน) * fade),
        floor(((ไซน์(h + 4.189) * 0.5 + 0.5) * ช่วง + ฐาน) * fade)
    ]
}

ฟังก์ชัน เมฆดาว(t, fade, gather) {
    # ── สามชั้นอนุภาควนสีคนละเฟส (เดิมสองชั้นในเป็นสีตายตัว ขาว/ชมพู ไม่วนเลย) ──
    #    วนสีตรงนี้คือ "ก่อนพาสเบลอเฟรม" — เบลอเฟรมคอมโพสิต max(เฟรมนี้, เฟรมก่อน*retention)
    #    ตอน present ฉะนั้นเฉดที่เปลี่ยนไปเรื่อย ๆ จะทิ้งหางเป็นสายรุ้งค้างจริง ๆ
    bind c1 = เมฆสีวน(t*0.55,        fade, 120, 120)
    bind c2 = เมฆสีวน(t*0.75 + 2.1,  fade, 110, 140)
    bind c3 = เมฆสีวน(t*0.95 + 4.2,  fade, 100, 150)
    อนุภาคทรงกลม(0,0,0, 44.0, 360.0, t*0.4,        list_get(c1,0), list_get(c1,1), list_get(c1,2))
    อนุภาคทรงกลม(0,0,0, 28.0, 240.0, t*0.6+10.0,   list_get(c2,0), list_get(c2,1), list_get(c2,2))
    อนุภาคทรงกลม(0,0,0, 15.0, 150.0, t*0.9+20.0,   list_get(c3,0), list_get(c3,1), list_get(c3,2))

    # เครื่องดนตรี/สัญลักษณ์ลอย — lerp เข้าหาดาวเคราะห์ด้วย gather
    bind ig = 1.0 - gather
    # อาร์กิวเมนต์ตัวท้ายของลาย* คือ hue — เดิมส่งค่าคงที่ทุกตัว ไวร์เฟรมเลยสีนิ่ง
    # ใส่พจน์เวลาคนละอัตราให้แต่ละลายไล่สีเหลื่อมกัน (เร็วกว่าอนุภาคเล็กน้อยเพื่อให้
    # หางเบลอเฟรมของเส้นเป็นริ้วสีชัดกว่ากลุ่มอนุภาค)
    ลายดาว(18.0*ig, (0.0-8.0)*ig + (0.0-14.0)*gather, 12.0*ig,   1,0,0, 0,1,0,  7, 3.0, 1.5, 0.05, t, 0.05 + t*0.95)
    ลายยันต์((0.0-20.0)*ig, 6.0*ig + (0.0-14.0)*gather, (0.0-10.0)*ig, 1,0,0, 0,0,1,  5, 4.0, t, 1.2 + t*1.25)
    ลายจักร(10.0*ig, 14.0*ig + (0.0-14.0)*gather, (0.0-18.0)*ig, 1,0,0, 0,0,1,  4.0, 10, t, 2.4 + t*0.80)
    ลายดอกบัว((0.0-14.0)*ig, (0.0-14.0)*ig + (0.0-14.0)*gather, 16.0*ig, 1,0,0, 0,0,1,  1.5, 5.0, 12, t, 0.6 + t*1.10)
    ลายจักร((0.0-22.0)*ig, (0.0-4.0)*ig + (0.0-14.0)*gather, 8.0*ig, 0,1,0, 0,0,1,  3.0, 8, t*1.3, 3.5 + t*1.45)

    # ── ตัวอักษรเดี่ยวลอยฟ้า สีหมุนเวียนเร็ว (lerp เข้าหาดาวเคราะห์) ──
    bind i = 0
    ขณะที่ i < 14 {
        bind a = i * 2.39996
        bind rad = 12.0 + (i % 6) * 5.0
        bind lx = โคไซน์(a) * rad
        bind ly = 0.0 - 4.0 - (i % 7) * 3.0
        bind lz = ไซน์(a) * rad
        bind fx = lx * ig
        bind fy = ly * ig + (0.0-14.0) * gather
        bind fz = lz * ig
        bind hh = t * 2.6 + i * 0.7
        สีดินสอ(
            floor(((ไซน์(hh)         * 0.5 + 0.5) * 200 + 40) * fade),
            floor(((ไซน์(hh + 2.094) * 0.5 + 0.5) * 200 + 40) * fade),
            floor(((ไซน์(hh + 4.189) * 0.5 + 0.5) * 200 + 40) * fade)
        )
        bind code = (i * 7) % 26
        วาดกลิฟ(0, code, fx, fy, fz, 0.9)
        bind i = i + 1
    }
}

# ════════════════════════════════════════════════════════════════════════════
# ดาวหางดนตรี — พุ่งเร็วสุ่มทิศผ่านกล้อง; ปลายฉาก (endf→1) สไปรัลเข้าโคจรดาวเคราะห์
# ════════════════════════════════════════════════════════════════════════════
# ตำแหน่งดาวหางที่เวลา tt (deterministic-สุ่มต่อดวง) — คืน [x,y,z]
ฟังก์ชัน ตำแหน่งดาวหาง(tt, L, endf) {
    # ทิศพุ่งสุ่มต่อดวง
    bind dx = ไซน์(L*1.7 + 0.5)
    bind dy = ไซน์(L*2.3 + 1.1) * 0.6
    bind dz = โคไซน์(L*1.9 + 0.3)
    bind dl = รากที่สอง(dx*dx + dy*dy + dz*dz) + 0.0001
    # เฟสพุ่ง (เร็ว) → วิ่งจาก -40..40 ตามทิศ ผ่านบริเวณกลาง
    bind raw = tt * 1.3 + L * 0.37
    bind ph = raw - floor(raw)
    bind d = (ph - 0.5) * 80.0
    bind sx = dx/dl*d + โคไซน์(L*3.1) * 7.0
    bind sy = dy/dl*d + ไซน์(L*2.0) * 5.0 - 4.0
    bind sz = dz/dl*d + ไซน์(L*4.2) * 7.0
    # วงโคจรรอบดาวเคราะห์ (0,-14,0) — สไปรัลเข้า เมื่อ endf→1
    bind oa = tt * 1.6 + L * 0.785
    bind orad = 5.0 + (1.0 - endf) * 22.0
    bind ox2 = โคไซน์(oa) * orad
    bind oy2 = 0.0 - 14.0 + ไซน์(L + tt*0.5) * orad * 0.25
    bind oz2 = ไซน์(oa) * orad
    คืน [sx*(1.0-endf) + ox2*endf, sy*(1.0-endf) + oy2*endf, sz*(1.0-endf) + oz2*endf]
}

ฟังก์ชัน เมฆดาวหาง(t, L, mag, fade, endf) {
    bind m = mag
    ถ้า m > 2.5 { bind m = 2.5 }
    bind pa = ตำแหน่งดาวหาง(t, L, endf)
    bind pb = ตำแหน่งดาวหาง(t - 0.05, L, endf)
    bind ax = list_get(pa,0);  bind ay = list_get(pa,1);  bind az = list_get(pa,2)
    bind tdx = ax - list_get(pb,0);  bind tdy = ay - list_get(pb,1);  bind tdz = az - list_get(pb,2)

    # hue เดิมเป็นค่าคงที่ต่อดวง (L*0.785) ดาวหางเลยไม่เคยไล่สีเลย — เติมพจน์เวลา
    # เข้าไป หัวและหางจึงวนสีระหว่างพุ่ง หางที่เบลอเฟรมค้างไว้กลายเป็นริ้วสายรุ้ง
    bind hue = L * 0.785 + t * 1.15
    bind cc = เมฆสีวน(hue, fade, 55, 200)
    bind rr = list_get(cc,0);  bind gg = list_get(cc,1);  bind bb = list_get(cc,2)

    bind sz = 0.7 + m * 2.2
    # หัวดาวหาง
    อนุภาคทรงกลม(ax, ay, az, sz, 22, t*0.5 + L, rr, gg, bb)
    # หางพุ่งตามทิศเคลื่อน (จางลง 2 ช่วง)
    สีดินสอ(rr, gg, bb)
    วาดเส้น3มิติ(ax, ay, az,  ax - tdx*5.0, ay - tdy*5.0, az - tdz*5.0)
    สีดินสอ(floor(rr*0.45), floor(gg*0.45), floor(bb*0.45))
    วาดเส้น3มิติ(ax - tdx*5.0, ay - tdy*5.0, az - tdz*5.0,  ax - tdx*10.0, ay - tdy*10.0, az - tdz*10.0)
}

# เป่านกหวีด — เสียงนกหวีดของดาวหางขณะสไปรัลเข้า (vibrato)
ฟังก์ชัน เป่านกหวีด(ch, freq, endf, mag) {
    bind v = endf * (0.05 + mag * 0.04)
    ถ้า v > 0.18 { bind v = 0.18 }
    เสียงโทน(ch, 0,0,0, 1.0, freq, v, 7.0, 0.5)
}

# เล่นเลน — เล่นโน้ต 1 เลน (ส่งอาเรย์ความถี่ครั้งเดียว เลี่ยง clone ซ้อน)
ฟังก์ชัน เล่นเลน(ch, freqL, slot, amp) {
    bind fr = list_get(freqL, slot)
    ถ้า fr > 0.0 { เสียงโทน(ch, 0,0,0, 1.0, fr, amp, 0.0, 0.0) }
    มิฉะนั้น { เสียงโทน(ch, 0,0,0, 1.0, 220.0, 0.0, 0.0, 0.0) }
}

# ── คำ + เมนูเลือกเลเวล + ตัวช่วยของเก็บ/แถบคะแนน ────────────────────────────
bind คำ_เลเวล = [11,4,21,4,11,-1,18,4,11,4,2,19]      # level select
bind คำ_เลฺว1 = [11,4,21,4,11,-1,14,13,4]              # level one
bind คำ_เลฺว2 = [11,4,21,4,11,-1,19,22,14]             # level two
bind คำ_เลฺว3 = [11,4,21,4,11,-1,19,7,17,4,4]          # level three
bind เมนูเลเวล = [คำ_เลฺว1, คำ_เลฺว2, คำ_เลฺว3]

# ตั้งค่าสมาชิกที่ idx ของรายการ (สร้างใหม่ — รายการ ling แก้ในที่ไม่ได้)
# ตั้งรายการ(lst, idx, val) — ตอนนี้เป็น engine builtin (O(n) สำเนาเดียว) แทน
# ฟังก์ชัน ling แบบเดิมที่วน list_push/list_get เป็น O(n²) ต่อการเซ็ต (คอขวดต่อเฟรม)

# แถบคะแนน (pip ต่อของเก็บ) — ใช้กล้องโอเวอร์เลย์
ฟังก์ชัน วาดแถบคะแนน(เก็บแล้ว, n, t) {
    bind i = 0
    ขณะที่ i < n {
        bind px = 0.0 - 11.0 + i * (22.0 / (n - 1))
        bind py = 0.0 - 10.0
        ถ้า list_get(เก็บแล้ว, i) > 0 {
            bind h = t*2.0 + i*0.3
            สีดินสอ(floor((ไซน์(h)*0.5+0.5)*170+85), floor((ไซน์(h+2.094)*0.5+0.5)*170+85), floor((ไซน์(h+4.189)*0.5+0.5)*170+85))
        }
        มิฉะนั้น { สีดินสอ(45, 45, 62) }
        bind s = 0.34
        วาดสามเหลี่ยม3มิติ(px-s, py-s, 0, px+s, py-s, 0, px+s, py+s, 0)
        วาดสามเหลี่ยม3มิติ(px-s, py-s, 0, px+s, py+s, 0, px-s, py+s, 0)
        bind i = i + 1
    }
    flush_3d()
}

# วาดกลิฟ3มิติ — กลิฟหนา: ปูหลายชั้นบาง ๆ ตามแนวตั้งฉาก (ชั้นซ้อนกัน = ด้านข้างทึบ)
#   นูนน้อยลง (ลึกรวม ~0.32) แต่ 5 ชั้นทำให้ขอบ/ด้านข้างดูตัน ไม่โปร่ง
ฟังก์ชัน วาดกลิฟ3มิติ(code, x, y, z, sp, scale) {
    ถ้า code < 26 { วาดอบเมช(list_get(_กลิฟเดมอน3มิติ, code), x, y, z,  โคไซน์(sp),0,ไซน์(sp), 0,0.0-1.0,0, scale) }
}

ฟังก์ชัน วาดกลิฟ3มิติหน้า(code, x, y, z, sp, scale, t) {
    ถ้า code < 26 {
        bind rx = โคไซน์(sp);  bind rz = ไซน์(sp)
        bind nx = rz;  bind nz = 0.0 - rx
        bind h = t*4.0 + code*0.53
        set_flat_shade(0.0)
        สีดินสอ(floor((ไซน์(h)*0.5+0.5)*170+85), floor((ไซน์(h+2.094)*0.5+0.5)*170+85), floor((ไซน์(h+4.189)*0.5+0.5)*170+85))
        วาดอบเมช(list_get(_กลิฟเดมอน3มิติ, code), x, y, z, rx,0,rz, 0,0.0-1.0,0, scale)
        สีดินสอ(floor((ไซน์(h+2.094)*0.5+0.5)*150+70), floor((ไซน์(h+4.189)*0.5+0.5)*150+70), floor((ไซน์(h)*0.5+0.5)*150+70))
        วาดอบเมช(list_get(_กลิฟเดมอน3มิติ, code), x+nx*scale*0.045, y, z+nz*scale*0.045, rx,0,rz, 0,0.0-1.0,0, scale*0.99)
        สีดินสอ(floor((ไซน์(h+4.189)*0.5+0.5)*130+55), floor((ไซน์(h)*0.5+0.5)*130+55), floor((ไซน์(h+2.094)*0.5+0.5)*130+55))
        วาดอบเมช(list_get(_กลิฟเดมอน3มิติ, code), x-nx*scale*0.045, y+scale*0.018, z-nz*scale*0.045, rx,0,rz, 0,0.0-1.0,0, scale*0.99)
    }
}

# ตำแหน่งเก็บ(ci) — กระจายของเก็บไป 5 ห้อง (ci%5) + ออฟเซ็ตจาก MIDI → [gx, gz]
ฟังก์ชัน ตำแหน่งเก็บ(ci) {
    bind room = ci % 5
    bind rcx = 0.0;  bind rcz = 0.0
    ถ้า room == 1 { bind rcz = 0.0 - 704.0 }
    ถ้า room == 2 { bind rcz = 704.0 }
    ถ้า room == 3 { bind rcx = 704.0 }
    ถ้า room == 4 { bind rcx = 0.0 - 704.0 }
    คืน [rcx + list_get(เก็บX, ci) * 6.0, rcz + list_get(เก็บZ, ci) * 6.0]
}

# ── ไอคอนดาบเวกเตอร์ (โชว์ในช่องเซฟว่าได้ดาบแล้ว) ───────────────────────────
ฟังก์ชัน วาดไอคอนดาบ(x, y) {
    สีดินสอ(210, 228, 255)
    วาดเส้น3มิติ(x, y-1.0, 0.0,  x, y+0.5, 0.0)        # ใบมีด
    สีดินสอ(180, 170, 120)
    วาดเส้น3มิติ(x-0.45, y+0.5, 0.0,  x+0.45, y+0.5, 0.0) # การ์ด
    วาดเส้น3มิติ(x, y+0.5, 0.0,  x, y+1.0, 0.0)        # ด้าม
}

# ช่องเซฟนี้มีดาบไหม (ฟิลด์ที่ 4 ของ "x,y,z,skill" >= 1)
ฟังก์ชัน ช่องมีดาบ(data) {
    ถ้า data == "empty" { คืน 0 }
    bind pp = แยก(data, ",")
    ถ้า ความยาว(pp) > 3 { ถ้า list_get(pp, 3) * 1.0 >= 1.0 { คืน 1 } }
    คืน 0
}

# ════════════════════════════════════════════════════════════════════════════
# ดาบ: อาร์คฟัน (คอมโบ 0=แนวนอน 1=แนวตั้ง 2=แทง) + ไอคอนไอเทม + HUD
# ════════════════════════════════════════════════════════════════════════════
ฟังก์ชัน วาดสแลช(cx, cy, cz, yaw, typ, prog, bright, rad) {
    bind fwx = ไซน์(yaw);  bind fwz = โคไซน์(yaw)
    สีดินสอ(floor(220*bright), floor(238*bright), floor(255*bright))
    ถ้า typ == 2 {
        bind ext = rad * (0.5 + prog*1.6)        # แทง: พุ่งไปข้างหน้า
        วาดเส้น3มิติ(cx, cy, cz,  cx+fwx*ext, cy, cz+fwz*ext)
        วาดเส้น3มิติ(cx, cy-1.2, cz,  cx+fwx*ext, cy-1.2, cz+fwz*ext)
        คืน 0
    }
    bind k = 1
    bind ppx = cx;  bind ppy = cy;  bind ppz = cz
    ขณะที่ k < 10 {
        bind a = 0.0-1.3 + (k/9.0)*2.6*prog
        bind px = cx;  bind py = cy;  bind pz = cz
        ถ้า typ == 0 {
            bind aa = yaw + a
            bind px = cx + ไซน์(aa)*rad;  bind pz = cz + โคไซน์(aa)*rad
        }
        มิฉะนั้น {
            bind px = cx + fwx*โคไซน์(a)*rad;  bind pz = cz + fwz*โคไซน์(a)*rad
            bind py = cy - ไซน์(a)*rad
        }
        ถ้า k > 1 { วาดเส้น3มิติ(ppx, ppy, ppz, px, py, pz) }
        bind ppx = px;  bind ppy = py;  bind ppz = pz
        bind k = k + 1
    }
    คืน 0
}

# วงกลมจากเส้น 3 มิติ (ใช้กับไอคอน — วาดวงกลม builtin เป็น 2D พิกเซล ใช้ไม่ได้)
ฟังก์ชัน วงเส้น(x, y, r) {
    bind k = 0;  bind ppx = x + r;  bind ppy = y
    ขณะที่ k < 9 {
        bind a = (k / 8.0) * 6.28318
        bind px = x + โคไซน์(a)*r;  bind py = y + ไซน์(a)*r
        ถ้า k > 0 { วาดเส้น3มิติ(ppx, ppy, 0.0, px, py, 0.0) }
        bind ppx = px;  bind ppy = py;  bind k = k + 1
    }
}

# ไอคอนไอเทมเวกเตอร์ (id 1..10) ที่ (x,y) สเกล s — เส้นล้วน (overlay 3D)
ฟังก์ชัน ไอคอนไอเทม(id, x, y, s) {
    ถ้า id == 1 { สีดินสอ(210,228,255);  วาดเส้น3มิติ(x,y-s,0, x,y+s*0.4,0);  สีดินสอ(180,170,120);  วาดเส้น3มิติ(x-s*0.5,y+s*0.4,0, x+s*0.5,y+s*0.4,0) }
    ถ้า id == 2 { สีดินสอ(255,120,60);  วงเส้น(x, y+s*0.2, s*0.6);  วาดเส้น3มิติ(x,y-s*0.4,0, x+s*0.4,y-s,0) }
    ถ้า id == 3 { สีดินสอ(120,200,255);  วาดเส้น3มิติ(x-s,y,0, x+s,y,0);  วาดเส้น3มิติ(x+s,y,0, x+s*0.5,y-s*0.4,0);  วาดเส้น3มิติ(x+s,y,0, x+s*0.5,y+s*0.4,0) }
    ถ้า id == 4 { สีดินสอ(200,200,120);  วาดเส้น3มิติ(x,y-s,0, x-s*0.6,y-s*0.3,0);  วาดเส้น3มิติ(x-s*0.6,y-s*0.3,0, x-s*0.6,y+s*0.5,0);  วาดเส้น3มิติ(x-s*0.6,y+s*0.5,0, x,y+s,0);  วาดเส้น3มิติ(x,y+s,0, x+s*0.6,y+s*0.5,0);  วาดเส้น3มิติ(x+s*0.6,y+s*0.5,0, x+s*0.6,y-s*0.3,0);  วาดเส้น3มิติ(x+s*0.6,y-s*0.3,0, x,y-s,0) }
    ถ้า id == 5 { สีดินสอ(210,228,255);  วาดเส้น3มิติ(x-s*0.3,y-s,0, x-s*0.3,y+s*0.4,0);  วาดเส้น3มิติ(x+s*0.3,y-s,0, x+s*0.3,y+s*0.4,0) }
    ถ้า id == 6 { สีดินสอ(255,90,30);  วาดเส้น3มิติ(x,y+s,0, x-s*0.4,y,0);  วาดเส้น3มิติ(x-s*0.4,y,0, x,y-s,0);  วาดเส้น3มิติ(x,y-s,0, x+s*0.4,y,0);  วาดเส้น3มิติ(x+s*0.4,y,0, x,y+s,0) }
    ถ้า id == 7 { สีดินสอ(150,220,255);  วาดเส้น3มิติ(x,y-s,0, x,y+s,0);  วาดเส้น3มิติ(x-s,y,0, x+s,y,0);  วาดเส้น3มิติ(x-s*0.7,y-s*0.7,0, x+s*0.7,y+s*0.7,0);  วาดเส้น3มิติ(x-s*0.7,y+s*0.7,0, x+s*0.7,y-s*0.7,0) }
    ถ้า id == 8 { สีดินสอ(220,180,255);  วงเส้น(x, y, s*0.7);  วาดเส้น3มิติ(x,y-s,0, x,y+s,0);  วาดเส้น3มิติ(x-s,y,0, x+s,y,0) }
    ถ้า id == 9 { สีดินสอ(255,210,140);  วงเส้น(x, y, s*0.8);  วงเส้น(x, y, s*0.4) }
    ถ้า id == 10 { สีดินสอ(255,70,90);  วงเส้น(x, y, s*0.7);  วาดเส้น3มิติ(x-s*0.4,y+s*0.3,0, x+s*0.4,y+s*0.3,0) }
}

# HUD: แถวไอคอนสกิลที่ได้ (1..สกิล) ล่างจอ; ไฮไลต์ที่เลือก
ฟังก์ชัน วาดHUDสกิล(สกิล, เลือก, t) {
    bind i = 1
    ขณะที่ i <= สกิล {
        bind x = 0.0 - 11.0 + (i-1) * 2.4
        bind y = 9.0
        ถ้า i == เลือก {
            สีดินสอ(255,255,180);  วงเส้น(x, y, 1.3)
        }
        ไอคอนไอเทม(i, x, y, 0.85)
        bind i = i + 1
    }
    flush_3d()
}

← Back to index