无人在线时依然鲜活的像素小镇:NPC 与幽灵
让一座像素小镇显得有人居住的,主要是那些站着不动的人。在《宝可梦 绿宝石》的 16 座城镇里,没有隐藏旗标、因此每次造访都在场的 100 位镇民分成三类:59 人守在原位,朝一个方向或四处张望;39 人在离初始位置一两格的范围内游荡;2 人原地踏步。再加上会被剧情旗标移除的 58 人(大多是反派组织成员、劲敌和有名字的角色,其中 54 人站着),比例就成了 158 人中 113 人(71.5%)、43 人(27.2%)和 2 人。游荡者用 16 帧走一步,然后等待 32、64、96 或 128 帧,所以它处于移动状态的时间最多只占 11% 到 33%。1 一天的事务不是靠一个在无人游玩时也在走的时钟推进,而是在玩家回来时,由一个函数带着经过的天数运行一次来结算。23 没有别人在场时,经典作品用他人的痕迹填满小镇,这些痕迹装在小小的固定槽位里(《绿宝石》的混合记录会把 20 个秘密基地、5 条流行语句和另一位玩家的老爷爷复制进您的游戏),并让陌生人只共享存在感、不共享言语。45 我测量这一切,是因为我自己的小镇 Kiradex World 也必须在它第一个安静的早晨回答同一个问题;而用模拟时钟驱动它服务器自身的代码后,答案在三点上都不理想:四位收藏者在房间空了之后就停下;它们每秒才走一步,而配置值是 0.7 秒,并且每段行走有 75% 的时间站着不动;在一个依据应用自身绘制代码建立的模型里,当一条移动消息在某一步的中途到达时,另一位收藏者会被画成向后跳:跑步时 64 次移动中有 45 次如此,40 格行走在 30 毫秒和 80 毫秒网络抖动下分别有 7 帧和 16 帧如此,但在没有抖动的匀速行走中从不发生。67 本文依次讲述实测的经典作品、现代参照、带数字的实时存在感、儿童可能进入的世界所需遵守的规则,以及简报。
TL;DR
- 小镇里多数人是站着的。 《绿宝石》城镇中没有隐藏旗标的 100 人里,十分之六的默认移动类型是静止型;把剧情可移除的 58 人算进来,158 人中则有十分之七(剧情脚本仍可以让站着的人走动,比如橙华市道馆的男孩会领着玩家走到道馆)。其余几乎所有人都以玩家自己的步速在一两格的范围内游荡,每格 16 帧(268 毫秒),每步之间等待 0.54 到 2.14 秒。未白镇有 6 人,橙华市有 7 人,紫堇市有 10 人,其中没有隐藏旗标的分别是 2 人、3 人和 6 人。18
- 日程是几个带时刻的点加一把键;记忆是每天一个旗标。 《星露谷物语》把村民的一天写成一串带时刻的点(Pierre 的普通一天有 7 个),并从分成三组的 22 种键格式中选出当天适用的那份,先匹配者胜出:先检查一个对所有人都适用的特殊键,然后已婚村民只用 5 种婚姻格式,其他所有人用 16 种普通格式;每天和村民说一次话可获得 +20 好感度。《集合啦!动物森友会》中,当天第一次对话在 255 点里加 1 点,并且每天教会一个反应(Reaction)。29101112
- 他人的痕迹填满空荡的小镇,它们装在固定槽位里,并会消退。 《绿宝石》的混合记录携带另一位玩家世界的 5,188 字节;在联合大道(Join Avenue)上,真实访客留空的店铺槽位由粉丝以访客 75% 的价值来填补(据 Serebii 的描述);Death Stranding 的建筑会 “destroyed by Timefall after some time”(一段时间后被时间雨摧毁);Journey 一次只让您遇见一位陌生人,直到片尾字幕才显示名字,唯一的交流是一声鸣响。413145
- 实时存在感是缓冲问题,而不是发送频率问题。 爱好者制作的 Habbo 服务器模拟器以 500 毫秒为周期推进每个房间;Gaffer on Games 的缓冲是 “3X the packet send rate”(发包间隔的 3 倍);在本系列每秒 3.75 格和 7.5 格的速度下为 Kiradex 的行走建模,用比估计服务器时间晚 350 毫秒的时钟来绘制其他收藏者,在最高 80 毫秒抖动下倒退帧为 0、断档帧为 0;而 100 毫秒时,在发送方仍在发送期间绘制的 600 帧中,有 125 到 456 帧没有数据可画。15167
- 儿童规则都带着日期。 Game Center 将儿童限制为 “to sending and receiving preset messages”(只能收发预设消息);Apple 的文档说,当 Screen Time 限制多人游戏时,带有自定义多人功能的游戏 “should disable it”(应当将其停用);修订后的 COPPA 规则自 2026 年 4 月 22 日起要求合规,要求一份写明删除期限的书面数据保留政策。171819
- 今天的 Kiradex 与它自己的计划有四处不同。 虽然
server/app/npcs.py写着 “A town is never empty”(小镇从不空荡),广场上的收藏者在房间无人时却会停下;配置是 0.7 秒,它们却每 1.0 秒才走一步;计划的推理写的是 8 到 10 Hz,应用却是每格发送一次移动(行走每秒 4 次,跑步每秒 6.0 到 6.4 次);计划把身份与家长控制都建立在 Game Center 之上,却没有任何代码读取 Game Center 的限制旗标。62021222324 - 这给 Kiradex 带来什么。 一份关于八位镇民的简报:日程以应用自己的时段为键,每个房间只用一个时钟,位置由这个时钟计算;最近八位访客的行走以服务器挑选的素色形象重放为无名的回声,保留七天,发送时不带任何 ID;每天一次的问候永不衰减;350 毫秒的渲染缓冲;一本分六个分支、每支 8 到 12 句完整句子的台词簿;以及七项安全设置。每一项都附有可由脚本、测试或录屏判定通过与否的检查。7119
1. 实测镇民:《绿宝石》、《星露谷物语》与《动物森友会》
每个小型社交世界在第一晚都会遇到同一个问题:玩家走进广场,却没有别人在,这时他会看到什么?经典作品给出了三个答案,本文依次展开。第一个答案是过着自己生活的镇民,这样无论有没有人在线,小镇都热闹。第二个答案是他人的痕迹,由此刻不在场的玩家留下。第三个答案是有人在时做好的实时存在感,并受到儿童可能进入的世界所需遵守的规则约束。第一个答案最古老,也测量得最充分,因为其中一位大师的源代码是公开的。
《绿宝石》的人在做什么
《宝可梦 绿宝石》读自 pret 的反编译(pokeemerald 提交 731ad5b 的浅克隆),并用脚本 measure_npcs.py 计数,它读取移动类型常量和每张城镇地图的对象事件。1 《绿宝石》定义了从 0x00 到 0x50 共 81 种移动类型。按画面上的人物做什么来归类:24 种沿四段固定环线行走,17 种站立并朝向某处(固定、交替或四处张望),16 种原地走、小跑或奔跑,8 种模仿玩家,5 种在范围内随机游荡,4 种来回踱步,4 种隐藏或伪装;最后三种是 NONE、玩家,以及树果树,而树果树是物体,不是人。125
城镇只用到其中很少几种。16 张城镇地图共有 172 个对象事件,并非都是人:6 个是道具球,2 个是搬家卡车,1 个是船,5 个是生物。脚本按 graphics_id 分类,单独统计其中的 158 个人。18 81 种移动类型中,只有 11 种出现在人物身上。158 人中 113 人(71.5%)站立并朝向某处,43 人(27.2%)在范围内游荡,2 人(1.3%)原地踏步。最常见的单一类型是 FACE_DOWN 32 人、FACE_RIGHT 23 人、LOOK_AROUND 20 人、FACE_UP 19 人和 WANDER_AROUND 17 人。18
这 158 人并不都是居民。其中 58 人带有隐藏旗标,也就是一个会把他们从地图上移除的剧情旗标;58 人中有 41 人使用反派组织、劲敌或有名字角色的 sprite,所以这 58 人大多是剧情场景里的演员,其中 54 人站着。没有隐藏旗标的 100 人,也就是每次造访都在地图上的人,比例则不同:59 人站立并朝向某处,39 人在范围内游荡,2 人原地踏步。所以玩家大多数时候看到的居民是十分之六站着、近十分之四游荡;全体十分之七的比例,来自加上几乎都站着的演员。这些是默认移动类型,即没有脚本驱动时这个人物做的事;剧情脚本仍可以让站着的人走动,比如橙华市的道馆男孩,默认四处张望、也没有隐藏旗标,却会领着玩家走到道馆。这个计数描述的是静止状态下的小镇,而不是小镇里的每一个场景。18
《绿宝石》地图上常驻的镇民里,十分之六的默认移动类型是静止型,算上剧情中可移除的演员则是十分之七;游荡者只在一两格的范围里活动。可以驱使任何人走动的脚本场景不计在内。26
三座规模不同的城镇呈现出同样的形态。表中只统计人;被排除的对象事件是未白镇的两辆卡车和三个道具球(橙华市两个,紫堇市一个)。最后一列按行为统计没有隐藏旗标的人。1
| 城镇 | 人数 | 站立朝向 | 在范围内游荡 | 游荡范围 (x, y) | 带隐藏旗标 | 无隐藏旗标(站立,游荡) |
|---|---|---|---|---|---|---|
| 未白镇 | 6 | 3(朝上) | 3(WANDER_AROUND) |
(1, 2), (2, 1), (2, 1) | 4 | 2 (0, 2) |
| 橙华市 | 7 | 5(张望 2,朝上 2,朝下 1) | 2(四处 1,上下 1) | (1, 1), (0, 1) | 4 | 3 (2, 1) |
| 紫堇市 | 10 | 8(张望 2,朝上 2,朝左 2,朝右 1,朝下 1) | 2(左右) | (1, 1), (1, 0) | 4 | 6 (4, 2) |
游荡者的范围是逐格强制的:迈步之前,IsCoordOutsideObjectEventMovementRange 会拒绝任何超出该对象初始位置范围的目标,所以游荡者是绕着自己的家格打转,而不是四处远行。27 隐藏旗标是人口构成的另一半。未白镇 6 人中的 4 人、橙华市 7 人中的 4 人、紫堇市 10 人中的 4 人都带有一个旗标,剧情推进后就会把他们移除,所以小镇的人口是玩家进度的函数:人们离开或到来,是因为发生了什么事。1
他们多久动一次
时序也在同一个文件里。游荡或张望的镇民在下一步或下一次转身之前,会从 sMovementDelaysMedium 中随机选取 32、64、96 或 128 帧来等待:在 Game Boy Advance 的 59.7275 Hz 下是 0.54、1.07、1.61 或 2.14 秒,平均 1.34 秒。在两个相反方向之间交替朝向的两种类型 FACE_DOWN_AND_UP 和 FACE_LEFT_AND_RIGHT 也是如此。在两个相邻方向之间交替、或在三个方向之间朝向的类型则从 sMovementDelaysShort 中取 32、48、64 或 80 帧,平均 0.94 秒;两种旋转类型固定等待 48 帧。第三张表 sMovementDelaysLong 在源码中标着 // Unused,没有任何移动类型读取它。127
迈步本身与玩家相同。PlayerWalkNormal 调用 GetWalkNormalMovementAction,与游荡者迈步用的是同一个动作;MOVE_SPEED_NORMAL 对应 sStep1Funcs,16 帧内每帧移动 1 像素,268 毫秒走完一个 16 像素的格,每秒 3.73 格。跑步是 MOVE_SPEED_FAST_1,即 sStep2Funcs,每格 8 帧、134 毫秒,每秒 7.47 格。127 把这两行放在一起看,游荡者在“一步加一次等待”的 0.81 到 2.41 秒里只有 268 毫秒在移动:按每迈出的一步计,占时间的 11% 到 33%。这是上限。当游荡者选的方向被墙或范围边缘挡住时,MovementType_WanderAround_Step4 会让它不动、转向并重新等待,所以它真正处于移动的时间比例更低。其余时间它站着,或者转身。127
《绿宝石》的游荡者在漫长的等待之间一次只走一步;第 6 节测量的 Kiradex 收藏者在本该行走的时候,以一格长的短促爆发移动。26
在我看来,效果来自三个细节。镇上没有什么东西比您走得更快或更慢,所以没有什么显得格格不入。等待比迈步更长,所以小镇是一幅幅静止画面被偶尔的动作打断,这也是为什么即使有 6 到 10 人也从不显得拥挤。而人口通过隐藏旗标写进了剧情,所以小镇的变化是因为玩家做了什么,而不是因为某个计时器触发。
《绿宝石》的一天在到达时结算
《绿宝石》会记日子,但不是用一个在无人游玩时也在走的时钟。它在 DAILY_FLAGS_START 之后预留了 64 个每日旗标槽位,用了其中 12 个:九份树果礼物(华丽大赛大厅的、树果名人和他妻子的、五位道路或城镇赠送者的、花店的)、秘密基地的每日旗标、彩票,以及 FLAG_DAILY_APPRENTICE_LEAVES。228 玩家回来时,UpdatePerDay 会带着经过的天数运行一次,游玩期间它也会从 DoTimeBasedEvents 运行;它依次调用 ClearDailyFlags、UpdateDewfordTrendPerDay、UpdateTVShowsPerDay、UpdateWeatherPerDay、UpdatePartyPokerusTime、UpdateMirageRnd、UpdateBirchState、UpdateFrontierManiac、UpdateFrontierGambler、SetShoalItemFlag 和 SetRandomLotteryNumber。23 流行、电视和天气都按回来那一刻算出的经过天数来推进。3 卡带关机时什么都没发生;小镇却表现得仿佛发生过。
《星露谷物语》:日程就是一个字符串
在拥有生活的镇民方面,现代的参照是《星露谷物语》。它的 wiki 的 Villagers 页面列出 6 位单身汉、6 位单身女性、22 位不可结婚的村民和 12 位不能送礼的村民,因此可送礼的村民共 34 位,并写道 “Each villager has a daily routine, so they can be located in different sections of town depending on the in-game time of the day and weather.”(每位村民都有日常作息,因此会依游戏内的时刻和天气出现在镇上的不同区域。)229
日程是数据。每位村民在 Content/Characters/schedules/ 下有一个文件,每个条目是一串以斜杠分隔的点,每个点的形式是 <time> [location] <tileX> <tileY> [facing] [animation] [dialogue],时间为不带冒号的 24 小时制,朝向 0 为上、1 为右、2 为下、3 为左。9 页面给出的 Abigail 的星期三是 1000 ArchaeologyHouse 11 9 0/1800 Town 47 87 0/2200 SeedShop 1 9 3 abigail_sleep:三个点,各点之间步行,最后一个点有一段动画。9 经营杂货店的 Pierre 在普通一天有 7 个带时刻的点(早上 6:00 在柜台,7:00 在货架间,8:30 回到柜台,然后下午 5:00、7:00、9:00 和 11:00 依次经过货架、厨房、书架和床),他的页面列出了日程的 6 种变体:绿雨、春季 15 日修好的巴士、沙漠节、雨天、星期五和普通的一天。230
聪明之处在于键。某一天运行哪份日程,由分成三组的 22 种键格式决定,每组按固定顺序尝试,先匹配者胜出。特殊键 GreenRain 只有一个,“checked first, regardless of marriage status.”(无论婚姻状况都最先检查。)已婚村民接下来只尝试 5 种婚姻格式,从 marriage_<festivalID> 到日期、marriageJob 和星期几:“Married NPCs don’t use any other schedule keys. If the marriage keys don’t match, they won’t have a schedule for that day.”(已婚 NPC 不使用任何其他日程键。如果婚姻键不匹配,他们当天就没有日程。)其他所有村民尝试 16 种普通格式,从 <festivalID>、<season>_<dayofmonth>、<dayofmonth>_<hearts>、<dayofmonth>、bus、rain2(“50% chance of applying on rainy days”,雨天有 50% 的几率适用)和 rain 开始,然后是星期几加心数的键和星期几,直到 <season>、spring,最后是 default。29 所以在我看来,一位村民有许多种可能的一天,同样的几个点重新组合成一种生活,而心数键意味着喜欢您的村民会过不一样的一天。页面关于局限的说明道出了一天是如何计算的:当 NPC 被加入一个已有的存档时,“they generally don’t follow their schedule correctly until you’ve slept once in-game (which triggers their first day update).”(在您于游戏中睡一觉之前,他们通常不会正确遵循日程,因为睡觉会触发他们的第一次每日更新。)9 与《绿宝石》一样,星露谷的一天是在日界处算出的,而不是连续推进的。
好感度是每天的记忆,而且有代价。一颗心是 250 点;wiki 上提升好感度方法列表的第一项是 “talking to them once per day (+20)”(每天和他们说一次话,+20);村民每天收一份礼物、每周两份,生日礼物的效果乘以 8;不说话则每天扣分,大多数村民扣 2,送过花束之后扣 10,直到计量条满了或达到上限为止,配偶则扣 20,且这种衰减永不停止。10 心事件,即编好剧本的场景,在达到心数门槛时开启,并且 “most events can be viewed anytime (with some time restrictions) or out of order.”(大多数事件可以随时观看(有一些时间限制),也可以不按顺序观看。)10 村民在您不在时也会对您有所行动:3 颗心时 Pierre “will send you a recipe in the mail”(会给您寄一份食谱),而只要心数高于零,他就可能寄来 250g 或更多。30
《动物森友会》:知道您来过的村民
《集合啦!动物森友会》把“知道您今天和他说过话”的村民压缩成了一个点。好感度从 0 到 255,起始为 25;Nookipedia 列出的好感度提升方式中,第一项是 “speaking to the villager for the first time each day”(每天第一次和村民说话),“+1 point”;村民每天收一份礼物;在 150 点时,村民收到礼物后有 6% 的几率回赠照片,每点增加 0.04%,到 255 点时为 10.2%。11 在我看来,这是一个旗标而不是计量条,因为玩家从来看不到它变动。
村民看得见的生活很小,而且只属于他们自己。每位村民有六种爱好之一(教育、时尚、健身、音乐、自然、玩耍),在《集合啦!动物森友会》中 “a villager always has one hobby that doesn’t change”(村民总有一个不变的爱好);村民会追虫子和鱼,但 “still will not catch either of them”(仍然一样也不会抓到),会运动、看书、跟着附近的音乐播放器唱歌,而自 2.0 更新起,他们会 “invite the player to their house, ask the player for an invitation”(邀请玩家到家里,或请玩家邀请自己),或者来一次 “a random visit”(随机拜访)。3132 八种性格塑造他们的话语:男性村民有懒散(lazy)、运动(jock)、暴躁(cranky)和自恋(smug)四种,女性村民有普通(normal)、元气(peppy)、成熟(snooty)和大姐姐(big sister)四种,其中 “Smug and big sister were introduced in New Leaf, with the other six being present since Doubutsu no Mori.”(自恋和大姐姐是在 New Leaf 中引入的,其余六种自初代《动物森林》起就已存在。)33
整个系列里,小镇一直都很小。32
| 游戏 | 开局村民数 | 村民上限 |
|---|---|---|
| Animal Crossing | 6 | 15 |
| Wild World | 3 | 8 |
| City Folk | 6 | 10 |
| New Leaf | 5 | 10 |
| 集合啦!动物森友会 | 2(从 83 位候选中选出运动和大姐姐各一位) | 10 |
Wild World 还改变了节奏:在那一作里 “the villagers walk at a much slower pace than the player”(村民走路比玩家慢得多),City Folk 沿用了这一点。32 这与《绿宝石》的规则恰恰相反,但在我看来两者之所以都奏效,原因相同:慢悠悠踱步的村民看起来像居民,以您的步速迈一步再等待的镇民看起来也像居民。破坏效果的,是两种步速都不是的人物,而这正是第 6 节里 Kiradex 的问题。
这个系列的预设表达是反应(Reaction),而它本身就是镇民按每日配给赠送的礼物。《集合啦!动物森友会》在 1.0.0 时有 44 个反应,到 2.0 时又增加 44 个,共 88 个,并且 “The player can learn one Reaction per day”(玩家每天可以学会一个反应),其中一些只能从特定性格的村民那里、或在好感度高时学到。212
2. 他人的痕迹:无人在线时填满小镇的东西
镇民让小镇热闹,但每天都是同样的面孔。第二个答案,是每一款研究过的游戏在担心世界空荡时都会伸手去拿的东西:用真实之人的痕迹填满世界,这些痕迹由此刻无需在线的玩家留下。《绿宝石》靠一根通信线做到了这一点。
混合记录:装在固定槽位里的另一位玩家的世界
两位《绿宝石》玩家进行混合记录(record mixing)时,每个游戏都会向对方发送一个固定结构 PlayerRecordEmerald,由 11 种记录组成,共 0x1444 字节,即 5,188 字节。434 对“别人来过这里”的感觉而言,重要的数字是槽位:20 个秘密基地、25 个电视槽位(5 个普通加 20 个额外)、16 条新闻、5 条流行语句,以及游戏保存的 4 位徒弟中的 2 位。4 在我看来,这就是槽位的意义:朋友的基地、他们的节目和他们的语句被带进您的游戏,您的小镇因为别人玩过而改变了。
最干净的例子是单独一个人。紫堇市的宝可梦中心里有一位老爷爷(Old Man),他是五人之一(吟游诗人(Bard)、潮人(Hipster)、交易者(Trader)、说书人(Storyteller)和兴奋的人(Giddy))。新游戏分到哪一位由 (trainerId % 10) / 2 决定,源码自己的注释是 “Determine man based on the last digit of the player’s trainer ID”(根据玩家训练家 ID 的末位数字决定是哪个人),而混合记录时,ReceiveOldManData 会把对方的老爷爷复制过来覆盖您的。435 您镇上的居民,字面意义上就是另一位玩家的镇民。
潮人按接触来配给词汇。他的脚本会设置 FLAG_UNLOCKED_TRENDY_SAYINGS,并在 taughtWord 为 false 时教一个词,然后把它设为 true;唯一一处把它重新设为 false 的调用,即经由 ResetMauvilleOldManFlag 的 ResetHipsterFlag,只在一个地方发生:record_mixing.c 中 ReceiveOldManData 的末尾。35 所以流行语(Trendy Saying)在每次混合记录带来潮人时解锁一次,此外开局就有他的玩家还会再解锁一次;按选择规则,这类玩家的训练家 ID 末位是 2 或 3。本系列的第一篇文章对这条规则的表述也是如此。3635 词汇靠与人相遇而增长,而不是靠等待。
用算术看流行
流行语句是武斗镇视为当下流行说法的两个简易会话(Easy Chat)词,也是《绿宝石》唯一会打分的痕迹。它的源码注释说,一条 “boring”(无聊)的语句会 “lose trendiness over time until it reaches 0, at which point it will stop being boring and gain trendiness until it reaches maxTrendiness (then it becomes boring again and the cycle repeats).”(随时间失去流行度直到 0,此时它不再无聊并开始获得流行度,直到 maxTrendiness;然后再次变得无聊,循环往复。)37 一条语句的峰值由至多三次嵌套的 Random() % 98 抽取决定,介于 30 到 127 之间。若把每次抽取视为在其 98 个取值上独立且均匀,峰值的平均值为 62.9,且 81.3% 的峰值不超过 80;起始分数在 30 与峰值之间均匀分布,平均 46.5;分数每经过一天移动 5,与其他一切一样在 UpdatePerDay 中结算。383 这些数字是对代码的近似,而不是代码的精确输出:Random() 返回 16 位,所以 0 到 71 的余数在 65,536 次中出现 669 次,72 到 97 的余数出现 668 次;而且连续的抽取来自同一个生成器,并非独立的骰子。按这种方式给余数加权(仍假设抽取独立),两个数字在小数点后一位都不变。3837 若以连续速率来看,峰值 30 的语句从零升到峰值再落回需要 12 天,峰值 64 需要 25.6 天,峰值 127 需要 50.8 天。游戏以整天为单位移动分数,而会越过峰值或零的分数会按余量反弹,所以每日分数并不按这些小数周期重复:从零开始上升,逐日移植计算,精确序列首次重复是在 12、128 和 254 天之后。3837 最多保存 5 条流行,混合时 “their own trends are replaced with their mixing partner’s, unless the phrase is the same, in which case the version with a higher trendiness value is used.”(自己的流行会被混合对象的流行替换,除非语句相同,此时采用流行度较高的版本。)3837
流行是一条每天采样一次的缓慢锯齿:81.3% 的峰值不超过 80,一次升降约 32 天或更短。2638
在我看来,对于一种必须让小镇不至于陈旧的痕迹,这是正确的形态:它会上升,会下降,寿命在诞生时就已确定,而与另一位玩家的接触可以把它换成更新鲜的东西。
简易会话与联合房间
《绿宝石》有两种让玩家彼此说话的方式,它们正是本文安全讨论的两端。联合房间(Union Room)是本地无线大厅,显示 8 位小组领队,每个无线小组容纳 5 人(RFU_CHILD_MAX 的 4 再加 1),绘制 40 个 sprite,定义 30 个活动代码。它的聊天是键盘输入:四页键盘(UPPER、LOWER、EMOJI、REGISTER),每条消息 15 个字符(MAX_MESSAGE_LENGTH),存档中保存 10 条登记短语(UNION_ROOM_KB_ROW_COUNT)。439 我的理解是,键盘聊天在那里之所以可以接受,是因为房间里的每个人都在您的无线范围之内。
玩家之间说的其他一切都要经过简易会话。它有 22 个分组,共 1,815 个条目。一开始就开放的有:训练家(27,其中 6 个停用)、状态(109)、对战(63)、问候(42)、人物(75)、声音(63)、言谈(60)、语尾(69)、心情(69)、情况(69)、行动(78)、生活(45)、爱好(54)、时间(45)、其他(42)和形容词(36),外加一份 202 个条目的名字列表,其中每个名字只有在玩家见过该物种后才开放;锁住的有通关前的事件(29)和两份招式列表(154 和 200)、潮人旗标出现前的流行语(33),以及一份 251 个条目的全国名字列表。3840 去掉训练家组中任何玩家都选不到的 6 个停用条目,从第一个小时起就开放的词共有 940 个,不计名字。easy_chat.c 中的一个 switch 决定哪些分组解锁,另一个判断决定每个条目:名字看其物种是否见过,其他词看其 enabled 字段。3840 语句是固定的格子:个人资料是 2×2,4 格;对战开场白 2×3,6 格;邮件 2×5,10 格,最多容纳 9 个词;流行语句和好话 2×1;问卷 2×2,4 个词。3840
一个由 940 个词组成、可以自由组合的词汇表很有表现力,但在我看来,它也是一个有心的孩子能从中拼出东西的词汇表。这正是第 7 节 Kiradex 简报所拒绝的取舍:完整的句子,绝不是单词。
其他游戏里陌生人的痕迹
其他每一款研究过的、担心世界空荡的游戏,都伸手去找了其他玩家,其中多数是去找玩家留下的痕迹。来源的分量各不相同,每段都注明了自己的来源。
Dark Souls(FromSoftware,2011 年)。 Wikipedia 引用其自身来源写道:“The player can see ghostly images of other players, activate bloodstains that show how other players died, and leave messages using preset phrases.”(玩家可以看到其他玩家幽灵般的影像,激活显示其他玩家如何死去的血迹,并用预设短语留言。)41 The Daily Telegraph 授予它 “Best Integration of Online Features”(最佳在线功能整合)奖。41 Bandai Namco Europe 的复刻版页面在主要特色中列出 “The Way of the Multiplayer (up to 6 players with dedicated servers)”(多人之道,专用服务器最多 6 人),但对幽灵与留言这一层再无着墨;FromSoftware 自己的说明未能获取。42 该页面没有说明幽灵般的影像是在重放已经离开的玩家,还是在显示此刻正连接着的玩家;Wikipedia 把它们与血迹、留言一起列为游戏 “integrates”(整合)进 “the single-player world”(单人世界)的 “online features”(在线功能),并把 “Direct multiplayer”(直接多人),即召唤与入侵,放在下一句。41
Journey(thatgamecompany,2012 年)。 同样是 Wikipedia:“In each level, the player may come across one other player temporarily connected to their game”(在每个关卡中,玩家可能遇到一位暂时连接到其游戏的其他玩家);两人 “cannot communicate via speech or text and cannot see each other’s names until after the game’s credits”(无法通过语音或文字交流,在片尾字幕之后才能看到彼此的名字);“The only form of communication between the two is a musical chime.”(两人之间唯一的交流方式是一声音乐般的鸣响。)开发者 “felt having text or voice communication or showing usernames would allow players’ biases and preconceptions to come between them and the other player.”(认为文字或语音交流、或显示用户名,会让玩家的偏见与先入之见横亘在他们与对方之间。)5 这是以设计形式呈现的安全论证,而且是为成年人做的。
Death Stranding(Kojima Productions,2019 年)。 Sony 的页面写道:“Donate valuable resources to rebuild structures in your world and others’, and offer likes in support of player structures that appear in yours.”(捐出宝贵资源,重建您和他人世界中的建筑,并为出现在您世界中的玩家建筑点赞支持。)43 Wikipedia 补充说,玩家 “can leave supplies, structures, and messages that can be viewed and used by other players, although structures will eventually be destroyed by Timefall after some time”(可以留下其他玩家能看到和使用的物资、建筑与留言,不过建筑在一段时间后终将被时间雨摧毁),以及 “The player does not directly encounter other players in the world.”(玩家不会在世界中直接遇见其他玩家。)14 痕迹会消退,所以世界不会被淤塞。
Splatoon(Nintendo,2015 年)。 据 Wikipedia 引用其来源所述,玩家的 Miiverse 帖子会 “appear in-game as graffiti on various buildings”(以涂鸦的形式出现在游戏中的各种建筑上)。涂鸦来自玩家发到该游戏 Miiverse 社区的帖子,所以在我看来,这种痕迹依赖的是 Nintendo 的 Miiverse 服务,而不是游戏自身的联网游玩。44
后面三项宝可梦的功能只依赖一个来源,即长期运营的粉丝网站 Serebii;pokemon.com 上《宝可梦 黑2·白2》和《宝可梦 太阳·月亮》的页面都没有提到联合大道或节庆广场,也没有保存关于 GO 道馆的发行商页面,所以接下来的三段都请当作 Serebii 的描述来读,而不是发行商的。45
联合大道(Join Avenue,《宝可梦 黑2·白2》,2012 年)。 据 Serebii 所述,这条大道 “starts off as an empty pathway”(起初是一条空荡荡的小路),人们会来开店,而这 “is done automatically whenever you connect with someone”(每当您与某人连接时就会自动发生),途径包括擦肩通信、红外线、联合房间、Wi-Fi、GTS 等。共有八家店;“every 10th person shown to the shop, the shop will rank up”(每带第 10 个人到店,店铺就会升一级),最高到 10 级,“discounts of 1% for every rank up”(每升一级打折 1%),直到 “a 40% discount”(40% 的折扣,即六折);普通访客值 “from 100 to 200 points”(100 到 200 分);击败四天王之后,“various fans will come and visit”(各种粉丝会来造访),而 “If you help a fan rather than another player, they will give 75% of the normal popularity points.”(如果您帮助的是粉丝而不是其他玩家,他们会给出正常人气分的 75%。)13 在我看来,这是经典作品中对后备规则最干净的表述:替身填补真人留空的槽位,而价值稍低一些。
节庆广场(Festival Plaza,《宝可梦 太阳·月亮》,2016 年)。 据 Serebii 所述,广场 “will propogate itself with players you will find online and locally”(会用您在线上和本地找到的玩家来填充自己,原文如此),并且 “This is not limited to people on your friend list”(这不限于您好友列表上的人);有所求的客人会说出来,您帮忙后他们会付节庆币,被帮助过的客人可以设为 VIP,这会让他们 “more likely to appear in your plaza in the future”(今后更可能出现在您的广场);广场等级在 1 级时花费 6 枚币,从 101 级起涨到 300。46 这是通过化身实现的存在感:您互动的是另一位玩家的化身,而 Serebii 把化身背后的人描述为 “all players that are also playing online”(同样在线游玩的所有玩家),他们 “when they see you”(看到您时)可以 “challenge you to battles or request trades”(向您发起对战或请求交换)。按这一描述,广场上的人物代表本身也在线的玩家;它与 Journey 一起,是这份列表中来源描述为实时存在感而非痕迹的两项功能之一。46
Pokémon GO 道馆(2017 年改版)。 据 Serebii 所述,一座道馆最多容纳六只防守者;每输一场对战,防守者的干劲就会下降,“Once their Motivation has dropped to 0, then they are removed from the Gym”(一旦干劲降到 0,就会被移出道馆);树果可以恢复干劲;防守者每 10 分钟赚 1 枚金币,“capped at 50 Coins earned a day.”(每天最多赚 50 枚金币。)47 这个地方在另一位玩家不在时继续展示他的伙伴,而如果社区不去喂它,它就会消退。
在我看来,这八项共有的模式是:少量固定槽位(8 家店、6 只防守者、1 个伙伴、20 个基地),会消退或轮换的痕迹(时间雨、干劲、流行的周期),价值低于真人的替身,以及除节庆广场的对战与交换请求之外,陌生人之间除了一句预设短语或一声鸣响,没有任何实时通道。
3. 实时存在感:频率、缓冲与擦身而过的行走
第三个答案是人们最先想到的那个:当其他玩家在线时,就把他们显示出来,在同一个广场里走动。两个小型 2D 社交世界 Habbo 和 Club Penguin 展示了网格存在感的两端。两家公司都没有公布过它们的频率。我手头的是两个开源的爱好者服务器和一个爱好者客户端,它们都是为兼容原版客户端而写的,所以下面的数字是那些客户端预期什么的证据,而不是 Sulake 或 Disney 公布的数据。154849
Habbo:每个服务器周期走一步
Arcturus Morningstar 是 Habbo 服务器的开源重写版,它以固定的 500 毫秒周期运行每个房间(scheduleAtFixedRate(this, 500, 500, TimeUnit.MILLISECONDS)),并在每个房间周期对每个行走中的单位调用一次 cycle;它在 IDLE_CYCLES = 240,即 120 秒时把化身标为闲置,并在 IDLE_CYCLES_KICK = 480,即 240 秒时移除非房主。15 Nitro 是开源的 Habbo 客户端,它以固定 500 毫秒的 DEFAULT_UPDATE_INTERVAL 移动房间物体:每收到服务器的一次更新,它取到新位置的变化量,按经过时间除以 500 做线性插值,在终点处钳制,然后把变化量清零。49 一个周期恰好是一格,这是我把两者放在一起的理解(500 毫秒的服务器周期用 500 毫秒的插值来画);我没有读服务器的 RoomUnit 来确认。如果这个理解成立,那么渲染在结构上就比服务器晚一个周期,所以只要服务器跟得上,它就不会断档。
Club Penguin:是目的地,不是数据流
Houdini 是 Solero 项目的开源 Club Penguin 服务器,展示了另一种设计。一次移动是一条携带目的地 x 和 y 的 sp 消息,原样转发给房间,由客户端自己走过去。一句安全聊天是一条携带一个数字的 ss 消息,其他种类的预设话语也以同样方式传送:笑话用 sj,吉祥物的消息用 sma,舞台台词用 sl,导游台词用 sg,表情动作用 se,每一种都以 ID 转发。48 服务器把表情动作和动作限制为每秒一次,把帧变化限制为每半秒一次;一个心跳任务每 61 秒检查一次已连接的客户端,关闭在这段时间里没有发送心跳的客户端。48 移动是点击目的地式的,不是逐格的;话语是一个在对方那边查表的数字。在我看来,这就是为什么单词过滤器从来不必看到大部分说出的话。
这两个世界都没有像下面的物理演示那样每秒发送 10 到 60 次更新。16 在人们一格一格行走的世界里,一步或一个目的地就是更新本身。
Gaffer on Games:延后渲染,在两个已知点之间绘制
主导客户端一侧的方法,见于 Glenn Fiedler 的 “Snapshot Interpolation”(Gaffer on Games,2014 年 11 月 30 日)。把到达的数据缓冲起来,在两个延迟的更新之间绘制:“In effect, we’ve traded a small amount of added latency for smoothness.”(实际上,我们用少量额外延迟换来了平滑。)16 他对延迟的经验法则是 “enough delay so that I can lose two packets in a row and still have something to interpolate towards”(足够的延迟,使我连丢两个包后仍有可以插值的目标);在 2% 到 5% 的丢包率下,效果最好的是 “is 3X the packet send rate. At 10 packets per-second this is 300ms”(发包间隔的 3 倍。每秒 10 个包时就是 300ms),再加一两帧应对抖动,所以他的演示以 350 毫秒运行。在 “30 snapshots per-second”(每秒 30 个快照)时,同样的保护需要 150 毫秒,而 “60 packets per-second needs only 85ms.”(每秒 60 个包只需 85ms。)16 外推,即向前猜测,“doesn’t work very well for rigid bodies because their motion is non-linear and unpredictable.”(对刚体效果不太好,因为它们的运动是非线性且不可预测的。)16
Kiradex 的广场通过 WebSocket 连接到一台 FastAPI 服务器,23 而 WebSocket 跑在 TCP 上,TCP 不会丢失任何东西,但重传的包会晚到。在我看来,这使 WebSocket 世界的预算成了抖动而非丢包,也使网格上的行走者成为 Fiedler 方法的简单情形:网格上的行走者在图块中心之间直线移动、戛然而止,所以既没有什么可外推,也没有非线性运动需要掩盖。
擦身而过的行走模型
为了确定 Kiradex 的缓冲大小,我用 10 月 5 日读到的应用自身 rig 的逻辑,模拟了另一位收藏者从旁走过。脚本 measure_remote_walk.py 模拟两件事。一是 rig 按自身算术的现行行为:一步的 progress 是 32 位 Float,每帧增加 Float(deltaTime) 乘以每秒 4 格;每条到来的移动消息都会从行走者最后所在的整格重新规划路径,并把进行中的这一步重置为零,哪怕正走到一半;长于 6 格的路径是一次跳跃;行走者画在整像素上。rig 用行走的每秒 4 格来画其他所有收藏者,因为只有玩家自己的行走才会被标为跑步。二是替代方案:在一个稳定的渲染时钟上做快照插值,该时钟为估计的服务器时间减去 100 到 350 毫秒的延迟,在时间戳夹住渲染时钟的两个已收到更新之间绘制。750 对于现行 rig,发送方按今天测得的频率每走完一格发送一次移动:以每秒 4 格行走时每 250 毫秒一次,以每秒 6.4 格跑步时每 156 毫秒一次,6.4 是跑步的名义速率(本系列的运动篇把应用在 60 Hz 下的跑步建模为每秒 6.0 格,因为每格的超出量被丢弃了)。对于缓冲,发送方还会以本系列的运动约定移动,即运动篇简报中 60 Hz 下行走每格 16 拍、跑步 8 拍,每秒 3.75 格和 7.5 格,这也是第 7 节所要建成的目标。网络加入 0、30 或 80 毫秒的均匀抖动,以每秒 60 帧绘制 10 秒钟的行走。72122
算术很关键。在 32 位下,15 帧 1/60 秒以每秒 4 格累加恰好等于 1.0,所以一步用 15 帧,正好是发送方的 250 毫秒;在 64 位算术里同样的和是 0.9999999999999999,一步要用 16 帧,按这种方式写的模型会在匀速行走中找出 rig 自身算术并不会产生的回弹。7
这是一个模型,而它的假设正是应把其数字当作尺寸参考而非结论的原因。它假设道路笔直开阔,所以路径长度是曼哈顿距离;在 rig 中耗时 √2 倍的斜向一步没有建模;抖动是均匀的;每一帧的 deltaTime 都恰好是 1/60 秒,而手机上会有波动;32 位运算按代码顺序执行,没有融合乘加;没有任何数据是从两台设备上采集的。所以,无抖动的匀速行走在手机上是否会回弹,是模型无法定论的问题:只比 1/60 偏一丝的帧时间就可能让一步差一次舍入而不到 1.0。只要移动消息在一步中途到达就会触发的重置本身,是对源码的阅读,而不是模型的结果。缓冲那一半还假设了两件其结果所依赖的事:渲染端对服务器时钟的估计是精确的;更新除抖动之外不需要额外时间到达。真实客户端必须估计服务器时钟,而这种估计的任何误差,或任何未计入的稳定单向延迟,都会直接从缓冲里扣掉。7
在模型中,当移动消息在一步中途到达时,现行 rig 会把经过的收藏者画成向后移动:每一次跑步都如此,任何带抖动的行走也如此;在运动约定的速度下,350 毫秒的缓冲从不如此,也从不断档。26
现行 rig 在无抖动行走时能把行走者画得干干净净:每一步都在下一条移动到达前一帧结束,所以 40 格中回弹为 0、跳跃为 0、倒退帧为 0。抖动打破了这一点。在 30 毫秒时,有 13 条移动在一步中途到达并引发重置,其中 7 条到得足够晚,使行走者被画成向后,另有 1 条路径长到触发跳跃;在 80 毫秒时,重置 25 次,倒退帧 16 个,跳跃 2 次。跑步时,发送方的速度超过了 rig 画它所用的每秒 4 格,所以在测试的每种抖动下,64 条移动中都有 45 条在一步中途到达,每条都画出一个倒退帧,跳跃 9 次。只要发生回弹,画出的行走者就会落后发送方最多 5.9 到 6.7 格,直到一次跳跃把它拉上来;在匀速行走中,它从未落后满一格(最多 0.94)。7 一次回弹会把行走者按这一步已经画出的距离向后拉,在这些运行中,16 像素的图块上最多 14 像素。750
有了缓冲,行走者就永远不会向后移动:全部 60 次带缓冲的运行(四种速度乘三种抖动乘五种延迟)倒退帧均为 0。问题只在于渲染端多常没有目标可画,统计范围是发送方仍在发送期间绘制的 600 帧。在运动约定的行走速度每秒 3.75 格下,落后 300 毫秒的渲染时钟在 0、30 和 80 毫秒抖动下断档 0、0 和 27 帧;325 毫秒断档 0、0 和 6 帧;350 毫秒一帧也不断档。落后 100 毫秒时,在这些速度的每种情形下都会断档 125 到 456 帧。以 7.5 跑步时,300 毫秒及以上在测试的每种抖动下都是 0 帧。在今天的每秒 4 格和 6.4 格下,同一模型在行走 300 毫秒时断档 0、0 和 7 帧,325 和 350 毫秒则为零。7 在我看来,比估计服务器时间晚 350 毫秒的渲染时钟就是答案,而理由是一个上界,而不是一个计数。每格一次移动时,一帧所需的更新,其时间戳至多比渲染时钟晚一步,并且至多在时间戳之后最坏抖动的时长内到达。在每秒 3.75 格时,一步是 1/3.75 秒,即 266.7 毫秒,一步加 80 毫秒抖动是 346.7 毫秒,所以在时钟精确的前提下,350 毫秒的延迟对 80 毫秒以内的任何一次抖动抽取都不会断档,还剩 3.3 毫秒余量。7 这 3 毫秒就是留给时钟估计的全部空间:估计比服务器时钟快出约 3 毫秒以上,就会失去保证,可能出现断档帧。上面的计数来自一次抖动抽取,即种子 1;在那次抽取上,325 毫秒以 4 帧之差达到了第 7.4 节设定的标准,但并非每次抽取都能达到:用种子 1 到 200 重跑,以每秒 3.75 格行走、80 毫秒抖动,325 毫秒在全部 200 次中都出现断档,其中 8 次超过 10 帧,而 350 毫秒在任何一次中都没有。7 350 毫秒也是 Fiedler 自己的演示所用的延迟。在每格一次移动的情况下,它比一个行走步(每秒 3.75 格时一步 267 毫秒)稍长,这正是把 Fiedler 的 “3X the packet send rate”(发包间隔的 3 倍)反过来,用于一个包即是步子、且在 TCP 上包只会迟到而不会丢失的世界。16
4. 技法,作为带数字的规则
下面每条规则都可以追溯到上文的某项测量或引用来源。凡是规则由我综合多个来源得出的,来源一列会写明是哪些。
| 要素 | 规则 | 来源 |
|---|---|---|
| 谁在动 | 常驻镇民中约十分之六的默认移动类型是静止型,守在原位朝向一方或四处张望;近十分之四在原位周围一两格的范围内游荡。来来去去的剧情演员大多站着,这会把整体比例推到十分之七和四分之一。 | 《绿宝石》中无隐藏旗标的 100 人:59 人站立,39 人游荡,2 人原地踏步;全部 158 人:113 人和 43 人;范围 1 或 2 格1 |
| 步速 | 镇民以玩家的步速迈步,然后等待比这一步更长的时间:一步 268 毫秒,然后随机等待 0.54、1.07、1.61 或 2.14 秒。更慢的踱步也可行(Wild World);两者都不是的步速则不行。 | 《绿宝石》共用的行走动作与延迟表1;Wild World32 |
| 移动占比 | 游荡者处于移动的时间按每迈出一步计最多占 11% 到 33%;被挡住的一步只会转向并等待。 | 《绿宝石》,基于上面两行的算术;为上限127 |
| 人口 | 六到十人就成一座小镇;《动物森友会》的整个村子最多也就 8 到 15 人。 | 未白镇 6 人,橙华市 7 人,紫堇市 10 人,其中无隐藏旗标的分别是 2、3、6 人1;《动物森友会》的城镇最多 8 到 15 人32 |
| 变化 | 部分镇民随玩家进度通过旗标离开或到来。 | 三座城镇的 23 人中有 12 人带隐藏旗标1 |
| 日程 | 一天是三到七个带时刻的点;由键选择适用哪一天,按具体程度的固定顺序尝试,先匹配者胜出。 | Abigail 的星期三 3 个点,Pierre 的普通一天 7 个;22 种键格式92 |
| 一天 | 在玩家回来时用一次带经过天数的运行结算一天;绝不在无人游玩时运行计时器。 | UpdatePerDay3;星露谷的第一次每日更新9 |
| 记忆 | 每位镇民用一个旗标记住当天第一次交谈;奖励保持很小。 | 《集合啦!动物森友会》中 255 点里的 +111;星露谷中相对每颗心 250 点的 +2010 |
| 新词 | 新的表达方式按每天一个、或每次相遇一个来配给。 | 每天一个反应12;每次潮人到来一个流行语35 |
| 痕迹 | 他人的痕迹放在少量固定槽位里,会消退或轮换。 | 20 个基地和 5 条流行4;8 家店13;6 只防守者47;时间雨14;流行的一次升降为 12 天到约 50.8 天38 |
| 替身 | 替身填补真人留空的槽位,价值较低。 | 联合大道的粉丝为 75%,据 Serebii 的描述13 |
| 陌生人 | 陌生人共享存在感,不共享言语:没有名字,只有一声鸣响或一句预设短语。 | Journey5;Dark Souls41;面向儿童的 Game Center17 |
| 话语 | 预设台词以 ID 传送,不仅能问候,也能应答;完整的句子拼不出单词表能拼出的任何东西。 | Club Penguin 的 ss ID48;简易会话开放的 940 个词,2 到 10 格的格子38 |
| 更新 | 网格上每一步一次更新;用比估计服务器时间晚略多于一个行走步的时钟来渲染远程行走者,绝不重置进行中的一步。 | Habbo 的 500 毫秒周期与插值(模拟器与爱好者客户端)1549;每秒 3.75 格和 7.5 格下的模型,假设时钟精确同步且无基础延迟:落后 350 毫秒时,在 80 毫秒以内的抖动下倒退帧为 0、断档帧为 07 |
| 闲置 | 玩家两分钟无输入即标为闲置,并关闭不再响应心跳的连接。 | Arcturus:240 个周期(120 秒)标为闲置,480 个周期(240 秒)移除闲置的非房主15;Houdini 关闭 61 秒窗口内未发送心跳的客户端48。简报中“240 秒无 ping”把两者合在一起,属于我的改编 |
| 限制 | 当 Game Center 报告多人游戏受限(包括仅限好友)时,自定义多人功能撤下;在我看来,这也包括真实玩家行走的重放:这样的玩家只看到镇民。 | Apple 关于 isMultiplayerGamingRestricted 的文档18;把回声视为多人功能是我的理解(第 7.6 节) |
| 交流 | 当 Game Center 报告个性化交流受限,或玩家未成年时,自定义交流撤下。 | Apple 关于 isPersonalizedCommunicationRestricted 的文档51 |
| 保留 | 儿童数据只保留到其书面目的所需的时间,并在告知中写明删除期限。 | 修订后的 16 CFR 312.10;自 2026 年 4 月 22 日起须合规19 |
5. Apple 的方式:Game Center 的旗标、年龄段、审核指南与 COPPA
一座儿童可以走进来的小镇,受到 Apple 和美国联邦贸易委员会(FTC)规则的约束,而其中大多数都带着日期。本节给出 Apple 和 FTC 自己的原话,并在 Apple 公布了适用版本时一并注明。我对某条规则对 Kiradex 意味着什么的所有说法,都是我的解读,并标明为解读;其中没有任何内容构成法律建议。
Game Center 的三个限制旗标
Game Center 的本地玩家带有三个布尔值,游戏可以在打开任何社交功能之前读取它们。适用版本取自 2026 年 10 月 4 日保存的 Apple 文档,每段说明均逐字引用。185152
| 属性 | iOS | Apple 的说明 |
|---|---|---|
GKLocalPlayer.isMultiplayerGamingRestricted |
13.0 | “If this property is true, the local player can’t join multiplayer games. If your game uses a custom multiplayer feature, you should disable it.”(如果此属性为 true,本地玩家无法加入多人游戏。如果您的游戏使用自定义多人功能,应当将其停用。) |
GKLocalPlayer.isPersonalizedCommunicationRestricted |
14.0 | “If this property or the underage property is true, the local player can’t include personalized messages on invitations or enable voice communication in multiplayer games. If your game includes any custom communication features, you should disable them.”(如果此属性或 underage 属性为 true,本地玩家无法在邀请中附上个性化消息,也无法在多人游戏中启用语音交流。如果您的游戏包含任何自定义交流功能,应当将其停用。) |
GKLocalPlayer.isUnderage |
4.1 | “If this property is true, Game Center disables some features for the local player.”(如果此属性为 true,Game Center 会为本地玩家停用部分功能。) |
第一个旗标的页面补充了最重要的细节:这个值来自 Screen Time,家长可以在其中允许孩子与所有人、仅与好友或不与任何人进行多人游戏,并且 “when you configure the setting to friends only, this property returns true for restricted.”(当您把设置配置为仅限好友时,此属性会返回 true,表示受限。)18 所以,一个只被允许和好友一起玩的孩子仍会被读作受限,而拥有自己广场的游戏会被要求将其关闭。第二个旗标的页面有两句话,覆盖范围不同。第一句列出 Game Center 自己屏蔽的东西,即邀请中的个性化消息和语音;第二句要求游戏停用它自己的 “any custom communication features”(任何自定义交流功能),而且没有为预设内容留出例外。在我看来,一份由完整预设句子组成的菜单就是一项自定义交流功能,简报也是这样对待它的。51
面向儿童的 Game Center,以及 Screen Time
Apple 的 Game Center 隐私页面定下了 Kiradex 已经对所有人采用的规则:“when communicating with other players in Game Center, children cannot send or receive user-inputted text. They are restricted to sending and receiving preset messages. In-game voice chat is disabled for children.”(在 Game Center 中与其他玩家交流时,儿童不能发送或接收用户输入的文字。他们仅限于收发预设消息。游戏内语音聊天对儿童停用。)17 它还说 “Children’s accounts never make real names visible to friends”(儿童账户从不向好友显示真实姓名),以及家长可以用 Screen Time 屏蔽 “multiplayer functionality, the ability to add friends, and the ability to connect with friends playing the same game”(多人功能、添加好友的能力,以及与玩同一款游戏的好友建立连接的能力)。17 Apple 的 Screen Time 支持页面按名称列出了 Game Center 的各项限制,其中包括 “Connect with Friends”(与好友连接)和 “Private Messaging”(私信),每项都是 “Allowed or Blocked”(允许或阻止)。53
Declared Age Range
2025 年 6 月 11 日,Apple 宣布儿童账户 “is required for children under 13”(对 13 岁以下儿童为必需),并且 “App developers will be able to request this information through the new Declared Age Range API”(App 开发者将能够通过新的 Declared Age Range API 请求这一信息),由家长选择以 “always, for each app request, or never”(始终、每次 App 请求时、或从不)的方式分享孩子的年龄段,且 “in a way that does not reveal the child’s birth date”(以不透露孩子出生日期的方式)。54 同一则公告还说,年龄分级将在当年年底前 “expanded to five categories”(扩展为五个类别),为青少年设立 13+、16+ 和 18+。54 Kiradex 的世界计划把这个 API 列为其少年规则的开关,并记录了 10 月 2 日的决定:按申报年龄段,13 岁以下完全没有广场;13 到 15 岁有带少年规则的广场;16 岁及以上或未申报,则是完整的广场。23 我没有为本文抓取该 API 自己的参考页面,所以这里不写它的最低 iOS 版本。
App 审核指南
有三条指南约束社交广场,引自 Apple 注明 “Last Updated: June 8, 2026”(最后更新:2026 年 6 月 8 日)的版本。55
- 1.2,用户生成内容。 App “with user-generated content or social networking services must include”(包含用户生成内容或社交网络服务的 App 必须包含):过滤不良内容的方法、“A mechanism to report offensive content and timely responses to concerns”(举报冒犯性内容的机制以及对问题的及时回应)、“The ability to block abusive users from the service”(在服务中屏蔽滥用用户的能力),以及 “Published contact information so users can easily reach you”(公开的联系方式,方便用户联系您)。最终 “used primarily for pornographic content, Chatroulette-style experiences, random or anonymous chat”(主要用于色情内容、Chatroulette 式体验、随机或匿名聊天)的 App 以及该列表中的其余各类,“do not belong on the App Store and may be removed without notice”(不属于 App Store,可能被不经通知地移除)。55
- 1.3,儿童类别。 该类别中的 App “must not include links out of the app, purchasing opportunities, or other distractions to kids unless reserved for a designated area behind a parental gate”(不得包含指向 App 外的链接、购买机会或其他让儿童分心的内容,除非限定在家长门控之后的指定区域),并且除少数例外,“should not include third-party analytics or third-party advertising”(不应包含第三方分析或第三方广告)。55
- 5.1.4,儿童。 “collect, transmit, or have the capability to share personal information (e.g. name, address, email, location, photos, videos, drawings, the ability to chat, other personal data, or persistent identifiers used in combination with any of the above) from a minor must include a privacy policy and must comply with all applicable children’s privacy statutes”(收集、传输或能够分享未成年人个人信息(例如姓名、地址、电子邮件、位置、照片、视频、绘画、聊天能力、其他个人数据,或与上述任何一项结合使用的持久标识符)的 App,必须包含隐私政策,并遵守所有适用的儿童隐私法规),而 “the parental gate requirement for the Kid’s Category is generally not the same as securing parental consent”(儿童类别的家长门控要求通常不等同于取得家长同意)。55
Kiradex 的计划走的是其设计文档从审核指南中归纳出的两条路中的第二条:在儿童类别之外,分级 9+ 或 12+(具体分级尚未确定),适用 5.1.4。23 在我看来,5.1.4 列表中的 “the ability to chat”(聊天能力)意味着,一个只有预设台词的广场无论回避了什么,仍然需要隐私政策。
修订后的 COPPA
FTC 修订后的《儿童在线隐私保护规则》(Children’s Online Privacy Protection Rule)于 2025 年 4 月 22 日刊登在《联邦公报》(Federal Register)上(文件号 2025-05904,90 FR 16918)。它 “is effective June 23, 2025”(自 2025 年 6 月 23 日起生效),并且 “Except with respect to § 312.11(d)(1), (d)(4), and (g), regulated entities have until April 22, 2026 to comply.”(除 § 312.11(d)(1)、(d)(4) 和 (g) 外,受监管实体须在 2026 年 4 月 22 日前合规。)19 FTC 在 2025 年 1 月 16 日以 5 比 0 投票通过后发布的新闻稿概括了这次修订:向第三方披露儿童信息若 “related to targeted advertising or other purposes”(与定向广告或其他目的相关),需单独取得可验证的家长同意;保留期限为 “for as long as reasonably necessary to fulfill a specific purpose”(实现特定目的合理所需的时长),因此 “operators cannot retain the information indefinitely”(运营者不能无限期保留信息)。56
规则正文中有三处与一个会记住任何东西的广场有关。
- 保留,§ 312.10。 “Personal information collected online from a child may not be retained indefinitely. At a minimum, the operator must establish, implement, and maintain a written data retention policy that sets forth the purposes for which children’s personal information is collected, the business need for retaining such information, and a timeframe for deletion of such information.”(从儿童处在线收集的个人信息不得无限期保留。运营者至少必须制定、实施并维护一份书面数据保留政策,写明收集儿童个人信息的目的、保留该信息的业务需要,以及删除该信息的时间期限。)这份政策要写进在线告知。19
- 持久标识符,§ 312.2。 个人信息包括 “A persistent identifier that can be used to recognize a user over time and across different websites or online services”(可用于跨时间、跨不同网站或在线服务识别用户的持久标识符),例如 “a customer number held in a cookie, an Internet Protocol (IP) address, a processor or device serial number, or unique device identifier”(存于 cookie 中的客户编号、IP 地址、处理器或设备序列号,或唯一设备标识符)。19
- 内部运营,§ 312.5(c)(7)。 “Where an operator collects a persistent identifier and no other personal information and such identifier is used for the sole purpose of providing support for the internal operations of the website or online service”(当运营者只收集持久标识符而不收集其他个人信息,且该标识符仅用于为网站或在线服务的内部运营提供支持时),在有告知的前提下无需取得同意。19
修订后的 § 312.8 还要求为儿童数据制定书面的信息安全计划。19 关于这对一个重放访客行走的广场意味着什么,我的解读写在简报里,并标明为解读;WORLD.md 第 3 节要求请律师审阅最终设计,而 10 月 2 日的决定把这件事推迟到应用有了起色之后,因此在那之前,这一解读维持不变。23
代价
本节的内容都没有在设备上测量过。认证后读取 GKLocalPlayer 上三个布尔值的耗时,这里没有计时;在一个 40 人的房间里为最多 39 位其他收藏者维持 350 毫秒渲染缓冲所需的内存或 CPU 也没有。21 在我看来,两者与绘制一帧相比都无足轻重,但这只是解读,而简报的检查针对的是行为,不是耗时。
6. 案例研究:用自身代码实测 Kiradex 的广场
Kiradex World 是我的集卡应用 Kiradex 里的像素小镇:一个用 16 像素图块绘制的 40×30 广场,由一台小型 FastAPI 与 WebSocket 服务器支撑,它把存在感保存在内存中,什么都不存储,只是其 Web 服务器默认写入的日志除外(第 7.6 节,设置 6)。212357 我在 10 月 5 日从工作树读取了服务器和应用的世界代码,没有做任何改动,并用两个脚本在模拟时钟上驱动服务器自己的类,所以本节的数字是代码的行为,而不是我对代码的解读。21620 仓库是私有的,所以文件以路径指称。以下是 docs/WORLD.md 中的计划、代码自身的 docstring 与代码实际行为之间的差异;其中没有任何一项是有人刻意隐瞒的缺陷,而第四项的一部分,计划已经列为尚未构建。
现有的东西
服务器有 23 句预设台词,14 句给玩家(索引 0 到 13),9 句给小镇自己的收藏者(14 到 22);房间容量 40 人;每位玩家每秒最多 8 次移动、每 2.0 秒一句台词;小镇时钟每 0.5 秒一拍(server/app/main.py 中的 TICK_SECONDS);向同一位收藏者展示卡片有 60 秒冷却;在 40×30 广场上的到达点是图块 (19, 13)。2120 四位按脚本行动的收藏者住在服务器上,这样同一房间里的每个人看到的都是同样的人:Moss、Prism、Comet 和 Dawn,每人有 4 个路点,环线长度分别为 18、22、30 和 32 格,各自想看某一类卡片(老卡、闪卡、随便什么、日文卡),各有 2 或 3 句台词。server/app/npcs.py 设置 STEP_SECONDS = 0.7,让每位收藏者在路点停留均匀分布的 3 到 8 秒,每隔均匀分布的 20 到 40 秒说一句台词,第一句在 8 到 20 秒之后。2120
在我看来,这个直觉是对的:镇民在服务器上而不在每部手机里,台词走在慢时钟上,每人各有所求,让收藏有了用武之地。实测下来,其中三点被削弱了,第四点是计划与代码之间的落差。
1. 行走走走停停
measure_npc_cadence.py 导入 server/app/npcs.py 和 rooms.py,构建广场和四位收藏者,并以真实的 0.5 秒拍推进一个模拟小时。6 路段内步与步之间的 6,253 个间隔,每一个都恰好是 1.0 秒,而不是 0.7 秒:一步在其 next_move 当时或之后的第一拍触发,所以 0.7 秒被向上取整为两拍。这是每秒 1.0 格,而常量暗示的是 1.43。每段路之前的停留平均 6.75 秒(4.5 到 9.0,共 1,202 次),台词之间平均相隔 30.7 秒,统计的是同一位收藏者连续两句台词之间的 465 个间隔(一小时共 469 句台词)。6
客户端用 250 毫秒画完任何一步:PlazaRig.swift 在第 101 行把 tilesPerSecond 设为 4。50 所以一位以每秒一格行走的收藏者,在每段路中只有 25% 的时间被画成在动,另外 75% 的时间在每两格之间站着不动。6 《绿宝石》的游荡者以玩家的步速迈步,然后等待;Kiradex 的收藏者在本该行走时,以玩家的步速做一格长的短促爆发,在我看来,这既不像居民的踱步,也不像正常行走(第 1 节时间线图的最下面一行)。
2. 另一位收藏者从旁走过时会回弹
服务器转发另一位收藏者的移动时,PlazaRig.move(other:to:facing:)(PlazaRig.swift 第 526 到 542 行)从行走者最后所在的整格规划路径,把行走者的步子设为这条路径,并在每条消息上都设置 progress = 0,哪怕一步只画了一半;长于 6 格的路径是一次跳跃。50 第 3 节的模型用 rig 自身的 32 位算术给它配上了数字。无抖动的匀速行走从不回弹,因为每一步都恰在下一条移动到达前结束。有 30 或 80 毫秒抖动时,一段 40 格的行走会在一步中途被重置 13 或 25 次,其中 7 或 16 次被画成向后。跑步时,rig 用行走的每秒 4 格来画行走者,所以在测试的每种抖动下,64 条移动中都有 45 条在一步中途到达,跳跃 9 次;而且只要发生回弹,画出的行走者就会落后发送方最多 5.9 到 6.7 格。7 本系列较早的一篇文章像素艺术的运动从代码中读出了玩家自己在一步中途第二次点按时同样的回弹;那篇简报定下的规则,即绝不重置进行中的一步,正是本简报应用到其他收藏者身上的规则。22
3. 小镇空了就停下
server/app/npcs.py 的模块 docstring 这样说收藏者:“A town is never empty, and it has somewhere for a collection to matter.”(小镇从不空荡,而且有一个让收藏派上用场的地方。)20 server/app/rooms.py 中的 Room.tick 却在自己的 docstring 里说了相反的话,“A room with nobody in it stands still”(没有人的房间静止不动),并且照此执行:房间里没有玩家时它立刻返回,于是收藏者就冻结在原地。206 脚本测量了第一个到来的人随后看到什么:用一拍把新建的收藏者推进到 10,000 个模拟秒,会同时冒出四句台词,每位收藏者一句,他们都还站在原来站的地方;中间什么都没发生。6 而且因为服务器什么都不存储,安静的早晨第一个进来的收藏者看到的还是同样的四个人,看不到任何别人曾经来过的迹象。在 Kiradex/ 和 server/app/ 中搜索,找不到任何记录访问或每日对话的东西。24
4. 频率,以及 Game Center 的旗标
有两处差异存在于 docs/WORLD.md 与代码之间。计划第 5 节保留了促成这台服务器的推理,其中设计的是客户端以 8 到 10 Hz 发送位置和朝向。23 应用实际上是每走完一格发送一次 move:现在行走每秒 4 次,跑步按名义速率每秒 6.4 次(在运动篇对代码的模型中,60 Hz 下为 6.0 次),无论哪种都低于服务器每秒 8 次的上限;本系列的运动约定,即 60 Hz 下行走每格 16 拍、跑步 8 拍,会使其成为 3.75 和 7.5,仍低于上限(第 7.4 节)。2122 在我看来,对网格而言逐格发送是更好的设计,简报保留它;需要更新的是计划里的那句话。
计划第 3 节把身份建立在 Game Center 上,理由是 “a child’s Game Center is controlled by the parent in Screen Time (multiplayer on/off, adding friends on/off, sharing the friend list with games on/off).”(儿童的 Game Center 由家长在 Screen Time 中控制(多人游戏开/关、添加好友开/关、与游戏共享好友列表开/关)。)23 Kiradex/ 和 server/app/ 中没有任何代码读取 GKLocalPlayer.isMultiplayerGamingRestricted、isUnderage 或 isPersonalizedCommunicationRestricted,也没有调用 Declared Age Range API。24 计划第 5 节已经把 “junior mode by Declared Age Range”(按 Declared Age Range 的少年模式)列入尚未构建的事项。23 它没有列出的是 Game Center 的旗标:Screen Time 的多人设置管的是 Game Center 自己的多人功能,而 Apple 的文档要求带有 “a custom multiplayer feature”(自定义多人功能)的游戏自行停用它,所以在应用读取这个旗标之前,家长的设置到不了广场。18
套接字断开后,客户端以 min(30, 2^attempts) 秒的退避重新连接(PlazaClient.swift 第 165 行),隐藏某位收藏者是在设备上按 ID 完成的。215023 简报中两者都保持原样,只给隐藏列表增加一个用途:它随每次请求广场回声一起发送,让服务器可以剔除该玩家已隐藏的收藏者的行走(第 7.2 节)。
7. 简报:Kiradex 要构建什么,以及必须通过的检查
简报保留广场已有的东西(服务器自己的收藏者、只有预设台词、每格一次移动、存在感只在内存中),并按经典作品给出的顺序加上三个答案:生活不会停下的镇民、来过之人的痕迹,以及绘制时不会回弹的存在感。每一项都有一个可由脚本、测试或录屏判定通过与否的检查。图块坐标是广场的坐标,x 向右,y 向下。21
关于借用的界线,与本系列第一篇文章为专利和表达所划的界线相同:没有生物,没有宝可梦地名,不用任何系列专有词作为功能名称,也没有任何瞄准投掷来捕获、显示捕获概率、派伙伴去战斗或骑乘什么的东西。36 下面的每个元素,都是行走、站立、说完整预设句子的镇民,以及其他收藏者行走的淡色重放。上文以已出版作品的事实身份提到了各游戏的系统;Kiradex 的功能在这里用 Kiradex 自己的名字:镇民、回声、今日问候、台词簿和安静广场。docs/WORLD.md 第 3 节已经把每一位不是好友的在线收藏者称为路人;这个说法保留,而重放称为回声,以免两者混淆。23 法律方面的说明是解读,不是建议。
7.1 有日程的镇民
- 人数。 把广场自己的收藏者从 4 位增加到 8 位,每人一个岗位。21 比例遵循《绿宝石》常驻人群,即 100 人中 59 人站立、41 人不站立(39 人游荡,2 人原地踏步),换算到八人四舍五入为五人站立、三人移动。五人在岗位上站立并转身:商店柜台、大厅门口、喷泉和两个摊位。两人像《绿宝石》的游荡者那样,在岗位周围 2 格的范围内游荡。第三个移动者并非来自《绿宝石》的比例,那 158 位镇民要么站立、要么在范围内游荡、要么原地踏步,没有一人沿路线行走:它是保留下来的今天四条路点环线之一,在岗位之间巡走一圈,于是今天的四条环线变成一条。121 四位新收藏者和今天的四位一样,用应用自己的形象系统绘制,昵称取自应用自己的词表。20
- 等待。 岗位上的收藏者转身、游荡者迈步之前,等待服务器 0.5 秒时钟的 1、2、3 或 4 拍,随机选取:0.5、1.0、1.5 或 2.0 秒,即《绿宝石》的 0.54、1.07、1.61 和 2.14 取到最近的一拍。以整拍计的等待不会再被拍子取整,而这正是第 6 节在步子上测出的毛病。16
- 房间的时钟。 每个房间只跑一个时钟:服务器的 UTC 时间,按房间自己的时区来读,时区在服务器配置中为每个房间设一个 IANA 时区。服务器在到达时通过
welcome和安静广场的响应({"zone": "<IANA name>", "clock": <server ms>})同时发送这两者,广场开着的时候,应用从这里而不是从手机获取时段,这样同一房间里的每个人都在同样的光线下看到同样的镇民。今天的Daylight.now从Calendar.current,也就是手机自己的日历和时区中取小时,而服务器只保留一个单调时钟,不指定任何时区。50 房间用哪个时区、收藏者被分到哪个房间,是本简报留待决定的配置;所有房间共用一个时区是最简单的起点。在广场之外,手机的时钟仍是收藏者的时钟。 - 日程。 每位镇民的一天是 3 到 7 个带时刻的点,以一个字符串写在服务器数据里,由一组按顺序尝试的键选出:日期、星期几、时段,然后是默认,全部按房间的时钟读取。9 时段是应用自己的,取自
Kiradex/World/Daylight.swift中的Daylight.Phase:早晨 6 点到 9 点,白天 9 点到 17 点,傍晚 17 点到 20 点,其余为夜晚。50 按房间的时钟到了夜里,岗位上的五人和两个游荡者各自从自己的门进屋,就像《星露谷物语》里 Pierre 的星期五在酒吧结束那样(“Returns home to sleep.”,回家睡觉),而保留环线上的行走者缩短巡走的一圈。30 - 步速。 用两种改动之一修正走走停停的行走。首选:服务器一次发出一整段路,
{"t": "walk", "id": ..., "path": [...], "at": <server ms>},客户端从at开始,按本系列运动约定下玩家的行走速度走完它,即 60 Hz 下每格 16 拍,每秒 3.75 格(今天的 rig 中是 4)。22 最低限度:让服务器的步间隔是整数拍,STEP_SECONDS = 0.5,客户端用同样的 0.5 秒画镇民的一步,每秒 2 格,这是居民的踱步,而不是玩家的行走。632 - 房间空着时的时间。 镇民所在的图块是房间时钟的纯函数:由路线、时段和一个种子在被询问时计算,而不是靠
Room.tick推进。种子是服务器配置中的一个全局值,不按房间或时区区分,所以时区相同的两个房间会在相同的图块上显示相同的镇民。按房间时钟 07:40 第一个进来的收藏者,会在 07:40 时 Moss 应在的地方看到 Moss;用设成另一个时区的手机进入同一房间的收藏者也一样。63
检查。 (a) 按设计检查步速。对于首选的整段路,客户端测试输入一段带 5 格直线路径和 at 时间戳的脚本化 walk,像运动简报的 -motionLog 记录玩家位置那样逐拍记录镇民的位置,并断言:它在客户端估计的房间时钟上 at 所落的那一拍出发,按顺序经过路径上的图块,每拍恰好移动 1 像素且从不后退,每格用 16 拍,并在 at 之后第 80 拍站在最后一格上;服务器测试断言每段路都带着时间戳、完整地只发送一次。对于最低限度的备选方案,服务器测试以 0.5 秒拍驱动镇民一个模拟小时,断言路段内每个步间隔都等于配置值;今天 measure_npc_cadence.py 测得的是 1.0 秒,而配置是 0.7 秒。无论哪种,服务器测试都断言每次等待都是整数拍。622 (b) 一个测试以相隔 600 秒的方式打开两个时区相同的房间,两者都用同一个全局种子,断言在相同服务器时间下镇民所在图块完全一致;一个客户端测试把手机时区设成与房间不同的时区,断言广场的时段跟随房间的时钟。(c) 一个测试把服务器时钟设到在房间时区中为 03:00 的某个 UTC 时刻,断言岗位上的五人和两个游荡者不在广场地图上,而保留环线上的行走者在。(d) 一个服务器测试按类别清点八人,得到岗位 5 人、游荡 2 人、保留环线 1 人。
7.2 回声:最近八次行走
- 机制。 广场保存最近 8 次访问。413 一次访问是走过的路径,以图块与时间的配对记录,从到达开始,到第一次进门或满 60 秒为止,取较短者;不保存行走者的形象。当房间里的在线收藏者少于 8 人时,回声填补空缺:每个回声按自己的步速重放其行走,并在它进入的那扇门处淡去。回声没有名牌,不举卡片,没有点按目标,什么也不说。它是一段行走,不是一个人。5
- 外观。 回声的形象绝不从行走者的形象派生。服务器从四个素色形象中给每个回声挑一个:清单里的默认部件,染成四种素色,由锻炉专为回声制作,并以浅淡方式绘制(锻炉的某条染色色阶加固定透明度)。四个中选哪个,由服务器保管且从不发送的密钥下、对该次行走自身盐值的带密钥哈希决定,所以同一位收藏者的两次行走会得到互不相关的形象。收藏者也可以穿默认部件,所以恰好穿着这四套之一的行走者可能碰巧与自己的回声相同,但这种相同不携带任何信息:形象对是谁走过只字不提。58
- 消退。 回声在 7 天后或有 8 次更新的访问到来时被丢弃,以先到者为准。14
- 谁能看到回声。 在线广场里的收藏者,只要广场上的收藏者少于 8 人。
isMultiplayerGamingRestricted为 true 的人看不到:他们的安静广场只有镇民,因为在我看来,回声是广场多人功能的一部分(7.6,设置 1)。18 - 隐藏。 隐藏一位收藏者也会隐藏其回声,由服务器负责过滤。每条存储的行走带有一个随机盐值和一个标签:在只有服务器持有的密钥下,对行走者 ID 与该盐值做的带密钥哈希(HMAC),所以同一位收藏者的两次行走带有互不相关的标签。请求回声时附带请求者已隐藏的 ID;服务器对每条存储的行走重新计算每个被隐藏 ID 的标签,剔除匹配的行走,并在请求答复后,或在实时连接中套接字关闭时,丢弃这份列表。送达客户端的只有素色形象、路径和小时:没有 ID,没有标签,没有盐值,也没有任何取自行走者形象的东西。这样就去掉了标识符和外貌,而今天这两者在每次到达时都是一起发送的(
server/app/rooms.py中的Player.public()把 ID、昵称和形象放在一个对象里发送,server/app/main.py再把它广播给房间)。它没有去掉行走本身。一条路径、它的步速和它的小时,对于那个小时在广场里的人,或知道朋友总往哪里去的人来说,可能指向某位收藏者;而在那次行走时就在房间里的客户端,已经通过moved收到了附带行走者 ID 的每一格。回声是无名的,而不是匿名的。2320 - 存储什么,以及对 COPPA 的解读。 不存昵称、Game Center ID、设备 ID、行走者的形象、地区,也不存比小时更细的时间:只存图块、相对于行走开始的步时间、小时、同时用来挑选素色形象的盐值,以及用于施加隐藏的标签。这是解读,不是建议: 标签由持久标识符派生,所以我把它当作持久标识符对待。它留在服务器上,从不发送,只用于施加隐藏这一唯一目的,我把这理解为 § 312.5(c)(7) 所述、在有告知前提下的内部运营用途。以不带标识符、由服务器挑选形象的方式发送的行走,我理解为不在该规则的个人信息定义之内。这一解读的薄弱之处在于,行走是一种行为,那个小时看过广场的人可能把它与某位收藏者联系起来;只记到小时的时间、60 秒的上限和七天期限缩小了这种可能,但没有消除它。请求所附带的隐藏 ID 只用于该次请求,既不记录也不保留。隐私告知中的书面保留政策写明 7 天,这正是 § 312.10 所要求的。19 这使服务器从什么都不存储,变为把行走存储一周,而这一解读应交给计划推迟到应用有起色之后再请的律师审阅。23
- 记录谁。 不记录 13 岁以下的任何人:按 10 月 2 日的决定,他们根本没有广场。23 不记录
isMultiplayerGamingRestricted为 true 的任何人(他们没有可行走的在线广场,设置 1),不记录isUnderage为 true 的任何人(设置 2 已经把它当作儿童信号),也不记录按申报年龄段为 13 到 15 岁的任何人,这是计划留待编写的少年规则中的第一条。185223 - 不得相似。 血迹、重放的死亡、召唤记号、贴满玩家帖子的墙。回声绝不是生物,也绝不战斗。4144
检查。 (a) 一个服务器测试记录 10 次访问,断言保留 8 次且最旧的被丢弃,并且时钟推进 7 天后无一留存。(b) 一个模式测试断言:存储的行走只有路径、小时、盐值和标签这几个字段,其中没有昵称、ID 或形象字符串;客户端收到的回声只有形象、路径和小时这几个字段,没有哈希、标签、盐值或 ID 字段。它还断言回声的形象字符串是行走盐值的带密钥哈希所选出的那四个之一,不论行走者穿了什么都相同,并且对于穿着素色四套之外形象的行走者,它不是行走者的形象。(c) 一个没有在线同伴的 UI 测试显示最多 8 个回声,且没有一个响应点按。(d) 分别带多人受限旗标、带 isUnderage、以及处于 13 到 15 岁段的三个测试,断言服务器没有记录该客户端的任何访问。(e) 一个服务器测试存储两位收藏者的行走,在隐藏其中一位的情况下请求回声,发现该收藏者的行走被剔除,随后在服务器状态中任何地方都找不到被隐藏的 ID。
7.3 今日问候
- 机制。 每位镇民记得您今天是否和他说过话。1110 按当地日期当天第一次交谈会选一句问候台词(“Back again!”(又来啦!)之类,为 Kiradex 而写,加在
server/app/lines.py的索引 22 之后);同一天之后的交谈选普通台词。21 每当您与同一位镇民交谈过的天数满三天,对方就在一架隐藏的熟客阶梯上升一级,从第一次交谈算起,永不重置,每升一级就会说一句新台词。不显示计量条。 - 不衰减。 错过一天甚至一个月,都不会失去任何东西。星露谷的好感度在您每个没去说话的日子都会衰减,而这个永远不会,因为 PEGI 把 “daily quests, log-in streak or event-based rewards that expire after a while”(每日任务、连续登录或一段时间后失效的活动奖励)评为 PEGI 7,并在 “players are punished by losing rewards or game status when they do not return in time”(玩家未能及时回来时会因失去奖励或游戏地位而受罚)时评为 PEGI 12。105958
- 存放在哪里。 在设备上:收藏者档案里的一个字典,键是镇民 ID,值是最后交谈的当地日期和交谈过的天数,这样服务器就不需要按玩家存储任何东西。
检查。 (a) 一个单元测试在 2026-10-05 与同一位镇民交谈两次、在 2026-10-06 交谈一次,得到两次当日首次问候和一句普通台词。(b) 一个单元测试把设备日期往前往后调,发现阶梯上的级数从未被拿走。(c) 在 server/app 中搜索,没有为此功能新增任何按玩家的存储。(d) 一个单元测试在相隔数周的三天里各有一次首次交谈,发现该镇民在阶梯上升了一级。
7.4 不回弹的存在感
- 发送。 和今天一样,每走完一格发送一次
move。今天的基线是行走每秒 4 次、跑步 6.0 到 6.4 次(分别对应 60 Hz 下建模的跑步和名义速率的跑步);在本系列的运动约定下,即运动简报中 60 Hz 下行走每格 16 拍、跑步 8 拍,则是 3.75 和 7.5,本简报以这一约定为目标。两者都低于每秒 8 次的上限。2122 服务器给它转发的每条moved加上自己的毫秒时间戳。 - 渲染。 每个客户端根据收到的时间戳维护对服务器时钟的估计,并在比该估计晚 350 毫秒的渲染时钟上绘制每一位其他收藏者:在夹住渲染时钟的两个已收到时间戳之间线性插值,行走循环按已走的距离驱动。绝不重置进行中的一步。缓冲耗尽时,停在最后一格;网格上的行走者戛然而止,所以不做任何外推。只有当行走者落后超过 6 格或 2 秒时才跳跃。716 350 毫秒来自一个上界,并在一个假设时钟估计精确、除抖动外没有单向延迟的模型中做了检验(第 3 节):在运动约定的行走速度每秒 3.75 格下,一步是 266.7 毫秒,一步加 80 毫秒抖动是 346.7 毫秒,所以至少这么长的延迟对任何一次抖动抽取都不会断档。这给时钟估计的误差留下约 3 毫秒;估计若比服务器时钟快出更多,就可能出现断档帧。325 毫秒在模型的第一次抖动抽取上通过了检查 (a),80 毫秒时断档 6 帧,但在 200 次抽取中有 8 次未通过;350 毫秒在任何一次中都没有断档。7 它是一个有待设备录屏确认的起始值,不是结论。
- 闲置。 收藏者 120 秒无输入即标为闲置,用 rig 现有的
doze闲置循环来显示,就像 Arcturus 在 240 个 500 毫秒周期时把化身标为闲置那样。1550 240 秒没有 ping 就关闭套接字。这条规则是我对两个来源的改编:ping 检查来自 Houdini,它会关闭在 61 秒窗口内没有发送心跳的客户端;240 秒则是 Arcturus 移除闲置非房主的时长,不过 Arcturus 计数的是没有动作的周期,而不是缺失的 ping。4815
检查。 (a) 把新逻辑移植进 measure_remote_walk.py,渲染时钟取自客户端对服务器时钟的估计,而不是发送方自己的时钟;在发送方以每秒 3.75 格和 7.5 格移动时,要求六种情形全部倒退帧为 0,在 0 和 30 毫秒抖动下断档帧为 0,在 80 毫秒下,发送方发送期间绘制的 600 帧中断档帧至多 10 帧。时钟精确的模型在 350 毫秒下、200 次抖动抽取中的每一次,六种情形全都断档为 0。7 (b) 一个 UI 测试让脚本化同伴从左到右走过广场,录屏中不出现同伴 x 坐标减小的任何一帧。(c) 一个服务器测试断言每条转发的 moved 都带有时间戳。(d) 一个服务器测试在没有 ping 的情况下把时钟推进 241 秒,断言套接字已关闭。
7.5 台词簿
- 形态。 六个分支,上线时每支 8 到 12 句,共 48 到 72 句,每句台词都在两次点按之内(分支,然后台词):问候、回答(喜欢的年代、收藏了多久、哪个系列)、收藏、交换(安排交换,绝不定价)、喝彩和告别。今天只有一个列表里的 14 句玩家台词。21 表情动作仍是单独的一行。Game Center 报告交流受限、或未成年的玩家,完全拿不到台词簿(7.6,设置 2)。
- 增长。 通过游玩最多再解锁 30 句,每天至多一句,来自 7.3 中的熟客阶梯和等级。1235 列表由服务器保管,而且它本来就在到达时发送列表,所以可以在一小时内下架某句台词。20
- 与经典作品对照。 简易会话开放 940 个词,并把它们组合进 2 到 10 格的格子里;《集合啦!动物森友会》有 88 个反应;《绿宝石》的联合房间接受 15 个字符的键盘输入。38124 Kiradex 的台词都是完整的句子,绝不由单词拼成,所以无论怎样连续选择,都拼不出一个名字、一个数字或一个地点。
- 不得相似。 简易会话的分组结构或单词格子、流行语列表,或任何系列专有词。40
拟议的台词簿:本文中唯一一张表示提案而非测量的图。26
检查。 (a) 一个针对 lines.py 的测试断言上线时有 6 个分支、每支 8 到 12 句,并且每句台词都在离根节点 2 次点按之内。(b) 一个测试断言没有任何台词包含数字或自由填写的空位。(c) 一个测试读取 pret 的简易会话词表,确认没有台词与某个条目相同,也没有两个词及以上的条目出现在台词中。(d) 一个单元测试在连续两天各解锁一句台词,并拒绝同一天的第二次解锁。
7.6 安全设置,各附一项检查
- Game Center 的限制会关掉其他收藏者,无论实时还是重放。 当
GKLocalPlayer.local.isMultiplayerGamingRestricted为 true 时(包括仅限好友),广场以安静广场的形式打开:只有镇民,没有回声,也没有 WebSocket。镇民和房间的时钟通过一次请求取得,该请求不带账户 ID、设备 ID 和隐藏 ID;服务器只用请求的 IP 地址来答复它,广场服务器不记录它;主机自己的边缘请求日志在此未经核实(设置 6)。18 解读: 页面说 “If your game uses a custom multiplayer feature, you should disable it”(如果您的游戏使用自定义多人功能,应当将其停用),而回声是真实收藏者行走的重放,是第 2 节所考察的那类其他玩家的痕迹,并且观看者可能认出它(7.2)。Wikipedia 把 Dark Souls 中其他玩家幽灵般的影像放在游戏的 Multiplayer 标题下描述,却没有说明它们是录像还是当时连接着的玩家。这个来源是两可的:该标题下的正文说游戏 “integrates online features into the single-player world”(把在线功能整合进单人世界),并把 “Direct multiplayer”(直接多人)作为与幽灵分开的协作召唤和入侵给出。简报仍然站在保守的一边:在我看来,回声属于广场自定义多人功能的一部分,家长限制了多人游戏的玩家一个也看不到。1841 能让回声为这位玩家重新出现的,是 App 审核或 Apple 以书面形式,把一段以素色形象重放、不附任何标识符的行走认定为多人功能之外的东西;本简报不预设这一点。检查: 一个把旗标桩设为 true 的 UI 测试断言不打开 WebSocket、不绘制回声,并且广场显示它很安静;一个服务器测试调用安静广场的路由,确认响应中没有回声、服务器日志中没有 IP 地址。 - 关闭自定义交流。 当
isPersonalizedCommunicationRestricted或isUnderage为 true 时,默认只有表情动作:台词簿隐藏,不发送say,不为这位玩家绘制其他收藏者的台词,并且任何时候都没有语音、邀请消息或输入框。5152 解读: 页面说 “If your game includes any custom communication features, you should disable them”(如果您的游戏包含任何自定义交流功能,应当将其停用),而一本预设句子的台词簿就是一项自定义交流功能。Game Center 在自己的功能中允许儿童使用预设消息,并不等于游戏自己的功能可以豁免。5117 我保留表情动作,是因为挥手或点头不携带任何孩子可以用来联系别人的东西;但表情动作也算一种交流(Wikipedia 对 Journey 的描述把它的鸣响称为 “The only form of communication between the two”(两人之间唯一的交流方式)),这是该解读中最薄弱的一环:如果 App 审核也把表情动作视为自定义交流功能,它们也要撤下,这位玩家的广场就只剩存在感。5 为这位玩家保留台词,属于需要 Apple 书面同意的例外;本简报不采取这一做法。检查: 在Kiradex/World中搜索TextField、TextEditor和UITextField一无所获,并且分别把每个旗标桩设为 true 的 UI 测试显示表情动作行、不显示台词簿、不发送say,也不绘制其他收藏者的台词。 - 按 10 月 2 日的决定,依 Declared Age Range 划分年龄段。 13 岁以下:没有广场,只有自动生成的昵称。13 到 15 岁:带少年规则的广场,第一条规则是他们的行走永不被记录。16 岁及以上或未申报:完整的广场。2354 检查: 一个覆盖三个年龄段与三个 Game Center 旗标的模式表单元测试。
- 举报和隐藏覆盖到回声。 隐藏一位收藏者会隐藏其实时形象,并按 7.2 所述在服务器上逐次请求过滤,隐藏其回声;举报仍保留在每位收藏者的面板上。55 回声穿的是素色形象、没有面板,所以玩家无法挑出某位被隐藏收藏者的回声来隐藏;这件事由服务器的过滤完成。隐藏对在其之前和之后存储的行走都有效;它覆盖不到其他玩家实时看到的东西,或从一段行走中认出的东西。检查: 7.2(e) 中的服务器测试,以及一个隐藏脚本化同伴、重放其已记录行走并断言它不被绘制的 UI 测试。
- 不做随机配对,不做匿名聊天。 陌生人只会共享广场;没有任何东西会把两个陌生人配对进一个私密空间。55 检查: 一个针对
server/app/main.py中处理器的测试,确认没有任何消息会在两位不是 Game Center 好友的收藏者之间开启通道。 - 书面保留政策。 隐私告知加入书面数据保留政策:存在感只在内存中;回声保留 7 天,每条都带一个对行走者 ID 的带密钥哈希,它留在服务器上,只用于施加隐藏,从不发送;请求附带的隐藏 ID 不保留;连接的 IP 地址只用于答复该连接并路由回复,我把这理解为 § 312.5(c)(7) 下对内部运营的支持,并且广场服务器不记录它;举报按原因和房间记录。19 今天服务器以默认设置启动 uvicorn 0.37.0(
server/Procfile,--proxy-headers),而就我阅读 uvicorn 自身代码所见,这些默认设置会为每个 HTTP 请求(访问日志除非关闭否则开启)和每个被接受的 WebSocket(uvicorn.error上的一行 INFO)把客户端的转发地址写入日志;简报以--no-access-log --log-level warning启动它。57 检查: 告知的 URL 可以从设置中访问,7.2(a) 中的服务器测试守住 7 天,并且一个在生产启动命令下打开 WebSocket 并调用安静广场路由的测试,在捕获的日志中找不到任何 IP 地址。主机的边缘请求日志不在该测试范围内,在此未经核实:它们是否记录客户端地址、记录多久,需要在撰写告知之前向主机确认。 - 世界里不放第三方分析或广告。55 检查: 对应用目标的依赖审计中没有列出任何分析或广告 SDK。
拒绝的东西
- 我们自己的游玩计时器。 Screen Time 已经能限制在一个应用里的时长,而 Club Penguin 的服务器会为孩子倒数允许的分钟数并在归零时断开连接;家长已经熟悉的,是平台的限制。1748
- 粉丝数,以及任何谁喜欢谁的计数;计划已经排除了它们。23
- 带名字的访客列表,即一页谁来过的记录。回声是刻意无名的:它不带 ID,也不带任何取自行走者形象的东西。但它不是匿名的:那个小时在广场上的人,或知道朋友习惯的人,仍可能认出一段行走,这就是为什么回声只带小时、不带更细的时间,也是为什么没有可以读取回声的列表。
- 可以点按的回声,可以与之交谈或跟随的回声。
- 今日问候的计量条,或任何因为没回来而失去的东西。59
不在本简报之内
- 人们装饰的房间。 家具、帽子以及拜访其他收藏者的房间,属于本系列即将发布的一篇文章。
- 新台词的措辞。 简报确定台词簿的形态;句子为 Kiradex 撰写,并另行本地化。
- 多人同玩。 计划中列为尚未构建的交换、展示和交换界面,不在本简报范围之内。23
- 任何来自所测游戏的东西。 不使用来自宝可梦、《星露谷物语》、《动物森友会》、Dark Souls、Journey、Death Stranding、Splatoon、Habbo 或 Club Penguin 的任何生物、地名、标志、图块或声音。
- 设备上的时序。 检查针对的是行为、录屏和模型;这里不承诺任何帧时间。
核心要点
如果您负责画美术
- 画站着的人要比走着的人多。《绿宝石》地图上常驻的镇民里,十分之六站在岗位上,大多朝向一方,一些四处张望(算上剧情演员则是十分之七);给每个站立的形象一个朝向和一个张望动作,把行走循环留给那近十分之四的游荡者。1
- 让另一位玩家行走的回声读起来像痕迹,而不是人:浅淡、无名、手里什么都不拿、在门口淡去。它的画法里不应有任何东西引人去点按。5
如果您负责做引擎
- 在到达时用一次带经过天数的运行结算小镇的一天,并从房间里所有人共享的一个时钟、而不是每部手机的时钟,算出镇民所在的图块;房间一空就返回的服务器循环会冻结小镇,正如 Kiradex 实测所示。36
- 让服务器的步间隔为整数拍,并按给定的间隔画完每一步;0.5 秒拍上的 0.7 秒一步会每 1.0 秒落一次、用 0.25 秒画完,成了一段 75% 时间都站着不动的行走。6
- 每格发送一次更新,在服务器上打时间戳,并在比估计服务器时间晚 350 毫秒的时钟上绘制其他行走者,绝不重置进行中的一步;在模型中,以本系列的每秒 3.75 格和 7.5 格、并假设时钟估计精确,在最高 80 毫秒抖动下倒退帧和断档帧都为 0。7
- 打开套接字之前,先读取
isMultiplayerGamingRestricted、isPersonalizedCommunicationRestricted和isUnderage;仅限好友会被读作受限,而在我看来,受限的玩家也不应看到其他玩家的重放。185152
如果您负责设计游戏循环
- 把他人的痕迹放进少量会消退的固定槽位:七天里八段行走,足以说明有人来过。在服务器上施加隐藏,发送痕迹时不带 ID、用素色形象而不是行走者自己的形象,并坦白说明看到过的人仍可能认出一段行走。414
- 用一个旗标记住今日问候,绝不因为错过一天而收回任何东西;PEGI 会为会过期的连续奖励评级。59
- 给陌生人句子,而不是单词:一本由完整句子组成的台词簿拼不出任何东西,而 Game Center 本来就把儿童限制在预设消息里。当 Game Center 表示交流受限时,连台词簿也一并收走。381751
常见问题
没有其他玩家在线时,如何让游戏里的小镇显得有生气?
给它有自己日程的镇民,以及来过之人的痕迹。在《宝可梦 绿宝石》的 16 座城镇中,没有隐藏旗标的 100 位镇民里,59 人守在岗位上,39 人在一两格的范围内游荡,2 人原地踏步(若算上会被旗标移除、大多站着的剧情演员,全部 158 人中依次为 71.5%、27.2% 和 1.3%),他们以玩家的步速迈步,每步之间等待 0.54 到 2.14 秒;《星露谷物语》的村民过着由 22 种键格式选出的、由几个带时刻的点组成的一天;而《绿宝石》的混合记录会把另一位玩家的基地、流行语句和居民复制进您的小镇。124
俯视角像素游戏里,NPC 应该多久动一次?
在《绿宝石》里,游荡者迈出一个 16 帧的步子(268 毫秒,与玩家行走是同一个动作),然后等待 32、64、96 或 128 帧,所以按每迈出一步计,它处于移动的时间最多占 11% 到 33%,所选的一步被挡住时还要更少;多数镇民的默认移动类型根本不迈步,不过剧情脚本仍可以让他们走动。《动物森友会》的 Wild World 走了相反的路,把村民放慢到低于玩家的步速。读起来糟糕的,是两者都不是的步速:Kiradex 的收藏者以玩家的速度一格一格地冲,格与格之间停 0.75 秒。1326
《宝可梦 绿宝石》的混合记录是什么?
一种通过通信线进行的交换,每个游戏向对方发送一个由 11 种记录组成、共 5,188 字节的固定结构,其中包括 20 个秘密基地、25 个电视槽位、16 条新闻和 5 条流行语句。对方紫堇市的老爷爷会替换您的老爷爷,而潮人的到来是唯一能让他再教一个流行语的事情。435
2D 多人游戏需要多少插值延迟?
Glenn Fiedler 的规则是足以扛过两个丢包,约为三个发送间隔,再加一两帧应对抖动。16 在跑于 TCP 之上的 WebSocket 中,什么都不会丢,迟到的包会被重传,所以在我看来预算是抖动而不是丢包,略多于一个间隔的缓冲就可以。对于每格发送一次更新的网格世界,以本系列行走每秒 3.75 格、跑步 7.5 格为 Kiradex 的行走建模,发现比估计服务器时间晚 350 毫秒的渲染时钟,在最高 80 毫秒抖动下倒退帧为 0、断档帧为 0;在一次抖动抽取上,以 80 毫秒行走时,300 毫秒在 600 帧中断档 27 帧,325 毫秒断档 6 帧,而 100 毫秒在每种情形下都断档 125 到 456 帧;在 200 次抽取中,350 毫秒一次也没有断档。350 毫秒依据的是一个上界:每秒 3.75 格时 266.7 毫秒的一步加上 80 毫秒抖动,是 346.7 毫秒。这是一个假设道路笔直、抖动均匀、时钟估计精确且没有基础延迟的模型,不是设备录屏。7
儿童能在 iOS 游戏里使用聊天吗?
Game Center 说儿童 “cannot send or receive user-inputted text”(不能发送或接收用户输入的文字),并且 “restricted to sending and receiving preset messages”(仅限于收发预设消息),语音聊天停用。Apple 的文档要求带有 “any custom communication features”(任何自定义交流功能)的游戏在 isPersonalizedCommunicationRestricted 或 isUnderage 为 true 时将其停用,而指南 1.2 要求用户生成内容具备过滤、举报、屏蔽和公开的联系方式。那句话并没有豁免预设消息,所以在我看来,只要任一旗标为 true,游戏自己的预设台词就要撤下,即使 Game Center 在自己的功能中允许儿童使用预设消息;Kiradex 的简报只为这类玩家保留表情动作,而这一解读是我的,不是 Apple 的原文。175155
COPPA 是否允许存储其他玩家的移动并作为幽灵重放?
我不是律师,这是解读,不是建议。修订后的规则把 “A persistent identifier that can be used to recognize a user over time”(可用于跨时间识别用户的持久标识符)算作个人信息,允许运营者在有告知的前提下 “for the sole purpose of providing support for the internal operations”(仅为内部运营提供支持之目的)单独使用持久标识符,并自 2026 年 4 月 22 日起要求一份写明删除期限的书面保留政策。Kiradex 的设计存储一段不含昵称、账户 ID、设备 ID、也不是行走者形象的行走,外加一个对行走者 ID 的带密钥哈希,它留在服务器上,只用于施加其他玩家的隐藏,从不发送;回声以服务器挑选的四个素色形象之一绘制,两者都保留七天,并写进那份政策。我把该哈希当作用于内部运营的持久标识符,把发送出去的行走视为在定义之外,但行走是一种行为,那个小时看过广场的人可能把它与某位收藏者联系起来,所以回声是无名而非匿名的,最终设计应由律师审核。19
本站相关文章:iPhone 上的像素艺术世界是本系列的第一篇指南,讲述了本文小镇所在的广场与图块系统,其法律常见问题划出了简报所遵循的专利界线;像素艺术人物构建了收藏者和锻炉,回声所穿的素色形象正出自其中,并引用了让今日问候不会衰减的 PEGI 规则;像素艺术建筑建造了镇民夜里回家要经过的门;像素艺术的运动测量了掌机上的行走,为应用自己的行走建模,并定下了简报应用于其他收藏者的“绝不重置一步”规则;面向开发者的 iPhone Duo 介绍了广场所面向的设备。
参考来源
-
作者测量,
measure_npcs.py(位于作者为本文准备的证据文件夹中),2026 年 10 月 5 日修订并重新运行,输出保存为measure_npcs.out。输入:pret 的pokeemerald反编译,提交731ad5b(master,2026 年 10 月 1 日)的浅克隆:include/constants/event_object_movement.h(81 种移动类型),16 个城镇的data/maps/*/map.json文件(对象事件、图形 ID、移动类型、范围、隐藏旗标),以及src/event_object_movement.c(步进表、按 59.7275 Hz 换算的三张延迟表、哪种移动类型设置哪种延迟,以及MovementType_WanderAround_Step4)。修订版按graphics_id把对象事件分为人物、物体(6 个ITEM_BALL、2 个TRUCK、1 个MR_BRINEYS_BOAT)和生物(5 个),遇到未分类的图形 ID 就停止;VAR_0和VAR_3事件的 sprite 来自变量,由于它们每一个都带有劲敌的隐藏旗标,计为人物。早先版本把全部 172 个对象事件都算作镇民;它及其输出以.r1后缀保留在旁边,修订版的输出原样重复早先的各行。第一次修订及其输出以.r2后缀保留;当前脚本是第二次修订,按对象事件中的隐藏旗标拆分 158 人:带旗标的 58 人(54 人站立,4 人游荡;按脚本中固定的图形 ID 列表,其中 41 人使用反派组织、劲敌或有名字角色的 sprite)和不带旗标的 100 人(59 人站立,39 人游荡,2 人原地踏步),并重新统计三座城镇中不带隐藏旗标的人(未白镇 2,橙华市 3,紫堇市 6);其输出原样重复早先的每一行。不带隐藏旗标的人在每次载入时都在地图上;把这 58 人称为剧情演员,是我对其 sprite 和旗标名称的解读,并非数据中的字段。移动占比(11% 到 33%)是对步进行与延迟行的算术:16 帧除以 16 加 32 到 128,按每迈出一步计;这是上限,因为所选方向被挡住的游荡者会不动、转向并重新等待。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
作者测量,
measure_daily.py(同一文件夹),2026 年 10 月 5 日重新运行,输出与measure_daily.out相同。输入:pretpokeemerald731ad5b 的include/constants/flags.h和src/clock.c;保存的 Stardew Valley Wiki 页面 “Villagers”(oldid 191346,2026 年 2 月 26 日编辑)、“Pierre” 和 “Modding:Schedule data”(22 种键格式按该页的表格统计);以及 Nookipedia 的 “Reaction” 页面(《集合啦!动物森友会》各版本的数量)。 ↩↩↩↩↩↩↩↩↩↩↩ -
pret,
pokeemerald/src/clock.c(DoTimeBasedEvents,以及带daysSince即自上次运行以来的天数运行一次的UpdatePerDay),提交731ad5b;DoTimeBasedEvents也会在src/overworld.c的地图载入时、以及src/field_tasks.c中的周期性场景任务里被调用,2026 年 10 月 4 日访问,https://github.com/pret/pokeemerald/blob/master/src/clock.c ↩↩↩↩↩↩↩ -
作者测量,
measure_record_mixing.py(同一文件夹),2026 年 10 月 5 日重新运行,输出与measure_record_mixing.out相同。输入:pretpokeemerald731ad5b 的src/record_mixing.c(struct PlayerRecordEmerald、其 11 个成员和 0x1444 字节的大小)、include/constants/global.h及相关头文件(SECRET_BASES_COUNT、TV_SHOWS_COUNT、POKE_NEWS_COUNT、SAVED_TRENDS_COUNT、APPRENTICE_COUNT)、src/mauville_old_man.c(五位老爷爷与选择规则),以及src/union_room_chat.c、include/union_room.h和include/constants/global.h(联合房间的领队、小组人数、sprite、活动代码、键盘页、MAX_MESSAGE_LENGTH和UNION_ROOM_KB_ROW_COUNT)。 ↩↩↩↩↩↩↩↩↩↩↩↩ -
Wikipedia,“Journey (2012 video game)”,Gameplay 与 Development 章节,每条陈述引用其自身来源,2026 年 10 月 4 日保存,https://en.wikipedia.org/wiki/Journey_(2012_video_game) 。thatgamecompany 自己的 Journey 页面也已保存,但只有导航内容。 ↩↩↩↩↩↩↩
-
作者测量,
measure_npc_cadence.py(同一文件夹),2026 年 10 月 5 日修订并重新运行,输出保存为measure_npc_cadence.out;修订只增加了对每条said消息的计数(一小时 469 条),与求出 30.7 秒平均值所用的、同一位收藏者连续台词之间的 465 个间隔并列,其余各行不变(上一版本以.pre-astra后缀保留)。它从 2026 年 10 月 5 日读取的工作树(文件与哈希见注 20)导入 Kiradex 服务器自己的app.npcs(STEP_SECONDS、make_all)和app.rooms(Plaza),从server/app/main.py读取TICK_SECONDS,以种子 1 在理想的 0.5 秒时钟上推进四位收藏者 3,600 个模拟秒,记录每条moved和said消息。连续两步之间 1.5 秒及以下的间隔计为路段内;更长的间隙计为停留。空房间测试检查Room.tick是否提前返回,并对新建的收藏者调用一次advance(10000.0)。25% 和 75% 这两个数字是用客户端 250 毫秒的一步(PlazaRig.tilesPerSecond = 4)除以测得的 1.0 秒间隔。这次运行取代了measure_kiradex.py仅凭常量推出的 0.7 秒和 36%。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
作者模型,
measure_remote_walk.py(同一文件夹),2026 年 10 月 5 日修订并重新运行,输出保存为measure_remote_walk.out;以 64 位浮点进行 rig 算术的上一版本及其输出以.pre-f32后缀保留。修订版把 rig 以Float做的每一步运算都按代码顺序以 32 位(NumPyfloat32)执行:progress是Float(PlazaRig.swift第 29 行),update接收Float(event.deltaTime)(PlazaStage.swift第 149 行),advance()累加dt * tilesPerSecond * (running ? runPace : 1) / length(第 660 行),running只在玩家身上设置(第 614 行);位置为(Float(x) + 0.5) * 16(TileMap.swift第 235 行),绘制在from + (to - from) * progress,取整到整像素,平分时远离零(第 670 和 726 行)。它打印出这样的核对:15 帧 1/60 秒在 32 位下恰好累加为 1.0,在 64 位下为 0.9999999999999999。它按 10 月 5 日所读重新实现了PlazaRig.move(other:to:facing:)(从行走者最后所在整格出发的路径;每条消息progress = 0;路径为空或长于 6 步时跳跃),把仍有剩余步子且 progress 超过 0.05 时的重置计为一次回弹,把绘制像素位置小于上一帧的计为倒退帧,并由绘制像素位置计算落后量。另外,它在稳定渲染时钟rt = now - delay上对快照插值建模,延迟取 100、250、300、325 和 350 毫秒,其中now是发送方的时钟,所以所建模的规则是估计服务器时间减去延迟,而不是最新更新减去延迟;这一半是拟议的渲染器而不是 rig,以 64 位浮点运行。假设:每帧deltaTime恰为 1/60 秒;没有融合乘加;发送方与渲染端时钟完全同步;没有基础单向延迟,因此更新在其时间戳加抖动时到达;道路笔直开阔(路径长度为曼哈顿距离;在 rig 中耗时 √2 倍的斜向步子未建模);发送方每走完一格发出一次移动,两半都以今天的行走每秒 4 格和跑步名义 6.4 格发送,缓冲部分另以本系列运动约定的每秒 3.75 格和 7.5 格发送;J 取 0、30 和 80 时 0 到 J 毫秒的均匀抖动;每秒 60 帧;种子 1。rig 每种情形运行 12 个模拟秒(10 秒发送加 2 秒稳定),每次带缓冲的运行 660 帧(11 秒),断档帧只在发送方仍在发送期间绘制的 600 帧内统计。第二个脚本measure_remote_walk_seeds.py(同一文件夹),2026 年 10 月 5 日运行,输出保存为measure_remote_walk_seeds.out,它从measure_remote_walk.py的源码中载入同一个插值函数,断言种子 1 复现出 27、6 和 0 个断档帧,然后以种子 1 到 200 重跑每秒 3.75 格和 7.5 格的缓冲:在 80 毫秒抖动下行走时,325 毫秒在全部 200 个种子上都有断档,其中 8 个超过 10 帧,350 毫秒在任何种子上都没有,325 和 350 毫秒的其余所有情形也都没有。它打印出 350 毫秒背后的上界:每秒 3.75 格的一步是 266.67 毫秒,加 80 毫秒抖动为 346.67 毫秒,余 3.33 毫秒。它还通过偏移渲染时钟来模拟比服务器时钟快的时钟估计;在这个帧和步子都对齐到 1/60 秒网格的模型中,快 10 毫秒在 200 个种子上仍然没有断档,快 25 毫秒则表现得如同 325 毫秒,所以正文中的 3 毫秒是上界给出的保证,而不是此模型开始断档的点。它是一个模型,不是来自两台设备的录屏。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
pret,
pokeemerald/data/maps/,16 个城镇的map.json文件,其中包括LittlerootTown、PetalburgCity和MauvilleCity,以及data/maps/PetalburgCity/scripts.inc(道馆男孩LOCALID_GYM_BOY,在map.json中为MOVEMENT_TYPE_LOOK_AROUND且无隐藏旗标,由PetalburgCity_Movement_BoyWalkToGym的移动走到道馆),提交731ad5b,2026 年 10 月 4 日访问,https://github.com/pret/pokeemerald/tree/master/data/maps ↩↩↩↩ -
Stardew Valley Wiki,“Modding:Schedule data”,2026 年 10 月 4 日保存,https://stardewvalleywiki.com/Modding:Schedule_data 。日程格式、游戏版本 1.5.1 时 Abigail 的原始数据、键格式及其顺序,以及 “Limitations” 说明。 ↩↩↩↩↩↩↩↩
-
Stardew Valley Wiki,“Friendship”,2026 年 10 月 4 日保存,https://stardewvalleywiki.com/Friendship 。每颗心的点数、好感度的升降方式、衰减表、礼物、生日倍数和心事件。 ↩↩↩↩↩↩
-
Nookipedia,“Friendship”,《集合啦!动物森友会》章节,2026 年 10 月 4 日保存,https://nookipedia.com/wiki/Friendship ↩↩↩↩
-
Nookipedia,“Reaction”,《集合啦!动物森友会》章节,2026 年 10 月 4 日保存,https://nookipedia.com/wiki/Reaction 。各版本的数量来自
measure_daily.py(注 2)。 ↩↩↩↩↩ -
Serebii.net,“Join Avenue”,《宝可梦 黑2·白2》章节,2026 年 10 月 4 日保存,https://www.serebii.net/black2white2/joinavenue.shtml 。粉丝网站,也是本功能所用的唯一来源。 ↩↩↩↩↩
-
Wikipedia,“Death Stranding”,Gameplay 章节,引用其自身来源,2026 年 10 月 4 日保存,https://en.wikipedia.org/wiki/Death_Stranding ↩↩↩↩↩
-
Krews,Arcturus Morningstar,由爱好者编写的 Habbo 服务器开源模拟器,
src/main/java/com/eu/habbo/habbohotel/rooms/Room.java(IDLE_CYCLES = 240、IDLE_CYCLES_KICK = 480、scheduleAtFixedRate(this, 500, 500, TimeUnit.MILLISECONDS)),2026 年 10 月 5 日保存,https://git.krews.org/krews/Morningstar/-/blob/master/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java 。是模拟器的常量,不是 Sulake 公布的数据;未阅读RoomUnit。 ↩↩↩↩↩↩↩ -
Glenn Fiedler,“Snapshot Interpolation”,Gaffer on Games,2014 年 11 月 30 日,2026 年 10 月 4 日保存,https://gafferongames.com/post/snapshot_interpolation/ ↩↩↩↩↩↩↩↩
-
Apple,“Game Center & Privacy”,“Children in Game Center” 章节与好友章节,2026 年 10 月 4 日保存,https://www.apple.com/legal/privacy/data/en/game-center/ ↩↩↩↩↩↩↩↩
-
Apple Developer Documentation,
GKLocalPlayer.isMultiplayerGamingRestricted(iOS 13.0+),文档 JSON 于 2026 年 10 月 4 日保存,https://developer.apple.com/documentation/gamekit/gklocalplayer/ismultiplayergamingrestricted 。引文为 Discussion 正文,其中对true的行内引用以单词呈现,并由草稿文件夹中的sources/extract_gk.py展平。 ↩↩↩↩↩↩↩↩↩↩↩↩ -
美国联邦贸易委员会(FTC),“Children’s Online Privacy Protection Rule”,最终规则,《联邦公报》(Federal Register),2025 年 4 月 22 日,文件号 2025-05904,90 FR 16918,2026 年 10 月 4 日保存,https://www.federalregister.gov/documents/2025/04/22/2025-05904/childrens-online-privacy-protection-rule 。DATES 章节以及修订后的 16 CFR 312.2(个人信息的定义)、312.5(c)(7)、312.8 和 312.10。本文关于该规则对 Kiradex 意味着什么的所有陈述,都是作者的解读,而非法律建议。 ↩↩↩↩↩↩↩↩↩↩↩
-
作者对 Kiradex 仓库(私有)的阅读,2026 年 10 月 5 日的工作树,只读,未运行任何 git 命令;文件以其 SHA-256 的前八位十六进制标识:
server/app/main.py(87c6cc9f;TICK_SECONDS = 0.5、SHOW_EVERY_SECONDS、消息处理器,其中包括第 223 行携带行走者id与图块的moved转发)、server/app/npcs.py(d84f12a5;模块 docstring、STEP_SECONDS = 0.7、四个定义及其路线、所求和台词)、server/app/rooms.py(dcd66866;第 115 行的Player.public(),在一个对象中返回 ID、昵称和形象,并在到达时于main.py第 203 行广播给房间;CAPACITY、MOVES_PER_SECOND、SAY_EVERY_SECONDS、Plaza,以及带 docstring 和提前返回的Room.tick)、server/app/lines.py(391f5391;23 句台词,以及说明服务器在到达时发送列表的 docstring)。 ↩↩↩↩↩↩↩↩↩↩↩ -
作者测量,
measure_kiradex.py(同一文件夹),2026 年 10 月 5 日重新运行,输出与measure_kiradex.out相同。它从注 20 中的工作树导入app.lines、app.rooms和app.npcs,并从PlazaRig.swift和PlazaClient.swift(注 50)读取客户端常量:预设台词数、房间容量、速率限制、拍、冷却、广场尺寸与到达点、四位收藏者的路线与环线长度、行走与跑步速度、6 步跳跃阈值和重连退避。它推出的 0.7 秒和 36% 已被注 6 取代。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Blake Crosley,“Pixel-Art Motion: Walks, Cameras and Doors on iPhone”,blakecrosley.com,2026 年 10 月 5 日,https://blakecrosley.com/blog/pixel-art-motion-on-iphone 。其注 6 在精确的 60 Hz 下为应用的行走建模:跑步每格 10 帧,每秒 6.00 格,而每秒 4 格乘以跑步的 1.6 意味着 6.4,在 120 Hz 下为 6.32;其关于现行代码的章节从
walk(to:)读出了一步中途第二次点按造成的回弹,其简报定下了绝不重置进行中一步的规则;其简报第 1 项把行走定为 60 Hz 下每格 16 拍、跑步 8 拍,即每秒 3.75 格和 7.5 格,也就是本系列的运动约定,其检查以-motionLog启动参数逐拍记录玩家位置。 ↩↩↩↩↩↩↩ -
Kiradex,
docs/WORLD.md(“Kiradex World: research and plan”),2026 年 10 月 5 日的工作树(SHA-256 c7714bc1),只读:第 2 节(儿童类别与 5.1.4 的路线,分级 9+ 或 12+)、第 3 节(Game Center 身份、Screen Time 控制、不设粉丝数、预设台词、律师的审阅)、第 5 节(截至 10 月 2 日建成的服务器:FastAPI 与 WebSocket,只有存在感,不存储任何东西,每秒 8 次移动,在设备上隐藏,“Not yet”(尚未)中包括按 Declared Age Range 的少年模式;以及促成它的推理,其中客户端以 8 到 10 Hz 发送位置与朝向),以及 “Decisions taken (Blake, October 2, 2026)”(已作出的决定,Blake,2026 年 10 月 2 日)第 6 项(分级与年龄段)和第 8 项(有起色前不请律师)。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
作者对 Kiradex 工作树中
Kiradex/和server/app/的搜索,2026 年 10 月 5 日,搜索isMultiplayerGamingRestricted、isUnderage、isPersonalizedCommunicationRestricted、Declared Age Range API,以及任何存储访问或每日对话的东西:无匹配。 ↩↩↩ -
pret,
pokeemerald/include/constants/event_object_movement.h,提交731ad5b,2026 年 10 月 4 日访问,https://github.com/pret/pokeemerald/blob/master/include/constants/event_object_movement.h ↩ -
图由
figures/make_figures.py于 2026 年 10 月 5 日绘制,该脚本保存在文章草稿旁,数据来自measure_npcs.py、measure_npc_cadence.py、measure_chat_trend.py、measure_record_mixing.py、measure_kiradex.py和measure_remote_walk.py已保存的输出;每个值都从这些文件解析,并在绘制前与预期值核对:预期值来自研究资料,但城镇人口图除外,它现在依据修订后的measure_npcs.py只绘制人物,并增加一行没有隐藏旗标的 100 人;台词簿中开放的 940 个词来自修订后的measure_chat_trend.py。在第二引擎审阅之后,流行图依据修订后的measure_chat_trend.py绘制游戏自己的整天分数(每经过一天一个点,而非连续的三角波),远程行走图依据修订后的measure_remote_walk.py绘制:现行 rig 用其自身的 32 位算术,缓冲则按运动约定的每秒 3.75 格和 7.5 格,延迟为 100、300、325 和 350 毫秒。台词簿图是简报提案的示意图,其对照数字以同样方式解析。未使用任何游戏美术、sprite 或地图渲染。 ↩↩↩↩↩ -
pret,
pokeemerald/src/event_object_movement.c(IsCoordOutsideObjectEventMovementRange、sMovementDelaysMedium、sMovementDelaysShort、带// Unused注释的sMovementDelaysLong、两种旋转类型固定的 48 帧等待、MovementType_WanderAround_Step4、GetWalkNormalMovementAction、sStep表),提交731ad5b,2026 年 10 月 4 日访问,https://github.com/pret/pokeemerald/blob/master/src/event_object_movement.c ↩↩↩↩↩ -
pret,
pokeemerald/include/constants/flags.h(DAILY_FLAGS_START与 12 个每日旗标),提交731ad5b,2026 年 10 月 4 日访问,https://github.com/pret/pokeemerald/blob/master/include/constants/flags.h ↩ -
Stardew Valley Wiki,“Villagers”,oldid 191346,2026 年 2 月 26 日编辑,2026 年 10 月 4 日保存,https://stardewvalleywiki.com/Villagers 。34 位可送礼村民是对该页列表的计数(6 位单身汉、6 位单身女性、22 位非结婚候选人);wiki 的 “Friendship” 页面统计的是另一组共 28 位初始镇民。 ↩
-
Stardew Valley Wiki,“Pierre”,2026 年 10 月 4 日保存,https://stardewvalleywiki.com/Pierre 。常规日程、六种日程变体,以及任何心数时和三颗心时的信件。 ↩↩↩
-
Nookipedia,“Hobby”,2026 年 10 月 4 日保存,https://nookipedia.com/wiki/Hobby ↩
-
Nookipedia,“Villager”,各作章节,2026 年 10 月 4 日保存,https://nookipedia.com/wiki/Villager ↩↩↩↩↩↩↩
-
Nookipedia,“Villager”,Personalities 章节,2026 年 10 月 4 日保存,https://nookipedia.com/wiki/Villager#Personalities ↩
-
pret,
pokeemerald/src/record_mixing.c(struct PlayerRecordEmerald、ReceiveOldManData、ReceiveDewfordTrendData),提交731ad5b,2026 年 10 月 4 日访问,https://github.com/pret/pokeemerald/blob/master/src/record_mixing.c ↩ -
pret,
pokeemerald/src/mauville_old_man.c(选择用的switch ((trainerId % 10) / 2)及其注释、ResetHipsterFlag、ResetMauvilleOldManFlag、SetHipsterTaughtWord)、pokeemerald/src/record_mixing.c(ReceiveOldManData末尾对ResetMauvilleOldManFlag的唯一调用)和pokeemerald/data/scripts/mauville_man.inc(setflag FLAG_UNLOCKED_TRENDY_SAYINGS、special HasHipsterTaughtWord)以及pokeemerald/data/maps/MauvilleCity_PokemonCenter_1F/map.json(脚本为MauvilleCity_PokemonCenter_1F_EventScript_MauvilleOldMan的对象事件),提交731ad5b,作者于 2026 年 10 月 4 日和 5 日阅读,https://github.com/pret/pokeemerald/blob/master/src/mauville_old_man.c 。训练家 ID 末位为 2 或 3 的游戏开局即有潮人,这是作者对该switch的算术。 ↩↩↩↩↩↩ -
Blake Crosley,“Pixel-Art Worlds on iPhone: What the 16-Bit Masters Knew”,blakecrosley.com,2026 年 10 月 3 日(法律常见问题于 2026 年 10 月 4 日更新),https://blakecrosley.com/blog/pixel-art-world-on-iphone 。它把流行语规则表述为每次混合记录带来潮人时解锁一个,其法律常见问题列出了针对 Palworld 开发商主张的三项专利(瞄准并投出捕获道具、捕获概率指示、骑乘可骑乘角色),并附有自己的来源。 ↩↩
-
pret,
pokeemerald/src/dewford_trend.c(关于trendiness和maxTrendiness以及保存流行的头部注释),提交731ad5b,2026 年 10 月 4 日访问,https://github.com/pret/pokeemerald/blob/master/src/dewford_trend.c ↩↩↩↩ -
作者测量,
measure_chat_trend.py(同一文件夹),2026 年 10 月 5 日修订并重新运行,输出保存为measure_chat_trend.out。修订版从开放词中减去标为.enabled = FALSE的条目(946 个条目,940 个启用),打印easy_chat_groups.h中训练家分组的numEnabledWords那一行(“Excludes Red, Green, Flame, Gold, Leaf, and Silver”,不含赤红、青绿、火焰、黄金、叶子和白银),并打印IsEasyChatIndexAndGroupUnlocked中的逐条目规则,按此规则,202 个条目的名字列表中的名字只有在其物种被见过时才开放;早先版本及其输出以.r1后缀保留。输入:pretpokeemerald731ad5b 的src/data/easy_chat/(分组大小、停用条目、numEnabledWords)、src/easy_chat.c(分组解锁的switch、逐条目规则和语句格子)、include/constants/global.h(MAIL_WORDS_COUNT、NUM_QUESTIONNAIRE_WORDS、SAVED_TRENDS_COUNT)以及src/dewford_trend.c(SeedTrendRng和每日步进)。第二引擎审阅后的第二次修订(上一版本及其输出以.pre-astra后缀保留)把峰值分布标为独立均匀近似:它由三次嵌套的Random() % 98抽取算出,每次都视为在 0 到 97 上的独立均匀抽取。它还加入了按 16 位取模权重计算的同一分布(src/random.c中的Random()返回高 16 位,因此余数 0 到 71 在 65,536 次中出现 669 次,72 到 97 出现 668 次),得到平均 62.89、80 及以下 81.34%,对应 62.90 和 81.33%,仍假设抽取独立;并以每次调用经过一天的方式移植UpdateDewfordTrendPerDay,从分数 0 上升开始,对峰值 30、64 和 127,首次回到该状态分别在 12、128 和 254 天之后(从每个可达的起始状态出发,周期长度相同)。12、25.6 和 50.8 天的升降长度是峰值的两倍除以 5,是连续近似。 ↩↩↩↩↩↩↩↩↩↩↩↩ -
pret,
pokeemerald/src/union_room_chat.c、pokeemerald/include/union_room.h和pokeemerald/include/constants/global.h,提交731ad5b,2026 年 10 月 4 日访问,https://github.com/pret/pokeemerald/blob/master/src/union_room_chat.c 。关于键盘聊天在那里为何可以接受的解读出自作者。 ↩ -
pret,
pokeemerald/src/easy_chat.c和pokeemerald/src/data/easy_chat/,提交731ad5b,2026 年 10 月 4 日访问,https://github.com/pret/pokeemerald/blob/master/src/easy_chat.c 和 https://github.com/pret/pokeemerald/tree/master/src/data/easy_chat ↩↩↩↩ -
Wikipedia,“Dark Souls (video game)”,Multiplayer 章节(引用其来源 [5])与 Awards 章节,2026 年 10 月 4 日保存,https://en.wikipedia.org/wiki/Dark_Souls_(video_game) ↩↩↩↩↩↩
-
Bandai Namco Entertainment Europe,“Dark Souls Remastered”,Key features,2026 年 10 月 5 日保存,https://en.bandainamcoent.eu/dark-souls/dark-souls-remastered 。Bandai Namco America 的页面保存下来只是一个仅有 127 字节文字的脚本外壳;FromSoftware 自己的说明未能获取。 ↩
-
PlayStation,“Death Stranding”,“Unique social strand gameplay” 章节,2026 年 10 月 4 日保存,https://www.playstation.com/en-us/games/death-stranding/ ↩
-
Wikipedia,“Splatoon (video game)”,Gameplay 与 Multiplayer 章节,引用其自身来源,2026 年 10 月 4 日保存,https://en.wikipedia.org/wiki/Splatoon_(video_game) 。未抓取 Nintendo 自己的 Splatoon 页面。 ↩↩
-
The Pokémon Company International,pokemon.com 上的游戏页面 “Pokémon Black Version 2 and Pokémon White Version 2” 和 “Pokémon Sun and Pokémon Moon”,2026 年 10 月 4 日保存,https://www.pokemon.com/us/pokemon-video-games/pokemon-black-version-2-and-pokemon-white-version-2 和 https://www.pokemon.com/us/pokemon-video-games/pokemon-sun-and-pokemon-moon ;两者都没有提到联合大道或节庆广场。未尝试 Bulbapedia。 ↩
-
Serebii.net,“Festival Plaza”,《宝可梦 太阳·月亮》章节,2026 年 10 月 4 日保存,https://www.serebii.net/sunmoon/festivalplaza.shtml 。粉丝网站,也是本功能所用的唯一来源。 ↩↩
-
Serebii.net,“Gyms”,Pokémon GO 章节,2026 年 10 月 4 日保存,https://www.serebii.net/pokemongo/gyms.shtml 。粉丝网站,也是本功能所用的唯一来源。 ↩↩
-
Solero,Houdini,由爱好者编写的 Club Penguin 开源服务器模拟器,
houdini/handlers/play/player.py(sp、ss、sj、sma、sl、sg和se处理器及其冷却;server_heartbeat,它记下时间、休眠 61 秒,然后关闭所有最后一次心跳早于该时间的客户端(if penguin.heartbeat < timer: await penguin.close(),读自保存的 HTML;保存的文本提取版把这一行弄乱了);以及server_egg_timer,它每分钟为玩家倒数允许的分钟数,在 7 分钟和 5 分钟时警告,在归零时断开连接),2026 年 10 月 4 日保存,https://github.com/solero/houdini/blob/master/houdini/handlers/play/player.py 。是原版客户端预期行为的证据,而非 Disney 公布的行为。 ↩↩↩↩↩↩↩ -
billsonnn,Nitro renderer,由爱好者编写的 Habbo 开源客户端,
src/nitro/room/object/logic/MovingObjectLogic.ts(DEFAULT_UPDATE_INTERVAL = 500以及update中的插值),2026 年 10 月 4 日保存,https://github.com/billsonnn/nitro-renderer/blob/main/src/nitro/room/object/logic/MovingObjectLogic.ts ↩↩↩ -
作者对 Kiradex 仓库(私有)的阅读,2026 年 10 月 5 日的工作树,只读:
Kiradex/World/PlazaRig.swift(SHA-256 819cd8d6;第 29 行作为Float的progress、第 101 行的tilesPerSecond、第 526 到 542 行的move(other:to:facing:)、第 614 行只在玩家身上设置的running、第 652 到 672 行的advance()、第 722 到 727 行的place()及其像素取整、含doze的idleEmotes列表)、Kiradex/World/TileMap.swift(db4272c2;第 60 行为 16 的tileSize和第 235 行的centre)、Kiradex/World/PlazaClient.swift(5c491933;第 165 行的重连延迟)、Kiradex/World/PlazaStage.swift(8b4850af;应用到广场的昼夜阶段,以及第 149 行的rig.update(Float(event.deltaTime)))和Kiradex/World/Daylight.swift(536350dc;Daylight.Phase及其小时范围,以及从设备自己的日历与时区Calendar.current获取小时的Daylight.now)。同一天也搜索了服务器的时钟:server/app/main.py和server/app/rooms.py使用time.monotonic(),server/app中没有任何东西指定时区。 ↩↩↩↩↩↩↩↩↩ -
Apple Developer Documentation,
GKLocalPlayer.isPersonalizedCommunicationRestricted(iOS 14.0+),文档 JSON 于 2026 年 10 月 4 日保存,https://developer.apple.com/documentation/gamekit/gklocalplayer/ispersonalizedcommunicationrestricted 。与注 18 一样为引用而展平。游戏自己的预设台词属于该页要求游戏停用的 “custom communication features”(自定义交流功能),而表情动作不属于,这两点是作者的解读。 ↩↩↩↩↩↩↩↩ -
Apple Developer Documentation,
GKLocalPlayer.isUnderage(iOS 4.1+),文档 JSON 于 2026 年 10 月 4 日保存,https://developer.apple.com/documentation/gamekit/gklocalplayer/isunderage 。与注 18 一样为引用而展平。 ↩↩↩↩ -
Apple Support,“Set up parental controls to manage your child’s iPhone or iPad”,“Set restrictions for Game Center” 章节,2026 年 10 月 4 日保存,https://support.apple.com/en-us/105121 ↩
-
Apple Newsroom,“Apple expands tools to help parents protect kids and teens online”,2025 年 6 月 11 日,2026 年 10 月 4 日保存,https://www.apple.com/newsroom/2025/06/apple-expands-tools-to-help-parents-protect-kids-and-teens-online/ ↩↩↩
-
Apple,“App Review Guidelines”,最后更新于 2026 年 6 月 8 日,指南 1.2、1.3 和 5.1.4,2026 年 10 月 4 日保存,https://developer.apple.com/app-store/review/guidelines/ ↩↩↩↩↩↩↩↩
-
美国联邦贸易委员会(FTC),“FTC Finalizes Changes to Children’s Privacy Rule Limiting Companies’ Ability to Monetize Kids’ Data”,新闻稿,2025 年 1 月 16 日,2026 年 10 月 4 日保存,https://www.ftc.gov/news-events/news/press-releases/2025/01/ftc-finalizes-changes-childrens-privacy-rule-limiting-companies-ability-monetize-kids-data ↩
-
作者对 Kiradex 服务器启动命令及其所运行 Web 服务器的阅读,2026 年 10 月 5 日,只读:
server/Procfile(SHA-256 2e883a7e)和server/railway.toml(17f8a3ba)以--proxy-headers --forwarded-allow-ips='*'、且不带任何日志选项的方式启动uvicorn app.main:app;server/requirements.txt(71c30c3b)锁定uvicorn[standard]==0.37.0。在服务器环境中安装的该版本里,uvicorn/config.py(379e9baf)把access_log默认设为 true;uvicorn/protocols/http/httptools_impl.py(b6e4010a)和h11_impl.py(e1bf8ab3)在其为 true 时为每个请求记录get_client_addr(self.scope);uvicorn/protocols/websockets/websockets_impl.py(029977b2)在uvicorn.error记录器上以 INFO 级别连同客户端地址记录'%s - "WebSocket %s" [accepted]',而--log-level warning会让它静默。这是对代码的阅读;没有运行服务器,也没有看过任何已部署的日志。 ↩↩ -
Blake Crosley,“Pixel-Art People: Characters and a Creator on iPhone”,blakecrosley.com,2026 年 10 月 3 日,第 4 节(PEGI 的规则)以及第 6、7 节(形象字符串、清单和绘制每个形象的锻炉),https://blakecrosley.com/blog/pixel-art-characters-on-iphone ↩↩↩
-
PEGI,“What do the labels mean?”,2026 年 10 月 3 日访问,https://pegi.info/what-do-the-labels-mean ,引自角色篇(注 58);本文未重新抓取。 ↩↩↩