Skip to content

Commit dcd3709

Browse files
authored
feat: drop bird crest, route Letters Capture to world, rename Profile → My Identity (#55)
* feat(kira): hide MB_Crest meshes + export loadMaskedScene The Blender GLB ships with stacked crest pieces on the crown. Hide them at load time so the bird reads as a bald-headed Masked Bower — the designer asked to drop the hair. Also export loadMaskedScene so the onboarding hatch preview (a separate consumer) can clone the loaded scene to render the same bird that flies in one screen later, instead of falling through to buildStandingBird with the masked palette (which produces a yellow-ish generic bird that doesn't match the world Kira). * feat(nav, capture): rename Profile→My Identity, reorder rail, route Letters Capture to world Two related tweaks: **Navigation rail** — top group reorders to Home → My Identity → Path Finder → History (was: Home → History → Profile → Path Finder) and the Profile pill is relabeled to "My Identity". The bottom group (Letters, Settings) is unchanged. ProfileSheet's page title also updates to keep the language consistent with the rail. **Letters Capture** — clicking Capture inside a letter now routes to / first, then opens the AskSheet via OverlayController. The sheet's existing camera-zoom + captureFocus useEffect was always designed to fire from the world canvas, but the prior wiring opened the overlay while still on /letters — the camera dolly ran "off-screen" with no visible world. With the route hop in place the typical capture flow plays correctly: camera dollies toward Kira, she turns to face the lens, sheet mounts on top. Tests updated for the new rail order/label.
1 parent 318d500 commit dcd3709

6 files changed

Lines changed: 41 additions & 7 deletions

File tree

src/components/student-space/navigation/SideRail.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ const TOP_RAIL_ITEMS: Array<{
2424
Icon: LucideIcon
2525
}> = [
2626
{ id: 'home', label: 'Island', Icon: Home },
27-
{ id: 'history', label: 'History', Icon: History },
28-
{ id: 'profile', label: 'Profile', Icon: User },
27+
{ id: 'profile', label: 'My Identity', Icon: User },
2928
{ id: 'trajectory', label: 'Path Finder', Icon: Compass },
29+
{ id: 'history', label: 'History', Icon: History },
3030
]
3131

3232
const BOTTOM_RAIL_ITEMS: Array<{

src/components/student-space/sheets/LettersSheet.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ export function LettersSheet() {
100100
const overlay = (
101101
engine as unknown as { view?: { overlayController?: OverlayControllerLike } } | null
102102
)?.view?.overlayController
103+
// Capture is a world-anchored ceremony — the camera dollies in on Kira
104+
// and her `captureFocus` flag turns her toward the camera. That only
105+
// reads correctly from the world route, so route home first and let the
106+
// overlay mount inside StudentSpaceHost. The AskSheet's camera-zoom
107+
// useEffect fires once it sees `open` flip true on the world canvas.
108+
navigate({ to: '/' })
103109
overlay?.open('ask', { prompt, dismissOnBack: true, letterId: selectedId })
104110
}
105111

src/components/student-space/sheets/ProfileSheet.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ export function ProfileSheet() {
273273
<PageSurface className="isolate" style={themeStyle}>
274274
<SheetSidebar>
275275
<SheetIdentityHeader>
276-
<SheetTitle>Profile</SheetTitle>
276+
<SheetTitle>My Identity</SheetTitle>
277277
<SheetDescription>
278278
The shape of your reflections so far across values, interests, personality, and skills.
279279
</SheetDescription>

src/engine/student-space/Game/View/Kira.d.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,22 @@ export type StandingBirdParts = {
4848
wingBaseZR: number
4949
}
5050
export function buildStandingBird(spec: KiraSpecies): StandingBirdParts
51+
52+
// Loader for the Blender-authored Masked Bower GLB. Module-cached, so the
53+
// scene is parsed once and re-handed to every caller. The scene already has
54+
// the world-Kira's setup applied: yaw flip, scale, crest hidden, body/tie
55+
// recolor, leg pivots reparented, bone refs surfaced.
56+
//
57+
// IMPORTANT: a THREE.Object3D can only have one parent. Don't add the
58+
// returned `scene` to your own group directly while the world Kira owns
59+
// it — clone first with SkeletonUtils.clone() if you need a second copy
60+
// (e.g. for the onboarding hatchling preview).
61+
export function loadMaskedScene(): Promise<{
62+
scene: import('three').Object3D
63+
head: import('three').Object3D | null
64+
wingL: import('three').Object3D | null
65+
wingR: import('three').Object3D | null
66+
beakLower: import('three').Object3D | null
67+
legPivotL: import('three').Group | null
68+
legPivotR: import('three').Group | null
69+
}>

src/engine/student-space/Game/View/Kira.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ function _makeMaskedLegPivot(legPost, foot, hipX)
5858
}
5959

6060
let _maskedScenePromise = null
61-
function loadMaskedScene()
61+
export function loadMaskedScene()
6262
{
6363
if(_maskedScenePromise) return _maskedScenePromise
6464

@@ -88,6 +88,15 @@ function loadMaskedScene()
8888
o.castShadow = true
8989
o.receiveShadow = true
9090

91+
// The Blender export ships with stacked crest pieces on the
92+
// crown. Hide them — the colorway already reads as a Masked
93+
// Bower without the crest, and the user asked for a clean
94+
// bald-headed silhouette.
95+
if(/^MB_Crest/i.test(o.name))
96+
{
97+
o.visible = false
98+
}
99+
91100
// Repaint a couple of materials to match the requested
92101
// colorway: body+wings warm orange like the head, tie red.
93102
// Idempotent — module-level promise caches the scene.

test/components/student-space/navigation/side-rail.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ describe('SideRail', () => {
9696
it.each([
9797
['Letters', '/letters'],
9898
['History', '/history'],
99-
['Profile', '/profile'],
99+
['My Identity', '/profile'],
100100
['Path Finder', '/trajectory'],
101101
])('marks %s active immediately while routed navigation is pending', async (label, expectedPathname) => {
102102
const user = userEvent.setup()
@@ -116,7 +116,7 @@ describe('SideRail', () => {
116116
})
117117

118118
it.each([
119-
['Profile', '/profile'],
119+
['My Identity', '/profile'],
120120
['Letters', '/letters'],
121121
['Path Finder', '/trajectory'],
122122
['History', '/history'],
@@ -153,7 +153,7 @@ describe('SideRail', () => {
153153
const labelsIn = (group: HTMLElement) =>
154154
Array.from(group.querySelectorAll('button')).map((b) => b.getAttribute('aria-label'))
155155

156-
expect(labelsIn(topGroup)).toEqual(['Island', 'History', 'Profile', 'Path Finder'])
156+
expect(labelsIn(topGroup)).toEqual(['Island', 'My Identity', 'Path Finder', 'History'])
157157
expect(labelsIn(bottomGroup)).toEqual(['Letters', 'Settings'])
158158
})
159159

0 commit comments

Comments
 (0)