feat(toolkit): scaffold wiki-driven toolkit sections
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
|||||||
requiredPageRoutes,
|
requiredPageRoutes,
|
||||||
toolkitRoutes,
|
toolkitRoutes,
|
||||||
routeDefinitions,
|
routeDefinitions,
|
||||||
|
normalizeRouteName,
|
||||||
} from '../src/navigation/routes';
|
} from '../src/navigation/routes';
|
||||||
|
|
||||||
describe('routeDefinitions', () => {
|
describe('routeDefinitions', () => {
|
||||||
@@ -19,11 +20,19 @@ describe('routeDefinitions', () => {
|
|||||||
'policiesCookies',
|
'policiesCookies',
|
||||||
'credits',
|
'credits',
|
||||||
'toolkit',
|
'toolkit',
|
||||||
'toolkitOpeningSequence',
|
'toolkitOpening',
|
||||||
'toolkitCredits',
|
'toolkitPetitioners',
|
||||||
'toolkitAudio',
|
'toolkitRulings',
|
||||||
'toolkitLore',
|
'toolkitSeed',
|
||||||
'toolkitResources',
|
'toolkitResources',
|
||||||
|
'toolkitMap',
|
||||||
|
'toolkitFactions',
|
||||||
|
'toolkitFlags',
|
||||||
|
'toolkitStory',
|
||||||
|
'toolkitLore',
|
||||||
|
'toolkitAudio',
|
||||||
|
'toolkitEndings',
|
||||||
|
'toolkitReferences',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for (const routeName of requiredPageRoutes) {
|
for (const routeName of requiredPageRoutes) {
|
||||||
@@ -50,11 +59,26 @@ describe('routeDefinitions', () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
expect(toolkitRoutes).toEqual([
|
expect(toolkitRoutes).toEqual([
|
||||||
'toolkitOpeningSequence',
|
'toolkitOpening',
|
||||||
'toolkitCredits',
|
'toolkitPetitioners',
|
||||||
'toolkitAudio',
|
'toolkitRulings',
|
||||||
'toolkitLore',
|
'toolkitSeed',
|
||||||
'toolkitResources',
|
'toolkitResources',
|
||||||
|
'toolkitMap',
|
||||||
|
'toolkitFactions',
|
||||||
|
'toolkitFlags',
|
||||||
|
'toolkitStory',
|
||||||
|
'toolkitLore',
|
||||||
|
'toolkitAudio',
|
||||||
|
'toolkitEndings',
|
||||||
|
'toolkitReferences',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('normalizes stale persisted toolkit routes', () => {
|
||||||
|
expect(normalizeRouteName('toolkitOpeningSequence')).toBe('toolkit');
|
||||||
|
expect(normalizeRouteName('toolkitCredits')).toBe('toolkit');
|
||||||
|
expect(normalizeRouteName('not-a-route')).toBe('home');
|
||||||
|
expect(normalizeRouteName('toolkitPetitioners')).toBe('toolkitPetitioners');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -240,11 +240,21 @@ describe('screen scaffold', () => {
|
|||||||
const text = getRenderedText(renderer);
|
const text = getRenderedText(renderer);
|
||||||
|
|
||||||
expect(text).toContain('Toolkit');
|
expect(text).toContain('Toolkit');
|
||||||
expect(text).toContain('Opening Sequence');
|
expect(text).toContain('Opening');
|
||||||
expect(text).toContain('Credits');
|
expect(text).toContain('Petitioners');
|
||||||
expect(text).toContain('Audio');
|
expect(text).toContain('Rulings');
|
||||||
expect(text).toContain('Lore');
|
expect(text).toContain('Seed');
|
||||||
expect(text).toContain('Resources');
|
expect(text).toContain('Resources');
|
||||||
|
expect(text).toContain('Map');
|
||||||
|
expect(text).toContain('Factions');
|
||||||
|
expect(text).toContain('Flags');
|
||||||
|
expect(text).toContain('Story');
|
||||||
|
expect(text).toContain('Lore');
|
||||||
|
expect(text).toContain('Audio');
|
||||||
|
expect(text).toContain('Endings');
|
||||||
|
expect(text).toContain('References');
|
||||||
|
expect(text).toContain('Read-only planning panel');
|
||||||
|
expect(text).toContain('Future authoring area');
|
||||||
|
|
||||||
const homeButton = renderer.root.findByProps({ testID: 'toolkit-home' });
|
const homeButton = renderer.root.findByProps({ testID: 'toolkit-home' });
|
||||||
|
|
||||||
@@ -256,6 +266,48 @@ describe('screen scaffold', () => {
|
|||||||
expect(renderer.root.findByProps({ testID: 'toolkit-socket-indicator' })).toBeTruthy();
|
expect(renderer.root.findByProps({ testID: 'toolkit-socket-indicator' })).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders purpose panels for every toolkit section without forms', () => {
|
||||||
|
const expectedPurposeText = {
|
||||||
|
toolkitAudio: 'Preview and catalog sound identity',
|
||||||
|
toolkitEndings: 'Tune collapse states',
|
||||||
|
toolkitFactions: 'Tune faction trust',
|
||||||
|
toolkitFlags: 'Debug delayed consequences',
|
||||||
|
toolkitLore: 'Reference changing canon',
|
||||||
|
toolkitMap: 'Support settlement and world-map iteration',
|
||||||
|
toolkitOpening: 'Preview and sequence the first-run premise',
|
||||||
|
toolkitPetitioners: 'Inspect and eventually author throne-room cases',
|
||||||
|
toolkitReferences: 'Keep design inspirations',
|
||||||
|
toolkitResources: 'Tune the core Bones, Minions, and Milk economy',
|
||||||
|
toolkitRulings: 'Inspect and eventually tune choice outcomes',
|
||||||
|
toolkitSeed: 'Debug why a day produced specific petitioners',
|
||||||
|
toolkitStory: 'Organize Act I-IV pressure',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
for (const routeName of toolkitRoutes) {
|
||||||
|
const renderer = render(
|
||||||
|
<ToolkitScreen
|
||||||
|
goHome={jest.fn()}
|
||||||
|
navigate={jest.fn()}
|
||||||
|
routeName={routeName}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
const text = getRenderedText(renderer);
|
||||||
|
|
||||||
|
expect(text).toContain(expectedPurposeText[routeName]);
|
||||||
|
expect(renderer.root.findByProps({ testID: 'toolkit-purpose' })).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
renderer.root.findByProps({ testID: 'toolkit-change-note' }),
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(renderer.root.findByProps({ testID: 'toolkit-sources' })).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
renderer.root.findByProps({ testID: 'toolkit-future-fields' }),
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
renderer.root.findByProps({ testID: 'toolkit-future-placeholder' }),
|
||||||
|
).toBeTruthy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('renders the toolkit websocket connection indicator', () => {
|
it('renders the toolkit websocket connection indicator', () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
useToolkitSocketStore.getState().setConnected('test-socket');
|
useToolkitSocketStore.getState().setConnected('test-socket');
|
||||||
@@ -300,10 +352,10 @@ describe('screen scaffold', () => {
|
|||||||
expect(optionGroup.props.style).toMatchObject({ flexDirection: 'row' });
|
expect(optionGroup.props.style).toMatchObject({ flexDirection: 'row' });
|
||||||
expect(homeSlot.props.style).toMatchObject({ marginLeft: 'auto' });
|
expect(homeSlot.props.style).toMatchObject({ marginLeft: 'auto' });
|
||||||
expect(
|
expect(
|
||||||
renderer.root.findByProps({ testID: 'toolkit-toolkitOpeningSequence-icon' }),
|
renderer.root.findByProps({ testID: 'toolkit-toolkitOpening-icon' }),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(
|
expect(
|
||||||
renderer.root.findByProps({ testID: 'toolkit-toolkitOpeningSequence-label' }),
|
renderer.root.findByProps({ testID: 'toolkit-toolkitOpening-label' }),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { PoliciesScreen } from '../screens/PoliciesScreen';
|
|||||||
import { SettingsScreen } from '../screens/SettingsScreen';
|
import { SettingsScreen } from '../screens/SettingsScreen';
|
||||||
import { ToolkitScreen } from '../screens/ToolkitScreen';
|
import { ToolkitScreen } from '../screens/ToolkitScreen';
|
||||||
import { useNavigationStore } from '../state';
|
import { useNavigationStore } from '../state';
|
||||||
import { toolkitRoutes, type RouteName } from './routes';
|
import { normalizeRouteName, toolkitRoutes, type RouteName } from './routes';
|
||||||
|
|
||||||
const fadeOutDurationMs = process.env.NODE_ENV === 'test' ? 0 : 140;
|
const fadeOutDurationMs = process.env.NODE_ENV === 'test' ? 0 : 140;
|
||||||
const fadeInDurationMs = process.env.NODE_ENV === 'test' ? 0 : 180;
|
const fadeInDurationMs = process.env.NODE_ENV === 'test' ? 0 : 180;
|
||||||
@@ -19,8 +19,9 @@ export function AppNavigator() {
|
|||||||
const cancelPendingRoute = useNavigationStore(state => state.cancelPendingRoute);
|
const cancelPendingRoute = useNavigationStore(state => state.cancelPendingRoute);
|
||||||
const completeNavigation = useNavigationStore(state => state.completeNavigation);
|
const completeNavigation = useNavigationStore(state => state.completeNavigation);
|
||||||
const pendingRouteName = useNavigationStore(state => state.pendingRouteName);
|
const pendingRouteName = useNavigationStore(state => state.pendingRouteName);
|
||||||
const routeName = useNavigationStore(state => state.currentRouteName);
|
const rawRouteName = useNavigationStore(state => state.currentRouteName);
|
||||||
const setPendingRoute = useNavigationStore(state => state.setPendingRoute);
|
const setPendingRoute = useNavigationStore(state => state.setPendingRoute);
|
||||||
|
const routeName = normalizeRouteName(rawRouteName);
|
||||||
const opacity = useRef(new Animated.Value(1)).current;
|
const opacity = useRef(new Animated.Value(1)).current;
|
||||||
const isTransitioning = useRef(false);
|
const isTransitioning = useRef(false);
|
||||||
|
|
||||||
@@ -29,9 +30,15 @@ export function AppNavigator() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
completeNavigation(pendingRouteName);
|
completeNavigation(normalizeRouteName(pendingRouteName));
|
||||||
}, [completeNavigation, pendingRouteName]);
|
}, [completeNavigation, pendingRouteName]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (routeName !== rawRouteName) {
|
||||||
|
completeNavigation(routeName);
|
||||||
|
}
|
||||||
|
}, [completeNavigation, rawRouteName, routeName]);
|
||||||
|
|
||||||
const navigate = useCallback((nextRouteName: RouteName) => {
|
const navigate = useCallback((nextRouteName: RouteName) => {
|
||||||
if (
|
if (
|
||||||
nextRouteName === routeName ||
|
nextRouteName === routeName ||
|
||||||
|
|||||||
+90
-19
@@ -43,25 +43,57 @@ export const routeDefinitions = {
|
|||||||
label: 'Toolkit',
|
label: 'Toolkit',
|
||||||
title: 'Toolkit',
|
title: 'Toolkit',
|
||||||
},
|
},
|
||||||
toolkitOpeningSequence: {
|
toolkitOpening: {
|
||||||
label: 'Opening Sequence',
|
label: 'Opening',
|
||||||
title: 'Opening Sequence',
|
title: 'Opening Sequence',
|
||||||
},
|
},
|
||||||
toolkitCredits: {
|
toolkitPetitioners: {
|
||||||
label: 'Credits',
|
label: 'Petitioners',
|
||||||
title: 'Toolkit Credits',
|
title: 'Petitioners',
|
||||||
},
|
},
|
||||||
toolkitAudio: {
|
toolkitRulings: {
|
||||||
label: 'Audio',
|
label: 'Rulings',
|
||||||
title: 'Audio',
|
title: 'Rulings',
|
||||||
|
},
|
||||||
|
toolkitSeed: {
|
||||||
|
label: 'Seed',
|
||||||
|
title: 'Daily Seed',
|
||||||
|
},
|
||||||
|
toolkitResources: {
|
||||||
|
label: 'Resources',
|
||||||
|
title: 'Resources',
|
||||||
|
},
|
||||||
|
toolkitMap: {
|
||||||
|
label: 'Map',
|
||||||
|
title: 'World Map',
|
||||||
|
},
|
||||||
|
toolkitFactions: {
|
||||||
|
label: 'Factions',
|
||||||
|
title: 'Factions',
|
||||||
|
},
|
||||||
|
toolkitFlags: {
|
||||||
|
label: 'Flags',
|
||||||
|
title: 'Flags & History',
|
||||||
|
},
|
||||||
|
toolkitStory: {
|
||||||
|
label: 'Story',
|
||||||
|
title: 'Story Arcs',
|
||||||
},
|
},
|
||||||
toolkitLore: {
|
toolkitLore: {
|
||||||
label: 'Lore',
|
label: 'Lore',
|
||||||
title: 'Lore',
|
title: 'Lore',
|
||||||
},
|
},
|
||||||
toolkitResources: {
|
toolkitAudio: {
|
||||||
label: 'Resources',
|
label: 'Audio',
|
||||||
title: 'Resources',
|
title: 'Audio',
|
||||||
|
},
|
||||||
|
toolkitEndings: {
|
||||||
|
label: 'Endings',
|
||||||
|
title: 'Failure & Endings',
|
||||||
|
},
|
||||||
|
toolkitReferences: {
|
||||||
|
label: 'References',
|
||||||
|
title: 'References',
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@@ -84,12 +116,43 @@ export const policyRoutes = [
|
|||||||
] as const satisfies readonly RouteName[];
|
] as const satisfies readonly RouteName[];
|
||||||
|
|
||||||
export const toolkitRoutes = [
|
export const toolkitRoutes = [
|
||||||
|
'toolkitOpening',
|
||||||
|
'toolkitPetitioners',
|
||||||
|
'toolkitRulings',
|
||||||
|
'toolkitSeed',
|
||||||
|
'toolkitResources',
|
||||||
|
'toolkitMap',
|
||||||
|
'toolkitFactions',
|
||||||
|
'toolkitFlags',
|
||||||
|
'toolkitStory',
|
||||||
|
'toolkitLore',
|
||||||
|
'toolkitAudio',
|
||||||
|
'toolkitEndings',
|
||||||
|
'toolkitReferences',
|
||||||
|
] as const satisfies readonly RouteName[];
|
||||||
|
|
||||||
|
export type ToolkitRouteName = (typeof toolkitRoutes)[number];
|
||||||
|
|
||||||
|
const legacyToolkitRoutes = [
|
||||||
'toolkitOpeningSequence',
|
'toolkitOpeningSequence',
|
||||||
'toolkitCredits',
|
'toolkitCredits',
|
||||||
'toolkitAudio',
|
] as const;
|
||||||
'toolkitLore',
|
|
||||||
'toolkitResources',
|
export function normalizeRouteName(routeName: unknown): RouteName {
|
||||||
] as const satisfies readonly RouteName[];
|
if (typeof routeName !== 'string') {
|
||||||
|
return 'home';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routeName in routeDefinitions) {
|
||||||
|
return routeName as RouteName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((legacyToolkitRoutes as readonly string[]).includes(routeName)) {
|
||||||
|
return 'toolkit';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'home';
|
||||||
|
}
|
||||||
|
|
||||||
export const requiredPageRoutes = [
|
export const requiredPageRoutes = [
|
||||||
'home',
|
'home',
|
||||||
@@ -102,9 +165,17 @@ export const requiredPageRoutes = [
|
|||||||
'policiesCookies',
|
'policiesCookies',
|
||||||
'credits',
|
'credits',
|
||||||
'toolkit',
|
'toolkit',
|
||||||
'toolkitOpeningSequence',
|
'toolkitOpening',
|
||||||
'toolkitCredits',
|
'toolkitPetitioners',
|
||||||
'toolkitAudio',
|
'toolkitRulings',
|
||||||
'toolkitLore',
|
'toolkitSeed',
|
||||||
'toolkitResources',
|
'toolkitResources',
|
||||||
|
'toolkitMap',
|
||||||
|
'toolkitFactions',
|
||||||
|
'toolkitFlags',
|
||||||
|
'toolkitStory',
|
||||||
|
'toolkitLore',
|
||||||
|
'toolkitAudio',
|
||||||
|
'toolkitEndings',
|
||||||
|
'toolkitReferences',
|
||||||
] as const satisfies readonly RouteName[];
|
] as const satisfies readonly RouteName[];
|
||||||
|
|||||||
@@ -2,17 +2,15 @@ import { StyleSheet, Text, View } from 'react-native';
|
|||||||
|
|
||||||
import { BackHomeButton } from '../components/BackHomeButton';
|
import { BackHomeButton } from '../components/BackHomeButton';
|
||||||
import { ScreenLayout } from '../components/ScreenLayout';
|
import { ScreenLayout } from '../components/ScreenLayout';
|
||||||
import { routeDefinitions, type RouteName } from '../navigation/routes';
|
import {
|
||||||
|
routeDefinitions,
|
||||||
|
type RouteName,
|
||||||
|
type ToolkitRouteName,
|
||||||
|
} from '../navigation/routes';
|
||||||
|
|
||||||
type PageRouteName = Exclude<
|
type PageRouteName = Exclude<
|
||||||
RouteName,
|
RouteName,
|
||||||
| 'home'
|
'home' | 'policies' | 'settings' | 'toolkit' | ToolkitRouteName
|
||||||
| 'policies'
|
|
||||||
| 'settings'
|
|
||||||
| 'toolkit'
|
|
||||||
| 'toolkitOpeningSequence'
|
|
||||||
| 'toolkitCredits'
|
|
||||||
| 'toolkitAudio'
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type PageScreenProps = Readonly<{
|
type PageScreenProps = Readonly<{
|
||||||
|
|||||||
+378
-21
@@ -5,32 +5,255 @@ import {
|
|||||||
routeDefinitions,
|
routeDefinitions,
|
||||||
toolkitRoutes,
|
toolkitRoutes,
|
||||||
type RouteName,
|
type RouteName,
|
||||||
|
type ToolkitRouteName,
|
||||||
} from '../navigation/routes';
|
} from '../navigation/routes';
|
||||||
import { useToolkitSocketStore, type ToolkitSocketStatus } from '../state';
|
import { useToolkitSocketStore, type ToolkitSocketStatus } from '../state';
|
||||||
|
|
||||||
type ToolkitRouteName = (typeof toolkitRoutes)[number];
|
|
||||||
|
|
||||||
type ToolkitScreenProps = Readonly<{
|
type ToolkitScreenProps = Readonly<{
|
||||||
goHome: () => void;
|
goHome: () => void;
|
||||||
navigate: (routeName: RouteName) => void;
|
navigate: (routeName: RouteName) => void;
|
||||||
routeName: RouteName;
|
routeName: RouteName;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const toolkitDescriptions: Partial<Record<RouteName, string>> = {
|
type ToolkitSectionContent = Readonly<{
|
||||||
toolkit: 'Select a toolkit area to preview game support assets.',
|
futureFields: string[];
|
||||||
toolkitAudio: 'Audio tools and checks will live here.',
|
liableToChange: string;
|
||||||
toolkitCredits: 'Toolkit-specific credit review will live here.',
|
purpose: string;
|
||||||
toolkitLore: 'Lore reference material will live here.',
|
sources: string[];
|
||||||
toolkitOpeningSequence: 'Opening sequence preview controls will live here.',
|
}>;
|
||||||
toolkitResources: 'Production and gameplay resources will live here.',
|
|
||||||
|
const toolkitOverview: ToolkitSectionContent = {
|
||||||
|
futureFields: [
|
||||||
|
'section purpose map',
|
||||||
|
'wiki source references',
|
||||||
|
'future forms',
|
||||||
|
'tables',
|
||||||
|
'previews',
|
||||||
|
],
|
||||||
|
liableToChange:
|
||||||
|
'The Toolkit should follow whichever gameplay loops prove most volatile during early design.',
|
||||||
|
purpose:
|
||||||
|
'Select a toolkit area to inspect the systems most likely to change while the throne room loop stabilizes.',
|
||||||
|
sources: ['Home', 'Gameplay', 'Technical Design Specs'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const toolkitSections: Record<ToolkitRouteName, ToolkitSectionContent> = {
|
||||||
|
toolkitAudio: {
|
||||||
|
futureFields: [
|
||||||
|
'throne room theme',
|
||||||
|
'world map theme',
|
||||||
|
'petitioner stings',
|
||||||
|
'BLB voice direction',
|
||||||
|
'comedy SFX rules',
|
||||||
|
],
|
||||||
|
liableToChange:
|
||||||
|
'Audio identity will shift as petitioner types, faction tone, and readable decision pacing evolve.',
|
||||||
|
purpose:
|
||||||
|
'Preview and catalog sound identity tied to throne room, map, petitioner, BLB voice, and comedy contexts.',
|
||||||
|
sources: ['Music', 'Gameplay', 'References'],
|
||||||
|
},
|
||||||
|
toolkitEndings: {
|
||||||
|
futureFields: [
|
||||||
|
'resource collapse',
|
||||||
|
'stability collapse',
|
||||||
|
'settlement collapse count',
|
||||||
|
'faction coup',
|
||||||
|
'ending summaries',
|
||||||
|
],
|
||||||
|
liableToChange:
|
||||||
|
'Failure thresholds and ending categories will change as the run length and kingdom identity model settle.',
|
||||||
|
purpose:
|
||||||
|
'Tune collapse states and outcome summaries so failure and victory reflect accumulated rulings.',
|
||||||
|
sources: ['Story', 'Technical Design Specs'],
|
||||||
|
},
|
||||||
|
toolkitFactions: {
|
||||||
|
futureFields: [
|
||||||
|
'faction ID',
|
||||||
|
'trust',
|
||||||
|
'power',
|
||||||
|
'agenda',
|
||||||
|
'likes',
|
||||||
|
'dislikes',
|
||||||
|
'coup risks',
|
||||||
|
],
|
||||||
|
liableToChange:
|
||||||
|
'Faction trust and power are expected to move as petitioner pressure and map consequence rules mature.',
|
||||||
|
purpose:
|
||||||
|
'Tune faction trust, power, agenda pressure, and the events attached to faction relationships.',
|
||||||
|
sources: ['Lore', 'Gameplay', 'Technical Design Specs'],
|
||||||
|
},
|
||||||
|
toolkitFlags: {
|
||||||
|
futureFields: [
|
||||||
|
'active flags',
|
||||||
|
'seen petitioners',
|
||||||
|
'rulings',
|
||||||
|
'daily summaries',
|
||||||
|
'unlocked events',
|
||||||
|
'chain triggers',
|
||||||
|
],
|
||||||
|
liableToChange:
|
||||||
|
'Delayed consequences and recurring petitioner chains depend on flags that will expand quickly.',
|
||||||
|
purpose:
|
||||||
|
'Debug delayed consequences, recurring petitioner chains, run history, and unlocked future events.',
|
||||||
|
sources: ['Gameplay', 'Story', 'Technical Design Specs'],
|
||||||
|
},
|
||||||
|
toolkitLore: {
|
||||||
|
futureFields: [
|
||||||
|
'settlements',
|
||||||
|
'factions',
|
||||||
|
'BLB origin variants',
|
||||||
|
'BLB limits',
|
||||||
|
'canon rules',
|
||||||
|
'joke rules',
|
||||||
|
],
|
||||||
|
liableToChange:
|
||||||
|
'Canon needs room to absorb absurd jokes while still preserving consequence and internal logic.',
|
||||||
|
purpose:
|
||||||
|
'Reference changing canon, world concepts, BLB rules, faction needs, and settlement identity.',
|
||||||
|
sources: ['Lore', 'Story', 'References'],
|
||||||
|
},
|
||||||
|
toolkitMap: {
|
||||||
|
futureFields: [
|
||||||
|
'settlements',
|
||||||
|
'owner',
|
||||||
|
'faction influence',
|
||||||
|
'production',
|
||||||
|
'stability',
|
||||||
|
'need',
|
||||||
|
'status effects',
|
||||||
|
],
|
||||||
|
liableToChange:
|
||||||
|
'The map is the consequence layer, so settlement state will change as ruling effects become visible.',
|
||||||
|
purpose:
|
||||||
|
'Support settlement and world-map iteration as throne room choices ripple into territory state.',
|
||||||
|
sources: ['Gameplay', 'Lore', 'Technical Design Specs'],
|
||||||
|
},
|
||||||
|
toolkitOpening: {
|
||||||
|
futureFields: [
|
||||||
|
'opening premise',
|
||||||
|
'first day setup',
|
||||||
|
'initial resources',
|
||||||
|
'first petitioners',
|
||||||
|
'first map action',
|
||||||
|
],
|
||||||
|
liableToChange:
|
||||||
|
'The first-run sequence will change as the tutorial learns how much of the loop to teach up front.',
|
||||||
|
purpose:
|
||||||
|
'Preview and sequence the first-run premise, Act I intro beats, and early tutorial framing.',
|
||||||
|
sources: ['Story', 'Gameplay', 'Technical Design Specs'],
|
||||||
|
},
|
||||||
|
toolkitPetitioners: {
|
||||||
|
futureFields: [
|
||||||
|
'petitioner ID',
|
||||||
|
'name',
|
||||||
|
'type',
|
||||||
|
'faction',
|
||||||
|
'settlement',
|
||||||
|
'tags',
|
||||||
|
'day range',
|
||||||
|
'weight',
|
||||||
|
'flags',
|
||||||
|
],
|
||||||
|
liableToChange:
|
||||||
|
'Petitioners are the primary content loop and will change as cases, factions, and map pressure evolve.',
|
||||||
|
purpose:
|
||||||
|
'Inspect and eventually author throne-room cases, the main variable content loop of each day.',
|
||||||
|
sources: ['Gameplay', 'Story', 'Technical Design Specs'],
|
||||||
|
},
|
||||||
|
toolkitReferences: {
|
||||||
|
futureFields: [
|
||||||
|
'game references',
|
||||||
|
'visual pillars',
|
||||||
|
'tone references',
|
||||||
|
'BLB asset links',
|
||||||
|
'repo links',
|
||||||
|
'build notes',
|
||||||
|
],
|
||||||
|
liableToChange:
|
||||||
|
'Reference material will grow as art, audio, design, and implementation sources become concrete.',
|
||||||
|
purpose:
|
||||||
|
'Keep design inspirations, source links, asset references, and build notes close to implementation.',
|
||||||
|
sources: ['References', 'Home'],
|
||||||
|
},
|
||||||
|
toolkitResources: {
|
||||||
|
futureFields: [
|
||||||
|
'current totals',
|
||||||
|
'daily production',
|
||||||
|
'upkeep',
|
||||||
|
'conversion rules',
|
||||||
|
'thresholds',
|
||||||
|
'warnings',
|
||||||
|
'collapse triggers',
|
||||||
|
],
|
||||||
|
liableToChange:
|
||||||
|
'The Bones, Minions, and Milk triangle is the core economy and will need repeated tuning.',
|
||||||
|
purpose:
|
||||||
|
'Tune the core Bones, Minions, and Milk economy plus production, upkeep, and collapse rules.',
|
||||||
|
sources: ['Gameplay', 'Technical Design Specs'],
|
||||||
|
},
|
||||||
|
toolkitRulings: {
|
||||||
|
futureFields: [
|
||||||
|
'label',
|
||||||
|
'tone',
|
||||||
|
'costs',
|
||||||
|
'resource effects',
|
||||||
|
'settlement effects',
|
||||||
|
'faction effects',
|
||||||
|
'future weights',
|
||||||
|
'result text',
|
||||||
|
],
|
||||||
|
liableToChange:
|
||||||
|
'Ruling effects need to be tuned separately from flavor text as consequence balance changes.',
|
||||||
|
purpose:
|
||||||
|
'Inspect and eventually tune choice outcomes independently from petitioner presentation.',
|
||||||
|
sources: ['Gameplay', 'Technical Design Specs'],
|
||||||
|
},
|
||||||
|
toolkitSeed: {
|
||||||
|
futureFields: [
|
||||||
|
'eligible pool',
|
||||||
|
'filters',
|
||||||
|
'weights',
|
||||||
|
'shortage modifiers',
|
||||||
|
'trust modifiers',
|
||||||
|
'selected IDs',
|
||||||
|
'day history',
|
||||||
|
],
|
||||||
|
liableToChange:
|
||||||
|
'Daily selection rules will change as shortages, unstable settlements, and faction pressure matter more.',
|
||||||
|
purpose:
|
||||||
|
'Debug why a day produced specific petitioners from deterministic seed and world-state weighting.',
|
||||||
|
sources: ['Gameplay', 'Technical Design Specs'],
|
||||||
|
},
|
||||||
|
toolkitStory: {
|
||||||
|
futureFields: [
|
||||||
|
'act definitions',
|
||||||
|
'recurring petitioner arcs',
|
||||||
|
'campaign pressure',
|
||||||
|
'kingdom signals',
|
||||||
|
'endgame hooks',
|
||||||
|
],
|
||||||
|
liableToChange:
|
||||||
|
'Story pressure should remain sandbox-first, so arcs will shift around emergent kingdom state.',
|
||||||
|
purpose:
|
||||||
|
'Organize Act I-IV pressure, recurring petitioner arcs, and long-term kingdom identity.',
|
||||||
|
sources: ['Story', 'Lore', 'Gameplay'],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const toolkitIcons: Record<ToolkitRouteName, string> = {
|
const toolkitIcons: Record<ToolkitRouteName, string> = {
|
||||||
toolkitAudio: '♪',
|
toolkitAudio: '♪',
|
||||||
toolkitCredits: '★',
|
toolkitEndings: '!',
|
||||||
|
toolkitFactions: '◇',
|
||||||
|
toolkitFlags: '#',
|
||||||
toolkitLore: '✦',
|
toolkitLore: '✦',
|
||||||
toolkitOpeningSequence: '▶',
|
toolkitMap: '⌖',
|
||||||
|
toolkitOpening: '▶',
|
||||||
|
toolkitPetitioners: '◉',
|
||||||
|
toolkitReferences: '※',
|
||||||
toolkitResources: '▦',
|
toolkitResources: '▦',
|
||||||
|
toolkitRulings: '§',
|
||||||
|
toolkitSeed: '◎',
|
||||||
|
toolkitStory: '★',
|
||||||
};
|
};
|
||||||
|
|
||||||
const socketStatusCopy: Record<ToolkitSocketStatus, string> = {
|
const socketStatusCopy: Record<ToolkitSocketStatus, string> = {
|
||||||
@@ -47,6 +270,7 @@ export function ToolkitScreen({
|
|||||||
const safeAreaInsets = useSafeAreaInsets();
|
const safeAreaInsets = useSafeAreaInsets();
|
||||||
const activeRoute = routeDefinitions[routeName];
|
const activeRoute = routeDefinitions[routeName];
|
||||||
const socketStatus = useToolkitSocketStore(state => state.status);
|
const socketStatus = useToolkitSocketStore(state => state.status);
|
||||||
|
const toolkitContent = getToolkitContent(routeName);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
@@ -87,15 +311,72 @@ export function ToolkitScreen({
|
|||||||
<View style={styles.contentPanel}>
|
<View style={styles.contentPanel}>
|
||||||
<Text style={styles.eyebrow}>Bone Lord Bob</Text>
|
<Text style={styles.eyebrow}>Bone Lord Bob</Text>
|
||||||
<Text style={styles.title}>{activeRoute.title}</Text>
|
<Text style={styles.title}>{activeRoute.title}</Text>
|
||||||
<Text style={styles.description}>
|
<Text style={styles.readOnlyLabel}>Read-only planning panel</Text>
|
||||||
{toolkitDescriptions[routeName] ?? toolkitDescriptions.toolkit}
|
<View style={styles.detailGrid}>
|
||||||
</Text>
|
<ToolkitDetail
|
||||||
|
label="Purpose"
|
||||||
|
testID="toolkit-purpose"
|
||||||
|
value={toolkitContent.purpose}
|
||||||
|
/>
|
||||||
|
<ToolkitDetail
|
||||||
|
label="Liable to change because"
|
||||||
|
testID="toolkit-change-note"
|
||||||
|
value={toolkitContent.liableToChange}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.sourcePanel} testID="toolkit-sources">
|
||||||
|
<Text style={styles.detailLabel}>Source wiki pages</Text>
|
||||||
|
<View style={styles.sourceRow}>
|
||||||
|
{toolkitContent.sources.map(source => (
|
||||||
|
<Text key={source} style={styles.sourcePill}>
|
||||||
|
{source}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<ToolkitDetail
|
||||||
|
label="Anticipated future fields / entities"
|
||||||
|
testID="toolkit-future-fields"
|
||||||
|
value={toolkitContent.futureFields.join(', ')}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={styles.placeholderPanel}
|
||||||
|
testID="toolkit-future-placeholder">
|
||||||
|
<Text style={styles.placeholderTitle}>Future authoring area</Text>
|
||||||
|
<Text style={styles.placeholderCopy}>
|
||||||
|
Forms, tables, previews, and schema-backed validation will mount
|
||||||
|
here after the JSON and Markdown content formats stabilize.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
<ToolkitSocketIndicator status={socketStatus} />
|
<ToolkitSocketIndicator status={socketStatus} />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getToolkitContent(routeName: RouteName) {
|
||||||
|
if ((toolkitRoutes as readonly RouteName[]).includes(routeName)) {
|
||||||
|
return toolkitSections[routeName as ToolkitRouteName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return toolkitOverview;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToolkitDetailProps = Readonly<{
|
||||||
|
label: string;
|
||||||
|
testID: string;
|
||||||
|
value: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
function ToolkitDetail({ label, testID, value }: ToolkitDetailProps) {
|
||||||
|
return (
|
||||||
|
<View style={styles.detailPanel} testID={testID}>
|
||||||
|
<Text style={styles.detailLabel}>{label}</Text>
|
||||||
|
<Text style={styles.detailValue}>{value}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type ToolkitSocketIndicatorProps = Readonly<{
|
type ToolkitSocketIndicatorProps = Readonly<{
|
||||||
status: ToolkitSocketStatus;
|
status: ToolkitSocketStatus;
|
||||||
}>;
|
}>;
|
||||||
@@ -187,11 +468,31 @@ const styles = StyleSheet.create({
|
|||||||
padding: 14,
|
padding: 14,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
},
|
},
|
||||||
description: {
|
detailGrid: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 10,
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
detailLabel: {
|
||||||
|
color: '#d6b25e',
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: '800',
|
||||||
|
letterSpacing: 0,
|
||||||
|
marginBottom: 5,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
},
|
||||||
|
detailPanel: {
|
||||||
|
backgroundColor: '#131518',
|
||||||
|
borderColor: '#2d2f32',
|
||||||
|
borderRadius: 6,
|
||||||
|
borderWidth: 1,
|
||||||
|
flex: 1,
|
||||||
|
padding: 10,
|
||||||
|
},
|
||||||
|
detailValue: {
|
||||||
color: '#c9c1b3',
|
color: '#c9c1b3',
|
||||||
fontSize: 14,
|
fontSize: 12,
|
||||||
lineHeight: 20,
|
lineHeight: 17,
|
||||||
maxWidth: 420,
|
|
||||||
},
|
},
|
||||||
eyebrow: {
|
eyebrow: {
|
||||||
color: '#d6b25e',
|
color: '#d6b25e',
|
||||||
@@ -209,6 +510,60 @@ const styles = StyleSheet.create({
|
|||||||
lineHeight: 34,
|
lineHeight: 34,
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
},
|
},
|
||||||
|
placeholderCopy: {
|
||||||
|
color: '#c9c1b3',
|
||||||
|
fontSize: 12,
|
||||||
|
lineHeight: 17,
|
||||||
|
},
|
||||||
|
placeholderPanel: {
|
||||||
|
backgroundColor: '#24272b',
|
||||||
|
borderColor: '#4d4432',
|
||||||
|
borderRadius: 6,
|
||||||
|
borderStyle: 'dashed',
|
||||||
|
borderWidth: 1,
|
||||||
|
marginTop: 10,
|
||||||
|
padding: 10,
|
||||||
|
},
|
||||||
|
placeholderTitle: {
|
||||||
|
color: '#f6f1e7',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '800',
|
||||||
|
letterSpacing: 0,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
readOnlyLabel: {
|
||||||
|
color: '#80796c',
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: '800',
|
||||||
|
letterSpacing: 0,
|
||||||
|
marginBottom: 10,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
},
|
||||||
|
sourcePanel: {
|
||||||
|
backgroundColor: '#131518',
|
||||||
|
borderColor: '#2d2f32',
|
||||||
|
borderRadius: 6,
|
||||||
|
borderWidth: 1,
|
||||||
|
marginBottom: 10,
|
||||||
|
padding: 10,
|
||||||
|
},
|
||||||
|
sourcePill: {
|
||||||
|
backgroundColor: '#2a261d',
|
||||||
|
borderColor: '#4d4432',
|
||||||
|
borderRadius: 4,
|
||||||
|
borderWidth: 1,
|
||||||
|
color: '#f5d98d',
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: '800',
|
||||||
|
letterSpacing: 0,
|
||||||
|
paddingHorizontal: 7,
|
||||||
|
paddingVertical: 4,
|
||||||
|
},
|
||||||
|
sourceRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 6,
|
||||||
|
},
|
||||||
socketDot: {
|
socketDot: {
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
height: 10,
|
height: 10,
|
||||||
@@ -245,7 +600,7 @@ const styles = StyleSheet.create({
|
|||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
minHeight: 34,
|
minHeight: 34,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 8,
|
||||||
paddingVertical: 6,
|
paddingVertical: 6,
|
||||||
},
|
},
|
||||||
toolbarButtonActive: {
|
toolbarButtonActive: {
|
||||||
@@ -255,7 +610,7 @@ const styles = StyleSheet.create({
|
|||||||
toolbarButtonContent: {
|
toolbarButtonContent: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
gap: 5,
|
gap: 4,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
toolbarButtonIcon: {
|
toolbarButtonIcon: {
|
||||||
@@ -272,7 +627,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
toolbarButtonLabel: {
|
toolbarButtonLabel: {
|
||||||
color: '#f6f1e7',
|
color: '#f6f1e7',
|
||||||
fontSize: 12,
|
fontSize: 10,
|
||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
letterSpacing: 0,
|
letterSpacing: 0,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
@@ -285,6 +640,8 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
toolbarGroup: {
|
toolbarGroup: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
flexShrink: 1,
|
||||||
|
flexWrap: 'wrap',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user