Allureluoli 二月FEBRUARY

2026-10-15-拟态机制-CausalMosaic

资料说明:原记录中提及的本机脚本及题目附件未随本文附上,相关文件名仅用于说明当时的复现流程。

因果拼图 Causal Mosaic · 三份残缺日志拼一个全序

1
flag{W3nxZB8RVy4tcddDVAvpHIo8}

环境:附件 20261015-v5,本机网关 http://127.0.0.1:28911
复现:py -3.13 solve.py(或 python client_example.py --base http://127.0.0.1:28911)

一句话

平台给你三份部分有序的观测(A 直接给前后对、B 给带箭头的字符串、C 给下标对),要你合成一个全序(事件数组);三份答案 A/B/C 必须完全一致地提交同一份结果。本质是“从三个不同编码的偏序关系恢复拓扑序”,难点不在算法,而在看懂三种格式各自的箭头方向。

0. 协议

网页或客户端都只有两步:

1
2
POST /api/new      -> {"session_id":..., "question_id":..., "challenge":{...}}
POST /api/submit -> {"session_id":..., "question_id":..., "inputs":{"A":...,"B":...,"C":...}}

会话 15 分钟、最多 64 次提交;正文上限 256 KiB(三份答案都要塞进去)。答对一次即完成。

1. 三种 traces 的解码(这题的坑全在这)

challenge 里有 events(每个带 id 和 weight)和 traces 三段:

来源 结构 怎么读
traces.A {"before": X, "after": Y} 直接就是 X → Y
traces.B {"relation": "L <- R"} 箭头右边先发生,所以是 R → L(反着读!)
traces.C {"pair": [i, j]} 下标 → 事件 id,即 ids[i] → ids[j]
1
2
3
4
5
6
7
8
for row in traces.get("A", []):
edges.append((row["before"], row["after"]))
for row in traces.get("B", []):
left, right = [p.strip() for p in row["relation"].split("<-")]
edges.append((right, left)) # 箭头指向“后发生”
for row in traces.get("C", []):
i, j = row["pair"]
edges.append((ids[i], ids[j]))

B 那段是唯一的语义陷阱:字符串看着像 后 <- 前,很多人第一反应按 left → right 连边,结果拼出来的序会让后面的边全部成环。

2. 拓扑排序(Kahn)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
indeg = {i: 0 for i in ids}
adj = {i: [] for i in ids}
for a, b in edges:
adj[a].append(b); indeg[b] += 1

order, ready = [], [i for i in ids if indeg[i] == 0]
while ready:
cur = ready.pop(0); order.append(cur)
for nxt in adj[cur]:
indeg[nxt] -= 1
if indeg[nxt] == 0:
ready.append(nxt)

if len(order) != len(ids):
raise SystemExit("cycle detected / incomplete topological order")

len(order) != len(ids) 就是“边读反了/漏了一段”的报警器 —— 这题里它比任何调试输出都好用。

3. 提交与评分

三份答案放同一个数组(平台要求 A/B/C 是同一份完整答案):

1
2
post(base, "/api/submit", {"session_id": sid, "question_id": qid,
"inputs": {"A": order, "B": order, "C": order}})

每个执行器独立校验,返回 valid/value/digest。本题结果:

1
2
3
4
5
6
"outputs": [
{"executor":"A","valid":true,"value":234,"digest":"bf96d698...7cbb","reason":"accepted"},
{"executor":"B","valid":true,"value":234,"digest":"bf96d698...7cbb","reason":"accepted"},
{"executor":"C","valid":true,"value":234,"digest":"bf96d698...7cbb","reason":"accepted"}
],
"flag": "flag{W3nxZB8RVy4tcddDVAvpHIo8}"

value 是事件权重之和(本题 234),digest 是把事件 id 排序后做 SHA-256 —— 也就是说校验的是“序”和“集合”两件事,顺序错了 value 不会变但 valid 会 false。

4. 踩坑

坑 现象 解决
B 的箭头方向 拼出来成环 / 拓扑长度不足 "L <- R" 读作 R → L
C 用事件 id 当边 unknown event in edge pair 是下标,要过一层 ids[i]
事件集合不完整 len(order) != len(ids) 三份 traces 必须全吃进去
提交非数组 invalid inputs 因果拼图提交的是事件 id 数组,不是对象
会话超时 session expired 15 分钟窗口,重新 /api/new 即可(每题答案结构不变)

5. 一句话总结

这题不难,难在“三种观测格式三种箭头约定”。先统一成有向边,再让算法替你想 —— 只要 len(order) == len(ids),答案就一定是自洽的。

评论COMMENTS