Dadu yang benar-benar berguling: lemparan 3D yang baru
Dadu di papan kini berupa kubus 3D sungguhan yang mengambil ancang-ancang, berguling, dan mendarat di angkamu — tanpa jeda palsu, dan ramah bagi mode kurangi gerakan.

Dengan bahasa sederhana
Dulu melempar dadu terasa datar: angkanya hanya berbalik. Kini kedua dadu gerak — dan dadu Cepat — adalah kubus 3D sungguhan yang merunduk, melompat, dan berguling di udara sebelum mendarat dengan sisi menghadap ke atas di meja.
Inilah bagian serunya: dadu hanya terus berputar selama permainan menghitung langkahmu, lalu jatuh dan mendarat tepat di angka yang kamu lempar. Tidak ada jeda palsu yang dibuat-buat — begitu hasilmu siap, dadu langsung jatuh. Pada koneksi yang cepat, semuanya selesai dalam sekejap mata.
- Ancang-ancang singkat begitu kamu menekan Lempar, supaya terasa responsif seketika.
- Gulingan sungguhan di udara selagi langkahmu dihitung — gulingan ini memanjang secara alami jika server lambat, alih-alih menampilkan placeholder.
- Pendaratan yang mulus tepat di angka aslimu, dan bidakmu mulai bergerak begitu dadu berhenti.
- Dadu Cepat ikut bermain juga, dan tetap menampilkan «?» yang samar di antara lemparan supaya kamu selalu tahu ia sedang dimainkan.

Ini perubahan yang murni visual — aturan, peluang, dan papan semuanya berjalan persis seperti sebelumnya. Dadunya akhirnya benar-benar terasa seperti dadu.
Untuk yang ingin tahu teknisnya
Setiap dadu adalah kubus CSS 3D bersisi enam yang digerakkan oleh sebuah state machine kecil yang berjalan idle → windup → loop → settle → jitter → landed. Orientasi disimpan sebagai quaternion sehingga kita bisa memutar di sekitar sumbu mana pun dan mengonversinya menjadi satu rotate3d() CSS per frame; FACE_Q memetakan setiap nilai 1–6 ke orientasi yang membawa sisi titik itu ke depan.
// apps/web/app/[locale]/game/[id]/dice3d.ts
// quaternion per face → one CSS rotate3d() at rest
const FACE_Q: Record<DieValue, Quat> = {
1: Q.identity,
2: Q.axis([0, 1, 0], +Math.PI / 2),
3: Q.axis([0, 1, 0], -Math.PI / 2),
4: Q.axis([1, 0, 0], +Math.PI / 2),
5: Q.axis([1, 0, 0], -Math.PI / 2),
6: Q.axis([1, 0, 0], Math.PI),
};Lemparannya optimistis: sebuah ketukan menaikkan nonce optimistis, memicu windup 180ms dan loop gulingan yang tak terbatas. Saat lemparan otoritatif tiba (kenaikan rollSeq), dadu yang berputar langsung berhenti mengikuti rotasi maju terpendek ke FACE_Q[value] lalu jatuh — tidak ada batas putaran minimum, jadi respons yang cepat menghasilkan lemparan yang cepat.
// authoritative values arrive → settle now, no spin floor
onRollSeq(values) {
values.forEach((v, i) => {
if (controller[i].isSpinning) controller[i].setResult(v, arrival + i * 78);
else controller[i].startRoll(arrival), controller[i].setResult(v, /* after windup */);
});
}Atas keputusan pemilik, pantulan pendaratan yang meluruh dari handoff dihapus (restitution 0), sehingga proses berhentinya berupa satu penurunan mulus yang dipercepat yang menyentuh tepat di nilai itu, hanya menyisakan goyangan teredam yang singkat. Satu loop rAF bersama hanya berjalan selama ada dadu yang sedang dianimasikan, setiap fase memakai stempel waktu absolut dari performance.now() (agar tab yang berada di latar belakang maju cepat dengan benar), dan sebuah cadangan akan berhenti di nilai store saat ini jika tidak ada lemparan yang tiba. Mode kurangi gerakan melewatkan nonce optimistis sepenuhnya dan cuma mengganti sisi dadu. Hanya web: engine, daemon, dan basis data tidak tersentuh.
