← 所有文章

像素艺术的道路:iPhone 上的地图连接与街区

宝可梦的前三个世代,走完一个 16 像素的格都是 16 帧,约每秒 3.73 格;Kiradex 的收藏者每秒走 4 个图块,所以在老游戏里量出的路,可以按每图块四分之一秒的固定比率换算到我们的小镇。1234 每部作品的第一条道路长 2~6 个游戏机屏幕,没有训练家。第一段路程,即从玩家家门口到下一座城镇的宝可梦中心,在《绿宝石》里是 58 格、步行 15.5 秒,在《火红》里是 94 格、25.2 秒;回程在两作中都更短,分别是 42 格和 76 格,因为沿途的台阶全都朝向家的方向。567 在美术风格变化的地方,关都会在路上放一栋建筑:《红》的 78 条地图连接里,有 76 条连接的是同一套图块集,而《红》里每一座连接两个不同地点的关口,都连接着两套图块集。8 丰缘的引擎会把相邻地图的 7 格复制到当前地图周围的边距里(三条边各 7 格,东边 8 格),用当前地图的图块绘制,并在你跨过去的那一刻载入相邻地图自己的次级图块和调色板,没有淡入淡出。910 《绿宝石》的地图界面是一张 28×15 格的网格;未白镇玩家家二楼墙上的地图可以打开它,从卡那兹市起宝可导航可以随身带着它;它画出每一个地点,不论是否去过,只有两处要等剧情标志;而飞翔只列出你走进去时访问标志已被置位的城镇。111213 如今的 Kiradex World 只是一座 40×30 的小镇,篱笆外什么也没有。14 于是我从反编译结果中测量了经典作品的道路、接缝、关口和地图界面,把《星露谷物语》、《动物森友会》和《塞尔达传说 织梦岛》作为对照模型来读,再把这些数字写成一份简报:广场之外的四个区域、一个地图界面,以及一条在你第一次到达时开通站点的有轨电车。

TL;DR

  • 走一格要 16 帧,约四分之一秒。 《红》《水晶》《绿宝石》都以 16 帧走一格,跑步或骑车 8 帧;按硬件的 59.7 赫兹,就是每秒 3.73 格和 7.47 格。Kiradex 设定为每秒走 4 个图块,比掌机快约 7%;跑步名义上是 6.4,但按其代码的逐帧模型,在 60 赫兹下是 6.00,比掌机的跑步慢约 20%。1231541617
  • 一条道路长 2~10 个屏幕,第一条短且草最多。 测量的 16 条道路沿其轴向从 2.0 个屏幕(《绿宝石》Route 101)到 10.0 个屏幕(《水晶》Route 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
  • 第 3 世代的接缝画出相邻地图的 7 格(东边 8 格),并在跨越时载入它的美术。 缓冲区边距是 MAP_OFFSET 7,因为 “the player has 7 metatiles of view horizontally in either direction”(玩家左右各有 7 个元图块的视野),东边复制 MAP_OFFSET + 1 列;跨越时载入新地图的次级图块集、音乐和天气,并滑入一个持续约 3.2 秒的地名提示,没有淡入淡出。91021
  • 地图界面显示每个地点;快速移动只显示你去过的地方。 《绿宝石》的地图是 28×15 格,其中 158 格由 54 个地点占用;一旦玩家能打开它(先是家里二楼的墙上地图,之后是宝可导航),它就画出每个地点,不论是否去过,只有两处被剧情标志压着;一座城镇只有在进入时置位了它的 FLAG_VISITED_* 标志,才会成为飞翔的目的地;《红》《火红》《水晶》和《织梦岛》也保存同一类访问记录。11121322232425
  • 这给 Kiradex 带来什么。 一份简报:中央广场之外的四个区域(卡册街、草地步道、湖畔和画廊区),总面积是中央广场的 2.8 倍;没有淡入淡出的开放接缝;应用与服务器共用的一张八方向行走图;一个 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 像素的行走方格:第 3 世代的一个元图块,或第 1、2 世代一个区块的四分之一。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 拍、每拍 2 像素,骑车一步是 4 拍、每拍 4 像素,而大地图每 2 帧走一拍(MaxOverworldDelay: db 2),同样是行走一格 16 帧、骑车 8 帧。2 《绿宝石》的步进表中,行走(sStep1Funcs)有 16 项,跑步和冲浪 8 项,越野自行车和水流 6 项,全速的音速自行车 4 项,最快的 2 项;跳台阶(JUMP_DISTANCE_FAR)走两格要 32 帧,最高点升起 12 像素(sJumpY_High)。3

时钟的另一半是帧。GBATEK 给出 Game Boy Advance 的一帧是 280,896 个周期,“ca. 59.737 Hz”(约 59.737 赫兹);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

十六条道路

我测量了每部作品的前四条道路:《红》的 Route 1~4、《水晶》的 29~32、《绿宝石》的 101~104 和《火红》的 1~4。第 3 世代的格从 map.bin 读取(第 0~9 位是元图块,第 10、11 位是碰撞,第 12~15 位是高度),每格的行为在主或次 metatile_attributes.bin 中查找;训练家是地图对象事件中 trainer_type 不为 TRAINER_TYPE_NONE 的那些。6 第 1 世代的格读取每个 16×16 格左下角的 8×8 图块,与图块集的碰撞列表、草图块 $52 和台阶表对照;第 2 世代的格读取每个区块按四分之一区域各带一个的四个碰撞字节。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 格)。可行走比例一列是行人能站立的格所占的比例。台阶格在第 1、3 世代指跳跃格,在第 2 世代指边沿格。步行距离在从门可到达的接缝格之间测量;被隔断的道路,是指两半之间隔着森林、洞窟、关口或大海,因而地图内不存在从接缝到接缝的步行路线。29

十六条早期宝可梦道路的条形图:每张地图的长边以格计,圆点表示各方向的横穿最短步行,标签注明跨越的游戏机屏幕数,从《绿宝石》Route 101 的 2.0 个屏幕到《水晶》Route 32 的 10.0 个屏幕,坐标轴以格和步行秒数计。

沿长轴的道路长度,以及各方向的横穿最短步行;由 figures/make_figures.py 根据两个道路脚本的输出绘制。

从表中可以看出五件事。

道路跨 2~10 个屏幕,第一条很短。 《绿宝石》Route 101 高两个 Game Boy Advance 屏幕;《水晶》Route 32 高十个 Game Boy 屏幕;每部作品的第一条道路是 2~6 个屏幕。56 以格计,测量的道路长边从 20 到 108 格。6

较长方向的横穿是长边的 1.15~1.70 倍。 十六条道路中有九条可以从一端走到另一端。在这九条上,较长方向的最短步行与道路长边之比分别是:《绿宝石》Route 101 北行 34 比 20,1.70;《水晶》Route 29 90 比 60,1.50;《水晶》Route 32 132 比 90,1.47;《火红》Route 1 58 比 40,1.45;《红》Route 1 52 比 36,1.44;《水晶》Route 30 74 比 54,1.37;《绿宝石》Route 102 62 比 50,1.24;《红》Route 3 84 比 70,1.20;《火红》Route 3 97 比 84,1.15。5617 回程更直,是长边的 0.95~1.42 倍:《绿宝石》Route 101 19 比 20,《水晶》Route 30 57 比 54,《火红》Route 1 43 比 40,《绿宝石》Route 102 55 比 50,《红》Route 1 40 比 36,《火红》Route 3 94 比 84,《红》Route 3 82 比 70,《水晶》Route 29 71 比 60,以及《水晶》Route 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%,而且《红》Route 3(22.6%)脚下的草比《红》Route 1(18.8%)更多。在《绿宝石》Route 101 上,237 个可行走格中有 91 个是草丛,占 38.4%。5617

第一条道路没有训练家。 《红》1、《火红》1、《绿宝石》101 和《水晶》29 一个也没有;训练家从第二或第三条道路开始出现,《绿宝石》Route 102 有 4 位,《水晶》Route 30 有 3 位,《红》和《火红》的 Route 3 有 8 位。56 这些道路上训练家的视野从 0 到 7 格;《绿宝石》Route 103 的九位训练家视野 1~5 格,Route 104 的八位 0~7 格。6

遇敌区在短暂的宽限之后,约每九步判定命中一次。 玩家进入地图后(无论经由接缝还是门)以及战斗之后,《绿宝石》在头四步里从不判定:CheckStandardWildEncounter 在 sWildEncounterImmunitySteps < 4 期间只计步数,每次遇敌后重新计数,每次载入地图和战斗开始时也重新计数。此后,每踏上一个遇敌格都会判定:当这一步走到不同行为的格上时,有 40% 的几率跳过判定,然后是 Random() % 2880 < rate × 16。Route 101~104 都用 rate 20,所以未经修正的判定是 2,880 分之 320,步行时每步 11.1%;自行车、笛子、净化之符或某些领头宝可梦的特性会先对它进行缩放。31 《火红》加了一个下限和一道坡:遇敌之后以及进入地图时,有一段 8 − rate/10 个遇敌格步数的冷却(Route 1~4 的 rate 为 21,即 6 步),冷却期间每一步若命中 5% 的判定,就让这一步尝试遇敌而不结束冷却;然后是新行为上的 60% 判定;然后是 1,600 分之 rate × 16,即 21%,再加上每次判定失败后增长一个 rate 的加成(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

Route 1 的三种画法

《红》《火红》和《水晶》以三种分辨率画同一个想法,而 Route 1 是最干净的比较对象。《红》的 Route 1 是 10×18 个区块,20×36 格,高四个 Game Boy 屏幕,篱笆桩和树行之间有 10 段朝南的台阶,共 42 格,草 104 格。5 为了检查第 1 世代的格模型,我只把这一张地图从区块集按两倍大小渲染出来,加上 16 像素网格;渲染图显示西侧树行外有一条 3 格宽的空地,两条接缝都碰得到,却从任一城镇的门都走不过去,这就是搜索要从相邻城镇的门出发来确定穿越点的原因。5 第 1 世代模型每格只读一个 8×8 图块,即左下角那个,而且只在那张渲染图上用肉眼核对过;像水与陆这样的图块对碰撞没有建模。29

《火红》的 Route 1 是 24×40 格,在更大的屏幕上仍然高四个屏幕,台阶 61 格,草 178 格,占地图 18.5%:重制版保留了以屏幕计的长度,地图每个方向加大 4 格,并增加了台阶。6 从真新镇的家门到常磐市的宝可梦中心是 94 格,25 秒,反方向是 76 格。7 《水晶》的第一条道路 Route 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 秒 穿过森林的两座关口
火红 深灰市宝可梦中心 Route 4 宝可梦中心 150 40.2 秒 20.1 秒 市、道路、道路
火红 Route 4 宝可梦中心 华蓝市宝可梦中心 314 84.1 秒 42.3 秒 穿过洞窟的三层,其中一层走两次

步行秒数是格数乘以 16 帧,再除以 59.7275 赫兹。跑步是第二次搜索,求最少帧数,每格 8 帧,而每次跳台阶仍是 32 帧,因为无论什么步态,跳台阶都是同样的 32 帧跳跃;上面七个路段中,只有洞窟路段跳了一次台阶,所以它是 2,528 帧而不是 2,512 帧。“居合斩后”的数字是把 Route 2 的五棵居合斩树移除后的同一搜索,这会打开穿过 ROUTE2_EAST_BUILDING 的东边小路。淡入淡出和战斗不计入。7 搜索中的两处简化对洞窟路段影响最大:第 3 世代的高度被简化为两种情况,即高度相同,或任一侧为 0 或 15;传送点在踏入时触发,而不是在到达时触发。因此,穿过洞窟、依次经过 1F、B1F、B2F 再回到 B1F 的 314 格,是表中最不确定的数字。29

宝可梦《绿宝石》和《火红》中七个门到门路段的水平条形图,分去程和回程,以步行秒数和格数表示,五个双向都测过的路段带有回程条;两个第一路段去程为 15.5 秒和 25.2 秒,回程为 11.3 秒和 20.4 秒;下方是三条范围条,表示 Kiradex 简报在每秒 4 个图块下的目标。

门到门,去程,以及测过回程的回程,并把简报的目标与经典作品并列;由 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

秘传学习器是一条穿过你早已走过的关口的捷径。 移除 Route 2 的居合斩树后,常磐市到深灰市是 156 格而不是 267 格,路线改走 Route 2 的东边建筑,而不是森林。7

丰缘的头三段路

《绿宝石》一开局就对“城镇之间有什么”这个问题给出三种不同的回答,而测量让这个设计变得清晰可读。

Route 101 是一条两端各有一扇前门的走廊。它是 20×20 格,高两个屏幕,22.8% 是草丛,即 237 个可站立格中的 91 个;三段朝南、各长四格的台阶横贯其中。从未白镇往北走需要 34 格,至少要穿过 10 个草格;从古辰镇往南走只需 19 格,跳下台阶就能完全避开草丛。从玩家家门到古辰镇宝可梦中心是 58 格,步行 15.5 秒。6187

Route 102 更宽,有了第一批训练家。它是 50×20 格,宽 3.3 个屏幕,草占 14.3%,有 4 位视野 2 或 3 格的训练家、一个 26 个水格的池塘和 17 个台阶格。橙华市以 −10 的偏移与它相接,所以这条道路的 20 行对着橙华市的第 10~29 行。宝可梦中心到宝可梦中心,古辰镇到橙华市是 87 格、23.3 秒,回程 81 格。67

Route 104 是一条分成两半的道路。它是 40×80 格,高八个屏幕,四分之一是水,地图内没有一条步行路线能连通它的三条接缝:南半部通往橙华森林的南入口,距橙华市接缝 51 格;北半部通往森林的北出口,从卡那兹市接缝到出口两个门口格中较近的一个是 60 格(到另一个是 61 格)。从橙华市宝可梦中心到卡那兹市宝可梦中心要穿过五张地图(市、道路、森林、道路、市),共 250 格,步行 67 秒,回程 196 格。67

这三段之间变化的是分母。第一条道路是一段不到 20 秒的步行,没有训练家,回程不必踩草;第三段则是穿过迷宫的一分钟步行。依我的解读,这款游戏要等玩家学会走路之后,才开始花费距离。

台阶朝向家

九条早期道路的哑铃图,显示离开较早城镇的去程最短步行与回程最短步行,以格计:《红》Route 1 52 和 40,《火红》Route 1 58 和 43,《绿宝石》Route 101 34 和 19,《绿宝石》Route 102 62 和 55,《水晶》Route 29 90 和 71,《水晶》Route 30 74 和 57,《红》Route 3 84 和 82,《火红》Route 3 97 和 94,《水晶》Route 32 132 和 128;下方是四条测量道路上各方向最少草丛格数的成对条形。

每条两端可步行相通的测量道路的去程与回程,以及各方向最少的草格数;由 figures/make_figures.py 根据道路脚本的输出和 grass_paths.txt 绘制。

在每条两端可步行相通的测量道路上(九条全都有台阶),朝较早城镇方向的步行都更短。《红》Route 1 回程 40 格,去程 52 格,短 23%;《火红》Route 1 43 对 58,26%;《绿宝石》Route 101 19 对 34,44%;《绿宝石》Route 102 55 对 62,11%;《水晶》Route 29 71 对 90,21%;《水晶》Route 30 57 对 74,23%;《红》Route 3 82 对 84,《火红》Route 3 94 对 97,《水晶》Route 32 128 对 132,各短 2%~3%。Route 32 的 4 格差距来自从一个转角台阶向侧面的一次跳跃,这是回程唯一的一跳。5617 第一道路上的台阶全都朝同一个方向:《红》Route 1 的 10 段 42 个台阶格、《火红》Route 1 的 10 段 61 格,以及《绿宝石》Route 101 的 13 格(三段各四格加一个转角格)全部朝南,转角朝东南,而在每张地图上,这都是回到出发城镇的方向。56

台阶还把草从回程中拿走了。第二次搜索以穿过的草丛格数而非步数为最小化目标,结果显示:在《红》Route 1 上,北行至少要穿过 15 个草格,南行只需 4 个,而且全都在真新镇旁边的入口处;《火红》Route 1 是 19 对 5;《绿宝石》Route 101 是 10 对 0;《绿宝石》Route 102 去程 4、回程 0。18 Bulbapedia 关于关都 Route 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

第 2 世代的数字各需一条附注。《水晶》的碰撞是每个 32 像素区块 4 个字节,每四分之一区域一个,脚本假设它们按行优先顺序排列(左上、右上、左下、右下),因为 tilecoll 宏没有写明顺序。29 第 2 世代的 COLL_HOP_* 字节标记的是玩家站立的边沿格,而不是它之外的那一格,这是 probe_gen2_ledges.py 在 Route 30 的全部 21 个 HOP_DOWN 格下方都找到一个 WALL 格后确定的;边沿这一解读只在 Route 29 和 30 上探查过。529 《水晶》还有单侧墙。从 COLL_RIGHT_WALL 到 COLL_UP_LEFT_WALL 的碰撞字节是陆地,但 GetMovementPermissions 会拒绝从这种图块的墙侧走出的一步,也拒绝从那一侧走进的一步;在 Route 32 上,四条中唯一有单侧墙的一条(41 格,全是 UP_WALL),它们把最短步行从忽略单侧墙的搜索所得的双向 110 格,延长到去程 132 格、回程 128 格。它的转角台阶 HOP_DOWN_RIGHT 和 HOP_DOWN_LEFT 在普通一步被拒绝后,会按玩家面对的方向向下或向侧面跳,而回程就从其中一个向侧面跳。《红》没有单侧墙:它唯一的其他移动规则,即图块对碰撞,是双向生效的,而且只登记在洞窟和森林图块集中,这里测量的地图都不用它们。5 此外,搜索把一次台阶算作两格移动,这与第 3 世代 32 帧的跳跃相符,对第 1、2 世代则是假设。293

数据显示的是结果,不是意图:我没有找到任何开发者声明说台阶是为了缩短回程而放置的,只知道在测量的每一条道路上,它们确实起到了这个作用。29 我从中得出的规则是:台阶是一条回程的单向捷径,绝不是一堵挡在前方的墙。

高度的问题

这一发现与上一篇文章不太协调。像素艺术建筑 背后的建筑调研档案干脆拒绝了台阶,“No ledges to jump, no climbing, no isometric”(不跳台阶,不攀爬,不做等距视角),而那篇文章也把单向台阶列为其构建不做的事情之一。3637 这份调研档案则把台阶测量为经典作品中回家的捷径。两者在事实上并不冲突;分歧在于,一座收藏小镇要取得这种效果是否需要高度。这一决定属于本计划的下一篇文章,即关于高度、台阶和桥的那篇,它尚未发布。在那里作出决定之前,第 7 节的简报把台阶推迟到高度篇,回程的缩短改由一道树篱门来实现:草地步道上一道只能从湖那一侧打开的门,同样是单向的回程捷径,只是没有高度。26

3. 接缝、关口与地图界面

宝可梦的大地图不是一张大地图。它由许多小地图组成,每张都有自己的尺寸、图块和音乐,边与边相接,让你不经切换就能从一张走进下一张。这种拼接方式几乎决定了本节的一切:建筑必须立在路上的什么位置,重制版为什么少了这些建筑,以及地图界面能知道些什么。

第 1 和第 2 世代:复制到边距里的一条带

《红》的一条连接是地图头文件里的一行,写明方向、相邻地图和以区块计的偏移;Route 1 的连接是 connection north, ViridianCity, VIRIDIAN_CITY, -5,宏会预先算好要把相邻地图的哪一条区块带复制进当前地图的缓冲区。38 缓冲区 wOverworldMap 在地图四周都留出 MAP_BORDER EQU 3 个区块,即 6 格的边距;连接用相邻地图最近的 3 行或 3 列区块填满这段边距,而当玩家的坐标越过 −1 或地图的宽度或高度时,CheckMapConnections 就切换地图。39

由于这条带是以区块编号复制、并用当时载入的区块集绘制的,两张地图只有共用一套图块集才能在接缝处相接,而《红》的数据也守住了这一点:在 78 行连接中,有 76 行连接的是同一图块集的地图。两个例外是 Route 22 和 Route 23,即 OVERWORLD 对 PLATEAU,头文件把这两行都标注为“; unnecessary”(不必要)。840 《水晶》有 142 行连接;138 行连接同一图块集(例外是满金市与 Route 35,以及 Route 32 与 Route 33,即 Johto 对 Johto Modern),92 行留在同一个地图组内。841

第 3 世代:相邻地图的七格(东边八格),美术在跨越时载入

第 3 世代的一条连接,是地图 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 个元图块的视野)。FillSouthConnection 及其同类函数按连接的偏移,把相邻地图的 7 行或 7 列(东边 8 列)复制进这段边距,范围只覆盖相邻地图自己的宽度(FillNorthConnection 从偏移加 MAP_OFFSET 处开始,在放得下的范围内复制相邻地图的宽度),而整个缓冲区最多容纳 MAX_MAP_DATA_SIZE 10240 格。9 在没有连接的地方,边距是布局的 2×2 边界图案,并与 MAPGRID_IMPASSABLE 做按位或,所以永远无法行走(GetBorderBlockAt)。9

一张 20×20 格道路的第 3 世代地图缓冲区示意图,北边以偏移 0 连着一张 20 格宽的相邻地图:中间是道路,西、北、南三边各 7 格边距,东边 8 格;相邻地图的 7 行只在道路的 20 列上填满北边边距,四角和其他边距是不可通行的边界图案,一块 15×10 格的屏幕跨在北边边缘上。

接缝处缓冲区里装着什么,示意图:每格一个方块,尺寸取自 MAP_OFFSET、FillNorthConnection,以及 Route 101 的 20×20 布局和以偏移 0 位于其北的古辰镇 20 格宽布局;由 figures/make_figures.py 绘制,未使用游戏美术。

在玩家跨过之前,相邻地图的那条带用当前地图的图块集绘制(DrawMetatileAt 取的是你所在地图的主、次图块集)。跨越时调用 LoadMapFromCameraTransition,它运行新地图的切换脚本,只载入它的次级图块集和调色板,切换音乐和天气,并显示地名提示,没有淡入淡出。10 这就是为什么没有任何一对相连的地图主图块集不同:《绿宝石》134 对中为 0,《火红》120 对中为 0。《绿宝石》有 38 对、《火红》有 34 对连接的次级图块集不同,而其中各有 24 对,边距里相邻地图的那条带(7 格,东边 8 格,超出缓冲区的部分按引擎的做法截掉)只用主元图块,所以从任一侧看都画得正确。8

偏移很常见。《绿宝石》134 条连接中有 36 条、《火红》120 条中有 68 条偏移不为零;《火红》的 Route 1 位于常磐市 48 格宽度中向内 12 格的位置。8 在《火红》里,互惠并非完全普遍:120 项中有 112 项是互惠的,其余 8 项中有 6 项出自那座带城墙的城市。金黄市单向连接到 Route 5~8,而这些道路连接的不是金黄市,而是一张单独的 SAFFRON_CITY_CONNECTION 地图;最后两项是七之岛的原型地图。8 我把金黄市的这种安排读作一种绘制技巧:这条连接的存在,是为了在道路边缘之外画出城墙,而进城的路是四座关口,在《红》里,关口的守卫在拿到饮料之前会把玩家挡回去。819 当一个地方必须看起来连续、却又需要设卡时,第 3 世代就在边距里画出邻居,把门放进一栋建筑。

跨过接缝进入设置了 show_map_name 的地图时,地点名会滑入。提示任务先等 31 帧,以每帧 2 像素滑入 40 像素(20 帧),停留到计时器超过 120(121 帧),再滑出(20 帧):约 190 帧,3.2 秒。21 我是按阈值数出任务的各个状态的,所以每个状态都可能比我数的多一帧或少一帧。29

关口:美术变化之处

《红》有 27 张地图使用 GATE 或 FOREST_GATE 图块集:Route 11、12、15、16 和 18 的两层关口(10 张地图),Route 2、5、6、7 和 8 的单层关口,Route 22 的关口,狩猎地带的关口及其 4 间休息屋,4 个地下通道入口,以及常磐森林的 2 座关口。8 《红》里每一座连接两个不同地点的关口,都连接两套图块集:OVERWORLD 到 FOREST(常磐森林、狩猎地带),到 UNDERGROUND(四个地下通道入口),或到 PLATEAU(Route 22 到 Route 23),这些都是《红》的连接几乎从不建立的衔接。8

Route 2 是最清楚的例子。它在《红》里是 20×72 格,在《火红》里是 24×80 格,无法从一端走到另一端:南北两半只通过常磐森林相连,而在《红》里,这片森林是用 FOREST 图块集而非 OVERWORLD 绘制的,要经由两座关口进入。58 在《火红》里,从常磐市宝可梦中心到深灰市宝可梦中心,经过南关口、森林和北关口,共 267 格。7 由此得出三种解读,它们是对数据的解读,不是开发者的陈述。

  1. 关口是美术变化的地方。 第 1 世代的接缝用当前区块集绘制相邻地图的区块,所以两张地图只有共用图块集才能在接缝处相接,而《红》在 78 条连接中的 76 条做到了这一点。因此,一片用森林图块集画出的森林需要一扇门,关口就是那扇门。388
  2. 关口是规则可以安身的地方。 金黄市的四座关口在你给出饮料之前会把玩家挡回去(TEXT_ROUTE5GATE_GUARD_GEE_IM_THIRSTY、BIT_GAVE_SAFFRON_GUARDS_DRINK);Route 22 的关口检查 BIT_BOULDERBADGE。19 还有三座关口奖励收集:当拥有的宝可梦种类数(对 wPokedexOwned 做 CountSetBits)在 Route 2 关口达到 10、在 Route 11 关口二楼达到 30、在 Route 15 关口二楼达到 50 时,大木博士的助手会送出道具。20
  3. 关口是有人在的停顿。 Route 2 的关口里有大木博士的助手和一个少年;走路在一次淡入淡出之后,于一个新地方继续。43

重制版按引擎分道扬镳。《水晶》有 25 张环境为 GATE 的地图,其中 19 张连接不同地图组的地图,7 张连接不同的图块集。8 《火红》保留了关都的关口:8 张室内地图通向两张不同的室外地图(常磐森林那一对、金黄市的四个入口、Route 22 的北入口和狩猎地带入口),另有 13 张在同一张室外地图的两个不同侧面各有一扇门,其中包括五座两层的道路关口和 Route 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(Route 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

《星露谷物语》:由日历开启的中心与辐条

《星露谷物语》在每一条边缘都淡入淡出。用它维基的话说:“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

山谷是围绕一个中心的小图。鹈鹕镇在西北方通往巴士站和农场,在西南方通往煤矿森林,向南通往海滩,向北通往山区。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 版《动物森友会》用地块(acre)搭建城镇。“Acres are grid elements measuring 16x16 spaces”(地块是 16×16 格的网格单元);城镇是“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,《集合啦!动物森友会》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》采取了与宝可梦连续卷动相反的做法。在 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.”(在大地图上,房间按游戏中的布局存储在一个 16×16 的房间网格上)。58 大地图是 512 字节的双字节房间指针,即 256 个房间,房间的图块覆盖层是“a straight forward array of 10x8 tile numbers”(一个简单直接的 10×8 图块编号数组)。59 “Overworld rooms are grouped in 2x2 sections (i.e. 4 rooms). Each section has a tileset ID,”(大地图房间按 2×2 区段分组,即 4 个房间,每个区段有一个图块集 ID),而在过渡到需要它们的房间时,区段的 32 个图块会被替换。58 每个大地图房间在 256 字节的 wOverworldRoomStatus 中有一个状态字节,其中 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%。第一条道路不放训练家。 道路表;草丛占可行走格的比例
节奏 在遇敌地面上,约每九步有一样值得留意的东西,到达后先给几步宽限;用下限和坡让干旱和洪水都保持短暂。 《绿宝石》的四步豁免,之后 rate 20,未修正为 11.1%;《火红》的冷却与加成
回程 比去程短,在九条带台阶且两端可步行相通的道路上全都短 2%~44%;回程的草格为 0~5,去程为 4~19。台阶是回程的单向捷径,绝不是挡在前方的墙。 九条道路;最少草丛搜索
接缝 只有在两侧都用同一套地面集绘制边缘的地方,才让两个区域在开放接缝处相接;在美术变化之处放一栋有两扇门的建筑。 《红》78 中 76,《水晶》142 中 138,第 3 世代主图块集不同者 134 中 0 与 120 中 0;《红》里每一座连接两个地点的关口
相邻带 在边缘之外至少画出半个视野的相邻区域。 第 3 世代对 15 格屏幕画 7 格(东边 8 格);第 1 世代对 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 日的图块数;《绿宝石》Route 101 的 20×20 对展开 Duo 的 29.7×20.9

表格来源:第 1 节的步进代码和帧时序;12315 Kiradex 的步速;4 道路、行程、草丛和连接脚本;567188 遇敌代码;3132 《绿宝石》的缓冲区、跨越和地名提示代码;91021 关口脚本;1920 地图界面、玩家如何得到它们,以及飞翔;1112132345244422 《星露谷物语》《动物森友会》和《织梦岛》;465253555825 本系列第一篇中的各设备图块数。60

其中三条规则值得再多说一句。

接缝规则是作为关于美术、而不是关于引擎的规则来表述的。Game Boy 用当前地图的区块集绘制相邻带,所以它根本无法显示使用不同图块集的邻居;Game Boy Advance 在你跨过之前一直用当前地图的图块集绘制相邻带,依我的解读,这就是它的接缝保持在同一套主图块集、而且大多只用主元图块的原因。38108 我把两者都读作图块内存的限制,而一部只绘制一张图集的手机并没有这种限制。留下来的是视觉上的理由:一条路如果在横穿屏幕的一条直线上突然换了材质,无论引擎是否需要,看起来都是一条接缝。这就是为什么即便没有任何东西强迫,这条规则仍保留了前半句(地面延续之处开放相接)和后半句(美术变化之处放一栋建筑)。

快速移动规则是最值得原样照搬的一条。这里测量的每一部宝可梦作品都在游戏早期交出地图,画出地图上的每个地点,不论是否去过,只有少数被剧情或事件标志压后的后期地点除外,而只给捷径设卡;每一作都让玩家靠走到过某个地方来赢得捷径,而不是靠付钱。44451213232224 对于一款 XP 只来自现实世界中扫描的卡牌的收藏应用来说,访问标志是正确的货币,因为它的代价只是走一趟,别无其他。27

朝向规则来自手机,而不是经典作品。竖持的 Pro Max 按 Kiradex 的比例能看到 10.3×22.4 个图块,展开的 iPhone Duo 是 29.7×20.9,所以南北走向的道路在常见的手机上一次能显示其长度中的 22 个图块,在相机居中跟随的收藏者前方约 11 个;而东西走向的道路只有在展开的 Duo 上才能一次显示 30 个,前方约 15 个。602717 20×20 的《绿宝石》Route 101 能完整装进展开 Duo 的一个视野里,这意味着这么短的道路在那里已经没有什么可揭示的了。617

6. Apple 的做法:视野、接缝、房间与地图

Kiradex World 底层的引擎就是第一篇文章所阐述的那套:把 RealityKit 场景当作 2D 渲染器使用,正交相机按每纹素整数个设备像素对齐,地面是一个 MeshDescriptor 网格、一次绘制调用画完,每种材质都是带不透明度阈值的 UnlitMaterial,并关闭 RealityView 的运动模糊和色调映射。60 建筑篇在此基础上加入了在第二个网格中按深度排序的物体、作为数据的传送点,以及通过给舞台一个新身份 .id(floor.name) 来原地切换楼层,让 SwiftUI 拆掉一个 RealityView 再建起下一个。37 篱笆之外的区域还需要这套引擎再提供三样东西:越过接缝看到邻居的视野、在服务器上切换房间而不出现断档的方法,以及一个地图界面。这些都还没有构建,本节的任何内容也都没有在手机上测量过;这是简报所依托的计划,每项成本都已点名并标注为未测量。

手机能看到什么

图块数取自第一篇文章的表格,是根据 Apple 的规格页面和 Xcode 27 模拟器配置文件,按 Kiradex 的整数比例计算出来的。60

设备与方向 每纹素像素数 可见图块(横×纵) 半个视野
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 倍到玻璃上,所以在真机上,同样数量的图块应当铺满面板,约为每纹素 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 再建起下一个,需要淡入淡出来掩盖,而应用的门目前还没有淡入淡出:现在,踏上门后 220 毫秒地点才切换,楼层变化不经淡入淡出就替换舞台,而本系列的运动篇提议用一层蒙版同时遮住这两者。接缝按定义就没有淡入淡出。376316 所以接缝的方案是保持同一个场景。当收藏者走到距离一条有连接的边缘不到半个视野时,舞台用与第一个网格同类的 MeshDescriptor 把邻居的地面构建成第二个网格,按世界单位放在连接的偏移处,并把邻居的物体加到各自的深度上。当收藏者的脚跨过边缘时,舞台重新定基:邻居变成当前区域,旧区域变成身后的那条带,而相机今天是在地图范围内钳制着跟随玩家的,在两者都已载入期间则钳制在两者的并集范围内。2726

有三条引擎事实让这在纸面上很便宜。地面是每个区域一个网格、一次绘制调用,所以一个邻居只增加一次地面绘制调用和一次物体绘制调用。6037 每个像素都是镂空的并写入深度,所以邻居的树木与收藏者的前后关系按与当前区域相同的深度规则排序,不需要新的排序。37 而锻炉已经能把整个区域导出为字母、自动拼接的地面、物体和传送点,所以邻居只是同类的第二个文件;简报为它加上一个 connections 列表。1426 这些都没有告诉我的是:在需要的那一刻构建第二个网格要花多少成本,以及在一帧之内构建它是否会造成卡顿;这一点没有测量。因此简报的检查 6 分为两半,两者都覆盖整个接近过程,从构建邻居网格之前一直到跨越结束:一份逐帧计时日志,用来抓主线程卡顿;以及对每一帧截获画面的检查,用来抓黑帧。抽样检查不够:每秒六十帧中的一帧黑帧,未必会扰乱回调的时序,而每 100 毫秒抽一次样,在 60 赫兹下只能看到六帧中的一帧。17 计时这一半和逐帧规则,是我在调研档案原本只抽样检查亮度的那项检查上增加的。

自动拼接是 GBA 规则唯一没有涵盖的东西。第 3 世代让接缝保持在共用的地面上,是因为邻居的那条带是用当前地图的图块绘制的。108 Kiradex 用同一套图集调色板绘制每个区域,所以这一限制并不适用,但区域边缘的 blob 自动拼接图块是根据该区域自己的字母计算的;如果锻炉不看接缝另一侧,一条跨过接缝的小路就会在两侧都以一道边框收尾。因此简报让锻炉跨越每条连接计算边缘遮罩。26

房间、在场状态与重连

Kiradex 的广场是一个服务器房间:每个房间最多约 40 名玩家,按地区分片,运行在 FastAPI 和 WebSockets 上。27 篱笆之外的一个区域自然是另一个房间,这就带来了单人掌机从未有过的问题:接缝另一侧的人。方案是让客户端在开始绘制邻近房间的那条带时,订阅该房间中行走的人,这样在中央广场的边缘就能看到卡册街上的人;并在跨越时切换自己的房间,让行走者不必停下。26 门和有轨电车有淡入淡出,即运动篇简报为门加上的那层蒙版,而这次淡入淡出可以遮住重连,正如建筑调研档案已经为室内提出的那样。1626 第一篇文章针对黑屏的修复适用于这次淡入淡出:对承载 RealityView 的 SwiftUI 视图做不透明度动画,会让它一直没有被绘制,所以淡入淡出要做成画在它上面的一层蒙版。60

服务器需要与客户端所绘制的同一张地图。如今 server/app/plaza.json 保存着小镇的字母,让服务器能验证行走;简报让每个区域的副本都带上它的字母和连接,这样跨越接缝的行走在接缝两侧都能得到验证。6026 在真实网络上切换房间的成本,以及订阅第二个房间是否会让接缝附近广场的消息量翻倍,都没有测量。

用 SwiftUI 做地名卡、地图和有轨电车

区域的名字属于 SwiftUI,不属于像素层。第一篇文章发现,一款五纹素的像素字体在 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”,展开内屏上的广场是这一点的最佳形态:宽广的视野,点按某人时资料卡出现在世界旁边);在手机上它是一张底部弹出页。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 上也应该是开通的,所以已访问的集合随收藏者的资料保存在服务器上,而不是存在应用的本地存储里。26 这扩展了世界计划的第 3 节,那一节写道“Our server holds only the public profile card and the economy”(我们的服务器只保存公开资料卡和经济系统);我会把已访问的集合作为另一个字段加在它们旁边,只记录收藏者进入过五个区域中的哪些,不含位置、不含文字、不含访问时间,并像其余字段一样以 Game Center 玩家 ID 作为键;计划所要求的隐私审查也必须涵盖它。2726

成本,以及尚未测量的部分

本节的一切都没有计时。未测量的成本有:在收藏者接近边缘时即时构建邻居的地面和物体网格;两个区域都已载入时绘制两者的网格;在接缝附近订阅第二个房间;以及跨越时的房间切换。第一篇文章的截屏规则决定了如何测量它们:在 iPhone Duo 模拟器上,只有从测试包内部(XCUIScreen.screens[1])而不是从宿主机截取时,RealityKit 的帧才是准确的。60 简报的检查 6 正是用这种截屏来确认接缝之前能看到邻居,而它的逐帧检查和从 CADisplayLink 回调读取的帧计时日志,则在设备构建上而不是模拟器上运行:有了展开的 iPhone Duo 就用它,在那之前用 iPhone 18 Pro Max,因为在我写作时,我的 Mac 上还没有配对任何 iPhone Duo。64 目前还没有任何截屏方法被证明能记录每一个显示出的帧;检查中的帧数匹配,即录像的帧数对照计时日志的回调数,决定某种方法能否做到。

7. 简报:Kiradex 要做什么,以及必须通过的检查

作者的综合。 以下是我的提案,由第 1 至第 6 节推导而来,并对照 Kiradex 仓库中的世界计划,即其第 4 节(“The game”)和第 7 节(“Order of work”)进行调整。2726 它不使用任何宝可梦作品中的地点、生物或标志的名字;凡借用机制之处,引用的是第 5 节的规则,而不是原作的用语。所有尺寸都以 16 像素图块计,一段步行的长度是它在应用自身寻路中的代价,一步斜走算 √2 个图块(第 3 项)。28 所有时间都是该长度按名义的每秒 4 个图块计算的,是用来与经典作品比较的数字,不是测量值;运动篇的简报在 60 赫兹下直走一个图块用 16 拍,即每秒 3.75 个图块,并把斜走向上取整为 23 拍,按那种算法,这里的每个时间在直走时会增加十五分之一,在斜走时约增加十二分之一。41617

Kiradex 的循环在现实世界。XP 只来自扫描并登记的卡牌:“XP comes only from the real world, which is the point of the app.”(XP 只来自现实世界,这正是这款应用的意义所在)。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

简报五个区域的示意图,画在每格代表 10 个图块的 16×12 地图界面上:湖畔在最上方,其下是草地步道,中央广场在底部中央,卡册街在中央广场东侧,画廊区在其西侧、隔着一道虚线拱门;每个圆点标出一个区域的电车站,注释写明中央广场之外共 3,376 个图块,是其 1,200 的 2.8 倍。

提议的区域,按地图界面的网格绘制;仅为示意,由 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 个图块,其中 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>}],其中偏移是相邻区域原点沿共享边的位置,沿用第 3 世代的惯例。148 每条连接都有偏移取反的反向连接(第 5 节,连接),没有例外:经典作品的单向连接是一种本简报不需要的绘制技巧。服务器上每个区域的副本带有字母和连接,所以行走会跨接缝得到验证。6026

一张行走图同时服务两端。应用的 TileMap.path 做八方向搜索,一步斜走代价为 √2,且只有当它经过的两个图块都可行走时才允许,所以没人能切过墙角;服务器在 server/app/rooms.py 中的 Plaza.path 只做四方向搜索,尽管它的注释称其为“the same walk the app takes”(与应用相同的行走)。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. 名字与美术

每个区域的名字、招牌和字符串都要通过一份受保护名称的禁用清单,每个图块和精灵都来自锻炉。2726 这份禁用清单还不存在:调研档案承诺要在仓库里放一份,可仓库里没有。制作它也是本简报的一部分:一份朴素的受保护名称清单,每行一个,提交到仓库里锻炉旁边,再配一项测试,从锻炉的导出文件中读取每个区域的名字、招牌和字符串,只要发现清单中的任何一项(不区分大小写)就失败。26 不用带编号的道路名,不用“地区”式的命名体系,也不建造任何复刻经典关口外观的建筑:尺寸和规则可以一致,但形状和名字必须是 Kiradex 自己的。26

检查: 13(名字与美术)。

11. 工作顺序

世界计划把广场(房间、同步、气泡、资料、交换界面)排在第三,把硬币和商店排在第四。27 这些区域作为第 3b 阶段排进去,在广场的网络稳定之后,与硬币同时或在其之前:26

  1. 锻炉和服务器中的连接(格式、互惠、接缝绘制、房间切换)、服务器的八方向搜索,以及禁用清单及其测试,以卡册街作为第一个区域,因为它用的是建筑篇已经设计好的卡牌店,而且最小。
  2. 草地步道和拾得物(服务器放置、每日上限、奖励内容)。
  3. 地图界面和有轨电车。
  4. 湖畔。
  5. 等博物馆的室内存在之后,带拱门的画廊区。卡册街上的摊位排要等市集阶段。

每一步都是一个 TestFlight 构建,正如世界计划对每个阶段的要求,并附上相应检查的截屏。27

验收检查

每一项都可以通过一个针对锻炉导出文件和服务器测试的脚本来运行,或者通过模拟器上的截屏来运行;检查 6 在设备上运行,只有其中一次截屏在模拟器上进行。26

  1. 互惠。 导出文件恰好包含三条开放接缝:中央广场东侧到卡册街、中央广场北侧到草地步道,以及草地步道北侧到湖畔;并且对每条连接 (a, dir, b, off),导出文件都包含 (b, opposite(dir), a, −off);只要有任何一条单向连接,测试就失败,没有例外。
  2. 穿越点。 从每个区域的到达点和门出发的广度优先搜索,要在每条接缝的两侧各找到至少 2 个面对对面可行走图块的穿越图块,即 measure_gen3_routes.py 的方法。6
  3. 距离。 在拼接后的区域上运行第 3 项共用的八方向搜索,把树篱门作为单向边(只能从湖那一侧以直走的一步进入,永远不能朝湖的方向离开),每个长度都按一步斜走算 √2 的路径代价计,报告:到达点到卡牌店最多 60 个图块;到达点到湖畔栈桥 110~120;栈桥到到达点最多为去程的 0.8 倍;到达点到博物馆门口最多 70。锻炉按每秒 4 个图块打印时间。
  4. 比例。 中央广场之外的图块数除以中央广场的图块数,介于 2.0 和 3.9 之间。
  5. 朝向。 草地步道的高度大于宽度。
  6. 接缝处没有黑帧,也没有主线程卡顿。 在设备构建上,收藏者走过中央广场与卡册街之间的接缝:从距离接缝超过半个视野的图块出发,在舞台开始构建卡册街的网格之前,一直走到越过接缝的第一步完成、中央广场变成身后那条带为止。两份记录覆盖这整段过程。本次运行中,显示链接通过它的 preferredFrameRateRange 请求 60 赫兹,以免 ProMotion 显示屏不断变化的刷新率在行走途中改变计数;Apple 把这个范围描述为显示链接会尽力遵守的范围,所以显示实际维持了何种刷新率的,是计时日志,而不是请求本身。67 第一份记录是显示屏显示的每一帧,用该刷新率的录像保存,并逐帧检查:如果一帧的平均亮度低于这段过程中位数帧的四分之一(这是我定的阈值),就算黑帧,过程中任何一处只要出现一帧黑帧,检查就失败。这段过程中录像的帧数必须等于同一段过程中计时日志的回调数,这样一个丢帧的截屏方法就不可能因为恰好漏掉那一帧黑帧而通过。目前还没有任何截屏方法被证明能记录每一个显示出的帧,而这项匹配决定了某种方法能否做到。当两个数不一致、而计时日志通过了下面它自己的规则时,问题出在截屏,本次运行不计;当两个数不一致、而计时日志没有通过该规则时,检查失败。第二份记录是注册在主运行循环上的 CADisplayLink 回调的 targetTimestamp,与 Apple 自己的示例注册方式相同,按照 Apple 的文档,它是下一帧显示的时间,每一帧都记录;相邻两个值的间隔都不得超过 25 毫秒,即 60 赫兹下的一帧半,这样一次丢帧就会像一帧黑帧一样让检查失败,而且既能抓住跨越时的主线程卡顿,也能抓住构建邻居期间的卡顿。计时日志稳定在 60 赫兹以外任何刷新率上的运行都不计,本次运行要关闭低电量模式,因为 Apple 把该模式、严重的热状态和辅助功能设置都列为可能改变刷新率的因素。67 设备在拿到展开的 iPhone Duo 后就用它;在我写作时,我的 Mac 上还没有配对任何 iPhone Duo,所以在那之前,检查在 iPhone 18 Pro Max 上跨越同一条接缝运行,这能显示在手机上接近和跨越是否会卡顿或黑屏,但不能显示 Duo 内屏如何调度它的帧。模拟器无法替代:依我的解读,它的帧计时测量的是运行它的 Mac,而不是手机。另外,在展开 Duo 的模拟器上从测试包内部截屏,当收藏者站在接缝前中央广场的最后一个图块上时,要能看到卡册街至少 14 个图块。6064
  7. 地名卡。 跨越接缝或乘电车到达后,地名卡在屏幕上停留 2.5 到 3.5 秒。
  8. 地图界面模型。 单元测试:每个区域的矩形都在 16×12 之内,任意两个不重叠,每个区域都有名字,当前区域被标出,未访问区域的站点被禁用。
  9. 电车规则。 服务器测试:前往未访问区域的乘车被拒绝;从室内出发的乘车被拒绝;乘车终止于目的地的站点。
  10. 拾得物。 服务器测试:每次进入区域最多一个闪光,每位收藏者每天最多三个,同一收藏者、日期和区域得到同一个闪光,奖励内容只从装饰品和任务便条表中抽取,绝不是卡牌、XP 或硬币。一项锻炉测试:花丛覆盖草地步道可行走图块的 12%~18%。
  11. 拱门管理员。 一项单元测试:四份里程碑礼物在已登记数为 10、30、50 和 100 时各触发一次,绝不因硬币或购买而触发。
  12. 服务器一致。 服务器载入每个区域的字母和连接,用第 3 项的八方向搜索,让一位收藏者从到达点跨过两条接缝走到湖畔栈桥,再经由树篱门走回来;应用和服务器在两个方向上都返回相同的图块和相同的代价,客户端搜索会拒绝的一步,例如切角,或从小径穿过门走出去的一步,服务器也同样拒绝,这沿用了现有服务器测试的模式。3728
  13. 名字与美术。 禁用清单及其测试存在于仓库中,且测试通过:每个区域名、招牌和字符串都通过清单;每个图块和精灵都来自锻炉。

不在本简报之内

  • 逐步的随机遇敌,或任何玩起来像野生对战的东西。拾得物是放置好且看得见的。
  • 核心区域的能力门槛。 没有任何区域靠等级、别针、购买或工具开启;区域靠走过去就会开启,只有捷径,也就是有轨电车,是要赢得的,而且靠到访来赢得。这是以宽厚的方式解读第 5 节的开启一行。5226
  • 人们相聚的两个地方之间超过约 30 秒的行程,且没有电车。经典作品中一分钟的路段是迷宫。7
  • 台阶、带单向落差的台地,以及任何逐格高度。 推迟到高度篇,由它来决定;在那之前,简报用树篱门来获得更短的回程,它是单向的,但是平的。36
  • 带编号的道路名、“地区”式的命名体系、凭徽章开启的快速移动,或复刻经典关口外观的建筑。27
  • 单向连接。 经典作品把它用作绘制技巧;本简报一条也没有,只要出现一条,检查 1 就失败。8
  • 硬币类拾得物,直到世界计划规定每日访问能赚多少硬币为止。27
  • 在市集阶段之前出现在卡册街上的市集摊位。27

核心要点

如果你负责画美术

  • 只在地面延续的地方画接缝。《红》在 78 条连接中的 76 条上连接同一套图块集,而《红》里每一座连接两个地点的关口都正好立在图块集变化之处;一栋有两扇门的建筑,是换材质的诚实做法。8
  • 让第一条道路短,并且草最多:2~6 个屏幕,地图的 14%~23% 是遇敌地面,没有训练家。随着旅程推进,逐渐变稀。56
  • 把地图界面画成一张命名矩形的粗网格。《绿宝石》的是 28×15 格,95 个矩形中有 49 个只占一格。11

如果你负责做引擎

  • 把连接存成带方向、邻居和偏移的数据,并测试每条连接都有偏移取反的反向连接;《绿宝石》的 134 条全都有。842
  • 在边缘之外至少画出半个视野的邻居。第 3 世代复制 7 格(东边 8 格),因为玩家左右各看得到 7 格;按 Kiradex 的比例,这在展开的 Duo 上是 15 个图块,在竖持的 Pro Max 上是 12 个。960
  • 跨接缝时保持同一个场景,把场景切换留给门和快速移动,那里有淡入淡出来遮掩。1037
  • 用搜索而不是直尺来测距离,并纳入引擎的每一条移动规则:在两端可步行相通的九条测量道路上,较长方向的横穿是长边的 1.15~1.70 倍,回程是 0.95~1.42 倍,回程可以比去程短四分之一。不考虑《水晶》单侧墙和侧向转角跳的搜索,得出 Route 32 双向各 110 格;考虑之后是去程 132、回程 128。5617
  • 让应用和服务器共用一张行走图。如果客户端走八方向而服务器走四方向,测试测出的每个距离都只属于其中一方。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 个元图块的视野),并用相邻地图的 7 行或 7 列(东边 8 列)填满有连接一侧的边距,用当前地图的图块绘制。玩家跨过时,它载入新地图的次级图块集、调色板、音乐和天气,并滑入一个地名提示,没有淡入淡出。《红》也用 3 个区块、6 格的边距做同样的事。91039

关都的道路为什么有关口?

依我对数据的解读,是因为 Game Boy 的接缝只能连接两张用同一套图块集绘制的地图,而关口就是图块集变化之处的一扇门:《红》的 78 条连接中有 76 条连接同一套图块集,而《红》里每一座连接两个地点的关口都连接两套。关口也承载规则:想要饮料的金黄市守卫、Route 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

照搬宝可梦构建道路的方式合法吗?

机制、尺寸和方法不受著作权保护。具体的图块和精灵受著作权保护,名称和标志受商标保护。本系列第一篇文章讨论了这条界线,涉及美国版权局的 Circular 33(它把系统和操作方法排除在著作权之外),以及任天堂起诉 Palworld 开发商一案中的专利。60 本简报借用的是数字和规则,每个图块都出自 Kiradex 自己的锻炉,名字也对照禁用清单检查过。26


本文是怎么做出来的。 本文背后的调研档案于 2026 年 10 月 4 日编成,依据是 pret 反编译的浅克隆,由六个连同输出一起保存的 Python 脚本测量,并参考了 Stardew Valley Wiki、Nookipedia、zladx 反汇编、Porymap 手册、GBATEK、Pan Docs、一个 Bulbapedia 页面以及 Apple 页面的存档副本。正文中的每个宝可梦数字都是脚本的输出,每条注释都写明了对应的脚本。发布前,10 月 5 日的第二轮审阅带来了三次重新测量,即《水晶》的单侧墙和转角跳、跨越台阶的跑步,以及按应用的八方向行走重算简报中的草地,还带来了对遇敌代码宽限步数更仔细的重读;每条注释都写明了哪些数字有变动。第 7 节的提案是我的,并已如此标注;其中任何内容都还没有构建。

本站相关内容:在 iPhone 上造一个像素艺术世界 是本系列的第一篇,讲了图块系统、RealityKit 配方,以及本文视野所依据的各设备图块数;像素艺术人物:iPhone 上的角色与捏人器 讲的是走在这些道路上的收藏者;像素艺术建筑:iPhone 上的房屋、大厅与室内 讲了有轨电车和拱门所复用的门、传送点和楼层切换,以及本文留给高度篇去处理的对台阶的拒绝;面向开发者的 iPhone Duo 推导出计算半视野相邻带所依据的内屏尺寸;为 iPhone Duo 准备你的 app 讲了地图面板所遵循的并排布局;Xcode 27.1 beta:把应用放进 iPhone Duo 模拟器 讲了简报中的接缝检查所运行的模拟器。

参考来源


  1. pret,pokered/home/overworld.asm,提交 d2704a6(OverworldLoop 每次迭代调用两次 DelayFrame;AdvancePlayerSprite 每次迭代移动 2 像素,共 8 次迭代,wWalkCounter 为 8;骑自行车时每次迭代调用两次 AdvancePlayerSprite),2026 年 10 月 4 日访问,https://github.com/pret/pokered/blob/master/home/overworld.asm ↩↩↩↩

  2. pret,pokecrystal/engine/overworld/map_objects.asm(StepVectors:行走一步 8 拍、每拍 2 像素,骑车一步 4 拍、每拍 4 像素)和 pokecrystal/engine/overworld/events.asm(MaxOverworldDelay: db 2),提交 5beda23,2026 年 10 月 4 日访问,https://github.com/pret/pokecrystal/blob/master/engine/overworld/map_objects.asm ↩↩↩↩

  3. pret,pokeemerald/src/event_object_movement.c,提交 731ad5b(步进表:行走的 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 ↩↩↩↩↩

  4. Kiradex 源码,Kiradex/World/PlazaRig.swift 第 101 行(tilesPerSecond: Float = 4)和第 122 行(runPace: Float = 1.6),2026 年 10 月 4 日从工作树读取;以及第 660 和 666 行(每帧把 dt × tilesPerSecond × pace ÷ length 加到一步的进度上,一步完成后把进度设回 0,丢弃越过的部分),2026 年 10 月 5 日读取;私有仓库。每秒 6.4 个图块是名义跑速,即 4 × 1.6。 ↩↩↩↩↩

  5. 作者测量,2026 年 10 月 4 日:measure_gen12_routes.py(位于作者为本文准备的证据文件夹中),输出 gen12_routes.txt。来自 pret pokered(d2704a6)的输入:constants/map_constants.asm(以区块计的尺寸,乘二得格数)、maps/Route*.blk、对照 Overworld_Coll 的 gfx/blocksets/overworld.bst、草图块 $52 和 data/tilesets/ledge_tiles.asm(每格一个 8×8 图块,取左下角)、data/maps/objects/Route*.asm(带 OPP_ 类别的 object_event 行视为训练家)、data/maps/headers/Route*.asm(连接)。来自 pret pokecrystal(5beda23):maps/Route*.blk、经由 CollisionPermissionTable 的 data/tilesets/<tileset>_collision.asm(假设每区块 4 字节按行优先排列)、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 行);由于一次跳的代价为 2,最短路径用 Dijkstra 搜索求得。只有 Route 32 有侧墙(41 格,全是 UP_WALL,其中包括 data/tilesets/johto_collision.asm 第 113 行的区块),也只有它的步行数发生了变化:之前双向各 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。第 2 世代的边沿解读来自 probe_gen2_ledges.py(Route 30 的 21 个 HOP_DOWN 格中,21 个下方都有 WALL 格)。第 1 世代格模型是在 render_gen1.py 以两倍大小、加 16 像素网格渲染的《红》Route 1(render_Route1.png,仅供分析,衍生自受版权保护的美术,不公开)上用肉眼核对的。注释 33 背后的道路和城镇面积也来自此脚本。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  6. 作者测量,2026 年 10 月 4 日:measure_gen3_routes.py(位于作者为本文准备的证据文件夹中),输出 gen3_routes.txt 和 gen3_routes.json。来自 pret pokeemerald(731ad5b)和 pokefirered(037335f)的输入:data/layouts/layouts.json(尺寸)、每条道路 map.bin 的每一格(第 0~9 位元图块,第 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)。最短步行用广度优先搜索求得。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  7. 作者测量,2026 年 10 月 4 日:measure_travel.py(位于作者为本文准备的证据文件夹中),输出 travel.txt 和 travel.json,针对 pret pokeemerald(731ad5b)和 pokefirered(037335f)。在(地图, 格)对上经由接缝、传送点和台阶进行广度优先搜索,从第一扇门下方的格到最后一扇门,计数包括走进门的最后一步(第 136 行,“+1: the step up into the door”,走进门的那一步加 1);步行秒数为格数 × 16 帧 ÷ 59.7275 赫兹。跑步于 2026 年 10 月 5 日扩展(早先把每个跑步格都按 8 帧计的输出保存为 travel.pre-ledgerun.txt),是求最少帧数的第二次搜索,每步 8 帧,每次跳台阶 32 帧,因为《火红》的跳跃是 JUMP_DISTANCE_FAR 下的 Jump2,无论何种步态都是 32 帧(src/field_player_avatar.c 的 PlayerJumpLedge,第 905 行;src/event_object_movement.c 的 DoJumpSpriteMovement,第 9094 行);Route 4 宝可梦中心到华蓝市的路段为 2,528 帧、42.3 秒,含一次跳跃,表中其他去程路段都不跳台阶;居合斩树、碎岩岩石和怪力巨石会阻挡其所在的格,除非该次运行另有说明(“居合斩后”的运行移除了它们,在 Route 2 上会打开 ROUTE2_EAST_BUILDING)。淡入淡出和战斗不计入。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  8. 作者测量,2026 年 10 月 4 日,并于 2026 年 10 月 5 日扩展:按 FillEastConnection 的复制方式在东边读取 8 列,并按 FillNorthConnection、FillSouthConnection、FillWestConnection 和 FillEastConnection 的截断方式,在每条带超出缓冲区处截断;只用主图块的计数从 22/38(《绿宝石》)和 25/34(《火红》)变为 24/38 和 24/34。measure_connections.py(位于作者为本文准备的证据文件夹中),输出 connections.txt 和 connections.json,覆盖全部四份反编译:每条地图连接、跨每条接缝的图块集是否一致(第 3 世代为主、次图块集,以及边距中的相邻带,即 7 行或 7 列、东边 8 列,是否只用主元图块)、偏移取反的互惠性、非零偏移、《红》中使用 GATE 和 FOREST_GATE 图块集的地图(data/maps/headers/*.asm)、《水晶》中环境为 GATE 的地图(data/maps/maps.asm),以及第 3 世代中传送点通向两张不同室外地图或同一张室外地图两侧的室内地图。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  9. 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 做按位或)、data/maps/Route101/map.json(古辰镇以偏移 0 位于北侧)和 data/layouts/layouts.json(LAYOUT_ROUTE101 和 LAYOUT_OLDALE_TOWN,均为 20×20),731ad5b,2026 年 10 月 4 日访问,https://github.com/pret/pokeemerald/blob/master/include/fieldmap.h 和 https://github.com/pret/pokeemerald/blob/master/src/fieldmap.c ↩↩↩↩↩↩↩

  10. pret,pokeemerald/src/field_camera.c(DrawMetatileAt 用当前布局的主、次图块集绘制)和 pokeemerald/src/overworld.c(LoadMapFromCameraTransition:切换脚本、次级图块集和调色板、音乐、天气和地名提示,没有淡入淡出),731ad5b,2026 年 10 月 4 日访问,https://github.com/pret/pokeemerald/blob/master/src/overworld.c ↩↩↩↩↩↩↩↩↩↩

  11. 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,731ad5b,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 ↩↩↩↩↩↩↩

  12. pret,pokeemerald/src/field_control_avatar.c(行为为 MB_REGION_MAP 的元图块运行 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 行),731ad5b,2026 年 10 月 4 日访问;墙上地图布局由 scan_wall_maps.py 找出(位于作者为本文准备的证据文件夹中,输出 wall_maps.txt:未白镇两栋住宅的楼上,以及每座宝可梦中心的一楼),https://github.com/pret/pokeemerald/blob/master/data/event_scripts.s ↩↩↩↩↩↩↩↩

  13. 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),731ad5b,2026 年 10 月 4 日访问,https://github.com/pret/pokeemerald/blob/master/src/region_map.c ↩↩↩↩↩↩↩↩↩↩

  14. Kiradex 源码,scripts/forge/town.py 第 79 和 80 行(MARGIN = 12、WIDTH, HEIGHT = 40, 30)及其 BUILDINGS 列表(七栋建筑,门通向 room:me、room:npc-* 或 hall,一个到达点),2026 年 10 月 4 日读取;私有仓库。 ↩↩↩↩↩↩

  15. Martin Korth,GBATEK,LCD 时序(“Total 228 lines, 16.743 ms, 280896 cycles - ca. 59.737 Hz”,共 228 行,16.743 毫秒,280896 个周期,约 59.737 赫兹),2026 年 10 月 4 日保存为 gbatek.txt,https://problemkaputt.de/gbatek.htm ↩↩↩

  16. 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 毫秒等待和不经淡入淡出的楼层切换;以及简报中的行走,每拍 1 像素,60 赫兹下每图块 16 拍,每秒 3.75 个图块,并用分级蒙版覆盖门和楼层),https://blakecrosley.com/blog/pixel-art-motion-on-iphone ↩↩↩↩↩

  17. 作者根据正文中的测量值所作的计算,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 拍对 √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%(《红》Route 3)到 44.12%(《绿宝石》Route 101);每条道路草丛占可行走格的比例,由两个道路脚本的草格数和可行走格数算出;《绿宝石》地图上城镇所占方格,由 region_map_sections.json 中 16 个城镇和城市区段的 width 和 height 算出(十个 1×1,六个 1×2 或 2×1,共 22);Duo 内屏的图块数为 2853 ÷ 96 和 2007 ÷ 96,96 即 16 个纹素乘 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 后取整。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  18. 作者测量,2026 年 10 月 4 日:measure_grass_paths.py(位于作者为本文准备的证据文件夹中),输出 grass_paths.txt:一种 0-1 广度优先搜索,进入草丛格代价为 1,其他移动代价为 0,台阶单向,使用两个道路脚本的格模型和从门可到达的穿越点;对象为《红》Route 1、《火红》Route 1、《绿宝石》Route 101 和 102。入口格来自 probe_grass_gateway.py(输出 grass_gateway.txt),它在《红》Route 1 南行搜索中依次把每个草格改为普通地面重新运行:只有入口最后四行的 8 个格,每行两个,会降低最小值。 ↩↩↩↩↩↩

  19. pret,pokered/scripts/Route5Gate.asm(TEXT_ROUTE5GATE_GUARD_GEE_IM_THIRSTY、BIT_GAVE_SAFFRON_GUARDS_DRINK)和 pokered/scripts/Route22Gate.asm(BIT_BOULDERBADGE),d2704a6,2026 年 10 月 4 日访问,https://github.com/pret/pokered/blob/master/scripts/Route5Gate.asm ↩↩↩↩↩

  20. pret,pokered/scripts/Route2Gate.asm、scripts/Route11Gate2F.asm、scripts/Route15Gate2F.asm(阈值 10、30 和 50)以及 engine/events/oaks_aide.asm(对 wPokedexOwned 做 CountSetBits),d2704a6,2026 年 10 月 4 日访问,https://github.com/pret/pokered/blob/master/engine/events/oaks_aide.asm ↩↩↩↩↩

  21. pret,pokeemerald/src/map_name_popup.c,731ad5b(POPUP_OFFSCREEN_Y 40、每帧 2 像素的滑动、tOnscreenTimer > 120),2026 年 10 月 4 日访问,https://github.com/pret/pokeemerald/blob/master/src/map_name_popup.c 。190 帧的总数是作者按阈值清点任务各状态得出的,每个状态都可能多一帧或少一帧。 ↩↩↩↩↩

  22. 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 位,以及室外地图),d2704a6,2026 年 10 月 4 日访问,https://github.com/pret/pokered/blob/master/engine/items/town_map.asm ↩↩↩↩↩↩↩

  23. 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),037335f,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 ↩↩↩↩↩↩↩↩↩↩

  24. 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),5beda23,2026 年 10 月 4 日访问,https://github.com/pret/pokecrystal/blob/master/data/maps/flypoints.asm ↩↩↩↩↩↩↩

  25. zladx,LADX-Disassembly,提交 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 ↩↩↩

  26. 作者的调研档案,“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 日、即档案之后确定、未写入其副本的还有:应用与服务器共用的八方向行走图、草地的四道树篱、检查 6 的逐帧检查、作为待办工作的禁用清单,以及硬币类拾得物的推迟。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  27. 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 构建);以及 2026 年 10 月 2 日的决定(服务器采用 FastAPI 和 WebSockets)。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  28. Kiradex 源码,2026 年 10 月 5 日读取(私有仓库):Kiradex/World/TileMap.swift,自第 325 行起的 path(from:to:)(八种移动;斜走代价为 Float(2).squareRoot(),并在第 348 行,除非它经过的两个图块都可行走,否则跳过;优先展开代价最低的开放图块),以及 server/app/rooms.py,自第 52 行起的 Plaza.path(第 66 行对四种移动做广度优先搜索;其文档字符串:“the same walk the app takes”)。 ↩↩↩↩↩

  29. 作者的调研档案,“Beyond the plaza: routes, map connections and districts in the canon”,2026 年 10 月 4 日编成,“Method”一节和第 5 节(“What is measured and what is not”):共用的行走定义(一步为一格;台阶单向、算两格;只有当相邻地图对面的格能从它的某扇门到达时接缝才算数,对于没有传送点的相邻地图,从其他连接往下一层取起点;忽略训练家、NPC 和剧情阻挡;不含冲浪;第 3 世代高度简化为高度相同,或任一侧为 0 或 15;传送点在踏入时触发;居合斩树、碎岩岩石和怪力巨石除另有说明外视为阻挡)、第 1 和第 2 世代格模型的附注、地名提示时长的附注、帧率数字、分类附注、未与游戏数据核对的二手资料,以及并非事实的解读。Kiradex 仓库 docs/research/routes/01-routes.md;私有,作者的调研状态目录中有一份副本。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  30. gbdev,Pan Docs,“Rendering”(每帧 70,224 点,“@ 59.7 fps”),2026 年 10 月 4 日保存为 pandocs-timing.txt,https://gbdev.io/pandocs/Rendering.html ↩

  31. pret,pokeemerald/src/field_control_avatar.c(CheckStandardWildEncounter,第 668~686 行:在 sWildEncounterImmunitySteps < 4 期间只计步数、不判定就返回;StandardWildEncounter 成功后把计数设回 0)、src/overworld.c(LoadMapFromCameraTransition 中的 RestartWildEncounterImmunitySteps,第 800 行,以及 LoadMapFromWarp,第 850 行)、src/battle_setup.c(战斗开始时的同一调用,第 370 行)、src/wild_encounter.c(StandardWildEncounter:行为变化时 40% 的跳过,第 531~539 行和第 599 行;WildEncounterCheck,自第 502 行起:rate 乘 16,音速自行车和越野自行车缩放到 80%,再经 ApplyFluteEncounterRateMod 和 ApplyCleanseTagEncounterRateMod 以及领头宝可梦的特性缩放,然后用 Random() % 2880 与之比较)和 src/data/wild_encounters.json(Route 101~104 的陆地 rate 为 20),731ad5b,2026 年 10 月 4 日和 5 日访问,https://github.com/pret/pokeemerald/blob/master/src/field_control_avatar.c ↩↩

  32. pret,pokefirered/src/wild_encounter.c,037335f,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_RATE 的 1,600 为分母);以及 AddToWildEncounterRateBuff,第 778 行(除非除虫喷雾生效,每次判定失败后都加上 rate),https://github.com/pret/pokefirered/blob/master/src/wild_encounter.c ↩↩

  33. 作者测量,2026 年 10 月 4 日,由 measure_gen12_routes.py(《红》:ROUTE_1 到 ROUTE_25 对 constants/map_constants.asm 中低于 $0B 的 11 个地图 ID,区块数乘 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。 ↩↩↩↩↩↩

  34. 作者测量,2026 年 10 月 4 日:measure_region_maps.py(位于作者为本文准备的证据文件夹中),输出 region_maps.txt 和 region_maps.json(《绿宝石》:28×15 网格上 129 个道路方格和 22 个城镇方格)。 ↩↩

  35. Bulbapedia,“Kanto Route 1”,攻略部分,2026 年 10 月 4 日保存为 bulba-route1.txt,https://bulbapedia.bulbagarden.net/wiki/Kanto_Route_1 。只使用一次,作为最少草丛搜索的交叉核对。 ↩

  36. 作者为建筑篇准备的调研档案,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 日;私有。 ↩↩↩↩

  37. 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 ↩↩↩↩↩↩↩

  38. pret,pokered/data/maps/headers/Route1.asm(connection north, ViridianCity, VIRIDIAN_CITY, -5)和 pokered/macros/scripts/maps.asm(connection 宏的带指针),d2704a6,2026 年 10 月 4 日访问,https://github.com/pret/pokered/blob/master/macros/scripts/maps.asm ↩↩↩

  39. pret,pokered/constants/map_data_constants.asm(MAP_BORDER EQU 3)和 pokered/home/overworld.asm(CheckMapConnections),d2704a6,2026 年 10 月 4 日访问,https://github.com/pret/pokered/blob/master/constants/map_data_constants.asm ↩↩

  40. pret,pokered/data/maps/headers/Route22.asm 和 Route23.asm(connection north, Route23, ROUTE_23, 0 ; unnecessary 及其反向),d2704a6,2026 年 10 月 4 日访问,https://github.com/pret/pokered/blob/master/data/maps/headers/Route22.asm ↩

  41. pret,pokecrystal/data/maps/attributes.asm(连接)和 data/maps/maps.asm(图块集、环境和地图组),5beda23,2026 年 10 月 4 日访问,https://github.com/pret/pokecrystal/blob/master/data/maps/attributes.asm ↩

  42. Porymap 文档,“Editing Map Connections”,2026 年 10 月 4 日保存为 porymap-connections.txt,https://huderlem.github.io/porymap/manual/editing-map-connections.html ↩↩

  43. pret,pokered/data/maps/objects/Route2Gate.asm(大木博士的助手和一个少年),d2704a6,2026 年 10 月 4 日访问,https://github.com/pret/pokered/blob/master/data/maps/objects/Route2Gate.asm ↩

  44. 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 只出现在区块 14 中)和 maps/BluesHouse.blk(区块 14 位于第一行第二格),d2704a6,2026 年 10 月 4 日访问,https://github.com/pret/pokered/blob/master/scripts/BluesHouse.asm ↩↩↩↩↩↩

  45. pret,pokecrystal/maps/CherrygroveCity.asm(向导老伯的礼物,setflag ENGINE_MAP_CARD,第 72 行)和 engine/pokegear/pokegear.asm(地图卡的 POKEGEAR_MAP_CARD_F;PokegearMap_CheckRegion 只有在玩家站在关都地标上时才显示关都地图;关都的飞翔点要等访问石英高原后才开放),5beda23,2026 年 10 月 4 日访问,https://github.com/pret/pokecrystal/blob/master/maps/CherrygroveCity.asm ↩↩↩↩↩

  46. Stardew Valley Wiki,“Modding:Maps”,2026 年 10 月 4 日保存为 sdv-modding-maps.txt,https://stardewvalleywiki.com/Modding:Maps ↩↩

  47. Stardew Valley Wiki,“Pelican Town”,2026 年 10 月 4 日保存为 sdv-pelican-town.txt,https://stardewvalleywiki.com/Pelican_Town ↩

  48. Stardew Valley Wiki,“Bus Stop”,2026 年 10 月 4 日保存为 sdv-bus-stop.txt,https://stardewvalleywiki.com/Bus_Stop ↩

  49. Stardew Valley Wiki,“Cindersap Forest”,2026 年 10 月 4 日保存为 sdv-cindersap.txt,https://stardewvalleywiki.com/Cindersap_Forest ↩

  50. Stardew Valley Wiki,“Backwoods”,2026 年 10 月 4 日保存为 sdv-backwoods.txt,https://stardewvalleywiki.com/Backwoods ↩

  51. Stardew Valley Wiki,“Railroad” 页面上显示的地点导航框,2026 年 10 月 4 日保存为 sdv-railroad.txt,https://stardewvalleywiki.com/Railroad ;按作者的清点为 26 个地点。 ↩

  52. Stardew Valley Wiki,“The Mountain”(最初的两个出口;春季第 5 天开放的矿井;夏季第 3 天地震后开放的铁路;用工艺室收集包或 25,000g 修复的采石场桥),2026 年 10 月 4 日保存为 sdv-mountain.txt,https://stardewvalleywiki.com/The_Mountain ↩↩↩↩

  53. 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 ↩↩

  54. Stardew Valley Wiki,“Speed”,2026 年 10 月 4 日保存为 sdv-speed.txt,https://stardewvalleywiki.com/Speed 。该页面没有给出单位。 ↩

  55. Nookipedia,“Acre”(16×16 格的地块;GameCube 版横五块、纵六块的城镇;位于 A-3 的车站;标签;进入时生成鱼和虫、离开六格后消失;后续作品的城镇尺寸),2026 年 10 月 4 日保存为 nook-acre.txt,https://nookipedia.com/wiki/Acre ↩↩↩↩↩

  56. Nookipedia,“Map”,2026 年 10 月 4 日保存为 nook-map.txt,https://nookipedia.com/wiki/Map ↩

  57. 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 ↩

  58. zladx,LADX-Disassembly 维基,“Game engine documentation”,2026 年 10 月 4 日保存为 ladx-engine.txt,https://github.com/zladx/LADX-Disassembly/wiki/Game-engine-documentation ↩↩↩

  59. zladx,LADX-Disassembly 维基,“Maps data format”(由双字节房间指针组成的 512 字节大地图;10×8 的覆盖层),2026 年 10 月 4 日保存为 ladx-maps-format.txt,https://github.com/zladx/LADX-Disassembly/wiki/Maps-data-format ↩

  60. 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”:导出给应用和服务器的同一张字母地图;关于著作权和专利的常见问题),https://blakecrosley.com/blog/pixel-art-world-on-iphone ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  61. Apple,“iPhone Duo: Technical Specifications”,2026 年 10 月 4 日保存为 apple-duo-specs.txt,https://www.apple.com/iphone-duo/specs/ ↩

  62. Apple,人机界面指南,“Designing for games”(通过文档 JSON 获取),2026 年 10 月 4 日保存为 hig-games-plain.txt,https://developer.apple.com/design/human-interface-guidelines/designing-for-games ↩↩

  63. Kiradex 源码,Kiradex/World/PlazaStage.swift,2026 年 10 月 5 日读取(私有仓库):第 132 和 133 行(门的传送先 Task.sleep(for: .milliseconds(220)),再 onWarp(warp),没有淡入淡出)以及第 698~700 行(floor: 传送原地设置新楼层,没有淡入淡出)。 ↩

  64. Apple Developer Documentation,“targetTimestamp”(CADisplayLink 的实例属性,Core Animation;iOS 和 iPadOS 10.0 及以上),2026 年 10 月 4 日保存(摘要 “The time interval that represents when the next frame displays”,即表示下一帧何时显示的时间值,以及把显示链接添加到主运行循环的示例),https://developer.apple.com/documentation/quartzcore/cadisplaylink/targettimestamp ↩↩

  65. 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 日读取;私有仓库。 ↩↩

  66. 作者测量,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 图块宽、与小径成一直线,测量到它靠陆地的那个图块。它按应用的图(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),而在小径碰到建筑的布置下,门什么也省不下来。 ↩↩↩↩

  67. Apple Developer Documentation,“preferredFrameRateRange”(CADisplayLink 的实例属性,Core Animation;iOS 和 iPadOS 15.0 及以上),2026 年 10 月 4 日保存(讨论部分:显示链接会尽力在该范围内回调,系统还会考虑硬件、其他工作、低电量模式、热状态和辅助功能设置,默认情况下该范围等于显示屏的最大刷新率),https://developer.apple.com/documentation/quartzcore/cadisplaylink/preferredframeraterange ↩↩

相关文章

Pixel-Art Structures: Houses, Halls and Interiors on iPhone

How Pokémon, the SNES RPGs and Stardew build houses, interiors and floors, measured from source, and how Kiradex's town …

80 分钟阅读

Pixel-Art People: Characters and a Creator on iPhone

How the best pixel-art games build and dress their people, measured, and how we rebuilt Kiradex's collector, its wardrob…

79 分钟阅读

让应用为 iPhone Duo 做好准备:一个完整的实例

为 iPhone Duo 准备并提交应用:决定布局的 SDK 标记、一款真实应用走完每种姿态的过程、两块屏的截图,以及 App Store 的规则。

36 分钟阅读