像素藝術的道路:iPhone 上的地圖連接與街區
寶可夢的前三個世代走過一個 16 像素的格都是 16 個影格,約每秒 3.73 格,而 Kiradex 的收藏者每秒走 4 個圖塊,所以在老遊戲裡量到的一段路,換算到我們的城鎮就是每個圖塊四分之一秒。1234 每一款的第一條道路橫跨兩到六個主機畫面,而且沒有訓練家;第一段路程,從玩家家門口走到下一個城鎮的寶可夢中心,在《綠寶石》是 58 格、走 15.5 秒,在《火紅》是 94 格、25.2 秒;回程在兩款都比較短,分別是 42 格與 76 格,因為沿途的矮崖全都朝向家的方向。567 在美術改變的地方,關都會在路上放一棟建築:《紅》的 78 條地圖連接中有 76 條只接同一組圖塊集,而《紅》每一座連接兩個不同地方的關口都接兩組。8 豐緣的引擎會把相鄰地圖的七格複製到目前地圖周圍的邊距裡,三面七格、東側八格,並以目前地圖的圖塊來繪製,等到您跨過去的那一刻,才載入鄰圖自己的次要圖塊與調色盤,全程沒有淡出。910 《綠寶石》的地圖畫面是 28 乘 15 的方格;未白鎮玩家家中樓上的一幅壁掛地圖可以打開它,從卡那茲市起改由寶可導航隨身攜帶;它會畫出每一個地方,不論去過與否,只有兩處要等劇情旗標,而「飛翔」只列出您走進時已設下「去過」旗標的城鎮。111213 今天的 Kiradex World 是一座 40 乘 30 的城鎮,圍籬外什麼也沒有。14 所以我從反編譯原始碼量測了經典作品的道路、接縫、關口與地圖畫面,把《星露谷物語》、《動物森友會》與《薩爾達傳說 織夢島》當作對照的模型來讀,再把這些數字整理成一份規格書:廣場之外的四個區域、一個地圖畫面,以及一班在您第一次抵達時才開放該站的電車。
重點摘要
- 走一格要 16 個影格,約四分之一秒。 《紅》、《水晶》與《綠寶石》都是 16 個影格走一格,跑步或騎車則是 8 個,在硬體的 59.7 赫茲下分別是每秒 3.73 格與 7.47 格;Kiradex 設定為每秒走 4 個圖塊,比掌機快約 7%,跑步則是名目上的 6.4,而依其程式碼建立的影格模型在 60 赫茲下算出的是 6.00,比掌機的跑步慢約 20%。1231541617
- 一條道路長 2 到 10 個畫面,第一條很短,草也最多。 量測的十六條道路沿其軸向橫跨 2.0(《綠寶石》101號道路)到 10.0(《水晶》32號道路)個主機畫面;四條第一條道路以地圖面積計有 14.4% 到 22.8% 是高草叢,是各款之中最高的,而且都沒有訓練家。在兩端可以步行相通的九條道路上,因為路徑會繞,較長方向的穿越步行是道路長邊的 1.15 到 1.70 倍,回家的路則是 0.95 到 1.42 倍。5617
- 矮崖讓回家的路變短。 在兩端可以步行相通、而且全都有矮崖的九條量測道路上,往較早那座城鎮的步行都短了 2% 到 44%,回家路上最少要踩的高草格是 0 到 5 格,出門則是 4 到 19 格。5618
- 關口蓋在美術改變的地方,也承載規則。 《紅》每一座連接兩個地方的關口都接兩組圖塊集;關口裡有徽章檢查、一位口渴的守衛,以及擁有 10、30、50 種寶可夢時的三份獎勵。豐緣的引擎在接縫處載入新的圖塊集,只留下 4 棟關口建築,相對於《紅》的 27 張關口圖塊集地圖。8192010
- 第三世代的接縫會畫出鄰圖的七格(東側八格),並在跨越時載入它的美術。 緩衝區的邊距是
MAP_OFFSET 7,理由是「the player has 7 metatiles of view horizontally in either direction」(玩家水平方向兩側各看得到 7 個 metatile),而東側複製MAP_OFFSET + 1欄;跨越時載入新地圖的次要圖塊集、音樂與天氣,並滑入一個約 3.2 秒的地名提示,沒有淡出。91021 - 地圖畫面顯示每一個地方;快速移動只顯示您去過的地方。 《綠寶石》的地圖是 28 乘 15 的方格,其中 158 格屬於 54 個地方;玩家一旦能打開它(先是家中樓上的壁掛地圖,再來是寶可導航),它就畫出每一個地方,不論去過與否,只有兩處被劇情旗標擋住;一座城鎮要在進入時設下
FLAG_VISITED_*旗標,才會成為「飛翔」的目的地,而《紅》、《火紅》、《水晶》與《織夢島》也都保有同類的「去過」紀錄。11121322232425 - 這帶給 Kiradex 的是什麼。 一份規格書:中央廣場之外的四個區域(卡冊街、草原步道、湖畔與藝廊區),面積是中央廣場的 2.8 倍;沒有淡出的開放接縫;App 與伺服器共用的一張八方向步行圖;16 乘 12 的地圖畫面;一班在第一次造訪時才開放站點的電車;只會是裝飾品或任務提示的拾獲物;以及十三項可由腳本或擷取畫面執行的驗收檢查。262728
1. 實測道路
本系列 10 月 3 日的三篇文章都停在圍籬之內。iPhone 上的像素藝術世界定下了圖塊系統與 RealityKit 配方,像素藝術人物定下了收藏者,像素藝術建築則定下了建築與門。Kiradex World 由此成形為一座城鎮:40 乘 30 個圖塊,圍籬外再畫出 12 個圖塊深的樹林,七棟建築的門通往各個房間或大廳,以及一個抵達圖塊。14 這一篇談的是圍籬之外有什麼,而它的起點和前幾篇一樣,是反編譯原始碼。
以下每一個寶可夢數字,都是從 pret 反編譯專案的淺層複製量出來的,版本為 pokered d2704a6、pokecrystal 5beda23、pokeemerald 731ad5b 與 pokefirered 037335f,由六支小型 Python 腳本計算,每一支重跑約一秒或更短;註腳會寫明每個數字背後是哪一支腳本。29 單位是格,也就是一個 16 乘 16 像素的步行方塊:第三世代的一個 metatile,或第一、第二世代一個 block 的四分之一。Game Boy 的畫面顯示 10 乘 9 格(160 乘 144 像素),Game Boy Advance 則是 15 乘 10 格(240 乘 160)。29 每支腳本的步行模型都相同:一步花一格;矮崖只能順著它的方向跨越,落在一格之外,算兩格移動;在《水晶》中,一步不能穿過圖塊的單向牆,角落矮崖可以朝它的兩個方向之一跳下(程式碼見第 2 節);接縫只在鄰圖對面那一格能從鄰圖某扇門步行抵達時才算數;訓練家與其他人物一律忽略,也不模擬衝浪。29
時鐘
三個世代的步調相同。在《紅》中,OverworldLoop 每次迭代呼叫兩次 DelayFrame,而 AdvancePlayerSprite 每次迭代讓視野移動 2 像素、共 8 次迭代(wWalkCounter 為 8),所以一格需要 16 個影格;騎自行車時 AdvancePlayerSprite 每次迭代執行兩次,一格 8 個影格。1 《水晶》的 StepVectors 給走路一步 8 個 tick、每個 2 像素,騎車一步 4 個 tick、每個 4 像素,而它的野外每 2 個影格跑一個 tick(MaxOverworldDelay: db 2),同樣是走路一格 16 個影格、騎車 8 個。2 《綠寶石》的步進表:走路 16 項(sStep1Funcs),跑步與衝浪 8 項,越野自行車與水流 6 項,音速自行車全速 4 項,最快的 2 項;一次跳下矮崖(JUMP_DISTANCE_FAR)花 32 個影格跨兩格,最高點升起 12 像素(sJumpY_High)。3
影格是時鐘的另一半。GBATEK 給出 Game Boy Advance 的一個影格是 280,896 個週期,「ca. 59.737 Hz」(約 59.737 Hz);Pan Docs 給出 Game Boy 的是 70,224 個點「@ 59.7 fps」(約每秒 59.7 影格)。1530 以每格 16 個影格計,走路約每秒 3.73 格,跑步約 7.47 格。17 本文的秒數一律以 224 赫茲除以 280,896 計算,也就是每秒 59.7275 個影格;GBATEK 給的是 59.737,差距不到 0.02%。29
Kiradex 的收藏者以 tilesPerSecond = 4 走路,跑步是它的 1.6 倍,名目上每秒 6.4 個圖塊。4 跑步的數字是常數,不是程式碼實際達到的速度:本系列的動態篇逐影格模擬了這段程式碼,發現跑步在 60 赫茲下每秒走 6.00 個圖塊,在 120 赫茲下是 6.32,因為每一步都會把超出的進度丟掉。16 走路比掌機的走路快約 7%;跑步在 60 赫茲下比掌機的跑步慢約 20%,以名目上的 6.4 計則慢 14%。17 走路的差距小到可以用固定比率換算:一條道路以掌機的步速計,時間是格數乘以 0.27 秒;以我們的步速計,則是圖塊數乘以 0.25 秒。17
十六條道路
我量了每一款的前四條道路:《紅》的 1 到 4 號道路、《水晶》的 29 到 32 號、《綠寶石》的 101 到 104 號,以及《火紅》的 1 到 4 號。第三世代的格從 map.bin 讀取(位元 0 到 9 是 metatile,10 與 11 是碰撞,12 到 15 是高度),每一格的行為則在主要或次要的 metatile_attributes.bin 中查找;訓練家是地圖上 trainer_type 不是 TRAINER_TYPE_NONE 的物件事件。6 第一世代的格讀取每個 16 乘 16 格左下角那個 8 乘 8 圖塊,比對圖塊集的碰撞清單、草叢圖塊 $52 與矮崖表;第二世代的格則讀取每個 block 為各象限所帶的四個碰撞位元組。5
| 遊戲 | 道路 | 尺寸(格) | 沿軸向的畫面數 | 可行走 | 高草叢,格數(佔地圖 %) | 矮崖格數 / 段數 | 訓練家 | 最短穿越步行,各方向(格) |
|---|---|---|---|---|---|---|---|---|
| 紅 | 1 | 20 乘 36 | 4.0 | 76.8% | 104(14.4%) | 42 / 10 | 0 | 往南 40,往北 52 |
| 紅 | 2 | 20 乘 72 | 8.0 | 55.9% | 84(5.8%) | 52 / 14 | 0 | 被森林隔開 |
| 紅 | 3 | 70 乘 18 | 7.0 | 35.1% | 100(7.9%) | 36 / 10 | 8 | 往深灰市 82,離開 84 |
| 紅 | 4 | 90 乘 18 | 9.0 | 45.0% | 60(3.7%) | 169 / 27 | 1 | 被洞窟隔開 |
| 水晶 | 29 | 60 乘 18 | 6.0 | 47.7% | 160(14.8%) | 35 / 13 | 0 | 往東 71,往西 90 |
| 水晶 | 30 | 20 乘 54 | 6.0 | 47.6% | 140(13.0%) | 21 / 6 | 3 | 往南 57,往北 74 |
| 水晶 | 31 | 40 乘 18 | 4.0 | 39.3% | 64(8.9%) | 13 / 5 | 1 | 西端是關口 |
| 水晶 | 32 | 20 乘 90 | 10.0 | 48.3% | 152(8.4%) | 6 / 5 | 8 | 往南 132,往北 128 |
| 綠寶石 | 101 | 20 乘 20 | 2.0 | 59.2% | 91(22.8%) | 13 / 3,另有 1 個角落 | 0 | 往南 19,往北 34 |
| 綠寶石 | 102 | 50 乘 20 | 3.3 | 48.9% | 143(14.3%) | 17 / 4 | 4 | 往東 55,往西 62 |
| 綠寶石 | 103 | 80 乘 22 | 5.3 | 27.3% | 92(5.2%) | 24 / 3 | 9 | 被海隔開 |
| 綠寶石 | 104 | 40 乘 80 | 8.0 | 34.0% | 126(3.9%) | 13 / 4 | 8 | 被森林隔開 |
| 火紅 | 1 | 24 乘 40 | 4.0 | 62.3% | 178(18.5%) | 61 / 10 | 0 | 往南 43,往北 58 |
| 火紅 | 2 | 24 乘 80 | 8.0 | 40.8% | 84(4.4%) | 44 / 13 | 0 | 被森林隔開 |
| 火紅 | 3 | 84 乘 20 | 5.6 | 37.6% | 117(7.0%) | 42 / 10 | 8 | 往深灰市 94,離開 97 |
| 火紅 | 4 | 108 乘 20 | 7.2 | 42.9% | 84(3.9%) | 186 / 28 | 1 | 被洞窟隔開 |
表格來源:measure_gen12_routes.py 與 measure_gen3_routes.py,套用在四個反編譯專案上。56 畫面數一欄是把長邊除以主機在該軸向的視野(Game Boy 為 10 或 9 格,Game Boy Advance 為 15 或 10 格)。可行走一欄是步行者能站立的格所佔的比例。矮崖格數在第一與第三世代是跳躍格,在第二世代是崖緣格。步行路線在可從門抵達的接縫格之間計算;所謂被隔開的道路,是指因為森林、洞窟、關口或海洋擋在兩半之間,地圖內不存在接縫到接縫的步行路線。29
長軸上的道路長度,附上各方向的最短穿越步行;由 figures/make_figures.py 依兩支道路腳本的輸出繪製。
從表格可以看出五件事。
道路橫跨 2 到 10 個畫面,第一條很短。 《綠寶石》101號道路高兩個 Game Boy Advance 畫面;《水晶》32號道路高十個 Game Boy 畫面;每一款的第一條道路是兩到六個畫面。56 以格數計,量測的道路長邊從 20 到 108 不等。6
較長的穿越步行是長邊的 1.15 到 1.70 倍。 十六條道路中有九條可以從一端走到另一端。在這九條上,較長方向的最短步行與道路長邊之比分別是:《綠寶石》101號道路往北 34 比 20,1.70;《水晶》29號道路 90 比 60,1.50;《水晶》32號道路 132 比 90,1.47;《火紅》1號道路 58 比 40,1.45;《紅》1號道路 52 比 36,1.44;《水晶》30號道路 74 比 54,1.37;《綠寶石》102號道路 62 比 50,1.24;《紅》3號道路 84 比 70,1.20;以及《火紅》3號道路 97 比 84,1.15。5617 回家的路比較直,是長邊的 0.95 到 1.42 倍:《綠寶石》101號道路 19 比 20,《水晶》30號道路 57 比 54,《火紅》1號道路 43 比 40,《綠寶石》102號道路 55 比 50,《紅》1號道路 40 比 36,《火紅》3號道路 94 比 84,《紅》3號道路 82 比 70,《水晶》29號道路 71 比 60,以及《水晶》32號道路 128 比 90,它的單向牆讓兩個方向的步行都得繞遠路。5617 比值低於 1 並不代表抄了空間上的捷徑:在一張 20 格的地圖上,從第一列的接縫格走到最後一列的接縫格要 19 步,所以一條筆直的道路得分是 0.95。17 依我的解讀,決定道路尺寸應該看出門那個數字:看它蜿蜒的步行路線,而不是它的外框。
草叢隨旅程推進而變稀。 高草叢佔道路地圖的 3.7% 到 22.8%。四條第一條道路,《紅》1號、《水晶》29號、《綠寶石》101號與《火紅》1號,在各自遊戲的四條中草最多,分別是 14.4%、14.8%、22.8% 與 18.5%;第二條道路是 4.4% 到 14.3%,第三與第四條是 3.7% 到 8.9%。56 若改以可站立的地面計算,比例會更高,而這個趨勢只在平均上成立:第一條道路的高草叢佔 18.8% 到 38.4%,其餘十二條佔 8.2% 到 29.2%,而且《紅》3號道路(22.6%)腳下的草比《紅》1號道路(18.8%)還多。在《綠寶石》101號道路上,237 個可行走格中有 91 格是高草叢,佔 38.4%。5617
第一條道路都沒有訓練家。 《紅》1號、《火紅》1號、《綠寶石》101號與《水晶》29號都沒有;訓練家從第二或第三條道路開始出現,《綠寶石》102號道路有 4 位,《水晶》30號道路有 3 位,《紅》與《火紅》的 3號道路各有 8 位。56 他們在這些道路上的視線範圍是 0 到 7 格;《綠寶石》103號道路的九位訓練家看得到 1 到 5 格,104號道路的八位看得到 0 到 7 格。6
遭遇區約每九步擲一次骰,前面還有短暫的寬限。 玩家進入一張地圖之後(不論經由接縫或門),或在一場戰鬥之後,《綠寶石》從不檢定最初的四步:CheckStandardWildEncounter 在 sWildEncounterImmunitySteps < 4 時只計步不檢定,並在每次遭遇後重新計數,而每次載入地圖與每次戰鬥開始也都會重新計數。過了這段之後,每踏上一個遭遇格就檢定一次:當這一步踏上的是不同的行為時,有 40% 的機率跳過檢定,接著是 Random() % 2880 < rate × 16。101 到 104 號道路的遭遇率都是 20,所以未經修正的擲骰是 2,880 分之 320,步行時每步 11.1%;自行車、笛子、潔淨之符或某些帶頭寶可夢的特性會先調整這個值。31 《火紅》加上了一道下限與一段漸增:遭遇之後以及進入地圖時,會有 8 − rate/10 個遭遇格步數的冷卻(1 到 4 號道路的遭遇率是 21,所以是 6 步),冷卻中的每一步都有 5% 的機率讓那一步嘗試遭遇,但不會結束冷卻;然後是踏上新行為時的 60% 擲骰;最後是 1,600 分之 rate × 16,也就是 21%,再加上一個每次擲骰失敗就增加一個遭遇率的加成(encounterRateBuff)。32 我把寬限、下限與漸增解讀為讓乾旱與氾濫都不會持續太久的手段。Kiradex 不會有野生對戰,所以這個數字在這裡只是一種節奏:過了最初幾步之後,第一條道路大約每走九步草叢,就提供一樣值得注意的東西。
一個世界有多少是道路
| 遊戲 | 道路地圖數 | 道路格數 | 城鎮地圖數 | 城鎮格數 | 道路對城鎮 |
|---|---|---|---|---|---|
| 紅 | 25 | 31,500 | 11 | 11,880 | 2.65 |
| 水晶(城都與關都) | 54 | 49,880 | 23 | 25,380 | 1.97 |
| 綠寶石(陸上道路) | 41 | 87,787 | 16 | 23,700 | 3.70 |
| 火紅(含七之島) | 57 | 98,451 | 19 | 25,470 | 3.87 |
道路的面積是城鎮的兩到四倍。33 《綠寶石》的 11 條海上道路還會再加上 45,600 格。33 這個比值取決於地圖如何分類,而每一款的分類方式都不同:《紅》依地圖 ID 與名稱,《水晶》依環境(例如它的 ROUTE 環境就包含阿爾佛遺跡的戶外部分),《綠寶石》與《火紅》依 map_type,其中《火紅》的道路包含七之島,《綠寶石》的則不含海洋。33 在《綠寶石》的地圖畫面上,同一個世界讀起來是 129 個道路方格對 22 個城鎮方格,5.9 比 1,因為一座城鎮不論多大都只佔一或兩個方格:16 座城鎮與城市中有十座佔一格,六座佔兩格。341117
1號道路的三種畫法
《紅》、《火紅》與《水晶》以三種解析度畫出同一個構想,而1號道路是最乾淨的比較。《紅》的1號道路是 10 乘 18 個 block,也就是 20 乘 36 格,高四個 Game Boy 畫面,有 42 個矮崖格,分成 10 段朝南,夾在柵欄柱與樹列之間,另有 104 格草叢。5 為了檢驗第一世代的格模型,我用它的 blockset 把這一張地圖以兩倍大小算繪出來,並疊上 16 像素的格線;圖上可以看到西側樹列外有一條 3 格寬的空地,碰得到兩個接縫,卻無法從任何一座城鎮的門走到,這就是為什麼搜尋要從鄰圖的門開始播種穿越點。5 第一世代的模型每格只讀一個 8 乘 8 圖塊,也就是左下角那個,而且只在那一張算繪圖上用肉眼檢查過;像水到陸地這類圖塊對碰撞並未模擬。29
《火紅》的1號道路是 24 乘 40 格,在較大的畫面上仍然高四個畫面,有 61 個矮崖格與 178 個草叢格,佔地圖 18.5%:重製版保留了以畫面計的長度,地圖每個方向加大 4 格,還增加了矮崖。6 從真新鎮的家門口到常磐市的寶可夢中心是 94 格、25 秒,反方向是 76 格。7 《水晶》的開場道路,29號道路,是 60 乘 18 格,寬六個 Game Boy 畫面,草叢佔 14.8%,沒有訓練家;從若葉鎮那一側走到吉花市那一側是 90 格,走回來是 71 格。5
2. 門到門,以及回家的路
一條道路本身的長度只是一趟旅程的一部分。玩家感受到的是一段路程:走出一棟建築,穿過城鎮,越過道路與下一座城鎮,走進下一棟建築。measure_travel.py 搜尋的就是這整條路徑,以(地圖,格)為節點,穿過接縫、傳送點與矮崖,從第一扇門下方那一格走到最後一扇門,並把踏進門的那一步也算進去。7
| 遊戲 | 起點 | 終點 | 格數 | 步行 | 跑步 | 穿越的地圖 |
|---|---|---|---|---|---|---|
| 綠寶石 | 未白鎮,玩家的家 | 古辰鎮寶可夢中心 | 58 | 15.5 秒 | 7.8 秒 | 城鎮、道路、城鎮 |
| 綠寶石 | 古辰鎮寶可夢中心 | 橙華市寶可夢中心 | 87 | 23.3 秒 | 11.7 秒 | 城鎮、道路、城市 |
| 綠寶石 | 橙華市寶可夢中心 | 卡那茲市寶可夢中心 | 250 | 67.0 秒 | 33.5 秒 | 城市、道路、森林、道路、城市 |
| 火紅 | 真新鎮,玩家的家 | 常磐市寶可夢中心 | 94 | 25.2 秒 | 12.6 秒 | 城鎮、道路、城市 |
| 火紅 | 常磐市寶可夢中心 | 深灰市寶可夢中心 | 267(居合斬後 156) | 71.5 秒(41.8 秒) | 35.8 秒 | 穿過森林的兩座關口 |
| 火紅 | 深灰市寶可夢中心 | 4號道路寶可夢中心 | 150 | 40.2 秒 | 20.1 秒 | 城市、道路、道路 |
| 火紅 | 4號道路寶可夢中心 | 華藍市寶可夢中心 | 314 | 84.1 秒 | 42.3 秒 | 穿過三層洞窟,其中一層走兩次 |
步行秒數是格數乘以 16 個影格再除以 59.7275 赫茲。跑步是第二次搜尋,求的是最少影格數,每格 8 個影格,但每次跳下矮崖仍是 32 個影格,因為不論步態為何,那都是同一個 32 影格的跳躍;上表七段路程中,只有洞窟那一段跳了一次矮崖,所以它是 2,528 個影格而不是 2,512 個。「居合斬後」的數字是同一個搜尋,但移除了2號道路上的五棵居合斬樹,這會打通穿過 ROUTE2_EAST_BUILDING 的東側路徑。淡出與戰鬥不計入。7 搜尋中有兩項簡化對洞窟那一段影響最大:第三世代的高度只簡化為兩種情況,高度相同,或任一側為 0 或 15;傳送點在踩進去時觸發,而不是在抵達時觸發;所以穿過洞窟的 314 格,路徑依序經過 1F、B1F、B2F 再回到 B1F,是表中最不確定的數字。29
門到門的去程,以及量過回程者的回程,並把規格書的目標放在經典作品旁;由 figures/make_figures.py 依 travel.json 繪製。
第一段路程是 15 到 25 秒的步行。 從家門口到下一座城鎮的寶可夢中心,在《綠寶石》是 58 格、15.5 秒,在《火紅》是 94 格、25.2 秒。7 長達一分鐘的路程(《綠寶石》的 250 格、《火紅》的 267 與 314 格)都要穿過森林或洞窟;依我的解讀,那些是迷宮,不是道路,而迷宮本身是另一種地方。
回程比較短。 從古辰鎮的寶可夢中心回到未白鎮的玩家家是 42 格,去程是 58 格;常磐市回真新鎮 76 格對 94 格;橙華市回古辰鎮 81 格對 87 格;卡那茲市回橙華市 196 格對 250 格;深灰市回常磐市 254 格對 267 格。7
秘傳學習器是一條穿過您早已路過的關口的捷徑。 移除2號道路的居合斬樹後,常磐市到深灰市是 156 格而不是 267 格,路徑改走2號道路的東側建築,而不是森林。7
豐緣的前三段路程
《綠寶石》一開場就對「城鎮之間有什麼」這個問題給了三種不同的答案,而量測讓這套設計變得一目了然。
101號道路是一條兩端各有一扇前門的走廊。它是 20 乘 20 格,高兩個畫面,22.8% 是高草叢,也就是 237 個可站立格中的 91 格;三段各四格、朝南的矮崖橫越其上。從未白鎮往北走要 34 格,至少踩過 10 格草叢;從古辰鎮往南走只要 19 格,而且只要跳下矮崖就能完全避開草叢。從玩家家門口到古辰鎮的寶可夢中心是 58 格,步行 15.5 秒。6187
102號道路比較寬,第一批訓練家也在這裡。它是 50 乘 20 格,寬 3.3 個畫面,草叢 14.3%,有 4 位視線 2 或 3 格的訓練家、一個 26 個水格的池塘,以及 17 個矮崖格。橙華市以 −10 的偏移量與它相接,所以道路的 20 列對著橙華市的第 10 到 29 列。寶可夢中心到寶可夢中心,古辰鎮到橙華市是 87 格、23.3 秒,回程 81 格。67
104號道路是一條分成兩半的道路。它是 40 乘 80 格,高八個畫面,四分之一是水,而且在地圖內沒有任何步行路線能連起它的三個接縫:南半部通往橙華森林的南入口,距橙華市的接縫 51 格;北半部通往森林的北出口,從卡那茲市的接縫到出口兩個門格中較近的一個是 60 格(到另一個是 61 格)。橙華市的寶可夢中心到卡那茲市的是 250 格,跨五張地圖(城市、道路、森林、道路、城市),步行 67 秒,回程 196 格。67
這三條之間改變的是分母。第一條道路走不到 20 秒,沒有訓練家,還有一條不必踩草的回家路;第三條則是一分鐘、穿過迷宮的步行。依我的解讀,這款遊戲要等玩家學會走路之後,才開始花用距離。
矮崖指向家
每一條兩端可步行相通的量測道路上的出門與回家,以及各方向最少的草叢格數;由 figures/make_figures.py 依道路腳本的輸出與 grass_paths.txt 繪製。
在每一條兩端可以步行相通的量測道路上,九條全都有矮崖,往較早那座城鎮的步行都比較短。《紅》1號道路回家 40 格、出門 52 格,短 23%;《火紅》1號道路 43 對 58,26%;《綠寶石》101號道路 19 對 34,44%;《綠寶石》102號道路 55 對 62,11%;《水晶》29號道路 71 對 90,21%;《水晶》30號道路 57 對 74,23%;《紅》3號道路 82 對 84、《火紅》3號道路 94 對 97、《水晶》32號道路 128 對 132,各短 2% 到 3%。32號道路的 4 格來自從一個角落矮崖橫向跳下的一次,那是回家路上唯一的一跳。5617 第一條道路上的矮崖全都朝同一個方向:《紅》1號道路 10 段共 42 個矮崖格、《火紅》1號道路 10 段共 61 格,以及《綠寶石》101號道路的 13 格(三段各四格,加一個角落格)全都朝南,角落那一格朝東南,而在這幾張地圖上,那正是回到出發城鎮的方向。56
矮崖也把草叢從回家路上移開。第二次搜尋改以穿越的高草格數最少為目標,而不是步數最少,結果發現:在《紅》1號道路上往北走至少要穿過 15 格草叢,往南只要 4 格,而且全在真新鎮旁的入口處;《火紅》1號道路是 19 對 5;《綠寶石》101號道路 10 對 0;《綠寶石》102號道路出門 4 格、回家 0 格。18 Bulbapedia 對關都1號道路的攻略用文字說了同一件事:往南走時,訓練家「can either hop down ledges to completely avoid any contact with wild Pokémon」(可以跳下矮崖,完全避開與野生寶可夢的任何接觸),或是穿過「the grass patches, the required path for northbound travel.」(草叢,那是往北走的必經之路)。35 這是一個維基頁面,只用了一次,作為搜尋結果的交叉驗證,而搜尋與它一致,只有真新鎮旁的入口處例外:那裡寬兩格、深四列草叢,所以格模型中每一條往南的步行都會在那裡踩過 4 格草叢,每列一格。矮崖讓步行者繞過路上其他所有的草叢,但不是全部。18
第二世代的數字各需要一項附註。《水晶》的碰撞是每個 32 像素 block 四個位元組,每個象限一個,而腳本假設它們依列優先順序排列(左上、右上、左下、右下),因為 tilecoll 巨集沒有寫明順序。29 第二世代的 COLL_HOP_* 位元組標記的是玩家站立的崖緣,而不是崖緣之外那一格,這一點由 probe_gen2_ledges.py 確認:在30號道路的 21 個 HOP_DOWN 格下方,每一個都有一個 WALL 格;崖緣的解讀只在 29 與 30 號道路上探測過。529 《水晶》還有單向牆。從 COLL_RIGHT_WALL 到 COLL_UP_LEFT_WALL 的碰撞位元組是陸地,但 GetMovementPermissions 會拒絕從這類圖塊的牆面一側走出去的一步,也拒絕從那一側走進來的一步;在32號道路上,四條中唯一有這種牆的一條(41 格,全是 UP_WALL),它們把最短步行從忽略單向牆的搜尋所得的雙向 110 格,拉長為出門 132 格、回家 128 格。它的角落矮崖 HOP_DOWN_RIGHT 與 HOP_DOWN_LEFT,在一般的一步被拒絕之後,會依玩家面向的方向往下或往側面跳,而回家的路就從其中一個橫向跳下。《紅》沒有單向牆:它唯一的另一條移動規則,也就是圖塊對碰撞,是雙向的,而且只列在洞窟與森林的圖塊集上,這幾張地圖都沒有用到。5 此外,搜尋把一次矮崖算作兩格移動,這與第三世代 32 個影格的跳躍相符,在第一與第二世代則是假設。293
資料顯示的是結果,不是意圖:我沒有找到任何開發者說明矮崖是為了縮短回家路而擺放的,只知道在每一條量過的道路上,它們確實有這個效果。29 我從中得出的規則是:矮崖是一條回程的單向捷徑,絕不是一面擋在前方的牆。
高度的問題
這項發現與上一篇有些扞格。像素藝術建築背後的建築研究筆記直接拒絕了矮崖,「No ledges to jump, no climbing, no isometric,」(沒有可以跳的矮崖,沒有攀爬,沒有等角視角),而那篇文章也把單向矮崖列在它的版本不做的事情裡。3637 這份研究筆記則把矮崖量成經典作品的回家捷徑。兩者在事實上並不衝突;它們的分歧在於,一座收藏城鎮是否需要高度才能得到這個效果。這個決定屬於本計畫的下一篇,關於高度、矮崖與橋梁,尚未發表。在那裡做出決定之前,第 7 節的規格書把矮崖延後交給高度篇,改用一道樹籬門來取得較短的回家路:草原步道上一道只能從湖那一側打開的門,同樣是回程的單向捷徑,卻不需要任何高度。26
3. 接縫、關口與地圖畫面
寶可夢的野外不是一張大地圖。它是許多小地圖,各有自己的尺寸、圖塊與音樂,邊對邊接在一起,讓您從一張走進下一張時不會有切換。接合的方式幾乎決定了這一節的其他一切:建築為什麼必須擋在路上、重製版為什麼比較少,以及地圖畫面能知道什麼。
第一與第二世代:複製到邊距裡的一條帶狀區
《紅》的一條連接是地圖標頭中的一行,寫明方向、鄰圖與以 block 計的偏移量;1號道路的是 connection north, ViridianCity, VIRIDIAN_CITY, -5,而巨集會預先算好鄰圖的哪一條 block 帶要複製進目前地圖的緩衝區。38 緩衝區 wOverworldMap 存放地圖,四周各有 MAP_BORDER EQU 3 個 block、也就是 6 格的邊距;連接會以鄰圖最近的 3 列或 3 欄 block 填滿這段邊距,而當玩家的座標越過 −1 或地圖的寬度或高度時,CheckMapConnections 就切換地圖。39
因為這條帶是以 block 編號複製,並用當時載入的 blockset 繪製,兩張地圖只有在共用同一組圖塊集時才能在接縫處相接,而《紅》的資料也遵守這一點:它的 78 行連接中,有 76 行連接的地圖使用相同的圖塊集。兩個例外是 22號道路與 23號道路,OVERWORLD 對 PLATEAU,而標頭檔把這兩行都標註為「; unnecessary」(不需要)。840 《水晶》有 142 行連接;其中 138 行連接同一組圖塊集(例外是滿金市與35號道路,以及32號與33號道路,Johto 對 Johto Modern),92 行留在同一個地圖群組內。841
第三世代:鄰圖的七格(東側八格),以及跨越時才載入的美術
第三世代的一條連接,是地圖 map.json 中的一筆 map、offset 與 direction。《綠寶石》有 134 筆上下左右的連接,另有 14 筆潛水與浮上的連接,而這 134 筆每一筆都有一筆偏移量取負號、方向相反的對應連接。8 Porymap 是反編譯社群使用的地圖編輯器,它用三句話描述這項功能:「Maps can be connected together so that the player can seamlessly walk between them」(地圖可以彼此連接,讓玩家在其間無縫行走);「A connection has a direction, offset, and destination map」(一條連接有方向、偏移量與目的地地圖);以及「Connections are one-way, which means that you must keep the two connections in sync between the two maps.」(連接是單向的,所以您必須讓兩張地圖之間的兩條連接保持同步)。42
引擎把目前的地圖複製進一個緩衝區,每一側留 MAP_OFFSET 7 格的邊距(東側 8 格,因為緩衝區寬度是地圖寬度加上 MAP_OFFSET_W,即 2 × 7 + 1),標頭還在註解中寫出理由:「the player has 7 metatiles of view horizontally in either direction.」(玩家水平方向兩側各看得到 7 個 metatile。)FillSouthConnection 與同類函式把鄰圖的 7 列或 7 欄(東側 8 欄)依連接的偏移量複製進邊距,範圍只涵蓋鄰圖本身的寬度(FillNorthConnection 從偏移量加 MAP_OFFSET 處開始,在放得下的範圍內複製鄰圖的寬度),而整個緩衝區最多只能容納 MAX_MAP_DATA_SIZE 10240 格。9 沒有連接的地方,邊距就是版面的 2 乘 2 邊界圖樣,並與 MAPGRID_IMPASSABLE 做 OR 運算,讓它永遠無法行走(GetBorderBlockAt)。9
接縫處緩衝區的內容,示意圖:一個方塊代表一格,尺寸依 MAP_OFFSET、FillNorthConnection 以及101號道路 20 乘 20 的版面與北側偏移量 0、寬 20 的古辰鎮版面而定;由 figures/make_figures.py 繪製,未使用任何遊戲美術。
在玩家跨過去之前,鄰圖的帶狀區是以目前地圖的圖塊集繪製的(DrawMetatileAt 取用您所在地圖的主要與次要圖塊集)。跨越時會呼叫 LoadMapFromCameraTransition,它執行新地圖的轉場腳本,只載入它的次要圖塊集與調色盤,切換音樂與天氣,並顯示地名提示,沒有淡出。10 這就是為什麼沒有任何一對相接的地圖主要圖塊集不同:《綠寶石》134 對中是 0,《火紅》120 對中也是 0。《綠寶石》有 38 對、《火紅》有 34 對連接的是不同的次要圖塊集,而其中各有 24 對,邊距中鄰圖的帶狀區(7 格,東側 8 格,超出緩衝區的部分會像引擎那樣截掉)只使用主要 metatile,所以從哪一側看都能正確繪製。8
偏移量很常見。《綠寶石》134 條連接中有 36 條、《火紅》120 條中有 68 條的偏移量不為零;《火紅》的1號道路位於常磐市 48 格寬度中的第 12 格處。8 在《火紅》中,對應關係並非完全普遍:120 筆中有 112 筆是雙向對應的,而那座被城牆圍起的城市佔了其餘 8 筆中的 6 筆。金黃市單向連接到 5 到 8 號道路,而這幾條道路連接的不是金黃市,而是另一張 SAFFRON_CITY_CONNECTION 地圖;最後兩筆是七之島的原型地圖。8 我把金黃市的安排解讀為一種繪圖技巧:這條連接的存在,是為了在道路邊緣之外畫出城牆,而進城的路要穿過四座關口,在《紅》中,關口的守衛會把玩家擋回去,直到拿到飲料為止。819 當一個地方必須看起來連續、卻又要設關卡時,第三世代會把鄰圖畫在邊距裡,再把門放進一棟建築。
跨過接縫進入一張設有 show_map_name 的地圖時,地名會滑入畫面。這個提示任務先等 31 個影格,以每影格 2 像素滑入 40 像素(20 個影格),停留到計時器超過 120(121 個影格),再滑出(20 個影格):約 190 個影格,3.2 秒。21 我是依各狀態的門檻來數這個任務的狀態,所以每個狀態可能比我算的多或少一個影格。29
關口:美術改變的地方
《紅》有 27 張地圖使用 GATE 或 FOREST_GATE 圖塊集:11、12、15、16 與 18 號道路的兩層關口(10 張地圖),2、5、6、7 與 8 號道路的單層關口,22號道路關口,狩獵地帶的關口與它的 4 間休息小屋,4 個地下通道入口,以及 2 座常磐森林關口。8 《紅》每一座連接兩個不同地方的關口都連接兩組圖塊集:OVERWORLD 接 FOREST(常磐森林、狩獵地帶),接 UNDERGROUND(四個通道入口),或接 PLATEAU(22號道路接23號道路),而這些組合是《紅》的連接幾乎從不使用的。8
2號道路是最清楚的例子。它在《紅》中是 20 乘 72 格,在《火紅》中是 24 乘 80 格,無法從一端走到另一端:它的南北兩半只透過常磐森林相通,而常磐森林在《紅》中是以 FOREST 圖塊集而非 OVERWORLD 繪製,經由兩座關口進入。58 在《火紅》中,常磐市的寶可夢中心到深灰市的是 267 格,經過南關口、森林與北關口。7 由此得出三點解讀,而它們是對資料的解讀,不是開發者的說法。
- 關口在美術改變的地方。 第一世代的接縫以目前的 blockset 繪製鄰圖的 block,所以兩張地圖只有在共用同一組圖塊集時才能在接縫處相接,而《紅》的 78 條連接中有 76 條遵守這一點。因此一座以森林圖塊集繪製的森林需要一扇門,而關口就是那扇門。388
- 關口是規則可以存在的地方。 金黃市的四座關口會把玩家擋回去,直到給了飲料為止(
TEXT_ROUTE5GATE_GUARD_GEE_IM_THIRSTY、BIT_GAVE_SAFFRON_GUARDS_DRINK);22號道路的關口檢查BIT_BOULDERBADGE。19 還有三座關口獎勵收集:當擁有的寶可夢種類數(以CountSetBits計算wPokedexOwned)在2號道路關口達到 10、在11號道路關口二樓達到 30、在15號道路關口二樓達到 50 時,大木博士的助手會送出道具。20 - 關口是有人在裡面的一次停頓。 2號道路的關口裡有大木博士的助手與一個少年;淡出之後,步行在一個新的地方繼續。43
重製版依引擎分成兩路。《水晶》有 25 張環境為 GATE 的地圖,其中 19 張連接不同地圖群組的地圖,7 張連接不同的圖塊集。8 《火紅》保留了關都的關口:8 張室內地圖通往兩張不同的戶外地圖(常磐森林那一對、金黃市的四個入口、22號道路的北入口與狩獵地帶入口),另外 13 張的門開在同一張戶外地圖的兩個不同側,其中包括五座兩層的道路關口與2號道路的東側建築。8 豐緣幾乎把關口拿掉了。在《綠寶石》中,只有 2 張室內地圖通往兩張不同的戶外地圖(送神山的一樓與狩獵地帶入口),5 張的門開在同一張地圖的兩側,兩座自行車道站在其中;道路與城鎮在開放的接縫處相接。8 以建築計,《綠寶石》有 4 座關口(兩座自行車道站、狩獵地帶入口與對戰開拓區的關口),相對於《火紅》的 14 座與《紅》的 27 張關口圖塊集地圖。8 我的解讀,而它只是解讀:豐緣之所以能拿掉關口,是因為它的引擎在跨越的那一刻載入新的次要圖塊集,所以一條道路可以和以不同美術繪製的城鎮相接,中間不需要任何建築。10
地圖畫面與「飛翔」
《綠寶石》的地區地圖是 28 乘 15 的方格(MAP_WIDTH 28、MAP_HEIGHT 15),共 420 格;其中 158 格,即 37.6%,屬於某個地方,分給 54 個區段:34 條道路、16 座城鎮與城市,以及 4 個其他地點。道路佔 129 格,城鎮佔 22 格。11 region_map_sections.json 的 213 筆項目中,有 95 筆在方格上有一個矩形;其中 49 個只有一格,最大的是 4 乘 3(124號道路與它的海底版本),凱那市是 1 乘 2,紫堇市是 2 乘 1。11 就這些證據來看,地圖畫面是一張由具名矩形組成的粗略方格,大多數地方只佔一格。
「飛翔」是地圖畫面與步行交會的地方。《綠寶石》的「飛翔」需要第六個徽章(徽章表中 FIELD_MOVE_FLY 對應 FLAG_BADGE06_GET),以及戶外的地圖類型(Overworld_MapTypeAllowsTeleportAndFly:道路、城鎮、城市或海上道路)。在地圖上,一座城鎮只有在它的 FLAG_VISITED_* 旗標已設下時才是 MAPSECTYPE_CITY_CANFLY,而 16 座城鎮與城市各自在自己的轉場腳本中設下旗標;古辰鎮的寫的是 setflag FLAG_VISITED_OLDALE_TOWN。13
其他幾款的形狀也一致。《火紅》的地圖每頁是 22 乘 15 格,共四頁(關都與三組七之島),每頁分兩層,野外與迷宮;關都的野外層以 38 個區段填滿 90 格,「飛翔」需要第三個徽章。《火紅》的一個地方在它的 FLAG_WORLD_MAP_* 旗標設下時才算去過,而每座城鎮的轉場腳本都會在進入時設下它(setworldmapflag FLAG_WORLD_MAP_CELADON_CITY),「飛翔」也只接受去過的方格。23 《水晶》的地圖以像素位置放置 95 個地標,提供 24 個飛行點,城都 12 個、關都 12 個;一座城鎮的飛行點在您進入時由回呼設下(VioletCityFlypointCallback: setflag ENGINE_FLYPOINT_VIOLET),而「飛翔」檢查的是 ENGINE_STORMBADGE。24 《紅》的城鎮地圖在一個 4 位元的方格上列出 37 筆戶外項目(其中一筆標為未使用),以及 60 筆各有自己位置的室內項目;進入它 11 張城市地圖中的任何一張,都會在 wTownVisitedFlag 中設下該城市的位元,原始碼的註解是「mark town as visited (for flying)」(把城鎮標記為去過,供飛翔使用),飛行清單會跳過沒去過的城鎮,而「飛翔」需要橙色徽章與一張戶外地圖。22
這四款都不是一開始就讓玩家帶著地圖;每一款都在遊戲進行中才交給玩家。《紅》與《火紅》的城鎮地圖是真新鎮勁敵的姊姊給的道具(在《紅》中,要等玩家拿到寶可夢圖鑑之後才會給);《水晶》的地圖卡是在吉花市拿到的禮物,那是離開家鄉後的第一座城鎮;《綠寶石》的寶可導航來自卡那茲市的得文公司大樓,那是離開家鄉後的第三座城鎮。442345127 壁掛地圖能更早打開同一個畫面:《綠寶石》在玩家家的樓上與每一間寶可夢中心的一樓各掛了一幅,《火紅》掛在寶可夢中心、常磐市的學校與一些民宅裡,而《紅》的 HOUSE 圖塊集,也就是真新鎮勁敵家使用的那一組,有一個壁掛地圖圖塊。122344
把這些放在一起,規則在四款遊戲、三個世代中是一致的。玩家一旦有了地圖,它就畫出地區裡的每一個地方,不論去過與否;例外是被劇情或活動擋住的後期地點:《火紅》的七之島頁面,要等1之島寶可夢中心裡的腳本設下它們的旗標,而在那幾頁上,肚臍岩與誕生之島會被塗掉,直到玩家去過為止;《綠寶石》的對戰開拓區與南方孤島,要等它們的地標旗標;《水晶》的關都地圖,要等玩家站上關都。44451223 快速移動只列出您走進去過的地方,由進入時設下的旗標記錄,只能在戶外使用,而且要等到遊戲中段的一個徽章之後:《紅》與《火紅》是第三個,《綠寶石》是第六個。13232422
4. 其他模型:星露谷物語、動物森友會、織夢島
寶可夢是「城鎮之間的土地」的一種答案。另外三款遊戲各有不同的答案,而每一個差異都是 Kiradex 必須做的一項選擇。本節的數字來自這些遊戲的社群維基,《織夢島》則來自一份反組譯專案;除了註腳特別說明之處,我沒有拿它們去對照遊戲程式碼。29
星露谷物語:一個樞紐加上輻條,依行事曆開放
《星露谷物語》(Stardew Valley)在每一個邊緣都會淡出。用它的維基的話說:「When you reach the edge of an area or enter a building, and the screen fades to black during the transition, you’re moving between maps.」(當您走到一個區域的邊緣或進入一棟建築,而畫面在轉場時淡出成黑色,您就是在地圖之間移動。)傳送點是一個地圖屬性,維基給出的值格式是 Warp [ <int fromX> <int fromY> <string toArea> <int toX> <int toY> ]+,範例為「6 20 Mountain 76 9」。46
這座山谷是圍繞一個樞紐的小型圖。鵜鶘鎮(Pelican Town)在西北方連接公車站與農場,西南方連接煤灰森林(Cindersap Forest),南方連接海灘,北方連接山區。47 公車站「connects the Farm to the west with Pelican Town to the east」(連接西邊的農場與東邊的鵜鶘鎮),並且順著往西的道路通往下半部的後山森林(Backwoods)。48 煤灰森林「has exits to the north into the Farm, to the east into Pelican Town, to the south into The Sewers, and to the northwest into the Secret Woods.」(北邊出口通往農場,東邊通往鵜鶘鎮,南邊通往下水道,西北邊通往秘密森林。)49 後山森林「consists of two disconnected sections」(由兩個互不相連的部分組成),上半部的小徑連接農場與山區,下半部則從公車站的道路進入。50 依我的計算,這些頁面上的地點導覽框列出了 26 個地方,從後山森林到女巫沼澤。51
輻條依日期與行動開放。山區一開始「only two exits: to the south leading to Pelican Town, and to the west leading to the Backwoods」(只有兩個出口:往南通往鵜鶘鎮,往西通往後山森林);礦井在春季第 5 天開放,鐵路在夏季第 3 天的一場地震之後開放,採石場的橋則要完成工藝室的收集包或付 25,000g。52 沙漠需要修好公車,靠金庫的收集包或 40,000g;秘密森林需要用鋼斧或更好的斧頭清掉一根原木;海灘的潮池需要一座用 300 木材搭的橋。53 維基列出玩家的基本速度是走路 2、跑步 5、騎馬 6.6,沒有單位,而我沒有打開遊戲的程式碼去查。54
我的解讀:寶可夢以玩家身上帶著的能力來封鎖一個區域,一個徽章或一個秘傳學習器。《星露谷物語》則以社群做過的事來封鎖,一個收集包、一座橋,或某個日期的一場地震。後者是一座共享的收藏城鎮可以使用的那一種,不會讓任何人因為等級而覺得被擋在門外。
動物森友會:區塊方格與火車
GameCube 版的《動物森友會》(Animal Crossing)用區塊(acre)組成它的城鎮。「Acres are grid elements measuring 16x16 spaces」(區塊是 16x16 格的方格元素);城鎮是「five acres across by six down」(橫五個區塊、縱六個區塊),火車站「always located in Acre A-3」(總是位於 A-3 區塊),而地圖把欄標為 1 到 5,把列標為 A 到 F。55 總共是 80 乘 96 格。17 後來的作品改變了方格:Wild World 是 4 乘 4 個區塊,City Folk 5 乘 5,New Leaf 5 乘 4,《集合啦!動物森友會》(New Horizons)7 乘 6,共 42 個區塊。55
區塊也是一條生成規則。「Every time the player moves into an acre (with the exception of a player exiting their house upon loading the game), one fish and one bug spawn at the locations programmed within the acre data,」(每當玩家移入一個區塊時(載入遊戲後走出自己家除外),就會在區塊資料中設定的位置生成一條魚和一隻蟲),而玩家離開那個區塊六格之後,牠們就會消失。55 城鎮地圖很早就拿到,在打工期間由狸克給的;它「is organized in a grid, with each square representing an acre,」(以方格組織,每一格代表一個區塊),顯示海、河流、建築與住家,但不顯示樹木、物品或村民。56
火車是 GameCube 版前往另一座城鎮的唯一方式,透過記憶卡,而「The train ride takes a few minutes」(搭火車要花幾分鐘);車站「is located at the top of the player’s town,」(位於玩家城鎮的最上方),新遊戲也是從玩家抵達那裡開始。57
我的解讀:區塊讓一座城鎮變得易讀,成為一張由具名方格構成的地圖,並提供一條生成規則,獎勵四處走動,卻不必每一步擲骰。火車則讓世界之間的移動成為一場戲,而不是一道接縫。
織夢島:切成一個個畫面的世界
《薩爾達傳說 織夢島 DX》(The Legend of Zelda: Link’s Awakening DX)採取了與寶可夢連續捲動相反的做法。在 zladx 反組譯專案的文件中,遊戲「takes place in non-scrollable screen-wide rooms,」(發生在不可捲動、與畫面同寬的房間中),而「On the Overworld, rooms are stored as they are laid out during the game, on a 16x16 grid of rooms.」(在野外,房間依它們在遊戲中的排列方式儲存,位於一個 16x16 的房間方格上。)58 野外地圖是 512 位元組、由兩位元組的房間指標組成,共 256 個房間,而一個房間的圖塊覆蓋層是「a straight forward array of 10x8 tile numbers」(一個單純的 10x8 圖塊編號陣列)。59 「Overworld rooms are grouped in 2x2 sections (i.e. 4 rooms). Each section has a tileset ID,」(野外的房間以 2x2 的區段分組,也就是 4 個房間。每個區段有一個圖塊集 ID),而轉場進入需要它們的房間時,會換入該區段的 32 個圖塊。58 每一個野外房間在 wOverworldRoomStatus 中都有一個狀態位元組,共 256 位元組,其中 OW_ROOM_STATUS_VISITED EQU $80 與表示門、變化與貓頭鷹的位元並列;這個位元是我從反組譯的常數讀出來的,並沒有追查地圖畫面是否只畫出設有它的房間。25
我的解讀:寶可夢捲動一個連續的世界,並分段命名;《織夢島》把世界切成大小相同的畫面,並記錄您看過哪些。在一支視野因裝置而異的手機上,iPhone 18 Pro Max 直向橫跨 10.3 個圖塊,展開的 iPhone Duo 則是 29.7 個,只有捲動模型能在不替每個房間加上黑邊的情況下運作,這也是規格書採用它的原因。60
5. 這門手藝,化為帶數字的規則
以下是量測所支持的規則,每一條都附上來源。當一條規則把量測到的事實轉成建議時,建議是我的,規則會用它的動詞表明這一點。
| 元素 | 規則 | 來源 |
|---|---|---|
| 步調 | 走一格花 16 個影格,約 0.27 秒;Kiradex 的一個圖塊花 0.25 秒。每趟旅程都以遊戲自己的步速換算成秒數來決定大小。 | 《紅》、《水晶》與《綠寶石》的步進程式碼;GBATEK;PlazaRig.swift |
| 道路長度 | 沿行進軸向 2 到 10 個畫面;一款遊戲的第一條道路 2 到 6 個。 | 量測的十六條道路 |
| 蜿蜒 | 較長的穿越步行是道路長邊的 1.15 到 1.70 倍,回家路是 0.95 到 1.42 倍。以路徑而不是外框來決定道路的大小。 | 兩端可步行相通的九條道路上,最短步行除以長邊 |
| 第一段路程 | 從一扇門到下一座城鎮的主要大門 15 到 25 秒。一分鐘的路程是迷宮,不是道路。 | 《綠寶石》58 格、《火紅》94 格;穿過森林或洞窟的 250、267 與 314 格 |
| 比例 | 以面積計,道路是城鎮的 2 到 4 倍;在地圖畫面上是 5.9 比 1,因為一座城鎮只佔一或兩格。 | 《水晶》1.97、《紅》2.65、《綠寶石》3.70、《火紅》3.87;《綠寶石》的地圖 |
| 密度 | 遭遇地面佔第一條道路地圖的 14% 到 23%,到第三、第四條道路時變稀為 4% 到 9%;以可行走地面計,第一條道路是 19% 到 38%。第一條道路沒有訓練家。 | 道路表;草叢佔可行走格的比例 |
| 節奏 | 在遭遇地面上大約每九步有一樣值得注意的東西,抵達後先給幾步寬限;用下限與漸增讓乾旱與氾濫都保持短暫。 | 《綠寶石》的四步免疫,接著遭遇率 20,未修正為 11.1%;《火紅》的冷卻與加成 |
| 回家的路 | 比出門的路短,在兩端可步行相通、都有矮崖的九條道路上短 2% 到 44%,草叢格數 0 到 5 對 4 到 19。矮崖是回程的單向捷徑,絕不是一面擋在前方的牆。 | 九條道路;最少草叢的搜尋 |
| 接縫 | 只有在兩個區域都以同一套地面繪製邊緣時,才讓它們在開放的接縫處相接;在美術改變的地方放一棟有兩扇門的建築。 | 《紅》78 中 76、《水晶》142 中 138、第三世代主要圖塊集不同者 134 中 0 與 120 中 0;《紅》每一座連接兩個地方的關口 |
| 鄰圖帶狀區 | 在邊緣之外至少畫出半個視野的鄰圖。 | 第三世代 15 格畫面的 7 格(東側 8 格);第一世代 10 格畫面的 6 格 |
| 連接 | 偏移量是常態;每一條連接都有一條偏移量取負號的反向連接,單向的連接是刻意的繪圖技巧。 | 《綠寶石》134 中 36 有偏移、134 中 134 雙向對應;《火紅》的金黃市 |
| 地名卡 | 進入時顯示地名約三秒。 | 《綠寶石》的提示,約 190 個影格 |
| 關口 | 關口是規則與獎勵存在的地方:里程碑檢查、一位守衛、達到某個數量時的禮物。 | 《紅》的徽章檢查、口渴的守衛,以及擁有 10、30、50 種寶可夢時的禮物 |
| 地圖畫面 | 一張由具名矩形組成的粗略方格;大多數地方一格,最大的幾格。 | 《綠寶石》28 乘 15,95 個矩形中 49 個只有一格,最大 4 乘 3;《動物森友會》5 乘 6 的區塊 |
| 快速移動 | 玩家一旦有了地圖,它就顯示每一個地方,不論去過與否,只有少數被劇情或活動旗標擋住;快速移動只顯示您去過的地方,由進入時的旗標設定,且只能從戶外出發。 | 城鎮地圖、地圖卡、寶可導航與壁掛地圖;《紅》的 wTownVisitedFlag、《水晶》的飛行點、《綠寶石》的 FLAG_VISITED_*、《火紅》的 FLAG_WORLD_MAP_*;一筆未追查到地圖畫面的「去過」紀錄:《織夢島》的 OW_ROOM_STATUS_VISITED 位元 |
| 解鎖 | 區域依行動與日期開放,也依能力開放。 | 《星露谷物語》的礦井、鐵路、採石場、沙漠與秘密森林;居合斬讓 267 格變成 156 格 |
| 轉場 | 在同一個戶外世界內用接縫;穿過門與在世界的邊緣時淡出。 | 寶可夢的接縫、《星露谷物語》的淡出、《織夢島》的推移 |
| 方向 | 讓道路沿著裝置的長軸延伸;一條在一個視野內就能完整看見的道路,已經沒有東西可以揭露。 | 10 月 3 日的圖塊數;《綠寶石》101號道路的 20 乘 20 對展開的 Duo 的 29.7 乘 20.9 |
表格來源:第 1 節的步進程式碼與影格時序;12315 Kiradex 的步行速度;4 道路、旅程、草叢與連接腳本;567188 遭遇程式碼;3132 《綠寶石》的緩衝區、跨越與提示程式碼;91021 關口腳本;1920 地圖畫面、玩家取得它們的方式,以及「飛翔」;1112132345244422 《星露谷物語》、《動物森友會》與《織夢島》;465253555825 以及本系列第一篇的各裝置圖塊數。60
其中三條規則值得多說一句。
接縫規則是以美術而非引擎來陳述的。Game Boy 用目前地圖的 blockset 繪製鄰圖的帶狀區,所以完全無法顯示使用不同圖塊集的鄰圖;Game Boy Advance 在您跨過去之前,都用目前地圖的圖塊集繪製帶狀區,依我的解讀,這就是它的接縫為什麼都維持同一組主要圖塊集,而且大多只用主要 metatile。38108 我把這兩者都解讀為圖塊記憶體的限制,而一支只繪製一張圖集的手機並沒有這個限制。留下來的是視覺上的理由:一條路在畫面上一條直線處突然換了材質,不論引擎是否需要,看起來都像一道接縫。這就是為什麼即使沒有任何東西強迫,這條規則仍保留它的前半(地面延續之處開放相接)與後半(美術改變之處放一棟建築)。
快速移動的規則是最值得原樣照抄的一條。這裡量測的每一款寶可夢遊戲都在遊戲早期交出地圖,在地圖上畫出每一個地方,不論去過與否,只保留少數被劇情或活動旗標擋住的後期地點,並且只封鎖捷徑;每一款都是靠走到某個地方來換得捷徑,而不是靠付費。44451213232224 對一個 XP 只來自在現實世界掃描卡片的收藏 App 來說,「去過」旗標是對的貨幣,因為它只花一趟步行,別無其他。27
方向的規則來自手機,而不是經典作品。Pro Max 直向以 Kiradex 的比例看得到 10.3 乘 22.4 個圖塊,展開的 iPhone Duo 是 29.7 乘 20.9,所以一條南北向的道路在常見的手機上一次顯示 22 個圖塊的長度,在鏡頭置中的收藏者前方約 11 個;而一條東西向的道路一次顯示 30 個、前方約 15 個,只發生在展開的 Duo 上。602717 《綠寶石》101號道路是 20 乘 20,能整個放進展開的 Duo 的一個視野內,這代表一條那麼短的道路,在那裡已經沒有東西可以揭露。617
6. Apple 的做法:視野、接縫、房間與一張地圖
Kiradex World 底下的引擎,就是第一篇所提出的那一套:一個當作 2D 算繪器使用的 RealityKit 場景、一台每個 texel 對應整數個裝置像素的正交鏡頭、以一個 MeshDescriptor 網格一次繪製呼叫畫出的地面、每一種材質都是帶不透明度門檻的 UnlitMaterial,並關閉 RealityView 的動態模糊與色調映射。60 建築篇再加上依深度排序、放在第二個網格裡的物件、以資料表示的傳送點,以及原地切換的樓層:給舞台一個新的身分 .id(floor.name),讓 SwiftUI 拆掉一個 RealityView 再建起下一個。37 圍籬之外的區域還需要這套引擎提供三樣東西:跨過接縫看見鄰圖的視野、在伺服器上不切斷畫面就換房間的方法,以及一個地圖畫面。這些都還沒有建置,本節沒有任何一項在手機上量測過;這是規格書所依據的計畫,每一項成本都會寫明,並標示為未量測。
一支手機看得到什麼
圖塊數來自第一篇的表格,以 Apple 的規格頁面與 Xcode 27 模擬器設定檔,在 Kiradex 的整數比例下計算而得。60
| 裝置與方向 | 每 texel 像素數 | 可見圖塊(橫 乘 縱) | 半個視野 |
|---|---|---|---|
| iPhone 18 Pro Max,直向 | 8 | 10.3 乘 22.4 | 橫 6,縱 12 |
| iPhone Duo 外螢幕,直向 | 6 | 14.6 乘 21.2 | 橫 8,縱 11 |
| iPhone Duo 內螢幕,展開 | 6 | 29.7 乘 20.9 | 橫 15,縱 11 |
最後一欄是半個視野無條件進位,也就是接縫規則要求的鄰圖帶狀區。17 Apple 給出 iPhone Duo 內螢幕是 1878 乘 2670 像素、每英寸 430 像素,外螢幕是 1398 乘 2034、每英寸 460 像素。61 內螢幕的圖塊數並不是從這些面板像素算來的:它們來自模擬器 2007 乘 2853、比例 3.0 的邏輯影格緩衝區(2853 ÷ 96 是 29.7,2007 ÷ 96 是 20.9),而第一篇說明過,這個緩衝區會以約 0.936 的比例降採樣到玻璃上,所以在硬體上,同樣數量的圖塊應該會填滿面板,約每 texel 5.6 個面板像素,前提是合成器如第一篇所預期的那樣降採樣;這一點尚未在硬體上驗證,而第一篇的第一步就是在裝置上記錄 nativeScale。6017 人機介面指南要求遊戲「look good and behave well at various aspect ratios」(在各種長寬比下都好看且運作良好),第一篇把這解讀為一個顯示更多世界而不是加黑邊的理由,而廣場正是這麼做的。6260
由此對這些區域得出兩點。鏡頭跟著收藏者時,前方的視野約是半個視野。27 南北向的道路是每一支手機都能好好呈現的那一種:在直向的 Pro Max 上一次看得到 22 個圖塊,前方約 11 個;在展開的 Duo 上一次 21 個,前方約 10 個。東西向的道路在展開的 Duo 上一次看得到 30 個圖塊,前方約 15 個,但在直向的 Pro Max 上只有 10 個,前方約 5 個,所以它適合放在目的地很近的地方。6017 這就是為什麼規格書裡那段長途步行,草原步道,是南北向的;也是為什麼卡冊街,最明顯東西向的區域(44 乘 14,寬度是高度的三倍多),是一條在展開的 Duo 上站在哪裡都能看到大半的店面街。湖畔(36 乘 20)與藝廊區(30 乘 20)也是寬大於高,但在規格書中它們是停留的地方,一片湖岸與一個博物館區,而不是通往他處的步道。2617
RealityKit 裡的一道接縫
建築篇的樓層切換用在接縫上是錯的工具。拆掉一個 RealityView 再建起下一個需要一段淡出來掩飾,而 App 的門目前還沒有:今天踏上門之後 220 毫秒地點就會改變,樓層變更時舞台的切換也沒有淡出,而本系列的動態篇提出了能同時蓋住兩者的那層遮罩。接縫按照定義就沒有淡出。376316 所以接縫的計畫是保持同一個場景。當收藏者走到一條有連接的邊緣、距離不到半個視野時,舞台把鄰圖的地面建成第二個網格,與第一個用同一種 MeshDescriptor,依連接的偏移量以世界單位放置,並把鄰圖的物件加在它們各自的深度上。當收藏者的腳跨過邊緣時,舞台重新定基:鄰圖成為目前的區域,舊的區域成為身後的帶狀區,而目前依地圖範圍夾住、跟著玩家的鏡頭,在兩者都載入時改為夾在兩者的聯集內。2726
有三項引擎事實讓這件事在紙面上很便宜。每個區域的地面是一個網格、一次繪製呼叫,所以一個鄰圖只為地面多一次繪製呼叫,為物件再多一次。6037 每個像素都是鏤空的並寫入深度,所以鄰圖的樹會以與目前區域相同的深度規則和收藏者排序,不需要新的排序。37 而工坊本來就把整個區域匯出為字母、自動拼接的地面、物件與傳送點,所以鄰圖就是另一個同類的檔案;規格書在其中加入一個 connections 清單。1426 這些都沒告訴我的是:在需要的那一刻建立第二個網格要花多少成本,或者在一個影格內建好它會不會造成卡頓;這一點尚未量測。因此規格書的檢查 6 分成兩半,而且兩半都涵蓋整個接近過程,從鄰圖的網格建立之前,一直到跨越結束:一份逐影格的時序紀錄,用來抓出主執行緒停頓;以及對每一個擷取影格的檢查,用來抓出黑畫面。取樣式的檢查不夠:每秒六十個影格中的一個黑畫面不一定會打亂回呼的時序,而每 100 毫秒取一次樣,在 60 赫茲下只看得到六個影格中的一個。17 時序那一半與逐影格的規則是我在研究筆記的檢查上新增的,原本的檢查是取樣亮度。
自動拼接是 GBA 的規則唯一沒有涵蓋的事。第三世代讓接縫維持在共用的地面上,因為鄰圖的帶狀區是用目前地圖的圖塊繪製的。108 Kiradex 以同一張圖集調色盤繪製每一個區域,所以那個限制不適用,但區域邊緣的 blob 自動拼接圖塊是依區域自己的字母計算的;如果工坊不看接縫另一側,一條穿過接縫的路會在兩側各收成一圈邊框。因此規格書要求工坊跨越每一條連接計算邊緣位元遮罩。26
房間、在場與重新連線
Kiradex 的廣場是一個伺服器房間:每個房間最多約 40 位玩家,依地區分片,建在 FastAPI 與 WebSockets 上。27 圍籬之外的一個區域,很自然就是另一個房間,這就帶出一個單人掌機從未遇過的問題:接縫另一頭的人。計畫是讓用戶端在開始繪製鄰近房間的帶狀區時,就訂閱那個房間的步行者,讓人從中央廣場的邊緣就看得到卡冊街上的人,並在跨越時才切換自己的房間,讓步行者永遠不必停下。26 門與電車會有淡出,也就是動態篇的規格書為門加上的那層遮罩,而這段淡出會蓋住重新連線,正如建築研究筆記早已為室內所提議的。1626 第一篇對黑色視野的修正也適用於這段淡出:對包住 RealityView 的 SwiftUI 視圖做不透明度動畫,會讓它沒有被繪製,所以淡出是一層畫在它上面的遮罩。60
伺服器需要與用戶端所繪製的同一張地圖。今天 server/app/plaza.json 帶著城鎮的字母,讓伺服器能驗證一次步行;規格書讓每個區域的副本都帶著它的字母與連接,讓一次跨越接縫的步行在接縫兩側都得到驗證。6026 在真實網路上換一次房間的成本,以及在接縫附近訂閱第二個房間是否會讓廣場的訊息量加倍,都尚未量測。
SwiftUI 裡的地名卡、地圖與電車
區域的名稱屬於 SwiftUI,而不是像素層。第一篇發現,一套五 texel 高的像素字型在 Kiradex 的比例下會落在 13.3 或 10 點,後者低於人機介面指南 11 點的下限,於是把對話文字移到 UI 比例的 SwiftUI;地名卡也是同一類文字。60 它的時間長度來自《綠寶石》:在畫面上約三秒。21
地圖畫面是一個建立在小型模型上的 SwiftUI 視圖:一張由具名矩形組成的方格、目前的區域,以及去過的區域集合。在展開的 Duo 上,它以面板的形式放在世界旁邊,就像世界計畫把收藏者的個人資料卡放在廣場旁邊那樣(「The plaza on the open inner display is the best version of this: wide view, your profile card beside the world when you tap someone」,展開的內螢幕上的廣場是它最好的版本:寬廣的視野,點一下某人時,您的個人資料卡就在世界旁邊);在一般手機上則是一張工作表(sheet)。27 電車是從地圖出發的一次傳送:淡出、換房間、在目的地的車站淡入。
去過的集合必須跟著收藏者走。人機介面指南的遊戲頁面寫道「Let players pick up their game on any of their devices,」(讓玩家在任何一台裝置上接續他們的遊戲),讓人能「start back up exactly where they left off on a different device.」(在另一台裝置上從他們離開的地方原樣接續)。62 在手機上開放的電車站,在 iPad 上也應該是開放的,所以去過的集合存放在伺服器上收藏者的個人資料中,而不是 App 的本機儲存。26 這延伸了世界計畫的第 3 節,那一節寫的是「Our server holds only the public profile card and the economy」(我們的伺服器只保存公開的個人資料卡與經濟系統);我會在它們旁邊多加一個欄位,就是去過的集合,只記錄收藏者進入過五個區域中的哪幾個,不含位置、不含文字、也不含造訪時間,並和其他資料一樣以 Game Center 玩家 ID 為鍵;計畫要求的隱私審視必須把它也涵蓋進去。2726
成本為何,以及哪些沒有量測
本節的一切都還沒有計時。未量測的成本包括:在收藏者接近邊緣的那一刻建立鄰圖的地面與物件網格;在兩個區域都載入時繪製兩者的網格;在接縫附近訂閱第二個房間;以及跨越時的房間切換。第一篇的擷取規則決定了它們要如何量測:在 iPhone Duo 模擬器上,RealityKit 的影格只有從測試套件內部擷取(XCUIScreen.screens[1])才忠實,從主機擷取則不然。60 規格書的檢查 6 正是用那種擷取來確認接縫前看得到鄰圖,而它的逐影格檢查與從 CADisplayLink 回呼讀取的影格時序紀錄,則在裝置版本上執行,而不是模擬器:有了展開的 iPhone Duo 之後就在它上面跑,在那之前則在 iPhone 18 Pro Max 上跑,因為在我寫作時,沒有任何 iPhone Duo 與我的 Mac 配對。64 目前還沒有任何擷取方法被證實能記錄每一個顯示出來的影格;檢查中的影格數比對,也就是錄影的影格數對上時序紀錄的回呼數,決定了某個方法是否做得到。
7. 規格書:Kiradex 要建什麼,以及必須通過的檢查
作者的綜合整理。 以下是我的提案,由第 1 到第 6 節推導而來,並配合 Kiradex 儲存庫中的世界計畫,也就是它的第 4 節(「The game」,遊戲)與第 7 節(「Order of work」,工作順序)。2726 它不使用任何寶可夢遊戲中的地名、生物或商標;借用某個機制時,它引用的是第 5 節的規則,而不是原作的用語。所有尺寸都以 16 像素的圖塊計,一段步行的長度是它在 App 自己的路徑搜尋上的成本,一步斜走算 √2 個圖塊(第 3 項)。28 所有時間都是這個長度以名目上每秒 4 個圖塊的步速換算,是用來與經典作品比較的數字,不是量測值;動態篇的規格書會讓直走一個圖塊花 16 個 tick(60 赫茲下每秒 3.75 個圖塊),斜走則進位為 23 個 tick,在這種情況下,這裡的每一個時間在直走時會增加十五分之一,在斜走時約增加十二分之一。41617
Kiradex 的循環在現實世界裡。XP 只來自掃描並登錄的卡片:「XP comes only from the real world, which is the point of the app.」(XP 只來自現實世界,這正是這個 App 的意義。)27 所以步行是一種成本,不是內容,而每一個區域都必須讓步行者能拿他們的收藏做點什麼:展示它、圍繞它交換、為它贏得一件裝飾品,或是被引導回掃描器。走路找到或贏得的任何東西都絕不會是一張卡或 XP,世界裡的任何東西都不以金錢販售,除非是透過世界計畫已經定義好的商店。27
1. 五個區域:中央廣場,以及它之外的四個
| 區域 | 它是什麼 | 尺寸(圖塊) | 連接到 | 在收藏循環中的角色 |
|---|---|---|---|---|
| 中央廣場(已存在) | 城鎮:房屋、大廳、販賣亭、噴泉平台、抵達前庭 | 40 乘 30 | 樞紐 | 抵達、炫耀最喜歡的卡、進入房間與大廳 |
| 卡冊街 | 一條購物街:建築工具組裡的卡牌店、交換站、一面求卡看板,之後再加一排六個市集攤位 | 44 乘 14 | 中央廣場,東側,開放接縫 | 花金幣買裝飾品;看看鎮上的收藏者想看什麼;交換畫面;市集階段到來時的市集攤位 |
| 草原步道 | 一條穿過草原與小樹叢的公園小徑,有會出現拾獲物的花叢,還有一條樹籬夾道的回家小巷,巷口的門只能從湖那一側打開 | 24 乘 60,長軸南北向 | 中央廣場,北側,開放接縫 | 拾獲物(第 6 項);和朋友一起散步;通往湖的路 |
| 湖畔 | 步道的盡頭:一片湖岸、一座棧橋、長椅、一間船屋 | 36 乘 20 | 草原步道,北側,開放接縫 | 坐下來在夕陽下展示一張卡的安靜地方;每週的聚會地點 |
| 藝廊區 | 一個石造街區:博物館與收藏者展示品的藝廊 | 30 乘 20,另加一間 8 乘 6 的拱門屋 | 中央廣場,西側,穿過拱門(兩扇門) | 整台伺服器完成的套組會填滿博物館;拱門裡的里程碑看守人(第 7 項) |
中央廣場之外的總面積是 616 加 1,440 加 720 加 600,共 3,376 個圖塊,是中央廣場 1,200 個圖塊的 2.8 倍,落在經典作品 1.97 到 3.87 的區間內(第 5 節,比例)。1733 藝廊區之所以位於一棟建築之後,是因為它是用另一套地面以石材繪製的(第 5 節,接縫)。另外三個區域與中央廣場共用草地與鋪面,所以以開放接縫相接,共三道:卡冊街接中央廣場的東緣,草原步道接它的北緣,湖畔接草原步道的北緣。26 卡冊街的求卡看板顯示鎮上的收藏者自己想看什麼,並由已經在做這件事的程式碼(Kiradex/World/Wants.swift)對照真實的卡片來判定;它的攤位要等到市集,世界計畫把市集安排在之後的階段、透過 eBay 進行。6527
依地圖畫面的方格繪製的提案區域;僅為示意,由 figures/make_figures.py 繪製,它會斷言每個矩形都是該區域的尺寸除以 10 後四捨五入。
檢查: 4(比例)與 5(方向)。
2. 距離與時間
| 路程 | 目標(圖塊) | 以每秒 4 個圖塊計 | 經典作品參考 |
|---|---|---|---|
| 抵達前庭到卡牌店的門 | 最多 60 | 最多 15 秒 | 第 5 節,第一段路程:15 到 25 秒 |
| 抵達前庭到湖畔棧橋,去程 | 110 到 120 | 28 到 30 秒 | 比第一段路程(15 到 25 秒)長,短於經典作品最長 40 秒的道路路程與一分鐘的迷宮;蜿蜒,去程是長邊的 1.15 到 1.70 倍 |
| 湖畔棧橋到抵達前庭,回程,穿過樹籬門 | 最多為去程步行的 0.8 倍 | 最多 24 秒 | 回家的路比較短 |
| 抵達前庭穿過拱門到博物館的門 | 最多 70 | 最多 18 秒 | 第一段路程 |
| 任一區域搭電車到任一去過的區域 | 一次淡出 | 約 1 秒加上重新連線 | 快速移動;轉場 |
草原步道 60 個圖塊的長度蜿蜒成 78 到 90 個圖塊的路徑,係數 1.3 到 1.5(第 5 節,蜿蜒)。26 光靠一條路徑無法讓回家的路變短:在每一步都能雙向行走的方格上,從抵達點到棧橋的最短步行,就是走回來的最短步行,而一條更直的路徑在去程也會被走。這項節省需要某種單向的東西。在這裡,它是一道樹籬門。一條 2 個圖塊寬、樹籬夾道的小巷,筆直地沿著草原步道的東側延伸,與棧橋對齊,一路到中央廣場北緣、大廳與它東邊那棟房子之間,那裡兩者之間的草地往下通向抵達前庭。小巷湖端的門只能從湖那一側打開。去程時它保持關閉,路徑蜿蜒;回程時它打開,小巷是直的。這就是經典作品的回程單向捷徑(第 5 節,回家的路),只是攤平了。我在第 3 項的共用八方向圖上建立了這三個區域的模型,城鎮依工坊今天的畫法,小巷與棧橋也依上述方式放置。我選擇用四道橫越草原的樹籬讓它蜿蜒,每 12 列一道,各留一個 2 個圖塊寬的缺口。斜走會把每一個之字彎截短。在所有缺口位置中,兩道等距的樹籬在這張圖上無法讓草原蜿蜒到 78 個圖塊:從一端到另一端最多只到 66.46 個圖塊。三道等距的樹籬可以,但只是勉強:它們的 8,000 種缺口位置中有 6 種讓它蜿蜒 78 到 79.77 個圖塊,而這 6 種中有 5 種的去程也落在 110 到 120 個圖塊的區間內,為 114.05 到 119.02,回家的路是去程的 0.740 到 0.772,低於 0.8 的目標。四道樹籬在 160,000 種位置中有 5,912 種讓它蜿蜒 78 到 90 個圖塊,給工坊留下了佈置草原的空間。這個餘裕是我選擇四道的理由;這張圖並沒有強迫這麼做。在這些四道樹籬的佈局上,回家的路是 88.07 個圖塊,去程則是 108.74 到 133.75。0.8 的目標需要去程至少 110.09,而在 110 到 120 個圖塊的去程區間內,全部 4,111 種佈局都達標,比值為 0.73 到 0.80。如果讓門雙向都能通行,兩段步行就會相等。66 八方向步行正是先前模型的兩道樹籬不再足夠的原因:它只把回家的小巷從 91 個圖塊縮短到 88.07,因為小巷本來就是直的,卻從蜿蜒的去程路徑上削掉了多得多的長度,所以除非草原蜿蜒得更厲害,否則這道門能省下的就比較少。同一個模型也定出了區間的下限。在它嘗試過的任何偏移量或棧橋位置中,沒有一種佈局的去程短於 107.18 個圖塊,而回家的路從不短於 88.07,所以去程若遠低於 110 個圖塊,就達不到 0.8 的目標。66 草原平台上的單向落差也能以經典作品的方式達成同樣效果,但建築那一篇拒絕了矮崖,所以落差延後交給高度篇決定。3626
檢查: 3(距離)。
3. 以資料表示的連接
每個區域都是像 town.json 那樣的工坊匯出檔:字母、自動拼接的地面、物件、傳送點與抵達點,再加上 connections: [{"dir": "north"|"south"|"east"|"west", "area": "<id>", "offset": <tiles>}],其中偏移量是鄰圖原點沿共用邊緣的位置,也就是第三世代的慣例。148 每一條連接都有一條偏移量取負號的反向連接(第 5 節,連接),沒有例外:經典作品的單向連接是一種這份規格書用不到的繪圖技巧。伺服器上每個區域的副本都帶著字母與連接,讓一次步行在跨越接縫時得到驗證。6026
一張步行圖同時服務兩端。App 的 TileMap.path 以八個方向搜尋,一步斜走成本為 √2,而且只有在它所經過的兩個圖塊都可行走時才允許,所以沒有人能切過牆角;伺服器在 server/app/rooms.py 中的 Plaza.path 則以四個方向搜尋,雖然它的註解稱之為「the same walk the app takes」(與 App 相同的走法)。28 規格書讓用戶端的搜尋成為契約:伺服器端的修改把 Plaza.path 換成同樣的八方向、√2、不切牆角的搜尋,而兩端都把樹籬門(第 2 項)讀為一條有向邊,只能從湖那一側以直走一步進入,永遠不能朝湖的方向離開。這份規格書中的每一個距離都是在這張圖上量測的。
檢查: 1(對應關係)、2(跨越點)與 12(伺服器一致性)。
4. 開放接縫,沒有淡出
跨越開放接縫時沒有淡出(第 5 節,轉場)。用戶端把鄰圖的地面與物件畫到至少半個視野深:東西向的接縫畫 15 個圖塊,也就是展開的 Duo 的 29.7 的一半;南北向的接縫畫 12 個圖塊,也就是直向 Pro Max 的 22.4 的一半(第 5 節,鄰圖帶狀區)。6017 工坊跨越每一道接縫計算自動拼接的遮罩,讓一條穿過接縫的路不會收成一圈邊框,並在每一道接縫處於城鎮的圍籬上切出一個至少 2 個圖塊寬的缺口,缺口的格子在兩側都可行走。1426 工坊今天在城鎮周圍畫出的 12 個圖塊深的樹林(MARGIN = 12),沿著整條共用邊緣讓位給鄰圖:鄰圖的帶狀區在那裡取代樹林邊距,樹林只保留在沒有區域相接的地方。這修訂了研究筆記,筆記原本也在樹林邊距中切出 2 個圖塊的缺口,那會讓樹林遮住鄰圖帶狀區的大部分。1426 用戶端在開始繪製鄰近房間的帶狀區時就訂閱該房間的步行者,並在跨越時切換自己的房間。26
檢查: 6(沒有黑畫面、沒有主執行緒停頓,且在接縫前就看得到帶狀區)。
5. 地名卡
經由接縫或電車進入一個區域時,它的名稱會滑入、停留,再於約 3 秒內離開(第 5 節,地名卡),以 UI 比例在 SwiftUI 中呈現,絕不放在像素層。2160
檢查: 7(地名卡時間)。
6. 草原步道上的拾獲物
- 在哪裡。 花叢覆蓋草原步道可行走圖塊的 12% 到 18%,以它們自己的一套自動拼接圖塊繪製,讓它們從遠處就能辨認。26 這個比例是我的選擇,不是經典作品的數字。以同樣的方式、也就是以可行走地面計算,經典作品的第一條道路有 18.8% 到 38.4% 是高草叢,之後的道路是 8.2% 到 29.2%(第 1 節),所以花叢落在之後的道路那一帶,低於每一條第一條道路:一道閃光是被放置且看得見的,所以花叢只需要框住它,而不必承擔每一步的擲骰。17
- 它們如何出現。 不是靠每一步擲骰。進入草原步道時,伺服器會為那位收藏者在一片花叢中最多放置一道看得見的閃光,依收藏者、日期與區域以確定性的方式選出,也就是《動物森友會》的區塊進入規則,直到那位收藏者當天已經拿過三道為止。5526 一道看得見的閃光讓規則對孩子來說也一目了然,並且不需要第 5 節節奏一列中的冷卻加漸增,就能避免連續出現或連續落空。
- 它們是什麼。 只限世界裡的東西:房間用的貼紙或相框、卡套或別針裝飾品,或是一張寫出當天某項任務的紙條,而那項任務只有真實的掃描才能完成;任務本來就是從收藏本身判定的(
Kiradex/World/Quests.swift)。2765 絕不是卡片,絕不是 XP,絕不是任何販售品;一件拾獲物永遠不能成為購買的理由。金幣先等等:世界計畫允許透過每日造訪賺取金幣,但沒有訂出數量或上限,並把金幣放在它的第四階段,所以金幣拾獲物延後到那條規則存在之後,屆時也會從同一份每日造訪的額度中扣除,而不是額外增加。27 - 誰看得到。 每位收藏者的閃光是他自己的;一起散步的朋友各自看到自己的。沒有東西可搶,也沒有東西可爭。
檢查: 10(拾獲物)。
7. 拱門與它的看守人
中央廣場與藝廊區之間的建築是一個 8 乘 6 的房間,兩側各有一扇門,櫃台後有一位看守人。當收藏者已登錄的卡片數第一次超過 10、30、50 與 100 時,看守人會送出一件裝飾品,這就是第 5 節關口一列中的收藏里程碑關口,並且像 XP 一樣從現實世界的收藏計算。2026 這個房間也是美術從草地換成石材的地方(第 5 節,接縫)。沒有任何東西被鎖住:拱門永遠敞開,看守人只負責給予。
檢查: 11(拱門看守人)。
8. 地圖畫面
一張 16 乘 12 的具名矩形方格,每格代表 10 個圖塊,四捨五入:中央廣場 4 乘 3,卡冊街 4 乘 1 在中央廣場東側,草原步道 2 乘 6 在它上方,湖畔 4 乘 2 在最上方,藝廊區 3 乘 2 在西側。湖畔、草原步道與中央廣場疊起來,佔了 12 列中的 11 列(第 5 節,地圖畫面)。2617 每一個區域從第一天起就畫出來並標上名稱。這是 Kiradex 自己的選擇,不是經典作品的做法:那四款遊戲在玩家拿到地圖之後才畫出每一個地方(只保留少數後期地點),但地圖是在遊戲進行中才交出的(第 5 節,快速移動),而一座收藏城鎮沒有理由讓任何人去掙得看見它的權利。目前的區域會閃動;去過的區域顯示它們的電車站。在展開的 Duo 上,地圖以面板的形式放在世界旁邊;在一般手機上則是一張工作表。27
檢查: 8(地圖畫面模型)。
9. 電車
每一個戶外區域都有一個車站。收藏者第一次進入某個區域時,那一站就會開放,這就是第 5 節快速移動一列中的「去過」旗標,在進入時設下,絕不靠等級或付費。1326 在地圖畫面上點一下去過的區域,就會搭車前往:淡出、換房間、在該區域的車站淡入,接著是地名卡。室內不能搭車,就像「飛翔」不允許從室內出發一樣(第 5 節,快速移動)。13 走過一次之後,電車就把到湖畔 28 到 30 秒的步行變成一次點按,這是一條靠造訪而不是靠能力換得的捷徑(第 5 節,解鎖)。26
檢查: 9(電車規則)。
10. 名稱與美術
每一個區域的名稱、招牌與字串都要通過一份受保護名稱的封鎖清單,每一個圖塊與 sprite 都來自工坊。2726 這份封鎖清單還不存在:研究筆記承諾會在儲存庫中放一份,而儲存庫裡沒有。製作它是這份規格書的一部分:一份單純的受保護名稱清單,每行一個,提交到儲存庫中、放在工坊旁邊,再加一個測試,從工坊的匯出檔讀出每個區域的名稱、招牌與字串,只要在其中找到清單上的任何一項(不分大小寫)就判定失敗。26 不用編號的道路名稱,不用「地區」式的命名方式,也不蓋任何重現經典關口外觀的建築:尺寸與規則可以相同,但形狀與名稱必須是 Kiradex 自己的。26
檢查: 13(名稱與美術)。
11. 工作順序
世界計畫把廣場(房間、同步、對話泡泡、個人資料、交換畫面)排在第三,金幣與商店排在第四。27 這些區域適合作為第 3b 階段,排在廣場的網路功能穩定之後,與金幣同時或在它之前:26
- 工坊與伺服器中的連接(格式、對應關係、接縫繪製、房間切換)、伺服器的八方向搜尋,以及封鎖清單與它的測試,並以卡冊街作為第一個區域,因為它使用建築那一篇已經設計好的卡牌店,而且是最小的。
- 草原步道與拾獲物(伺服器端放置、每日上限、內容物)。
- 地圖畫面與電車。
- 湖畔。
- 藝廊區與拱門,等博物館的室內存在之後。卡冊街的攤位排則等到市集階段。
每一步都是一個 TestFlight build,正如世界計畫對每一個階段的要求,並附上各項檢查的擷取畫面。27
驗收檢查
每一項都可以由一支腳本在工坊的匯出檔與伺服器的測試上執行,或由模擬器上的一次擷取來執行;檢查 6 在裝置上執行,只有其中一次擷取在模擬器上。26
- 對應關係。 匯出檔恰好包含三道開放接縫:中央廣場東側接卡冊街、中央廣場北側接草原步道,以及草原步道北側接湖畔;而且對每一條連接
(a, dir, b, off),匯出檔都包含(b, opposite(dir), a, −off);只要有任何一條單向連接,測試就失敗,沒有例外。 - 跨越點。 從每個區域的抵達點與各扇門出發的廣度優先搜尋,在每一道接縫的兩側都找到至少 2 個跨越圖塊,面對接縫另一側的可行走圖塊,也就是
measure_gen3_routes.py的方法。6 - 距離。 第 3 項的共用八方向搜尋,在相接的各區域上執行,把樹籬門當作一條單向邊(只能從湖那一側以直走一步進入,永遠不能朝湖的方向離開),每一個長度都是路徑成本、一步斜走算 √2,回報的結果是:抵達點到卡牌店最多 60 個圖塊;抵達點到湖畔棧橋 110 到 120;棧橋到抵達點最多是去程步行的 0.8 倍;抵達點到博物館的門最多 70。工坊以每秒 4 個圖塊印出時間。
- 比例。 中央廣場之外的圖塊數除以中央廣場的圖塊數,落在 2.0 到 3.9 之間。
- 方向。 草原步道的高度大於它的寬度。
- 接縫處沒有黑畫面,也沒有主執行緒停頓。 在裝置版本上,收藏者走過中央廣場與卡冊街之間的接縫,從距離接縫超過半個視野的一個圖塊出發,也就是在舞台開始建立卡冊街的網格之前,一直到跨過接縫的第一步結束、中央廣場已經成為身後的帶狀區為止。兩份紀錄涵蓋這整段期間。在這次執行中,display link 透過它的
preferredFrameRateRange要求 60 赫茲,讓 ProMotion 螢幕變動的更新率不會在走到一半時改變計數;Apple 把這個範圍描述為 display link 會盡力遵守的範圍,所以真正維持住的更新率要看時序紀錄,而不是看要求。67 第一份紀錄是螢幕顯示的每一個影格,以該更新率錄影保存,並逐一檢查每個影格:當一個影格的平均亮度低於這段期間中位數影格的四分之一時,就判定為黑畫面(這是我訂的門檻),而期間內任何一個黑畫面都會讓檢查失敗。錄影在這段期間的影格數,必須等於時序紀錄在同一段期間的回呼數,這樣一個會掉影格的擷取方法就不能靠漏掉那個黑畫面而過關。目前還沒有任何擷取方法被證實能記錄每一個顯示出來的影格,而這項比對正是決定某個方法是否做得到的依據。當數量不同、而時序紀錄通過它自己在下文的規則時,問題出在擷取,這次執行不算數;當數量不同、而時序紀錄也沒通過那條規則時,檢查就失敗。第二份紀錄是註冊在主執行迴圈上的CADisplayLink回呼的targetTimestamp,註冊方式與 Apple 自己的範例相同;依 Apple 文件的說法,那是下一個影格顯示的時間,每個影格都記錄下來;相鄰兩個值之間的間隔都不得超過 25 毫秒,也就是 60 赫茲下的一個半影格,所以掉一個影格就會讓檢查失敗,與一個黑畫面相同,這樣既能抓出建立鄰圖時的主執行緒停頓,也能抓出跨越時的停頓。時序紀錄若穩定在 60 赫茲以外的任何更新率,這次執行就不算數,而且執行時要關閉低耗電模式,因為 Apple 把這個模式、嚴重的熱狀態以及輔助使用設定列為可能改變更新率的因素。67 裝置在手上有展開的 iPhone Duo 之後就用它;在我寫作時沒有任何 iPhone Duo 與我的 Mac 配對,所以在那之前,這項檢查在 iPhone 18 Pro Max 上、跨過同一道接縫執行,這能看出接近與跨越過程是否會在一支手機上造成停頓或黑屏,但看不出 Duo 的內螢幕如何安排它的影格節奏。模擬器無法替代:依我的解讀,它的影格時序量到的是執行它的那台 Mac,而不是手機。另外,在展開的 Duo 模擬器上從測試套件內部擷取一次,讓收藏者站在中央廣場接縫前的最後一個圖塊上,畫面中要看得到卡冊街至少 14 個圖塊。6064 - 地名卡。 在跨越接縫或電車抵達之後,地名卡在畫面上停留 2.5 到 3.5 秒。
- 地圖畫面模型。 單元測試:每一個區域的矩形都在 16 乘 12 之內,任兩者不重疊,每個區域都有名稱,目前的區域有標示,而未去過的區域的車站是停用的。
- 電車規則。 伺服器測試:前往未去過的區域的搭乘會被拒絕;從室內出發的搭乘會被拒絕;搭乘結束在目的地的車站。
- 拾獲物。 伺服器測試:每次進入區域最多一道閃光,每位收藏者每天最多三道,同一位收藏者、同一天、同一個區域得到同一道閃光,而內容物只從裝飾品與任務紙條的表中抽取,絕不是卡片、XP 或金幣。一項工坊測試:花叢覆蓋草原步道可行走圖塊的 12% 到 18%。
- 拱門看守人。 一項單元測試:四份里程碑禮物各觸發一次,在已登錄數量為 10、30、50 與 100 時觸發,而且絕不由金幣或購買觸發。
- 伺服器一致性。 伺服器載入每個區域的字母與連接,並以第 3 項的八方向搜尋,讓一位收藏者從抵達點走到湖畔棧橋、跨過兩道接縫,再經由樹籬門走回來;App 與伺服器在兩個方向都回傳相同的圖塊與相同的成本,而用戶端的搜尋會拒絕的一步,例如切過牆角,或從小巷穿過門走出去的一步,伺服器也會拒絕,這是沿用現有伺服器測試的模式。3728
- 名稱與美術。 封鎖清單與它的測試存在於儲存庫中,而且測試通過:每一個區域名稱、招牌與字串都通過清單;每一個圖塊與 sprite 都來自工坊。
不在這份規格書內
- 每一步隨機遭遇,或任何玩起來像野生對戰的東西。拾獲物是被放置且看得見的。
- 核心區域的能力關卡。 沒有任何區域靠等級、別針、購買或工具開放;區域靠走到那裡而開放,只有捷徑,也就是電車,是靠造訪換得的。這是用友善的方式來讀第 5 節的解鎖一列。5226
- 兩個人們聚集的地方之間超過約 30 秒的路程,卻沒有電車。經典作品長達一分鐘的路程是迷宮。7
- 矮崖、帶單向落差的平台,以及任何逐格的高度。 延後交給高度篇決定;在那之前,規格書用樹籬門取得較短的回家路,它是單向的,卻是平的。36
- 編號的道路名稱、「地區」式的命名方式、需要徽章的快速移動,或是抄襲經典關口外觀的建築。27
- 單向連接。 經典作品把它們當作繪圖技巧;這份規格書一條也沒有,而且只要有任何一條,檢查 1 就失敗。8
- 金幣拾獲物,直到世界計畫訂出一次每日造訪能賺多少金幣為止。27
- 在市集階段之前,卡冊街上的市集攤位。27
重點整理
如果您負責畫美術
- 只在地面延續的地方畫接縫。《紅》的 78 條連接中有 76 條只接同一組圖塊集,而《紅》每一座連接兩個地方的關口,都正是圖塊集改變之處;一棟有兩扇門的建築,是更換材質的誠實做法。8
- 讓第一條道路短,而且草最多:兩到六個畫面,地圖的 14% 到 23% 是遭遇地面,沒有訓練家。隨著旅程推進再讓它變稀。56
- 把地圖畫面畫成由具名矩形組成的粗略方格。《綠寶石》的是 28 乘 15 格,95 個矩形中有 49 個只有一格。11
如果您負責建引擎
- 把連接存成資料,包含方向、鄰圖與偏移量,並測試每一條都有一條偏移量取負號的反向連接;《綠寶石》的 134 條全都有。842
- 在邊緣之外至少畫出半個視野的鄰圖。第三世代複製 7 格(東側 8 格),因為玩家兩側各看得到 7 格;在 Kiradex 的比例下,這在展開的 Duo 上是 15 個圖塊,在直向的 Pro Max 上是 12 個。960
- 跨越接縫時保持同一個場景,把場景切換留給門與快速移動,在那裡有淡出可以掩飾。1037
- 用搜尋而不是用尺來量距離,並納入引擎所有的移動規則:在兩端可步行相通的九條量測道路上,較長的穿越步行是長邊的 1.15 到 1.70 倍,回家的路是 0.95 到 1.42 倍,而回程可以比去程短上四分之一。不考慮《水晶》單向牆與角落橫跳的搜尋,會把32號道路算成雙向各 110 格;考慮之後是去程 132、回程 128。5617
- 讓 App 與伺服器共用一張步行圖。如果用戶端走八個方向、伺服器走四個,那麼測試量到的每一個距離都只屬於其中一方。28
如果您負責設計循環
- 每一趟旅程都以秒數決定大小。在經典作品中,第一段路程是 15 到 25 秒的步行;一分鐘就是迷宮。7
- 讓回家的路比出門的路短,並給它某種單向的東西來做到這一點,因為在一張可以雙向行走的方格上,走回來的最短路線就是走出去的最短路線。經典作品用矮崖做到,最多短 44%;一道只能從遠端打開的門,不需要高度也能做到。5666
- 玩家一旦有了地圖,就在上面顯示每一個地方,只讓捷徑靠造訪來換得。這裡量測的每一款寶可夢遊戲都在遊戲進行中交出地圖,在地圖上畫出每一個地方,不論去過與否,只保留少數被劇情或活動旗標擋住的後期地點,在進入時設下「去過」旗標,並且只把去過的地方列為快速移動的目的地;Kiradex 更進一步,從第一天起就顯示它的地圖。4445121323222426
- 像《星露谷物語》那樣依行動與日期開放區域,而不是靠等級或購買,並讓步行者無論走到哪裡,都能拿他們的收藏做點什麼。5227
常見問題
俯視角 RPG 的一條道路應該多長?
在 2D 的寶可夢遊戲中,沿行進軸向是兩到十個主機畫面,第一條道路是兩到六個。以格數計,我量測的十六條道路長邊從 20 到 108 不等;在可以從一端走到另一端的九條上,因為路徑會繞,較長方向的最短穿越步行是那個長度的 1.15 到 1.70 倍,回家的路是 0.95 到 1.42 倍。以每格 16 個影格計,從一扇門到下一座城鎮的主要建築,第一段路程在《綠寶石》要 15.5 秒,在《火紅》要 25.2 秒。56717
寶可夢遊戲如何在沒有載入畫面的情況下連接地圖?
每張地圖都列出它的鄰圖,附上方向與偏移量。在《綠寶石》中,引擎把目前的地圖複製進一個帶有 7 格邊距(東側 8 格)的緩衝區,「the player has 7 metatiles of view horizontally in either direction」(玩家水平方向兩側各看得到 7 個 metatile),並在有連接的一側,以鄰圖的 7 列或 7 欄(東側 8 欄)填滿邊距,用目前地圖的圖塊繪製。玩家跨過去時,它載入新地圖的次要圖塊集、調色盤、音樂與天氣,並滑入一個地名提示,沒有淡出。《紅》用 3 個 block、也就是 6 格的邊距做同樣的事。91039
關都的道路為什麼有關口?
依我對資料的解讀,是因為 Game Boy 的接縫只能連接以同一組圖塊集繪製的兩張地圖,而關口就是圖塊集改變之處的一扇門:《紅》的 78 條連接中有 76 條只接同一組圖塊集,而《紅》每一座連接兩個地方的關口都接兩組。關口也承載規則:想要一杯飲料的金黃市守衛、22號道路上的徽章檢查,以及在擁有 10、30、50 種寶可夢時送出道具的三位助手。沒有任何開發者說法證實圖塊集這個理由。8192029
寶可夢的矮崖為什麼是單向的?
一個矮崖格的行為指定了一個行進方向,玩家只能朝那個方向跨越它。在早期道路上量測,矮崖都朝向較早的那座城鎮:在兩端可步行相通、都有矮崖的九條道路上,回家的步行比出門短 2% 到 44%。在為此搜尋過的四條道路上,回家的路要穿過 0 到 5 格高草叢,出門則是 4 到 19 格。365618
「飛翔」如何決定您能飛到哪些城鎮?
靠您進入城鎮時設下的「去過」旗標。在《綠寶石》中,16 座城鎮與城市各自在轉場腳本中設下自己的 FLAG_VISITED_* 旗標,而地區地圖只在旗標設下時才把一座城鎮標為「飛翔」的目的地;《紅》在 wTownVisitedFlag 中為每座城市保留一個位元,《水晶》則在地圖回呼中設下飛行點。「飛翔」還需要一個徽章(《紅》與《火紅》是第三個,《綠寶石》是第六個)以及一張戶外地圖。13222423
一座城鎮和它的道路相比有多大?
小兩到四倍。道路格數除以城鎮格數,在《水晶》是 1.97,《紅》是 2.65,《綠寶石》的陸上道路是 3.70,《火紅》是 3.87;在《綠寶石》的地圖畫面上,這個比值讀起來是 5.9 比 1,因為一座城鎮不論多大都只佔一或兩格。3334
模仿寶可夢打造道路的方式合法嗎?
機制、尺寸與方法不受著作權保護。具體的圖塊與 sprite 受著作權保護,名稱與標誌則受商標保護。本系列的第一篇談過這條界線,並引用了美國著作權局的 Circular 33(它把系統與操作方法排除在著作權之外),以及任天堂控告《幻獸帕魯》(Palworld)開發商一案中的專利。60 這份規格書借用的是數字與規則,每一個圖塊都由 Kiradex 自己的工坊繪製,名稱也都對照封鎖清單檢查過。26
本文的製作方式。 背後的研究筆記於 2026年10月4日彙整完成,來源是 pret 反編譯專案的淺層複製、由六支 Python 腳本量測並連同輸出一起保存,以及 Stardew Valley Wiki、Nookipedia、zladx 反組譯專案、Porymap 手冊、GBATEK、Pan Docs、一頁 Bulbapedia 與 Apple 頁面的存檔副本。正文中的每一個寶可夢數字都是某支腳本的輸出,每個註腳都寫明是哪一支。發表前,10 月 5 日的第二次審查帶來三項重新量測,《水晶》的單向牆與角落橫跳、跑步越過矮崖,以及規格書中的草原改用 App 的八方向步行,並促成對遭遇程式碼寬限步數更仔細的解讀;每個註腳都說明了哪些數字有所變動。第 7 節的提案是我的,並已如此標示;其中沒有任何一項已經建置。
本站相關文章:iPhone 上的像素藝術世界是本系列第一篇,包含本文的視野所依據的圖塊系統、RealityKit 配方與各裝置圖塊數;像素藝術人物:iPhone 上的角色與一套創角器是走在這些道路上的收藏者;像素藝術建築:iPhone 上的房屋、大廳與室內有電車與拱門所沿用的門、傳送點與樓層切換,以及本文留給高度篇處理的「拒絕矮崖」;開發者眼中的 iPhone Duo 推導出計算半個視野帶狀區所依據的內螢幕尺寸;為 iPhone Duo 準備您的 App 涵蓋地圖面板所遵循的並排版面;而 Xcode 27.1 beta:在 iPhone Duo 模擬器上跑您的 App 則涵蓋規格書的接縫檢查所使用的模擬器。
參考資料
-
pret,
pokered/home/overworld.asm,commitd2704a6(OverworldLoop每次迭代呼叫兩次DelayFrame;AdvancePlayerSprite每次迭代移動 2 像素、共 8 次迭代,wWalkCounter為 8;騎自行車時每次迭代呼叫兩次AdvancePlayerSprite),2026年10月4日讀取,https://github.com/pret/pokered/blob/master/home/overworld.asm。 ↩↩↩↩ -
pret,
pokecrystal/engine/overworld/map_objects.asm(StepVectors:走路一步 8 個 tick、每個 2 像素,騎車一步 4 個 tick、每個 4 像素)與pokecrystal/engine/overworld/events.asm(MaxOverworldDelay: db 2),commit5beda23,2026年10月4日讀取,https://github.com/pret/pokecrystal/blob/master/engine/overworld/map_objects.asm。 ↩↩↩↩ -
pret,
pokeemerald/src/event_object_movement.c,commit731ad5b(步進表:走路的sStep1Funcs有 16 項,跑步與衝浪 8 項,越野自行車與水流 6 項,音速自行車 4 項,最快的 2 項;JUMP_DISTANCE_FAR以 32 個影格跨兩格;sJumpY_High最高 12 像素),2026年10月4日讀取,https://github.com/pret/pokeemerald/blob/master/src/event_object_movement.c。 ↩↩↩↩↩ -
Kiradex 原始碼,
Kiradex/World/PlazaRig.swift,第 101 行(tilesPerSecond: Float = 4)與第 122 行(runPace: Float = 1.6),2026年10月4日從工作樹讀取;以及第 660 與 666 行(每個影格把dt × tilesPerSecond × pace ÷ length加到一步的進度上,一步完成時把進度歸零,捨棄超出的部分),2026年10月5日讀取;私有儲存庫。每秒 6.4 個圖塊是名目上的跑步速度,4 × 1.6。 ↩↩↩↩↩ -
作者的量測,2026年10月4日:作者為本文準備的證據資料夾中的
measure_gen12_routes.py,輸出gen12_routes.txt。來自 pretpokered(d2704a6)的輸入:constants/map_constants.asm(以 block 計的尺寸,加倍為格)、maps/Route*.blk、gfx/blocksets/overworld.bst對照Overworld_Coll、草叢圖塊$52與data/tilesets/ledge_tiles.asm(每格一個 8 乘 8 圖塊,取左下角)、data/maps/objects/Route*.asm(訓練家為帶有OPP_類別的object_event行)、data/maps/headers/Route*.asm(連接)。來自 pretpokecrystal(5beda23):maps/Route*.blk、經由CollisionPermissionTable的data/tilesets/<tileset>_collision.asm(每個 block 的四個位元組假設為列優先)、maps/Route*.asm(訓練家為OBJECTTYPE_TRAINER)。2026年10月5日擴充(先前的輸出保存為gen12_routes.pre-walls.txt):《水晶》的側牆,也就是碰撞位元組COLL_RIGHT_WALL到COLL_UP_LEFT_WALL($b0到$b7)以及側面浮標,會拒絕從牆面一側走出的一步與從那一側走進的一步,依GetMovementPermissions的設定(home/map.asm,自第 1511 行起)與.CheckLandPerms的讀取(engine/overworld/player_movement.asm,第 686 行);角落矮崖HOP_DOWN_RIGHT與HOP_DOWN_LEFT往下或往側面跳(.ledge_table,第 383 行),而且只有在一般的一步失敗後才嘗試跳(.Normal,第 45 到 56 行);最短路徑以 Dijkstra 搜尋求得,因為一次跳躍成本為 2。只有32號道路有側牆(41 格,全是UP_WALL,其中包括data/tilesets/johto_collision.asm第 113 行的 block),也只有它的步行有所變動:之前雙向各 110,之後往南 132、往北 128。probe_gen12_exact.py(輸出probe_gen12_exact.txt)跑遍三項變更的所有組合:單有牆時雙向各 132,加上雙向的角落跳之後回家路縮短為 128,而精確搜尋沒有改變任何結果;probe_route32_path.py追蹤這一跳,從格 (8, 80) 的HOP_DOWN_RIGHT崖緣越過一個WALL格到 (10, 80)。pokered 沒有逐圖塊的單向通行設定;它的TilePairCollisionsLand(data/tilesets/pair_collision_tile_ids.asm)雙向阻擋一對圖塊,而且只列出 CAVERN 與 FOREST 圖塊集,而這裡量測的每一張地圖都是 OVERWORLD。第二世代的崖緣解讀來自probe_gen2_ledges.py(30號道路上 21 個HOP_DOWN格中,21 個下方都有一個WALL格)。第一世代的格模型以肉眼在render_gen1.py對《紅》1號道路的算繪圖上檢查,兩倍大小並疊上 16 像素格線(render_Route1.png,僅供分析,源自受著作權保護的美術,不公開)。另見註腳 33 背後的道路與城鎮面積。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
作者的量測,2026年10月4日:作者為本文準備的證據資料夾中的
measure_gen3_routes.py,輸出gen3_routes.txt與gen3_routes.json。來自 pretpokeemerald(731ad5b)與pokefirered(037335f)的輸入:data/layouts/layouts.json(尺寸)、每條道路map.bin的每一格(位元 0 到 9 為 metatile,10 與 11 為碰撞,12 到 15 為高度)、它在主要或次要metatile_attributes.bin中的行為(《綠寶石》為 u16,行為在位元 0 到 7;《火紅》為 u32,行為在位元 0 到 8,遭遇類型在位元 24 到 26)、MB_TALL_GRASS、MB_JUMP_*與水的行為計數,以及data/maps/Route*/map.json(連接;訓練家為trainer_type不是TRAINER_TYPE_NONE的object_events;trainer_sight_or_berry_tree_id)。最短步行以廣度優先搜尋求得。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
作者的量測,2026年10月4日:作者為本文準備的證據資料夾中的
measure_travel.py,輸出travel.txt與travel.json,對象為 pretpokeemerald(731ad5b)與pokefirered(037335f)。以(地圖,格)為節點的廣度優先搜尋,穿過接縫、傳送點與矮崖,從第一扇門下方那一格到最後一扇門,計數包含踏進門的最後一步(第 136 行,「+1: the step up into the door」);步行秒數為格數 × 16 個影格 ÷ 59.7275 赫茲。跑步於 2026年10月5日擴充(先前把每個跑步格都算作 8 個影格的輸出保存為travel.pre-ledgerun.txt),是第二次搜尋,求最少影格數,每步 8 個、每次跳下矮崖 32 個,因為《火紅》的跳躍是JUMP_DISTANCE_FAR的Jump2,不論步態都是 32 個影格(src/field_player_avatar.cPlayerJumpLedge,第 905 行;src/event_object_movement.cDoJumpSpriteMovement,第 9094 行);4號道路寶可夢中心到華藍市那一段是 2,528 個影格、42.3 秒,跳一次,表中其他去程路程都沒有跳矮崖;居合斬樹、碎岩岩石與怪力巨石會擋住它們所在的格,除非該次執行另有說明(「居合斬後」那次執行會移除它們,在2號道路上這會打通ROUTE2_EAST_BUILDING)。淡出與戰鬥不計入。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
作者的量測,2026年10月4日,並於 2026年10月5日擴充為依
FillEastConnection的複製方式在東側讀取 8 欄,並依FillNorthConnection、FillSouthConnection、FillWestConnection與FillEastConnection的截斷方式,截掉帶狀區超出緩衝區的部分;只用主要圖塊的計數因此從 38 中 22(《綠寶石》)與 34 中 25(《火紅》)變為 38 中 24 與 34 中 24:作者為本文準備的證據資料夾中的measure_connections.py,輸出connections.txt與connections.json,涵蓋四個反編譯專案:每一條地圖連接、每道接縫兩側的圖塊集是否一致(第三世代包括主要與次要圖塊集,以及邊距中鄰圖的帶狀區,即 7 列或 7 欄、東側 8 欄,是否只使用主要 metatile)、偏移量取負號的對應關係、非零偏移量、《紅》中使用 GATE 與 FOREST_GATE 圖塊集的地圖(data/maps/headers/*.asm)、《水晶》中環境為 GATE 的地圖(data/maps/maps.asm),以及第三世代中傳送點通往兩張不同戶外地圖、或通往同一張地圖兩側的室內地圖。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
pret,
pokeemerald/include/fieldmap.h(MAP_OFFSET 7與它的註解「the player has 7 metatiles of view horizontally in either direction」;MAX_MAP_DATA_SIZE 10240;MAP_OFFSET_W為MAP_OFFSET * 2 + 1,MAP_OFFSET_H為MAP_OFFSET * 2)、pokeemerald/src/fieldmap.c(InitMapLayoutData把緩衝區設為地圖加上MAP_OFFSET_W乘MAP_OFFSET_H;FillSouthConnection與同類函式,其中FillNorthConnection在帶狀區放得下時設定x = offset + MAP_OFFSET與width = cWidth,高MAP_OFFSET列,而FillEastConnection複製MAP_OFFSET + 1欄;GetBorderBlockAt把邊界與MAPGRID_IMPASSABLE做 OR 運算)、data/maps/Route101/map.json(古辰鎮在北側,偏移量 0)與data/layouts/layouts.json(LAYOUT_ROUTE101與LAYOUT_OLDALE_TOWN,各為 20 乘 20),commit731ad5b,2026年10月4日讀取,https://github.com/pret/pokeemerald/blob/master/include/fieldmap.h 與 https://github.com/pret/pokeemerald/blob/master/src/fieldmap.c。 ↩↩↩↩↩↩↩ -
pret,
pokeemerald/src/field_camera.c(DrawMetatileAt以目前版面的主要與次要圖塊集繪製)與pokeemerald/src/overworld.c(LoadMapFromCameraTransition:轉場腳本、次要圖塊集與調色盤、音樂、天氣與地名提示,沒有淡出),commit731ad5b,2026年10月4日讀取,https://github.com/pret/pokeemerald/blob/master/src/overworld.c。 ↩↩↩↩↩↩↩↩↩↩ -
pret,
pokeemerald/src/region_map.c(MAP_WIDTH 28、MAP_HEIGHT 15)、src/data/region_map/region_map_layout.h與src/data/region_map/region_map_sections.json,commit731ad5b,2026年10月4日讀取,計數由measure_region_maps.py完成(輸出region_maps.txt),https://github.com/pret/pokeemerald/blob/master/src/data/region_map/region_map_layout.h。 ↩↩↩↩↩↩↩ -
pret,
pokeemerald/src/field_control_avatar.c(行為為MB_REGION_MAP的 metatile 會執行EventScript_RegionMap)、data/event_scripts.s(EventScript_RegionMap、special FieldShowRegionMap)、data/maps/RustboroCity_DevonCorp_3F/scripts.inc(setflag FLAG_SYS_POKENAV_GET)、src/start_menu.c(只有在FLAG_SYS_POKENAV_GET設下時才出現寶可導航的選單項目)與src/region_map.c(對戰開拓區與南方孤島在它們的FLAG_LANDMARK_*旗標設下之前為MAPSECTYPE_NONE,第 1213 到 1216 行),commit731ad5b,2026年10月4日讀取,壁掛地圖的版面由作者為本文準備的證據資料夾中的scan_wall_maps.py找出(輸出wall_maps.txt:未白鎮兩棟房子的樓上,以及每一間寶可夢中心的一樓),https://github.com/pret/pokeemerald/blob/master/data/event_scripts.s。 ↩↩↩↩↩↩↩↩ -
pret,
pokeemerald/src/party_menu.c(FIELD_MOVE_FLY對應FLAG_BADGE06_GET)、src/overworld.c(Overworld_MapTypeAllowsTeleportAndFly)、src/region_map.c(城鎮的FLAG_VISITED_*設下時為MAPSECTYPE_CITY_CANFLY)與data/maps/OldaleTown/scripts.inc(setflag FLAG_VISITED_OLDALE_TOWN),commit731ad5b,2026年10月4日讀取,https://github.com/pret/pokeemerald/blob/master/src/region_map.c。 ↩↩↩↩↩↩↩↩↩↩ -
Kiradex 原始碼,
scripts/forge/town.py,第 79 與 80 行(MARGIN = 12、WIDTH, HEIGHT = 40, 30)與它的BUILDINGS清單(七棟建築,門通往room:me、room:npc-*或hall,一個抵達點),2026年10月4日讀取;私有儲存庫。 ↩↩↩↩↩↩ -
Martin Korth,GBATEK,LCD 時序(「Total 228 lines, 16.743 ms, 280896 cycles - ca. 59.737 Hz」,共 228 行、16.743 毫秒、280,896 個週期,約 59.737 Hz),2026年10月4日存檔為
gbatek.txt,https://problemkaputt.de/gbatek.htm。 ↩↩↩ -
Blake Crosley,「Pixel-Art Motion: Walks, Cameras and Doors on iPhone」,blakecrosley.com,2026年10月5日,第 7 節(「Where the walk stands today」:Kiradex 步行程式碼的影格模型,其中跑步在 60 赫茲下每個圖塊 10 個影格、每秒 6.00 個圖塊,在 120 赫茲下 19 個影格、6.32,因為每個圖塊超出的部分都被捨棄;門的 220 毫秒等待,以及沒有淡出的樓層變更;以及規格書中每個 tick 走 1 像素、60 赫茲下每個圖塊 16 個 tick、每秒 3.75 個圖塊的步行,與蓋在門和樓層上的階梯式遮罩),https://blakecrosley.com/blog/pixel-art-motion-on-iphone。 ↩↩↩↩↩
-
作者依正文中的量測值所做的計算,10 月 5 日的數字由作者為本文準備的證據資料夾中的
arith_oct5.py印出(輸出arith_oct5.txt):每秒格數為 59.7275 ÷ 16 與 ÷ 8;Kiradex 每秒 4 個圖塊對 3.73(快 7.2%),以及它的跑步對 7.47,以 6.00 計(慢 19.6%)與以 6.4 計(慢 14.3%);每秒 4 個圖塊的時間對每秒 3.75 的時間為 4 ÷ 3.75,長十五分之一,以及斜走在 60 赫茲下的 23 個 tick 對 √2 ÷ 4 秒為 0.3833 ÷ 0.3536 = 1.084,約長十二分之一(由r6_arith.py印出);每格秒數為 16 ÷ 59.7275(0.27),每個 Kiradex 圖塊為 1 ÷ 4(0.25);路徑係數為最短步行除以道路長邊,分別就較長方向與回家路計算,對象是雙向都有接縫到接縫步行的九條道路(在 20 格的地圖上,從第一列直走到最後一列是 19 步,0.95),節省比例為(去程 − 回程)÷ 去程,從 2.38%(《紅》3號道路)到 44.12%(《綠寶石》101號道路);每條道路的高草叢佔可行走格比例,取自兩支道路腳本的草叢與可行走計數;《綠寶石》地圖上城鎮的方格數,取自region_map_sections.json中 16 個城鎮與城市區段的width與height(十個 1 乘 1,六個 1 乘 2 或 2 乘 1,共 22 格);Duo 內螢幕的圖塊數為 2853 ÷ 96 與 2007 ÷ 96,96 即 16 個 texel 乘以 6 像素;置中收藏者前方的圖塊數為沿行進軸向可見圖塊數的一半;100 毫秒的取樣為 60 赫茲下 100 × 60 ÷ 1,000 = 6 個影格中的一個;時序門檻為 60 赫茲下的兩個影格,33 毫秒;《動物森友會》的 5 乘 6 個區塊、每個 16 乘 16 格,即 80 乘 96;規格書各區域為 44 × 14 + 24 × 60 + 36 × 20 + 30 × 20 = 3,376 個圖塊,除以 40 × 30 = 1,200;半個視野為 10 月 3 日的圖塊數減半後無條件進位;地圖畫面的方格數為每個區域的圖塊數除以 10 後四捨五入。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
作者的量測,2026年10月4日:作者為本文準備的證據資料夾中的
measure_grass_paths.py,輸出grass_paths.txt:一個 0-1 廣度優先搜尋,進入高草叢格成本為 1,其他移動成本為 0,矮崖單向,使用兩支道路腳本的格模型與可從門抵達的跨越點;對象為《紅》1號道路、《火紅》1號道路、《綠寶石》101與102號道路。入口處的格子來自probe_grass_gateway.py(輸出grass_gateway.txt),它把《紅》1號道路往南的搜尋重跑一遍,每次把一個草叢格改成普通地面:只有入口處最後四列的 8 格、每列兩格,會降低最小值。 ↩↩↩↩↩↩ -
pret,
pokered/scripts/Route5Gate.asm(TEXT_ROUTE5GATE_GUARD_GEE_IM_THIRSTY、BIT_GAVE_SAFFRON_GUARDS_DRINK)與pokered/scripts/Route22Gate.asm(BIT_BOULDERBADGE),commitd2704a6,2026年10月4日讀取,https://github.com/pret/pokered/blob/master/scripts/Route5Gate.asm。 ↩↩↩↩↩ -
pret,
pokered/scripts/Route2Gate.asm、scripts/Route11Gate2F.asm、scripts/Route15Gate2F.asm(門檻 10、30 與 50)與engine/events/oaks_aide.asm(以CountSetBits計算wPokedexOwned),commitd2704a6,2026年10月4日讀取,https://github.com/pret/pokered/blob/master/engine/events/oaks_aide.asm。 ↩↩↩↩↩ -
pret,
pokeemerald/src/map_name_popup.c,commit731ad5b(POPUP_OFFSCREEN_Y 40、每影格 2 像素的滑動、tOnscreenTimer > 120),2026年10月4日讀取,https://github.com/pret/pokeemerald/blob/master/src/map_name_popup.c。190 個影格的總數是作者依門檻計算任務各狀態所得;每個狀態可能多或少一個影格。 ↩↩↩↩↩ -
pret,
pokered/data/maps/town_map_entries.asm(37 筆戶外項目,一筆未使用;60 筆室內)、engine/overworld/toggleable_objects.asm(wTownVisitedFlag,註解為「mark town as visited (for flying)」)、engine/items/town_map.asm(飛行清單跳過沒去過的城鎮)與engine/menus/start_sub_menus.asm(「飛翔」需要橙色徽章,位元 2,以及一張戶外地圖),commitd2704a6,2026年10月4日讀取,https://github.com/pret/pokered/blob/master/engine/items/town_map.asm。 ↩↩↩↩↩↩↩ -
pret,
pokefirered/src/data/region_map/region_map_layout_kanto.h與三份七之島版面(每頁 22 乘 15,分野外與迷宮兩層)、src/party_menu.c與include/constants/party_menu.h(「飛翔」為FLAG_BADGE01_GET + fieldMove,FIELD_MOVE_FLY為 2,即第三個徽章)、src/region_map.c(GetMapsecType只在地點的FLAG_WORLD_MAP_*旗標設下時回傳MAPSECTYPE_VISITED,自第 2957 行起;「飛翔」只接受去過的方格;七之島的頁面在FLAG_SYS_SEVII_MAP_123與FLAG_SYS_SEVII_MAP_4567設下之前不顯示,第 1028 與 1556 行,這兩個旗標由1之島寶可夢中心的腳本設下;肚臍岩與誕生之島在FLAG_WORLD_MAP_NAVEL_ROCK_EXTERIOR與FLAG_WORLD_MAP_BIRTH_ISLAND_EXTERIOR設下之前被塗掉,第 1521 到 1524 行)、data/maps/CeladonCity/scripts.inc(轉場腳本中的setworldmapflag FLAG_WORLD_MAP_CELADON_CITY)、data/maps/PalletTown_RivalsHouse/scripts.inc(勁敵的姊姊給出城鎮地圖,RECEIVED_TOWN_MAP)與data/event_scripts.s(EventScript_WallTownMap、special ShowTownMap),commit037335f,2026年10月4日讀取,計數由measure_region_maps.py完成,壁掛地圖由scan_wall_maps.py找出(輸出wall_maps.txt:每一間寶可夢中心的一樓、常磐市的學校與一些民宅),https://github.com/pret/pokefirered/blob/master/src/data/region_map/region_map_layout_kanto.h。 ↩↩↩↩↩↩↩↩↩↩ -
pret,
pokecrystal/data/maps/landmarks.asm(95 個地標)、data/maps/flypoints.asm(24 個飛行點)、maps/VioletCity.asm(VioletCityFlypointCallback: setflag ENGINE_FLYPOINT_VIOLET)與engine/events/overworld.asm(ENGINE_STORMBADGE),commit5beda23,2026年10月4日讀取,https://github.com/pret/pokecrystal/blob/master/data/maps/flypoints.asm。 ↩↩↩↩↩↩↩ -
zladx,LADX-Disassembly,commit
2a5c2c0,src/constants/memory/wram.asm(wOverworldRoomStatus,ds $100)與src/constants/gameplay.asm(OW_ROOM_STATUS_VISITED EQU $80與其他狀態位元),2026年10月4日存檔為ladx-wram.asm與ladx-gameplay.asm,https://github.com/zladx/LADX-Disassembly/blob/main/src/constants/gameplay.asm。 ↩↩↩ -
作者的研究筆記,「Beyond the plaza: routes, map connections and districts in the canon」,2026年10月4日,第 4 節(「The brief for Kiradex」),作者的綜合整理:五個區域與它們的尺寸、距離目標、連接格式、接縫、在場與圍籬缺口的計畫、拾獲物、拱門看守人、地圖畫面、電車、工作順序、十三項驗收檢查,以及拒絕的項目。Kiradex 儲存庫
docs/research/routes/01-routes.md;私有。去程區間於 2026年10月5日從研究筆記的 90 到 120 個圖塊收窄為 110 到 120,理由見註腳 66;研究筆記的副本仍保留先前的區間。同樣在 10 月 5 日、研究筆記之後設定、未載入其副本的還有:App 與伺服器共用的八方向步行圖、草原的四道樹籬、檢查 6 的逐影格檢查、把封鎖清單列為待辦工作,以及延後金幣拾獲物。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Kiradex,
docs/WORLD.md,「Kiradex World: research and plan」,2026年10月4日讀取(私有儲存庫):第 1 節(「The rules for Kiradex World」:世界裡不放任何他們的東西);第 4 節(「Levels」:「XP comes only from the real world, which is the point of the app」;「Coins and the shop」:靠掃描、等級與每日造訪賺取金幣;「The plaza」:每個房間最多約 40 位玩家,依地區分片,鏡頭跟著玩家並夾在地圖範圍內);第 5 節(「What the Duo gives us」:「The plaza on the open inner display is the best version of this: wide view, your profile card beside the world when you tap someone」);第 6 節(透過 eBay 的市集、攤位);第 7 節(「Order of work」,每個階段一個 TestFlight build);以及 2026年10月2日的決定(伺服器採用 FastAPI 與 WebSockets)。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Kiradex 原始碼,2026年10月5日讀取(私有儲存庫):
Kiradex/World/TileMap.swift,path(from:to:)自第 325 行起(八種移動;斜走成本為Float(2).squareRoot(),並在第 348 行,除非它經過的兩個圖塊都可行走,否則略過;先展開成本最低的開放圖塊),以及server/app/rooms.py,Plaza.path自第 52 行起(在第 66 行以四種移動做廣度優先搜尋;它的 docstring:「the same walk the app takes」)。 ↩↩↩↩↩ -
作者的研究筆記,「Beyond the plaza: routes, map connections and districts in the canon」,2026年10月4日彙整,「Method」一節與第 5 節(「What is measured and what is not」):共用的步行定義(一步一格;矮崖單向、算兩格;接縫只在鄰圖對面那一格能從鄰圖某扇門抵達時才算數,對沒有傳送點的鄰圖則從其他連接往下一層播種;訓練家、NPC 與劇情阻擋一律忽略;不含衝浪;第三世代的高度簡化為高度相同或任一側為 0 或 15;傳送點在踩進去時觸發;居合斬樹、碎岩岩石與怪力巨石除非另有說明都視為阻擋)、第一與第二世代格模型的附註、提示時序的附註、影格率數字、分類的附註、未對照遊戲資料檢查的次級來源,以及不屬於事實的解讀。Kiradex 儲存庫
docs/research/routes/01-routes.md;私有,作者的研究狀態中另有一份副本。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
gbdev,Pan Docs,「Rendering」(每影格 70,224 個點,「@ 59.7 fps」),2026年10月4日存檔為
pandocs-timing.txt,https://gbdev.io/pandocs/Rendering.html。 ↩ -
pret,
pokeemerald/src/field_control_avatar.c(CheckStandardWildEncounter,第 668 到 686 行:當sWildEncounterImmunitySteps < 4時,它計入這一步並不檢定就返回;在一次成功的StandardWildEncounter之後,把計數歸零)、src/overworld.c(LoadMapFromCameraTransition中的RestartWildEncounterImmunitySteps,第 800 行,以及LoadMapFromWarp,第 850 行)、src/battle_setup.c(戰鬥開始時的同一個呼叫,第 370 行)、src/wild_encounter.c(StandardWildEncounter:行為改變時 40% 的跳過,第 531 到 539 行與第 599 行;WildEncounterCheck,自第 502 行起:遭遇率乘以 16,經音速與越野自行車縮放為 80%,再經ApplyFluteEncounterRateMod、ApplyCleanseTagEncounterRateMod與帶頭寶可夢的特性縮放,然後以Random() % 2880比對)與src/data/wild_encounters.json(101 到 104 號道路的陸地遭遇率為 20),commit731ad5b,2026年10月4日與5日讀取,https://github.com/pret/pokeemerald/blob/master/src/field_control_avatar.c。 ↩↩ -
pret,
pokefirered/src/wild_encounter.c,commit037335f,2026年10月4日與5日讀取:GetMapBaseEncounterCooldown(8 − rate/10步,第 686 行);HandleWildEncounterCooldown,第 707 到 755 行(自上次遭遇以來的步數達到冷卻值後,每一步都可以嘗試;在那之前,每一步遭遇格步數加一,並在Random() % 100 < 5時讓那一步嘗試,但不結束冷卻);TryStandardWildEncounter,第 757 到 776 行(計數與加成只在遭遇開始後才歸零);每次載入地圖時的同一個重設,src/overworld.c(第 764 與 799 行)呼叫RestartWildEncounterImmunitySteps,在《火紅》中它會呼叫ResetEncounterRateModifiers(src/field_control_avatar.c,第 735 到 737 行);踏上新行為時的 60% 擲骰(第 370 行);DoWildEncounterRateTest,第 309 行(rate × 16加上encounterRateBuff × 16 / 200,分母為MAX_ENCOUNTER_RATE1,600);以及AddToWildEncounterRateBuff,第 778 行(每次擲骰失敗後加上遭遇率,除非噴霧正在作用),https://github.com/pret/pokefirered/blob/master/src/wild_encounter.c。 ↩↩ -
作者的量測,2026年10月4日,來自
measure_gen12_routes.py(《紅》:ROUTE_1到ROUTE_25對照constants/map_constants.asm中低於$0B的 11 個地圖 ID,block 數乘以 4;《水晶》:data/maps/maps.asm中環境 ROUTE 對照 TOWN,尺寸取自constants/map_constants.asm)與measure_connections.py(《綠寶石》與《火紅》:map_type,尺寸取自layouts.json;《綠寶石》的 11 張MAP_TYPE_OCEAN_ROUTE地圖共 45,600 格,不計入陸地數字;《火紅》的道路包含七之島)。輸出gen12_routes.txt與connections.txt。 ↩↩↩↩↩↩ -
作者的量測,2026年10月4日:作者為本文準備的證據資料夾中的
measure_region_maps.py,輸出region_maps.txt與region_maps.json(《綠寶石》:28 乘 15 方格上的 129 個道路方格與 22 個城鎮方格)。 ↩↩ -
Bulbapedia,「Kanto Route 1」,攻略段落,2026年10月4日存檔為
bulba-route1.txt,https://bulbapedia.bulbagarden.net/wiki/Kanto_Route_1。只使用一次,作為最少草叢搜尋的交叉驗證。 ↩ -
作者為建築篇所做的研究筆記,Kiradex 儲存庫
docs/research/structures/01-structures-in-the-canon.md,第 8.8 節(「What to refuse」:「No ledges to jump, no climbing, no isometric」),以及02-structures-craft-and-engine.md(《綠寶石》的GetLedgeJumpDirection,讓矮崖只能朝它的行為所指定的方向跨越),2026年10月3日;私有。 ↩↩↩↩ -
Blake Crosley,「Pixel-Art Structures: Houses, Halls and Interiors on iPhone」,blakecrosley.com,2026年10月3日(第 6 節:單一深度規則與物件網格、以資料表示的傳送點、以
.id(floor.name)原地切換樓層;第 7 節:伺服器對plaza.json的測試,以及版本不做的事情清單,其中包括「a one-way ledge」),https://blakecrosley.com/blog/pixel-art-structures-on-iphone。 ↩↩↩↩↩↩↩ -
pret,
pokered/data/maps/headers/Route1.asm(connection north, ViridianCity, VIRIDIAN_CITY, -5)與pokered/macros/scripts/maps.asm(connection巨集的帶狀區指標),commitd2704a6,2026年10月4日讀取,https://github.com/pret/pokered/blob/master/macros/scripts/maps.asm。 ↩↩↩ -
pret,
pokered/constants/map_data_constants.asm(MAP_BORDER EQU 3)與pokered/home/overworld.asm(CheckMapConnections),commitd2704a6,2026年10月4日讀取,https://github.com/pret/pokered/blob/master/constants/map_data_constants.asm。 ↩↩ -
pret,
pokered/data/maps/headers/Route22.asm與Route23.asm(connection north, Route23, ROUTE_23, 0 ; unnecessary與它的反向連接),commitd2704a6,2026年10月4日讀取,https://github.com/pret/pokered/blob/master/data/maps/headers/Route22.asm。 ↩ -
pret,
pokecrystal/data/maps/attributes.asm(連接)與data/maps/maps.asm(圖塊集、環境與地圖群組),commit5beda23,2026年10月4日讀取,https://github.com/pret/pokecrystal/blob/master/data/maps/attributes.asm。 ↩ -
Porymap 文件,「Editing Map Connections」,2026年10月4日存檔為
porymap-connections.txt,https://huderlem.github.io/porymap/manual/editing-map-connections.html。 ↩↩ -
pret,
pokered/data/maps/objects/Route2Gate.asm(大木博士的助手與一個少年),commitd2704a6,2026年10月4日讀取,https://github.com/pret/pokered/blob/master/data/maps/objects/Route2Gate.asm。 ↩ -
pret,
pokered/scripts/BluesHouse.asm(勁敵的姊姊在EVENT_GOT_POKEDEX設下後給出TOWN_MAP,然後設下EVENT_GOT_TOWN_MAP)、engine/items/town_map.asm(DisplayTownMap逐一走過TownMapOrder的每一筆項目,沒有任何「去過」檢查;wTownVisitedFlag只在BuildFlyLocationsList中讀取)、data/tilesets/bookshelf_tile_ids.asm(bookshelf_tile HOUSE, $3D, TownMapText,一幅打開同一個畫面的壁掛地圖)、data/maps/headers/BluesHouse.asm(勁敵的家使用 HOUSE 圖塊集;玩家的家使用REDS_HOUSE_1與REDS_HOUSE_2)、gfx/blocksets/house.bst(圖塊 $3D 只出現在 block 14)與maps/BluesHouse.blk(block 14 位於最上排的第二格),commitd2704a6,2026年10月4日讀取,https://github.com/pret/pokered/blob/master/scripts/BluesHouse.asm。 ↩↩↩↩↩↩ -
pret,
pokecrystal/maps/CherrygroveCity.asm(嚮導老爺爺的禮物,setflag ENGINE_MAP_CARD,第 72 行)與engine/pokegear/pokegear.asm(地圖卡的POKEGEAR_MAP_CARD_F;PokegearMap_CheckRegion只在玩家站在關都地標時顯示關都地圖;關都的飛行點要等造訪石英高原之後才開放),commit5beda23,2026年10月4日讀取,https://github.com/pret/pokecrystal/blob/master/maps/CherrygroveCity.asm。 ↩↩↩↩↩ -
Stardew Valley Wiki,「Modding:Maps」,2026年10月4日存檔為
sdv-modding-maps.txt,https://stardewvalleywiki.com/Modding:Maps。 ↩↩ -
Stardew Valley Wiki,「Pelican Town」,2026年10月4日存檔為
sdv-pelican-town.txt,https://stardewvalleywiki.com/Pelican_Town。 ↩ -
Stardew Valley Wiki,「Bus Stop」,2026年10月4日存檔為
sdv-bus-stop.txt,https://stardewvalleywiki.com/Bus_Stop。 ↩ -
Stardew Valley Wiki,「Cindersap Forest」,2026年10月4日存檔為
sdv-cindersap.txt,https://stardewvalleywiki.com/Cindersap_Forest。 ↩ -
Stardew Valley Wiki,「Backwoods」,2026年10月4日存檔為
sdv-backwoods.txt,https://stardewvalleywiki.com/Backwoods。 ↩ -
Stardew Valley Wiki,「Railroad」頁面上顯示的地點導覽框,2026年10月4日存檔為
sdv-railroad.txt,https://stardewvalleywiki.com/Railroad;26 個地方為作者的計數。 ↩ -
Stardew Valley Wiki,「The Mountain」(一開始的兩個出口;春季第 5 天開放的礦井;夏季第 3 天地震後開放的鐵路;以工藝室收集包或 25,000g 換得的採石場橋),2026年10月4日存檔為
sdv-mountain.txt,https://stardewvalleywiki.com/The_Mountain。 ↩↩↩↩ -
Stardew Valley Wiki,「The Desert」(公車、金庫收集包或 40,000g)、「Secret Woods」(原木與鋼斧)與「The Beach」(300 木材的橋),2026年10月4日存檔為
sdv-desert.txt、sdv-secret-woods.txt與sdv-beach.txt,https://stardewvalleywiki.com/The_Desert、https://stardewvalleywiki.com/Secret_Woods 與 https://stardewvalleywiki.com/The_Beach。 ↩↩ -
Stardew Valley Wiki,「Speed」,2026年10月4日存檔為
sdv-speed.txt,https://stardewvalleywiki.com/Speed。該頁面沒有給出單位。 ↩ -
Nookipedia,「Acre」(16 乘 16 格的區塊;GameCube 版五乘六個區塊的城鎮;位於 A-3 的車站;欄列標籤;進入時生成魚與蟲、離開六格後消失;後續作品的城鎮尺寸),2026年10月4日存檔為
nook-acre.txt,https://nookipedia.com/wiki/Acre。 ↩↩↩↩↩ -
Nookipedia,「Map」,2026年10月4日存檔為
nook-map.txt,https://nookipedia.com/wiki/Map。 ↩ -
Nookipedia,「Train」與「Train station」,2026年10月4日存檔為
nook-train.txt與nook-train-station.txt,https://nookipedia.com/wiki/Train 與 https://nookipedia.com/wiki/Train_station。 ↩ -
zladx,LADX-Disassembly wiki,「Game engine documentation」,2026年10月4日存檔為
ladx-engine.txt,https://github.com/zladx/LADX-Disassembly/wiki/Game-engine-documentation。 ↩↩↩ -
zladx,LADX-Disassembly wiki,「Maps data format」(512 位元組、由兩位元組房間指標組成的野外地圖;10 乘 8 的覆蓋層),2026年10月4日存檔為
ladx-maps-format.txt,https://github.com/zladx/LADX-Disassembly/wiki/Maps-data-format。 ↩ -
Blake Crosley,「Pixel-Art Worlds on iPhone: What the 16-Bit Masters Knew」,blakecrosley.com,2026年10月3日(第 5 節:RealityKit 配方、地圖的單一
MeshDescriptor網格、RealityView 的預設值、依 Apple 規格頁面與 Xcode 27 模擬器設定檔計算的比例表、11 點的文字下限與 SwiftUI 文字、不透明度動畫的錯誤與它的遮罩、測試套件內的XCUIScreen擷取;「Since publishing」:匯出給 App 與伺服器的同一張字母地圖;關於著作權與專利的常見問題),https://blakecrosley.com/blog/pixel-art-world-on-iphone。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple,「iPhone Duo: Technical Specifications」,2026年10月4日存檔為
apple-duo-specs.txt,https://www.apple.com/iphone-duo/specs/。 ↩ -
Apple,人機介面指南,「Designing for games」(透過文件 JSON),2026年10月4日存檔為
hig-games-plain.txt,https://developer.apple.com/design/human-interface-guidelines/designing-for-games。 ↩↩ -
Kiradex 原始碼,
Kiradex/World/PlazaStage.swift,2026年10月5日讀取(私有儲存庫):第 132 與 133 行(在門的傳送時,Task.sleep(for: .milliseconds(220))之後呼叫onWarp(warp),沒有淡出),以及第 698 到 700 行(一個floor:傳送原地設定新樓層,沒有淡出)。 ↩ -
Apple 開發者文件,「targetTimestamp」(
CADisplayLink的實例屬性,Core Animation;iOS 與 iPadOS 10.0 及以上),2026年10月4日存檔(摘要「The time interval that represents when the next frame displays,」,即代表下一個影格顯示時間的時間間隔,以及把 display link 加到主執行迴圈的範例),https://developer.apple.com/documentation/quartzcore/cadisplaylink/targettimestamp。 ↩↩ -
Kiradex 原始碼,
Kiradex/World/Wants.swift(「What the town’s own collectors ask to be shown, and whether the collection has it」,鎮上的收藏者想看什麼,以及收藏中是否有它)與Kiradex/World/Quests.swift(當天與當週的任務,「judged from the collection itself」,從收藏本身判定),2026年10月4日讀取;私有儲存庫。 ↩↩ -
作者的量測,2026年10月5日:作者為本文準備的證據資料夾中的
measure_meadow_gate.py,輸出meadow_gate.txt。它依第 7 節的尺寸,把規格書的中央廣場、草原步道與湖畔接成一張方格:中央廣場依工坊出貨的樣子(town.json,40 乘 30,抵達點在第 19 欄、第 13 列,伺服器的可行走字母),北側圍籬沿共用邊緣打開;草原步道 24 乘 60,從中央廣場西端往內 4 個圖塊,讓它的小巷接上大廳與東邊那棟房子之間的草地,小巷 2 個圖塊寬,位於東側一道樹籬之後,湖端的門是一條單向邊,其餘部分由四道樹籬橫越,位於草原的第 12、24、36 與 48 列,2 個圖塊的缺口嘗試每一個位置;湖畔 36 乘 20,以步道為中心,北半部是湖,棧橋 2 個圖塊寬、與小巷對齊,量到它靠岸的那一個圖塊。它走的是 App 的圖(TileMap.path):八個方向,斜走成本 √2,只要它經過的兩個圖塊有任一被擋住就拒絕;門拒絕任何從小巷朝湖方向的一步,只接受從湖那一側直走進來的一步。蜿蜒程度是草原本身從南緣到北緣、小巷以西的最短步行。兩道樹籬,位於草原的第 20 與 40 列,在全部 400 個位置中讓它蜿蜒 59.00 到 66.46 個圖塊;三道(第 15、30 與 45 列)59.00 到 79.77(8,000 中 6 個落在 78 到 90 之內);四道 59.00 到 95.43(160,000 中 5,912 個落在 78 到 90 之內);這些界限是針對等距的樹籬,而不等距的間隔依同一個函式會蜿蜒得更厲害(r7_uneven_probe.py,10 月 5 日,輸出r7_uneven_probe.txt:位於第 0 與 2 列的兩道樹籬達到 77.00,位於第 20、22 與 24 列的三道達到 95.00)。以三道樹籬、位於草原第 15、30 與 45 列做同樣的掃描(在檢核的第六輪之後,於 10 月 5 日加入腳本的第 5 節):這 6 種佈局回家 88.07 個圖塊,去程 114.05 到 120.44,比值 0.731 到 0.772,全都等於或低於 0.8,其中 5 種落在 110 到 120 的區間內,去程 114.05 到 119.02,比值 0.740 到 0.772。所以三道樹籬在 5 個位置上符合每一項目標;四道,也就是作者的選擇,在 4,111 個位置上符合。在那 5,912 種四道樹籬的佈局上:回家 88.07 個圖塊,去程 108.74 到 133.75,比值 0.658 到 0.810,其中 5,886 種等於或低於 0.8;0.8 的目標需要去程 88.07 ÷ 0.8 = 110.09;在 110 到 120 的區間內有 4,111 種佈局,去程 110.15 到 119.95,比值 0.734 到 0.800,全都等於或低於 0.8。抵達點到北緣是 17.07 個圖塊;門若雙向都能通行,兩段步行相等(每種樹籬數各有一個佈局,雙向都是 88.07)。在 0 到 16 個圖塊的每一個偏移量與兩個棧橋位置上,最短的去程是 107.18,最短的回程是 88.07;以每秒 4 個圖塊計,110 與 120 個圖塊是 27.5 與 30 秒。同一份輸出也對照先前的模型自我檢查:從它保存的副本執行,四方向廣度優先搜尋加兩道樹籬保留 125 種佈局,去程 113 到 119,回家 91,比值 0.765 到 0.805,而這 125 種佈局改以八方向行走後,去程 97.18 到 99.67,回家 88.07,比值 0.884 到 0.906,沒有一種達到 0.8。先前的模型把蜿蜒程度算成搜尋所回傳的任一條最短路徑在草原中的步數,而這會因平手而改變;從一端到另一端的蜿蜒程度則不會。規格書沒有描述、但在同一份輸出中掃描過的放置方式:棧橋放在湖岸中央時,最佳比值是 0.721(偏移量 4,5,912 種佈局中有 2,182 種等於或低於 0.8),而小巷若接到一棟建築,這道門就什麼也省不下。 ↩↩↩↩ -
Apple 開發者文件,「preferredFrameRateRange」(
CADisplayLink的實例屬性,Core Animation;iOS 與 iPadOS 15.0 及以上),2026年10月4日存檔(Discussion 一節:display link 會盡力在範圍內回呼,系統也會考量硬體、其他工作、低耗電模式、熱狀態與輔助使用設定,而預設範圍等於螢幕的最高更新率),https://developer.apple.com/documentation/quartzcore/cadisplaylink/preferredframeraterange。 ↩↩