Plan to lift the 高考志愿 application out of the generic reflection-game repo into its own well-structured codebase, with a safe old→new transition. Plan only — no code moved yet. Grounded in an actual dependency scan of meno-sh/reflection-game @ main.

0 · TL;DR

  • gaokao is ~3.7k LOC (Python, excl. mini-program); it touches the generic engine in only 3 small seams. It is ~95 % self-contained → separation is low-risk.
  • The live ranked engine (ranked.py) has zero direct dependency on the generic code; the coupling enters only via the scene generator (trajectory.py/demo.py) and the legacy interactive.py.
  • Strategy: extract → run old & new in parallel → verify byte/contract parity on golden replays → cut over. The old codebase keeps serving live inference until the new one is verified.
  • Target: a core/ engine well under the 5k-LOC soft cap (estimated ~2.0–2.5k), plus one folder per functionality.

1 · Dependency analysis — how coupled is it?

The app lives in reflection-game/gaokao/. Its only ties to the generic engine:

SeamGeneric module (LOC)Imported byWhat’s actually used
Scene-JSON parse/validatecore.emergent (400)trajectory.py, interactive.pyparse_emergent_json, validate_emergent_payload, EmergentValidationError
State machinecore.state_manager (301)trajectory.py, interactive.pyStateManager (imported; appears near-unused — no method calls found)
Value dimensionshuman_modeling.personality (292)profile.py, voice_profile.py, compare.pythe DIMENSIONS constant
  • ranked.py (the live engine) imports only gaokao.* (sources/prompts/profile) → no generic coupling.
  • Generic code referenced ≈ 993 LOC across 3 files, but only a handful of functions/one constant are used → the extractable surface is a few hundred LOC + the DIMENSIONS data.
  • Everything else gaokao needs (WTE/ranked, grounding, resolve, profile, prompts, scenes & dimensions data, web server) is already inside gaokao/.

Verdict: separation = copy 3 small seams into the new core/, then drop the rest of reflection-game (the TRPG plot/, arcs, etc.). No deep entanglement.

2 · How the gaokao game plays out (core flow)

intake (4 pages: 专业选择 / 纠结+宏观思考 / 价值权重+去向 / 确定度)rank_start (build 画像 + first scene) → per-stage loop: WTE / unified_turn picks the next value-dimension (VOI-style) and generates one scene — top vs contender, with grounding-backed takes → user reorders / per-stage AI chat / elicits confidence → rank_next … (until cap or “看结论”) → rank_endrank_report (reflective report). Cross-cutting: grounding layer (阳光高考 facts: major_grounding/uni_employment), resolve (专业@学校 → canonical), profile (load-bearing 画像, incrementally updated each scene).

3 · Proposed structure of the new repo

gaokao-engine/                  (new standalone repo)
  core/                         minimal engine — target < 5k LOC
    ranked.py  wte.py           world-turning engine: dimension selection + scene gen (unified_turn)
    profile.py                  human model: load-bearing 画像 + incremental update
    grounding.py  resolve.py    grounding (阳光高考 facts) + 专业/学校 resolution
    prompts.py  sources.py
    emergent.py                 ← extracted from core.emergent (scene-JSON parse/validate)
    dimensions.py               ← the DIMENSIONS list (from human_modeling.personality)
  human_modeling/               variants of human models (as reflection-game did)
  serving/        web.py        the real-time HTTP inference server (kept OUT of core/)
  data/                         grounding/employment/scenes/dimensions JSON (the factual layers)
  developer/                    tooling: build_grounding, enrich, enumerate, export, eval/
  prompts/                      prompts.json (+ defaults)
  miniprogram/                  the WeChat MP (or split to its own repo — your call)
  tests/                        golden-replay parity tests

LOC budget: runtime ≈ 2.9k now; move the web server (~1.0k → serving/) and tooling (~0.75k → developer/) out, and core/ lands at ≈ 2.0–2.5k LOC — comfortably under the 5k soft cap, small enough for direct oversight.

4 · Transition plan (the delicate part)

Old keeps serving live until new is verified; then cut over. Your three requirements:

4.1 Preserve running data. The live game persists via the box listener (session_log.jsonl over the cloudflared tunnel, GAOKAO_LOG_SINK). The new engine writes to the same sink with the same event schema (start/stage/chat/end/report) → no data migration; both append to the same log. Freeze the event schema as a doc first. In-flight sessions (sid → state, used by rank_revisit) keep the same persisted format so a cutover never strands a live game.

4.2 New engine bug-free. (a) Golden-transcript replay — capture N real sessions’ full request/response traces from the OLD engine, replay the same inputs through the NEW engine, assert parity (pin the model; compare structure/fields, not exact LLM prose). (b) Contract tests on every endpoint against API.md. (c) Shadow run — mirror a copy of live traffic to the new engine (no user-facing effect), diff outputs until clean.

4.3 Experience exactly the same. The new engine serves the identical API.md contract, so the web + MP frontends are unchanged (just repoint the backend URL). Byte-diff the served rank_page.html and the API responses on the golden set. Cut over only after shadow parity is clean; keep the old deployable hot for instant rollback for N days.

4.5 雨来 contract-parity (hard gate before cutover). 雨来’s platform integrates against our API, so before flipping the yulai deploy to the new repo, verify — as an explicit acceptance checklist:

  • Every endpoint 雨来 uses (resolve_options, rank_start, rank_next, rank_reorder, rank_chat, rank_end, rank_report, rank_revisit, voice_profile) returns byte/shape-identical responses old-vs-new on the golden set.
  • Auth unchanged: the new yulai deploy carries the same GAOKAO_API_KEYS (雨来’s existing key) → 401 without / 200 with, exactly as today.
  • Same URL: yulai.gaokao.meno.sh cut to the new repo (same Render service swaps repo, or DNS → new deploy) → 雨来 changes nothing on their side.
  • Rollback ready: old yulai deploy kept hot for instant revert. Only flip 雨来’s deploy after all four are green. (雨来 sees zero change.)

4.4 Sequencing. ① extract the 3 seams + assemble core/ (no behavior change) → ② stand up new serving/ on the same data + sink → ③ golden-replay + shadow-run vs the live old engine → ④ cut over at a low-traffic window, old stays as hot rollback → ⑤ decommission old.

5 · Open decisions for you

  • Repo name + does the mini-program live in the same repo or its own?
  • StateManager: it looks near-unused in ranked mode — confirm during extraction whether we drop it entirely (further shrinks core/).
  • Web server in core/ or serving/? I propose serving/, keeping core/ pure engine (better for your <5k oversight goal).
  • Go order: do you want me to start with the dependency-freeze + extraction (step ①) as a no-risk first PR (new repo, old untouched), then we gate steps ②–⑤ on your review?