Lesson 246: Ren'Py Playtest Save Language Isolation Receipt on BUILD_RECEIPT (2026)
Direct answer: Before July playtest scale on a Ren'Py SKU with fest_public still live, file renpy_playtest_save_language_receipt_v1.json proving config.save_directory and language persistent keys are unique per SAVE_SURFACE, pass the installed-build isolation matrix (playtest slot 2 round-trip, fest slot 2 empty, no language pref bleed), and link cousin po_hash_language_receipt_v1 from Lesson 225—then promote BUILD_RECEIPT renpy_playtest_save_language_ok. Distinct from po_hash_language (PO packaging) and Lesson 238 Godot save isolation (user:// paths).

Why this matters now (July 2027 Ren'Py dual-branch playtest)
July 2027 visual-novel teams pass Lesson 225 PO-hash on the fest depot, then invite playtest_july while itch and Steam fest builds stay public. Playtesters finish in 日本語, launch the fest exe, and see English menus—or chapter 7 in fest slot 2—because persistent.language and config.save_directory were never isolated per surface.
The Ren'Py save/language isolation preflight is the ninety-second checklist—246 is the BUILD_RECEIPT milestone. Pair Lesson 245 wasm crash row and Lesson 239 tag save_isolation.
Beginner path (dual-exe matrix)
| Step | Action | Success check |
|---|---|---|
| 1 | Confirm Lesson 225 GREEN on fest | Cousin PO hash |
| 2 | Pin save_surface_config.rpy per surface |
S1 pass |
| 3 | Isolate language persistent keys | S2 pass |
| 4 | Playtest exe: JP + save slot 2 | S3 pass |
| 5 | Fest exe: slot 2 empty, language per fest policy | S4 pass |
| 6 | File receipt + BUILD_RECEIPT | save_language_isolation_ok: true |
Time: ~62 minutes first dual-export wiring; ~15 minutes when surface config is pinned.
Developer path (gates S1–S6)
| Gate | Check | Fail when |
|---|---|---|
| S1 | SAVE_SURFACE → config.save_directory |
Shared save folder |
| S2 | Language persistent per surface | persistent.language shared |
| S3 | Playtest installed round-trip | Editor-only proof |
| S4 | Fest: no save bleed + no language bleed | Cross-contamination |
| S5 | build_label on receipt |
Mismatch vs playtest pin |
| S6 | renpy_playtest_save_language_receipt_v1.json |
save_language_isolation_ok: false |
Cousin receipt crosswalk
| Cousin | Schema | When required |
|---|---|---|
| Lesson 225 | po_hash_language_receipt_v1 |
Fest SKU PO packaging |
| Lesson 238 | playtest_save_isolation_receipt_v1 |
Godot SKU same policy |
| This lesson | renpy_playtest_save_language_receipt_v1 |
Ren'Py save + language surfaces |
Do not merge PO hash proof into this JSON—reference paths only.
renpy_playtest_save_language_receipt_v1.json
{
"schema": "renpy_playtest_save_language_receipt_v1",
"build_label": "playtest-july-2027-rc1",
"surfaces": {
"playtest_invite": {
"save_directory": "MyVN-playtest-1",
"language_persistent_key": "playtest_language",
"boot_language": "japanese"
},
"fest_public": {
"save_directory": "MyVN-fest-1",
"language_persistent_key": "fest_language",
"boot_language": "english"
}
},
"isolation_matrix": {
"playtest_slot2_roundtrip": "pass",
"fest_slot2_cross_contamination": "none",
"fest_language_pref_bleed": "none"
},
"cousin_receipts": {
"po_hash_language": "release-evidence/localization/PO_HASH_LANGUAGE_RECEIPT.json",
"godot_save_isolation": "release-evidence/saves/PLAYTEST_SAVE_ISOLATION_RECEIPT.json"
},
"gates": {
"S1_save_directory": "pass",
"S2_language_persistent": "pass",
"S3_playtest_roundtrip": "pass",
"S4_fest_isolation": "pass",
"S5_build_label": "pass",
"S6_receipt": "pass"
},
"save_language_isolation_ok": true,
"promotion_allowed": true
}
Pin under release-evidence/saves/RENPY_PLAYTEST_SAVE_LANGUAGE_RECEIPT.json.
BUILD_RECEIPT row (S6)
| Column | Pass when |
|---|---|
renpy_playtest_save_language_ok |
save_language_isolation_ok: true |
po_hash_language |
Lesson 225 cousin (separate column) |
playtest_save_isolation |
Lesson 238 cousin if Godot SKU ships |
Thursday row review — Ren'Py isolation line: S4 matrix pass Y/N.
Key takeaways
- 225 PO hash ≠ 246 save/language isolation—run both before July scale.
config.save_directorymust differ perSAVE_SURFACE(S1).persistent.playtest_languagevsfest_language—never one sharedpersistent.language(S2).- Prove on two installed exes—not Ren'Py launcher alone (S3–S4).
- Godot Lesson 238 shares policy, different engine wiring.
- Tag triage via Lesson 239—
save_isolation, not new emoji. - Q4 capstone 253 will wire 245–252 including this row.
- “Weblate 100% but English” → PO-hash guide—not this lesson.
- “Fest demo Japanese after playtest” → this lesson S4 language bleed.
SAVE_SURFACEinjection belongs in CI per Steam depot job.
Common mistakes
- GREEN 225 but shared
save_directorybetween exes. - S2 skipped—menus flip language without player input.
- Merging schemas into
po_hash_language_receipt_v1. - Testing only itch WebGL when receipt targets desktop VN exes—file
surfacehonestly. - Conflating with Lesson 212 labels-only cousin.
Troubleshooting
| Symptom | Lane |
|---|---|
| Fest loads playtest chapter | S1 + S4 |
| Fest language = playtest JP | S2 persistent keys |
| English menus, hashes OK | R-gates rpyc |
| Godot SKU same policy | Lesson 238 |
| Wrong depot | Lesson 201 |
Mini exercise (60 minutes)
- Ship one build with shared
save_directory—reproduce fest bleed. - Add
save_surface_config.rpy+ S2 keys—pass matrix on two installs. - Link cousin
PO_HASH_LANGUAGE_RECEIPT.json. - File receipt; BUILD_RECEIPT GREEN.
- Pin facilitator note beside playtest isolation blog.
Continuity — Q4 2027 July playtest scale (245–253)
| Lesson | Receipt focus |
|---|---|
| 245 | Bevy wasm crash label |
| 246 (this) | Ren'Py save + language isolation |
| 247 | Unreal menu minidump build_label |
| 253 | Q4 capstone (queued) |
Previous: Lesson 245 — Bevy playtest crash build_label
Next: Lesson 247 — Unreal playtest menu minidump build_label
FAQ
Same as Lesson 225?
225 = PO hash packaging; 246 = save roots + language persistent per surface.
Same as save isolation guide?
Guide = S1–S6 checklist; 246 = BUILD_RECEIPT promotion.
Same as Lesson 238?
238 = Godot user://; 246 = Ren'Py save_directory + persistent.
English-only VN?
Set renpy_playtest_save_language_ok: n/a when only one surface ships—do not fake matrix.
Localized playtest must not rewrite fest saves or menus—isolate directories and language keys, prove the matrix on installed exes, then renpy_playtest_save_language_ok on BUILD_RECEIPT.