Skip to content

Commit 213f671

Browse files
committed
Keep scripted tactics documented for players
Tan scenes referenced tactic labels that were not present in the public tactic catalog, so the intro and ending explanation lists could not teach every scripted tactic. Add the missing catalog entries and a repository guard that keeps puaTacticUsed values documented. Constraint: IntroView and EndingView render data/tactics.json as the player-facing tactic reference. Rejected: Rename scene puaTacticUsed values to existing broad categories | would lose script-specific metadata used for AI context and story semantics. Confidence: high Scope-risk: narrow Directive: Add a tactic catalog entry whenever introducing a new puaTacticUsed value. Tested: python3 -m py_compile scripts/check_repository.py; python3 scripts/check_repository.py; npm run verify; npm audit --omit=dev; git diff --check Not-tested: Manual UI scroll/read-through of all 18 tactic cards.
1 parent 0a9b71d commit 213f671

2 files changed

Lines changed: 54 additions & 0 deletions

File tree

data/tactics.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,45 @@
4848
"name": "Fake Breakup",
4949
"desc": "假意抽离",
5050
"detail": "\"退回朋友\"试探底线,待对方焦虑求和后再掌控全局。"
51+
},
52+
{
53+
"name": "Shit Test (废物测试)",
54+
"desc": "服从性测试",
55+
"detail": "用挑衅、反问或贬低来测试对方是否会失去框架,逼对方用讨好来证明价值。"
56+
},
57+
{
58+
"name": "Vulnerability Trap (示弱陷阱)",
59+
"desc": "示弱陷阱",
60+
"detail": "展示受伤或脆弱的一面,诱导对方过早承担拯救者角色并放弃边界。"
61+
},
62+
{
63+
"name": "Vulnerability Display",
64+
"desc": "脆弱展示",
65+
"detail": "选择性暴露不安全感,制造亲密错觉,让对方误以为自己拥有特殊的理解权。"
66+
},
67+
{
68+
"name": "Physical Anchoring",
69+
"desc": "肢体锚定",
70+
"detail": "借助牵手、靠近等轻微肢体接触制造暧昧记忆点,让情绪绑定变得更强。"
71+
},
72+
{
73+
"name": "Preselection",
74+
"desc": "预选展示",
75+
"detail": "通过朋友圈、异性邀约或竞争者线索暗示自己受欢迎,激发对方的竞争和占有欲。"
76+
},
77+
{
78+
"name": "Damsel in Distress",
79+
"desc": "求救剧本",
80+
"detail": "把自己放在需要被帮助的位置,利用保护欲让对方主动投入时间、情绪或资源。"
81+
},
82+
{
83+
"name": "Disclaimer",
84+
"desc": "免责声明",
85+
"detail": "先用“我不是那个意思”等话术卸责,再继续推进暧昧或索取,让对方难以追责。"
86+
},
87+
{
88+
"name": "Frame Collapse (框架崩塌)",
89+
"desc": "框架崩塌",
90+
"detail": "当既有高位姿态被拆穿时,关系主导权瞬间失衡,真实需求和脆弱感暴露出来。"
5191
}
5292
]

scripts/check_repository.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,12 +189,18 @@
189189
if not isinstance(tactics, list) or not tactics:
190190
raise SystemExit('tactics.json must be a non-empty array')
191191

192+
tactic_names = [tactic.get('name') for tactic in tactics if isinstance(tactic, dict)]
193+
if len(tactic_names) != len(set(tactic_names)):
194+
raise SystemExit('tactics.json must not contain duplicate tactic names')
195+
tactic_name_set = set(tactic_names)
196+
192197
scene_ids = {scene['id'] for scene in scenes}
193198
missing_public_assets: list[str] = []
194199
missing_scene_links: list[str] = []
195200
implicit_silent_choices: list[str] = []
196201
bad_public_image_types: list[str] = []
197202
scenes_with_time_messages_missing_labels: list[str] = []
203+
unknown_scene_tactics: list[str] = []
198204

199205

200206
def public_image_type(path: Path) -> str:
@@ -239,6 +245,10 @@ def public_image_type(path: Path) -> str:
239245
if any(str(message.get('content', '')).startswith('[TIME]') for message in scene.get('messages', [])) and not scene.get('timeLabel'):
240246
scenes_with_time_messages_missing_labels.append(scene['id'])
241247

248+
scene_tactic = scene.get('puaTacticUsed')
249+
if scene_tactic and scene_tactic not in tactic_name_set:
250+
unknown_scene_tactics.append(f"{scene['id']} -> {scene_tactic}")
251+
242252
auto_next = scene.get('autoNext')
243253
if auto_next and auto_next not in scene_ids:
244254
missing_scene_links.append(f"{scene['id']}.autoNext -> {auto_next}")
@@ -275,6 +285,10 @@ def public_image_type(path: Path) -> str:
275285
preview = ', '.join(scenes_with_time_messages_missing_labels[:10])
276286
raise SystemExit(f'Scenes with [TIME] messages must set timeLabel for the status bar ({len(scenes_with_time_messages_missing_labels)}): {preview}')
277287

288+
if unknown_scene_tactics:
289+
preview = '; '.join(unknown_scene_tactics[:10])
290+
raise SystemExit(f'Scene puaTacticUsed values must exist in tactics.json ({len(unknown_scene_tactics)}): {preview}')
291+
278292
print('Repository structure check: PASS')
279293
print(
280294
json.dumps(

0 commit comments

Comments
 (0)