Phigros 第九章《穹顶孤舟》网页解谜复盘:从 Chaocipher 到 13 段碎片拼字
这篇是 2026-09-25 晚上的解谜复盘。官方在 TapTap 发了一段密文,指向一个带倒计时的隐藏页,顺着挖下去是一整条链:Chaocipher、PNG 尾部拼接、零宽字符隐写、Nihilist 密码、按指纹分发的碎片,最后靠竖读拼出两个词。链尾还有一页「End」,而这一环的密码,就是 End 页那句 slogan。
先说清楚两件事:
链条里每一步的手法(Chaocipher 的字母表、PNG 切分位置、零宽字符映射、Nihilist 的减法、acrostic 的读法)都尽量用本地脚本复算过。
但这些步骤意味着什么——比如哪些页面是官方有意给的、13 段碎片为什么这样分发、两个拼出来的词最后要怎么用——大部分是 GPT 和 GLM 的推断。成链的关键步骤已经和 Phigros 解密 3 群(社区)交叉核对过(见文末致谢),但仍不是官方口径,看结论可以,别当成官方答案。
下面几处关键思路来自社区玩家,不是我们独立想出来的:outside_the_birdcage 的 FFT 线索、零宽字符 → 摩尔斯 → AROUSAL、以及标题 nihilist 指向的 Nihilist 解法(5×5 方阵 + AROUSAL 减法 → JUSTENGAGEWTH)。这些来自 QQ 群里的整理(__polar_ice 转述,Soucirif 等人的截图),字母表本身出自官方第九章 PV 简介。
主线:① 整条链 | ② Chaocipher | ③ 拆图 + FFT | ④ 氛围页 · 零宽 · 摩尔斯 | ⑤ Nihilist | ⑥ JUSTENGAGEWTH 的提示 | ⑦ 13 段碎片 | ⑧ 两个关键词 | ⑨ 尾声:读表 → End
一、从 Solivault 到 End:整条链的样子
一句话版:公告密文 → Solivault(倒计时页)→ 源码里那张图 → FFT → outside_the_birdcage → 13 段碎片排成表 → Cogito, ubi sit refugium → DISORIENTATION → JUSTENGAGEWTH → 栅栏 + 之字形读表 → NOL2re@etiKdA3!ig9t~UmSO(End 页)→ slogan 就是最终密码。
第九章的网页解谜入口不在游戏里,而是从两条公开线索接进林泊百科(wiki.pigeon-games.com):
- 官方公告线:TapTap 公告给的密文 + 两张字母盘 → Chaocipher 解出德语明文
WERJETZTALLEINISTWIRDESLANGEBLEIBEN→ 直接拼在 wiki 域名后面 → 隐藏页/WERJETZT…/(标题Solivault,倒计时到2026-10-02 17:00)。细节见 第 0 步。 - 网页源码线:某个网页的源码里藏着一张黑洞图 → 对它做 FFT → 浮出
Outside_the_birdcage→ 拼到 wiki 域名后 →/outside_the_birdcage/。细节见 第 1 步。
两条线都落在同一台 wiki 上。之后的 Cogito, ubi sit refugium、DISORIENTATION、JUSTENGAGEWTH、NOL2re@… 全都是同一个套路:把解出来的词当路径,一页一页走下去 —— 换句话说,每解出一个词,就等于拿到了下一站的链接。
两条来源,分开说清楚:
- 主干 + 最后一步(End 页地址):来自 Phigros 解密 3 群(第九章解谜的社区)。13 段碎片的收集与排序、「黄色格子顺时针读」、Nihilist 六步,以及最后「栅栏 + 之字形读表」读出
NOL2re@etiKdA3!ig9t~UmSO,都是社区一起解出来的;后面的 13×12 表格图和那张「变化后」的读表图,也来自社区。 - 本页的验证与细节:我们自己去抓的那部分 ——
start.png的字节切分、api.php的指纹分发、decodeCopy复算、JUSTENGAGEWTH复制提示池的采样,以及 End 页 / 《Sky of Your Vault》那两页的接口实测。
所以:链条是大家一起解出来的,这篇只是把它按顺序整理成一条线,再把能验的地方都验一遍。
第一步是入口 Solivault:
第二步起就在 wiki 里一页接一页往下走,终点是 End:
按顺序列一遍(每步都能在下面找到对应小节):
| # | 在哪 | 干了什么 | 得到 |
|---|---|---|---|
| 0 | TapTap 公告 | Chaocipher 解密 | 德语明文 → 隐藏页 Solivault |
| 1 | Solivault | 源码里的 start.png(尾部另外拼了一张同款图)→ 对它做 FFT | outside_the_birdcage |
| 2 | /outside_the_birdcage/ | slogan 由 api.php 按浏览器指纹分发 | 13 段碎片 |
| 3 | 13 段碎片 | 按末字母排成 13×12 表格(右侧列 = DISPOSSESSION、左侧列 = IGNORANCE) | 一张表 |
| 4 | 那张表 | 黄色格子顺时针读 | Cogito, ubi sit refugium |
| 5 | /Cogito,_ubi_sit_refugium/ | 文案里的零宽字符 → 摩尔斯 | DISORIENTATION |
| 6 | /DISORIENTATION/ | 标题 nihilist + Hint2「移除干扰」→ 5×5 方阵 + AROUSAL 减法 | JUSTENGAGEWTH |
| 7 | /JUSTENGAGEWTH/ | 标题 Fence?(栅栏密码)+ 复制出的提示 | 读法:之字形、从 NO 读到 SO |
| 8 | 回到那张表 | 照上面的读法读表 | NOL2re@etiKdA3!ig9t~UmSO = End 页 |
| 9 | End 页 | 读 slogan | 最终密码 |
| 10 | /Backward,…/ | 《Sky of Your Vault》星空留言墙 | 收尾(彩蛋) |
各阶段谜底速查
| 阶段 | 这一步在干什么 | 谜底 |
|---|---|---|
| 阶段一 | 源码里的黑洞图做 FFT | Outside_the_birdcage → /outside_the_birdcage/ |
| 阶段二 | 13 段碎片排成表,黄色格子顺时针读 | Cogito, ubi sit refugium → /Cogito,_ubi_sit_refugium/ |
| 阶段三 | 文案里的零宽字符 → 摩尔斯 | DISORIENTATION → /DISORIENTATION/ |
| 阶段四 | 标题 nihilist → 5×5 方阵 + AROUSAL 减法 | JUSTENGAGEWTH → /JUSTENGAGEWTH/ |
| 阶段五 | 标题 Fence? → 栅栏 + 之字形读表(从 NO 读到 SO) | NOL2re@etiKdA3!ig9t~UmSO → End 页 |
用到了哪些东西、在哪一步用上
| 手法 / 工具 | 用在哪一步 | 跳到 |
|---|---|---|
| Chaocipher(双盘轮转) | 公告密文 → Solivault | 第 0 步 |
PNG 字节切分(找第一个 IEND) | 拆 start.png,拿到页面真正渲染的那张图 | 第 1 步 |
| 傅立叶变换(FFT) | 从图里读出 Outside_the_birdcage | 第 1 步 |
| 零宽字符 → 摩尔斯 | 解出 AROUSAL / DISORIENTATION | 第 2 步 |
| Nihilist(5×5 方阵 + 数字相减) | 解出 JUSTENGAGEWTH | 第 3 步 |
| base64url + 逐字节 XOR | 复算 decodeCopy() 的 try for yourself | 第 4 步 |
浏览器指纹 + api.php | 13 段碎片按人分发 | 第 5 步 |
| acrostic 竖读 | DISPOSSESSION / IGNORANCE(排序与校验) | 竖读那一节 |
| 栅栏密码 + 之字形读表 | 从 13×12 表里读出 End 页地址 | 最后一环 |
| 接口只读探测 | 验证 End 页 / 星空留言墙的行为 | 星空留言墙实测 |
| 最终 | 读 End 页 slogan | Backward, go backward, turn back to the antemundane realm, go back to the - = 密码 |
二、第 0 步:Chaocipher 解出隐藏 URL
公告给了一段密文和两张字母盘:
- 密文:
ZGJGDGTDNUIOSQQCLIWWCWIIADNOFQDAEMG - 左盘:标准 A–Z
- 右盘:
PSZLMYQEBJARTFCHNUGVKDOXIW(来自官方第九章 PV 简介,社区玩家提供的链接是 BV16vbN6REhD)
Chaocipher 的规则按模型给的说法是:每解出一个字母,把命中的字母转到盘首,再把第 2 到第 14 位整体轮转一位。按这个规则解完得到:
WERJETZTALLEINISTWIRDESLANGEBLEIBEN断词是 WER JETZT ALLEIN IST, WIRD ES LANGE BLEIBEN —— 德语,「此刻孤独的人,会孤独很久」。把这串字母直接拼在 wiki 域名后面,就是一个隐藏页:
https://wiki.pigeon-games.com/WERJETZTALLEINISTWIRDESLANGEBLEIBEN/页面 <title> 是 Solivault,中间一个写死的倒计时,终点 2026-10-02 17:00。页面底部还留了两行注释,一行是 how to read code in phone?:add /index.html?code or /index.php?code,一行是 GUGUGU~。
更新(10-02 前):Solivault 开始播歌了,歌里藏着 SSTV 图
倒计时临近时这个页面换了壳:本体只剩一个几十行的引导脚本,先请求 state.php 拿状态({sound, open, sound_enabled, open_at, close_at}),再按状态加载 song.html(会响)或 nosong.html(静音)。音频是加密下发的:
我们用 Playwright 打开页面并 hook decodeAudioData,直接拿到了解密后的完整音频:单声道 44.1kHz / 16-bit / 111.356 秒的 WAV。
写完这篇之后我们复查了一次:主页恢复成了原来的老代码(start.png 两图拆分 + 倒计时),state.php 与 key.php 接口已撤(404 / 错误页),song.html 现在返回的是 wiki 回退首页 —— 整套加密音乐系统被完整移除了。所以上面这段「播歌 + SSTV」是限时窗口期的内容,我们抓到的音频与 SSTV 图是当时的完整存档;文内的音频试听与图片都来自那个窗口期。
这段声音的分析结论:
- BPM ≈ 139.7(能量包络自相关最强峰),频谱质心 2.6~3.5kHz,是一段带人声切片的电子曲。
- 语音识别(whisper base)在 60s 与 90s 处两次听到「Sper, sper, sper…」(罗马尼亚语「我希望」),但
no_speech_prob高达 0.83~0.91,更可能是节奏切片造成的识别幻觉,先存疑。 - 真正的彩蛋是 SSTV:每 0.43 秒(正好一个节拍)出现一次 1200Hz 同步脉冲,后面跟着 1500~2300Hz 的图像数据 —— 也就是把 SSTV 传输按节拍藏进了歌里。用 SSTV 解码器跑一遍,解出一张 320×256 的图(Robot 36 模式标准尺寸):

上图:SSTV 解码结果,横向噪纹是 SSTV 的固有失真。图来自游戏第九章 ARG 的 Solivault 页面。
倒放的音频里没有第二张图,正放这张就是全部。图上是一位黑发、黄绿色眼睛、白色裙装、黑色颈环的角色 —— 具体是谁、对应哪首新曲,等官方揭晓。
顺带核对了一个细节:key.php?n=1 的响应里还有个 au 字段,指向 c9.gaoice.run/Solivault-<每会话不同哈希>.png(1920×1080,裸 curl 会吃 403)。让浏览器正常加载页面后把图拿下来做像素级比对 —— 和第一阶段 start.png 里的 part1.png 相关系数 1.000、dHash 距离 0/992,就是同一张黑洞图。所以这首歌的「封面」没有新内容,本次更新唯一的新图就是上面的 SSTV 角色图。另外 state.php 里的 close_at 换算后正是 2026-10-02 17:00,和原倒计时一致。
这篇声音分析的完整实录(含音频在线试听、频谱图、解码脚本)单独成了一篇:Solivault 的歌里藏了什么:一次 SSTV 解码实录。
Chaocipher 的明文结果是确定的:URL 能直接打开,页面标题就是 Solivault。本文就按上面这套盘序与轮转规则来记,不再引外部文档。
how to read code in phone?: 那行注释当时很容易想歪,以为是「密码输入框」。后来确认 code-dialog.js 只是个「在手机上看页面源码」的小工具:加 ?code 显示当前页源码,?code=<文本> 显示指定文本。它是工具,不是谜题的一部分。
海外 IP 访问 wiki.pigeon-games.com 会随机拿到 HTTP 502 或者一个 773「访问被拒绝」页。同一时间用国内 IP 访问一切正常。后面所有请求都是在国内 IP 下做的。
三、第 1 步:start.png 里其实有两张图
链上位置:
Solivault→ 拆start.png→ FFT →outside_the_birdcage
Solivault 的 JS 会去 https://c9.gaoice.run/start.png 拉一张图,但它不用整张文件:它找第一个 PNG 的 IEND 块,把后面的字节当成另一张 PNG 渲染。
所以这个文件是「拼接」的。按 PNG chunk 结构走一遍就能看到边界:
import struct
d = open("start.png", "rb").read()pos, i = [], 8while i < len(d): ln = struct.unpack(">I", d[i:i + 4])[0] if d[i + 4:i + 8] == b"IEND": pos.append(i + 8) # 第一张 PNG 的结束位置 i += 12 + ln
tail = d[pos[0]:] # 3,722,963 − 3,139,455 = 583,508 字节sig = b"\x89PNG\r\n\x1a\n" # PNG 签名part2 = tail[tail.find(sig):] # find() 返回 4 → 正好跳过那 4 个垃圾字节open("part2.png", "wb").write(part2)拼接处很讲究:尾部数据开头有 4 个垃圾字节 ae 42 60 82,然后才是标准的 PNG 签名 89 50 4E 47 0D 0A 1A 0A。这 4 个字节的作用就是让浏览器把整张文件当成一张正常图片解析、不报错;而页面 JS 只认签名,从偏移 4 处切开。
| 文件 | 尺寸 | 格式 | 大小 |
|---|---|---|---|
| part1.png | 1920×1080 | 8-bit RGB | 3,139,455 B |
| part2.png | 1920×1080 | 8-bit colormap | 583,504 B |
两张图内容几乎一样,就是下面这张:一张黑洞的特写,一根针状的细线斜穿而过,紫灰色的虚化背景,纯意象。(第一眼看也像眼珠,社区习惯叫它“黑洞图”。)

上图:start.png 尾部藏的那张 1920×1080 图(社区叫它“黑洞图”),缩放后导出。文件本身不含文字,它就是这一关的「画面」。
社区里有人把官方壁纸(也就是这张黑洞图)做了傅里叶变换:频谱图里出现一个十字星芒,星芒上下各有一行被打散的白色文字,拼起来是 outside_the_birdcage。这直接指向 wiki 下的同名页面,也是我们后面能顺着找到 4 个氛围页的起因。
这属于社区发现,不是我们独立解出来的——记在这里是因为它确实在链条里。

上图:FFT 后的结果图,十字星芒处能看到 outside_the_birdcage。图来自 Phigros 解密 3 群(社区)。
四、第 2 步:氛围页、零宽字符与摩尔斯
链上位置:
outside_the_birdcage→ 氛围页 → 零宽字符 →AROUSAL/DISORIENTATION
wiki 下还有 4 个能直接访问的静态页,都返回 200:
| URL | 页面标题 |
|---|---|
/outside_the_birdcage/ | Birdcage |
/Cogito,_ubi_sit_refugium/ | .Bravo _Charlie ␣Delta |
/DISORIENTATION/ | nihilist |
/JUSTENGAGEWTH/ | Fence? |
注意 DISORIENTATION 的标题叫 nihilist —— 这是官方给的密码类型提示,后面会用到。
其中 Cogito,_ubi_sit_refugium 这个页名不是猜出来的:把 13 段碎片排成 13×12 的表格后,表格里黄色格子顺时针读就是这句 Cogito, ubi sit refugium(拉丁语,“我在想,避难所在何处”)。
黄色格子是哪几格?看 /outside_the_birdcage/ 页面里那些小三角形落在哪 —— 三角形指到的格子就是黄格,顺着它们顺时针读即可。
页面 slogan 也有问题:
In what sense, exactly, do we “truly” inhabit the “world”?
U+200B = 点(.)| U+200C = 划(-)| U+200D = 字母分隔
exactly, 和 do we 之间藏了 26 个零宽字符。Cogito 页的 slogan 里藏了 43 个。编码规则很直白:

上图:F12 里看到的零宽字符串。图来自 Phigros 解密 3 群(社区)。
import re
html = open("DISORIENTATION.html", encoding="utf-8").read()seq = re.search(r"[\u200b\u200c\u200d]+", html).group() # 26 个零宽字符table = {"\u200b": ".", "\u200c": "-", "\u200d": " "} # ZWSP=点, ZWNJ=划, ZWJ=字母分隔
print("".join(table[c] for c in seq)) # .- .-. --- ..- ... .- .-.. → AROUSAL也就是 U+200B 当点、U+200C 当划、U+200D 当字母分隔符,查摩尔斯表得到 AROUSAL。解出来 Cogito 页藏的词是它自己的 URL 名,两页互相印证了规则没解错。
这里拿到的 AROUSAL 不是终点,它是下一关的钥匙(这也是这一题设计得好的地方:线索先给,用处后面才出现)。
五、第 3 步:meta 里的数字是 Nihilist 密文
链上位置:
DISORIENTATION(标题nihilist)→ 5×5 方阵 + 减法 →JUSTENGAGEWTH
DISORIENTATION 页头部藏了一串数字:
<meta name=" " content="56 75 65 76 35 56 75 63 97 66 67 47 72">标题点名密码类型 → 去掉 I 排出 5×5 → AROUSAL 去重查表得到钥匙 → 循环相减 → 坐标查回字母 → JUSTENGAGEWTH
一共 13 个数。加上页面标题 nihilist,指向 Nihilist 密码:经典的 Nihilist 是「5×5 方阵 + 数字相减」,而这里两样东西都齐了 —— 官方 PV 简介给的那 26 个字母,去掉 I 正好排成 5×5:
| 行 \ 列 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| 1 | P | S | Z | L | M |
| 2 | Y | Q | E | B | J |
| 3 | A | R | T | F | C |
| 4 | H | N | U | G | V |
| 5 | K | D | O | X | W |
这里为什么要「去重」?页面上的 Hint2 已经把规则说了:你拿到的元素不能直接用,要观察加密规则、把干扰项去掉 —— AROUSAL 里重复的 A 就是要去掉的干扰,剩下的 AROUSL 才是钥匙。
于是流程就很清楚了:把 AROUSAL 去重(A R O U S A L → AROUSL)当钥匙,查方阵得到 31 32 53 43 12 14,循环重复这 6 个数去减 meta 里的 13 组数字,得到的坐标再查回字母:
ALPHA = "PSZLMYQEBJARTFCHNUGVKDOXIW" # 官方第九章 PV 简介给的字母表GRID = ALPHA.replace("I", "") # 去掉 I → 5×5 = 25 格
def code(ch): i = GRID.index(ch) return (i // 5 + 1) * 10 + (i % 5 + 1) # 行号列号拼成两位数,例:A → 31
def char(n): r, c = divmod(n, 10) return GRID[(r - 1) * 5 + (c - 1)] # 两位数 → 字母
meta = [56, 75, 65, 76, 35, 56, 75, 63, 97, 66, 67, 47, 72]key = [code(c) for c in dict.fromkeys("AROUSAL")] # 去重 → AROUSL → [31, 32, 53, 43, 12, 14]plain = [n - key[i % len(key)] for i, n in enumerate(meta)]
print("".join(char(n) for n in plain)) # JUSTENGAGEWTHkey codes : 31 32 53 43 12 14meta : 56 75 65 76 35 56 75 63 97 66 67 47 72明文坐标 : 25 43 12 33 23 42 44 31 44 23 55 33 41明文 : J U S T E N G A G E W T H于是这一环的答案是 JUSTENGAGEWTH,又是一个页面名:https://wiki.pigeon-games.com/JUSTENGAGEWTH/。
把这 13 个数直接当成十进制字节值(chr() 一下),会得到 8KAL#8K?aBC/H:
print("".join(chr(int(n)) for n in "56 75 65 76 35 56 75 63 97 66 67 47 72".split()))# 8KAL#8K?aBC/H这个读法本身没错,但它和这一环的解法无关——#、? 都不在 base64 字母表里,也看不出下一步。真正能走通的是上面的 Nihilist 减法。
六、第 4 步:JUSTENGAGEWTH 页,和「1 step」
链上位置:
JUSTENGAGEWTH→ 标题Fence?+ 复制出的提示 → 读出读法
打开 /JUSTENGAGEWTH/,注释写着 just need 1 step。它页面 JS 里有个 pendingCopy 字符串、一段 base64url + 逐字节 XOR 的 decodeCopy():
function decodeCopy(s) { let t = s.replace(/-/g, "+").replace(/_/g, "/"); while (t.length % 4) t += "="; const bin = atob(t); const bytes = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i) ^ (0x37 + (i % 29)); return new TextDecoder("utf-8").decode(bytes);}对页面里硬编码的 pendingCopy = "Q0pAGl1TTx5GLzQwMCEpIA" 本地复算(base64url 解码 → 每字节 XOR 0x37 + i % 29)得到明文 try for yourself,也就是”你自己点点看”。真实内容要走 api.php?act=copy(点在 slogan 上触发)拿。
补挖:c 其实是按人给的小提示
一开始以为 try for yourself 是固定占位符。补测发现:带上指纹去请求,c 会变,解出来的东西也不一样:
import base64, json, subprocess
def decode_copy(s): # 和页面里的 decodeCopy() 等价 t = s.replace("-", "+").replace("_", "/") while len(t) % 4: t += "=" raw = base64.b64decode(t) return bytes(b ^ (0x37 + i % 29) for i, b in enumerate(raw)).decode()
fp = "67bd94f64711f889db59d5c7f3341957"url = f"https://wiki.pigeon-games.com/JUSTENGAGEWTH/api.php?act=copy&fp={fp}"c = json.loads(subprocess.run(["curl", "-sS", url], capture_output=True, text=True).stdout)["c"]print(c, "->", decode_copy(c)) # eXcU -> NO-| 请求 | c | 解出来 |
|---|---|---|
不带 fp(默认) | Q0pAGl1TTx5GLzQwMCEpIA | try for yourself(占位符) |
fp=67bd94f6…(我们的指纹) | eXcU | NO- |
| 另一个指纹 | Gmt2 | -SO |
| 另一个指纹 | RVdOABsIAh4Kfw | row: 4? 5? |
| 另一个指纹 | axdlFWcT | \/\/\/ |
多采一轮定论:40 次请求里 33 次拿到(7 次是 wiki 那个老毛病 502,约 18%),分布是:
| 解出来 | 次数 |
|---|---|
try for yourself | 8 |
NO- | 8 |
\/\/\/ | 7 |
-SO | 7 |
row: 4? 5? | 3 |
所以提示池正好 5 条(4 条真提示 + 1 条占位符),按指纹抽——33 次抽 5 个值,期望各 6.6 次,实测 8/8/7/7/3,随机性的偏差不显著。而且同一个指纹永远是同一条(同一个 fp 连测三次都是 NO-),说明是 fp → 提示的确定性映射,不是每次请求现抽。
NO- 起点 | -SO 终点 | \/\/\/ 之字形 | row: 4? 5? 行号定位 | t 字符提示
这几条提示现在都能读通了 —— 它们说的就是「从哪读、怎么读」:
| 复制出来的提示 | 它的作用 |
|---|---|
NO- | 读表的起点:从表格里 NO 那两个字符开始读 |
-SO | 读表的终点:一直读到 SO 为止 |
\/\/\/ | 之字形读法(对上页面标题 Fence?=栅栏密码) |
row: 4? 5? | 行号定位:在表里确认行(第 4 行还是第 5 行),和起点一起决定从哪一格起读 |
t | 同组的字符提示(官方复制内容里给过这一条) |
try for yourself | 占位符(不带指纹请求时返回的那条) |
把提示池和标题 Fence? 合起来看,读法其实已经被讲完了:用栅栏密码、按之字形、从 NO 读到 SO。照这个读法回到那张 13×12 表格,读出来的就是 24 个字符的 NOL2re@etiKdA3!ig9t~UmSO,也就是 End 页的地址(细节见 9.5)。
(复现脚本:hint_pool.py,只读、请求之间自动放慢 2.5 秒。)
把 birdcage 的 api.php 试了一圈:act=copy / url / end / next / state / all / list —— 七个动作返回的是同一份数据({"index":2,"text":"IV7aN2emf9nS","total":13}),act 参数被直接忽略。也就是说这一侧没有”下一页/下一个 token”的口子。
七、第 5 步:核心机制 —— 按指纹分发 13 段密文
链上位置:
outside_the_birdcage的 slogan → 13 段碎片 → 13×12 表格
outside_the_birdcage 页的 JS 是这样的:算一份浏览器指纹(UA、语言、平台、CPU 核数、屏幕、时区、canvas、WebGL,四次 FNV-1a 拼成 32 位 hex),然后发给 api.php?fp=...,服务端返回一个 index 和一段短文本,页面把 slogan 替换掉。
几个关键观察:
- 必须是真浏览器。 裸 curl 要么拿到默认段、要么拿到 773/502 页——因为它要完整指纹和 Cookie。用 Playwright 开页面、监听
api.php的响应,才能拿到真实结果:
// browser_probe.js 的核心:别自己拼请求,监听页面真正发出的那个 api.phppage.on("response", async (res) => { if (res.url().includes("api.php")) console.log("[API]", res.status(), res.url(), "->", await res.text());});await page.goto("https://wiki.pigeon-games.com/outside_the_birdcage/", { waitUntil: "networkidle" });fp必须是 32 位 hex,否则 400。&index=N参数存在但被忽略,返回的仍是该指纹算出来的那一段。- 随机枚举 300 个 32-hex 指纹,同一个 IP 下只刷出了 index 0 和 index 2。说明分发里还混了 IP/会话维度,单机收不齐 13 段——这是一个强制社区协作的设计。
最后我手上只有 4 段,其余从社区截图里补齐:
| # | 密文 | 首字母 | 末字母 |
|---|---|---|---|
| 1 | ?aT?~!4*jK^D | ? | D |
| 2 | S6&Dj#f=6T+I | S | I |
| 3 | IV7aN2emf9nS | I | S |
| 4 | GFToCYqyuH8P | G | P |
| 5 | NF9etodigrAO | N | O |
| 6 | OU/@ig=!9~VS | O | S |
| 7 | R#m6Aya#N=$S | R | S |
| 8 | ALessK0@Mt=mE | A | E |
| 9 | N2riNdAuY-US | N | S |
| 10 | C4_tubAxU+/S | C | S |
| 11 | E&rJaM\9-_I | E | I |
| 12 | u-D4wBeN31_O | u | O |
| 13 | 1SR&fuH6AbuN | 1 | N |

上图:社区把 13 段碎片按末字母排好的表,两侧列就是 IGNORANCE / DISPOSSESSION。图来自 Phigros 解密 3 群(社区)。
破解:把字母竖着读
13 段碎片本身是「乱码」,单看没意义。把每段的最后一个字母从上往下读:
D I S P O S S E S S I O N → DISPOSSESSION(剥夺)frags = ["?aT?~!4*jK^D", "S6&Dj#f=6T+I", "IV7aN2emf9nS", "GFToCYqyuH8P", "NF9etodigrAO", "OU/@ig=!9~VS", "R#m6Aya#N=$S", "ALessK0@Mt=mE", "N2riNdAuY-US", "C4_tubAxU+/S", r"E&rJaM\9-_I", "u-D4wBeN31_O", "1SR&fuH6AbuN"]
print("".join(f[-1] for f in frags)) # DISPOSSESSIONprint("".join(f[0] for f in frags)) # ?SIGNORANCEu1再读每段的第一个字符那一列:
? S I G N O R A N C E u 11 2 3 4 5 6 7 8 9 10 11 12 13第 3 到第 11 个字符连起来正好是 IGNORANCE(无知);开头那个 ? 和结尾的 u、1 是干扰字符——和注释里那句 This hint has been destroyed by Chaos / remove any interference 对得上。

上图:DISORIENTATION 页里的 Hint2。图来自 Phigros 解密 3 群(社区)。
我们手上的 4 段(第 1、3、5、6 段)末字母正好是 D / S / O / S,和总串完全吻合,所以这个解法可以确认是对的。
两个词还能和官方别的文本对上:官方另有一段看起来是 Atbash 的密文(Lmob Xszlh xzm ...),按 Atbash 解读出来是「Only Chaos can shatter the mirage that plagues the ignorant Fool.」——IGNORANCE 对应 ignorant Fool,DISPOSSESSION 对应「穹顶孤舟」失去一切的主题。
顺带一提,JUSTENGAGEWTH 的标题是 Fence?(栅栏):碎片被「栅栏」切开、强制 13 个玩家各自领一段、再拼起来。这个解释是推断,但和机制数据对得上。
八、第 6 步:两个关键词是表格的排序与校验,不是「下一站」
链上位置:表格两侧 →
IGNORANCE/DISPOSSESSION(排序与校验)
先把死路说清楚:把这两个词塞回 URL 走不通 —— /DISPOSSESSION/、/IGNORANCE/ 都返回 200,但 wgPageName 是「首页」(MediaWiki 回退),也就是说 wiki 上没有这两个词条。
现在能确定它们真正的用途了:它们就是那张 13×12 表格的两侧列。
- 右侧(末字母那一列)从上往下读 =
DISPOSSESSION→ 用来确定 13 段碎片的排列顺序; - 左侧(首字母那一列)从上往下读 =
IGNORANCE(我们那份转录里混进了几个干扰字符,见上一节)→ 用来校验顺序对不对。
表格的左右两侧本身就是这两个词:右侧那一列竖读是 DISPOSSESSION(定顺序),左侧那一列竖读是 IGNORANCE(做校验)。所以它们不是通往下一站的地址,而是把表格拼对、拼准的钥匙 —— 拼对了,后面的「黄色格子顺时针读」和最后的「之字形读表」才成立。
九、尾声:End 页与《Sky of Your Vault》
链上位置:
JUSTENGAGEWTH→ 之字形读表 →End页 → 最终密码 → 《Sky of Your Vault》
第六步之后还有一页 —— /JUSTENGAGEWTH/ 那句「只差一步(only one step)」,指的就是它。
9.1 End 页:地址是一串 token,提示就是下一页
GET /NOL2re@etiKdA3!ig9t~UmSO/ → 200,<title>End</title>。它不是 wiki 词条(页面里没有 wgPageName),和 Solivault、几个氛围页是同一类独立页。页面里有漂移的三角形 canvas、一张背景图,以及一句可以点击复制的 slogan:
Backward, go backward, turn back to the antemundane realm, go back to the -这句话本身就是下一页的地址。 把它当 wiki 路径丢进浏览器:
https://wiki.pigeon-games.com/Backward,%20go%20backward,%20turn%20back%20to%20the%20antemundane%20realm,%20go%20back%20to%20the%20-/→ 200,标题变成 Sky of Your Vault。所以「backward / 回退」的意思是回到提示本身去找路,而不是去反转字符串。
已经确认,并且实际验证通过:Backward, go backward, turn back to the antemundane realm, go back to the - 就是这一环要交出的密码(答案),不是随手写的氛围词。
它一物两用:
- 当路径用:把它丢进 wiki 地址栏,就是终点页《Sky of Your Vault》的地址(本节上面那段);
- 当答案用:终点页那句「带着你刚刚获得的答案回到该去的地方吧」,说的就是它 —— 所以还要把它带回前面的流程里用掉。
也正因为它是密码,End 页才特意做了「点一下复制全文」,省得你手打这一长串(注意末尾那个孤零零的 - 也在密码里,别漏)。
顺手确认:/End/、/END/、以及把 token 整体反转的 /OSmU~t9gi!3AdKite@er2LON/ 都不是有效页面(会落到 wiki 回退首页 Limbo Wiki),只有原 token 是。
背景图也查了:background_NOL2re@etiKdA3!ig9t~UmSO.png(在 c9.gaoice.run 上)是 1920×1080 的普通图,第一个 IEND 之后只剩 4 字节——没有 start.png 那种「尾部再拼一张图」。
9.2 Sky of Your Vault:结局 + 星空留言墙
这一页带着自己的后端:index.php?api=...(注意不是 api.php)。打开约半秒后弹出对话框:
终于,你亦抵达了这里。带着你刚刚获得的答案回到该去的地方吧。还是说,你还在试图向着无序的宇宙呼唤,企图得到点什么回应?如果你想的话,说说便是,因为这已没有任何意义。这段话信息量不小:它明确说这里不用再解谜了,而是让你「带着刚刚获得的答案回到该去的地方」。页面的 <meta name="robots"> 还写着 noindex, nofollow, noarchive, nosnippet, noimageindex——官方不想让搜索引擎收录它。
关掉弹窗,星空和「说话条」才浮出来:三层星(far / mid / near,只有中、近层可点),能拖着转、有惯性;点星星会高亮并显示那颗星上的留言。然后是一个输入框——placeholder 是「向无序的宇宙说点什么……」,署名可以留空,正文上限 130 字。点发送之后:
- 随机挑一颗星(按离画面中心的距离做高斯加权),把你的话挂上去;
- 署名留空 → 显示
——无名; - 本地留一份:cookie
player_messages(base64url 的 JSON,最多 20 条、24 小时); - 上后端:
POST index.php?api=messages(带X-CSRF-Token),失败就只留本地。
| 调用 | 作用 |
|---|---|
GET index.php?api=bootstrap | 发一个 CSRF token,返回 {"ok":true,"csrf":"…","ttl":21600} |
POST index.php?api=messages | 提交一条留言 |
GET index.php?api=highlights&count=10&lang=zh | 拉别人最近的留言(前端随机打散后点亮 50% 的星星) |
GET …&count=50 | 连续用大 count 会被限流:429 |
9.3 只读实测:星空上到底写了什么
这一节只做了 GET,没往官方留言墙里写任何东西。要点是:highlights 光有地址不行,必须先拿到会话 cookie,再带上 CSRF。
UA='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36'VAULT='https://wiki.pigeon-games.com/Backward,%20go%20backward,%20turn%20back%20to%20the%20antemundane%20realm,%20go%20back%20to%20the%20-/'
curl -sS -A "$UA" -c jar.txt -o /dev/null "$VAULT" # 1) 先拿会话 cookiecurl -sS -A "$UA" -b jar.txt -c jar.txt "$VAULT/index.php?api=bootstrap" # 2) 拿 csrf# → {"ok":true,"csrf":"<32 字符>","ttl":21600}
curl -sS -A "$UA" "$VAULT/index.php?api=highlights&count=10&lang=zh"# → 401 {"ok":false,"error":"unauthorized"} ← 没有会话就是这样
curl -sS -A "$UA" -b jar.txt -H "X-CSRF-Token: $CSRF" -H "Referer: $VAULT" \ "$VAULT/index.php?api=highlights&count=10&lang=zh"# → 200 {"ok":true,"items":[{"id":"…","text":"…"}, …]}拉回来 16 条不同的留言,长度 8~40 字符,全是乱码风格短串、没有一条是自然语言:
8 q0cpcLTL 15 mJ8eq4yiDosyfy0 16 Wa3p_7F1-u8si4li 19 jSNFdjoW_yZ6RSYcR8g 24 -orGOJgC7t_NHXhhrgj8O7fr 29 UP8YXeFBvmwps9TQqCFPMDA6Ef1hQ 40 QGGip0WrIe-PKELkWUgu82WjtbMVU-IQYBcikoXK也就是说,玩家们不是在聊天,而是在把自己解出来的 token / 碎片「呼唤」进星空。这和弹窗那句「这已没有任何意义」完全对得上:留言墙是彩蛋,不是下一环。
9.4 这一页说明了什么
- 命名:隐藏页叫
Solivault,终点页叫 Sky of Your Vault——合起来就是这条 ARG 的名字。 - 密码已经确定并验证通过:
Backward, go backward, turn back to the antemundane realm, go back to the -(End 页 slogan)。它既是终点页的地址,也是这一环要交出的答案。 - 终点页不再需要解密:它是「结局 + 留言墙」,作用是收尾,并提醒你把上面那句密码带回该去的地方用掉。
- 也因此,
DISPOSSESSION/IGNORANCE更像是中途的路标而不是终点答案;它们是否还有别的用途,得看2026-10-02 17:00倒计时归零后游戏里会不会有对应入口。
9.5 End 页的地址是怎么来的:之字形读表(已由社区解开)
起点 NO → 之字形(\/\/\/)→ 终点 SO,读出 24 个字符。
正确的读法是「栅栏密码 + 之字形读表」:
/JUSTENGAGEWTH/的提示Fence?= 栅栏密码;- 复制出来的
\/\/\/= 之字形(zigzag); NO-是起点、-SO是终点(我们采样的提示池里就有这两条,只是当时没读出来);- 回到最开始那张 13×12 碎片表,按之字形从
NO读到SO,就得到 24 字符的NOL2re@etiKdA3!ig9t~UmSO。
这张表的每一格都来自玩家各自领到的那段碎片,所以不同玩家的表长得不一样 —— 但读法完全一样,完整流程就在本文里:
- 把 13 段碎片按末字母排成 13×12 表格(就是上面那张表);
- 在表里找到
NO作为起点(row: 4? 5?这个提示就是让你确认行号),再找到SO作为终点; - 按
\/\/\/提示的之字形(栅栏式)路径一格一格读,读到SO为止; - 读出来的 24 个字符就是
NOL2re@etiKdA3!ig9t~UmSO(红色路径见上图)。
对照本文的两张表图,这条流程可以一步步照着走,不需要再去找别的地方。

上图:社区那张“变化后”的表,红色格子标出了之字形的读取路径(从 NO 到 SO)。图来自 Phigros 解密 3 群(社区)。
社区记录里记了过程:拿到提示后,先被 NO- 和 -SO 吸引 —— 在表里确实能连出一条 24 字符的串,形状是「SO 开头、NO 结尾」。他们先把它连成 NOUf9/@etigod=!ig9~rAVSO,又按 row: 4? 5? 把栅栏栏数试到 5(因为 5 栏的锯齿图案长得像 \/\/\/),解出 NtgOeii9OU@g!~Sf/o=rV9dA —— 结果粘到地址栏跳回了主页,说明这一串是错的。
最后他们在表格本身上按 \/\/\/ 的之字形路径连字符(从 NO 读到 SO),才拿到正确的 NOL2re@etiKdA3!ig9t~UmSO。也就是说:Fence? 讲的不是”把串拿去跑工具”,而是在表里沿锯齿走。
至于直觉上最像的那条线索 —— NO- 和 -SO 正好是 token 的头两个字符 NO 和尾两个字符 SO —— 现在也说得通了:它们本来就是在告诉你从哪读到哪里。
致谢
这一页能写完整,是大家一起拼出来的:
- Phigros 解密 3 群:13 段碎片的收集与排序、表格的整理、
Cogito, ubi sit refugium的顺时针读法、零宽字符转摩尔斯、Nihilist 六步解法、栅栏 + 之字形读表得出 End 页地址,都出自这个群的协作。 - 群里在链上留下信息的几位玩家:
__polar_ice(转述整理)、Soucirif(黑洞图 FFT)、unDefFtr(指出 meta 串的 ASCII 读法)、以及最早分享 End 页链接的人。 - 官方侧的依据:TapTap 公告、第九章 PV 简介里的字母表、以及 Limbo Wiki 上那些氛围页。
没有这个群的”拼桌子”,最后那一环根本走不完 —— 谢谢各位。
十、信息来源
这一页里的东西不是一个人挖出来的,标一下出处:
| 内容 | 来源 |
|---|---|
第一章密文、26 字母表 PSZLMYQEBJARTFCHNUGVKDOXIW | 官方 TapTap 公告 / 第九章 PV 简介(社区玩家提供 B 站链接 BV16vbN6REhD) |
黑洞图 FFT → outside_the_birdcage | 社区玩家(QQ 群截图,Soucirif 等人) |
零宽字符 → 摩尔斯 → AROUSAL | 社区玩家(__polar_ice 转述的群内整理) |
标题 nihilist → 5×5 方阵 + AROUSAL 减法 → JUSTENGAGEWTH | 社区玩家整理 |
| 13 段碎片的两层 acrostic(DISPOSSESSION / IGNORANCE) | 社区集齐片段后共同读出 |
PNG 尾部切图、api.php 指纹抓包、decodeCopy 复算 | 我们本地脚本(browser_probe.js、grab_all.py、各解码脚本) |
End 页 →《Sky of Your Vault》这条尾巴、index.php?api= 只读实测 | 我们自己补挖(end_chain_probe.sh,只做 GET 不做 POST) |
| 本轮密码 = End 页 slogan | 已确认,并已实测验证通过(密码即那句 Backward, … go back to the -) |
13 段表格黄色格子 → Cogito, ubi sit refugium、Nihilist 六步、栅栏 + 之字形读表 → End 地址 | Phigros 解密 3 群(社区) |
提示池(NO- / -SO / \/\/\/ / row: 4? 5?)的采样统计 | 本地只读探测(hint_pool.py) |
| 每步「意味着什么」的解释 | 大部分由 GPT / GLM 推断,仅供参考 |
十一、环境与工具
| 事项 | 说明 |
|---|---|
| 海外 IP | wiki 全线 502 / 773,必须国内 IP |
| api.php | 需要真浏览器指纹 + 国内 IP;裸 curl 只能拿默认段或错误页 |
| playwright | npm i playwright + npx playwright install chromium-headless-shell,脚本 browser_probe.js |
| 抓碎片 | grab_all.py(随机指纹枚举)、grab_index.py(探测 index 参数) |
| 解码 | zw_decode.py(零宽摩尔斯)、combine.py(Chaocipher / 碎片拼接)、rot_clean.py |
| 防转录污染 | 关键计算全部改成「脚本写文件 → read 回读」再采信,不信终端里直接打印的结果 |
十二、最后再说一次
已核实、可复现的部分:start.png 的字节边界、零宽字符 → AROUSAL、decodeCopy 解出 try for yourself、Nihilist 减法 → JUSTENGAGEWTH、13 段末字母的 acrostic → DISPOSSESSION、提示池按指纹分配(NO- / -SO / \/\/\/ / row: 4? 5?)、尾声两页(End 页 slogan 当 URL → Sky of Your Vault;index.php?api= 的 csrf / 401 / 429 行为、留言全是乱码短串),以及最后的栅栏 + 之字形读表 → NOL2re@etiKdA3!ig9t~UmSO(社区)。
密码(已确认并实测验证通过):Backward, go backward, turn back to the antemundane realm, go back to the -。
仍带推测的部分:Chaocipher 那一步的盘序与轮转细节(明文结果能确认,规则按本文记的这套),以及 meta 数字按十进制 ASCII 读出的 8KAL#8K?aBC/H(另一种读法,不参与主线)。这些多来自 GPT / GLM 的推断,仅供参考。
倒计时到 10-02 17<00>00> 之后,如果游戏里真的出现了 DISPOSSESSION 或 IGNORANCE 的入口,这篇就当是提前写好的答案笔记;如果没出现,那就当我和一群玩家、外加两个模型,一起做了一次网页考古。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!



