11import { Canvas } from "@react-three/fiber" ;
2- import type { CodeGraph } from "@repo/code-graph" ;
32import { PointerLook } from "@repo/renderer/controls" ;
43import { Player } from "@repo/renderer/player" ;
54import { World } from "@repo/renderer/world" ;
6- import type { GeneratedWorld } from "@repo/types" ;
7- import { generateWorld } from "@repo/world-generator" ;
8- import { mergeAreas } from "@repo/world-generator/areas" ;
95import type { Target } from "@repo/world-generator/interaction" ;
10- import { presets } from "@repo/world-generator/presets" ;
116import { useCallback , useEffect , useMemo , useState } from "react" ;
127
138import { Hud } from "~/hud" ;
@@ -17,31 +12,31 @@ import { initialSeed, nextSeed, parseWorldParams, withSeed } from "~/params";
1712import { createPoseFeed } from "~/pose-feed" ;
1813import { SourcePanel } from "~/source-panel" ;
1914import { sourceView } from "~/source-view" ;
15+ import { useAreaWorld } from "~/use-area-world" ;
16+ import type { WorldOptions } from "~/use-area-world" ;
2017import { useNavigation } from "~/use-navigation" ;
2118import {
22- areasFromCode ,
2319 breadcrumbOf ,
2420 codeGraphOf ,
2521 describeRoom ,
2622 promptOf ,
2723 sourceSeedOf ,
2824} from "~/world-from-code" ;
2925
30- type Generated = {
31- readonly world : GeneratedWorld | null ;
32- /** Modules of a code world, each its own area; one for other worlds. */
33- readonly modules : number ;
34- readonly codeGraph : CodeGraph | null ;
35- /** Graph connections and portals the layout could not realise, one line each. */
36- readonly warnings : readonly string [ ] ;
37- readonly error : string | null ;
38- } ;
39-
4026const initial = parseWorldParams ( globalThis . location . search ) ;
4127
4228// Parsed once: a syntax error in a bundled example does not depend on the
4329// seed, and it is shown in the HUD rather than thrown through the renderer.
4430const code = initial . code === null ? null : codeGraphOf ( initial . code ) ;
31+ const codeGraph = code ?. codeGraph ?? null ;
32+
33+ const worldOptions : WorldOptions = {
34+ codeGraph,
35+ codeError : code ?. error ?? null ,
36+ eager : initial . eager ,
37+ rooms : initial . rooms ,
38+ preset : initial . preset ,
39+ } ;
4540
4641// A code world without `?seed` takes its source's own seed, so the same
4742// program reloads into the same world; `?seed` and N override it.
@@ -53,62 +48,6 @@ const startSeed = initialSeed(
5348// Fed from the render loop and read only by the open map, never by the app.
5449const feed = createPoseFeed ( ) ;
5550
56- const generate = (
57- seed : number
58- ) : { readonly world : GeneratedWorld ; readonly modules : number } => {
59- const codeGraph = code ?. codeGraph ?? null ;
60- if ( codeGraph !== null ) {
61- const areas = areasFromCode ( codeGraph , seed ) ;
62- // The entrance is an area but not a module.
63- return { world : mergeAreas ( areas ) , modules : codeGraph . modules . length } ;
64- }
65- const world = generateWorld ( {
66- seed,
67- roomCount : initial . rooms ,
68- graph : initial . preset === null ? undefined : presets [ initial . preset ] ,
69- } ) ;
70- return { world, modules : 1 } ;
71- } ;
72-
73- /**
74- * Builds the world once per seed: it never regenerates while the player moves.
75- * Generation errors (an unplaceable graph) are shown, not thrown.
76- */
77- const useGeneratedWorld = ( seed : number ) : Generated =>
78- useMemo ( ( ) => {
79- const codeGraph = code ?. codeGraph ?? null ;
80- if ( code !== null && code . error !== null ) {
81- return {
82- world : null ,
83- modules : 0 ,
84- codeGraph,
85- warnings : [ ] ,
86- error : code . error ,
87- } ;
88- }
89- try {
90- const { world, modules } = generate ( seed ) ;
91- // Shown in the HUD, so the console stays quiet.
92- const warnings = [
93- ...world . layout . unresolved . map (
94- ( { from, to } ) => `${ from } -> ${ to } could not be laid out`
95- ) ,
96- ...world . layout . unplacedPortals . map (
97- ( { id } ) => `${ id } has no wall space`
98- ) ,
99- ] ;
100- return { world, modules, codeGraph, warnings, error : null } ;
101- } catch ( error ) {
102- return {
103- world : null ,
104- modules : 0 ,
105- codeGraph,
106- warnings : [ ] ,
107- error : String ( error ) ,
108- } ;
109- }
110- } , [ seed ] ) ;
111-
11251const App = ( ) => {
11352 const [ locked , setLocked ] = useState ( false ) ;
11453 const [ seed , setSeed ] = useState ( startSeed ) ;
@@ -117,20 +56,42 @@ const App = () => {
11756 const [ sourceOpen , setSourceOpen ] = useState ( false ) ;
11857 const [ mapOpen , setMapOpen ] = useState ( false ) ;
11958 const [ visits , setVisits ] = useState ( EMPTY_VISITS ) ;
120- const generated = useGeneratedWorld ( seed ) ;
59+ // Generated once per seed, then grown an area at a time: it never
60+ // regenerates while the player moves.
61+ const generated = useAreaWorld ( seed , worldOptions ) ;
62+ const { ensurePortal } = generated ;
12163 const built = generated . world ?. built ?? null ;
122- const navigation = useNavigation ( generated . world ) ;
123- const { onRoomChange, back, home } = navigation ;
64+ const navigation = useNavigation ( seed , generated . current ) ;
65+ const { onRoomChange, back, home, onPortal : stepPortal } = navigation ;
12466
12567 const onRoom = useCallback (
12668 ( id : string | null ) => {
12769 setRoomId ( id ) ;
128- setVisits ( ( previous ) => visit ( previous , built , id ) ) ;
70+ setVisits ( ( previous ) => visit ( previous , seed , id ) ) ;
12971 onRoomChange ( id ) ;
13072 } ,
131- [ built , onRoomChange ]
73+ [ seed , onRoomChange ]
74+ ) ;
75+
76+ // A portal entered before its far side was generated: generate it first,
77+ // so the step lands there.
78+ const onPortal = useCallback (
79+ ( portalId : string ) => {
80+ ensurePortal ( portalId ) ;
81+ stepPortal ( portalId ) ;
82+ } ,
83+ [ ensurePortal , stepPortal ]
13284 ) ;
13385
86+ // The portal in front of the player leads into an area not generated yet:
87+ // generate it now, before the player steps in. Also run when the world
88+ // changes, as a target kept across a new seed reports no change.
89+ useEffect ( ( ) => {
90+ if ( target ?. kind === "portal" ) {
91+ ensurePortal ( target . portalId ) ;
92+ }
93+ } , [ target , ensurePortal , generated . world ] ) ;
94+
13495 // N: next seed (the only thing that regenerates the world). Backspace and
13596 // R navigate, E shows the source and M or Tab the map, only while walking,
13697 // so a free pointer leaves them alone.
@@ -188,16 +149,14 @@ const App = () => {
188149 }
189150 } , [ generated . error ] ) ;
190151
191- const place =
192- roomId === null ? null : describeRoom ( generated . codeGraph , roomId ) ;
152+ const place = roomId === null ? null : describeRoom ( codeGraph , roomId ) ;
193153 const sources = code ?. error === null ? code . sources : null ;
194154 const source = useMemo (
195- ( ) =>
196- sources === null
197- ? null
198- : sourceView ( generated . codeGraph , sources , roomId ) ,
199- [ generated . codeGraph , sources , roomId ]
155+ ( ) => ( sources === null ? null : sourceView ( codeGraph , sources , roomId ) ) ,
156+ [ sources , roomId ]
200157 ) ;
158+ const failed =
159+ target ?. kind === "portal" ? generated . failedArea ( target . portalId ) : null ;
201160
202161 return (
203162 < div className = "relative h-dvh w-screen overflow-hidden bg-black" >
@@ -220,7 +179,7 @@ const App = () => {
220179 enabled = { locked }
221180 onRoomChange = { onRoom }
222181 placement = { navigation . placement }
223- onPortal = { navigation . onPortal }
182+ onPortal = { onPortal }
224183 onNearTarget = { setTarget }
225184 onMove = { feed . publish }
226185 />
@@ -230,7 +189,7 @@ const App = () => {
230189 { locked && mapOpen && built !== null && (
231190 < OverviewMap
232191 world = { built }
233- visited = { visitedIn ( visits , built ) }
192+ visited = { visitedIn ( visits , seed ) }
234193 current = { roomId }
235194 feed = { feed }
236195 />
@@ -243,15 +202,20 @@ const App = () => {
243202 seed = { seed }
244203 rooms = { generated . world ?. graph . rooms . length ?? 0 }
245204 modules = { generated . modules }
205+ lastMs = { generated . lastMs }
246206 preset = { initial . preset }
247207 code = { initial . code }
248208 place = { place }
249209 breadcrumb = { breadcrumbOf (
250- generated . codeGraph ,
210+ codeGraph ,
251211 navigation . frames ,
252212 navigation . roomId
253213 ) }
254- prompt = { promptOf ( generated . codeGraph , navigation . frames , target ) }
214+ prompt = {
215+ failed === null
216+ ? promptOf ( codeGraph , navigation . frames , target )
217+ : `closed · ${ failed } could not be laid out`
218+ }
255219 warnings = { generated . warnings }
256220 error = { generated . error }
257221 />
0 commit comments