V5 is one open-world narrative game wearing seven costumes, and combat is only
half of what each costume promises. The other half is the set of non-combat
signature verbs — the things a player does that no shooting model can stand in
for: leaning across a 1947 interrogation table reading a witness's face for the
lie, spinning a six-spoke conversation wheel to charm or threaten a sci-fi
quartermaster, and chalking a bank job onto a planning board before a four-crew
South Bay heist. Three Unreal C++ modules own those verbs: V5Interrogation
(the MotionScan-style facial-tell parser that anchors the Period noir Vice
Squad), V5Dialogue (the paragon/renegade dialogue wheel, sci-fi-forward
but shared across every cell), and V5Heist (the pre-mission planner that
is the urban-crime cell's defining loop). Each is the marquee interaction of a
different cell, and together they are the proof that V5 means "narrative
universe" and not "shooter with skins."
What unites them mechanically is a shared shape: an authored, JSON/YAML-driven
catalog of content; a small set of UBlueprintFunctionLibrary statics (plus
one AActor and one UWorldSubsystem) holding the deterministic decision
logic; and a classification step that turns the player's choices into a
graded outcome — interrogation rating, paragon meter, heist ending — which other
systems consume. None of the three rolls dice. A Truth/Doubt/Lie call is right
or wrong against an authored truth-state; a wheel spoke is unlocked or gated by
a threshold; a heist ends Smooth, Hard, or Catastrophic by a fixed rule over
measured metrics. That determinism is what makes them testable, and they are
tested. This page is part of the Signature Subsystems group; the section hub
is ../V5_ARCHITECTURE.md.
What ships, honestly#
All three modules are real, building Unreal C++ with domain-specific logic and
genuine automation tests that spawn worlds, run sessions, and assert
computed values — not skeletons, and not tests that only check truthiness. The
.so binaries (libUnrealEditor-V5Interrogation.so, -V5Dialogue.so,
-V5Heist.so) exist. But each sits at an honest distance from the captured
content and the in-world execution the design promises, and naming those
distances up front lets the rest of the page read at face value:
-
Interrogation logic is real; the faces are a content pipeline it points at.
V5Interrogationvalidates a tell library, runs Truth/Doubt/Lie scoring, plays back authored blendshape keys, and rates a session — all in tested C++. The 12-camera MotionScan capture, the per-actor 16-hour FACS calibration, and the 60 fps.usdblendshape stacks (arch §Capture Pipeline) are a production process, not code in this module: the validator only asserts that each tell references a/Game/V5FaceCapture/USD/path and carries ≥3 keyframes (V5InterrogationTellLibrary.cpp:198), and the player samples those keys deterministically. It does not perform face capture. -
The Vice Squad scene catalog is procedurally expanded, with placeholder prose.
ValidateSceneCatalogJsonexpands authored group templates into 54 scenes (V5InterrogationTellLibrary.cpp:306), each with three questions whose truth-states cycle and whose evidence requirements are derived. The logic is real; the dramatic lines ("The contradiction is pinned to collected evidence.") are templated, not 54 hand-written interrogations. -
The 12,000-node dialogue corpus is a validated manifest, not 12,000 authored lines. The wheel, gating, interrupt, and VO-bank lookup are real and tested against real
sampleTrees. The headline node count is a declared distribution the validator enforces (V5DialogueAuthoring.cpp:181) and thedialogue.pycompiler expands into node records (ids/speaker/cell) — the prose for the full corpus is not in the repo. -
The heist module is the planning + classification brain; the mission that spawns AI crew lives elsewhere.
V5Heistgenuinely linksV5CombatandV5Vehicles(V5Heist.Build.cs) and ships real plan validation, conflict geometry, rehearsal, a replay hash, and a three-ending resolver. ButResolveEndingclassifies anFV5HeistExecutionMetricsstruct that the in-world mission (theV5UrbanHeistCitymode plugin) must populate during play; the AI-crew spawn-and-drive runtime is not in this module.
The MotionScan interrogation system#
V5Interrogation is the Period cell's marquee verb: you watch a witness answer,
read the involuntary facial tell, and call their statement as Truth,
Doubt, or Lie — and a Lie is only a valid challenge if you are holding
the evidence that pins it.
Twelve tells and the Truth/Doubt/Lie call#
The vocabulary is a closed enum of twelve micro-expressions —
EV5InterrogationTellType (V5InterrogationTypes.h:8): EyeDart, BrowTwitch,
LipPress, JawClench, NostrilFlare, GazeBreak, MicroSmile, Swallow,
ShoulderTension, FingerTap, BreathHitch, VoiceCrack. The tell library is loaded
from JSON and validated hard: exactly 12 distinct tells, each a distinct
type, each with a USD-backed blendshape path and at least three keyframes; the
loader rejects anything else (V5InterrogationTellLibrary.cpp:233). A witness's
answer carries one of three hidden truth-states — Truthful, Withholding,
Lying (V5InterrogationTypes.h:24) — and the player picks one of three
responses, Truth, Doubt, Lie (:32).
The match is resolved by UV5_Interrogation_TruthDoubtLie::ResolveInput
(V5InterrogationTruthDoubtLie.cpp:3). The three correct pairings are explicit:
Truth↔Truthful, Doubt↔Withholding, and Lie↔Lying only when the required
evidence is in hand (:11). The scoring is graded by difficulty: a correct
evidence-backed Lie is worth +3, a correct Truth or Doubt +2, a Lie
attempted without the demanded evidence −2, and any other miss −1
(:16). That −2 is the design's teeth — bluffing a "Lie" call with no proof
is strictly worse than honestly doubting.
Evidence-backed lies and scoring#
The evidence gate is its own unit so it can be queried for UI affordances before
the player commits. UV5_Interrogation_EvidenceBackedLie::CanChallengeWithLie
returns true only when the statement is genuinely a lie, names a required
evidence id, and that id is in the player's inventory
(V5InterrogationEvidenceBackedLie.cpp:8) — the "press X to refute" prompt
lights up exactly when a refutation is actually available. A whole session's
calls are graded by UV5_Interrogation_RatingSystem::RateSession
(V5InterrogationRatingSystem.cpp:3): a Brilliant requires every call
correct and a score of at least 2 × the question count (so a flawless run
that never earned the +3 evidence bonus still clears it); ≥75% correct is
Good, ≥50% Average, else Poor (:23). The
V5.Interrogation.Session AndRating automation test drives a real
three-question scene — Doubt on a withholding answer, an evidence-backed Lie on
a lying answer, Truth on a truthful answer — and asserts the result is
Brilliant with bPerfect set (V5InterrogationTests.cpp:157). A Brilliant
rating is the gate the arch promises for downstream dialogue options and
companion reactions (arch §Interrogation Scoring).
Facial-tell playback, VO sync, and multi-pass#
The tell is not a flipbook —
UV5_Interrogation_FacialTellPlayer::SampleBlend shapeWeights
(V5InterrogationFacialTellPlayer.cpp:34) computes each blendshape weight as a
function of playback time, applying a linear fade from each keyframe's onset
scaled by the tell's IntensityScalar and taking the max where shapes overlap
(:42). It is a pure function of (tell, time), which is why two independent
players produce bit-identical weights — the test asserts exactly that
determinism on eyeLookRight_R (V5InterrogationTests.cpp:96).
UV5_Interrogation_VOSyncSubsystem::BuildSyncState
(V5InterrogationVOSyncSubsystem.cpp:3) layers lip-sync on top: it phase-drives
three visemes (A/E/M) across the line's duration and flags the facial tell
active while playback is within the tell window (:9), so the tell and the
mouth move together. Finally, AV5_Interrogation_Session
(V5InterrogationSession.cpp:7) is the per-scene actor that walks questions and
accumulates results, and UV5_Interrogation_MultiplePass grants a follow-up
pass whenever a session rates below Brilliant and fewer than three passes are
spent (V5InterrogationMultiplePass.cpp:18) — you can re-interrogate to claw
back a botched read, but a Brilliant closes the witness out.
Edge cases. Submitting a choice with no current question returns a default
zero-score result rather than crashing (V5InterrogationSession.cpp:36); a
non-lying question never demands evidence, so an honest Doubt is always
available; a malformed tell library surfaces a populated error array instead of
a partial load (V5InterrogationTests.cpp:116); and BestRating only improves
across passes, so a worse retry can never erase a prior better grade
(V5InterrogationMultiplePass.cpp:14).
The dialogue wheel#
V5Dialogue is the shared conversation system — primarily the sci-fi cell's
voice but reusable everywhere (arch §Module Inventory). It is a runtime state
machine plus an authoring/compile path, and its defining shape is the six-spoke
wheel with paragon/renegade morality baked into option gating.
Six spokes, fixed geometry, per-cell flavor#
UV5_Dialogue_Wheel::BuildWheelState always emits exactly six spokes at fixed
screen angles — Right at 0°, UpperRight 45°, UpperLeft 135°, Left 180°,
LowerLeft 225°, LowerRight 315° (V5DialogueWheel.cpp:5) — and tints each by
the option's tone (EV5DialogueTone: Neutral, Paragon, Renegade, Charm,
Intimidate, Continue), pulling Paragon/Charm to the cell's paragon tint and
Renegade/Intimidate to its renegade tint (:19). The tints and labels are
per-cell: Urban reads "Cop / Triad", Period "Family / Outsider"
(dialogue_cell_flavors.json), so the same wheel geometry speaks in each cell's
register. The state struct flags whether a Charm or Intimidate spoke is present
(:63) so the HUD can surface the persuasion affordance, and the
V5.Dialogue.BranchingWheel test asserts the wheel emits six spokes and marks
charm/intimidate availability (V5DialogueTests.cpp:65).
Paragon/Renegade gating and Charm/Intimidate#
The morality model lives in the runtime state — FV5DialogueRuntimeState
carries Paragon, Renegade, Reputation, and a set of state flags
(V5DialogueTypes.h:121). Option eligibility is decided by
UV5_Dialogue_CharmIntimidate::IsOptionUnlocked
(V5DialogueCharmIntimidate.cpp:3): an option is shown only if the player meets
its RequiredParagon, RequiredRenegade, and RequiredReputation thresholds
and holds any RequiredStateFlag. This is the arch's "Charm option needs
Paragon ≥ 70" rule made literal.
UV5_Dialogue_RuntimeStateMachine::ChooseOption applies the choice
(V5DialogueRuntimeStateMachine.cpp:40): it clamps the paragon/renegade meters
into [0,100] after each delta (:50), grants the option's state flag,
advances to the next node, and detects a terminal node (no options) to end the
conversation. The test seeds Paragon 80 / Reputation high, confirms all six root
spokes unlock including opt.charm and opt.threaten, then verifies a
low-reputation state rejects the same charm option (V5DialogueTests.cpp:79)
and that taking the paragon branch ticks the meter 80 → 85 and grants
flag.evac.mercy (:85) — gating that would be invisible against a stub.
Authoring: the 12,000-node catalog and the compile path#
Dialogue is authored in YAML and validated on two sides. In-engine,
UV5_Dialogue_Authoring::ValidateDialogueYaml enforces the launch distribution
— Urban 1,500 / Period 4,000 / Frontier 2,500 / Hunter 2,500 / SciFi 1,500,
summing to 12,000 nodes — and refuses any other shape
(V5DialogueAuthoring.cpp:181), then parses the sampleTrees into real
FV5DialogueNodeDefinition graphs the runtime can walk. Out-of-engine,
V5/tools/missions/dialogue.py is the compile-time path the arch names (arch
§Dialogue Authoring): it loads the same YAML, validates the identical
EXPECTED_COUNTS, and expands the groups into node records — assigning a
treeId every 24 nodes and a speaker every 64 — written out as a
V5DialogueDataTable artifact (compile_dialogue). The two validators agreeing
on 12,000 is the cross-check; as noted above, the count is a manifest target,
and only the sample trees carry shipped prose.
Edge cases. BeginTree fails closed if the tree has no id, no root, or no
nodes, and reports an immediately-terminal root rather than starting a dead
conversation (V5DialogueRuntimeStateMachine.cpp:5); choosing an ineligible or
unknown option leaves the state untouched and returns false (:44); and
UV5_Dialogue_Interrupt::EvaluateInterrupt fires a Paragon/Renegade cinematic
interrupt only when the input lands on the exact trigger frame
(V5DialogueInterrupt.cpp:8) — the frame-perfect QTE window the arch describes
for interrupt-able cinematic ranges.
The heist planner#
V5Heist is the urban-crime cell's signature loop: before the job you author
the crew, vehicles, loadouts, and route on a planning board; you rehearse it at
quarter speed to surface conflicts; and after the job a fixed rule grades the
outcome. The module links V5Combat and V5Vehicles (V5Heist.Build.cs) and
exposes its logic as a family of UBlueprintFunctionLibrary statics plus one
Slate panel.
Plan validation#
Each authoring stage has a validator that returns a tagged
FV5HeistValidationResult. UV5_Heist_CrewSelection::ValidateCrewSelection
requires unique crew ids, 1–4 protagonists, at least one Driver, and at
least one specialist (Hacker or Demo) (V5HeistSystems.cpp:105).
ValidateVehicleAssignments demands a Getaway vehicle, a real assigned crew
member, and reliability ≥ 0.3 (:154). ValidateWeaponLoadouts insists every
crew member has a primary, secondary, and special item (:175). And
UV5_Heist_RouteAuthoring::ValidateRoute requires all six waypoint phases —
Approach, Entry, InsideTarget, Escape, DumpVehicle, Safehouse — to be present
(:212), with BuildOrderedRoute sorting by sequence index (:202). The
20-heist catalog (12 main-campaign + 8 co-op replay templates, enforced at
V5HeistCatalog.cpp:321; 3,537 lines of authored intel/crew/route data) is the
content these validators run over.
Conflict detection is real geometry#
The rehearsal's value is its conflict highlighting, and that is genuine
computational geometry, not a flag.
UV5_Heist_ConflictDetection::DetectConflicts walks each consecutive route
segment and, for every patrol zone, computes the true point-to-segment
distance from the zone center to the path (DistancePointToSegment,
V5HeistSystems.cpp:20 — projecting the center onto the segment with a clamped
dot-product). A segment that passes within a zone's radius raises a
HighPatrolZone conflict whose severity is Red when the zone's patrol
weight ≥ 0.75 (else Amber) and whose risk score is weight × 100 (:250). The
V5.Heist test lays a patrol zone over a route leg and asserts a Red conflict
is flagged (V5HeistTests.cpp:70). That risk feeds
UV5_Heist_Planner::BuildPlannerPanelState, which sums conflict risk and maps
it to an accent color — green / amber / red at the 25 and 75 thresholds
(V5HeistSystems.cpp:68) — and renders through the real SV5HeistPlannerPanel
Slate widget (:83).
Rehearsal, live re-route, replay, and three endings#
UV5_Heist_RehearsalPreview::BuildRehearsalPreview fixes the 0.25× time
scale the arch promises (4× slowed) and stretches the preview duration
accordingly — a 120 s plan rehearses in 480 s (V5HeistSystems.cpp:261,
asserted at V5HeistTests.cpp:75). Mid-mission,
UV5_Heist_LiveOverride::ApplyLiveOverride swaps in a re-ordered route and
marks each waypoint bRuntimeOverride only while the Tab-pause is active
(:270) — the live re-routing the design calls for.
UV5_Heist_ReplayExport::BuildReplayExport builds a deterministic CRC32 hash
over the plan id, seed, crew count, and waypoint count (:284) so a plan can be
exported and reproduced. The capstone is
UV5_Heist_EndingResolver::ResolveEnding (:298), a strict classifier over
measured metrics: a failed escape, any crew loss, or near-zero gain is
Catastrophic (cut ×0.25, no follow-up); a clean run — no alarm, no civilian
casualties, ≥95% gain — is Smooth (cut ×1.15, follow-up unlocked);
everything between is Hard, with the cut clamped to the gain fraction and
follow-up gated at ≥35% gain. The V5.Heist.EndingClassification test pins all
three arms (V5HeistTests.cpp:107).
Edge cases. Conflict detection skips a degenerate zero-length segment by
falling back to point distance (V5HeistSystems.cpp:24); a live override with
an empty route or no active pause returns the current route unchanged (:272);
a replay is marked exportable only with a real heist id and non-empty crew and
route (:294); and the ending resolver's Catastrophic check runs first, so an
escape failure outranks an otherwise-high gain.
Where this connects#
These three verbs are the cells' showcases, and they hand off to the rest of V5 at well-defined seams:
- Presentation. The MotionScan tells, viseme lip-sync, dialogue-wheel HUD, and heist planner Slate panel are realized by the audio/VFX/UI layer — ./audio-vfx-cinematics-ui.md owns the VO ducking, the wheel widget, and the cinematic interrupt cameras these systems drive.
- Creation. Interrogation scenes, dialogue trees, and heists are all authorable content; the in-editor authoring tools (Heist Author, the dialogue and case authoring panels) and the cross-cell Mind Palace deduction board live in ./creator-suite-and-mind-palace.md.
- Cell spine. Interrogation is the Period Vice Squad's marquee loop and the dialogue wheel threads through the Hunter and Period stories; the cell rulesets, the Made Man/Vice Squad period structure, and the Bureau XP ledger that consumes interrogation ratings are covered in ./hunter-period-systems-and-bureau-spine.md.
- For these sections in prose and the full subsystem glossary, return to the hub: ../V5_ARCHITECTURE.md.