Το ίδιο σκάφος σε κάθε οθόνη

Μια ημερήσια κατάταξη είναι δίκαιη μόνο αν το ίδιο κομμάτι σπηλιάς είναι εξίσου δύσκολο σε ένα μικρό παλιό κινητό, σε ένα tablet στα 144 Hz και σε έναν browser υπολογιστή. Εδώ είναι τι χρειάστηκε, και τι δεν μπόρεσα να κάνω ίσο.

Devlog · 20 Σεπτεμβρίου 2026 · όλα τα άρθρα

Το άρθρο για την ημερήσια σπηλιά αφορά το πώς ο κόσμος γίνεται ίδιος για κάθε παίκτη. Αυτό αφορά το άλλο μισό της δικαιοσύνης: να συμπεριφέρεται το σκάφος ακριβώς το ίδιο. Υπάρχουν δύο ξεχωριστοί τρόποι με τους οποίους μια συσκευή μπορεί να αλλάξει το πώς παίζεται το παιχνίδι, και χρειάζονται δύο ξεχωριστές διορθώσεις. Ο ένας είναι πόσο συχνά ανανεώνεται η οθόνη. Ο άλλος είναι πόσο μεγάλη είναι η οθόνη.

Πρόβλημα πρώτο: ο ρυθμός καρέ

Το σκάφος έχει θέση και ταχύτητα. Σε κάθε καρέ, η βαρύτητα ή η ώθηση αλλάζει την ταχύτητα, και η ταχύτητα αλλάζει τη θέση. Ο προφανής τρόπος να το γράψεις είναι:

vy += accel * dt;     // update velocity
py += vy * dt;        // then move by the NEW velocity

Μοιάζει αθώο, και είναι ανεπαίσθητα λάθος. Η δεύτερη γραμμή προσποιείται ότι το σκάφος πέρασε όλο το καρέ κινούμενο ήδη με την ταχύτητα του τέλους του καρέ. Αν το σκάφος επιταχύνει, αυτό υπερβαίνει κατά το μισό του accel × dt² σε κάθε καρέ. Η παγίδα είναι ότι το dt, η διάρκεια του καρέ, διαφέρει σε κάθε συσκευή, οπότε διαφέρει και η υπέρβαση, και το σκάφος πετούσε μια ελαφρώς διαφορετική τροχιά σε κάθε ρυθμό ανανέωσης.

Το μέτρησα με κρατημένη ώθηση για μισό δευτερόλεπτο. Η ακριβής απάντηση είναι 225 pixel. Ο παλιός ολοκληρωτής έδινε 228 στα 144 Hz, 232 στα 60 Hz και 240 στα 30 Hz. Τρεις συσκευές, τρεις διαφορετικές αποστάσεις, από την ίδια είσοδο.

Η διόρθωση είναι ο κανόνας του τραπεζίου, που χρησιμοποιεί τον μέσο όρο της ταχύτητας πριν και μετά το καρέ:

const vyPrev = vy;
vy += accel * dt;
py += (vyPrev + vy) * 0.5 * dt;   // average speed over the frame

Για σταθερή επιτάχυνση αυτό είναι ακριβές, όποια κι αν είναι η διάρκεια του καρέ. Αν ξαναπαίξεις ένα ίδιο πρόγραμμα εισόδου, η απόκλιση είναι τώρα 0,000 pixel από τα 12 ως τα 144 Hz. Πριν ήταν 4,1 pixel. Ένα τεστ ελέγχει και τα δύο μισά: ότι ο νέος ολοκληρωτής συμφωνεί σε όλους τους ρυθμούς ανανέωσης και ότι ο παλιός πράγματι απέκλινε, ώστε αν κάποιος τον επαναφέρει, να αποτύχει το τεστ αντί να μετατοπίζεται σιωπηλά η κατάταξη.

Δύο ειλικρινείς επιφυλάξεις

Δεν έκλεισε τη διαφορά στο σκορ ανάμεσα στους ρυθμούς ανανέωσης. Περίμενα ότι θα το έκανε. Ένας προσομοιωμένος πιλότος έβγαζε ακόμα περίπου ένα τρίτο παραπάνω σκορ στα 144 Hz απ' ό,τι στα 60 Hz μετά την αλλαγή. Αυτή η διαφορά δεν είναι φυσική. Είναι ανάλυση εισόδου: ένας αντίχειρας μπορεί να αλλάξει από κράτημα σε άφημα μόνο στο όριο ενός καρέ, οπότε μια πιο γρήγορη οθόνη δίνει πράγματι πιο λεπτό έλεγχο. Κανένας ολοκληρωτής δεν μπορεί να το διορθώσει αυτό, και ένα bot που αλλάζει είσοδο σε κάθε καρέ το μεγαλοποιεί σε σύγκριση με έναν άνθρωπο. Το μισό της φυσικής είναι τώρα ακριβές, και τα υπόλοιπα είναι ιδιότητα του hardware.

Είναι μια πολύ μικρή εξασθένηση στα 60 Hz. Μια άνοδος μισού δευτερολέπτου καλύπτει τώρα 225 pixel αντί για 232. Το άφησα χωρίς αντιστάθμιση. Αν οι δοκιμές σε αληθινές συσκευές έβρισκαν ποτέ το σκάφος νωθρό, ο μοχλός είναι η σταθερά ώθησης: περίπου 3% παραπάνω θα επανέφερε τον παλιό ελιγμό του μισού δευτερολέπτου. Που είναι καλή στιγμή να πω ότι η ώθηση έχει αλλάξει δύο φορές στο παρελθόν μετά από σχόλια παικτών, και αυτή η ιστορία είναι ο λόγος που είμαι προσεκτικός στο να την αγγίξω.

Πρόβλημα δεύτερο: το ύψος της οθόνης

Η βαρύτητα σε pixel ανά δευτερόλεπτο στο τετράγωνο σημαίνει πολύ διαφορετικά πράγματα σε ένα κινητό ύψους 375 points και σε ένα tablet ύψους 520 points. Αν οι σταθερές ήταν ίδιες, η ίδια άνοδος θα διέσχιζε πολύ μικρότερο μέρος της οθόνης στο tablet, και το παιχνίδι θα έμοιαζε πιο βαρύ.

Γι' αυτό οι σταθερές ορίζονται ως προς ένα ύψος αναφοράς 440 points, το οριζόντιο ύψος του μεγαλύτερου iPhone, όπου ρυθμίστηκε και δοκιμάστηκε η αίσθηση του παιχνιδιού. Σε κάθε άλλη οθόνη, η βαρύτητα, η ώθηση και η οριακή ταχύτητα πολλαπλασιάζονται όλες επί H / 440. Επειδή και το μισό κενό του διαδρόμου κλιμακώνεται με το ύψος, κάθε τροχιά είναι γεωμετρικά όμοια με αυτή της αναφοράς: το ίδιο κλάσμα του διαδρόμου ανά δευτερόλεπτο, ο ίδιος χρόνος από τον έναν τοίχο στον άλλο, η ίδια αισθητή ζωηράδα. Μια μεγαλύτερη οθόνη παίρνει πιο απότομη επιτάχυνση σε pixel, και μια μικρότερη πιο ήπια, σκόπιμα.

Αυτό έρχεται με έναν κανόνα για όλο τον μελλοντικό κώδικα: οτιδήποτε σκέφτεται την κατακόρυφη κίνηση πρέπει να βασίζεται σε λόγους. Ένα κλάσμα της οριακής ταχύτητας, ένα κλάσμα του ύψους. Ποτέ μια ωμή σταθερά pixel ανά δευτερόλεπτο που συγκρίνεται με την ταχύτητα του σκάφους, γιατί αυτός ο αριθμός είναι σωστός μόνο σε μία συσκευή. Αν ένα literal είναι αναπόφευκτο, πολλαπλασιάζεται με την ίδια κλίμακα.

Η έκδοση web ακολουθεί τον ίδιο κανόνα. Το build για browser περιορίζει τον καμβά του σε 956 επί 440, το ίδιο αποτύπωμα με εκείνο το κινητό αναφοράς, οπότε το παιχνίδι στον browser είναι αντίγραφό του σε pixel και φυσική. Ένα παλαιότερο build για web είχε τον δικό του ξεχωριστό πολλαπλασιαστή αίσθησης, που υπήρχε μόνο για να πολεμά την αιώρηση που προκαλούσε ένας διαφορετικός περιορισμός, και διαγράφηκε μόλις ο περιορισμός ταίριαξε.

Πρόβλημα τρίτο: το πλάτος της οθόνης

Το ύψος είναι ο κατακόρυφος άξονας. Το πλάτος είναι ο οριζόντιος, και είναι πιο ύπουλο. Τα εμπόδια απέχουν σταθερές αποστάσεις μέσα στον κόσμο. Η σπηλιά κυλά με μια ταχύτητα που περιλαμβάνει έναν παράγοντα για το πλάτος της οθόνης. Έτσι σε μια πιο φαρδιά οθόνη τα ίδια εμπόδια περνούν πιο γρήγορα από την οθόνη, και ο χρόνος που έχεις για να αντιδράσεις στο καθένα μειώνεται αναλογικά με το πλάτος.

Αυτή ήταν μια πραγματική διαρροή. Ένα μεγάλο tablet Android με πλάτος περίπου 1.280 points αντιμετώπιζε την κοινή σπηλιά περίπου 1,75 φορές πιο γρήγορα από ένα μικρό κινητό. Γι' αυτό το πλάτος έχει όριο 956 points σε κάθε πλατφόρμα, το πλάτος του πιο φαρδιού iPhone σε οριζόντια θέση. Μια συσκευή πιο φαρδιά από αυτό παίρνει μαύρες μπάρες αριστερά και δεξιά. Στα iPhone δεν αλλάζει τίποτα και περιορίζει μόνο μεγάλες συσκευές Android και browsers υπολογιστών.

Το όριο αφήνει ένα υπόλοιπο που δεν έχω διορθώσει. Η ακτίνα σύγκρουσης του παίκτη κλιμακώνεται με το πλάτος, ενώ ο διάδρομος με το ύψος, οπότε σε ένα πιο τετράγωνο tablet το σκάφος είναι λίγο μικρότερο σε σχέση με το κενό απ' ό,τι σε ένα κινητό, περίπου 20% πιο επιεικές. Η διόρθωση σημαίνει αλλαγή του hitbox, που είναι αλλαγή αίσθησης και χρειάζεται δικό της playtest, και σφάλλει προς την επιεική πλευρά, οπότε μπορεί να περιμένει.

Δοκιμάζοντας τη δικαιοσύνη αντί να τη δηλώνεις

Τίποτα από αυτά δεν φαίνεται παίζοντας. Δεν νιώθεις μια μετατόπιση λίγων pixel, και κανείς δεν παίζει την ίδια σπηλιά σε έξι συσκευές ταυτόχρονα. Γι' αυτό κάθε ισχυρισμός έχει ένα τεστ που τον ξαναπαίζει: το τεστ φυσικής ολοκληρώνει μια σταθερή είσοδο σε ρυθμούς καρέ από 12 ως 144 Hz, και ένα τεστ σπηλιάς ξανατρέχει τους πραγματικούς spawners σε έξι μεγέθη συσκευών και συγκρίνει το αποτέλεσμα byte προς byte. Μια ιδιότητα δικαιοσύνης που δεν δοκιμάζεις έχει, από την εμπειρία μου, ήδη χαλάσει σιωπηλά.

Η σύντομη εκδοχή είναι ότι το TUNL είναι δίκαιο σε κάθε οθόνη με τρεις ξεχωριστούς τρόπους (ρυθμός καρέ, ύψος και πλάτος), ο καθένας με διαφορετική αιτία, διαφορετική διόρθωση και διαφορετικό υπόλοιπο. Μπορείς να δεις το αποτέλεσμα μόνος σου, σε όποια συσκευή έχεις, στη σημερινή σπηλιά.

Πέτα τη σημερινή σπηλιά