#!/usr/bin/env python3 """exp15 — build the EXPANDED S2' (full-power, owner decision 2026-07-18) from the first N 节 of 蛊真人. Produces TWO aligned versions of the same content (only the seam separator differs), + seam ledger: S2prime_big_flat.txt seams = single '\\n' -> greedy A0 is BLIND (blind-budget cuts) S2prime_big_blanklines.txt seams = '\\n\\n' -> greedy ORACLE cuts AT every seam (paragraph flush) Within a 节 the body is normalized to single '\\n' (collapse internal blank lines) so the ONLY '\\n\\n' are at seams — that is what makes the oracle cut exactly at scene boundaries. Labels for seams are NOT hand-classified here (too many); Q1b uses the oracle's cut-at-all-seams behavior and the covariate is per-seam. Deterministic straddle-trap finding happens in straddle_traps.py. Run: python build_s2prime_big.py [N] # default N=45 """ from __future__ import annotations import json import re import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) import build_s2prime as B OUT = Path("/home/ubuntu/books/gu-zhenren/exp15") def main(): n = int(sys.argv[1]) if len(sys.argv) > 1 else 45 secs = B.sections(B.load_text())[:n] bodies = [] for idx, header, body in secs: b = body.replace("\r\n", "\n").replace("\r", "\n") b = re.sub(r"\n{2,}", "\n", b).strip("\n") # single-\n within 节 bodies.append((idx, b)) flat = "\n".join(b for _, b in bodies) blank = "\n\n".join(b for _, b in bodies) # seam offsets in each version (start of body i, i>0) def seam_offsets(sep): offs, cur = [], 0 for i, (idx, b) in enumerate(bodies): if i > 0: cur += len(sep) if i > 0: offs.append({"seam_id": i, "prev_sec": bodies[i - 1][0], "next_sec": idx, "rune_offset": cur}) cur += len(b) return offs (OUT / "S2prime_big_flat.txt").write_text(flat, encoding="utf-8") (OUT / "S2prime_big_blanklines.txt").write_text(blank, encoding="utf-8") (OUT / "S2prime_big_seams_flat.json").write_text(json.dumps( {"version": "flat", "n_sections": n, "section_ids": [b[0] for b in bodies], "total_runes": len(flat), "seams": seam_offsets("\n")}, ensure_ascii=False, indent=2), encoding="utf-8") (OUT / "S2prime_big_seams_blank.json").write_text(json.dumps( {"version": "blanklines", "n_sections": n, "section_ids": [b[0] for b in bodies], "total_runes": len(blank), "seams": seam_offsets("\n\n")}, ensure_ascii=False, indent=2), encoding="utf-8") print(f"S2'-big: {n}节 flat={len(flat)} runes blank={len(blank)} runes seams={n-1}") # verify seam alignment fs = seam_offsets("\n"); bs = seam_offsets("\n\n") ok = all(flat[s["rune_offset"]:s["rune_offset"]+3] == bodies[i+1][1][:3] for i, s in enumerate(fs)) okb = all(blank[s["rune_offset"]:s["rune_offset"]+3] == bodies[i+1][1][:3] for i, s in enumerate(bs)) print(f"seam alignment: flat={ok} blank={okb}") if __name__ == "__main__": main()