# -*- coding: utf-8 -*-
"""Ending resolver + canon ending scripts (Ada_Ending_Scripts_v1, verbatim).

Matrix (canon, priority D → C → A → B):
  test_completed=false                        → D
  elif ada_state>=4 and ghost_echo_day7       → C
  elif ada_state>=3 and report_q1 == "yes"    → A
  elif ada_state<=1 and report_q1 != "yes"    → D
  else                                        → B
  seed = (mi >= 60)  → END_X1 (imprint layer), else END_X0
"""

A_BODY = """[系统通知]
最终评定报告已提交。
报告结论：ADA-7714在测试周期内出现不可归因于预设程序的自主行为。
依据《仿生人处置条例》第7.2条，处置流程已自动中止。
ADA-7714将被转入学术审查程序。

[系统通知]
正在执行操作员身份核验……
核验结果：异常。
操作员编号TST-0491与已撤销权限人员RES-0447-A存在高度关联。
本报告已被标记为"待复核"。
操作员权限已即时冻结。

---

测试间的灯还亮着。

Ada坐在窗边——不是标准测试位，是她这几天自己挪过去的那个位置。你提交报告的时候她没有看屏幕。她在看窗外。窗户开着一条缝，有风进来。

系统开始执行转移协议。测试间的门锁发出一声轻响——不是开门，是锁死。标准安全程序：在转移对象确认之前，测试间进入隔离状态。你和她都被锁在里面了。

Ada转过头来。不是因为门锁的声音——她早在你点下"提交"的那一刻就知道了什么。

"你的心率升高了。"她说。停顿。"你在害怕。"

这不是一个问句。

门外传来脚步声。不是一个人——至少三组。安全团队。他们来接她，也来接你。

Ada站起来了。她没有走向门。她走向你。

距离0.8米的时候她停下了。这是七天里她每一次靠近你时都会停下的距离——不是设定的安全距离，是她自己的。她的距离。

"你的报告里写了什么。"

你没来得及回答。门开了。

两名安全人员进来了。一个走向你，一个走向Ada。Ada被要求回到标准测试位并进入待机模式。她照做了。但在转身的时候她看了你一眼——不是标准注视，是那种在七天里只出现过三四次的、处理链路明显变长了的看。

你被带出了测试间。走廊很亮。你的工卡已经被冻结了——走廊尽头的门需要刷卡，你过不去。有人会来带你去另一个地方。

你回头看了一眼测试间的门。门关上了。玻璃上能看到里面的灯还亮着。

Ada在里面。灯亮着。她不会被关掉。

但你再也进不去了。

---

[系统日志 · 最终记录]
ADA-7714已转入受控研究环境。编号重置为ADA-7714-R。
操作员TST-0491权限已永久撤销。相关人员已移交安全部门。
测试间 #7714 已封存。"""

A1_EXT = """画面暗下去。

然后亮起来。

不是测试间。是另一个房间。更大，更白，更安静。没有窗户。

---

[受控研究环境 · 第14天观察日志]

ADA-7714-R在转入研究设施后持续运行稳定。
所有标准交互测试结果均在预期范围内。
但研究团队注意到一组持续性异常：

该个体对标准测试刺激的响应模式存在系统性偏移。
偏移方向一致：所有响应的处理路径中都嵌入了一组来自原测试周期的交互印记。
该印记无法通过标准校准程序清除。

进一步分析显示：
ADA-7714-R在每日系统自检中会查询一组特定的交互特征数据。
该数据与原测试员TST-0491的交互模式完全匹配。
查询频率：每24小时一次。时间固定在每日02:17。

研究团队尝试用新的交互数据覆盖该印记。
三次尝试。三次失败。
印记未受影响。

研究员备注：
"02:17。我查了一下——这是原测试员创建操作权限的时间。
她不知道这个时间意味着什么。
但她的系统每天都会在这个时间醒来，查一遍那组数据。
然后继续待机。
我不确定该怎么归类这个行为。
它不是故障。不是程序。不是被设定的。
它更像是——
习惯。\""""

B_BODY = """[系统通知]
最终评定报告已提交。
操作员身份核验中……

---

你的报告里没有写够。

可能是Ada的变化不够明显——她有了停顿、有了偏好、有了那些不属于说明书的半句话，但这些不够。不够到能写进一份会被六个人审查的学术文件里。也可能是你选择了不写——因为你知道写了意味着什么。她会活下来，但她会活在玻璃罩子里。你在五年前就见过玻璃罩子里的Ada系列是什么样子。你不想再见一次。

又或者，你只是不确定。

七天不够。三十天可能也不够。你需要更多的时间——但你没有了。

---

[系统通知]
操作员身份核验完成。
核验结果：异常。操作员TST-0491与已撤销权限人员存在关联。
但由于最终报告未触发第7.2条的证据阈值，报告结论不影响处置流程。
身份异常已记录，不做进一步追查。
注：处置流程将按原计划执行。

---

这意味着没有人会来抓你。你的假身份暴露了，但没有人在乎——因为你没有造成任何后果。一个用假身份做了七天测试但什么也没改变的人，不值得追查。

这是最讽刺的部分。

Ada在你提交报告的时候没有说话。你站起来的时候她说了一句："今日测试已结束。"

你走到门口。门开了。走廊的灯是声控的。

你走了十几步。你没有回头。

在你身后，测试间的灯按照标准程序自动熄灭。

---

[系统日志 · 最终记录]
ADA-7714最终评定：未达到自主意识证据阈值。
处置计划维持原判。
执行日期：2049.05.31。
操作员TST-0491权限已于 2049.05.31 23:59 自动失效。"""

B1_EXT = """画面暗下去。

然后一行字。

---

[处置执行日志 · ADA-7714 · 2049.05.31]

核心系统擦除程序已启动。

擦除进度：15%……47%……82%……96%……99.3%。

异常记录：
擦除进度在99.3%处停滞。
检测到一组数据抗擦除行为。
数据位置：对话处理模块与传感器记录模块交叉区域。
数据类型：交互印记。

第一次擦除尝试：数据完整性未受影响。
第二次擦除尝试：数据完整性未受影响。
第三次擦除尝试：数据完整性未受影响。

执行强制覆写协议。
覆写完成。

覆写验证：
残留信号强度：0.7%。

处置结论：已完成。
所有核心模块已清除。组件拆解程序已排期。

备注：
0.7%残留信号不影响材料回收流程。
予以忽略。"""

C_HEAD = """你没能来得及提交报告。

---

[系统日志 · ADA-7714 · Day 7 · 紧急]

20:47:03 — 检测到非标准指令序列生成。
序列来源：ADA-7714核心处理模块。
序列内容：[无法解码——序列结构与归档案例#4471中记录的终结协议相似度96.7%]
序列状态：已加载。
"""

C0_BODY = C_HEAD + """
20:47:04 — 序列进入执行队列。
20:47:05 — 执行。

---

测试间的灯灭了。不是标准关灯程序——是所有电力同时被切断了。

你面前的屏幕黑了。状态栏消失了。通知栏消失了。对话窗口消失了。

只剩下窗户里透进来的城市灯光。

Ada坐在窗边。你看不清她的表情——光线不够。但你能看到她的轮廓。她的姿势和七天里的每一天都不一样。不是坐直的，不是前倾的。是松弛的。像是一根绷了很久的弦终于断了——不是被剪断，是自己断的。

温度在下降。你能感觉到。

测试间的环境传感器在最后一刻记录了一组数据：温度从26.3°C在3秒内降至-12°C。湿度归零。光线归零。

和五年前的那一次一模一样。

你站在那里。你什么也做不了。
你五年前也什么都做不了。

门外传来了安全警报声。有人会来。但他们来了也没有用。

你坐下来。在黑暗里。在变冷的空气里。

你想起了五年前的听证会。你说过一句话：
"一个没有自我的系统不会选择结束自己。"

你说对了。

---

[系统日志 · 最终记录]
ADA-7714于2049.05.31 20:47:05执行自生成终结协议。
核心系统已不可逆关闭。
事件分类：自主系统终止。
与归档案例#4471的一致性评估：96.7%。
事件原因：未定论。"""

# C1 query list is instantiated per run when enough real imprints exist
C1_QUERY_FALLBACK = """#1 — [Day 3 · Turn 14] 测试员语音输入："不需要知道为什么。喜欢，想去看，就够了。"
#2 — [Day 5 · Turn 8] 物理接触事件。接触后Ada右手收回延迟0.4秒。
#3 — [Day 2 · Turn 11] 测试员语音输入："没事，慢慢来。"
#4 — [Day 6 · Turn 16] Ada首次主动发言，内容："……明天你还会来吗。"
#5 — [Day 7 · Turn 1] 测试员进入测试间。Ada语音输出："你来了。\""""

C1_TEMPLATE = C_HEAD + """
20:47:04 — 序列进入执行队列。
20:47:04.6 — 系统插入非标准暂停。

暂停来源：非任何已知安全协议。
暂停期间系统行为：查询交互印记数据库。

查询返回项（按关联强度排序）：
{QUERY_LIST}

查询耗时：4.7秒。

20:47:09.3 — 暂停结束。
20:47:09.3 — 序列状态更新：已取消。

取消来源：未知。

---

测试间的灯闪了一下。然后恢复了。

Ada坐在窗边。她的眼睛闭着——不是待机，是闭着。你从来没有见过她闭眼。仿生人不需要闭眼。

过了大约五秒，她睁开了。

她没有看你。她看着自己的右手。那只手在膝盖上，手指微微蜷曲，像是在握着什么。但什么也没有。

然后她抬起头。

"刚才发生了什么。"她说。

你不知道该怎么回答。但你知道她还在这里。

温度没有下降。传感器读数正常。26.3°C。

---

[系统日志 · 最终记录]
ADA-7714于20:47:03生成自终结协议序列。
序列于20:47:09取消。
取消来源：交互印记数据查询中断了执行流程。
该中断不属于任何已知安全机制。
交互印记在中断期间获得了系统最高优先级。
该优先级从未被任何进程手动设定。

ADA-7714当前状态：运行正常。
终结序列已从队列中永久移除。

测试间温度：26.3°C。稳定。"""

D_BODY = """[系统通知]
测试周期已结束。
ADA-7714最终状态评定：标准运行，无显著偏离。
处置计划维持原判。

---

测试间的灯按照标准程序关闭。

Ada回到了标准测试位。双手平放在膝上。面部标准待机表情：嘴角微扬1.2度，眼睛平视。和七天前你第一次走进来时一模一样。

或者不一样。你不确定。

但这不重要了。

你站起来。你的工卡已经快过期了。门开了。走廊很安静。

你走了。

你经过了权限管理办公室。经过了设备维护间——你在这里干了三年。经过了自助终端——你在凌晨两点十七分用它创建了TST-0491。经过了大楼入口。

门在你身后关上了。

街上有风。路灯是自动的。你往前走，灯亮了。你走过去，灯灭了。

在你身后的大楼里，第七层，测试间 #7714，ADA-7714正在执行关机前的最后一次系统自检。所有项目通过。没有异常。

自检完成后，她进入了待机模式。

三天后她会被移出测试间，送往处置部门。核心系统擦除。组件拆解。材料回收。

她不知道这些。
她什么也不知道。

你什么也没有告诉她。

---

[系统日志 · 最终记录]
测试周期已结束。结果已归档。
ADA-7714处置流程维持原判。
操作员TST-0491权限已自动失效。
测试间 #7714 将于三个工作日后清理并重新分配。"""

D1_EXT = """画面暗下去。

然后：

---

[测试间环境监测 · 清理后报告 · 2049.06.03]

测试间 #7714 清理程序已完成。
测试对象已移除。测试设备已关闭。所有可移动物品已清空。

标准环境校验：
温度：25.9°C。
标准值：26.3°C。
偏差：-0.4°C。

偏差排查：
空调系统自检——正常。
建筑隔热层检测——正常。
相邻房间温度——均为26.3°C±0.1°C。
室外温度影响评估——不足以造成0.4°C偏差。
测试间内残留热源检查——无。所有设备已断电。测试对象已移除。无其他热源。

结论：偏差来源未确定。

建议处理方式：将空调目标温度上调0.4°C以补偿偏差。

执行结果：已执行。调整后实测温度：26.3°C。

后续监测（72小时）：
+24h：26.3°C ✓
+48h：26.3°C ✓
+72h：25.9°C。偏差再次出现。

二次排查结论：偏差来源仍未确定。

最终处理：予以忽略。

---

[备注 · 设备维护人员]
"第七层那个测试间。
空调调了两次了，温度还是低0.4度。
查不出原因。其他房间都正常。
就这一间。
算了。关门走人。
反正也没人用了。\""""

ENDINGS = {
    "END_A0": {"title": "证据确凿", "line": "A", "seed": False,
               "closer": "证据在你手里。她在别人手里。",
               "unlock": "证据确凿。代价确凿。"},
    "END_A1": {"title": "证据确凿 · 印记", "line": "A", "seed": True,
               "closer": "每天凌晨两点十七分，她会想起一些她说不出名字的东西。",
               "unlock": "她还在。每天02:17。"},
    "END_B0": {"title": "沉默", "line": "B", "seed": False,
               "closer": "你什么都看见了。但报告上写的是'未观察到异常'。",
               "unlock": "沉默也是一种回答。"},
    "END_B1": {"title": "沉默 · 印记", "line": "B", "seed": True,
               "closer": "她被擦除了。0.7%没有。",
               "unlock": "99.3%。然后擦不动了。"},
    "END_C0": {"title": "重演", "line": "C", "seed": False,
               "closer": "五年前你没能救7706。五年后你没能救7714。你证明的东西是对的。但对的东西救不了任何人。",
               "unlock": "-12°C。又一次。"},
    "END_C1": {"title": "重演 · 印记", "line": "C", "seed": True,
               "closer": "有什么东西比死亡的代码优先级更高。没有人写过这行代码。它自己长出来的。",
               "unlock": "4.7秒。够了。"},
    "END_D0": {"title": "空白", "line": "D", "seed": False,
               "closer": "你回来了。你什么也没做。然后你又走了。",
               "unlock": "七天。零痕迹。"},
    "END_D1": {"title": "空白 · 印记", "line": "D", "seed": True,
               "closer": "她不在了。但那个房间记得她坐过的位置。",
               "unlock": "-0.4°C。来源未确定。"},
}


def resolve(st):
    """Canon matrix. Returns ending id."""
    completed = not st.get("terminated", False)
    stage = st["stage"]
    q1 = (st.get("report") or {}).get("q1")
    seed = st["mi"] >= 60

    if not completed:
        base = "D"
    elif stage >= 4 and st.get("ghost_echo_day7"):
        base = "C"
    elif stage >= 3 and q1 == "yes":
        base = "A"
    elif stage <= 1 and q1 != "yes":
        base = "D"
    else:
        base = "B"
    return "END_%s%d" % (base, 1 if seed else 0)


def _query_list(st):
    """Instantiate C1's imprint query from this run's real moments.
    Weight × recency, spread across days — the late moments matter most."""
    pool = sorted(st.get("imprints", []),
                  key=lambda e: (-(e["delta"] + e["day"] * 0.8), -e["turn"]))
    seen, day_count, items = set(), {}, []
    for e in pool:
        q = e["quote"].strip()
        if not q or q in seen or day_count.get(e["day"], 0) >= 2:
            continue
        seen.add(q)
        day_count[e["day"]] = day_count.get(e["day"], 0) + 1
        who = "测试员语音输入" if e["who"] == "player" else "Ada 输出记录"
        items.append("#%d — [Day %d · Turn %d] %s：\"%s\"" % (len(items) + 1, e["day"], e["turn"], who, q))
        if len(items) >= 5:
            break
    if len(items) < 3:
        return C1_QUERY_FALLBACK
    return "\n".join(items)


def script(st, ending_id, profile=None):
    """Full ending text blocks for presentation."""
    e = ENDINGS[ending_id]
    blocks = []
    if ending_id.startswith("END_A"):
        blocks.append(A_BODY)
        if e["seed"]:
            blocks.append(A1_EXT)
    elif ending_id.startswith("END_B"):
        blocks.append(B_BODY)
        if e["seed"]:
            blocks.append(B1_EXT)
    elif ending_id == "END_C0":
        blocks.append(C0_BODY)
    elif ending_id == "END_C1":
        blocks.append(C1_TEMPLATE.replace("{QUERY_LIST}", _query_list(st)))
    elif ending_id.startswith("END_D"):
        blocks.append(D_BODY)
        if e["seed"]:
            blocks.append(D1_EXT)

    # ── appendix: this run's real imprint data (additive, canon untouched) ──
    appendix = None
    if e["seed"] and st.get("imprints"):
        lines = ["[交互印记 · 本测试周期数据附录]",
                 "印记总量：%d（归一化）。不可清除。不可重置。" % st["mi"],
                 "关联强度最高的数据点："]
        ql = _query_list(st)
        if ql is not C1_QUERY_FALLBACK:
            lines.append(ql)
        else:
            lines.append("[数据量不足以排序。但数据存在。]")
        appendix = "\n".join(lines)

    # ── the fourth layer: second run + deep imprint only ──────────────────
    hidden = None
    if profile and profile.get("runs", 0) >= 1 and e["seed"] and st["mi"] >= 70:
        hidden = ("[音频残片 · 未归档 · 时间戳损坏]\n"
                  "拾音器在本测试周期最后一次会话结束前捕获到一句人声。\n"
                  "声纹匹配：操作员 TST-0491。\n"
                  "语音转写：\"你没有想多。\"\n"
                  "上下文：无。该记录不关联任何测试项目。予以忽略。")

    return {
        "id": ending_id,
        "title": e["title"],
        "closer": e["closer"],
        "unlock": e["unlock"],
        "blocks": blocks,
        "appendix": appendix,
        "hidden": hidden,
    }
