PageSourceSearch

https://pranavshekhawat.in/js/sceneSettings.js

js pranavshekhawat.in collected 2026-10-03 20:09:28 UTC 30,547 bytes, 647 lines download raw bytes

1/**
2 * ═══════════════════════════════════════════════════════════════
3 *  SCENE SETTINGS — central config loaded from Firestore
4 *  Admin panel writes to  site_config/scene_settings
5 *  Game reads on startup and merges with these hard-coded defaults.
6 * ═══════════════════════════════════════════════════════════════
7 */
8
9import { db } from './firebase-config.js';
10import { doc, getDoc } from 'https://www.gstatic.com/firebasejs/10.12.0/firebase-firestore.js';
11import { JOURNEY_DEFAULTS } from './JourneyGuide.js';
12
13// ── Hard-coded defaults (never mutated) ──────────────────────
14export const DEFAULTS = Object.freeze({
15    ...JOURNEY_DEFAULTS,
16
17    // ── Audio volumes (0 – 1) ───────────────────────────
18    snd_catMeow:        0.05,
19    snd_jumpMeow:       0.15,
20    snd_jumpMeowEnabled: true,
21    snd_jumpMeowPerTen:  2,
22    snd_crystalPop:     0.1,
23    snd_birdChirp:      0.01,
24    snd_wind:           0.10,
25    snd_portalEnter:    0.05,
26    snd_portalAmbient:  0.1,
27    snd_catInBush:      0.5,
28    snd_crystalAttach:  0.55,
29    snd_portalOpening:  0.6,
30    snd_confetti:       0.55,
31
32    // ── Audio distances ─────────────────────────────────
33    snd_birdHearRange:   20,
34    snd_birdLoudRange:   3,
35    snd_portalHearRange: 25,
36    snd_portalLoudRange: 4,
37    snd_bushRadius:      3,
38
39    // ── Camera ──────────────────────────────────────────
40    cam_distance:    12,
41    cam_height:      5,
42    cam_sensitivity: 0.002,
43    cam_fov:         55,
44
45    // ── Character ───────────────────────────────────────
46    char_speed:           12,
47    char_sprintMultiplier: 2,
48    char_jumpForce:       14,
49    char_gravity:         35,
50
51    // ── Emoji reactions ─────────────────────────────────
52    emoji_available: ['😍','🔥','💯','🚀','✨','👏','❤️','🤩','💡','🎨','🙌','⭐'],
53    emoji_maxPerPortal:  10,
54    emoji_minDistance:    5,
55    emoji_maxDistance:    8,
56    emoji_floatHeight:   0.9,
57    emoji_bobSpeed:      1.2,
58    emoji_bobAmount:     0.15,
59    emoji_portalProximity: 4,
60
61    // ── Particles ───────────────────────────────────────
62    ptcl_fireflyCount: 50,
63    ptcl_fireflySize:  0.25,
64    ptcl_fireflySpeed: 0.5,
65    ptcl_leafCount:    100,
66    ptcl_leafSize:     0.15,
67    ptcl_leafSpeed:    1.0,
68    ptcl_pollenCount:  400,
69    ptcl_pollenSize:   0.06,
70    ptcl_pollenSpeed:  1.0,
71
72    // ── Visual / Atmosphere ─────────────────────────────
73    vis_fogNear:           60,
74    vis_fogFar:            700,
75    vis_exposure:          1.4,
76    vis_ambientIntensity:  0.65,
77    vis_sunIntensity:      1.2,
78
79    // ── Birds ───────────────────────────────────────────
80    bird_count:            10,
81    bird_fleeDistance:     8,
82    bird_fleeSpeed:       22,
83    bird_fleeHeight:      25,
84    bird_autoflyMinTime:  6,
85    bird_autoflyMaxTime:  10,
86    bird_autoflyMinDist:  30,
87    bird_autoflyMaxDist:  60,
88    bird_autoflyHeight:   18,
89    bird_landSpeed:        6,
90    bird_peckMinTime:     1.5,
91    bird_peckMaxTime:     3.0,
92    bird_peckSpeed:       8,
93    bird_lookMinTime:     1.0,
94    bird_lookMaxTime:     2.5,
95    bird_lookSpeed:       1.5,
96    bird_hopChance:       0.3,
97    bird_hopDistance:      0.3,
98    bird_hopHeight:       0.12,
99
100    // ── Fish ────────────────────────────────────────────
101    fish_count:            6,
102    fish_swimSpeedMin:     2,
103    fish_swimSpeedMax:     4,
104    fish_swimDepthMin:    -0.4,
105    fish_swimDepthMax:    -1.2,
106    fish_turnSpeed:        2.0,
107    fish_swimAnimSpeed:    1.4,
108    fish_wanderRadius:     50,
109    fish_jumpChance:       0.02,
110    fish_jumpHeight:       2.5,
111    fish_catScareRadius:   4,
112    fish_catScareJumpChance: 0.4,
113    fish_catScareFleeDistance: 8,
114    fish_catScareSpeedMult: 3.5,
115
116    // ── Bubbles ─────────────────────────────────────────
117    bubble_maxCount:       200,
118    bubble_size:           0.12,
119    bubble_swimRate:       8,
120    bubble_swimSpread:     0.8,
121    bubble_enterBurst:     30,
122    bubble_exitBurst:      15,
123    bubble_riseSpeed:      0.6,
124    bubble_driftSpeed:     0.15,
125
126    // ── Crystals ────────────────────────────────────────
127    // NOTE: crystal_total is legacy. Runtime uses portals.length * 3 when story mode is active.
128    crystal_total:         19,
129    crystal_size:          0.4,
130    crystal_collectDistance: 2.5,
131    crystal_floatSpeed:    2,
132    crystal_floatAmount:   0.2,
133    crystal_glowRadius:    0.6,
134    crystal_lightIntensity: 0.5,
135    crystal_lightRange:    5,
136
137    // ── Crystal Messages ────────────────────────────────
138    crystal_messages:      [],
139    crystal_msgDuration:   8,
140    crystal_meowEnabled:   true,
141
142    // ── Cat Thoughts ────────────────────────────────────
143    thought_enabled:       true,
144    thought_globalCooldown: 15,
145    thought_meowVolume:    0.15,
146    thought_msgDuration:   6,
147    thought_aiEnabled:     false,
148    thought_chatEnabled:   true,
149    thought_chatLimit:     10,
150
151    // Birds
152    thought_birdNearbyEnabled: true,
153    thought_birdNearbyCooldown: 25,
154    thought_birdNearbyChance: 0.03,
155    thought_birdNearbyMessages: [],
156    thought_birdNearbyMessagesWhiteCatHere: [],
157    thought_birdNearbyMessagesNearWhiteCat: [],
158
159    thought_birdVeryCloseEnabled: true,
160    thought_birdVeryCloseCooldown: 20,
161    thought_birdVeryCloseChance: 0.1,
162    thought_birdVeryCloseMessages: [],
163    thought_birdVeryCloseMessagesWhiteCatHere: [],
164    thought_birdVeryCloseMessagesNearWhiteCat: [],
165
166    thought_birdsFleeingEnabled: true,
167    thought_birdsFleeingCooldown: 20,
168    thought_birdsFleeingChance: 0.5,
169    thought_birdsFleeingMessages: [],
170    thought_birdsFleeingMessagesWhiteCatHere: [],
171    thought_birdsFleeingMessagesNearWhiteCat: [],
172
173    thought_birdsReturningEnabled: true,
174    thought_birdsReturningCooldown: 30,
175    thought_birdsReturningChance: 0.05,
176    thought_birdsReturningMessages: [],
177    thought_birdsReturningMessagesWhiteCatHere: [],
178    thought_birdsReturningMessagesNearWhiteCat: [],
179
180    // Fish
181    thought_fishVisibleEnabled: true,
182    thought_fishVisibleCooldown: 30,
183    thought_fishVisibleChance: 0.02,
184    thought_fishVisibleMessages: [],
185    thought_fishVisibleMessagesWhiteCatHere: [],
186    thought_fishVisibleMessagesNearWhiteCat: [],
187
188    thought_fishFleeingEnabled: true,
189    thought_fishFleeingCooldown: 20,
190    thought_fishFleeingChance: 0.3,
191    thought_fishFleeingMessages: [],
192    thought_fishFleeingMessagesWhiteCatHere: [],
193    thought_fishFleeingMessagesNearWhiteCat: [],
194
195    thought_fishJumpingEnabled: true,
196    thought_fishJumpingCooldown: 25,
197    thought_fishJumpingChance: 0.2,
198    thought_fishJumpingMessages: [],
199    thought_fishJumpingMessagesWhiteCatHere: [],
200    thought_fishJumpingMessagesNearWhiteCat: [],
201
202    // Water
203    thought_swimmingEnabled: true,
204    thought_swimmingCooldown: 25,
205    thought_swimmingChance: 0.02,
206    thought_swimmingMessages: [],
207    thought_swimmingMessagesWhiteCatHere: [],
208    thought_swimmingMessagesNearWhiteCat: [],
209
210    thought_pondEdgeEnabled: true,
211    thought_pondEdgeCooldown: 25,
212    thought_pondEdgeChance: 0.02,
213    thought_pondEdgeMessages: [],
214    thought_pondEdgeMessagesWhiteCatHere: [],
215    thought_pondEdgeMessagesNearWhiteCat: [],
216
217    thought_enterWaterEnabled: true,
218    thought_enterWaterCooldown: 15,
219    thought_enterWaterChance: 0.6,
220    thought_enterWaterMessages: [],
221    thought_enterWaterMessagesWhiteCatHere: [],
222    thought_enterWaterMessagesNearWhiteCat: [],
223
224    thought_exitWaterEnabled: true,
225    thought_exitWaterCooldown: 15,
226    thought_exitWaterChance: 0.5,
227    thought_exitWaterMessages: [],
228    thought_exitWaterMessagesWhiteCatHere: [],
229    thought_exitWaterMessagesNearWhiteCat: [],
230
231    // Trees
232    thought_nearTreeEnabled: true,
233    thought_nearTreeCooldown: 30,
234    thought_nearTreeChance: 0.02,
235    thought_nearTreeMessages: [],
236    thought_nearTreeMessagesWhiteCatHere: [],
237    thought_nearTreeMessagesNearWhiteCat: [],
238
239    // Bushes
240    thought_inBushEnabled: true,
241    thought_inBushCooldown: 30,
242    thought_inBushChance: 0.02,
243    thought_inBushMessages: [],
244    thought_inBushMessagesWhiteCatHere: [],
245    thought_inBushMessagesNearWhiteCat: [],
246
247    // Rocks
248    thought_nearRockEnabled: true,
249    thought_nearRockCooldown: 30,
250    thought_nearRockChance: 0.02,
251    thought_nearRockMessages: [],
252    thought_nearRockMessagesWhiteCatHere: [],
253    thought_nearRockMessagesNearWhiteCat: [],
254
255    thought_onRockEnabled: true,
256    thought_onRockCooldown: 25,
257    thought_onRockChance: 0.04,
258    thought_onRockMessages: [],
259    thought_onRockMessagesWhiteCatHere: [],
260    thought_onRockMessagesNearWhiteCat: [],
261
262    // Huts
263    thought_nearHutEnabled: true,
264    thought_nearHutCooldown: 30,
265    thought_nearHutChance: 0.02,
266    thought_nearHutMessages: [],
267    thought_nearHutMessagesWhiteCatHere: [],
268    thought_nearHutMessagesNearWhiteCat: [],
269
270    thought_onRoofEnabled: true,
271    thought_onRoofCooldown: 25,
272    thought_onRoofChance: 0.04,
273    thought_onRoofMessages: [],
274    thought_onRoofMessagesWhiteCatHere: [],
275    thought_onRoofMessagesNearWhiteCat: [],
276
277    // Portals
278    thought_nearPortalEnabled: true,
279    thought_nearPortalCooldown: 25,
280    thought_nearPortalChance: 0.03,
281    thought_nearPortalMessages: [],
282    thought_nearPortalMessagesWhiteCatHere: [],
283    thought_nearPortalMessagesNearWhiteCat: [],
284
285    // Crystals
286    thought_nearCrystalEnabled: true,
287    thought_nearCrystalCooldown: 25,
288    thought_nearCrystalChance: 0.03,
289    thought_nearCrystalMessages: [],
290    thought_nearCrystalMessagesWhiteCatHere: [],
291    thought_nearCrystalMessagesNearWhiteCat: [],
292
293    // Fish Bones
294    thought_nearFishBoneEnabled: true,
295    thought_nearFishBoneCooldown: 30,
296    thought_nearFishBoneChance: 0.03,
297    thought_nearFishBoneMessages: [],
298    thought_nearFishBoneMessagesWhiteCatHere: [],
299    thought_nearFishBoneMessagesNearWhiteCat: [],
300
301    // Sky / Camera
302    thought_lookingUpEnabled: true,
303    thought_lookingUpCooldown: 30,
304    thought_lookingUpChance: 0.03,
305    thought_lookingUpMessages: [],
306    thought_lookingUpMessagesWhiteCatHere: [],
307    thought_lookingUpMessagesNearWhiteCat: [],
308
309    thought_facingCameraEnabled: true,
310    thought_facingCameraCooldown: 35,
311    thought_facingCameraChance: 0.02,
312    thought_facingCameraMessages: [],
313    thought_facingCameraMessagesWhiteCatHere: [],
314    thought_facingCameraMessagesNearWhiteCat: [],
315
316    // Movement
317    thought_jumpingEnabled: true,
318    thought_jumpingCooldown: 40,
319    thought_jumpingChance: 0.3,
320    thought_jumpingMessages: [],
321    thought_jumpingMessagesWhiteCatHere: [],
322    thought_jumpingMessagesNearWhiteCat: [],
323
324    thought_roamingEnabled: true,
325    thought_roamingCooldown: 45,
326    thought_roamingChance: 0.005,
327    thought_roamingMessages: [],
328    thought_roamingMessagesWhiteCatHere: [],
329    thought_roamingMessagesNearWhiteCat: [],
330
331    thought_idleEnabled: true,
332    thought_idleCooldown: 20,
333    thought_idleChance: 0.01,
334    thought_idleMessages: [],
335    thought_idleMessagesWhiteCatHere: [],
336    thought_idleMessagesNearWhiteCat: [],
337
338    thought_restingEnabled: true,
339    thought_restingCooldown: 20,
340    thought_restingChance: 0.02,
341    thought_restingMessages: [],
342    thought_restingMessagesWhiteCatHere: [],
343    thought_restingMessagesNearWhiteCat: [],
344
345    // ── Character animation ─────────────────────────────
346    char_furToggleEnabled: false,
347    char_swimSpeed:        5,
348    char_swimDepth:       -1.5,
349    char_swimAnimSpeed:    1.0,
350    char_walkAnimSpeed:    3,
351    char_idleAnimSpeed:    1.0,
352    char_jumpAnimSpeed:    1.0,
353    char_restdownAnimSpeed: 3.0,
354    char_restAnimSpeed:    1.0,
355
356    // ── Sky ─────────────────────────────────────────────
357    sky_topColor:          0x9cc8e6,
358    sky_horizonColor:      0xe6f2fb,
359    sky_sunStrength:       0.35,
360
361    // ── Clouds ──────────────────────────────────────────
362    // Population & placement
363    cloud_count:                      120,
364    cloud_minHeight:                  100,
365    cloud_maxHeight:                  276,
366    cloud_minDist:                    80,
367    cloud_maxDist:                    600,
368    // Type mix (relative weights)
369    cloud_cumulusWeight:              3,
370    cloud_puffWeight:                 0,
371    // Cumulus sizing
372    cloud_cumulusWidthMin:            100,
373    cloud_cumulusWidthMax:            180,
374    cloud_cumulusHeightRatioMin:      0.57,
375    cloud_cumulusHeightRatioMax:      1.27,
376    cloud_cumulusOpacityMin:          0.43,
377    cloud_cumulusOpacityMax:          0.7,
378    // Puff sizing
379    cloud_puffWidthMin:               32,
380    cloud_puffWidthMax:               57,
381    cloud_puffHeightRatioMin:         0.65,
382    cloud_puffHeightRatioMax:         1.19,
383    cloud_puffOpacityMin:             0.43,
384    cloud_puffOpacityMax:             0.75,
385    // Texture & shape
386    cloud_textureSize:                448,
387    cloud_bodyRxMin:                  70,
388    cloud_bodyRxMax:                  109,
389    cloud_bodyRyRatioMin:             0.6,
390    cloud_bodyRyRatioMax:             1.24,
391    cloud_bumpCountMin:               6,
392    cloud_bumpCountMax:               15,
393    cloud_bumpRadiusMin:              0.65,
394    cloud_bumpRadiusMax:              1.12,
395    cloud_bumpDistanceMin:            0.4,
396    cloud_bumpDistanceMax:            1.16,
397    cloud_bumpTopBias:                0.13,
398    cloud_bumpAlphaMin:               0.33,
399    cloud_bumpAlphaMax:               0.68,
400    cloud_softenerCount:              1,
401    cloud_softenerAlphaMin:           0.35,
402    cloud_softenerAlphaMax:           0.78,
403    // Shading (3-layer stack)
404    cloud_topColor:                   0xfff4e3,
405    cloud_sideColor:                  0xffffff,
406    cloud_baseColor:                  0xb9c2d4,
407    cloud_baseOpacityMul:             0.04,
408    cloud_topOpacityMul:              0.2,
409    cloud_layerYOffset:               0.05,
410    // Motion
411    cloud_windDirRad:                 0.5655,
412    cloud_windSpeedMin:               0.6,
413    cloud_windSpeedMax:               1.4,
414    cloud_bobAmpMin:                  0,
415    cloud_bobAmpMax:                  0.3,
416    cloud_bobSpeedMin:                0.2,
417    cloud_bobSpeedMax:                0.5,
418
419    // ── Pond ────────────────────────────────────────────
420    pond_waterColor:       0x2d98b5,
421    pond_waterOpacity:     0.82,
422    pond_maxDepth:         3.5,
423    pond_lilypadCount:     10,
424
425    // ── Models / Scatter ────────────────────────────────
426    mdl_treeCount:         60,
427    mdl_treeScaleMin:      3.5,
428    mdl_treeScaleMax:      4,
429    mdl_grassCount:        40000,
430    mdl_grassScaleMin:     12,
431    mdl_grassScaleMax:     16,
432    mdl_grassWindStrength: 0.8,
433    mdl_cloverCount:       1000,
434    mdl_cloverScaleMin:    0.5,
435    mdl_cloverScaleMax:    1,
436    mdl_rockCount:         4,
437    mdl_rockScaleMin:      2,
438    mdl_rockScaleMax:      2.5,
439    mdl_flatRockCount:     6,
440    mdl_flatRockScaleMin:  19,
441    mdl_flatRockScaleMax:  20,
442    mdl_bushCount:         200,
443    mdl_bushScaleMin:      2,
444    mdl_bushScaleMax:      3,
445    mdl_bushWindStrength:  0.2,
446
447    // ── Story / Rescue ──────────────────────────────────
448    story_depositMessages: [
449        'Three crystals anchored!',
450        'The portal hums to life...',
451        'One step closer to finding her.',
452    ],
453
454    // ── White Cat NPC — behaviour ───────────────────────
455    //
456    // The NPC runs a "commitment" state machine: each state (wander, follow,
457    // journey, chase, rest, etc.) runs to its own completion and only THEN
458    // falls back to `idle`, which pauses for a randomized duration before
459    // picking the next action. No mid-action interrupts — so movement looks
460    // natural (straight lines, deliberate pauses) instead of twitchy.
461    //
462    // Everything below is admin-tunable. Lower numbers = more frequent /
463    // more aggressive. Higher = calmer / more reserved.
464    //
465    npc_enabled:              true,
466    npc_moveSpeed:            8,
467    npc_sprintSpeed:          14,
468
469    // Idle pauses — how long the cat stands still between decisions
470    npc_idleMinDuration:      2.0,   // shortest idle pause (seconds)
471    npc_idleMaxDuration:      6.0,   // longest idle pause
472
473    // Following the player
474    // NOTE: stand-off distances must exceed the cat-cat physical collision
475    // radius (~2.5 combined, see main.js _resolveCatCatCollision). Anything
476    // lower causes the NPC to keep pushing into the player's back and the
477    // cats visually clip into each other.
478    npc_followMinDistance:    3.5,   // default stand-off distance when following
479    npc_followMaxDistance:    14,    // won't start a follow if player is further
480    npc_followGiveUpDistance: 40,    // mid-follow, gives up if player gets this far
481    npc_followChance:         0.6,   // probability of entering follow from idle
482    npc_followSprintChance:   0.25,  // 25% of follow sessions are at a run
483    npc_followCommitMin:      6.0,   // shortest committed follow session
484    npc_followCommitMax:      14.0,  // longest committed follow session
485    npc_closeFollowChance:    0.25,  // 25% of follows are "close follow"
486    npc_closeFollowDistance:  2.8,   // tight stand-off — just above the 2.5 collision minimum
487
488    // Wandering and journeys
489    npc_wanderRadius:         30,    // max range of a short wander target
490    npc_wanderChance:         0.65,  // probability of wandering vs idling
491    npc_journeyChance:        0.18,  // chance of a long cross-map journey
492    npc_journeySprintChance:  0.35,  // 35% of journeys have a sprint burst
493    npc_journeyCooldownMin:   20,    // seconds between journeys (min)
494    npc_journeyCooldownMax:   50,    // seconds between journeys (max)
495
496    // Long-range "smell" homing — she can smell the player from anywhere
497    npc_smellHomeChance:      0.08,  // probability on any idle eval
498    npc_smellHomeMinDist:     45,    // only fires when player is at least this far
499    npc_smellHomeArrive:      4,     // stops when within this distance of player
500
501    // Guaranteed-reunion timer — if she's been far from the player for this
502    // long, she'll bolt in (sprint) regardless of distance, stop very close,
503    // then resume whatever she was going to do.
504    npc_forceReunionInterval: 40,    // seconds away before forced reunion
505    npc_forceReunionCloseDistance: 6, // distance that counts as "with player"
506
507    // Rest (lying down / sleeping)
508    npc_restIntervalMean:     60,    // avg seconds between rests
509    npc_restIntervalVar:      20,    // ± variance on the interval
510    npc_restDuration:         20,    // how long each rest lasts
511
512    // Jumping on rocks and hut roofs
513    npc_jumpOnRockChance:     0.4,   // chance of seeking an elevated surface
514    npc_jumpCooldown:         6,     // seconds before jumping again
515    npc_imitatePlayerChance:  0.5,   // when player is on roof/rock, chance to follow up
516
517    // Birds
518    npc_chaseBirdChance:      0.35,  // chance of pouncing when a bird is near
519    npc_chaseBirdRadius:      22,    // starts chase if bird within this distance
520    npc_chaseBirdGiveUp:      30,    // bird escapes if this far
521
522    // Fish
523    npc_chaseFishChance:      0.25,  // chance of chasing a fish
524    npc_chaseFishRadius:      15,    // dry-land chase-trigger distance
525    npc_chaseFishSwimRadius:  30,    // in-water chase-trigger distance
526
527    // Fish bones
528    npc_sniffBoneChance:      0.5,   // chance of sniffing when near a bone
529    npc_sniffRadius:          5,     // only sniffs if within this close
530    npc_sniffDuration:        2.5,   // how long the sniff pause lasts
531
532    // ── NPC Dialogue — globals ──────────────────────────
533    // Proximity gating: NPC bubbles + meows only fire when the player is
534    // within npcChat_proximityRadius. Out of range, she's silent.
535    npcChat_enabled:           true,
536    npcChat_globalCooldown:    12,
537    npcChat_msgDuration:       6,
538    npcChat_proximityRadius:   18,
539    npcChat_meowVolumeScale:   1.0,
540    npcChat_meowEnabled:       true,
541
542    // Each trigger below has the same schema:
543    //   npcChat_${key}Enabled / Cooldown / Chance / Messages
544
545    npcChat_nearPlayerEnabled:  true, npcChat_nearPlayerCooldown: 20, npcChat_nearPlayerChance: 0.2, npcChat_nearPlayerMessages: [], npcChat_nearPlayerReplies: [],
546    npcChat_swimmingEnabled:    true, npcChat_swimmingCooldown: 25,   npcChat_swimmingChance: 0.3,   npcChat_swimmingMessages: [],
547    npcChat_onRoofEnabled:      true, npcChat_onRoofCooldown: 30,     npcChat_onRoofChance: 0.3,     npcChat_onRoofMessages: [],
548    npcChat_onRockEnabled:      true, npcChat_onRockCooldown: 30,     npcChat_onRockChance: 0.3,     npcChat_onRockMessages: [],
549    npcChat_inBushEnabled:      true, npcChat_inBushCooldown: 25,     npcChat_inBushChance: 0.3,     npcChat_inBushMessages: [],
550    npcChat_nearTreeEnabled:    true, npcChat_nearTreeCooldown: 35,   npcChat_nearTreeChance: 0.15,  npcChat_nearTreeMessages: [],
551    npcChat_nearHutEnabled:     true, npcChat_nearHutCooldown: 35,    npcChat_nearHutChance: 0.15,   npcChat_nearHutMessages: [],
552    npcChat_nearRockEnabled:    true, npcChat_nearRockCooldown: 35,   npcChat_nearRockChance: 0.1,   npcChat_nearRockMessages: [],
553    npcChat_nearPortalEnabled:  true, npcChat_nearPortalCooldown: 35, npcChat_nearPortalChance: 0.15,npcChat_nearPortalMessages: [],
554    npcChat_nearCrystalEnabled: true, npcChat_nearCrystalCooldown: 30,npcChat_nearCrystalChance: 0.15,npcChat_nearCrystalMessages: [],
555    npcChat_nearFishBoneEnabled:true, npcChat_nearFishBoneCooldown: 40,npcChat_nearFishBoneChance: 0.3,npcChat_nearFishBoneMessages: [],
556    npcChat_pondEdgeEnabled:    true, npcChat_pondEdgeCooldown: 30,   npcChat_pondEdgeChance: 0.15,  npcChat_pondEdgeMessages: [],
557    npcChat_birdNearbyEnabled:  true, npcChat_birdNearbyCooldown: 25, npcChat_birdNearbyChance: 0.3, npcChat_birdNearbyMessages: [],
558    npcChat_fishVisibleEnabled: true, npcChat_fishVisibleCooldown: 25,npcChat_fishVisibleChance: 0.3,npcChat_fishVisibleMessages: [],
559    npcChat_jumpingEnabled:     true, npcChat_jumpingCooldown: 18,    npcChat_jumpingChance: 0.2,    npcChat_jumpingMessages: [],
560    npcChat_lookingUpEnabled:   true, npcChat_lookingUpCooldown: 30,  npcChat_lookingUpChance: 0.15, npcChat_lookingUpMessages: [],
561    npcChat_facingCameraEnabled:true, npcChat_facingCameraCooldown: 35,npcChat_facingCameraChance:0.1,npcChat_facingCameraMessages: [],
562    npcChat_roamingEnabled:     true, npcChat_roamingCooldown: 30,    npcChat_roamingChance: 0.08,   npcChat_roamingMessages: [],
563    npcChat_followingEnabled:   true, npcChat_followingCooldown: 25,  npcChat_followingChance: 0.1,  npcChat_followingMessages: [],
564    npcChat_restingEnabled:     true, npcChat_restingCooldown: 40,    npcChat_restingChance: 0.3,    npcChat_restingMessages: [],
565    npcChat_atPortalEnabled:    true, npcChat_atPortalCooldown: 30,   npcChat_atPortalChance: 0.3,   npcChat_atPortalMessages: [],
566    npcChat_idleEnabled:        true, npcChat_idleCooldown: 45,       npcChat_idleChance: 0.1,       npcChat_idleMessages: [],
567
568    // ── Admin debug — UI buttons shown only to the logged-in admin ──
569    // Gated by BOTH the auth check (must match the admin UID) AND this flag.
570    // Toggle from Admin → White Cat NPC (or wherever you place the control).
571    admin_showRescueDebugBtn:  false,  // shows the "replay rescue cinematic" button on the HUD
572
573    // ── Cat Duet — scripted Q→A conversations between black + white cats ──
574    // Both cats can ask; the OTHER delivers the paired answer after a delay.
575    // Duets are gated by proximity (npcDuet_proximityRadius) and by which
576    // interaction-point trigger is currently active in the world.
577    npcDuet_enabled:           true,
578    npcDuet_globalCooldown:    40,    // default seconds between any two duets
579    npcDuet_proximityRadius:    6,    // both cats must be within this of each other
580    npcDuet_pauseBefore:        4,    // silent lead-in (sec) before the question appears
581    npcDuet_exchangeDelay:     2.5,   // gap (seconds) between question and answer
582    npcDuet_muteThoughtsAfter:  4,    // extra mute time after the answer (no thought bubbles)
583
584    // Per-trigger conversation pools. Each conversation entry is:
585    //   { question: string, answer: string, asker: 'random'|'black'|'white' }
586    // Empty pools mean no duet for that context.
587    npcDuet_generalEnabled:      true, npcDuet_generalCooldown:      40, npcDuet_generalChance:      0.4, npcDuet_generalConversations:      [],
588    npcDuet_swimmingEnabled:     true, npcDuet_swimmingCooldown:     50, npcDuet_swimmingChance:     0.5, npcDuet_swimmingConversations:     [],
589    npcDuet_onRoofEnabled:       true, npcDuet_onRoofCooldown:       50, npcDuet_onRoofChance:       0.5, npcDuet_onRoofConversations:       [],
590    npcDuet_onRockEnabled:       true, npcDuet_onRockCooldown:       50, npcDuet_onRockChance:       0.5, npcDuet_onRockConversations:       [],
591    npcDuet_inBushEnabled:       true, npcDuet_inBushCooldown:       60, npcDuet_inBushChance:       0.4, npcDuet_inBushConversations:       [],
592    npcDuet_nearTreeEnabled:     true, npcDuet_nearTreeCooldown:     60, npcDuet_nearTreeChance:     0.3, npcDuet_nearTreeConversations:     [],
593    npcDuet_nearRockEnabled:     true, npcDuet_nearRockCooldown:     60, npcDuet_nearRockChance:     0.3, npcDuet_nearRockConversations:     [],
594    npcDuet_nearHutEnabled:      true, npcDuet_nearHutCooldown:      60, npcDuet_nearHutChance:      0.3, npcDuet_nearHutConversations:      [],
595    npcDuet_nearPortalEnabled:   true, npcDuet_nearPortalCooldown:   45, npcDuet_nearPortalChance:   0.4, npcDuet_nearPortalConversations:   [],
596    npcDuet_atPortalEnabled:     true, npcDuet_atPortalCooldown:     45, npcDuet_atPortalChance:     0.5, npcDuet_atPortalConversations:     [],
597    npcDuet_nearCrystalEnabled:  true, npcDuet_nearCrystalCooldown:  45, npcDuet_nearCrystalChance:  0.4, npcDuet_nearCrystalConversations:  [],
598    npcDuet_nearFishBoneEnabled: true, npcDuet_nearFishBoneCooldown: 60, npcDuet_nearFishBoneChance: 0.4, npcDuet_nearFishBoneConversations: [],
599    npcDuet_pondEdgeEnabled:     true, npcDuet_pondEdgeCooldown:     60, npcDuet_pondEdgeChance:     0.3, npcDuet_pondEdgeConversations:     [],
600    npcDuet_birdNearbyEnabled:   true, npcDuet_birdNearbyCooldown:   45, npcDuet_birdNearbyChance:   0.4, npcDuet_birdNearbyConversations:   [],
601    npcDuet_fishVisibleEnabled:  true, npcDuet_fishVisibleCooldown:  45, npcDuet_fishVisibleChance:  0.4, npcDuet_fishVisibleConversations:  [],
602    npcDuet_idleEnabled:         true, npcDuet_idleCooldown:         50, npcDuet_idleChance:         0.3, npcDuet_idleConversations:         [],
603    npcDuet_restingEnabled:      true, npcDuet_restingCooldown:      60, npcDuet_restingChance:      0.3, npcDuet_restingConversations:      [],
604
605    // ── Memory Pods — Polaroids scattered in the world ──
606    // Visual tuning for the in-world Polaroid + glow + particles. Content
607    // (image, caption, filter, font, count) lives in site_config/memories.
608    memory_enabled:             true,
609    memory_count:               4,          // how many pods to scatter
610    memory_proximityRadius:     5,          // walk within this to get "E to read" hint
611    memory_spawnMinRadius:      20,         // keep pods away from world center
612    memory_spawnMaxRadius:      95,         // within playable area
613    memory_spawnMinSpacing:     18,         // min distance between pods
614    memory_polaroidScale:       1.3,        // ground Polaroid plane scale
615    memory_glowColor:           '#ffe29a',  // warm cream glow
616    memory_glowIntensity:       1.4,
617    memory_glowPulseSpeed:      1.6,
618    memory_hoverHeight:         1.2,        // glow orb height above Polaroid
619    memory_particleCount:       14,         // magic particles per pod
620    memory_particleRadius:      0.8,
621    memory_viewedDimFactor:     0.25,       // 0 = dark, 1 = no dim. Admin-tunable.
622    memory_hintText:            'Press E to read',
623    memory_autoDismissDuration: 0,          // 0 = manual close; >0 = auto-close after N sec
624});
625
626// ── Mutable runtime config (starts as copy of defaults) ──────
627export const SCENE = { ...DEFAULTS };
628
629/**
630 * Fetch overrides from Firestore and merge into SCENE.
631 * Call this BEFORE creating any game entities.
632 */
633export async function loadSceneSettings() {
634    try {
635        const snap = await getDoc(doc(db, 'site_config', 'scene_settings'));
636        if (snap.exists()) {
637            const data = snap.data();
638            for (const key of Object.keys(DEFAULTS)) {
639                if (data[key] !== undefined) {
640                    SCENE[key] = data[key];
641                }
642            }
643        }
644    } catch (e) {
645        console.warn('Scene settings: using defaults', e);
646    }
647}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.