@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,275 @@
1
+ import { geometryDetailModelUrl } from '../../../three/geometry-detail'
2
+ import * as THREE from 'three'
3
+
4
+ import { VisualTubeGeometry } from '../../../three/visual-geometry'
5
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
6
+ import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
7
+
8
+ import { PORTAL_FRAME_MAX_HALF_WIDTH, PORTAL_RIFT_HALF_HEIGHT, PORTAL_RIFT_HALF_WIDTH } from './portal-proportions'
9
+
10
+ export const SPACE_STATION_PORTAL_FRAME_THEME_ID = 'intergalactic-space-station'
11
+ export const SPACE_STATION_PORTAL_FRAME_MODEL_URL =
12
+ '/models/portals/space-station-projector-portal-v1.glb?revision=projector-arms-v17-narrow-articulated-tentacles'
13
+
14
+ export interface SpaceStationPortalFrameMaterials {
15
+ chassis: THREE.Material
16
+ panel: THREE.Material
17
+ rib: THREE.Material
18
+ trim: THREE.Material
19
+ light: THREE.Material
20
+ }
21
+
22
+ const FRAME_STYLE = 'space-station-curved-projector-arms'
23
+ const RIFT_CENTER_Y = -0.14
24
+ const RIFT_FIT_SCALE = 0.9
25
+ const PROJECTOR_POINTS = [
26
+ [1.4976, -0.9],
27
+ [1.6512, -0.28],
28
+ [1.6224, 0.48],
29
+ [1.3728, 1.16],
30
+ ] as const
31
+ const PROJECTOR_VERTICAL_AIM = [0.18, 0.14, -0.16, -0.24] as const
32
+ const MODEL_ROLES = ['CHASSIS', 'PANEL', 'RIB', 'TRIM', 'LIGHT'] as const
33
+ type ModelRole = (typeof MODEL_ROLES)[number]
34
+
35
+ let modelTemplatePromise: Promise<THREE.Object3D> | undefined
36
+ const MODEL_PLACEHOLDER_MATERIAL = new THREE.MeshBasicMaterial({ visible: false })
37
+
38
+ function markVisualOnly(object: THREE.Object3D, role: string): void {
39
+ object.userData.themeOnly = SPACE_STATION_PORTAL_FRAME_THEME_ID
40
+ object.userData.themeRole = role
41
+ object.userData.decorative = true
42
+ object.userData.visualOnly = true
43
+ object.userData.portalFrameStyle = FRAME_STYLE
44
+ }
45
+
46
+ function roleFromName(name: string): ModelRole | undefined {
47
+ return MODEL_ROLES.find(role => name.toUpperCase().includes(`SS_${role}`))
48
+ }
49
+
50
+ function loadModelTemplate(): Promise<THREE.Object3D> {
51
+ modelTemplatePromise ??= new GLTFLoader().loadAsync(geometryDetailModelUrl(SPACE_STATION_PORTAL_FRAME_MODEL_URL)).then(gltf => {
52
+ gltf.scene.name = 'space-station-projector-portal-template'
53
+ gltf.scene.traverse(object => {
54
+ if (!(object instanceof THREE.Mesh)) return
55
+ const materials = Array.isArray(object.material) ? object.material : [object.material]
56
+ materials.forEach(material => material.dispose())
57
+ object.material = MODEL_PLACEHOLDER_MATERIAL
58
+ })
59
+ return gltf.scene
60
+ })
61
+ return modelTemplatePromise
62
+ }
63
+
64
+ function createPairLightMaterial(source: THREE.Material, color: string): THREE.Material {
65
+ const material = source.clone()
66
+ if (material instanceof THREE.MeshStandardMaterial) {
67
+ const pairColor = new THREE.Color(color)
68
+ material.color.copy(pairColor)
69
+ material.emissive.copy(pairColor)
70
+ material.emissiveIntensity = Math.max(3, material.emissiveIntensity)
71
+ }
72
+ material.name = `space-station-projector-light-${color}`
73
+ return material
74
+ }
75
+
76
+ const PROJECTED_ENERGY_VERTEX_SHADER = /* glsl */ `
77
+ varying vec2 vBeamUv;
78
+ void main() {
79
+ vBeamUv = uv;
80
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
81
+ }
82
+ `
83
+
84
+ const PROJECTED_ENERGY_FRAGMENT_SHADER = /* glsl */ `
85
+ uniform float uTime;
86
+ uniform vec3 uColor;
87
+ uniform float uCore;
88
+ varying vec2 vBeamUv;
89
+ void main() {
90
+ float travel = 0.78 + 0.22 * sin(vBeamUv.x * 22.0 - uTime * 7.0);
91
+ float longitudinalFade = smoothstep(0.0, 0.08, vBeamUv.x) * (1.0 - smoothstep(0.82, 1.0, vBeamUv.x));
92
+ float intensity = travel * longitudinalFade;
93
+ vec3 hotColor = mix(uColor, vec3(1.0), uCore * 0.78);
94
+ float layerStrength = mix(1.18, 1.62, uCore);
95
+ float layerAlpha = mix(0.50, 0.98, uCore);
96
+ gl_FragColor = vec4(hotColor * intensity * layerStrength, intensity * layerAlpha);
97
+ }
98
+ `
99
+
100
+ function createProjectedEnergyMaterial(name: string, color: string, core: boolean): THREE.ShaderMaterial {
101
+ const material = new THREE.ShaderMaterial({
102
+ uniforms: {
103
+ uTime: { value: 0 },
104
+ uColor: { value: new THREE.Color(color) },
105
+ uCore: { value: core ? 1 : 0 },
106
+ },
107
+ vertexShader: PROJECTED_ENERGY_VERTEX_SHADER,
108
+ fragmentShader: PROJECTED_ENERGY_FRAGMENT_SHADER,
109
+ transparent: true,
110
+ depthWrite: false,
111
+ blending: THREE.AdditiveBlending,
112
+ toneMapped: false,
113
+ })
114
+ material.name = name
115
+ return material
116
+ }
117
+
118
+ function riftBoundaryX(y: number): number {
119
+ const normalizedY = THREE.MathUtils.clamp(
120
+ (y - RIFT_CENTER_Y) / (PORTAL_RIFT_HALF_HEIGHT * RIFT_FIT_SCALE),
121
+ -0.995,
122
+ 0.995,
123
+ )
124
+ const angle = Math.asin(normalizedY)
125
+ const variation =
126
+ Math.sin(angle * 3 + 0.7) * 0.038 +
127
+ Math.sin(angle * 7 - 0.4) * 0.022 +
128
+ Math.sin(angle * 13 + 1.2) * 0.015 +
129
+ Math.sin(angle * 19 - 0.3) * 0.009 +
130
+ Math.sin(angle * 31) * 0.006
131
+ return PORTAL_RIFT_HALF_WIDTH * Math.sqrt(1 - normalizedY * normalizedY) * (1 + variation) * RIFT_FIT_SCALE
132
+ }
133
+
134
+ function createProjectedRiftBeams(name: string, color: string): THREE.Group {
135
+ const group = new THREE.Group()
136
+ group.name = `${name}-projected-rift-beams`
137
+ const auraParts: THREE.BufferGeometry[] = []
138
+ const coreParts: THREE.BufferGeometry[] = []
139
+ for (const side of [-1, 1] as const) {
140
+ for (const [index, [emitterX, emitterY]] of PROJECTOR_POINTS.entries()) {
141
+ const targetY = emitterY + PROJECTOR_VERTICAL_AIM[index]!
142
+ const start = new THREE.Vector3(side * (emitterX - 0.245), emitterY, 0.02)
143
+ const boundary = Math.max(0.18, riftBoundaryX(targetY) - 0.055)
144
+ const end = new THREE.Vector3(side * boundary, targetY, 0.04)
145
+ const curve = new THREE.LineCurve3(start, end)
146
+ auraParts.push(new VisualTubeGeometry(curve, 5, 0.045, 12, false))
147
+ coreParts.push(new VisualTubeGeometry(curve, 5, 0.009, 10, false))
148
+ }
149
+ }
150
+ const auraGeometry = mergeGeometries(auraParts, false)
151
+ const coreGeometry = mergeGeometries(coreParts, false)
152
+ auraParts.forEach(geometry => geometry.dispose())
153
+ coreParts.forEach(geometry => geometry.dispose())
154
+ if (!auraGeometry || !coreGeometry) return group
155
+ const aura = new THREE.Mesh(auraGeometry, createProjectedEnergyMaterial(`${name}-beam-aura-material`, color, false))
156
+ const core = new THREE.Mesh(coreGeometry, createProjectedEnergyMaterial(`${name}-beam-core-material`, color, true))
157
+ aura.name = `${name}-projected-beam-aura`
158
+ core.name = `${name}-projected-beam-core`
159
+ for (const layer of [aura, core]) {
160
+ layer.renderOrder = 4
161
+ layer.frustumCulled = false
162
+ layer.userData.ownsMaterial = true
163
+ layer.userData.portalPairColor = color
164
+ layer.userData.portalLightRole = 'rift-matched-projection-beam'
165
+ markVisualOnly(layer, 'space-station-projected-rift-beam')
166
+ group.add(layer)
167
+ }
168
+ markVisualOnly(group, 'space-station-projected-rift-beams')
169
+ return group
170
+ }
171
+
172
+ function attachModel(
173
+ frame: THREE.Group,
174
+ name: string,
175
+ color: string,
176
+ materials: SpaceStationPortalFrameMaterials,
177
+ template: THREE.Object3D,
178
+ ): void {
179
+ const model = template.clone(true)
180
+ model.name = `${name}-space-station-projector-model`
181
+ const pairLight = createPairLightMaterial(materials.light, color)
182
+ let lightMaterialUsed = false
183
+ let loadedMeshCount = 0
184
+
185
+ model.traverse(object => {
186
+ markVisualOnly(object, 'space-station-projector-model')
187
+ if (!(object instanceof THREE.Mesh)) return
188
+ const role = roleFromName(object.name)
189
+ if (!role) {
190
+ object.visible = false
191
+ return
192
+ }
193
+ loadedMeshCount += 1
194
+ object.material =
195
+ role === 'CHASSIS'
196
+ ? materials.chassis
197
+ : role === 'PANEL'
198
+ ? materials.panel
199
+ : role === 'RIB'
200
+ ? materials.rib
201
+ : role === 'TRIM'
202
+ ? materials.trim
203
+ : pairLight
204
+ object.castShadow = role !== 'LIGHT'
205
+ object.receiveShadow = role !== 'LIGHT'
206
+ object.frustumCulled = true
207
+ object.userData.sharedGeometry = true
208
+ if (role === 'LIGHT') {
209
+ lightMaterialUsed = true
210
+ object.userData.portalPairColor = color
211
+ object.userData.portalLightRole = 'projector-lenses'
212
+ object.userData.ownsMaterial = true
213
+ }
214
+ markVisualOnly(object, `space-station-${role.toLowerCase()}`)
215
+ })
216
+ frame.userData.loadedMeshCount = loadedMeshCount
217
+ if (!lightMaterialUsed) pairLight.dispose()
218
+ frame.add(model)
219
+ }
220
+
221
+ /** Adds only the station-local projector arms. Shared aperture, rift, glow and dust stay global. */
222
+ export function addSpaceStationPortalFrame(
223
+ root: THREE.Group,
224
+ name: string,
225
+ color: string,
226
+ materials: SpaceStationPortalFrameMaterials,
227
+ modelTemplate?: THREE.Object3D,
228
+ ): THREE.Group {
229
+ const frame = new THREE.Group()
230
+ frame.name = `${name}-space-station-frame`
231
+ frame.userData.portalApertureClearHalfWidth = 1.0
232
+ frame.userData.portalSupportMaxHalfWidth = PORTAL_FRAME_MAX_HALF_WIDTH
233
+ frame.userData.portalSupportOuterHalfWidth = 1.9
234
+ frame.userData.drawCallBudget = 7
235
+ frame.userData.modelUrl = SPACE_STATION_PORTAL_FRAME_MODEL_URL
236
+ frame.userData.portalMechanism = 'two-curved-projector-arms'
237
+ markVisualOnly(frame, 'space-station-curved-projector-arms')
238
+
239
+ const pointLight = new THREE.PointLight(color, 0.68, 4.0, 2)
240
+ pointLight.name = `${name}-space-station-projector-light`
241
+ pointLight.position.set(0, 0.15, 0.55)
242
+ pointLight.userData.portalPairColor = color
243
+ pointLight.userData.portalLightRole = 'projected-rift-source'
244
+ markVisualOnly(pointLight, 'projected-rift-source')
245
+ frame.add(pointLight)
246
+ frame.add(createProjectedRiftBeams(name, color))
247
+ root.add(frame)
248
+
249
+ if (modelTemplate) {
250
+ attachModel(frame, name, color, materials, modelTemplate)
251
+ frame.userData.modelReady = Promise.resolve(frame)
252
+ } else {
253
+ frame.userData.modelReady = loadModelTemplate()
254
+ .then(template => {
255
+ attachModel(frame, name, color, materials, template)
256
+ return frame
257
+ })
258
+ .catch(error => {
259
+ // A missing optional facade must not create an unhandled rejection in
260
+ // headless catalog audits or take down the shared portal/rift visual.
261
+ frame.userData.modelLoadError = error instanceof Error ? error.message : String(error)
262
+ modelTemplatePromise = undefined
263
+ return frame
264
+ })
265
+ }
266
+ return frame
267
+ }
268
+
269
+ /** Projector arms are static; the globally shared rift remains the animated element. */
270
+ export function updateSpaceStationPortalFrame(frame: THREE.Group | undefined, timeSeconds: number): void {
271
+ frame?.traverse(object => {
272
+ if (!(object instanceof THREE.Mesh) || object.userData.portalLightRole !== 'rift-matched-projection-beam') return
273
+ if (object.material instanceof THREE.ShaderMaterial) object.material.uniforms.uTime!.value = timeSeconds
274
+ })
275
+ }