feat(frontend): ritual mini-game, judgment UI, and consequences (Workstream E)

Implements the Character Depth / Ghost Log spec's Workstream E:

- RitualPanel.tsx: a 4-step "focus the channel" hold-to-charge sequence
  (align/breathe/trace/lock), sending ritual_step frames as the seeker
  progresses. Success reveals the entity's true hidden traits; failure
  reveals nothing. Sequencing/timing logic lives in the pure, unit-tested
  lib/ritual.ts rather than inline in the component.
- JudgmentPanel.tsx: Trust/Banish/Cross Over/Test verdict buttons with
  rune-style SVG icons, sending the judgment frame and rendering a
  distinct treatment per judgment_result consequence — reward,
  escalation (also spikes the ambient haunting), withdrawal, resisted,
  neutral, and a calm glyph-fade farewell for crossed_over (deliberately
  not the reward treatment, since it's a goodbye).
- lib/haunting.ts: IdleEscalator gains forceEscalate()/forcedUntil so a
  judgment's "escalation" consequence can spike the ambient haunting
  immediately instead of waiting on the 90s idle clock; exports a
  sharedIdleEscalator singleton and a forceEscalate() free function.
  HauntingLayer now paces itself off that shared instance instead of a
  private one, so the forced spike actually reaches the running layer.
- state/seance.tsx: new ritual/judgmentResult state, a local_ritual_start
  action, and reducer cases for the ritual_complete/judgment_result server
  frames; SeanceContext exported for component testing; startRitual/
  sendRitualStep/sendJudgment added to the provider API.
- lib/types.ts: EntityTraits/JudgmentVerdict/JudgmentConsequence types and
  the new client/server WS frames, per the spec's Contract section.
- i18n: seance.ritual.* / seance.judgment.* keys in en.json and es.json.

Fast-forwarded this worktree's branch onto master first — it had been
created from a stale ancestor commit predating the frontend scaffold
entirely, with zero commits of its own ahead of that point.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Indiana
2026-07-23 11:29:29 +00:00
parent 6d8c6f2496
commit a52a781880
17 changed files with 1903 additions and 5 deletions

View File

@@ -180,6 +180,78 @@
"mythic": "mythic"
}
},
"ritual": {
"title": "focus the channel",
"none": "no presence to focus on",
"noneHint": "summon a spirit before attempting the rite",
"begin": "begin the rite",
"retry": "attempt again",
"hold": "hold to focus",
"awaiting": "awaiting the veil's answer…",
"successTitle": "the channel holds — the truth surfaces",
"failureTitle": "the channel collapses",
"failureHint": "the truth stays hidden — the rite may be tried again",
"traits": {
"alignment": "alignment",
"power": "power",
"volatility": "volatility",
"deceptiveness": "deceptiveness"
},
"steps": {
"align": {
"command": "> aligning the wire…",
"locked": "> wire aligned ✓"
},
"breath": {
"command": "> steadying the breath…",
"locked": "> breath steadied ✓"
},
"trace": {
"command": "> tracing the sigil…",
"locked": "> sigil traced ✓"
},
"lock": {
"command": "> locking the channel…",
"locked": "> channel locked ✓"
}
}
},
"judgment": {
"title": "render judgment",
"none": "no presence to judge",
"noneHint": "summon a spirit first",
"trust": "trust",
"banish": "banish",
"crossOver": "cross over",
"test": "test",
"pending": "awaiting the verdict…",
"favor": "favor",
"essence": "essence",
"reward": {
"title": "the bond holds",
"body": "the spirit answers your trust in kind."
},
"escalation": {
"title": "a reckoning",
"body": "wrongly trusted, it turns — the haunting sharpens."
},
"withdrawal": {
"title": "it recoils",
"body": "banished in error, the presence withdraws, wounded."
},
"crossedOver": {
"title": "at peace",
"body": "the spirit lets go, and the line falls quiet."
},
"resisted": {
"title": "it will not go",
"body": "a demon does not cross over — it claws back to stay."
},
"neutral": {
"title": "no verdict rendered",
"body": "a diagnostic pulse only — nothing risked, nothing gained."
}
},
"toast": {
"rate_limited": "the veil is thin — slow your knocking",
"veil_crowded": "too many spirits crowd the line"

View File

@@ -180,6 +180,78 @@
"mythic": "mítico"
}
},
"ritual": {
"title": "enfoca el canal",
"none": "ninguna presencia que enfocar",
"noneHint": "invoca un espíritu antes de intentar el rito",
"begin": "comenzar el rito",
"retry": "intentar de nuevo",
"hold": "mantén para enfocar",
"awaiting": "esperando la respuesta del velo…",
"successTitle": "el canal se sostiene — la verdad emerge",
"failureTitle": "el canal colapsa",
"failureHint": "la verdad permanece oculta — el rito puede intentarse de nuevo",
"traits": {
"alignment": "alineación",
"power": "poder",
"volatility": "volatilidad",
"deceptiveness": "engaño"
},
"steps": {
"align": {
"command": "> alineando el cable…",
"locked": "> cable alineado ✓"
},
"breath": {
"command": "> serenando el aliento…",
"locked": "> aliento sereno ✓"
},
"trace": {
"command": "> trazando el sigilo…",
"locked": "> sigilo trazado ✓"
},
"lock": {
"command": "> cerrando el canal…",
"locked": "> canal cerrado ✓"
}
}
},
"judgment": {
"title": "dicta juicio",
"none": "ninguna presencia que juzgar",
"noneHint": "invoca un espíritu primero",
"trust": "confiar",
"banish": "desterrar",
"crossOver": "cruzar al más allá",
"test": "poner a prueba",
"pending": "esperando el veredicto…",
"favor": "favor",
"essence": "esencia",
"reward": {
"title": "el vínculo se sostiene",
"body": "el espíritu responde a tu confianza de igual manera."
},
"escalation": {
"title": "un ajuste de cuentas",
"body": "confiado en error, se revuelve — la aparición se agudiza."
},
"withdrawal": {
"title": "se retrae",
"body": "desterrado en error, la presencia se retira, herida."
},
"crossedOver": {
"title": "en paz",
"body": "el espíritu se suelta, y la línea queda en silencio."
},
"resisted": {
"title": "no se irá",
"body": "un demonio no cruza al más allá — se aferra para quedarse."
},
"neutral": {
"title": "ningún veredicto emitido",
"body": "solo un pulso de diagnóstico — no se arriesga ni se gana nada."
}
},
"toast": {
"rate_limited": "el velo es delgado — golpea más despacio",
"veil_crowded": "demasiados espíritus se apiñan en la línea"