Dadu yang benar-benar berguling: lemparan 3D yang baru
Dadu di papan kini berupa kubus 3D asli yang mengambil ancang-ancang, berguling, dan mendarat di angka Anda — 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 langkah Anda, lalu jatuh dan mendarat tepat di angka yang Anda lempar. Tidak ada jeda palsu yang dibuat-buat — begitu hasil Anda siap, dadu langsung jatuh. Pada koneksi yang cepat, semuanya selesai dalam sekejap mata.
- Ancang-ancang singkat begitu Anda menekan “Lempar dadu”, supaya terasa responsif seketika.
- Gulingan sungguhan di udara selagi langkah Anda dihitung — gulingan ini memanjang secara alami jika server lambat, alih-alih menampilkan placeholder.
- Pendaratan yang mulus tepat di angka asli Anda, dan bidak Anda mulai bergerak begitu dadu berhenti.
- Dadu cepat ikut bermain juga, dan tetap menampilkan “?” yang samar di antara lemparan supaya Anda 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.