Від'ємні координати ламають математику чанків у двох місцях, а не в одному
Ділення з усіканням поміщає блок −1 у чанк 0 замість −1, а звичайний % дає зміщення всередині чанку −1 замість 15. Обидва помилкові, і обидва виглядають правильно, доки ви не підете на захід від спавну.
Кожне обчислення чанку є тривіальним у додатному квадранті та хибним в інших трьох. Є рівно два місця, де виникає помилка, вони не залежать одне від одного, і обидва дають відповіді, що виглядають правдоподібно.
Дві операції
chunk = floor(block / 16) not trunc(block / 16)
offset = ((block % 16) + 16) % 16 not block % 16
Координати регіону — це та сама пара, на рівень вище, з 32 замість 16:
region = floor(chunk / 32) -> r.<x>.<z>.mca
regionOff = ((chunk % 32) + 32) % 32
Отже, регіон складається з 32 × 32 чанків, що дорівнює 512 × 512 блоків.
Де криється підступ
Ділення. Більшість мов усікають до нуля, і floor rounds toward negative infinity. They agree for positives and disagree for every negative that is not an exact multiple:
- блок
−1→floor(−1/16) = −1✓, buttrunc(−1/16) = 0✗
Блок −1 знаходиться на один блок на захід від початку координат і належить до чанку −1. Усікання поміщає його в чанк 0 разом із блоком +1. Кожен від'ємний індекс чанку виходить на одиницю більшим.
Остача. У JavaScript, Java, C та більшості інших, % keeps the sign of the left operand:
- блок
−1→ correct offset15, but−1 % 16 = −1✗
Зміщення −1 взагалі не є позицією всередині чанку завширшки 16. Індексація масиву блоків за його допомогою або викликає помилку, або непомітно зчитує не ту комірку.
Практичний приклад
Блок −1290 на одній осі:
| крок | правильно | наївно |
|---|---|---|
| чанк | floor(−1290/16) = −81 | trunc = −80 ✗ |
| зміщення в чанку | 6 | −1290 % 16 = −10 ✗ |
| регіон | floor(−81/32) = −3 | trunc = −2 ✗ |
| зміщення в регіоні | 15 | −81 % 32 = −17 ✗ |
Чотири значення, чотири неправильні відповіді, і жодна з них не виходить за межі настільки, щоб здаватися очевидно зламаною. Регіон −3 охоплює чанки від −96 до −65, а чанк −81 розташований на 15 блоків від його початку — що саме і повертає скоригована остача.
Перетворення для Nether втрачає точність в одному напрямку
Перехід зі Звичайного світу до Nether ділить на 8 і також округлює вниз:
netherX = floor(overworldX / 8)
Повернення назад множить на 8. Це не зворотний шлях. floor throws away the remainder, so returning lands you on a multiple of 8:
- Звичайний світ
1000→ Nether125→ back to1000✓ - Звичайний світ
1007→ Nether125→ back to1000✗ — seven blocks short
Похибка становить від 0 до 7 блоків на вісь, тобто до 7 на X та 7 на Z одночасно. Саме тому для точного зв'язування порталів потрібно записувати координати Nether, а не обчислювати їх заново: після ділення початкову позицію у Звичайному світі вже не відновити.
Для хабу в Nether це рідко має значення — 7 блоків знаходяться в межах радіусу захоплення самого порталу. Для зв'язування двох порталів, які не повинні перехоплювати зв'язок один одного, це дуже важливо.
Від'ємні координати у Звичайному світі погіршують ситуацію вже знайомим чином: координата Звичайного світу −1 gives Nether floor(−1/8) = −1, while truncation gives 0 and sends you to the wrong side of the axis.