# The Mind Palace

V5 is one open world worn five ways — Urban Crime, Period Drama, Frontier,
Monster Hunter, Sci-Fi — and the **Mind Palace** is the single screen that
refuses to let those five cells drift into five disconnected games. It is the
detective deduction layer the design calls V5's signature: a graph of evidence
orbs collected anywhere in the world, a circular "memory room" the player
rotates to manipulate them, deduction _threads_ drawn between clues that
genuinely connect, and an accusation system that resolves a case into a
branching outcome. Pick up a brass-key clue in a 1947 noir case, examine a
faction badge in the Sci-Fi cell, and — if you have earned the Bureau XP to
unlock cross-era reasoning — drag the two orbs together and watch a gold thread
snap across two hundred years. That cross-cell "aha" is the whole pitch, and it
is the one mechanic that makes the anthology feel like a universe instead of a
menu.

This is a **cross-cell system**, not a sixth cell. Every cell's investigation
gameplay pushes its clues into the same graph through one interface; the Mind
Palace owns the reasoning and the verdict. Crucially, it allows wrong deductions
and wrong accusations — there is no game-over for a bad hypothesis — and it
proves the right one with a real proof-of-work gate rather than a coin flip.
This page is the player-facing tour of how that works, grounded in the committed
Unreal C++ behind each verb; the deep data-contract companion (and the in-editor
Case Author that authors the cases the Mind Palace solves) is
[../architecture/creator-suite-and-mind-palace.md](../architecture/creator-suite-and-mind-palace.md).
For the full mode taxonomy and the scope this slots into, start at the hub:
[../V5_features.md](../V5_features.md).

## What ships, honestly

**The logic is real, compiled, and tested.** The Mind Palace is the
`V5MindPalace` Unreal module — 36 source files under
`V5/ue/Source/V5MindPalace/` with a `Public/`/`Private/` split, a `.Build.cs`,
and a `Private/Tests/` automation spec — and the linker has been through it on
this box: `libUnrealEditor-V5MindPalace.so` sits in `V5/ue/Binaries/Linux/`,
built by the `ueagent` user. The architecture is a core state-and-algorithm
object, `UV5_MindPalace_Graph` (`Public/V5MindPalaceGraph.h`), wrapped by a
suite of thin `UBlueprintFunctionLibrary` classes that expose each verb
statically to Blueprint (`UV5_MindPalace_AttemptDeduction`,
`…_BuildHypothesisChain`, `…_MakeAccusation`, `…_AddEvidenceNode`,
`…_CrossEraGate`, `…_FilterRail`, `…_View_Volumetric`, `…_CrossEraCinematic`,
`…_CloudSync`, `…_CompanionAppRead`, `…_Catalog`). Four
`IMPLEMENT_SIMPLE_AUTOMATION_TEST` suites
(`Private/Tests/V5MindPalaceTests.cpp`) assert specific authored counts and
_computed_ verdicts — a Tier-1 ledger is blocked, a Tier-5 ledger unlocks, an
incomplete chain collapses to the wrong branch — not truthiness.

**Authored content is JSON, validated on load.** The 110 deduction pairs and 15
accusation outcomes are committed data
(`V5/ue/Content/V5MindPalace/Data/deduction_pairs.json`,
`accusation_outcomes.json`), parsed and schema-checked at load behind a
`schemaVersion` gate, not hard-coded.

**Honest fail-loud seams.** Mind-Palace cloud-sync builds a _real_ `FHttpModule`
POST, but its backend is a seam: an unhealthy service yields a `503`, a missing
JWT short-circuits to `401` rather than POSTing an unauthenticated diff, and the
live round-trip test is gated on `V5_ONLINE_LIVE=1`. These refuse to fabricate a
success they did not get.

**Art is referenced, not committed.** The memory-orb meshes, the rendered
threads and cinematic, and the examinable 3-D evidence the Detective mode counts
are asset _paths_ (`FSoftObjectPath`) named by the logic here — the UE-art
surface V5 keeps out of git. `V5MindPalace` computes the room (orb positions,
scales, colors, thread intensities, camera keyframes); the UMG/Niagara rendering
of it is the UI layer.

## Evidence enters the room — one push interface, five cells

Conceptually the Mind Palace is a side-car to all five cells: each cell's
exploration mode supports "examine" on a pick-up clue, and examining pushes a
notebook entry into the shared graph. That push is a single interface.
`IV5_Investigation_MindPalacePush` declares one method,
`EmitEvidenceNodeEvent(FV5InvestigationEvidenceNodeEvent)`, and the graph
implements it (`V5MindPalaceGraph.cpp:21`): it translates an in-cell evidence
event — `ClueId`, `SceneId`, `Tags`, `EvidenceTitle`, `EvidenceBody`,
`bFalseClue` — into an `FV5MindPalaceEvidenceNode` tagged to the `MindPalace`
cell, then upserts it. That is how a clue examined in any cell becomes an orb in
the room.

The node type is the design's `V5_Evidence_Node` (`Public/V5MindPalaceTypes.h`):
a `NodeId`, `CaseId`, source id, source `Cell`, `LocationId`, a
`TimestampInFiction` string, a `Tags` array, a `Description`, a soft `Image`
path, the `ExaminedDetails` the player has uncovered, and a `bFalseLead` flag
for diegetic red herrings. `AddEvidenceNode` (`V5MindPalaceGraph.cpp:38`) is the
gate into the graph and it is not a blind append: it rejects a node with an
empty `NodeId`, `CaseId`, or `Description`, and when a node id already exists it
**upserts** — refreshing the metadata and _merging_ new tags and examined
details onto the existing orb rather than duplicating it. So re-examining a clue
deepens it; it never clones it.

## The deduction mechanic — authored-first, weak fallback, cross-era gate

`AttemptDeduction(NodeA, NodeB, BureauLedger)` (`V5MindPalaceGraph.cpp:78`) is
the "drag two orbs onto the pedestal" action, and its logic is specific rather
than generic. After rejecting a same-node, none, or non-existent pair, it looks
up an **authored** edge for the _unordered_ pair via `FindAuthoredEdge`
(matching `from→to` or `to→from`, `:247`). If an authored insight exists, that
is the candidate. If not, it falls back to `BuildWeakEdge` (`:271`), which only
succeeds when the two nodes **share at least one tag**: it computes the tag
intersection, and if that intersection is empty it returns nothing and the
attempt reports _"No meaningful connection found."_ (`:91`). The system refuses
to invent a deduction the data does not support.

When the shared tag exists, the weak edge is honestly weak: id `weak.<a>.<b>`,
`Confidence 0.35`, `Kind = Weak`, with `DeductionText` reading _"A weak thematic
connection is visible, but it needs stronger corroboration."_ The cross-era
nuance is encoded right here: the weak edge is marked `bCrossEra` when the two
nodes come from different cells, and in that case it is stamped
`RequiredBureauTier 5` and `UnlockFlag MindPalace.CrossEraPairs` (`:303-305`). A
same-cell weak link is tier 1 and always passes; a cross-cell weak link is gated
exactly like an authored cross-era thread.

Every candidate — authored or weak — then passes the **cross-era gate** before
it is committed. `UV5_MindPalace_CrossEraGate::CanUnlockDeduction`
(`V5MindPalaceCrossEraGate.cpp:3`) is the spine connecting the Mind Palace to
progression: a non-cross-era edge is always allowed; a cross-era edge is allowed
only when `BureauLedger.CurrentTier >= max(1, RequiredBureauTier)` **or** the
ledger's `Unlocks` already contains the edge's `UnlockFlag`. If the gate blocks,
`AttemptDeduction` returns `bGateBlocked = true` with the candidate edge
attached for the UI to tease — but it is **not** added to `State.Edges`. The
graph stays unchanged until the player earns the unlock, and the cross-cell
epiphanies are paid for in Bureau XP, the same ledger (`FV5BureauXPLedger`,
`V5/ue/Source/V5Core/Public/V5Types.h`) whose Tier-5 milestone grants
`MindPalace.CrossEraPairs`. Only a cleared candidate is appended to state
(deduped by edge id), and the result reports `bSuccess` with the resolved
`Edge`.

## From a thread to an accusation — chains, the gate, and outcomes

A single thread is rarely the case. `BuildHypothesisChain(start, maxDepth)`
(`V5MindPalaceGraph.cpp:112`) is a real breadth-first traversal over the
_committed_ edges: a frontier expands outward (depth clamped to 1–12),
collecting every reachable node and edge id and accumulating an
`AverageConfidence` across the threads it walks. It is the UI helper for
visualizing a line of reasoning, and the test pins that an authored chain
carries real strength — `AverageConfidence >= 0.80` (`V5MindPalaceTests.cpp:99`)
— because the authored cross-era threads ship at confidence `0.86` and per-cell
threads at `0.74`, while weak guesses drag the average down toward `0.35`.

`MakeAccusation(case, accused, chain)` (`:165`) is the proof-of-work gate that
closes a case, and it defaults to the _worst_ outcome. It seeds the result as
`Wrong` / `Unproven` with the summary _"filed without a complete supporting
chain,"_ then searches the authored catalog for an outcome matching the
`(case, accused)` pair. A match only "sticks" with its authored `Rating`
(`Brilliant`/`Good`/`Doubtful`/`Wrong`) and `OutcomeBranch`
(`TruthRevealed`/`PlausibleConviction`/`PoliticalCoverup`/`Unproven`) if the
player's `EvidenceChain` **contains every** `RequiredEdgeId` the outcome
demands. A chain missing even one required link collapses straight back to
`Wrong` / `Unproven` with _"lacked the required evidence chain"_ (`:196`). The
outcome is always backed by the deductions the player actually assembled — you
cannot brute-force a conviction by naming the right culprit without the
reasoning that indicts them. Every resolved accusation, right or wrong, is
appended to `State.Accusations` and logged by
`UV5_MindPalace_OutcomesArchive::LogOutcome` into the reviewable Outcomes
Archive.

## How a player actually solves a case

Stitch the verbs together and the loop is exactly the Sherlock-style fantasy the
design promises — examine, theorize, chain, accuse — with the cross-era gate as
the long-game unlock.

```mermaid
flowchart TD
  EX["Examine a clue in ANY cell<br/>(Urban / Period / Frontier / Hunter / Sci-Fi)"]
  EX -->|"EmitEvidenceNodeEvent → AddEvidenceNode"| ORB["Orb appears in the memory room<br/>tagged cell · case · in-fiction date · key terms"]
  CAT[("deduction_pairs.json<br/>110 authored pairs")] -->|ApplyAuthoredDeductionCatalog| ROOM
  ORB --> ROOM{{"Volumetric room + FilterRail<br/>rotate · filter by cell / case / tag / date"}}
  ROOM -->|"drag two orbs → AttemptDeduction"| Q{authored edge<br/>for the pair?}
  Q -- yes --> GATE{cross-era gate<br/>CanUnlockDeduction}
  Q -- "no, shared tag" --> WEAK["weak thread<br/>Confidence 0.35"]
  Q -- "no, no shared tag" --> NONE["No meaningful<br/>connection found"]
  WEAK --> GATE
  GATE -- "Tier &lt; 5 (cross-era)" --> BLOCK["bGateBlocked — earn Bureau XP<br/>graph unchanged"]
  GATE -- allowed --> THREAD["thread committed to State.Edges<br/>cross-era → 4s gold-thread cinematic"]
  THREAD --> CHAIN["BuildHypothesisChain<br/>BFS · AverageConfidence"]
  CHAIN --> ACC{"MakeAccusation<br/>chain ⊇ RequiredEdgeIds?"}
  ACC -- yes --> WIN["authored Rating + branch<br/>Truth / Plausible / Coverup"]
  ACC -- no --> LOSE["Wrong / Unproven<br/>(the wrong answer still ships)"]
  WIN --> ARCH[("Outcomes Archive<br/>+ cloud-sync :4220")]
  LOSE --> ARCH
```

The player examines clues across cells; each becomes an orb. In the room they
filter the clutter, pick two orbs, and attempt a deduction. Most early pairings
resolve inside a single cell; the cross-era pairings stay gated until Bureau
Tier 5, at which point dragging a 1947 orb against a 2078 orb fires a
four-second "thread connects" cinematic. They chain threads into a hypothesis,
read its confidence, and at case-close accuse one suspect — but the accusation
only lands its authored branch if the required threads are in the chain. Get it
wrong and the case still resolves, to the wrong branch, logged to the Archive
for a New Game+ replay. No penalty, no soft-lock, just a worse outcome you can
see you earned.

## The volumetric memory room and the filter rail

`UV5_MindPalace_View_Volumetric::BuildViewState`
(`V5MindPalaceViewVolumetric.cpp:33`) renders the design's "circular memory room
with evidence orbs in concentric rings by cell of origin." It first filters the
node set through the rail, then places each surviving node on a ring whose
radius is a function of its cell — `RingForCell = 220 + cellIndex·70` cm, so
Urban orbs sit on the inner 220 cm ring and Sci-Fi orbs on a 500 cm ring — at an
angle evenly distributed around the circle (`2π·i/N`), lifted in z by index. The
selected orb scales up to `1.35` and glows at full intensity while the rest sit
at `0.45`; each orb takes a per-cell accent color (`ColorForCell` — Urban cyan,
Period amber, Hunter rust, Sci-Fi indigo). Deduction threads are drawn **only
between two visible orbs**; an authored thread's intensity tracks its confidence
(clamped to `0.25–1.0`) while a weak thread reads flat at `0.25`, and a
cross-era thread is gold where an in-cell thread is blue-grey. The whole layout
is deterministic geometry the UI consumes; nothing about it is random.

`UV5_MindPalace_FilterRail::FilterNodes` (`V5MindPalaceFilterRail.cpp:19`) is
the left rail: filter by cell, by case, by an in-fiction timestamp range, and by
required tags (a node must carry _all_ of them). The timestamp range is a string
comparison that works precisely because the in-fiction timestamps are written
ISO-ordered (`"1947-06-14T21:00:00"` sorts before `"2078-…"`), so a date window
is a lexical bound. The automation test drives this end-to-end: filter to a
single node by cell+tag, then build a view that resolves to exactly two orbs and
one thread (`V5MindPalaceTests.cpp:106-110`). The cross-era reveal is itself
code: `UV5_MindPalace_CrossEraCinematic::BuildThreadConnectCinematic`
(`V5MindPalaceCrossEraCinematic.cpp:19`) returns a four-second camera move whose
`ThreadAlpha` eases in over the duration (`FMath::InterpEaseInOut`) and whose
camera lerps to the midpoint between the two orbs — the test asserts the
duration is 4.0 s and the alpha advances strictly between 0 and 1.

## Authored content is data, validated on load

The 110 deduction pairs and 15 outcomes are not hard-coded.
`UV5_MindPalace_Catalog::LoadDeductionPairs` (`V5MindPalaceCatalog.cpp:121`)
reads `deduction_pairs.json` and validates it hard: `schemaVersion` must be 1,
the file is a compact _generator spec_ of `pairGroups`, and the generated edges
are de-duplicated by id, rejected on self-loops or non-positive confidence
(`AddDeductionIfValid`, `:103`). The spec is one `crossEra` group of `count: 30`
spanning all five cells at `requiredBureauTier 5`, plus five `perCell` groups of
`count: 16` (Urban, Period, Frontier, Hunter, Sci-Fi) = 80, and the validator
_counts_: it errors unless cross-era pairs total exactly 30 and per-cell pairs
total exactly 80 (`:269-276`), matching the test's `Deductions.Num() == 110`,
`CrossEraCount == 30`, `PerCellCount == 80`. `ValidateAccusationCatalogJson`
(`:280`) requires at least three outcomes, each with a non-empty
`requiredEdgeIds` and a unique id; `accusation_outcomes.json` ships 15 — five
anchor cases × three authored branches, an even Truth-Revealed /
Plausible-Conviction / Political-Coverup split, each branch demanding its own
pair of cross-era edges. The accusation test completes each outcome's required
deductions and asserts the verdict resolves the **expected** branch, covering
three distinct branches across the arc (`V5MindPalaceTests.cpp:179-202`).

## Cloud sync, the companion app, and the live seam

Mind-Palace state follows the account across devices, and that path is a real
request with an honest backend seam.
`UV5_MindPalace_CloudSync::BuildGraphDiffPayload`
(`V5MindPalaceCloudSync.cpp:16`) serializes a graph diff — nodes (id/case/cell)
and edges (id/from/to/cross-era) plus counts — targeting
`@v5/service-mindpalace-cloud-sync` at `/v5/mindpalace-cloud-sync/graph-diff`.
`FV5MindPalaceCloudSyncHttp::BuildHttpRequest`
(`V5MindPalaceCloudSyncHttp.cpp:35`) turns that into a genuine `FHttpModule`
POST on port `:4220` with a `Bearer` JWT and `application/json`, and the latent
`UV5_MindPalace_CloudSyncRequest::Activate` (`:75`) **fails loud** —
short-circuiting to `401 auth.jwt.required` when no token is present rather than
transmitting an unauthenticated diff, and reporting `service.unreachable` on a
connection failure. `ParseResult` maps `2xx→success` and `503→failure`; the
request-construction test pins the verb, URL, port, headers, and body, and a
second test does a real round-trip behind `V5_ONLINE_LIVE=1`, asserting the
response acknowledges the merged diff (`V5MindPalaceTests.cpp:246-318`).

The companion-app path (`V5MindPalaceCompanionAppRead.cpp`) is the Year-1
Companion-App Mind Palace Editor: `BuildReadOnlySnapshot` exposes the graph for
an AR/touch overlay, and `BuildEditableSnapshot` flips on the editor with two
operations — `add-clue` and `annotate-clue`. `ApplyCompanionEdit` (`:72`)
validates each draft (it requires a graph, a clue id, a case id, and an
annotation body), creates or annotates the targeted node — an annotation _must_
target an existing clue — and queues an append-only `mindpalace-edit` upload for
cloud-sync. The companion can add to your case file from your phone, offline,
and it merges back through the same seam.

## Edge cases & connections

- **A pairing with no authored edge and no shared tag** yields _"No meaningful
  connection found"_ — never a fabricated certainty. Shared-tag pairings produce
  only a `0.35`-confidence _weak_ thread, and a cross-_cell_ weak thread is
  gated exactly like an authored cross-era one.
- **A cross-era deduction below Bureau Tier 5** returns `bGateBlocked` with the
  candidate attached but **not** committed to state — the gate fails loud and
  leaves the graph untouched until the XP is earned.
- **An accusation with an incomplete chain** can never reach an authored
  conviction branch; the missing-link path is the explicit `Wrong`/`Unproven`
  collapse, so wrong answers ship (and are archived) without penalty — there is
  no deduction-graph game-over.
- **Offline / unhealthy backends** surface as `503` or, with no JWT, `401`; the
  live cloud-sync round-trip is opt-in behind `V5_ONLINE_LIVE`, so the default
  test run never depends on a service being up.
- **The Detective Mind Palace _mode_** — the ten-hour main arc, the
  deterministic daily-puzzle rotation, the leaderboard scoring, and the
  count-validated 100 examinable 3-D evidence assets — is a _separate_ runtime
  module (`V5DetectiveMindPalace`, also compiled on this box) built on top of
  this graph; its dailies-and-scoring internals are documented in the
  architecture companion rather than re-cited here.
- **Where cases are authored and where the spine lives.** The in-editor Case
  Author that validates a case under the runtime's own rules is the matched pair
  to this system —
  [../architecture/creator-suite-and-mind-palace.md](../architecture/creator-suite-and-mind-palace.md).
  The Bureau-XP ledger this gates against, the online backbone the cloud-sync
  targets, and the roster every cell shares are inventoried in
  [./shared-cross-cell-systems.md](./shared-cross-cell-systems.md). One of the
  five cells feeding evidence into the room is detailed in
  [./urban-crime-cell.md](./urban-crime-cell.md).
- The feature hub: [../V5_features.md](../V5_features.md).
