@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,120 @@
1
+ import * as THREE from 'three'
2
+
3
+ import { usesMobileRenderingBudget } from './mobile-rendering-budget'
4
+
5
+ import { loadThemeKtx2Texture } from './theme-material-assets'
6
+ import type { TrackThemeDefinition } from './track-theme'
7
+
8
+ export function configureThemePanoramaTexture(texture: THREE.Texture, themeId: string): THREE.Texture {
9
+ texture.name = `theme-panorama:${themeId}`
10
+ texture.colorSpace = THREE.SRGBColorSpace
11
+ texture.mapping = THREE.EquirectangularReflectionMapping
12
+ texture.wrapS = THREE.RepeatWrapping
13
+ texture.wrapT = THREE.ClampToEdgeWrapping
14
+ texture.magFilter = THREE.LinearFilter
15
+ texture.minFilter = texture.mipmaps.length > 1 ? THREE.LinearMipmapLinearFilter : THREE.LinearFilter
16
+ texture.generateMipmaps = !(texture instanceof THREE.CompressedTexture)
17
+ texture.needsUpdate = true
18
+ return texture
19
+ }
20
+
21
+ export function resolveThemePanoramaUrl(url: string, mobile: boolean): string {
22
+ if (!mobile) return url
23
+ return url.replace(/\.ktx2(?=($|\?))/, '-mobile.ktx2')
24
+ }
25
+
26
+ export interface ThemePanoramaLease {
27
+ readonly texture: THREE.Texture | undefined
28
+ release(): void
29
+ }
30
+
31
+ interface PanoramaEntry {
32
+ lastUsed: number
33
+ leases: number
34
+ promise: Promise<THREE.Texture>
35
+ texture?: THREE.Texture
36
+ }
37
+
38
+ class ThemeAssetCache {
39
+ private readonly panoramas = new Map<string, PanoramaEntry>()
40
+ private readonly useMobilePanoramas = usesMobileRenderingBudget()
41
+
42
+ async acquirePanorama(theme: TrackThemeDefinition): Promise<ThemePanoramaLease> {
43
+ if (!theme.panoramaUrl) return { texture: undefined, release: () => undefined }
44
+
45
+ let entry = this.panoramas.get(theme.id)
46
+ if (!entry) {
47
+ entry = {
48
+ lastUsed: performance.now(),
49
+ leases: 0,
50
+ promise: Promise.resolve(undefined as never),
51
+ }
52
+ const runtimeUrl = resolveThemePanoramaUrl(theme.panoramaUrl, this.useMobilePanoramas)
53
+ entry.promise = (
54
+ runtimeUrl.endsWith('.ktx2')
55
+ ? loadThemeKtx2Texture(runtimeUrl)
56
+ : new THREE.TextureLoader().loadAsync(runtimeUrl)
57
+ )
58
+ .then(texture => {
59
+ entry!.texture = configureThemePanoramaTexture(texture, theme.id)
60
+ entry!.lastUsed = performance.now()
61
+ this.evict()
62
+ return entry!.texture
63
+ })
64
+ .catch(error => {
65
+ this.panoramas.delete(theme.id)
66
+ throw error
67
+ })
68
+ this.panoramas.set(theme.id, entry)
69
+ }
70
+
71
+ entry.leases += 1
72
+ entry.lastUsed = performance.now()
73
+ let released = false
74
+
75
+ try {
76
+ const texture = await entry.promise
77
+ return {
78
+ texture,
79
+ release: () => {
80
+ if (released) return
81
+ released = true
82
+ entry!.leases = Math.max(0, entry!.leases - 1)
83
+ entry!.lastUsed = performance.now()
84
+ this.evict()
85
+ },
86
+ }
87
+ } catch (error) {
88
+ entry.leases = Math.max(0, entry.leases - 1)
89
+ throw error
90
+ }
91
+ }
92
+
93
+ private evict(): void {
94
+ const mobile = usesMobileRenderingBudget()
95
+ const allowance = (mobile ? 32 : 96) * 1024 * 1024
96
+ const pinnedBytes = [...this.panoramas.values()]
97
+ .filter(entry => entry.leases > 0)
98
+ .reduce((total, entry) => total + panoramaBytes(entry.texture), 0)
99
+ const previous = [...this.panoramas.entries()]
100
+ .filter(([, entry]) => entry.leases === 0 && entry.texture)
101
+ .sort((left, right) => right[1].lastUsed - left[1].lastUsed)
102
+ let retainedPrevious = false
103
+ for (const [themeId, entry] of previous) {
104
+ const fits = !retainedPrevious && pinnedBytes + panoramaBytes(entry.texture) <= allowance
105
+ if (fits) {
106
+ retainedPrevious = true
107
+ continue
108
+ }
109
+ entry.texture?.dispose()
110
+ this.panoramas.delete(themeId)
111
+ }
112
+ }
113
+ }
114
+
115
+ function panoramaBytes(texture: THREE.Texture | undefined): number {
116
+ const image = texture?.image as { width?: number; height?: number } | undefined
117
+ return Math.max(1, image?.width ?? 1) * Math.max(1, image?.height ?? 1) * 4
118
+ }
119
+
120
+ export const themeAssetCache = new ThemeAssetCache()
@@ -0,0 +1,43 @@
1
+ export const THEME_AUDIO_MUTED_STORAGE_KEY = 'marble.fun.theme-audio-muted'
2
+ const EVENT_NAME = 'marble-theme-audio-preference'
3
+
4
+ export function shouldPageAudioRemainActive(options: {
5
+ focused: boolean
6
+ mobile: boolean
7
+ visible: boolean
8
+ }): boolean {
9
+ return !options.mobile || (options.visible && options.focused)
10
+ }
11
+
12
+ export function isThemeAudioMuted(): boolean {
13
+ try {
14
+ return JSON.parse(window.localStorage.getItem(THEME_AUDIO_MUTED_STORAGE_KEY) ?? 'false') === true
15
+ } catch {
16
+ return false
17
+ }
18
+ }
19
+
20
+ export function notifyThemeAudioPreference(muted: boolean): void {
21
+ window.dispatchEvent(new CustomEvent<boolean>(EVENT_NAME, { detail: muted }))
22
+ }
23
+
24
+ export function subscribeToThemeAudioPreference(listener: (muted: boolean) => void): () => void {
25
+ const handlePreferenceChange = (event: Event): void => {
26
+ listener((event as CustomEvent<boolean>).detail)
27
+ }
28
+ const handleStorageChange = (event: StorageEvent): void => {
29
+ if (event.key !== THEME_AUDIO_MUTED_STORAGE_KEY) return
30
+ try {
31
+ listener(JSON.parse(event.newValue ?? 'false') === true)
32
+ } catch {
33
+ listener(false)
34
+ }
35
+ }
36
+
37
+ window.addEventListener(EVENT_NAME, handlePreferenceChange)
38
+ window.addEventListener('storage', handleStorageChange)
39
+ return () => {
40
+ window.removeEventListener(EVENT_NAME, handlePreferenceChange)
41
+ window.removeEventListener('storage', handleStorageChange)
42
+ }
43
+ }