minerender 2.1.0-beta.5 → 2.1.0-beta.7

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 (102) hide show
  1. package/dist/browser/esm/Materials.mjs +6 -3
  2. package/dist/browser/esm/Materials.mjs.map +1 -1
  3. package/dist/browser/esm/Ticker.mjs +3 -1
  4. package/dist/browser/esm/Ticker.mjs.map +1 -1
  5. package/dist/browser/esm/UVMapper.mjs +9 -9
  6. package/dist/browser/esm/UVMapper.mjs.map +1 -1
  7. package/dist/browser/esm/assets/AssetDefaults.mjs +8 -0
  8. package/dist/browser/esm/assets/AssetDefaults.mjs.map +1 -0
  9. package/dist/browser/esm/assets/AssetKey.mjs +1 -1
  10. package/dist/browser/esm/assets/AssetKey.mjs.map +1 -1
  11. package/dist/browser/esm/assets/AssetLoader.mjs +5 -5
  12. package/dist/browser/esm/assets/AssetLoader.mjs.map +1 -1
  13. package/dist/browser/esm/assets/Assets.mjs +1 -5
  14. package/dist/browser/esm/assets/Assets.mjs.map +1 -1
  15. package/dist/browser/esm/assets/BlockEntities.mjs +3 -1
  16. package/dist/browser/esm/assets/BlockEntities.mjs.map +1 -1
  17. package/dist/browser/esm/assets/BlockStates.mjs +1 -1
  18. package/dist/browser/esm/assets/BlockStates.mjs.map +1 -1
  19. package/dist/browser/esm/assets/Entities.mjs +1 -1
  20. package/dist/browser/esm/assets/Entities.mjs.map +1 -1
  21. package/dist/browser/esm/assets/Fonts.mjs +31 -4
  22. package/dist/browser/esm/assets/Fonts.mjs.map +1 -1
  23. package/dist/browser/esm/assets/Models.mjs +1 -1
  24. package/dist/browser/esm/assets/Models.mjs.map +1 -1
  25. package/dist/browser/esm/assets/_fonts/Unihex.mjs +168 -0
  26. package/dist/browser/esm/assets/_fonts/Unihex.mjs.map +1 -0
  27. package/dist/browser/esm/assets/index.mjs +1 -0
  28. package/dist/browser/esm/assets/index.mjs.map +1 -1
  29. package/dist/browser/esm/assets/source/HostedAssetSource.mjs +19 -9
  30. package/dist/browser/esm/assets/source/HostedAssetSource.mjs.map +1 -1
  31. package/dist/browser/esm/assets/source/parser/AssetParsers.mjs +1 -0
  32. package/dist/browser/esm/assets/source/parser/AssetParsers.mjs.map +1 -1
  33. package/dist/browser/esm/cache/Caching.mjs +2 -0
  34. package/dist/browser/esm/cache/Caching.mjs.map +1 -1
  35. package/dist/browser/esm/entity/scene/EntityAnimationPlayer.mjs +62 -11
  36. package/dist/browser/esm/entity/scene/EntityAnimationPlayer.mjs.map +1 -1
  37. package/dist/browser/esm/entity/scene/EntityObject.mjs +53 -20
  38. package/dist/browser/esm/entity/scene/EntityObject.mjs.map +1 -1
  39. package/dist/browser/esm/env/browser/BrowserCache.mjs +3 -3
  40. package/dist/browser/esm/env/browser/BrowserCache.mjs.map +1 -1
  41. package/dist/browser/esm/env/browser/section.worker.mjs +5 -3
  42. package/dist/browser/esm/env/browser/section.worker.mjs.map +1 -1
  43. package/dist/browser/esm/gui/GuiText.mjs +4 -3
  44. package/dist/browser/esm/gui/GuiText.mjs.map +1 -1
  45. package/dist/browser/esm/image/ImageLoader.mjs +3 -3
  46. package/dist/browser/esm/image/ImageLoader.mjs.map +1 -1
  47. package/dist/browser/esm/index.browser.d.mts +115 -20
  48. package/dist/browser/esm/inspector/SceneInspector.mjs +3 -3
  49. package/dist/browser/esm/inspector/SceneInspector.mjs.map +1 -1
  50. package/dist/browser/esm/instance/InstanceManager.mjs +4 -4
  51. package/dist/browser/esm/instance/InstanceManager.mjs.map +1 -1
  52. package/dist/browser/esm/model/block/scene/BlockObject.mjs +3 -1
  53. package/dist/browser/esm/model/block/scene/BlockObject.mjs.map +1 -1
  54. package/dist/browser/esm/model/multiblock/StructureParser.mjs +7 -4
  55. package/dist/browser/esm/model/multiblock/StructureParser.mjs.map +1 -1
  56. package/dist/browser/esm/model/scene/ModelObject.mjs +8 -4
  57. package/dist/browser/esm/model/scene/ModelObject.mjs.map +1 -1
  58. package/dist/browser/esm/nbt/NBTHelper.mjs +5 -2
  59. package/dist/browser/esm/nbt/NBTHelper.mjs.map +1 -1
  60. package/dist/browser/esm/renderer/Renderer.mjs +4 -2
  61. package/dist/browser/esm/renderer/Renderer.mjs.map +1 -1
  62. package/dist/browser/esm/renderer/SceneObject.mjs +0 -6
  63. package/dist/browser/esm/renderer/SceneObject.mjs.map +1 -1
  64. package/dist/browser/esm/request/Requests.mjs +3 -3
  65. package/dist/browser/esm/request/Requests.mjs.map +1 -1
  66. package/dist/browser/esm/skin/Skins.mjs +6 -4
  67. package/dist/browser/esm/skin/Skins.mjs.map +1 -1
  68. package/dist/browser/esm/texture/TextureLoader.mjs +4 -2
  69. package/dist/browser/esm/texture/TextureLoader.mjs.map +1 -1
  70. package/dist/browser/esm/util/log.mjs +75 -0
  71. package/dist/browser/esm/util/log.mjs.map +1 -1
  72. package/dist/browser/esm/util/model.mjs +7 -7
  73. package/dist/browser/esm/util/model.mjs.map +1 -1
  74. package/dist/browser/esm/util/performance.mjs.map +1 -1
  75. package/dist/browser/esm/world/Chunk.mjs +8 -5
  76. package/dist/browser/esm/world/Chunk.mjs.map +1 -1
  77. package/dist/browser/esm/world/MineRenderWorld.mjs +96 -54
  78. package/dist/browser/esm/world/MineRenderWorld.mjs.map +1 -1
  79. package/dist/browser/esm/world/SectionGeometry.mjs +5 -3
  80. package/dist/browser/esm/world/SectionGeometry.mjs.map +1 -1
  81. package/dist/browser/esm/world/SectionMesh.mjs +4 -0
  82. package/dist/browser/esm/world/SectionMesh.mjs.map +1 -1
  83. package/dist/browser/esm/world/SectionModels.mjs +2 -2
  84. package/dist/browser/esm/world/SectionModels.mjs.map +1 -1
  85. package/dist/browser/esm/world/WorldStreamer.mjs +21 -20
  86. package/dist/browser/esm/world/WorldStreamer.mjs.map +1 -1
  87. package/dist/browser/esm/world/_culling/BlockCulling.mjs +1 -1
  88. package/dist/browser/esm/world/_culling/BlockCulling.mjs.map +1 -1
  89. package/dist/browser/esm/world/_entities/WorldEntities.mjs +3 -1
  90. package/dist/browser/esm/world/_entities/WorldEntities.mjs.map +1 -1
  91. package/dist/browser/index.d.ts +115 -20
  92. package/dist/browser/index.js +3449 -3025
  93. package/dist/browser/index.js.map +1 -1
  94. package/dist/bundle.js +13217 -12745
  95. package/dist/bundle.js.map +1 -1
  96. package/dist/node/index.d.mts +115 -20
  97. package/dist/node/index.d.ts +115 -20
  98. package/dist/node/index.js +3477 -3046
  99. package/dist/node/index.js.map +1 -1
  100. package/dist/node/index.mjs +3456 -3027
  101. package/dist/node/index.mjs.map +1 -1
  102. package/package.json +1 -1
@@ -26,9 +26,9 @@ function cached(map, key, load) {
26
26
  function compactQuads(geometry, keep) {
27
27
  const quads = keep.flatMap((kept, index) => kept ? [index] : []);
28
28
  const compact = new BufferGeometry();
29
- for (const [name, size] of Object.entries({ position: 3, normal: 3, uv: 2, uvBounds: 4, color: 3 })) {
29
+ for (const [name, size] of Object.entries({ position: 3, normal: 3, uv: 2, uvBounds: 4, color: 3, shade: 1 })) {
30
30
  const source2 = geometry.getAttribute(name);
31
- const defaults = name === "uvBounds" ? [0, 0, 1, 1] : name === "color" ? [1, 1, 1] : void 0;
31
+ const defaults = name === "uvBounds" ? [0, 0, 1, 1] : name === "color" ? [1, 1, 1] : name === "shade" ? [1] : void 0;
32
32
  const values = new Float32Array(quads.length * 4 * size);
33
33
  for (let q = 0; q < quads.length; q++) {
34
34
  for (let vertex = 0; vertex < 4; vertex++) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/world/SectionModels.ts"],"sourcesContent":["import { BufferAttribute, BufferGeometry, Euler, Matrix4, Mesh, Vector3 } from \"three\";\nimport { AssetKey } from \"../assets/AssetKey\";\nimport { Models } from \"../assets/Models\";\nimport { CUBE_FACES, CUBE_FACE_OFFSETS } from \"../CubeFace\";\nimport { BlockState, BlockStateVariant } from \"../model/block/BlockState\";\nimport { BlockStateProperties } from \"../model/block/BlockStateProperties\";\nimport { BlockStateResolver } from \"../model/block/BlockStateResolver\";\nimport { BlockTints } from \"../model/block/BlockTints\";\nimport { FluidKind } from \"../model/fluid/FluidGeometry\";\nimport { BlockModel, Model, TripleArray } from \"../model/Model\";\nimport { ModelCulling } from \"../model/ModelCulling\";\nimport { ModelObject } from \"../model/scene/ModelObject\";\nimport { TextureAtlas } from \"../texture/TextureAtlas\";\nimport { UVMapper } from \"../UVMapper\";\nimport { SectionMeshTemplate } from \"./SectionMesh\";\n\ninterface PreparedVariant {\n model: Model;\n atlas: TextureAtlas;\n rotation: Euler;\n}\n\ninterface PreparedState {\n state: BlockStateProperties;\n groups: (BlockStateVariant | BlockStateVariant[])[];\n choices: Map<BlockStateVariant, PreparedVariant>;\n}\n\n// Section meshes supply the material; templates only need the existing model geometry pipeline.\nclass SectionModel extends ModelObject {\n protected applyTextures() {}\n}\n\nfunction cached<T>(map: Map<string, Promise<T>>, key: string, load: () => Promise<T>): Promise<T> {\n let value = map.get(key);\n if (!value) {\n value = load().catch(error => { map.delete(key); throw error; });\n map.set(key, value);\n }\n return value;\n}\n\nfunction compactQuads(geometry: BufferGeometry, keep: boolean[]): BufferGeometry {\n const quads = keep.flatMap((kept, index) => kept ? [index] : []);\n const compact = new BufferGeometry();\n for (const [name, size] of Object.entries({ position: 3, normal: 3, uv: 2, uvBounds: 4, color: 3 })) {\n const source = geometry.getAttribute(name);\n const defaults = name === \"uvBounds\" ? [0, 0, 1, 1] : name === \"color\" ? [1, 1, 1] : undefined;\n const values = new Float32Array(quads.length * 4 * size);\n for (let q = 0; q < quads.length; q++) {\n for (let vertex = 0; vertex < 4; vertex++) {\n for (let component = 0; component < size; component++) {\n values[(q * 4 + vertex) * size + component] = source\n ? source.getComponent(quads[q] * 4 + vertex, component) : defaults![component];\n }\n }\n }\n compact.setAttribute(name, new BufferAttribute(values, size));\n }\n const source = geometry.getIndex()!;\n const indices: number[] = [];\n for (let q = 0; q < quads.length; q++) {\n for (let k = 0; k < 6; k++) indices.push(source.getX(quads[q] * 6 + k) - quads[q] * 4 + q * 4);\n }\n compact.setIndex(indices);\n geometry.dispose();\n return compact;\n}\n\n/** Prepares and caches model geometry and fluid atlases for merged world sections. */\nexport class SectionModels {\n private states = new WeakMap<BlockState, Map<string, Promise<PreparedState | undefined>>>();\n private models = new WeakMap<Model, Map<string, Promise<SectionMeshTemplate>>>();\n private readonly templates = new Set<SectionMeshTemplate>();\n private readonly fluidAtlases = new Map<string, Promise<TextureAtlas>>();\n\n constructor(readonly maxAtlasSize = 2048) {\n if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError(\"maxAtlasSize must be a positive integer\");\n }\n\n /** Returns templates for the matched parts, or `undefined` when the block needs an individual render object. */\n public async get(blockState: BlockState, properties: BlockStateProperties = {},\n position?: Readonly<TripleArray>, biome?: string): Promise<SectionMeshTemplate[] | undefined> {\n const biomePosition: TripleArray | undefined = position ? [...position] : undefined;\n const prepared = await this.state(blockState, properties);\n if (!prepared) return undefined;\n return Promise.all(prepared.groups.map(async group => {\n const variant = BlockStateResolver.choose(group, biomePosition);\n const choice = prepared.choices.get(variant)!;\n const tints = await BlockTints.get(blockState.key, prepared.state, choice.model, undefined,\n () => BlockTints.getBiomeColor(blockState.key, biome, biomePosition));\n return this.template(variant, choice, tints);\n }));\n }\n\n /** Resolves shared templates; dynamic biome tints are supplied on section placements instead of cached geometry. */\n public async prepareState(blockState: BlockState, properties: BlockStateProperties = {},\n biomeColor?: number | (() => Promise<number | undefined>), dynamicBiomeTint = false):\n Promise<{ pick(position?: Readonly<TripleArray>): SectionMeshTemplate[]; biomeTint?: boolean } | undefined> {\n const prepared = await this.state(blockState, properties);\n if (!prepared) return undefined;\n let color: Promise<number | undefined> | undefined;\n const resolveColor = typeof biomeColor === \"function\" ? () => color ??= biomeColor() : biomeColor;\n const templates = new Map<BlockStateVariant, SectionMeshTemplate>();\n const source = dynamicBiomeTint && await BlockTints.biomeSource(blockState.key);\n await Promise.all([...prepared.choices].map(async ([variant, choice]) => {\n const tints = await BlockTints.get(blockState.key, prepared.state, choice.model, undefined, resolveColor);\n const biomeTints = source ? await BlockTints.get(blockState.key, prepared.state, choice.model, undefined, 0xffffff) : undefined;\n templates.set(variant, await this.template(variant, choice, tints, biomeTints));\n }));\n return {\n pick: (position?: Readonly<TripleArray>) => prepared.groups.map(group => templates.get(BlockStateResolver.choose(group, position))!),\n biomeTint: source ? [...templates.values()].some(template => template.biomeTint?.some(value => value === 1)) || undefined : undefined\n };\n }\n\n private async state(blockState: BlockState, properties: BlockStateProperties): Promise<PreparedState | undefined> {\n let states = this.states.get(blockState);\n if (!states) this.states.set(blockState, states = new Map());\n const key = JSON.stringify(Object.entries(properties).sort(([a], [b]) => a.localeCompare(b)));\n return cached(states, key, () => this.prepare(blockState, properties));\n }\n\n /** Returns the shared still/flow atlas used by per-block fluid objects. */\n public fluidAtlas(kind: FluidKind, root?: string): Promise<TextureAtlas> {\n const key = new AssetKey(\"minecraft\", kind, \"models\", \"fluid\", \"assets\", \".json\", root);\n return cached(this.fluidAtlases, key.serialize(), async () => {\n const atlas = await UVMapper.getAtlas({\n key,\n textures: { still: `minecraft:block/${kind}_still`, flow: `minecraft:block/${kind}_flow` },\n elements: []\n });\n if (!atlas) throw new Error(`Missing ${kind} fluid atlas`);\n return atlas;\n });\n }\n\n private async prepare(blockState: BlockState, properties: BlockStateProperties): Promise<PreparedState | undefined> {\n const state = { ...await BlockStateResolver.defaults(blockState), ...properties };\n const groups = BlockStateResolver.matching(blockState, state);\n if (!groups.length) return undefined;\n const prepared = new Map<BlockStateVariant, PreparedVariant>();\n for (const group of groups) {\n const choices = Array.isArray(group) ? group : [group];\n if (!choices.length) return undefined;\n for (const variant of choices) {\n if (!variant.model) return undefined;\n const modelKey = AssetKey.parse(\"models\", variant.model);\n modelKey.root = blockState.key?.root;\n const model = await Models.getMerged(modelKey);\n if (!model?.elements?.length) return undefined;\n const atlas = await UVMapper.getAtlas(model);\n const rotation = BlockStateResolver.rotation(variant);\n if (!atlas\n || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return undefined;\n prepared.set(variant, { model, atlas, rotation });\n }\n }\n return { state, groups, choices: prepared };\n }\n\n private async template(variant: BlockStateVariant, { model, atlas, rotation }: PreparedVariant,\n tints?: Record<number, number>, biomeTints?: Record<number, number>): Promise<SectionMeshTemplate> {\n let models = this.models.get(model);\n if (!models) this.models.set(model, models = new Map());\n const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints, biomeTints]);\n return cached(models, key, async () => {\n const object = new SectionModel(model, {\n instanceMeshes: false, mergeMeshes: true, tints,\n uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0)\n ? [rotation.x, rotation.y, rotation.z] : undefined\n });\n await object.init();\n let geometry = (object.children[0] as Mesh).geometry;\n geometry.applyMatrix4(new Matrix4().makeRotationFromEuler(rotation));\n object.clear();\n const keep: boolean[] = [];\n const cullFaces: number[] = [];\n const biomeTint: number[] | undefined = biomeTints ? [] : undefined;\n for (const element of atlas.model.elements!) {\n for (const face of CUBE_FACES) {\n const data = element.faces[face];\n keep.push(data !== undefined);\n if (!data) continue;\n biomeTint?.push(Number(data.tintindex !== undefined && biomeTints?.[data.tintindex] !== undefined));\n const local = CUBE_FACES.findIndex(name => name === data.cullface);\n if (local < 0) {\n cullFaces.push(0);\n continue;\n }\n const normal = new Vector3(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);\n const world = CUBE_FACE_OFFSETS.findIndex(([x, y, z]) =>\n Math.abs(normal.x - x) < 1e-6 && Math.abs(normal.y - y) < 1e-6 && Math.abs(normal.z - z) < 1e-6);\n cullFaces.push(world < 0 ? 0 : 1 << world);\n }\n }\n geometry = compactQuads(geometry, keep);\n const template: SectionMeshTemplate = {\n geometry, atlas, cullFaces: new Uint8Array(cullFaces),\n biomeTint: biomeTint?.some(Boolean) ? new Uint8Array(biomeTint) : undefined,\n occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,\n layer: atlas.hasTranslucency ? 1 : 0, ambientOcclusion: (model as BlockModel).ambientocclusion !== false\n };\n this.templates.add(template);\n return template;\n });\n }\n\n /** Disposes prepared geometry and clears this world's template caches. */\n public clear(): void {\n for (const template of this.templates) template.geometry.dispose();\n this.templates.clear();\n this.states = new WeakMap();\n this.models = new WeakMap();\n this.fluidAtlases.clear();\n }\n}\n"],"mappings":"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","names":["source"]}
1
+ {"version":3,"sources":["../../../../src/world/SectionModels.ts"],"sourcesContent":["import { BufferAttribute, BufferGeometry, Euler, Matrix4, Mesh, Vector3 } from \"three\";\nimport { AssetKey } from \"../assets/AssetKey\";\nimport { Models } from \"../assets/Models\";\nimport { CUBE_FACES, CUBE_FACE_OFFSETS } from \"../CubeFace\";\nimport { BlockState, BlockStateVariant } from \"../model/block/BlockState\";\nimport { BlockStateProperties } from \"../model/block/BlockStateProperties\";\nimport { BlockStateResolver } from \"../model/block/BlockStateResolver\";\nimport { BlockTints } from \"../model/block/BlockTints\";\nimport { FluidKind } from \"../model/fluid/FluidGeometry\";\nimport { BlockModel, Model, TripleArray } from \"../model/Model\";\nimport { ModelCulling } from \"../model/ModelCulling\";\nimport { ModelObject } from \"../model/scene/ModelObject\";\nimport { TextureAtlas } from \"../texture/TextureAtlas\";\nimport { UVMapper } from \"../UVMapper\";\nimport { SectionMeshTemplate } from \"./SectionMesh\";\n\ninterface PreparedVariant {\n model: Model;\n atlas: TextureAtlas;\n rotation: Euler;\n}\n\ninterface PreparedState {\n state: BlockStateProperties;\n groups: (BlockStateVariant | BlockStateVariant[])[];\n choices: Map<BlockStateVariant, PreparedVariant>;\n}\n\n// Section meshes supply the material; templates only need the existing model geometry pipeline.\nclass SectionModel extends ModelObject {\n protected applyTextures() {}\n}\n\nfunction cached<T>(map: Map<string, Promise<T>>, key: string, load: () => Promise<T>): Promise<T> {\n let value = map.get(key);\n if (!value) {\n value = load().catch(error => { map.delete(key); throw error; });\n map.set(key, value);\n }\n return value;\n}\n\nfunction compactQuads(geometry: BufferGeometry, keep: boolean[]): BufferGeometry {\n const quads = keep.flatMap((kept, index) => kept ? [index] : []);\n const compact = new BufferGeometry();\n for (const [name, size] of Object.entries({ position: 3, normal: 3, uv: 2, uvBounds: 4, color: 3, shade: 1 })) {\n const source = geometry.getAttribute(name);\n const defaults = name === \"uvBounds\" ? [0, 0, 1, 1] : name === \"color\" ? [1, 1, 1] : name === \"shade\" ? [1] : undefined;\n const values = new Float32Array(quads.length * 4 * size);\n for (let q = 0; q < quads.length; q++) {\n for (let vertex = 0; vertex < 4; vertex++) {\n for (let component = 0; component < size; component++) {\n values[(q * 4 + vertex) * size + component] = source\n ? source.getComponent(quads[q] * 4 + vertex, component) : defaults![component];\n }\n }\n }\n compact.setAttribute(name, new BufferAttribute(values, size));\n }\n const source = geometry.getIndex()!;\n const indices: number[] = [];\n for (let q = 0; q < quads.length; q++) {\n for (let k = 0; k < 6; k++) indices.push(source.getX(quads[q] * 6 + k) - quads[q] * 4 + q * 4);\n }\n compact.setIndex(indices);\n geometry.dispose();\n return compact;\n}\n\n/** Prepares and caches model geometry and fluid atlases for merged world sections. */\nexport class SectionModels {\n private states = new WeakMap<BlockState, Map<string, Promise<PreparedState | undefined>>>();\n private models = new WeakMap<Model, Map<string, Promise<SectionMeshTemplate>>>();\n private readonly templates = new Set<SectionMeshTemplate>();\n private readonly fluidAtlases = new Map<string, Promise<TextureAtlas>>();\n\n constructor(readonly maxAtlasSize = 2048) {\n if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError(\"maxAtlasSize must be a positive integer\");\n }\n\n /** Returns templates for the matched parts, or `undefined` when the block needs an individual render object. */\n public async get(blockState: BlockState, properties: BlockStateProperties = {},\n position?: Readonly<TripleArray>, biome?: string): Promise<SectionMeshTemplate[] | undefined> {\n const biomePosition: TripleArray | undefined = position ? [...position] : undefined;\n const prepared = await this.state(blockState, properties);\n if (!prepared) return undefined;\n return Promise.all(prepared.groups.map(async group => {\n const variant = BlockStateResolver.choose(group, biomePosition);\n const choice = prepared.choices.get(variant)!;\n const tints = await BlockTints.get(blockState.key, prepared.state, choice.model, undefined,\n () => BlockTints.getBiomeColor(blockState.key, biome, biomePosition));\n return this.template(variant, choice, tints);\n }));\n }\n\n /** Resolves shared templates; dynamic biome tints are supplied on section placements instead of cached geometry. */\n public async prepareState(blockState: BlockState, properties: BlockStateProperties = {},\n biomeColor?: number | (() => Promise<number | undefined>), dynamicBiomeTint = false):\n Promise<{ pick(position?: Readonly<TripleArray>): SectionMeshTemplate[]; biomeTint?: boolean } | undefined> {\n const prepared = await this.state(blockState, properties);\n if (!prepared) return undefined;\n let color: Promise<number | undefined> | undefined;\n const resolveColor = typeof biomeColor === \"function\" ? () => color ??= biomeColor() : biomeColor;\n const templates = new Map<BlockStateVariant, SectionMeshTemplate>();\n const source = dynamicBiomeTint && await BlockTints.biomeSource(blockState.key);\n await Promise.all([...prepared.choices].map(async ([variant, choice]) => {\n const tints = await BlockTints.get(blockState.key, prepared.state, choice.model, undefined, resolveColor);\n const biomeTints = source ? await BlockTints.get(blockState.key, prepared.state, choice.model, undefined, 0xffffff) : undefined;\n templates.set(variant, await this.template(variant, choice, tints, biomeTints));\n }));\n return {\n pick: (position?: Readonly<TripleArray>) => prepared.groups.map(group => templates.get(BlockStateResolver.choose(group, position))!),\n biomeTint: source ? [...templates.values()].some(template => template.biomeTint?.some(value => value === 1)) || undefined : undefined\n };\n }\n\n private async state(blockState: BlockState, properties: BlockStateProperties): Promise<PreparedState | undefined> {\n let states = this.states.get(blockState);\n if (!states) this.states.set(blockState, states = new Map());\n const key = JSON.stringify(Object.entries(properties).sort(([a], [b]) => a.localeCompare(b)));\n return cached(states, key, () => this.prepare(blockState, properties));\n }\n\n /** Returns the shared still/flow atlas used by per-block fluid objects. */\n public fluidAtlas(kind: FluidKind, root?: string): Promise<TextureAtlas> {\n const key = new AssetKey(\"minecraft\", kind, \"models\", \"fluid\", \"assets\", \".json\", root);\n return cached(this.fluidAtlases, key.serialize(), async () => {\n const atlas = await UVMapper.getAtlas({\n key,\n textures: { still: `minecraft:block/${kind}_still`, flow: `minecraft:block/${kind}_flow` },\n elements: []\n });\n if (!atlas) throw new Error(`Missing ${kind} fluid atlas`);\n return atlas;\n });\n }\n\n private async prepare(blockState: BlockState, properties: BlockStateProperties): Promise<PreparedState | undefined> {\n const state = { ...await BlockStateResolver.defaults(blockState), ...properties };\n const groups = BlockStateResolver.matching(blockState, state);\n if (!groups.length) return undefined;\n const prepared = new Map<BlockStateVariant, PreparedVariant>();\n for (const group of groups) {\n const choices = Array.isArray(group) ? group : [group];\n if (!choices.length) return undefined;\n for (const variant of choices) {\n if (!variant.model) return undefined;\n const modelKey = AssetKey.parse(\"models\", variant.model);\n modelKey.root = blockState.key?.root;\n const model = await Models.getMerged(modelKey);\n if (!model?.elements?.length) return undefined;\n const atlas = await UVMapper.getAtlas(model);\n const rotation = BlockStateResolver.rotation(variant);\n if (!atlas\n || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return undefined;\n prepared.set(variant, { model, atlas, rotation });\n }\n }\n return { state, groups, choices: prepared };\n }\n\n private async template(variant: BlockStateVariant, { model, atlas, rotation }: PreparedVariant,\n tints?: Record<number, number>, biomeTints?: Record<number, number>): Promise<SectionMeshTemplate> {\n let models = this.models.get(model);\n if (!models) this.models.set(model, models = new Map());\n const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints, biomeTints]);\n return cached(models, key, async () => {\n const object = new SectionModel(model, {\n instanceMeshes: false, mergeMeshes: true, tints,\n uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0)\n ? [rotation.x, rotation.y, rotation.z] : undefined\n });\n await object.init();\n let geometry = (object.children[0] as Mesh).geometry;\n geometry.applyMatrix4(new Matrix4().makeRotationFromEuler(rotation));\n object.clear();\n const keep: boolean[] = [];\n const cullFaces: number[] = [];\n const biomeTint: number[] | undefined = biomeTints ? [] : undefined;\n for (const element of atlas.model.elements!) {\n for (const face of CUBE_FACES) {\n const data = element.faces[face];\n keep.push(data !== undefined);\n if (!data) continue;\n biomeTint?.push(Number(data.tintindex !== undefined && biomeTints?.[data.tintindex] !== undefined));\n const local = CUBE_FACES.findIndex(name => name === data.cullface);\n if (local < 0) {\n cullFaces.push(0);\n continue;\n }\n const normal = new Vector3(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);\n const world = CUBE_FACE_OFFSETS.findIndex(([x, y, z]) =>\n Math.abs(normal.x - x) < 1e-6 && Math.abs(normal.y - y) < 1e-6 && Math.abs(normal.z - z) < 1e-6);\n cullFaces.push(world < 0 ? 0 : 1 << world);\n }\n }\n geometry = compactQuads(geometry, keep);\n const template: SectionMeshTemplate = {\n geometry, atlas, cullFaces: new Uint8Array(cullFaces),\n biomeTint: biomeTint?.some(Boolean) ? new Uint8Array(biomeTint) : undefined,\n occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,\n layer: atlas.hasTranslucency ? 1 : 0, ambientOcclusion: (model as BlockModel).ambientocclusion !== false\n };\n this.templates.add(template);\n return template;\n });\n }\n\n /** Disposes prepared geometry and clears this world's template caches. */\n public clear(): void {\n for (const template of this.templates) template.geometry.dispose();\n this.templates.clear();\n this.states = new WeakMap();\n this.models = new WeakMap();\n this.fluidAtlases.clear();\n }\n}\n"],"mappings":"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","names":["source"]}
@@ -74,19 +74,12 @@ var WorldStreamer = class {
74
74
  this.loading?.controller.abort();
75
75
  return this.disposal ?? (this.disposal = Promise.resolve().then(async () => {
76
76
  await this.running?.catch(() => void 0);
77
- let failure;
78
- let failed = false;
79
- for (const position of this.owned.values()) {
80
- try {
81
- await this.unload(position);
82
- } catch (error) {
83
- failed = true;
84
- failure ?? (failure = error);
85
- }
77
+ try {
78
+ await this.unloadMany([...this.owned.values()]);
79
+ } finally {
80
+ this.missing.clear();
81
+ this.failed.clear();
86
82
  }
87
- this.missing.clear();
88
- this.failed.clear();
89
- if (failed) throw failure;
90
83
  }).catch((error) => {
91
84
  this.disposal = void 0;
92
85
  throw error;
@@ -101,9 +94,9 @@ var WorldStreamer = class {
101
94
  for (const [key, position2] of this.failed) {
102
95
  if (!this.within(position2, this.unloadRadius)) this.failed.delete(key);
103
96
  }
104
- const obsolete = [...this.owned.values()].find((position2) => !this.loaded.has(this.key(position2)) || !this.within(position2, this.unloadRadius));
105
- if (obsolete) {
106
- await this.unload(obsolete);
97
+ const obsolete = [...this.owned.values()].filter((position2) => !this.loaded.has(this.key(position2)) || !this.within(position2, this.unloadRadius));
98
+ if (obsolete.length) {
99
+ await this.unloadMany(obsolete);
107
100
  continue;
108
101
  }
109
102
  const position = this.desired.find((position2) => this.needsLoad(position2));
@@ -133,7 +126,7 @@ var WorldStreamer = class {
133
126
  this.loaded.set(this.key(position), position);
134
127
  } catch (error) {
135
128
  try {
136
- await this.unload(position);
129
+ await this.unloadMany([position]);
137
130
  } catch (cleanupError) {
138
131
  const failure = error instanceof Error ? error : new Error(String(error));
139
132
  try {
@@ -154,10 +147,18 @@ var WorldStreamer = class {
154
147
  const key = this.key(position);
155
148
  return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);
156
149
  }
157
- async unload(position) {
158
- await this.world.unloadChunkColumn(position.x, position.z);
159
- this.owned.delete(this.key(position));
160
- this.loaded.delete(this.key(position));
150
+ async unloadMany(positions) {
151
+ if (!positions.length) return;
152
+ try {
153
+ await this.world.unloadChunkColumns(positions.map(({ x, z }) => ({ x, z })));
154
+ } catch (error) {
155
+ for (const position of positions) this.loaded.delete(this.key(position));
156
+ throw error;
157
+ }
158
+ for (const position of positions) {
159
+ this.owned.delete(this.key(position));
160
+ this.loaded.delete(this.key(position));
161
+ }
161
162
  }
162
163
  validateChunk(chunk, position) {
163
164
  if (chunk.x !== position.x || chunk.z !== position.z) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/world/WorldStreamer.ts"],"sourcesContent":["import type { AnvilChunk } from \"./AnvilParser\";\nimport type { MineRenderWorld } from \"./MineRenderWorld\";\nimport type { WorldChunkSource } from \"./WorldChunkSource\";\n\nexport interface WorldStreamerOptions {\n /** Loads a square of chunk columns around the center. Integer from 0 to 32; defaults to 2. */\n loadRadius?: number;\n /** Retains loaded columns within this radius. Defaults to loadRadius + 1, capped at 32. */\n unloadRadius?: number;\n}\n\ninterface ChunkPosition {\n x: number;\n z: number;\n}\n\n/**\n * Loads nearby chunk columns in distance order and unloads distant columns.\n * Updates coalesce to the latest center and serialize world mutations. Use a dedicated world:\n * the streamer replaces columns it loads, and callers must await disposal before editing that world.\n * The source remains caller-owned. Lighting, biome tint, and LOD are not added by streaming.\n */\nexport class WorldStreamer {\n private readonly loadRadius: number;\n private readonly unloadRadius: number;\n private readonly loaded = new Map<string, ChunkPosition>();\n private readonly owned = new Map<string, ChunkPosition>();\n private readonly missing = new Map<string, ChunkPosition>();\n private readonly failed = new Map<string, ChunkPosition & { error: unknown }>();\n private desired: ChunkPosition[] = [];\n private center?: ChunkPosition;\n private loading?: { position: ChunkPosition; controller: AbortController };\n private running?: Promise<void>;\n private disposal?: Promise<void>;\n private disposed = false;\n\n constructor(\n private readonly world: Pick<MineRenderWorld<boolean>, \"placeChunk\" | \"unloadChunkColumn\">,\n private readonly source: WorldChunkSource,\n options: WorldStreamerOptions = {}\n ) {\n this.loadRadius = options.loadRadius ?? 2;\n this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);\n if (![this.loadRadius, this.unloadRadius].every(value => Number.isInteger(value) && value >= 0 && value <= 32)\n || this.unloadRadius < this.loadRadius) {\n throw new RangeError(\"Chunk radii must be integers from 0 to 32, with unloadRadius at least loadRadius\");\n }\n }\n\n /** Returns absolute coordinates of successfully placed columns, including retained columns. */\n public get loadedChunks(): readonly { x: number; z: number }[] {\n return [...this.loaded.values()].map(position => ({ ...position }));\n }\n\n /** Returns source failures retained within the unload radius. Failed columns do not block other loads. */\n public get failedChunks(): readonly { x: number; z: number; error: unknown }[] {\n return [...this.failed.values()].map(failure => ({ ...failure }));\n }\n\n /** Counts desired columns still waiting for a source read or placement, including the active column. */\n public get pendingChunks(): number {\n return this.desired.filter(position => this.needsLoad(position)).length;\n }\n\n /**\n * Updates the center in absolute chunk coordinates. Repeated calls share the active drain.\n * Source failures are recorded in failedChunks; other columns continue loading. Missing and failed\n * columns are remembered within the retention radius. Call retryFailedChunks to retry source failures.\n * Placement and unload failures reject the drain; call update again to retry those operations.\n * Reads outside the latest load radius receive an abort signal; placement finishes before unloading.\n * Sources should represent an unchanged world during streaming.\n */\n public async update(x: number, z: number): Promise<void> {\n if (this.disposed) throw new Error(\"WorldStreamer is disposed\");\n if (![x, z].every(value => Number.isSafeInteger(value)\n && Number.isSafeInteger(value - this.unloadRadius) && Number.isSafeInteger(value + this.unloadRadius))) {\n throw new RangeError(\"Chunk coordinates must be safe integers within the streaming radius\");\n }\n if (!this.center || this.center.x !== x || this.center.z !== z) {\n this.center = { x, z };\n this.desired = [];\n for (let dz = -this.loadRadius; dz <= this.loadRadius; dz++) {\n for (let dx = -this.loadRadius; dx <= this.loadRadius; dx++) {\n this.desired.push({ x: x + dx, z: z + dz });\n }\n }\n this.desired.sort((a, b) => (a.x - x) ** 2 + (a.z - z) ** 2 - (b.x - x) ** 2 - (b.z - z) ** 2);\n if (this.loading && !this.within(this.loading.position, this.loadRadius)) {\n this.loading.controller.abort();\n }\n }\n return this.running ??= Promise.resolve().then(() => this.drain());\n }\n\n /** Updates from a camera or view-center position in scene units. One chunk spans 256 scene units. */\n public async updatePosition(position: { x: number; z: number }): Promise<void> {\n if (![position.x, position.z].every(Number.isFinite)) throw new RangeError(\"Scene coordinates must be finite\");\n return this.update(Math.floor(position.x / 256), Math.floor(position.z / 256));\n }\n\n /** Forgets recorded source failures and retries desired columns at the latest center. */\n public async retryFailedChunks(): Promise<void> {\n if (this.disposed) throw new Error(\"WorldStreamer is disposed\");\n this.failed.clear();\n if (this.center) await this.update(this.center.x, this.center.z);\n }\n\n /** Aborts source reads, waits for active work, then unloads owned columns. The world and source remain caller-owned. */\n public dispose(): Promise<void> {\n this.disposed = true;\n this.desired = [];\n this.center = undefined;\n this.loading?.controller.abort();\n return this.disposal ??= Promise.resolve().then(async () => {\n await this.running?.catch(() => undefined);\n let failure: unknown;\n let failed = false;\n for (const position of this.owned.values()) {\n try {\n await this.unload(position);\n } catch (error) {\n failed = true;\n failure ??= error;\n }\n }\n this.missing.clear();\n this.failed.clear();\n if (failed) throw failure;\n }).catch(error => {\n this.disposal = undefined;\n throw error;\n });\n }\n\n private async drain(): Promise<void> {\n try {\n while (!this.disposed) {\n for (const [key, position] of this.missing) {\n if (!this.within(position, this.unloadRadius)) this.missing.delete(key);\n }\n for (const [key, position] of this.failed) {\n if (!this.within(position, this.unloadRadius)) this.failed.delete(key);\n }\n // Failed placements remain owned until cleanup succeeds, even inside the retained area.\n const obsolete = [...this.owned.values()].find(position =>\n !this.loaded.has(this.key(position)) || !this.within(position, this.unloadRadius));\n if (obsolete) {\n await this.unload(obsolete);\n continue;\n }\n const position = this.desired.find(position => this.needsLoad(position));\n if (!position) return;\n let chunk: AnvilChunk | undefined;\n const controller = new AbortController();\n this.loading = { position, controller };\n try {\n chunk = await this.source.getChunk(position.x, position.z, controller.signal);\n if (chunk) this.validateChunk(chunk, position);\n } catch (error) {\n if (!controller.signal.aborted && !this.disposed && this.within(position, this.loadRadius)) {\n this.failed.set(this.key(position), { ...position, error });\n }\n continue;\n } finally {\n this.loading = undefined;\n }\n if (controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) continue;\n if (!chunk) {\n this.missing.set(this.key(position), position);\n continue;\n }\n this.owned.set(this.key(position), position);\n try {\n await this.world.placeChunk(chunk);\n this.loaded.set(this.key(position), position);\n } catch (error) {\n try {\n await this.unload(position);\n } catch (cleanupError) {\n const failure = error instanceof Error ? error : new Error(String(error));\n try {\n Object.defineProperty(failure, \"cause\", { value: cleanupError, configurable: true });\n } catch {\n throw Object.assign(new Error(failure.message), { cause: cleanupError });\n }\n throw failure;\n }\n throw error;\n }\n }\n } finally {\n this.running = undefined;\n }\n }\n\n private needsLoad(position: ChunkPosition): boolean {\n const key = this.key(position);\n return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);\n }\n\n private async unload(position: ChunkPosition): Promise<void> {\n await this.world.unloadChunkColumn(position.x, position.z);\n this.owned.delete(this.key(position));\n this.loaded.delete(this.key(position));\n }\n\n private validateChunk(chunk: AnvilChunk, position: ChunkPosition): void {\n if (chunk.x !== position.x || chunk.z !== position.z) {\n throw new Error(`World source returned chunk ${chunk.x},${chunk.z} for ${position.x},${position.z}`);\n }\n }\n\n private within(position: ChunkPosition, radius: number): boolean {\n return !!this.center && Math.abs(position.x - this.center.x) <= radius && Math.abs(position.z - this.center.z) <= radius;\n }\n\n private key(position: ChunkPosition): string {\n return `${position.x},${position.z}`;\n }\n}\n"],"mappings":"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","names":["position"]}
1
+ {"version":3,"sources":["../../../../src/world/WorldStreamer.ts"],"sourcesContent":["import type { AnvilChunk } from \"./AnvilParser\";\nimport type { MineRenderWorld } from \"./MineRenderWorld\";\nimport type { WorldChunkSource } from \"./WorldChunkSource\";\n\nexport interface WorldStreamerOptions {\n /** Loads a square of chunk columns around the center. Integer from 0 to 32; defaults to 2. */\n loadRadius?: number;\n /** Retains loaded columns within this radius. Defaults to loadRadius + 1, capped at 32. */\n unloadRadius?: number;\n}\n\ninterface ChunkPosition {\n x: number;\n z: number;\n}\n\n/**\n * Loads nearby chunk columns in distance order and unloads distant columns.\n * Updates coalesce to the latest center and serialize world mutations. Use a dedicated world:\n * the streamer replaces columns it loads, and callers must await disposal before editing that world.\n * The source remains caller-owned. Lighting, biome tint, and LOD are not added by streaming.\n */\nexport class WorldStreamer {\n private readonly loadRadius: number;\n private readonly unloadRadius: number;\n private readonly loaded = new Map<string, ChunkPosition>();\n private readonly owned = new Map<string, ChunkPosition>();\n private readonly missing = new Map<string, ChunkPosition>();\n private readonly failed = new Map<string, ChunkPosition & { error: unknown }>();\n private desired: ChunkPosition[] = [];\n private center?: ChunkPosition;\n private loading?: { position: ChunkPosition; controller: AbortController };\n private running?: Promise<void>;\n private disposal?: Promise<void>;\n private disposed = false;\n\n constructor(\n private readonly world: Pick<MineRenderWorld<boolean>, \"placeChunk\" | \"unloadChunkColumns\">,\n private readonly source: WorldChunkSource,\n options: WorldStreamerOptions = {}\n ) {\n this.loadRadius = options.loadRadius ?? 2;\n this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);\n if (![this.loadRadius, this.unloadRadius].every(value => Number.isInteger(value) && value >= 0 && value <= 32)\n || this.unloadRadius < this.loadRadius) {\n throw new RangeError(\"Chunk radii must be integers from 0 to 32, with unloadRadius at least loadRadius\");\n }\n }\n\n /** Returns absolute coordinates of successfully placed columns, including retained columns. */\n public get loadedChunks(): readonly { x: number; z: number }[] {\n return [...this.loaded.values()].map(position => ({ ...position }));\n }\n\n /** Returns source failures retained within the unload radius. Failed columns do not block other loads. */\n public get failedChunks(): readonly { x: number; z: number; error: unknown }[] {\n return [...this.failed.values()].map(failure => ({ ...failure }));\n }\n\n /** Counts desired columns still waiting for a source read or placement, including the active column. */\n public get pendingChunks(): number {\n return this.desired.filter(position => this.needsLoad(position)).length;\n }\n\n /**\n * Updates the center in absolute chunk coordinates. Repeated calls share the active drain.\n * Source failures are recorded in failedChunks; other columns continue loading. Missing and failed\n * columns are remembered within the retention radius. Call retryFailedChunks to retry source failures.\n * Placement and unload failures reject the drain; call update again to retry those operations.\n * Reads outside the latest load radius receive an abort signal; placement finishes before unloading.\n * Sources should represent an unchanged world during streaming.\n */\n public async update(x: number, z: number): Promise<void> {\n if (this.disposed) throw new Error(\"WorldStreamer is disposed\");\n if (![x, z].every(value => Number.isSafeInteger(value)\n && Number.isSafeInteger(value - this.unloadRadius) && Number.isSafeInteger(value + this.unloadRadius))) {\n throw new RangeError(\"Chunk coordinates must be safe integers within the streaming radius\");\n }\n if (!this.center || this.center.x !== x || this.center.z !== z) {\n this.center = { x, z };\n this.desired = [];\n for (let dz = -this.loadRadius; dz <= this.loadRadius; dz++) {\n for (let dx = -this.loadRadius; dx <= this.loadRadius; dx++) {\n this.desired.push({ x: x + dx, z: z + dz });\n }\n }\n this.desired.sort((a, b) => (a.x - x) ** 2 + (a.z - z) ** 2 - (b.x - x) ** 2 - (b.z - z) ** 2);\n if (this.loading && !this.within(this.loading.position, this.loadRadius)) {\n this.loading.controller.abort();\n }\n }\n return this.running ??= Promise.resolve().then(() => this.drain());\n }\n\n /** Updates from a camera or view-center position in scene units. One chunk spans 256 scene units. */\n public async updatePosition(position: { x: number; z: number }): Promise<void> {\n if (![position.x, position.z].every(Number.isFinite)) throw new RangeError(\"Scene coordinates must be finite\");\n return this.update(Math.floor(position.x / 256), Math.floor(position.z / 256));\n }\n\n /** Forgets recorded source failures and retries desired columns at the latest center. */\n public async retryFailedChunks(): Promise<void> {\n if (this.disposed) throw new Error(\"WorldStreamer is disposed\");\n this.failed.clear();\n if (this.center) await this.update(this.center.x, this.center.z);\n }\n\n /** Aborts source reads, waits for active work, then unloads owned columns. The world and source remain caller-owned. */\n public dispose(): Promise<void> {\n this.disposed = true;\n this.desired = [];\n this.center = undefined;\n this.loading?.controller.abort();\n return this.disposal ??= Promise.resolve().then(async () => {\n await this.running?.catch(() => undefined);\n try {\n await this.unloadMany([...this.owned.values()]);\n } finally {\n this.missing.clear();\n this.failed.clear();\n }\n }).catch(error => {\n this.disposal = undefined;\n throw error;\n });\n }\n\n private async drain(): Promise<void> {\n try {\n while (!this.disposed) {\n for (const [key, position] of this.missing) {\n if (!this.within(position, this.unloadRadius)) this.missing.delete(key);\n }\n for (const [key, position] of this.failed) {\n if (!this.within(position, this.unloadRadius)) this.failed.delete(key);\n }\n // Failed placements remain owned until cleanup succeeds, even inside the retained area.\n const obsolete = [...this.owned.values()].filter(position =>\n !this.loaded.has(this.key(position)) || !this.within(position, this.unloadRadius));\n if (obsolete.length) {\n await this.unloadMany(obsolete);\n continue;\n }\n const position = this.desired.find(position => this.needsLoad(position));\n if (!position) return;\n let chunk: AnvilChunk | undefined;\n const controller = new AbortController();\n this.loading = { position, controller };\n try {\n chunk = await this.source.getChunk(position.x, position.z, controller.signal);\n if (chunk) this.validateChunk(chunk, position);\n } catch (error) {\n if (!controller.signal.aborted && !this.disposed && this.within(position, this.loadRadius)) {\n this.failed.set(this.key(position), { ...position, error });\n }\n continue;\n } finally {\n this.loading = undefined;\n }\n if (controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) continue;\n if (!chunk) {\n this.missing.set(this.key(position), position);\n continue;\n }\n this.owned.set(this.key(position), position);\n try {\n await this.world.placeChunk(chunk);\n this.loaded.set(this.key(position), position);\n } catch (error) {\n try {\n await this.unloadMany([position]);\n } catch (cleanupError) {\n const failure = error instanceof Error ? error : new Error(String(error));\n try {\n Object.defineProperty(failure, \"cause\", { value: cleanupError, configurable: true });\n } catch {\n throw Object.assign(new Error(failure.message), { cause: cleanupError });\n }\n throw failure;\n }\n throw error;\n }\n }\n } finally {\n this.running = undefined;\n }\n }\n\n private needsLoad(position: ChunkPosition): boolean {\n const key = this.key(position);\n return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);\n }\n\n private async unloadMany(positions: readonly ChunkPosition[]): Promise<void> {\n if (!positions.length) return;\n try {\n await this.world.unloadChunkColumns(positions.map(({ x, z }) => ({ x, z })));\n } catch (error) {\n // A rejected batch may have removed any subset; retry every owner before reloading it.\n for (const position of positions) this.loaded.delete(this.key(position));\n throw error;\n }\n for (const position of positions) {\n this.owned.delete(this.key(position));\n this.loaded.delete(this.key(position));\n }\n }\n\n private validateChunk(chunk: AnvilChunk, position: ChunkPosition): void {\n if (chunk.x !== position.x || chunk.z !== position.z) {\n throw new Error(`World source returned chunk ${chunk.x},${chunk.z} for ${position.x},${position.z}`);\n }\n }\n\n private within(position: ChunkPosition, radius: number): boolean {\n return !!this.center && Math.abs(position.x - this.center.x) <= radius && Math.abs(position.z - this.center.z) <= radius;\n }\n\n private key(position: ChunkPosition): string {\n return `${position.x},${position.z}`;\n }\n}\n"],"mappings":"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","names":["position"]}
@@ -1,5 +1,5 @@
1
1
  // src/world/_culling/BlockCulling.ts
2
- import { DEFAULT_NAMESPACE } from "../../assets/Assets.mjs";
2
+ import { DEFAULT_NAMESPACE } from "../../assets/AssetDefaults.mjs";
3
3
  import { MineRenderData } from "../../assets/MineRenderData.mjs";
4
4
  import { CUBE_FACES, CubeFace } from "../../CubeFace.mjs";
5
5
  async function loadBlockCulling(root) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/world/_culling/BlockCulling.ts"],"sourcesContent":["import { DEFAULT_NAMESPACE } from \"../../assets/Assets\";\nimport { MineRenderData, MineRenderDatasets } from \"../../assets/MineRenderData\";\nimport { CUBE_FACES, CubeFace } from \"../../CubeFace\";\nimport type { Block } from \"../../model/block/Block\";\nimport type { Maybe } from \"../../util/util\";\n\nexport type BlockCullingData = MineRenderDatasets[\"blockCulling\"];\nexport type BlockCullingRule = BlockCullingData[\"profiles\"][number];\ntype CullingBlock = Pick<Block, \"type\" | \"properties\">;\n\n/** Loads vanilla's neighbor face rules, if the selected version publishes them. */\nexport async function loadBlockCulling(root?: string): Promise<Maybe<BlockCullingData>> {\n const { manifest } = await MineRenderData.resolve(root);\n return manifest.datasets.blockCulling ? MineRenderData.get(\"blockCulling\", root) : undefined;\n}\n\n/** The rule of a block type, or `undefined` when the block hides no faces this way. */\nexport function blockCullingRule(data: BlockCullingData, type: string): Maybe<BlockCullingRule> {\n if (!type.includes(\":\")) type = `${DEFAULT_NAMESPACE}:${type}`;\n return Object.prototype.hasOwnProperty.call(data.blocks, type) ? data.profiles[data.blocks[type]] : undefined;\n}\n\n/** `face` indexes CUBE_FACES. Occlusion by opaque cubes is a separate check. */\nexport function skipsBlockFace(data: BlockCullingData, rule: BlockCullingRule, block: CullingBlock, neighbor: CullingBlock, face: number): boolean {\n const type = block.type.includes(\":\") ? block.type : `${DEFAULT_NAMESPACE}:${block.type}`;\n const other = neighbor.type.includes(\":\") ? neighbor.type : `${DEFAULT_NAMESPACE}:${neighbor.type}`;\n const same = type === other;\n const vertical = CUBE_FACES[face] === CubeFace.UP || CUBE_FACES[face] === CubeFace.DOWN;\n switch (rule.type) {\n case \"same_block\": return same;\n case \"same_block_vertical\": return same && vertical;\n case \"connected\": {\n if (vertical) return same;\n const group = rule.group ? data.groups?.[rule.group] : undefined;\n return (same || !!group?.includes(type) && group.includes(other))\n && String(block.properties?.[CUBE_FACES[face]]) === \"true\"\n && String(neighbor.properties?.[CUBE_FACES[face ^ 1]]) === \"true\";\n }\n default: return false;\n }\n}\n"],"mappings":";AAAA,SAAS,yBAAyB;AAClC,SAAS,sBAA0C;AACnD,SAAS,YAAY,gBAAgB;AASrC,eAAsB,iBAAiB,MAAiD;AACpF,QAAM,EAAE,SAAS,IAAI,MAAM,eAAe,QAAQ,IAAI;AACtD,SAAO,SAAS,SAAS,eAAe,eAAe,IAAI,gBAAgB,IAAI,IAAI;AACvF;AAGO,SAAS,iBAAiB,MAAwB,MAAuC;AAC5F,MAAI,CAAC,KAAK,SAAS,GAAG,EAAG,QAAO,GAAG,iBAAiB,IAAI,IAAI;AAC5D,SAAO,OAAO,UAAU,eAAe,KAAK,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS,KAAK,OAAO,IAAI,CAAC,IAAI;AACxG;AAGO,SAAS,eAAe,MAAwB,MAAwB,OAAqB,UAAwB,MAAuB;AAC/I,QAAM,OAAO,MAAM,KAAK,SAAS,GAAG,IAAI,MAAM,OAAO,GAAG,iBAAiB,IAAI,MAAM,IAAI;AACvF,QAAM,QAAQ,SAAS,KAAK,SAAS,GAAG,IAAI,SAAS,OAAO,GAAG,iBAAiB,IAAI,SAAS,IAAI;AACjG,QAAM,OAAO,SAAS;AACtB,QAAM,WAAW,WAAW,IAAI,MAAM,SAAS,MAAM,WAAW,IAAI,MAAM,SAAS;AACnF,UAAQ,KAAK,MAAM;AAAA,IACf,KAAK;AAAc,aAAO;AAAA,IAC1B,KAAK;AAAuB,aAAO,QAAQ;AAAA,IAC3C,KAAK,aAAa;AACd,UAAI,SAAU,QAAO;AACrB,YAAM,QAAQ,KAAK,QAAQ,KAAK,SAAS,KAAK,KAAK,IAAI;AACvD,cAAQ,QAAQ,CAAC,CAAC,OAAO,SAAS,IAAI,KAAK,MAAM,SAAS,KAAK,MACxD,OAAO,MAAM,aAAa,WAAW,IAAI,CAAC,CAAC,MAAM,UACjD,OAAO,SAAS,aAAa,WAAW,OAAO,CAAC,CAAC,CAAC,MAAM;AAAA,IACnE;AAAA,IACA;AAAS,aAAO;AAAA,EACpB;AACJ;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/world/_culling/BlockCulling.ts"],"sourcesContent":["import { DEFAULT_NAMESPACE } from \"../../assets/AssetDefaults\";\nimport { MineRenderData, MineRenderDatasets } from \"../../assets/MineRenderData\";\nimport { CUBE_FACES, CubeFace } from \"../../CubeFace\";\nimport type { Block } from \"../../model/block/Block\";\nimport type { Maybe } from \"../../util/util\";\n\nexport type BlockCullingData = MineRenderDatasets[\"blockCulling\"];\nexport type BlockCullingRule = BlockCullingData[\"profiles\"][number];\ntype CullingBlock = Pick<Block, \"type\" | \"properties\">;\n\n/** Loads vanilla's neighbor face rules, if the selected version publishes them. */\nexport async function loadBlockCulling(root?: string): Promise<Maybe<BlockCullingData>> {\n const { manifest } = await MineRenderData.resolve(root);\n return manifest.datasets.blockCulling ? MineRenderData.get(\"blockCulling\", root) : undefined;\n}\n\n/** The rule of a block type, or `undefined` when the block hides no faces this way. */\nexport function blockCullingRule(data: BlockCullingData, type: string): Maybe<BlockCullingRule> {\n if (!type.includes(\":\")) type = `${DEFAULT_NAMESPACE}:${type}`;\n return Object.prototype.hasOwnProperty.call(data.blocks, type) ? data.profiles[data.blocks[type]] : undefined;\n}\n\n/** `face` indexes CUBE_FACES. Occlusion by opaque cubes is a separate check. */\nexport function skipsBlockFace(data: BlockCullingData, rule: BlockCullingRule, block: CullingBlock, neighbor: CullingBlock, face: number): boolean {\n const type = block.type.includes(\":\") ? block.type : `${DEFAULT_NAMESPACE}:${block.type}`;\n const other = neighbor.type.includes(\":\") ? neighbor.type : `${DEFAULT_NAMESPACE}:${neighbor.type}`;\n const same = type === other;\n const vertical = CUBE_FACES[face] === CubeFace.UP || CUBE_FACES[face] === CubeFace.DOWN;\n switch (rule.type) {\n case \"same_block\": return same;\n case \"same_block_vertical\": return same && vertical;\n case \"connected\": {\n if (vertical) return same;\n const group = rule.group ? data.groups?.[rule.group] : undefined;\n return (same || !!group?.includes(type) && group.includes(other))\n && String(block.properties?.[CUBE_FACES[face]]) === \"true\"\n && String(neighbor.properties?.[CUBE_FACES[face ^ 1]]) === \"true\";\n }\n default: return false;\n }\n}\n"],"mappings":";AAAA,SAAS,yBAAyB;AAClC,SAAS,sBAA0C;AACnD,SAAS,YAAY,gBAAgB;AASrC,eAAsB,iBAAiB,MAAiD;AACpF,QAAM,EAAE,SAAS,IAAI,MAAM,eAAe,QAAQ,IAAI;AACtD,SAAO,SAAS,SAAS,eAAe,eAAe,IAAI,gBAAgB,IAAI,IAAI;AACvF;AAGO,SAAS,iBAAiB,MAAwB,MAAuC;AAC5F,MAAI,CAAC,KAAK,SAAS,GAAG,EAAG,QAAO,GAAG,iBAAiB,IAAI,IAAI;AAC5D,SAAO,OAAO,UAAU,eAAe,KAAK,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS,KAAK,OAAO,IAAI,CAAC,IAAI;AACxG;AAGO,SAAS,eAAe,MAAwB,MAAwB,OAAqB,UAAwB,MAAuB;AAC/I,QAAM,OAAO,MAAM,KAAK,SAAS,GAAG,IAAI,MAAM,OAAO,GAAG,iBAAiB,IAAI,MAAM,IAAI;AACvF,QAAM,QAAQ,SAAS,KAAK,SAAS,GAAG,IAAI,SAAS,OAAO,GAAG,iBAAiB,IAAI,SAAS,IAAI;AACjG,QAAM,OAAO,SAAS;AACtB,QAAM,WAAW,WAAW,IAAI,MAAM,SAAS,MAAM,WAAW,IAAI,MAAM,SAAS;AACnF,UAAQ,KAAK,MAAM;AAAA,IACf,KAAK;AAAc,aAAO;AAAA,IAC1B,KAAK;AAAuB,aAAO,QAAQ;AAAA,IAC3C,KAAK,aAAa;AACd,UAAI,SAAU,QAAO;AACrB,YAAM,QAAQ,KAAK,QAAQ,KAAK,SAAS,KAAK,KAAK,IAAI;AACvD,cAAQ,QAAQ,CAAC,CAAC,OAAO,SAAS,IAAI,KAAK,MAAM,SAAS,KAAK,MACxD,OAAO,MAAM,aAAa,WAAW,IAAI,CAAC,CAAC,MAAM,UACjD,OAAO,SAAS,aAAa,WAAW,OAAO,CAAC,CAAC,CAAC,MAAM;AAAA,IACnE;AAAA,IACA;AAAS,aAAO;AAAA,EACpB;AACJ;","names":[]}
@@ -6,6 +6,8 @@ import { resolveSavedEntity } from "./SavedEntities.mjs";
6
6
  import { BannerPatterns } from "../../assets/BannerPatterns.mjs";
7
7
  import { Vector3 } from "three";
8
8
  import { hitNormal, pickableMeshes, visibleHits } from "../_picking/Picking.mjs";
9
+ import { logger } from "../../util/log.mjs";
10
+ var log = logger("WorldEntities");
9
11
  var WorldEntities = class {
10
12
  constructor(scene) {
11
13
  this.scene = scene;
@@ -74,7 +76,7 @@ var WorldEntities = class {
74
76
  placement.owner.set(object, { type: key.toNamespacedString(), position: placement.position });
75
77
  this.scene.add(object);
76
78
  } catch (error) {
77
- console.warn(`Could not draw saved entity ${placement.key.toNamespacedString()}, skipping it`, error);
79
+ log.warn(`Could not draw saved entity ${placement.key.toNamespacedString()}, skipping it`, error);
78
80
  } finally {
79
81
  if (object && !placement.owner.has(object)) object.dispose();
80
82
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/world/_entities/WorldEntities.ts"],"sourcesContent":["import { Renames } from \"../../assets/Renames\";\nimport { Entities } from \"../../assets/Entities\";\nimport { EntityObject } from \"../../entity/scene/EntityObject\";\nimport type { MultiBlockEntity } from \"../../model/multiblock/MultiBlockStructure\";\nimport type { MineRenderScene } from \"../../renderer/MineRenderScene\";\nimport { resolveSavedEntity } from \"./SavedEntities\";\nimport { BannerPatterns } from \"../../assets/BannerPatterns\";\nimport { Mesh, Raycaster, Vector3 } from \"three\";\nimport type { WorldEntityIntersection } from \"../WorldEntityIntersection\";\nimport { hitNormal, pickableMeshes, visibleHits } from \"../_picking/Picking\";\n\n/** Owns static entity placements independently of the world's block sections. */\nexport class WorldEntities {\n private readonly columns = new Map<string, Map<EntityObject, { type: string; position: MultiBlockEntity[\"position\"] }>>();\n\n constructor(private readonly scene: MineRenderScene) {}\n\n /** Finds the nearest visible triangle belonging to an entity owned by this world. */\n raycast(raycaster: Raycaster): WorldEntityIntersection | undefined {\n const owners = new Map<Mesh, { object: EntityObject; type: string; position: MultiBlockEntity[\"position\"] }>();\n for (const column of this.columns.values()) {\n for (const [object, placement] of column) {\n for (const mesh of pickableMeshes(object, this.scene, raycaster)) {\n owners.set(mesh, { object, ...placement });\n }\n }\n }\n for (const hit of visibleHits(raycaster, [...owners.keys()])) {\n const mesh = hit.object as Mesh;\n const owner = owners.get(mesh)!;\n return {\n type: owner.type,\n position: new Vector3(...owner.position),\n object: owner.object,\n point: hit.point.clone(),\n normal: hitNormal(hit),\n distance: hit.distance\n };\n }\n return undefined;\n }\n\n /** Captures column ownership before asynchronous block or asset loading starts. */\n prepare(entities: readonly MultiBlockEntity[] = [], column?: [number, number]): () => Promise<void> {\n const placements = entities.flatMap(entity => {\n const resolved = resolveSavedEntity(entity);\n if (!resolved) return [];\n const position = [...entity.position] as MultiBlockEntity[\"position\"];\n const key = column?.join(\",\") ?? `${Math.floor(position[0] / 16)},${Math.floor(position[2] / 16)}`;\n let owner = this.columns.get(key);\n if (!owner) this.columns.set(key, owner = new Map());\n return [{ ...resolved, position, column: key, owner }];\n });\n return async () => {\n await Promise.all(placements.map(async placement => {\n const current = () => this.columns.get(placement.column) === placement.owner;\n let object: EntityObject | undefined;\n try {\n if (!current()) return;\n const models = await Entities.getEntityList();\n // An ID the entity dataset does not list may be an older name for one it lists.\n const renamed = models.includes(placement.key.path) ? undefined : await Renames.key(\"entities\", placement.key);\n const key = renamed && models.includes(renamed.path) ? renamed : placement.key;\n if (!models.includes(key.path) || !current()) return;\n const model = await Entities.getEntity(key, placement.texture, { when: placement.when });\n if (!model || !current()) return;\n let tints = placement.tints;\n if (placement.woolDye) {\n const dye = (await BannerPatterns.getColors(placement.key.root))[placement.woolDye];\n // Sheep darken each dye channel; white wool has its own fixed shade.\n const tint = placement.woolDye === \"white\" ? 0xe6e6e6 : (Math.floor((dye >> 16 & 255) * 0.75) << 16)\n | (Math.floor((dye >> 8 & 255) * 0.75) << 8) | Math.floor((dye & 255) * 0.75);\n tints = { ...tints, wool_color: tint };\n }\n if (!current()) return;\n object = new EntityObject(model, { instanceMeshes: false, tints });\n object.scene = this.scene;\n // Block geometry is centered on integer coordinates; saved positions use block corners.\n object.position.fromArray(placement.position).multiplyScalar(16).addScalar(-8);\n object.rotation.y = placement.yaw;\n await object.init();\n if (!current()) return;\n placement.owner.set(object, { type: key.toNamespacedString(), position: placement.position });\n this.scene.add(object);\n } catch (error) {\n console.warn(`Could not draw saved entity ${placement.key.toNamespacedString()}, skipping it`, error);\n } finally {\n if (object && !placement.owner.has(object)) object.dispose();\n }\n }));\n };\n }\n\n clearColumn(x: number, z: number): void {\n const key = `${x},${z}`;\n const objects = this.columns.get(key);\n this.columns.delete(key);\n if (!objects) return;\n for (const object of objects.keys()) {\n object.removeFromScene();\n object.dispose();\n }\n objects.clear();\n }\n\n clear(): void {\n for (const key of this.columns.keys()) {\n const [x, z] = key.split(\",\").map(Number);\n this.clearColumn(x, z);\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../../src/world/_entities/WorldEntities.ts"],"sourcesContent":["import { Renames } from \"../../assets/Renames\";\nimport { Entities } from \"../../assets/Entities\";\nimport { EntityObject } from \"../../entity/scene/EntityObject\";\nimport type { MultiBlockEntity } from \"../../model/multiblock/MultiBlockStructure\";\nimport type { MineRenderScene } from \"../../renderer/MineRenderScene\";\nimport { resolveSavedEntity } from \"./SavedEntities\";\nimport { BannerPatterns } from \"../../assets/BannerPatterns\";\nimport { Mesh, Raycaster, Vector3 } from \"three\";\nimport type { WorldEntityIntersection } from \"../WorldEntityIntersection\";\nimport { hitNormal, pickableMeshes, visibleHits } from \"../_picking/Picking\";\nimport { logger } from \"../../util/log\";\n\nconst log = logger(\"WorldEntities\");\n\n/** Owns static entity placements independently of the world's block sections. */\nexport class WorldEntities {\n private readonly columns = new Map<string, Map<EntityObject, { type: string; position: MultiBlockEntity[\"position\"] }>>();\n\n constructor(private readonly scene: MineRenderScene) {}\n\n /** Finds the nearest visible triangle belonging to an entity owned by this world. */\n raycast(raycaster: Raycaster): WorldEntityIntersection | undefined {\n const owners = new Map<Mesh, { object: EntityObject; type: string; position: MultiBlockEntity[\"position\"] }>();\n for (const column of this.columns.values()) {\n for (const [object, placement] of column) {\n for (const mesh of pickableMeshes(object, this.scene, raycaster)) {\n owners.set(mesh, { object, ...placement });\n }\n }\n }\n for (const hit of visibleHits(raycaster, [...owners.keys()])) {\n const mesh = hit.object as Mesh;\n const owner = owners.get(mesh)!;\n return {\n type: owner.type,\n position: new Vector3(...owner.position),\n object: owner.object,\n point: hit.point.clone(),\n normal: hitNormal(hit),\n distance: hit.distance\n };\n }\n return undefined;\n }\n\n /** Captures column ownership before asynchronous block or asset loading starts. */\n prepare(entities: readonly MultiBlockEntity[] = [], column?: [number, number]): () => Promise<void> {\n const placements = entities.flatMap(entity => {\n const resolved = resolveSavedEntity(entity);\n if (!resolved) return [];\n const position = [...entity.position] as MultiBlockEntity[\"position\"];\n const key = column?.join(\",\") ?? `${Math.floor(position[0] / 16)},${Math.floor(position[2] / 16)}`;\n let owner = this.columns.get(key);\n if (!owner) this.columns.set(key, owner = new Map());\n return [{ ...resolved, position, column: key, owner }];\n });\n return async () => {\n await Promise.all(placements.map(async placement => {\n const current = () => this.columns.get(placement.column) === placement.owner;\n let object: EntityObject | undefined;\n try {\n if (!current()) return;\n const models = await Entities.getEntityList();\n // An ID the entity dataset does not list may be an older name for one it lists.\n const renamed = models.includes(placement.key.path) ? undefined : await Renames.key(\"entities\", placement.key);\n const key = renamed && models.includes(renamed.path) ? renamed : placement.key;\n if (!models.includes(key.path) || !current()) return;\n const model = await Entities.getEntity(key, placement.texture, { when: placement.when });\n if (!model || !current()) return;\n let tints = placement.tints;\n if (placement.woolDye) {\n const dye = (await BannerPatterns.getColors(placement.key.root))[placement.woolDye];\n // Sheep darken each dye channel; white wool has its own fixed shade.\n const tint = placement.woolDye === \"white\" ? 0xe6e6e6 : (Math.floor((dye >> 16 & 255) * 0.75) << 16)\n | (Math.floor((dye >> 8 & 255) * 0.75) << 8) | Math.floor((dye & 255) * 0.75);\n tints = { ...tints, wool_color: tint };\n }\n if (!current()) return;\n object = new EntityObject(model, { instanceMeshes: false, tints });\n object.scene = this.scene;\n // Block geometry is centered on integer coordinates; saved positions use block corners.\n object.position.fromArray(placement.position).multiplyScalar(16).addScalar(-8);\n object.rotation.y = placement.yaw;\n await object.init();\n if (!current()) return;\n placement.owner.set(object, { type: key.toNamespacedString(), position: placement.position });\n this.scene.add(object);\n } catch (error) {\n log.warn(`Could not draw saved entity ${placement.key.toNamespacedString()}, skipping it`, error);\n } finally {\n if (object && !placement.owner.has(object)) object.dispose();\n }\n }));\n };\n }\n\n clearColumn(x: number, z: number): void {\n const key = `${x},${z}`;\n const objects = this.columns.get(key);\n this.columns.delete(key);\n if (!objects) return;\n for (const object of objects.keys()) {\n object.removeFromScene();\n object.dispose();\n }\n objects.clear();\n }\n\n clear(): void {\n for (const key of this.columns.keys()) {\n const [x, z] = key.split(\",\").map(Number);\n this.clearColumn(x, z);\n }\n }\n}\n"],"mappings":"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","names":[]}