Dialogue was already plain text, so the gap was never the .yarn files. It was
that the character material a writing session needs was either missing or stored
somewhere a writer could not comfortably work.
Three things were wrong. The writing handbook was not in the repo and was cited
by SC101.yarn as though it were. The best voice material — register, verbal tics,
success/failure/passive samples — was a \n-escaped string inside a JSON field.
The named NPCs had no sheets at all, existing only as their Yarn lines.
writing/ now holds the dialogue-facing layer, at the repo root so it gets no
.meta files and is not a Unity asset:
STYLE.md register, the five tone moves, the ban list, branching
shapes, the Wrong Truth rule, flag families, candidate rules
YARN-PRIMER.md the one page of syntax needed to write a scene
GLOSSARY.md in-world and project vocabulary
voices/ the eleven skill voices, one readable sheet each
characters/ Bartender, Chevalier Cassian Thal, Bradford Kane
scenes/ SC-101's brief: intent, beats, checks, constraints
lore/ the three identity readings and the rules that bind them
STYLE.md is a digest of the Mystery Writing Handbook in the Obsidian vault, with
pointers rather than copies. The vault stays the authoring home for design work,
and Ground Truth — the solution document — deliberately stays out of this repo.
The voice sheets are generated from skill_bible.json but are the source of truth
for the prose. tools/writing/sync_voices.py folds edits back; voicelib.py
guarantees notes -> dict -> notes is identity for all eleven, so --to-json on
unchanged sheets is byte-identical. VoiceSheetSyncTests fails the build on drift
in either direction, and was verified to fail on real drift rather than merely
to pass.
SC101.yarn's header is trimmed from prose rationale to the six rules that bind
while editing that file, with the reasoning moved to the scene brief.
EditMode 42/42. YarnCheck compiles 9 files / 35 nodes.
Co-Authored-By: Claude Opus 5 <[email protected]>
Both were test bugs; MotionInputMath itself is correct.
WorldToCameraRelativeInput_Yaw90 expected +1 for the camera-relative x. A camera
yawed +90 degrees faces +X, so world +Z is to its left and the value is -1. The
conversion applies Quaternion.Euler(0, -cameraYaw, 0) and Unity's Y rotation is
clockwise viewed from above, so forward maps to -right. The round-trip test
already pinned this convention and passes precisely because of it.
ScreenDeltaToWorldPan compared Vector3 with Assert.AreEqual, which is exact.
-10f * 0.02f lands one ulp from the -0.2f literal, so a correct result failed a
comparison that printed as identical at two decimals. Now compared per component
with a tolerance.
EditMode suite is 39/39.
Co-Authored-By: Claude Opus 5 <[email protected]>
This is the scene the camera rig, UI layer, player control, and dialogue work in
the preceding commits were built against: 14 roots covering Room_101, the
Dialogue and Skill systems, the three UI canvases, the Cinemachine brain and
camera, the reveal volume, navigation, and the camera anchor.
Committed as-is, in binary, deliberately. The file is a valid Unity scene and
opens intact, but it is serialized as binary while the project is set to Force
Text, so it is not diffable or mergeable. Converting it needs the Asset
Serialization setting to be re-applied from the Editor UI, which cannot be done
from a batch-mode run — the setter does not trigger a reserialize pass. Checking
it in binary is strictly better than leaving days of scene wiring untracked;
the conversion is a follow-up, tracked in docs/restructure-plan.md.
Co-Authored-By: Claude Opus 5 <[email protected]>
DialogueInteractable, DialogueTransitionController, and DialogueUIVisibility are
reworked against the new UI layer: visibility is driven by UIStateController
rather than each component toggling canvases itself, which removes the
duplicated show/hide logic the two had accumulated.
NPCStandIn now ignores the interact button while UIStateController reports a
menu open, so pressing interact through an open panel no longer starts a
conversation behind it.
The yarnproject change is graph-editor node positions only.
Co-Authored-By: Claude Opus 5 <[email protected]>
Editor/PlayerControlSetup.cs is the one-shot menu that assembles the playable
player: the armature, the Cinemachine rig, the NavMesh agent, and the pointer
input wiring, so a scene can be brought up to a controllable state repeatably
rather than by hand.
StarterAssets input actions gain the pointer and interaction bindings the new
locomotion and interaction layers read, and PlayerArmature is updated to match.
ThirdPersonController blends the animator toward target speed scaled by input
magnitude. Previously a partial stick deflection or a click-to-move arrival
played a slowed run clip via MotionSpeed alone; scaling the blend makes those
use the walk cycle instead.
Co-Authored-By: Claude Opus 5 <[email protected]>
Runtime UI under Scripts/UI: a UIStateController owning menu/dialogue state, a
PlayerControlLock so opening a panel suppresses world input, panel visibility
helpers, and the menu input binding.
Views: the dialogue stream presenter and its options presenter and message row,
the portrait HUD, and the player menu with character, journal, and tab-bar
views. Skills gains SkillsPanelView and SkillRowView, which is why the Skills
asmdef now references UnityEngine.UI.
Prefabs/UI holds the authored canvases (dialogue stream, HUD, player menu) and
their rows. UILayerBootstrap remains a hand-added runtime fallback for scenes
where those prefabs are not instantiated; it deliberately does not auto-spawn.
Editor/UILayerSetup.cs is the one-shot menu that builds and wires those prefabs
into a scene, with UILayerSetupAutoRun reminding after a script reload.
Art/UI/Placeholder carries the placeholder portrait the HUD falls back to.
Co-Authored-By: Claude Opus 5 <[email protected]>
Camera: Cinemachine-based rig with an anchor, a state director, point-of-
interest framing, pan input, and dialogue framing volumes.
Player: click-to-move over the NavMesh, a pointer input layer, and a motion
arbiter that reconciles direct stick input with navigation-driven movement.
The pure math is isolated in NightclubArcadia.Locomotion.Math with an EditMode
suite, so the arbitration rules are testable without a scene.
Interaction: IClickInteractable plus a bridge that routes pointer clicks to it.
Supporting project settings: adds the Cinemachine package, the Interactable and
Ground tags the raycasts depend on, and NavMesh agent dimensions matched to the
player capsule (radius 0.3, height 1.8, climb 0.25).
Co-Authored-By: Claude Opus 5 <[email protected]>