feat: the Passage — crossing over becomes a layered rite (Workstream L)
cross_over was one verdict with one outcome. It is now five beats — listen, name, unbind, open, release — each revealing something true about the spirit, each paying escalating essence, each able to twist. Reveals use the entity's REAL traits; there is no second hidden state invented for the rite. Twists are trait-driven, verified directly rather than assumed: a demon collapses the rite 3.6x more often than a calm spirit (0.360 vs 0.099 at `unbind`) and lies ~31% of the time, while a spirit under DECEIT_FLOOR cannot lie on any draw. A demon still resists at `release` and never crosses — the existing judgment rule is preserved, not re-implemented. Every draw comes from veil_float on the room's physical entropy, never `random`. Essence is kept across a collapse. Clawing it back would punish a seeker for the spirit's instability, which is not theirs to control. FIXED AN INFINITE LOOP IN THE SALVAGED TESTS, not a flake: test_full_rite_on_a_calm_stuck_spirit ran `while layer is not None` while passing collapse=FIRES. Its fixture comment claimed "both twist chances are 0 at these values, so NO draw can make this entity lie or collapse" — that is false. COLLAPSE_FLOOR is 0.5 (deliberately below judgment's stuck bar of 0.6, as passage.py explains), and the fixture's volatility is 0.61, giving a real ~9.9% collapse chance. Forced to fire, every layer bounced back to `listen` and the run hung forever instead of failing. Three fixes: hold the collapse draw (deceit still fires, which is the point — it proves a spirit under the floor cannot lie even when told to), correct the false comment, and bound the loop so a future regression fails in seconds rather than hanging a test run. Also added the entire seance.passage i18n block in both languages — the agent died before writing it, so the gate was failing on 35 missing keys — and reworded a comment in PassagePanel that spelled out a translation call in full: the coverage checker greps source text and cannot tell a comment from real code, so it demanded a key for the placeholder. 34 passage tests pass; 385 frontend; i18n parity and typecheck clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -385,6 +385,64 @@
|
||||
"unknownTitle": "the lens will not open",
|
||||
"unknown": "Something refused the camera and would not say what. Try again, or turn to another channel."
|
||||
}
|
||||
},
|
||||
"passage": {
|
||||
"title": "the passage",
|
||||
"intro": "a spirit that wishes to go cannot go alone. five layers stand between them and the dark — unwrap each, and something true comes loose.",
|
||||
"none": "no presence to release",
|
||||
"noneHint": "summon a spirit before attempting the passage",
|
||||
"begin": "begin the passage",
|
||||
"attempt": "unwrap this layer",
|
||||
"retry": "gather the thread again",
|
||||
"earned": "essence gathered",
|
||||
"collapsed": "the rite comes apart in your hands — the thread returns to the first layer. what you have already gathered stays yours.",
|
||||
"lieNote": "what this layer told you was not true.",
|
||||
"crossedTitle": "it goes",
|
||||
"resistedTitle": "it will not go",
|
||||
"layers": {
|
||||
"listen": {
|
||||
"title": "listen",
|
||||
"prompt": "be silent, and let it speak first."
|
||||
},
|
||||
"name": {
|
||||
"title": "name",
|
||||
"prompt": "say what it was called, when it was called anything."
|
||||
},
|
||||
"unbind": {
|
||||
"title": "unbind",
|
||||
"prompt": "find the knot that holds it here, and loosen it."
|
||||
},
|
||||
"open": {
|
||||
"title": "open",
|
||||
"prompt": "make a door where there was only wall."
|
||||
},
|
||||
"release": {
|
||||
"title": "release",
|
||||
"prompt": "let go. this is the part that costs you."
|
||||
}
|
||||
},
|
||||
"reveal": {
|
||||
"alignment": {
|
||||
"low": "there is something under this one that does not wish you well.",
|
||||
"mid": "it is neither kind nor cruel — only tired.",
|
||||
"high": "whatever it was in life, it means you no harm."
|
||||
},
|
||||
"power": {
|
||||
"low": "it is thin as breath on glass; it could not hurt you if it tried.",
|
||||
"mid": "there is weight to it — enough to move a small thing.",
|
||||
"high": "it is far stronger than it has been letting you believe."
|
||||
},
|
||||
"volatility": {
|
||||
"low": "it is steady. it has been waiting a long time and is good at it.",
|
||||
"mid": "it flickers when you press too hard.",
|
||||
"high": "it will not hold still — the rite may come apart on you."
|
||||
},
|
||||
"deceptiveness": {
|
||||
"low": "it does not know how to lie, and never learned.",
|
||||
"mid": "it tells you most of a truth and keeps the rest.",
|
||||
"high": "very little of what it says can be trusted."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"codex": {
|
||||
|
||||
@@ -385,6 +385,64 @@
|
||||
"unknownTitle": "la lente no se abre",
|
||||
"unknown": "Algo rechazó la cámara y no dijo qué. Inténtalo de nuevo, o acude a otro canal."
|
||||
}
|
||||
},
|
||||
"passage": {
|
||||
"title": "el paso",
|
||||
"intro": "un espíritu que desea irse no puede irse solo. cinco capas se interponen entre él y la oscuridad — desenvuelve cada una, y algo verdadero se suelta.",
|
||||
"none": "ninguna presencia que liberar",
|
||||
"noneHint": "invoca a un espíritu antes de intentar el paso",
|
||||
"begin": "comenzar el paso",
|
||||
"attempt": "desenvolver esta capa",
|
||||
"retry": "recoger el hilo de nuevo",
|
||||
"earned": "esencia reunida",
|
||||
"collapsed": "el rito se deshace en tus manos — el hilo vuelve a la primera capa. lo que ya has reunido sigue siendo tuyo.",
|
||||
"lieNote": "lo que esta capa te dijo no era cierto.",
|
||||
"crossedTitle": "se va",
|
||||
"resistedTitle": "no se irá",
|
||||
"layers": {
|
||||
"listen": {
|
||||
"title": "escuchar",
|
||||
"prompt": "guarda silencio, y deja que hable primero."
|
||||
},
|
||||
"name": {
|
||||
"title": "nombrar",
|
||||
"prompt": "di cómo se llamaba, cuando se llamaba algo."
|
||||
},
|
||||
"unbind": {
|
||||
"title": "desatar",
|
||||
"prompt": "halla el nudo que lo retiene aquí, y aflójalo."
|
||||
},
|
||||
"open": {
|
||||
"title": "abrir",
|
||||
"prompt": "haz una puerta donde solo había muro."
|
||||
},
|
||||
"release": {
|
||||
"title": "soltar",
|
||||
"prompt": "suelta. esta es la parte que te cuesta."
|
||||
}
|
||||
},
|
||||
"reveal": {
|
||||
"alignment": {
|
||||
"low": "hay algo bajo este que no te desea bien.",
|
||||
"mid": "no es ni amable ni cruel — solo está cansado.",
|
||||
"high": "fuera lo que fuera en vida, no pretende hacerte daño."
|
||||
},
|
||||
"power": {
|
||||
"low": "es fino como aliento sobre el cristal; no podría herirte aunque lo intentara.",
|
||||
"mid": "tiene peso — suficiente para mover algo pequeño.",
|
||||
"high": "es mucho más fuerte de lo que te ha hecho creer."
|
||||
},
|
||||
"volatility": {
|
||||
"low": "es firme. lleva mucho tiempo esperando y se le da bien.",
|
||||
"mid": "parpadea cuando presionas demasiado.",
|
||||
"high": "no se está quieto — el rito podría deshacerse contigo."
|
||||
},
|
||||
"deceptiveness": {
|
||||
"low": "no sabe mentir, y nunca aprendió.",
|
||||
"mid": "te cuenta casi toda una verdad y se guarda el resto.",
|
||||
"high": "muy poco de lo que dice es de fiar."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"codex": {
|
||||
|
||||
Reference in New Issue
Block a user