負の座標は2か所でチャンク計算を壊す、1か所ではない
切り捨て除算ではブロック−1がチャンク−1ではなくチャンク0に入り、素の%ではチャンク内オフセットが15ではなく−1になる。どちらも誤っており、どちらもスポーンより西へ行くまで正しく見える。リージョン座標でも同じ罠があり、32を使います。
すべてのチャンク計算は正の象限では自明であり、残り3つの象限では誤っている。誤る箇所はちょうど2か所あり、それらは互いに独立しており、どちらももっともらしく見える答えを返す。
2つの演算
chunk = floor(block / 16) not trunc(block / 16)
offset = ((block % 16) + 16) % 16 not block % 16
リージョン座標も同じ組で、1レベル上、16ではなく32を使う:
region = floor(chunk / 32) -> r.<x>.<z>.mca
regionOff = ((chunk % 32) + 32) % 32
つまりリージョンは32×32チャンク、すなわち512×512ブロックである。
それぞれが噛みつく場所
除算。 ほとんどの言語はゼロ方向へ切り捨て、floor は負の無限大方向へ丸める。正の数では一致し、正確な倍数でないすべての負の数で食い違う:
- ブロック
−1→floor(−1/16) = −1✓、しかしtrunc(−1/16) = 0✗
ブロック−1は原点の1ブロック西にあり、チャンク−1に属する。切り捨てはそれをブロック+1と並んでチャンク0に入れる。負のチャンクインデックスはすべて1つ高く出る。
剰余。 JavaScript、Java、C、その他ほとんどの言語では、% は左オペランドの符号を保つ:
- ブロック
−1→ 正しいオフセット15、しかし−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 ✗ |
4つの値、4つの誤った答え、どれも明らかに壊れて見えるほど範囲外ではない。リージョン−3はチャンク−96から−65にわたり、チャンク−81はその開始から15の位置にある——これはまさに修正された剰余が返すものである。
ネザー変換は一方向で不可逆である
オーバーワールドからネザーへは8で割り、これも床関数を使う:
netherX = floor(overworldX / 8)
戻るときは正確な位置に8を掛けるので、ネザーのブロック 125 全体が、そのブロック内のどこに立っているかに応じて 1000~1007 のどこかへ逆写像される。これはブロック座標にとって往復ではない。floor は剰余を捨てる:
- オーバーワールド
1000→ ネザー125→ 戻ると1000✓ - オーバーワールド
1007→ ネザー125→ ブロック座標は1000と言う ✗ ——7ブロック足りない
誤差は軸あたり0~7ブロック、つまりXで最大7、Zで最大7が同時に起こりうる。これが、正確なポータルリンクにネザー座標を再導出ではなく書き留めておく必要がある理由のすべてである:一度割ってしまうと、それが由来したオーバーワールドの位置はもはや復元できない。
ネザーハブにとってこれはめったに問題にならない——7ブロックはポータル自身の捕捉半径の内側である。互いのトラフィックを奪ってはならない2つのポータルをリンクする場合、これは非常に大きく問題になる。
負のオーバーワールド座標はおなじみの形でさらに悪化させる:オーバーワールド −1 はネザー floor(−1/8) = −1 を与えるが、切り捨ては 0 を与え、軸の反対側へ送り込む。