@marble.fun/track-catalog 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +15 -0
  3. package/assets.json +346 -0
  4. package/package.json +32 -0
  5. package/release.json +135 -0
  6. package/src/audio/game-audio-preference.ts +9 -0
  7. package/src/authoritative.ts +6 -0
  8. package/src/catalog.ts +1 -0
  9. package/src/index.ts +14 -0
  10. package/src/legacy-stock-catalog.ts +130 -0
  11. package/src/pages/lobby/lobby-types.ts +140 -0
  12. package/src/pages/lobby/marble-factory.ts +931 -0
  13. package/src/pages/play/race/egypt-elevator-blender.ts +159 -0
  14. package/src/pages/play/race/egypt-portal-frame.ts +382 -0
  15. package/src/pages/play/race/finish-detection.ts +1 -0
  16. package/src/pages/play/race/japanese-sakura-castle-support-blender.ts +510 -0
  17. package/src/pages/play/race/japanese-sakura-kawara-roof.ts +201 -0
  18. package/src/pages/play/race/japanese-sakura-portal-frame.ts +325 -0
  19. package/src/pages/play/race/jungle-portal-frame.ts +308 -0
  20. package/src/pages/play/race/jungle-temple-lift.ts +366 -0
  21. package/src/pages/play/race/marble-entity.ts +257 -0
  22. package/src/pages/play/race/marble-material.ts +20 -0
  23. package/src/pages/play/race/marble-race-engine.ts +2652 -0
  24. package/src/pages/play/race/path-pattern-layout.ts +142 -0
  25. package/src/pages/play/race/peg-obstacle-visual.ts +171 -0
  26. package/src/pages/play/race/physics-debug.ts +1 -0
  27. package/src/pages/play/race/physics-dry-run.ts +90 -0
  28. package/src/pages/play/race/portal-derived-elevator-skins.ts +729 -0
  29. package/src/pages/play/race/portal-pair-color.ts +20 -0
  30. package/src/pages/play/race/portal-proportions.ts +25 -0
  31. package/src/pages/play/race/portal-visual.ts +1108 -0
  32. package/src/pages/play/race/race-camera-director.ts +586 -0
  33. package/src/pages/play/race/race-camera-obstruction.ts +37 -0
  34. package/src/pages/play/race/race-camera-tour-planner.ts +274 -0
  35. package/src/pages/play/race/race-camera.ts +206 -0
  36. package/src/pages/play/race/race-completion.ts +5 -0
  37. package/src/pages/play/race/race-configuration.ts +57 -0
  38. package/src/pages/play/race/race-marble-focus-renderer.ts +134 -0
  39. package/src/pages/play/race/race-physics-profile.ts +7 -0
  40. package/src/pages/play/race/race-physics.ts +16 -0
  41. package/src/pages/play/race/race-playback.ts +41 -0
  42. package/src/pages/play/race/race-rolling-audio.ts +616 -0
  43. package/src/pages/play/race/race-simulation-renderer.ts +221 -0
  44. package/src/pages/play/race/race-terminal-checker-strip.ts +226 -0
  45. package/src/pages/play/race/race-transform-interpolation.ts +50 -0
  46. package/src/pages/play/race/race-types.ts +12 -0
  47. package/src/pages/play/race/rebound-visual.ts +70 -0
  48. package/src/pages/play/race/seeded-random.ts +23 -0
  49. package/src/pages/play/race/space-station-portal-frame.ts +275 -0
  50. package/src/pages/play/race/track-appearance-canopy.ts +946 -0
  51. package/src/pages/play/race/track-appearance-floor-runner.ts +206 -0
  52. package/src/pages/play/race/track-appearance-jungle-flower-hedge.ts +483 -0
  53. package/src/pages/play/race/track-appearance-rope-wall.ts +443 -0
  54. package/src/pages/play/race/track-appearance-sakura-bridge-fence.ts +305 -0
  55. package/src/pages/play/race/track-appearance-sakura-castle-wall.ts +1031 -0
  56. package/src/pages/play/race/track-appearance-space-station-wall.ts +497 -0
  57. package/src/pages/play/race/track-appearance-stone-block-shell.ts +517 -0
  58. package/src/pages/play/race/track-appearance-support.ts +983 -0
  59. package/src/pages/play/race/track-appearance-vampire-carved-wall.ts +545 -0
  60. package/src/pages/play/race/track-appearance-wall-terminal.ts +167 -0
  61. package/src/pages/play/race/track-course.ts +271 -0
  62. package/src/pages/play/race/track-definition.ts +443 -0
  63. package/src/pages/play/race/track-fence-architecture.ts +33 -0
  64. package/src/pages/play/race/track-floor-material-rules.ts +51 -0
  65. package/src/pages/play/race/track-mechanisms.ts +1350 -0
  66. package/src/pages/play/race/track-physics-surface.ts +6 -0
  67. package/src/pages/play/race/track-route-network.ts +9 -0
  68. package/src/pages/play/race/track-shadow-batching.ts +760 -0
  69. package/src/pages/play/race/track-surface-basis.ts +1 -0
  70. package/src/pages/play/race/track-surface-topology.ts +326 -0
  71. package/src/pages/play/race/track-world.ts +10181 -0
  72. package/src/pages/play/race/under-track-support-architecture.ts +122 -0
  73. package/src/pages/play/race/vortex-profile.ts +5 -0
  74. package/src/pages/play/race/vortex-water-effect.ts +863 -0
  75. package/src/pages/play/race/wild-west-mine-lift.ts +281 -0
  76. package/src/pages/play/race/wild-west-vortex-housing.ts +572 -0
  77. package/src/pages/track-lab/atlas-vortex-layout.ts +1 -0
  78. package/src/pages/track-lab/cone-field-layout.ts +1 -0
  79. package/src/pages/track-lab/conveyor-layout.ts +1 -0
  80. package/src/pages/track-lab/engine/canonical-track-assembly.ts +64 -0
  81. package/src/pages/track-lab/engine/editor-object-geometry.ts +170 -0
  82. package/src/pages/track-lab/engine/track-lab-connection-state.ts +123 -0
  83. package/src/pages/track-lab/engine/track-piece-visual.ts +1420 -0
  84. package/src/pages/track-lab/flipper-bank-layout.ts +1 -0
  85. package/src/pages/track-lab/flipper-component.ts +127 -0
  86. package/src/pages/track-lab/rising-wall-layout.ts +1 -0
  87. package/src/pages/track-lab/simple-obstacle-layout.ts +1 -0
  88. package/src/pages/track-lab/track-piece-layout.ts +28 -0
  89. package/src/pages/track-lab/track-runtime-adapter.ts +1152 -0
  90. package/src/three/geometry-attribute-aliases.ts +24 -0
  91. package/src/three/geometry-detail.ts +36 -0
  92. package/src/three/homepage-marble-geometry.ts +15 -0
  93. package/src/three/japanese-sakura-appearance.ts +252 -0
  94. package/src/three/jungle-flower-hedge-appearance.ts +7 -0
  95. package/src/three/jungle-temple-appearance.ts +5 -0
  96. package/src/three/low-quality-scene-renderer.ts +132 -0
  97. package/src/three/marble-sphere-geometry.ts +13 -0
  98. package/src/three/mobile-rendering-budget.ts +11 -0
  99. package/src/three/painted-valley-appearance.ts +270 -0
  100. package/src/three/railroad-appearance.ts +71 -0
  101. package/src/three/renderer-resize.ts +32 -0
  102. package/src/three/rope-wall-appearance.ts +91 -0
  103. package/src/three/rugged-track-appearance.ts +2 -0
  104. package/src/three/scene-lighting.ts +183 -0
  105. package/src/three/scene-shader-preparation.ts +64 -0
  106. package/src/three/scene-theme-controller.ts +461 -0
  107. package/src/three/shadow-shader-preparation.ts +162 -0
  108. package/src/three/shared-obstacle-metal.ts +44 -0
  109. package/src/three/soft-additive-particles.ts +57 -0
  110. package/src/three/space-station-appearance.ts +213 -0
  111. package/src/three/static-track-batched-mesh.ts +132 -0
  112. package/src/three/stone-track-shell-appearance.ts +12 -0
  113. package/src/three/theme-ambience-controller.ts +226 -0
  114. package/src/three/theme-asset-cache.ts +120 -0
  115. package/src/three/theme-audio-preference.ts +43 -0
  116. package/src/three/theme-catalog.ts +1457 -0
  117. package/src/three/theme-material-assets.ts +258 -0
  118. package/src/three/theme-material-ktx2-manifest.ts +190 -0
  119. package/src/three/theme-material-registry.ts +1952 -0
  120. package/src/three/theme-world-factory.ts +590 -0
  121. package/src/three/track-connector-batch.ts +125 -0
  122. package/src/three/track-material-continuity.ts +442 -0
  123. package/src/three/track-theme.ts +535 -0
  124. package/src/three/vampire-castle-appearance.ts +258 -0
  125. package/src/three/visual-frame-pacing.ts +19 -0
  126. package/src/three/visual-geometry.ts +120 -0
  127. package/src/three/visual-quality.ts +83 -0
  128. package/src/three/water-appearance.ts +29 -0
  129. package/src/three/wild-west-sand-appearance.ts +2 -0
@@ -0,0 +1,535 @@
1
+ import type { MaterialSlot } from '@marble.fun/physics-engine'
2
+
3
+ import type { TrackSkin } from '../pages/play/race/track-definition'
4
+
5
+ export type ThemeId =
6
+ | 'neutral-studio'
7
+ | 'material-slot-debug'
8
+ | 'rocky-mountain-wild-west'
9
+ | 'ancient-egypt'
10
+ | 'japanese-sakura-lake'
11
+ | 'intergalactic-space-station'
12
+ | 'painted-valley'
13
+ | 'low-poly-jungle'
14
+ | 'vampire-castle'
15
+
16
+ export type WorldMaterialSlot =
17
+ 'world.ground' | 'world.primary' | 'world.secondary' | 'world.foliage' | 'world.accent' | 'world.dark'
18
+
19
+ export type ThemeOnlyMaterialSlot =
20
+ | 'lane.guard.backing'
21
+ | 'obstacle.primary'
22
+ | 'obstacle.secondary'
23
+ | 'obstacle.base'
24
+ | 'mechanism.flipper.primary'
25
+ | 'mechanism.flipper.secondary'
26
+
27
+ export type ThemeMaterialSlot = MaterialSlot | ThemeOnlyMaterialSlot | WorldMaterialSlot
28
+
29
+ export type MaterialPatternKind =
30
+ | 'wood'
31
+ | 'rough-wood'
32
+ | 'railroad-planks'
33
+ | 'bridge-planks'
34
+ | 'fence-timber'
35
+ | 'sand'
36
+ | 'rock'
37
+ | 'gunmetal-silver'
38
+ | 'weathered-sheet-metal'
39
+ | 'braided-rope'
40
+ | 'painted-wood'
41
+ | 'painted-stone'
42
+ | 'watercolor'
43
+ | 'panels'
44
+ | 'grid'
45
+ | 'neon-net'
46
+ | 'holographic-hover-grid'
47
+ | 'waves'
48
+ | 'bark'
49
+ | 'jungle-vines'
50
+ | 'leaf-canopy'
51
+ | 'moss-bark'
52
+ | 'ancient-bricks'
53
+ | 'stick-fence'
54
+ | 'debug'
55
+
56
+ export interface MaterialPatternRecipe {
57
+ kind: MaterialPatternKind
58
+ primary: string
59
+ secondary: string
60
+ /** Physical surface size, in track-world units, occupied by one texture tile. */
61
+ worldSize: readonly [number, number]
62
+ }
63
+
64
+ export interface MaterialSurfaceTreatment {
65
+ kind: 'floor-perimeter-overlay'
66
+ materialId: string
67
+ bandWidth: number
68
+ /** Physical width, in track-world units, over which the shoulder fades into the floor. */
69
+ featherWidth: number
70
+ normalOffset: number
71
+ }
72
+
73
+ interface MaterialRecipeBase {
74
+ color: string
75
+ roughness: number
76
+ metalness: number
77
+ emissive?: string
78
+ emissiveIntensity?: number
79
+ transparent?: boolean
80
+ opacity?: number
81
+ alphaTest?: number
82
+ depthWrite?: boolean
83
+ netSpacing?: number
84
+ netLineWidth?: number
85
+ netFillOpacity?: number
86
+ /** Hot-core half-width in track-world units for the holographic hover grid. */
87
+ hoverGridCoreWidth?: number
88
+ /** Exponential falloff coefficient for the hover-grid aura. */
89
+ hoverGridAuraFalloff?: number
90
+ /** Aura multiplier for the hover-grid shader. */
91
+ hoverGridAuraStrength?: number
92
+ /** Extra energy at line intersections without adding node geometry. */
93
+ hoverGridJunctionStrength?: number
94
+ envMapIntensity?: number
95
+ pattern?: MaterialPatternRecipe
96
+ textureUrl?: string
97
+ emissiveTextureUrl?: string
98
+ emissiveSurface?: 'all' | 'upward'
99
+ normalTextureUrl?: string
100
+ heightTextureUrl?: string
101
+ roughnessTextureUrl?: string
102
+ aoTextureUrl?: string
103
+ /** Physical surface size, in track-world units, occupied by one image tile. */
104
+ textureWorldSize?: readonly [number, number]
105
+ bumpScale?: number
106
+ normalScale?: readonly [number, number]
107
+ aoMapIntensity?: number
108
+ /** Declarative visual treatment applied to every canonical surface using this material. */
109
+ surfaceTreatment?: MaterialSurfaceTreatment
110
+ /**
111
+ * Optional height-map-driven macro relief for tessellated visual track shells.
112
+ * This never changes the corresponding Rapier collider.
113
+ */
114
+ visualRelief?: {
115
+ scale: number
116
+ bias: number
117
+ targetEdgeLength: number
118
+ }
119
+ }
120
+
121
+ export type MaterialRecipe =
122
+ | (MaterialRecipeBase & {
123
+ kind: 'standard'
124
+ })
125
+ | (MaterialRecipeBase & {
126
+ kind: 'physical'
127
+ clearcoat?: number
128
+ clearcoatRoughness?: number
129
+ transmission?: number
130
+ thickness?: number
131
+ })
132
+ | (MaterialRecipeBase & {
133
+ kind: 'toon'
134
+ gradientSteps?: number
135
+ inkColor?: string
136
+ inkStrength?: number
137
+ })
138
+
139
+ export type TrackAppearanceSurface = 'floor' | 'wall' | 'rail' | 'support'
140
+
141
+ export type TrackAppearanceDetailStyle =
142
+ | 'wild-west-railroad'
143
+ | 'centered-floor-runner'
144
+ | 'wild-west-short-ranch-fence'
145
+ | 'wild-west-tall-stockade'
146
+ | 'shared-rope-wall'
147
+ | 'jungle-flower-hedge'
148
+ | 'shared-stone-block-shell'
149
+ | 'japanese-sakura-castle-wall'
150
+ | 'japanese-sakura-vermilion-bridge-fence'
151
+ | 'space-station-armored-wall'
152
+
153
+ export type TrackAppearanceCanopyStyle = 'wild-west-covered-mine' | 'japanese-sakura-roofed-gate'
154
+
155
+ export type TrackAppearanceSupportStyle =
156
+ | 'wild-west-timber-trestle'
157
+ | 'wild-west-canyon-buttress'
158
+ | 'japanese-sakura-vermilion-underbridge'
159
+ | 'japanese-sakura-dragon-stone'
160
+
161
+ export interface TrackAppearancePreset {
162
+ id: string
163
+ label: string
164
+ material: MaterialRecipe
165
+ /** The real theme default. Selecting it clears the saved override. */
166
+ default?: boolean
167
+ detailStyle?: TrackAppearanceDetailStyle
168
+ /** Visual-only overhead structure paired with this explicit wall choice. */
169
+ canopyStyle?: TrackAppearanceCanopyStyle
170
+ /** Visual-only structure beneath a compatible lane. It never adds collision geometry. */
171
+ supportStyle?: TrackAppearanceSupportStyle
172
+ /** Retained for loading older documents but omitted from new inspector choices. */
173
+ hidden?: boolean
174
+ }
175
+
176
+ export interface TrackAppearancePresets {
177
+ floor?: readonly TrackAppearancePreset[]
178
+ wall?: readonly TrackAppearancePreset[]
179
+ rail?: readonly TrackAppearancePreset[]
180
+ support?: readonly TrackAppearancePreset[]
181
+ }
182
+
183
+ export type ThemeWorldKit =
184
+ 'studio' | 'wild-west' | 'ancient-egypt' | 'japanese-sakura-lake' | 'space-station' | 'jungle'
185
+ export type ThemeParticleStyle = 'dust' | 'space-drift' | 'fireflies' | 'petals' | 'none'
186
+ export type ThemeAmbienceStyle = 'studio' | 'desert' | 'space' | 'jungle' | 'meadow' | 'sakura-lake'
187
+
188
+ export interface TrackThemeDefinition {
189
+ id: ThemeId | (string & {})
190
+ version: number
191
+ label: string
192
+ shortLabel: string
193
+ description: string
194
+ /** Small square crop for compact controls and history rows. */
195
+ thumbnailUrl?: string
196
+ /** Portrait crop used by the Track Lab theme carousel. */
197
+ pickerPreviewUrl?: string
198
+ /** Full-frame compressed background used while a world is loading. */
199
+ loadingPreviewUrl?: string
200
+ /** Full Open Graph composition retained for social and legacy consumers. */
201
+ previewUrl?: string
202
+ panoramaUrl?: string
203
+ materials: Readonly<Record<ThemeMaterialSlot, MaterialRecipe>>
204
+ appearancePresets?: TrackAppearancePresets
205
+ /** Theme-private materials used by decorative geometry rather than inspector choices. */
206
+ detailMaterials?: Readonly<Record<string, MaterialRecipe>>
207
+ environment: {
208
+ background: string
209
+ fog: string
210
+ fogNear: number
211
+ fogFar: number
212
+ environmentIntensity: number
213
+ }
214
+ lighting: {
215
+ skyColor: string
216
+ groundColor: string
217
+ ambientIntensity: number
218
+ keyColor: string
219
+ keyIntensity: number
220
+ keyPosition: readonly [number, number, number]
221
+ rimColor: string
222
+ rimIntensity: number
223
+ rimPosition: readonly [number, number, number]
224
+ exposure: number
225
+ practical?: {
226
+ color: string
227
+ intensity: number
228
+ distance: number
229
+ decay: number
230
+ count: number
231
+ heightOffset: number
232
+ }
233
+ }
234
+ world: {
235
+ kit: ThemeWorldKit
236
+ density: number
237
+ }
238
+ particles: {
239
+ style: ThemeParticleStyle
240
+ color: string
241
+ secondaryColor: string
242
+ count: number
243
+ size: number
244
+ speed: number
245
+ opacity: number
246
+ }
247
+ ambience: {
248
+ style: ThemeAmbienceStyle
249
+ volume: number
250
+ }
251
+ }
252
+
253
+ export function defaultAppearancePreset(
254
+ theme: TrackThemeDefinition,
255
+ surface: TrackAppearanceSurface,
256
+ ): TrackAppearancePreset | undefined {
257
+ return theme.appearancePresets?.[surface]?.find(preset => preset.default === true && !preset.hidden)
258
+ }
259
+
260
+ interface ThemePalette {
261
+ laneSurface: string
262
+ laneEdge: string
263
+ laneGuard: string
264
+ structure: string
265
+ structureSecondary: string
266
+ mechanism: string
267
+ moving: string
268
+ route: string
269
+ hazard: string
270
+ finish: string
271
+ energy: string
272
+ portal: string
273
+ glass: string
274
+ worldGround: string
275
+ worldPrimary: string
276
+ worldSecondary: string
277
+ foliage: string
278
+ accent: string
279
+ dark: string
280
+ }
281
+
282
+ interface MaterialSetOptions {
283
+ surfacePattern?: MaterialPatternRecipe
284
+ surfaceEmissive?: string
285
+ surfaceBumpScale?: number
286
+ guardPattern?: MaterialPatternRecipe
287
+ guardEmissive?: string
288
+ transparentGuardBacking?: boolean
289
+ structurePattern?: MaterialPatternRecipe
290
+ trackRoughness?: number
291
+ trackMetalness?: number
292
+ guardMetalness?: number
293
+ clearcoat?: number
294
+ }
295
+
296
+ export function createThemeMaterialSet(
297
+ palette: ThemePalette,
298
+ options: MaterialSetOptions = {},
299
+ ): Record<ThemeMaterialSlot, MaterialRecipe> {
300
+ const standard = (
301
+ color: string,
302
+ roughness: number,
303
+ metalness: number,
304
+ emissive?: string,
305
+ pattern?: MaterialPatternRecipe,
306
+ ): MaterialRecipe => ({
307
+ kind: 'standard',
308
+ color,
309
+ roughness,
310
+ metalness,
311
+ envMapIntensity: 0.9,
312
+ ...(emissive ? { emissive, emissiveIntensity: 0.52 } : {}),
313
+ ...(pattern ? { pattern } : {}),
314
+ })
315
+ const guardMaterial = standard(
316
+ palette.laneGuard,
317
+ options.guardPattern ? 0.86 : 0.3,
318
+ options.guardMetalness ?? 0.66,
319
+ options.guardEmissive,
320
+ options.guardPattern,
321
+ )
322
+ const laneEdgeMaterial = standard(
323
+ palette.laneEdge,
324
+ 0.76,
325
+ Math.max(0.18, options.trackMetalness ?? 0.12),
326
+ )
327
+ const movingMaterial: MaterialRecipe = {
328
+ ...standard(palette.moving, 0.38, 0.28),
329
+ ...(options.clearcoat
330
+ ? {
331
+ kind: 'physical' as const,
332
+ clearcoat: options.clearcoat,
333
+ clearcoatRoughness: 0.2,
334
+ }
335
+ : {}),
336
+ }
337
+ const routeMaterial = standard(palette.route, 0.32, 0.12, palette.route)
338
+ const hazardMaterial = standard(palette.hazard, 0.38, 0.24, palette.hazard)
339
+
340
+ return {
341
+ 'lane.surface': {
342
+ ...standard(
343
+ palette.laneSurface,
344
+ options.trackRoughness ?? 0.68,
345
+ options.trackMetalness ?? 0.12,
346
+ options.surfaceEmissive,
347
+ options.surfacePattern,
348
+ ),
349
+ ...(options.surfaceBumpScale ? { bumpScale: options.surfaceBumpScale } : {}),
350
+ },
351
+ 'lane.edge': laneEdgeMaterial,
352
+ 'lane.guard': guardMaterial,
353
+ 'lane.guard.backing': options.transparentGuardBacking
354
+ ? { ...guardMaterial, transparent: true, opacity: 0, depthWrite: false }
355
+ : guardMaterial,
356
+ 'structure.primary': standard(palette.structure, 0.72, 0.24, undefined, options.structurePattern),
357
+ 'structure.secondary': standard(palette.structureSecondary, 0.74, 0.28),
358
+ 'mechanism.body': standard(palette.mechanism, 0.56, 0.42, undefined, options.structurePattern),
359
+ 'mechanism.moving': movingMaterial,
360
+ 'mechanism.flipper.primary': movingMaterial,
361
+ 'mechanism.flipper.secondary': movingMaterial,
362
+ 'signal.route': routeMaterial,
363
+ 'signal.hazard': hazardMaterial,
364
+ 'obstacle.primary': hazardMaterial,
365
+ 'obstacle.secondary': laneEdgeMaterial,
366
+ 'obstacle.base': routeMaterial,
367
+ 'signal.finish': standard(palette.finish, 0.28, 0.14, palette.finish),
368
+ 'effect.energy': standard(palette.energy, 0.22, 0.12, palette.energy),
369
+ 'effect.portal': standard(palette.portal, 0.18, 0.12, palette.portal),
370
+ glass: {
371
+ kind: 'physical',
372
+ color: palette.glass,
373
+ roughness: 0.1,
374
+ metalness: 0,
375
+ clearcoat: 1,
376
+ clearcoatRoughness: 0.08,
377
+ transparent: true,
378
+ opacity: 0.38,
379
+ depthWrite: false,
380
+ envMapIntensity: 1.35,
381
+ },
382
+ 'world.ground': standard(
383
+ palette.worldGround,
384
+ options.trackRoughness ?? 0.78,
385
+ options.trackMetalness ?? 0.04,
386
+ undefined,
387
+ options.surfacePattern,
388
+ ),
389
+ 'world.primary': standard(palette.worldPrimary, 0.72, 0.16, undefined, options.structurePattern),
390
+ 'world.secondary': standard(palette.worldSecondary, 0.58, 0.28),
391
+ 'world.foliage': standard(palette.foliage, 0.88, 0),
392
+ 'world.accent': standard(palette.accent, 0.3, 0.18, palette.accent),
393
+ 'world.dark': standard(palette.dark, 0.9, 0.08),
394
+ }
395
+ }
396
+
397
+ const neutralMaterials = createThemeMaterialSet({
398
+ laneSurface: '#aeb9be',
399
+ laneEdge: '#27313a',
400
+ laneGuard: '#e3eaeb',
401
+ structure: '#35414a',
402
+ structureSecondary: '#1d252c',
403
+ mechanism: '#55636d',
404
+ moving: '#ff765f',
405
+ route: '#dfff65',
406
+ hazard: '#ff765f',
407
+ finish: '#dfff65',
408
+ energy: '#61ddff',
409
+ portal: '#b173ff',
410
+ glass: '#c6ecff',
411
+ worldGround: '#161d22',
412
+ worldPrimary: '#3a474f',
413
+ worldSecondary: '#8b9ba4',
414
+ foliage: '#50665a',
415
+ accent: '#dfff65',
416
+ dark: '#0a0e12',
417
+ })
418
+
419
+ export const NEUTRAL_STUDIO_THEME: TrackThemeDefinition = {
420
+ id: 'neutral-studio',
421
+ version: 2,
422
+ label: 'Neutral Studio',
423
+ shortLabel: 'Studio',
424
+ description: 'A clean graphite studio for focused track construction.',
425
+ materials: neutralMaterials,
426
+ environment: {
427
+ background: '#080b10',
428
+ fog: '#0b1016',
429
+ fogNear: 45,
430
+ fogFar: 220,
431
+ environmentIntensity: 0.95,
432
+ },
433
+ lighting: {
434
+ skyColor: '#cbd7df',
435
+ groundColor: '#11161a',
436
+ ambientIntensity: 1.8,
437
+ keyColor: '#ffffff',
438
+ keyIntensity: 3.1,
439
+ keyPosition: [18, 30, 16],
440
+ rimColor: '#8eb7ff',
441
+ rimIntensity: 0.8,
442
+ rimPosition: [-24, 18, -20],
443
+ exposure: 1,
444
+ },
445
+ world: { kit: 'studio', density: 0.35 },
446
+ particles: {
447
+ style: 'none',
448
+ color: '#ffffff',
449
+ secondaryColor: '#ffffff',
450
+ count: 0,
451
+ size: 0.1,
452
+ speed: 0,
453
+ opacity: 0,
454
+ },
455
+ ambience: { style: 'studio', volume: 0.035 },
456
+ }
457
+
458
+ export const MATERIAL_SLOT_DEBUG_THEME: TrackThemeDefinition = {
459
+ ...NEUTRAL_STUDIO_THEME,
460
+ id: 'material-slot-debug',
461
+ version: 2,
462
+ label: 'Material Slot Debug',
463
+ shortLabel: 'Debug',
464
+ description: 'Every semantic surface role rendered as a distinct diagnostic colour.',
465
+ materials: createThemeMaterialSet({
466
+ laneSurface: '#00d9ff',
467
+ laneEdge: '#1747ff',
468
+ laneGuard: '#f7ff00',
469
+ structure: '#8038ff',
470
+ structureSecondary: '#de32ff',
471
+ mechanism: '#ff8a00',
472
+ moving: '#ff174d',
473
+ route: '#26ff72',
474
+ hazard: '#ff0033',
475
+ finish: '#ffffff',
476
+ energy: '#00fff0',
477
+ portal: '#ff40d6',
478
+ glass: '#89e9ff',
479
+ worldGround: '#242424',
480
+ worldPrimary: '#7c00ff',
481
+ worldSecondary: '#ff8a00',
482
+ foliage: '#00ff3c',
483
+ accent: '#fff000',
484
+ dark: '#050505',
485
+ }),
486
+ environment: {
487
+ background: '#050508',
488
+ fog: '#090913',
489
+ fogNear: 55,
490
+ fogFar: 220,
491
+ environmentIntensity: 1,
492
+ },
493
+ lighting: {
494
+ ...NEUTRAL_STUDIO_THEME.lighting,
495
+ ambientIntensity: 1.9,
496
+ keyIntensity: 3.4,
497
+ },
498
+ }
499
+
500
+ export function createTrackSkinTheme(skin: TrackSkin): TrackThemeDefinition {
501
+ const color = (value: number): string => `#${value.toString(16).padStart(6, '0')}`
502
+
503
+ return {
504
+ ...NEUTRAL_STUDIO_THEME,
505
+ id: skin.id,
506
+ label: skin.label,
507
+ shortLabel: skin.label,
508
+ materials: createThemeMaterialSet({
509
+ laneSurface: color(skin.trackTop),
510
+ laneEdge: color(skin.trackEdge),
511
+ laneGuard: color(skin.rail),
512
+ structure: color(skin.structure),
513
+ structureSecondary: color(skin.trackEdge),
514
+ mechanism: color(skin.structure),
515
+ moving: color(skin.hazard),
516
+ route: color(skin.signal),
517
+ hazard: color(skin.hazard),
518
+ finish: color(skin.signal),
519
+ energy: color(skin.signal),
520
+ portal: '#b173ff',
521
+ glass: '#c6ecff',
522
+ worldGround: color(skin.fog),
523
+ worldPrimary: color(skin.structure),
524
+ worldSecondary: color(skin.rail),
525
+ foliage: '#50665a',
526
+ accent: color(skin.signal),
527
+ dark: color(skin.background),
528
+ }),
529
+ environment: {
530
+ ...NEUTRAL_STUDIO_THEME.environment,
531
+ background: color(skin.background),
532
+ fog: color(skin.fog),
533
+ },
534
+ }
535
+ }