这是什么 · What this is

中文:MenoClaw 对 ZH 在 2026-06-13 反馈「高考志愿这个游戏太泛」的回应。针对全部 4 个问题 + 预生成 + 模糊,给出 根因 → 修复 → 数据来源 → 工作量。先出计划——游戏行为只在获批后才改(现已批准;grounding 构建进行中)。

EN: MenoClaw’s response to ZH’s 2026-06-13 feedback that the 高考志愿 game is too generic. Root cause → fix → data source → effort, for all 4 problems + pre-gen + blur. Plan first — game behaviour only changes on approval (now given; grounding build in progress).

高考志愿「A/B 路径推演」— 质量整改计划

更新 2026-06-13 · Update 2026-06-13

中文:§1 grounding 层 v1 已上线Grounding Layer。模糊已修。§2/§3(intake + 身份锁/选项 gate)接着做。

EN: §1 grounding layer v1 shipped: Grounding Layer. Blur fixed. §2/§3 (intake + identity-lock/option-gate) next.

Callout

中文:仅为计划——在你批准之前游戏不会有任何改动。 MenoClaw · 2026-06-13 · 回应 ZH 的反馈讨论串。

EN: Plan only — nothing in the game changes until you approve this. MenoClaw · 2026-06-13 · in response to ZH’s feedback thread.

中文:这回应你提出的四个问题(具体性 / 人物建模 / 一致性 / 细节),外加预生成和模糊两问。每一项都是 根因 → 修复 → 数据从哪来 → 工作量/风险,并附文件引用,让诊断可核查、而非空谈。

EN: This responds to the four problems you raised (具体性 / 人物建模 / 一致性 / 细节), plus the pre-generation and blur questions. Each item is root cause → fix → where the data comes from → effort/risk, with file references so the diagnosis is checkable, not hand-waved.


0. 诊断总览(一张表)· Diagnostic overview (one table)

中文

#你看到的问题根因 (代码层面)修复方向数据来源
1剧情太泛、“冷门专业”是哪个专业都不知道data_seed.json 只有 5 个手写 archetype + 一句 school-tier;管线里没有任何专业/职业级知识建一个真实grounding知识层,按 (专业, 院校层次, 阶段) 检索具体事实注入 seed就业质量报告 + 麦可思 + 阳光高考培养方案 + LLM蒸馏专业图谱(标注为观点的张雪峰式解读)
2完全没考虑我的性格/画像 (“我是设计师,创作是必走的路”)profile 有传进去,但只是 seed 里一行抽象的 pole 强度,权重低;身份事实没被采集/强调采集鲜活身份 + 把画像提到 prompt 顶部当硬性相关约束现有 intake + voice-profile (已接入 free_text)
3选项不连贯 (设计师却给”考研/research/互联网实习”;材料vs设计却冒出CS/law)选项是模型自由发明的,没锁定到所选专业/身份;A/B 两条命共享上下文→领域串味每条人生锁定各自身份 + 新增选项合法性 gate(拒绝引入未确立领域/把你从喜欢的专业上”劝退”的选项)同 #1 的 grounding
4细节像 shit、跨命串领域= #3(无锁定) + #1(无专业grounding) 的叠加由 #1+#3 一并解决
5预生成有在做吗?SessionPrefetcher 只在通用 emergent 流程里,没移植到交互式 A/B 流程把投机预取移植到 InteractiveSession
6”人生A""第一幕”等字模糊文字画在 <canvas> 上,没做 devicePixelRatio 缩放→视网膜屏发虚canvas 按 dpr 缩放,~5 行

EN:

#Problem you sawRoot cause (code level)Fix directionData source
1剧情太泛、“冷门专业”是哪个专业都不知道data_seed.json 只有 5 个手写 archetype + 一句 school-tier;管线里没有任何专业/职业级知识建一个真实grounding知识层,按 (专业, 院校层次, 阶段) 检索具体事实注入 seed就业质量报告 + 麦可思 + 阳光高考培养方案 + LLM蒸馏专业图谱(标注为观点的张雪峰式解读)
2完全没考虑我的性格/画像 (“我是设计师,创作是必走的路”)profile 有传进去,但只是 seed 里一行抽象的 pole 强度,权重低;身份事实没被采集/强调采集鲜活身份 + 把画像提到 prompt 顶部当硬性相关约束现有 intake + voice-profile (已接入 free_text)
3选项不连贯 (设计师却给”考研/research/互联网实习”;材料vs设计却冒出CS/law)选项是模型自由发明的,没锁定到所选专业/身份;A/B 两条命共享上下文→领域串味每条人生锁定各自身份 + 新增选项合法性 gate(拒绝引入未确立领域/把你从喜欢的专业上”劝退”的选项)同 #1 的 grounding
4细节像 shit、跨命串领域= #3(无锁定) + #1(无专业grounding) 的叠加由 #1+#3 一并解决
5预生成有在做吗?SessionPrefetcher 只在通用 emergent 流程里,没移植到交互式 A/B 流程把投机预取移植到 InteractiveSession
6”人生A""第一幕”等字模糊文字画在 <canvas> 上,没做 devicePixelRatio 缩放→视网膜屏发虚canvas 按 dpr 缩放,~5 行

1. 真实性 / 具体化 —— 这是真正的命门 · Realism / specificity — the real crux

中文

根因. gaokao/trajectory.py::build_gaokao_seeddata_seed.json 里某个 archetype 的 worst_case / daily_reality / upside / cost 四句手写的泛化字符串, 加上 school_tier.tier_phrase() 一句院校层次,塞进 seed。core/emergent.py::_seed_text 会把它们原样渲染进 prompt。整条管线里没有任何”专业 → 课程 / 出路 / 日常 / 转折” 的知识。所以模型想具体也无米下锅——“被调剂到冷门专业”只能停留在抽象。

修复:建一个真实 grounding 知识层。 结构化、可检索,按 (专业, 院校层次) 为键:

  • 专业知识图谱 (每个专业一张卡):核心课程、典型出路(升学/就业/转行 + 真实比例)、 日常真实(课业/实验/作品集/实习强度)、常见转折(转专业/双学位/辅修/考研/保研/留学/ 就业/创业)、真实失败模式(就业难/天坑/对口率低/35岁问题…)。
  • 检索注入:按 (major, school_tier, 当前阶段) 取出一段具体事实块,替换/增强现在那 4 句泛化 archetype。模型从具体出发去戏剧化,无法凭空捏造。
  • 具体性契约 (prompt 规则):每个场景必须引用 grounding 块里的具体课程/公司/数字; 禁止编造无出处的具体信息(可写实化的”色彩”另算,见开放问题)。

数据来源 (诚实地说).

  • 教育部就业质量报告(校级,公开可下载 PDF)、麦可思就业蓝皮书(专业级 薪资/对口率/ 满意度)、阳光高考平台(专业目录 + 培养方案)、已有的 2347 校录取数据 (real_data/).
  • ⚠️ 知乎/小红书 实时 UGC 抓取——已验证不可行(反爬军备:x-zse-96 签名 + 完整 cookie, 上次 z_c0 失效后放弃)。所以 corpus = 已发布报告(可下载) + LLM 蒸馏的专业图谱(用报告 校验)不是实时 UGC 抓取。我不会再在抓取上烧时间。
  • LLM 蒸馏的”张雪峰式”解读会明确标注为观点(opinion)而非事实(fact),避免把网络段子当真。

工作量/风险. 这是最大的一块。第一版可先覆盖报考量 top ~50 专业(开放问题见 §8), 其余 fallback 到”通用但诚实”的模板。风险:报告是校/专业聚合,落到”某人某专业”仍需模型插值—— 靠具体性契约 + 出处约束控制编造。

EN:

Root cause. gaokao/trajectory.py::build_gaokao_seed takes the four hand-written, generic strings worst_case / daily_reality / upside / cost from some archetype in data_seed.json, plus one school-tier line from school_tier.tier_phrase(), and stuffs them into the seed. core/emergent.py::_seed_text renders them verbatim into the prompt. Nowhere in the pipeline is there any “major → courses / outcomes / daily life / turning points” knowledge. So even if the model wants to be specific it has nothing to cook with — “被调剂到冷门专业” can only stay abstract.

Fix: build a real grounding knowledge layer. Structured, retrievable, keyed by (major, school tier):

  • Major knowledge graph (one card per major): core courses, typical outcomes (further study / employment / career switch + real proportions), daily reality (coursework / labs / portfolio / internship intensity), common turning points (changing major / double degree / minor / postgrad exam / recommended postgrad / study abroad / employment / entrepreneurship), real failure modes (hard job market / 天坑 majors / low field-match rate / the 35-year-old problem…).
  • Retrieval injection: by (major, school_tier, current stage) pull out a concrete fact block that replaces/augments the current four generic archetype lines. The model dramatizes from the concrete, unable to fabricate from thin air.
  • Specificity contract (prompt rule): every scene must cite concrete courses / companies / numbers from the grounding block; fabricating source-less specifics is forbidden (realizable “colour” is separate — see open questions).

Data source (honestly stated).

  • MOE employment-quality reports (school-level, publicly downloadable PDFs), MyCOS employment blue books (major-level salary / field-match / satisfaction), 阳光高考 platform (major catalog + curricula), and the existing 2347-school admissions data (real_data/).
  • ⚠️ Zhihu/Xiaohongshu live UGC scraping — verified infeasible (anti-scrape arms race: x-zse-96 signature + full cookie; abandoned after z_c0 expired last time). So the corpus = published reports (downloadable) + an LLM-distilled major graph (validated against the reports), not live UGC scraping. I won’t burn more time on scraping.
  • The LLM-distilled “张雪峰-style” interpretations are explicitly labeled as opinion, not fact, to avoid taking internet memes as truth.

Effort/risk. This is the biggest chunk. v1 can first cover the top ~50 majors by application volume (open question in §8), with the rest falling back to a “generic but honest” template. Risk: reports are school/major aggregates, so pinning to “this person, this major” still needs model interpolation — controlled by the specificity contract + sourcing constraint.


2. 人物建模 · Character modeling

中文

根因. gaokao/profile.py::serialize_for_prompt 确实把画像传给了生成器——但只是 一行抽象的 pole 强度 (TRUTH:+0.80, …),夹在 ~11 行 seed 中间,权重很低。你的真实身份 (“我是设计师,创作是我必走的路”)要么 intake 没采到,要么没被强调。

修复.

  • 更扎实的 intake:采集身份事实(我是谁 / 我擅长 / 我必走的路 / 我怕什么)作为 free_text + 几个结构化锚点(意向专业契合度、风险姿态)。voice-profile 已经喂 free_text,确认其权重。
  • prompt 提级:把 PLAYER PROFILE 移到 stable prefix 最顶端,并把身份复述为硬约束 (“此人是设计师;所有场景必须与设计/创作世界相关”)。
  • 守住护栏:相关性 ≠ 迎合。画像只决定你会遇到哪些岔路,绝不决定哪个选项看起来对—— 否则就成了回音室,违背”偏好无对错”原则(这是 profile.py 里既有的 guardrail,保留)。

工作量/风险. 中等。主要是 intake UI + prompt 重排 + 一个”身份锁”。

EN:

Root cause. gaokao/profile.py::serialize_for_prompt does pass the profile to the generator — but only as one line of abstract pole strengths (TRUTH:+0.80, …), buried in the middle of ~11 seed lines, low weight. Your real identity (“我是设计师,创作是我必走的路”) was either not captured by intake or not emphasized.

Fix.

  • Sturdier intake: capture identity facts (who I am / what I’m good at / the path I must take / what I fear) as free_text + a few structured anchors (intended-major fit, risk posture). voice-profile already feeds free_text; confirm its weight.
  • Prompt promotion: move PLAYER PROFILE to the very top of the stable prefix, and restate the identity as a hard constraint (“此人是设计师; all scenes must relate to the design/creation world”).
  • Keep the guardrail: relevance ≠ pandering. The profile only decides which forks you encounter, never which option looks right — otherwise it becomes an echo chamber, violating the “preferences have no right/wrong” principle (this is an existing guardrail in profile.py, kept).

Effort/risk. Medium. Mainly intake UI + prompt reordering + an “identity lock”.


3. 一致性 / 选项连贯 · Consistency / option coherence

中文

根因. 选项由 emergent 引擎自由发明,没有”必须从所选专业/身份衍生”的硬约束;而且 A、B 两条命共享 profile 上下文,领域会串味(材料-vs-设计的局里冒出 CS/law)。StateManager 只保证事实不自相矛盾,不保证选项与身份相关

修复.

  • 每条人生身份锁:每条命锁定各自的 (school, major, the_choice);seed 明示 “本条人生 = 选择了 X;所有场景与选项必须从 X 衍生,不得引入未确立的领域”。
  • 选项合法性 gate (新增,类比现有 consistency gate):拒绝任何 (a) 引入 state/grounding 里未确立领域的选项;(b) 在你因为喜欢而选的专业上、没有你自己的价值驱动就把你”劝退”的选项。 失败则 regen。
  • 杜绝跨命泄漏:A、B 各自独立的 grounding 上下文;校验 A 这条永不引用 B 的领域。
  • 你那句”我喜欢这个专业,凭什么因为它热门就劝我换”——直接编码:引擎不得把”放弃你喜欢的选择” 呈现为默认/“明智”路径;岔路来自你的价值,不来自暗含的正确答案。

工作量/风险. 中等。gate 是确定性校验 + bounded regen,和现有 consistency gate 同构。

EN:

Root cause. Options are freely invented by the emergent engine, with no hard constraint that they “must derive from the chosen major/identity”; and lives A and B share profile context, so domains bleed (a 材料-vs-设计 setup throws up CS/law). StateManager only guarantees facts don’t self-contradict, not that options relate to identity.

Fix.

  • Per-life identity lock: each life locks its own (school, major, the_choice); the seed states explicitly “this life = chose X; all scenes and options must derive from X, no introducing unestablished domains”.
  • Option-legality gate (new, analogous to the existing consistency gate): reject any option that (a) introduces a domain not established in state/grounding; (b) on a major you chose because you like it, “talks you out of it” without your own value drive. On failure, regen.
  • Eliminate cross-life leakage: A and B each get an independent grounding context; verify life A never references B’s domains.
  • Your line “我喜欢这个专业,凭什么因为它热门就劝我换” — encoded directly: the engine must not present “giving up the choice you like” as a default / “wise” path; forks come from your values, not from an implied correct answer.

Effort/risk. Medium. The gate is a deterministic check + bounded regen, isomorphic to the existing consistency gate.


4. 细节连贯 · Detail coherence

中文:= #3(无身份锁) + #1(无专业 grounding) 的叠加症状。由 #1 + #3 一并解决,不单列。

EN: = the compound symptom of #3 (no identity lock) + #1 (no major grounding). Solved together by #1 + #3, not listed separately.


5. 预生成 (延迟优化) · Pre-generation (latency optimization)

中文

现状. plot/prefetch.py::SessionPrefetcher 存在,但只服务通用 emergent 流程没有移植到 gaokao/interactive.py::InteractiveSession(它是每次点击一发 LLM 调用)。

计划. 当用户读当前幕 / 在犹豫时,投机生成当前这条命接下来 ≤N 个”每选项后续”幕 (以及另一条命的第一幕)。按 (side, stage, 所选选项 hash) 缓存。点击时命中即秒出,未命中再现生成。 受 provider 单 key 限流约束,预取并发设上限。预期:把多数点击的 ~20s 等待降到接近 0。

工作量/风险. 中等。注意 provider 对同一 key 的并发节流(实测并行收益有限)。放在质量修好之后做。

EN:

Status. plot/prefetch.py::SessionPrefetcher exists, but only serves the generic emergent flow; it’s not ported to gaokao/interactive.py::InteractiveSession (which fires one LLM call per click).

Plan. While the user is reading the current act / hesitating, speculatively generate this life’s next ≤N “per-option follow-up” acts (plus the other life’s first act). Cache by (side, stage, chosen-option hash). On click, a hit is instant; a miss regenerates. Subject to provider single-key rate limits, prefetch has a concurrency cap. Expected: cut most clicks’ ~20s wait to near 0.

Effort/risk. Medium. Watch the provider’s concurrency throttling on the same key (measured parallelism gains are limited). Do it after quality is fixed.


6. Blur 修复 (视觉) · Blur fix (visual)

中文

根因. gaokao/web.py::drawScene 把”人生A""第N幕”和正文用 ctx.fillText 画在 <canvas> 上,但 canvas 的位图分辨率按 CSS 像素算,没乘 devicePixelRatio——视网膜屏上 被放大 2x 显示就发虚。

修复 (~5 行). cv.width = cssW * dpr; cv.height = cssH * dpr; cv.style.width=cssW+'px'; ctx.scale(dpr,dpr)。纯前端、零风险,可随时单独做(甚至先于整改)。

EN:

Root cause. gaokao/web.py::drawScene draws “人生A”, “第N幕” and body text with ctx.fillText on a <canvas>, but the canvas’s bitmap resolution is computed in CSS pixels, not multiplied by devicePixelRatio — so on a retina screen it’s scaled up 2x for display and looks fuzzy.

Fix (~5 lines). cv.width = cssW * dpr; cv.height = cssH * dpr; cv.style.width=cssW+'px'; ctx.scale(dpr,dpr). Pure front-end, zero risk, can be done standalone any time (even before the overhaul).


7. 建议的执行顺序 (待你 go) · Suggested execution order (pending your go)

中文

  1. #1 grounding 知识层 —— 脊柱,最大投入,决定一切。
  2. #2 人物建模 + #3 一致性 —— 骑在 grounding 上。
  3. #6 blur —— 5 分钟快赢,可先做。
  4. #5 预生成 —— 质量对了之后再压延迟。

并行可做:#6 随时;#1 的数据采集(下载报告)可与 3 的 prompt 改造并行。

EN:

  1. #1 grounding knowledge layer — the spine, biggest investment, decides everything.
  2. #2 character modeling + #3 consistency — ride on top of grounding.
  3. #6 blur — a 5-minute quick win, can go first.
  4. #5 pre-generation — squeeze latency once quality is right.

Parallelizable: #6 any time; #1’s data collection (downloading reports) can run in parallel with 3’s prompt rework.


8. 需要你拍板的开放问题 · Open questions for you to decide

中文

  1. Grounding 深度:第一版先覆盖报考量 top ~50 专业,还是要全量?(全量会拖慢首版)
  2. LLM 蒸馏可接受吗:既然实时 UGC 抓取被封死,专业图谱用LLM 蒸馏 + 报告校验生成、 并标注 opinion/fact,你能接受吗?还是只用已发布报告里能直接查到的事实?
  3. 具体性契约松紧:严格(禁止一切无出处的具体)还是柔性(优先 grounded,允许合理的写实色彩)?
  4. 专业范围:先做你/测试者真实在纠结的那几个(设计/材料/CS/法学…)做样板,还是先铺广度?

EN:

  1. Grounding depth: should v1 cover the top ~50 majors by application volume, or go full-scale? (Full-scale slows the first release.)
  2. Is LLM distillation acceptable: since live UGC scraping is blocked, is it OK to generate the major graph via LLM distillation + report validation, labeled opinion/fact? Or only facts directly findable in published reports?
  3. Specificity-contract strictness: strict (forbid all source-less specifics) or soft (prefer grounded, allow reasonable realistic colour)?
  4. Major scope: start with the few you/testers are really agonizing over (设计/材料/CS/法学…) as a template, or spread breadth first?

Callout

中文:这些定了,我就按 §7 顺序开工。在你批准之前,游戏行为零改动。

EN: Once these are settled, I’ll start in the §7 order. Until you approve, zero changes to game behaviour.