ऐसा पासा जो सचमुच लुढ़कता है: नया 3D रोल
बोर्ड का पासा अब असली 3D घन है जो ज़ोर लगाता है, लुढ़कता है और आपके अंकों पर आकर रुकता है — कोई नकली देरी नहीं, और मोशन कम करने के लिए कोमल।

आसान शब्दों में
पहले पासा फेंकना सपाट था: अंक बस पलट जाते थे। अब चाल के दोनों पासे — और स्पीड पासा — असली 3D घन हैं जो झुकते हैं, उछलते हैं और हवा में लुढ़कते हैं, फिर मेज़ पर अंक वाली सतह ऊपर करके रुक जाते हैं।
अच्छी बात यह है: पासा सिर्फ़ तभी तक घूमता रहता है जब तक खेल आपकी चाल की गणना करता है, फिर गिरकर ठीक उन्हीं अंकों पर आ टिकता है जो आपने फेंके। कोई बनावटी, भरी हुई रुकावट नहीं — जैसे ही आपका परिणाम तैयार होता है, वे गिर पड़ते हैं। तेज़ कनेक्शन पर तो पूरा कुछ पल भर में निपट जाता है।
- जैसे ही आप पासा फेंकना पर टैप करते हैं, तुरंत एक तेज़ ज़ोर लगता है, ताकि वह फ़ौरन प्रतिक्रिया देता महसूस हो।
- जब आपकी चाल की गणना होती है तब हवा में असली लुढ़काव — अगर सर्वर धीमा हो तो यह स्वाभाविक रूप से खिंच जाता है, बजाय किसी प्लेसहोल्डर के दिखने के।
- आपके असली अंकों पर एक सहज लैंडिंग, और जैसे ही पासा रुकता है आपका टोकन चलना शुरू कर देता है।
- स्पीड पासा भी इसमें शामिल होता है, और रोल के बीच में अब भी एक धीमा «?» दिखाता है ताकि आपको हमेशा पता रहे कि वह खेल में है।

यह पूरी तरह दृश्य से जुड़ा बदलाव है — नियम, संभावनाएँ और बोर्ड सब बिल्कुल पहले जैसे ही चलते हैं। बस पासा आख़िरकार पासे जैसा महसूस होने लगा है।
तकनीकी जानकारी
हर पासा छह सतहों वाला एक 3D CSS घन है जिसे एक छोटी स्टेट मशीन चलाती है जो idle → windup → loop → settle → jitter → landed से गुज़रती है। दिशा को quaternion के रूप में संग्रहीत किया जाता है ताकि हम किसी भी अक्ष के चारों ओर घुमा सकें और प्रति फ़्रेम एकल CSS rotate3d() में बदल सकें; FACE_Q प्रत्येक मान 1–6 को उस दिशा से जोड़ता है जो उन बिंदुओं वाली सतह को आगे लाती है।
// 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),
};रोल आशावादी है: एक टैप एक आशावादी nonce को बढ़ाता है, जिससे 180ms का windup और एक अनिश्चित लुढ़काव loop शुरू हो जाता है। जब आधिकारिक रोल आता है (rollSeq में वृद्धि), तो घूमता हुआ पासा तुरंत सबसे छोटे आगे के घुमाव के साथ FACE_Q[value] पर आकर टिक जाता है और गिर पड़ता है — कोई न्यूनतम-घुमाव सीमा नहीं है, इसलिए एक तेज़ प्रतिक्रिया एक तेज़ रोल देती है।
// 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 */);
});
}मालिक के निर्णय से हैंडऑफ़ के घटते लैंडिंग उछाल हटा दिए गए (restitution 0), इसलिए पासा एक ही सहज, त्वरित अवतरण में आकर ठीक उसी मान को छूता है, और पीछे केवल एक छोटी सी दबी हुई डगमगाहट छोड़ता है। एक साझा rAF loop केवल तभी चलता है जब कोई पासा एनिमेट हो रहा हो, हर चरण performance.now() के निरपेक्ष टाइमस्टैम्प का उपयोग करता है (ताकि पृष्ठभूमि में गया टैब सही ढंग से आगे बढ़ जाए), और यदि कोई रोल नहीं आता तो एक बैकस्टॉप store के मौजूदा मानों पर आ टिकता है। मोशन कम करना आशावादी nonce को पूरी तरह छोड़ देता है और बस सतहें बदल देता है। केवल वेब: इंजन, daemon और डेटाबेस अछूते हैं।
