File size: 27,317 Bytes
5a10444
 
 
 
 
37f9b86
5a10444
 
 
 
 
 
 
 
 
 
37f9b86
5a10444
 
37f9b86
5a10444
37f9b86
5a10444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37f9b86
5a10444
37f9b86
 
5a10444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37f9b86
5a10444
37f9b86
 
 
 
 
5a10444
 
 
 
 
 
 
 
 
 
 
 
37f9b86
 
5a10444
 
 
 
 
37f9b86
5a10444
 
 
 
 
 
 
 
37f9b86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5a10444
 
 
37f9b86
5a10444
 
 
 
 
 
 
37f9b86
 
 
5a10444
 
 
37f9b86
5a10444
37f9b86
5a10444
 
37f9b86
5a10444
 
 
37f9b86
5a10444
 
 
 
 
 
37f9b86
5a10444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37f9b86
 
5a10444
37f9b86
5a10444
37f9b86
 
 
5a10444
 
 
 
 
 
 
 
 
 
 
 
37f9b86
5a10444
 
 
 
 
 
 
37f9b86
5a10444
 
 
37f9b86
5a10444
 
 
 
37f9b86
5a10444
 
 
37f9b86
 
 
 
 
 
5a10444
37f9b86
 
 
 
 
 
5a10444
 
37f9b86
5a10444
37f9b86
 
 
 
 
5a10444
 
 
 
 
 
 
 
 
 
37f9b86
 
5a10444
 
 
37f9b86
5a10444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37f9b86
5a10444
 
 
 
37f9b86
5a10444
 
 
 
 
 
 
37f9b86
5a10444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37f9b86
5a10444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37f9b86
5a10444
 
 
37f9b86
 
 
 
 
5a10444
 
 
37f9b86
5a10444
 
 
 
 
 
 
 
 
37f9b86
5a10444
 
 
 
 
 
37f9b86
5a10444
37f9b86
 
 
5a10444
 
 
37f9b86
5a10444
 
 
 
 
 
 
37f9b86
5a10444
 
 
 
 
 
 
 
 
37f9b86
5a10444
 
37f9b86
5a10444
 
 
 
 
 
 
 
37f9b86
5a10444
37f9b86
5a10444
 
 
37f9b86
5a10444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37f9b86
5a10444
37f9b86
5a10444
 
 
37f9b86
5a10444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37f9b86
5a10444
 
37f9b86
5a10444
 
37f9b86
5a10444
 
 
37f9b86
 
5a10444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>World Grid Test (Animation Fix)</title>
    <style>
        body { font-family: 'Courier New', monospace; background-color: #111; color: #eee; margin: 0; padding: 0; overflow: hidden; display: flex; flex-direction: column; height: 100vh; }
        #game-container { display: flex; flex-grow: 1; overflow: hidden; }
        #scene-container { flex-grow: 3; position: relative; border-right: 2px solid #444; min-width: 250px; background-color: #000; height: 100%; box-sizing: border-box; overflow: hidden; cursor: default; }
        #ui-container { flex-grow: 2; padding: 25px; overflow-y: auto; background-color: #2b2b2b; min-width: 320px; height: 100%; box-sizing: border-box; display: flex; flex-direction: column; }
        #scene-container canvas { display: block; }
        #story-title { color: #f0c060; margin: 0 0 15px 0; padding-bottom: 10px; border-bottom: 1px solid #555; font-size: 1.6em; text-shadow: 1px 1px 1px #000; }
        #story-content { margin-bottom: 25px; line-height: 1.7; flex-grow: 1; font-size: 1.1em; }
        #stats-inventory-container { margin-bottom: 25px; padding: 15px; border: 1px solid #444; border-radius: 4px; background-color: #333; font-size: 0.95em; }
        #stats-display, #inventory-display { margin-bottom: 10px; line-height: 1.8; }
        #stats-display span, #inventory-display .item-tag { display: inline-block; background-color: #484848; padding: 3px 9px; border-radius: 15px; margin: 0 8px 5px 0; border: 1px solid #6a6a6a; white-space: nowrap; box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); }
        #stats-display strong, #inventory-display strong { color: #ccc; margin-right: 6px; }
        #inventory-display em { color: #888; font-style: normal; }
        .item-quest { background-color: #666030; border-color: #999048;}
        .item-weapon { background-color: #663030; border-color: #994848;}
        .item-armor { background-color: #306630; border-color: #489948;}
        .item-consumable { background-color: #664430; border-color: #996648;}
        .item-unknown { background-color: #555; border-color: #777;}
        #choices-container { margin-top: auto; padding-top: 20px; border-top: 1px solid #555; }
        #choices-container h3 { margin-top: 0; margin-bottom: 12px; color: #ccc; font-size: 1.1em; }
        #choices { display: flex; flex-direction: column; gap: 12px; }
        .choice-button { display: block; width: 100%; padding: 12px 15px; margin-bottom: 0; background-color: #555; color: #eee; border: 1px solid #777; border-radius: 4px; cursor: pointer; text-align: left; font-family: 'Courier New', monospace; font-size: 1.05em; transition: background-color 0.2s, border-color 0.2s, box-shadow 0.1s; box-sizing: border-box; }
        .choice-button:hover:not(:disabled) { background-color: #e0b050; color: #111; border-color: #c89040; box-shadow: 0 0 5px rgba(255, 200, 100, 0.5); }
        .choice-button:disabled { background-color: #404040; color: #777; cursor: not-allowed; border-color: #555; opacity: 0.7; }
        .message { padding: 8px 12px; margin-bottom: 1em; border-left-width: 3px; border-left-style: solid; font-size: 0.95em; background-color: rgba(255, 255, 255, 0.05); border-color: #666; color: #aaa;}
        .message-failure { color: #f88; border-left-color: #a44; }
        #action-info { position: absolute; bottom: 10px; left: 10px; background-color: rgba(0,0,0,0.7); color: #ffcc66; padding: 5px 10px; border-radius: 3px; font-size: 0.9em; display: block; z-index: 10;}
    </style>
</head>
<body>
    <div id="game-container">
        <div id="scene-container">
             <div id="action-info">Initializing...</div>
        </div>
        <div id="ui-container">
            <h2 id="story-title">Initializing...</h2>
            <div id="story-content"><p>Loading assets...</p></div>
            <div id="stats-inventory-container">
                <div id="stats-display">HP: ?/? | XP: ?</div>
                <div id="inventory-display">Inventory: Empty</div>
            </div>
            <div id="choices-container">
                <h3>What will you do?</h3>
                <div id="choices">Loading...</div>
            </div>
        </div>
    </div>

    <script type="importmap">
        { "imports": {
                "three": "https://unpkg.com/[email protected]/build/three.module.js",
                "three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
        }}
    </script>

    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

        console.log("Script module execution started.");

        const sceneContainer = document.getElementById('scene-container');
        const storyTitleElement = document.getElementById('story-title');
        const storyContentElement = document.getElementById('story-content');
        const choicesElement = document.getElementById('choices');
        const statsElement = document.getElementById('stats-display');
        const inventoryElement = document.getElementById('inventory-display');
        const actionInfoElement = document.getElementById('action-info');

        console.log("DOM elements obtained.");

        let scene, camera, renderer, clock, controls;
        let worldGroup = null;
        let zoneGroups = {};
        let currentMessage = "";
        let currentLights = [];

        const MAT = {
            stone: new THREE.MeshStandardMaterial({ color: 0x777788, roughness: 0.85 }),
            wood: new THREE.MeshStandardMaterial({ color: 0x9F6633, roughness: 0.75 }),
            leaf: new THREE.MeshStandardMaterial({ color: 0x3E9B4E, roughness: 0.6, side: THREE.DoubleSide }),
            ground: new THREE.MeshStandardMaterial({ color: 0x556B2F, roughness: 0.95 }),
            dirt: new THREE.MeshStandardMaterial({ color: 0x8B5E3C, roughness: 0.9 }),
            grass: new THREE.MeshStandardMaterial({ color: 0x4CB781, roughness: 0.85 }),
            water: new THREE.MeshStandardMaterial({ color: 0x4682B4, roughness: 0.3, transparent: true, opacity: 0.85 }),
            simple: new THREE.MeshStandardMaterial({ color: 0xaaaaaa, roughness: 0.8 }),
        };

        let gameState = {};

        const itemsData = {
            "Rusty Sword": {type:"weapon", description:"Old but sharp.", baseDamage: 3},
            "Health Potion": {type:"consumable", description:"Restores 10 HP.", effect: { hpGain: 10 }},
            "Goblin Ear": {type:"quest", description:"A gruesome trophy."},
            "Cave Crystal": {type:"unknown", description:"A faintly glowing crystal shard."},
            "Rock": {type:"unknown", description:"A simple rock."}
        };

        const zoneCreators = {};
        const MAP_ROWS = 3;
        const MAP_COLS = 4;

        function initThreeJS() {
            console.log("initThreeJS started.");
            try {
                scene = new THREE.Scene();
                scene.background = new THREE.Color(0x1a1a1a);
                clock = new THREE.Clock();
                worldGroup = new THREE.Group();
                scene.add(worldGroup);
                console.log("Scene and worldGroup created.");

                const width = sceneContainer.clientWidth || 300;
                const height = sceneContainer.clientHeight || 200;
                console.log(`Renderer dimensions: ${width}x${height}`);

                camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000);
                camera.position.set(0, 6, 12);
                camera.lookAt(0, 1, 0);
                console.log("Camera created.");

                renderer = new THREE.WebGLRenderer({ antialias: true });
                renderer.setSize(width, height);
                renderer.shadowMap.enabled = true;
                renderer.shadowMap.type = THREE.PCFSoftShadowMap;
                sceneContainer.appendChild(renderer.domElement);
                console.log("Renderer created and appended.");

                controls = new OrbitControls(camera, renderer.domElement);
                controls.enableDamping = true;
                controls.dampingFactor = 0.1;
                controls.target.set(0, 1, 0);
                controls.maxPolarAngle = Math.PI / 2 - 0.05;
                controls.minDistance = 3;
                controls.maxDistance = 50;
                 console.log("OrbitControls initialized.");

                window.addEventListener('resize', onWindowResize, false);
                setTimeout(onWindowResize, 100);
                animate();
                console.log("initThreeJS finished successfully.");
            } catch (error) {
                 console.error("Error during initThreeJS:", error);
                 throw error;
            }
        }

        function onWindowResize() {
             console.log("onWindowResize called.");
             if (!renderer || !camera || !sceneContainer) return;
             const width = sceneContainer.clientWidth || 300;
             const height = sceneContainer.clientHeight || 200;
             if (width > 0 && height > 0) {
                 camera.aspect = width / height;
                 camera.updateProjectionMatrix();
                 renderer.setSize(width, height);
                 console.log(`Resized renderer to ${width}x${height}`);
             } else {
                 console.warn("Skipping resize, zero dimensions detected.");
             }
        }

        let frameCount = 0;
        function animate() {
            frameCount++;
            requestAnimationFrame(animate);
            controls.update();

            // Animate objects within the *currently visible* zone group
            if (gameState.currentZoneId && zoneGroups[gameState.currentZoneId]) {
                 zoneGroups[gameState.currentZoneId].group.traverse(obj => {
                     if (obj.userData.update) obj.userData.update(clock.elapsedTime, clock.getDelta()); // Pass time & delta
                 });
            }

            if (renderer && scene && camera) renderer.render(scene, camera);
        }


        function createMesh(geometry, material, pos = {x:0,y:0,z:0}, rot = {x:0,y:0,z:0}, scale = {x:1,y:1,z:1}) {
             const mesh = new THREE.Mesh(geometry, material);
             mesh.position.set(pos.x, pos.y, pos.z);
             mesh.rotation.set(rot.x, rot.y, rot.z);
             mesh.scale.set(scale.x, scale.y, scale.z);
             mesh.castShadow = true; mesh.receiveShadow = true;
             return mesh;
         }

        function createGround(material = MAT.ground, size = 20) {
             const geo = new THREE.PlaneGeometry(size, size);
             const ground = new THREE.Mesh(geo, material);
             ground.rotation.x = -Math.PI / 2; ground.position.y = 0;
             ground.receiveShadow = true; ground.castShadow = false;
             ground.userData.isGround = true;
             return ground;
        }

        function setupLighting(type = 'default') {
             console.log(`Setting up lighting for type: ${type}`);
             currentLights.forEach(light => {
                 if (light && light.parent) light.parent.remove(light);
                 if (light && scene.children.includes(light)) scene.remove(light);
             });
             currentLights = [];

             let ambientIntensity = 0.6;
             let dirIntensity = 1.0;
             let dirColor = 0xffffff;
             let dirPosition = new THREE.Vector3(5, 10, 7);

             if (type === 'forest') { ambientIntensity = 0.4; dirIntensity = 0.8; dirColor = 0xccffcc; dirPosition = new THREE.Vector3(5, 10, 5); }
             if (type === 'cave') { ambientIntensity = 0.2; dirIntensity = 0; }
             if (type === 'ruins') { ambientIntensity = 0.4; dirIntensity = 0.7; dirColor = 0xaaaaff; dirPosition = new THREE.Vector3(-8, 12, -5); }
             if (type === 'town') { ambientIntensity = 0.5; dirIntensity = 1.0; dirColor = 0xffeedd; dirPosition = new THREE.Vector3(12, 18, 10); }

             const ambientLight = new THREE.AmbientLight(0xffffff, ambientIntensity);
             scene.add(ambientLight);
             currentLights.push(ambientLight);

             if (dirIntensity > 0) {
                 const directionalLight = new THREE.DirectionalLight(dirColor, dirIntensity);
                 directionalLight.position.copy(dirPosition);
                 directionalLight.castShadow = true;
                 directionalLight.shadow.mapSize.set(1024, 1024);
                 directionalLight.shadow.camera.near = 0.5; directionalLight.shadow.camera.far = 50;
                 const sb = 25;
                 directionalLight.shadow.camera.left = -sb; directionalLight.shadow.camera.right = sb;
                 directionalLight.shadow.camera.top = sb; directionalLight.shadow.camera.bottom = -sb;
                 directionalLight.shadow.bias = -0.0005;
                 scene.add(directionalLight);
                 currentLights.push(directionalLight);
             }
              if (type === 'cave') {
                 const ptLight = new THREE.PointLight(0xffaa55, 1.5, 15, 1.5);
                 ptLight.position.set(0, 3, 0);
                 ptLight.castShadow = true;
                 ptLight.shadow.mapSize.set(512, 512);
                 currentLights.push(ptLight);
             }
             console.log(`Lighting setup complete. Lights tracked: ${currentLights.length}`);
        }

        // --- Zone Creation Functions ---
        function createFieldZone(zoneId) {
            console.log(`Creating zone: ${zoneId} (Field)`);
            const group = new THREE.Group();
            group.add(createGround(MAT.grass, 30));
            const rockGeo = new THREE.IcosahedronGeometry(0.8, 0); // Simpler rock geometry
            for(let i=0; i<8; i++) { // Loop index 'i' is available here
                 const rock = createMesh(rockGeo, MAT.stone, {x: (Math.random()-0.5)*25, y:0.4, z: (Math.random()-0.5)*25});
                 rock.rotation.set(Math.random()*0.5, Math.random() * Math.PI * 2, Math.random()*0.5); // Random rotation
                 rock.userData = { isPickupable: false, itemName: "Rock", description: "A field rock." }; // Initially not pickupable
                 group.add(rock);
                 // Add animation using index 'i' for offset
                 rock.userData.startY = rock.position.y;
                 rock.userData.update = (time) => {
                     // Use loop index 'i' for phase offset - THIS IS THE FIX
                     rock.position.y = rock.userData.startY + Math.sin(time * 1.5 + i * 0.5) * 0.1;
                 };
            }
            group.visible = false;
            return { group, lighting: 'default', title: "Open Field", entryText: `You are in ${zoneId}. It's grassy. Boulders bob gently.`, options: [], zoneId: zoneId };
        }
         // Add other create...Zone functions here (Forest, Cave, Ruins, Town etc.) - using the corrected animation logic if needed.
         function createForestZone(zoneId) { return createFieldZone(zoneId); } // Placeholder uses Field for now
         function createCaveZone(zoneId) { return createFieldZone(zoneId); }   // Placeholder uses Field for now
         function createRuinsZone(zoneId) { return createFieldZone(zoneId); }  // Placeholder uses Field for now
         function createTownZone(zoneId) { return createFieldZone(zoneId); }   // Placeholder uses Field for now


        function getZoneId(row, col) { return `zone_${row}_${col}`; }

        function populateZoneCreators() {
            console.log("Populating zone creators...");
            for (let r = 0; r < MAP_ROWS; r++) {
                for (let c = 0; c < MAP_COLS; c++) {
                    const zoneId = getZoneId(r, c);
                    let creatorFunc;
                    // Force FieldZone for all to ensure stability first
                    creatorFunc = createFieldZone;
                    zoneCreators[zoneId] = () => creatorFunc(zoneId);
                }
            }
             console.log(`Zone creators populated with ${Object.keys(zoneCreators).length} entries (all FieldZone for debug).`);
        }

        function getZoneNeighbors(zoneId) {
            const parts = zoneId.split('_');
            if (parts.length !== 3) return {};
            const r = parseInt(parts[1]);
            const c = parseInt(parts[2]);
            const neighbors = {};
            if (r > 0 && zoneCreators[getZoneId(r - 1, c)]) neighbors.north = getZoneId(r - 1, c);
            if (r < MAP_ROWS - 1 && zoneCreators[getZoneId(r + 1, c)]) neighbors.south = getZoneId(r + 1, c);
            if (c > 0 && zoneCreators[getZoneId(r, c - 1)]) neighbors.west = getZoneId(r, c - 1);
            if (c < MAP_COLS - 1 && zoneCreators[getZoneId(r, c + 1)]) neighbors.east = getZoneId(r, c + 1);
            return neighbors;
        }

        function startGame() {
            console.log("startGame called.");
            const defaultChar = {
                name: "Player",
                stats: { hp: 20, maxHp: 20, xp: 0 },
                inventory: []
            };
            gameState = {
                currentZoneId: null,
                character: JSON.parse(JSON.stringify(defaultChar))
            };
            populateZoneCreators();
            zoneGroups = {};
             while(worldGroup.children.length > 0){
                 worldGroup.remove(worldGroup.children[0]);
             }
            console.log("Starting new game state:", gameState);
            transitionToZone(getZoneId(1, 1));
            console.log("startGame finished.");
        }

        function transitionToZone(newZoneId) {
             console.log(`Attempting transition to ${newZoneId}`);
             currentMessage = "";

             if (gameState.currentZoneId && zoneGroups[gameState.currentZoneId]) {
                 console.log(`Hiding old zone: ${gameState.currentZoneId}`);
                 zoneGroups[gameState.currentZoneId].group.visible = false;
             } else {
                 console.log("No current zone to hide.");
             }

             let zoneInfo;
             if (zoneGroups[newZoneId]) {
                 zoneInfo = zoneGroups[newZoneId];
                 zoneInfo.group.visible = true;
                 console.log(`Re-entering cached zone ${newZoneId}`);
             } else {
                 const creator = zoneCreators[newZoneId];
                 if (creator) {
                    try {
                        zoneInfo = creator();
                        if (!zoneInfo || !zoneInfo.group) throw new Error("Creator function did not return valid zone info object with group.");
                        zoneGroups[newZoneId] = zoneInfo;
                        worldGroup.add(zoneInfo.group);
                        zoneInfo.group.visible = true;
                        console.log(`Created and entered new zone ${newZoneId}`);
                    } catch (creationError) {
                         console.error(`Error creating zone ${newZoneId}:`, creationError);
                         currentMessage = `<p class="message message-failure">Error creating zone ${newZoneId}.</p>`;
                         renderCurrentPageUI(); return;
                    }
                 } else {
                    console.error(`No creator found for zone ID: ${newZoneId}, critical error.`);
                    currentMessage = `<p class="message message-failure">Error: Zone creator missing for ${newZoneId}. Cannot proceed.</p>`;
                    storyTitleElement.textContent = "Fatal Error"; storyContentElement.innerHTML = currentMessage; choicesElement.innerHTML = ''; return;
                 }
             }

             gameState.currentZoneId = newZoneId;
             setupLighting(zoneInfo.lighting || 'default');

             const currentZoneGroup = zoneInfo.group;
             currentLights.forEach(light => {
                if (light && light.isPointLight && zoneInfo.lighting === 'cave') {
                     if(light.parent) light.parent.remove(light);
                     currentZoneGroup.add(light);
                     console.log("Added point light to cave group:", newZoneId);
                } else if (light && !light.isPointLight && light.parent !== scene) {
                    if(light.parent) light.parent.remove(light); // Remove from other groups
                    scene.add(light); // Add to main scene
                } else if (light && !light.isPointLight && !light.parent) {
                    scene.add(light); // Ensure it's in the scene if it wasn't attached
                }
             });

             camera.position.set(0, 6, 12);
             controls.target.set(0, 1, 0);
             controls.update();

             console.log(`Transition to ${newZoneId} complete. Rendering UI.`);
             renderCurrentPageUI();
        }


        function renderCurrentPageUI() {
            console.log(`renderCurrentPageUI called for zone: ${gameState.currentZoneId}`);
            const zoneInfo = zoneGroups[gameState.currentZoneId];
            const zoneId = gameState.currentZoneId;

            if (!storyTitleElement || !storyContentElement || !choicesElement || !statsElement || !inventoryElement || !actionInfoElement) {
                console.error("Crucial UI element missing!"); return;
            }
            // Added check for zoneInfo.group
            if (!zoneInfo || !zoneInfo.group) {
                console.error(`No zone info or group loaded for ${zoneId}`);
                storyTitleElement.textContent = "Error";
                storyContentElement.innerHTML = currentMessage + "<p>Cannot render current zone data.</p>";
                choicesElement.innerHTML = `<button class="choice-button" onclick="transitionToZoneWrapper('${getZoneId(1, 1)}')">Return to Start</button>`;
                updateStatsDisplay(); updateInventoryDisplay(); updateActionInfo(); return;
            }
            console.log(`Rendering UI for zone ${zoneId} with title "${zoneInfo.title}"`);

             storyTitleElement.textContent = zoneInfo.title || "Unknown Zone";
             storyContentElement.innerHTML = currentMessage + (zoneInfo.entryText ? `<p>${zoneInfo.entryText}</p>` : '');
             choicesElement.innerHTML = '';

             const neighbors = getZoneNeighbors(zoneId);
             const directions = {'north': 'North', 'south': 'South', 'east': 'East', 'west': 'West'};
             let addedChoices = 0;
             console.log("Adding neighbor buttons:", neighbors);
             for(const dir in directions) {
                  const neighborId = neighbors[dir];
                  const button = document.createElement('button');
                  button.classList.add('choice-button');
                  button.textContent = `Go ${directions[dir]}`;
                  if (neighborId) {
                      button.addEventListener('click', () => transitionToZone(neighborId));
                  } else {
                      button.disabled = true;
                  }
                  choicesElement.appendChild(button);
                  addedChoices++; // Count even disabled buttons for layout
             }

             if (zoneInfo.options && zoneInfo.options.length > 0) {
                 console.log("Adding zone specific options");
                 zoneInfo.options.forEach(option => {
                     const button = document.createElement('button');
                     button.classList.add('choice-button');
                     button.textContent = option.text;
                     button.addEventListener('click', () => console.log("Zone action TBC:", option.action));
                     choicesElement.appendChild(button);
                     addedChoices++;
                 });
             }

             if (addedChoices === 0 && choicesElement.innerHTML === '') {
                 choicesElement.innerHTML = '<p><i>No exits or actions defined here yet.</i></p>';
             }

             console.log("Updating stats, inventory, action info...");
             updateStatsDisplay();
             updateInventoryDisplay();
             updateActionInfo();
             console.log("renderCurrentPageUI finished.");
        }

        function transitionToZoneWrapper(zoneId) { transitionToZone(zoneId); }
        window.transitionToZoneWrapper = transitionToZoneWrapper;


        function updateStatsDisplay() {
            if (!gameState.character || !statsElement) return;
            const { hp, maxHp, xp } = gameState.character.stats;
            const hpColor = hp / maxHp < 0.3 ? '#f88' : (hp / maxHp < 0.6 ? '#fd5' : '#8f8');
            statsElement.innerHTML = `<strong>Stats:</strong> <span style="color:${hpColor}">HP: ${hp}/${maxHp}</span> <span>XP: ${xp}</span>`;
        }

        function updateInventoryDisplay() {
            if (!gameState.character || !inventoryElement) return;
             let invHtml = '<strong>Inventory:</strong> ';
             if (gameState.character.inventory.length === 0) {
                 invHtml += '<em>Empty</em>';
             } else {
                 gameState.character.inventory.forEach(item => {
                     const itemDef = itemsData[item] || { type: 'unknown', description: '???' };
                     const itemClass = `item-${itemDef.type || 'unknown'}`;
                     invHtml += `<span class="item-tag ${itemClass}" title="${itemDef.description}">${item}</span>`;
                 });
             }
             inventoryElement.innerHTML = invHtml;
        }

        function updateActionInfo() {
            if (!actionInfoElement || !gameState ) return;
            actionInfoElement.textContent = `Zone: ${gameState.currentZoneId || 'None'} | Mode: Explore`;
        }

        function pickupItem() { console.log("Pickup disabled."); }

        document.addEventListener('DOMContentLoaded', () => {
             console.log("DOM Ready - Initializing World Grid Test.");
             try {
                 initThreeJS();
                 if (!scene || !camera || !renderer) throw new Error("Three.js failed to initialize.");
                 startGame(); // Start game directly
                 console.log("Game world initialization sequence complete.");
             } catch (error) {
                 console.error("Initialization failed:", error);
                 storyTitleElement.textContent = "Initialization Error";
                 storyContentElement.innerHTML = `<p style="color:red;">Failed to start game:</p><pre style="color:red; white-space: pre-wrap;">${error.stack || error}</pre><p style="color:yellow;">Check console (F12) for details.</p>`;
                 if(sceneContainer) sceneContainer.innerHTML = '<p style="color:red; padding: 20px;">3D Scene Failed</p>';
                  const statsInvContainer = document.getElementById('stats-inventory-container');
                  const choicesCont = document.getElementById('choices-container');
                  if (statsInvContainer) statsInvContainer.style.display = 'none';
                  if (choicesCont) choicesCont.style.display = 'none';
             }
         });

    </script>
</body>
</html>