@nodecraft/rendermite 1.0.4 → 1.0.6

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 (121) hide show
  1. package/README.md +5 -0
  2. package/assets/README.md +3 -0
  3. package/assets/mod-tints/README.md +38 -0
  4. package/assets/mod-tints/advanced_ae/items/quantum_boots.json +16 -0
  5. package/assets/mod-tints/advanced_ae/items/quantum_chestplate.json +16 -0
  6. package/assets/mod-tints/advanced_ae/items/quantum_helmet.json +16 -0
  7. package/assets/mod-tints/advanced_ae/items/quantum_leggings.json +16 -0
  8. package/dist/atlas/registry.d.ts +3 -3
  9. package/dist/atlas/sources.d.ts +1 -1
  10. package/dist/{blend-Qvrn6YgK.d.ts → blend-CyDIxCne.d.ts} +1 -1
  11. package/dist/{builtin-C8GZEN40.js → builtin-caYjESh0.js} +5 -1
  12. package/dist/builtin.d.ts +3 -1
  13. package/dist/builtin.js +2 -2
  14. package/dist/cache-Cr2ON3RQ.js +66 -0
  15. package/dist/cli.js +40 -7
  16. package/dist/compose-CQCb2LVv.js +104 -0
  17. package/dist/descriptor-DWebtkI1.js +26 -0
  18. package/dist/{discovery-D89pVVJA.d.ts → discovery-wiJBGryo.d.ts} +1 -1
  19. package/dist/dynamic/generators.d.ts +1 -1
  20. package/dist/dynamic/generators.js +1 -1
  21. package/dist/{encode-3u49i-eD.js → encode-D6dQu2dJ.js} +16 -4
  22. package/dist/{encode-C5kdAm45.d.ts → encode-DaNQF28M.d.ts} +1 -1
  23. package/dist/{entity-fallback-CdCVEG6X.js → entity-fallback-BRCeGKBT.js} +1 -1
  24. package/dist/{face-CpVdnf-w.d.ts → face-BIYg14TE.d.ts} +10 -4
  25. package/dist/{face-D-N1amV4.js → face-DmCiHUd5.js} +23 -10
  26. package/dist/{generators-CcVRG30y.js → generators-BlG4KmtP.js} +1 -1
  27. package/dist/{generators-CCB8h0mP.d.ts → generators-DhRov4RB.d.ts} +1 -1
  28. package/dist/{geo-u8rndjLA.js → geo-Diky41Tw.js} +7 -4
  29. package/dist/index.d.ts +14 -14
  30. package/dist/index.js +13 -13
  31. package/dist/{item-styles-DJ3LWZS2.js → item-styles-upgEO1yP.js} +1 -1
  32. package/dist/items/discovery.d.ts +1 -1
  33. package/dist/items/discovery.js +1 -1
  34. package/dist/{jar-6T3xdgaL.d.ts → jar-CFkA-VgT.d.ts} +2 -1
  35. package/dist/{jar-Bt3i0ls4.js → jar-D9z-X7tA.js} +12 -12
  36. package/dist/{legacy-reshape-CewShfF3.js → legacy-reshape-C05SfAPR.js} +1 -1
  37. package/dist/{loader-A-BZa8J4.js → loader-B8NJO7pR.js} +12 -3
  38. package/dist/{loader-CBzn3Koc.js → loader-CfXMxcEV.js} +37 -7
  39. package/dist/{loader-C-6QN3xv.d.ts → loader-D22zZME7.d.ts} +2 -2
  40. package/dist/{loader-TFFNV4Jl.d.ts → loader-Myy22TDL.d.ts} +8 -2
  41. package/dist/{manager-dfI7b9N4.js → manager-CkletR5K.js} +29 -2
  42. package/dist/{manager-CoZaeQTu.d.ts → manager-Cv1NeFyF.d.ts} +28 -1
  43. package/dist/models/loader.d.ts +1 -1
  44. package/dist/models/loader.js +1 -1
  45. package/dist/mods/prepare.d.ts +1 -1
  46. package/dist/mods/prepare.js +1 -1
  47. package/dist/mods/synthesize.d.ts +2 -2
  48. package/dist/mods/synthesize.js +1 -1
  49. package/dist/mods/tints.d.ts +28 -0
  50. package/dist/mods/tints.js +2 -0
  51. package/dist/output/encode.d.ts +1 -1
  52. package/dist/output/encode.js +1 -1
  53. package/dist/output/item-styles.js +1 -1
  54. package/dist/pipeline-BIk1RRIp.js +221 -0
  55. package/dist/pipeline-zq-yU3Te.d.ts +58 -0
  56. package/dist/pool-CKGp1qup.js +131 -0
  57. package/dist/{prepare-Bvc5ve2m.js → prepare-BESRrh6K.js} +33 -5
  58. package/dist/{prepare-DnnGGv9k.d.ts → prepare-CPxO0kKz.d.ts} +8 -1
  59. package/dist/{prepare-DROxBZp1.js → prepare-Cc8QZ3j0.js} +4 -4
  60. package/dist/{prepare-wfWtaz_X.d.ts → prepare-Cs6QDqie.d.ts} +1 -1
  61. package/dist/{rasterizer-BHWDrmGz.js → rasterizer-76qO8TDh.js} +42 -4
  62. package/dist/{rasterizer-Dh35qXbn.d.ts → rasterizer-yqJkicm1.d.ts} +21 -2
  63. package/dist/render/blend.d.ts +1 -1
  64. package/dist/render/compose.d.ts +2 -2
  65. package/dist/render/compose.js +1 -1
  66. package/dist/render/entity-fallback.d.ts +6 -6
  67. package/dist/render/entity-fallback.js +1 -1
  68. package/dist/render/face.d.ts +2 -2
  69. package/dist/render/face.js +1 -1
  70. package/dist/render/fluid-bucket.d.ts +2 -2
  71. package/dist/render/geo.d.ts +4 -4
  72. package/dist/render/geo.js +1 -1
  73. package/dist/render/legacy-tints.d.ts +2 -2
  74. package/dist/render/obj.d.ts +5 -5
  75. package/dist/render/obj.js +1 -1
  76. package/dist/render/pipeline.d.ts +2 -0
  77. package/dist/render/pipeline.js +2 -0
  78. package/dist/render/rasterizer.d.ts +2 -2
  79. package/dist/render/rasterizer.js +2 -2
  80. package/dist/render/renderer.d.ts +2 -2
  81. package/dist/render/renderer.js +1 -1
  82. package/dist/render/tints.d.ts +1 -1
  83. package/dist/{renderer-BeOinVC8.js → renderer-CjT19tHd.js} +131 -54
  84. package/dist/{renderer-nbMCw1Pl.d.ts → renderer-DVsZHAvf.d.ts} +23 -6
  85. package/dist/resources/cache.d.ts +30 -0
  86. package/dist/resources/cache.js +2 -0
  87. package/dist/resources/descriptor.d.ts +2 -0
  88. package/dist/resources/descriptor.js +2 -0
  89. package/dist/resources/jar.d.ts +1 -1
  90. package/dist/resources/jar.js +1 -1
  91. package/dist/resources/manager.d.ts +1 -1
  92. package/dist/resources/manager.js +1 -1
  93. package/dist/resources/synthetic.d.ts +1 -1
  94. package/dist/resources/synthetic.js +1 -1
  95. package/dist/resources/zip-reader.d.ts +5 -0
  96. package/dist/resources/zip-reader.js +2 -0
  97. package/dist/run-BnICOF4I.js +209 -0
  98. package/dist/{run-BlSZw9Qk.d.ts → run-CKUtynTy.d.ts} +12 -2
  99. package/dist/run.d.ts +1 -1
  100. package/dist/run.js +1 -1
  101. package/dist/{sources-DH9dk3KF.d.ts → sources-B9X9w3qP.d.ts} +1 -1
  102. package/dist/{synthesize-CRgSEmEp.js → synthesize-3Q0aaOnK.js} +1 -1
  103. package/dist/{synthetic-CDsj--li.d.ts → synthetic-Qk-BSUXO.d.ts} +5 -1
  104. package/dist/{synthetic-DC2bSnGe.js → synthetic-ZAUjXT7E.js} +0 -0
  105. package/dist/textures/loader.d.ts +1 -1
  106. package/dist/textures/loader.js +1 -1
  107. package/dist/tints-B8UJmZMu.js +83 -0
  108. package/dist/{tints-txt-8xI5.d.ts → tints-DLBExVWN.d.ts} +2 -2
  109. package/dist/versions/legacy-reshape.d.ts +1 -1
  110. package/dist/versions/legacy-reshape.js +1 -1
  111. package/dist/versions/prepare.d.ts +1 -1
  112. package/dist/versions/prepare.js +1 -1
  113. package/dist/workers/pool.d.ts +36 -5
  114. package/dist/workers/pool.js +2 -67
  115. package/dist/workers/render-worker.d.ts +15 -0
  116. package/dist/workers/render-worker.js +40 -0
  117. package/dist/zip-reader-BSbJKUT1.js +156 -0
  118. package/package.json +4 -2
  119. package/dist/compose-qjhyBvnS.js +0 -41
  120. package/dist/run-DI-cB38y.js +0 -316
  121. package/dist/{discovery-iph0P2F2.js → discovery-0SM0cTLw.js} +1 -1
@@ -0,0 +1,221 @@
1
+ import { a as defaultProperties } from "./definition-3MBm8ar_.js";
2
+ import { n as ResourceLocator } from "./locator-ycZzh_6z.js";
3
+ import { n as LayeredResourceManager, t as FolderResourceManager } from "./manager-CkletR5K.js";
4
+ import { r as resolveItem } from "./discovery-0SM0cTLw.js";
5
+ import { s as toFloatTexture, t as blendPixel } from "./blend-CHP2-j9U.js";
6
+ import { t as encodeAnimatedIcon } from "./encode-D6dQu2dJ.js";
7
+ import { a as builtinDynamicResources } from "./generators-BlG4KmtP.js";
8
+ import { t as ModelLoader } from "./loader-B8NJO7pR.js";
9
+ import { a as renderModelFrames, i as renderModel, n as boxDownscale, r as pointResize } from "./renderer-CjT19tHd.js";
10
+ import { n as renderEntityFallback, r as spawnEggTints, t as renderDyedVariantFallback } from "./entity-fallback-BRCeGKBT.js";
11
+ import { i as renderGeo, t as findGeoForItem } from "./geo-Diky41Tw.js";
12
+ import { t as legacyTintsForChain } from "./legacy-tints-B09J0Rxh.js";
13
+ import { a as resolveTints } from "./tints-BtbHegpI.js";
14
+ import { t as loadAtlasResolver } from "./registry-B9TcjVbs.js";
15
+ import { n as TextureLoader } from "./loader-CfXMxcEV.js";
16
+ import path from "node:path";
17
+ import { mkdir, writeFile } from "node:fs/promises";
18
+ //#region src/render/pipeline.ts
19
+ /**
20
+ * The per-item pipeline: asset layering, model/texture loading, the render
21
+ * fallback chain, encoding, and file output. Extracted from run() so a worker
22
+ * thread renders items exactly the way the main thread does — a second
23
+ * implementation would drift, and the failure taxonomy depends on the fallback
24
+ * order being identical everywhere.
25
+ */
26
+ var ItemPipeline = class ItemPipeline {
27
+ static async create(base, settings) {
28
+ const dynamic = settings.builtin ? builtinDynamicResources() : null;
29
+ const resources = settings.builtin && dynamic ? new LayeredResourceManager([
30
+ new FolderResourceManager(settings.builtin),
31
+ dynamic.layer,
32
+ base
33
+ ]) : base;
34
+ const pipeline = new ItemPipeline(resources, dynamic?.items.map((item) => item.id) ?? null, settings);
35
+ pipeline.textures.setAtlasResolver(await loadAtlasResolver(resources, (locator) => pipeline.textures.load(locator)));
36
+ return pipeline;
37
+ }
38
+ settings;
39
+ models;
40
+ textures;
41
+ createdDirectories = /* @__PURE__ */ new Set();
42
+ /** The layered tree actually rendered (builtin + dynamic above the input). */
43
+ resources;
44
+ /** Dynamic vanilla items (trims, pots, crossbows), or null without a builtin pack. */
45
+ dynamicItems;
46
+ constructor(resources, dynamicItems, settings) {
47
+ this.resources = resources;
48
+ this.dynamicItems = dynamicItems;
49
+ this.settings = settings;
50
+ this.models = new ModelLoader(resources);
51
+ this.textures = new TextureLoader(resources);
52
+ }
53
+ /**
54
+ * Code-rendered models often still ship a flat item sprite (the old
55
+ * pipeline's black-icon audit checked exactly this). A real sprite beats
56
+ * no icon; the fallback is recorded so it is never a silent substitution.
57
+ */
58
+ async spriteFallback(id) {
59
+ const { size, quality } = this.settings;
60
+ const locator = ResourceLocator.parse(id);
61
+ for (const directory of ["item", "items"]) {
62
+ const texture = await this.textures.load(new ResourceLocator(locator.namespace, `${directory}/${locator.path}`));
63
+ if (texture) {
64
+ const upscaled = pointResize(toFloatTexture(texture), size * quality, size * quality);
65
+ return quality === 1 ? upscaled : boxDownscale(upscaled, quality);
66
+ }
67
+ }
68
+ return null;
69
+ }
70
+ /** Render an item's frames from its own model, without any fallback. */
71
+ async renderFrames(id) {
72
+ const { size, quality, lightingProfile, animation } = this.settings;
73
+ const { resources, models, textures } = this;
74
+ const instructions = await resolveItem(resources, id, defaultProperties());
75
+ let frames;
76
+ let delaysMs;
77
+ if (instructions.length === 1) {
78
+ const model = await models.load(instructions[0].model);
79
+ let tints = instructions[0].tints.length > 0 ? await resolveTints(instructions[0].tints, { textures }) : await legacyTintsForChain(model.chain, textures);
80
+ if (!tints && model.chain.some((entry) => entry.endsWith("item/template_spawn_egg"))) tints = await spawnEggTints(id, resources, textures).catch(() => null);
81
+ tints ??= [];
82
+ const result = await renderModelFrames(model, textures, {
83
+ size,
84
+ quality,
85
+ lightingProfile,
86
+ itemTransform: instructions[0].transform,
87
+ tints,
88
+ resources,
89
+ animation
90
+ });
91
+ frames = result.frames;
92
+ delaysMs = result.delaysMs;
93
+ } else {
94
+ let combined = null;
95
+ for (const instruction of instructions) {
96
+ const model = await models.load(instruction.model);
97
+ const tints = instruction.tints.length > 0 ? await resolveTints(instruction.tints, { textures }) : await legacyTintsForChain(model.chain, textures) ?? [];
98
+ const renderedModel = await renderModel(model, textures, {
99
+ size,
100
+ quality,
101
+ lightingProfile,
102
+ itemTransform: instruction.transform,
103
+ tints,
104
+ resources
105
+ });
106
+ combined = combined === null ? renderedModel : blendOver(combined, renderedModel);
107
+ }
108
+ frames = [combined ?? blankFrame(size)];
109
+ delaysMs = [50];
110
+ }
111
+ if (frames.length === 0) {
112
+ frames = [blankFrame(size)];
113
+ delaysMs = [50];
114
+ }
115
+ return {
116
+ frames,
117
+ delaysMs
118
+ };
119
+ }
120
+ /**
121
+ * Frames for an item, falling back through the same-name block model,
122
+ * GeckoLib geometry, builtin entity geometry, dyed variants, and finally the
123
+ * flat sprite. Rethrows the item's own error when nothing draws it.
124
+ */
125
+ async renderFramesWithFallback(id) {
126
+ try {
127
+ return {
128
+ ...await this.renderFrames(id),
129
+ usedFallback: false
130
+ };
131
+ } catch (err) {
132
+ const { size, quality, lightingProfile, builtin } = this.settings;
133
+ const { resources, models, textures } = this;
134
+ const blockLocator = ResourceLocator.parse(id);
135
+ const blockRender = await models.load(new ResourceLocator(blockLocator.namespace, `block/${blockLocator.path}`)).then((model) => renderModel(model, textures, {
136
+ size,
137
+ quality,
138
+ lightingProfile,
139
+ resources
140
+ })).catch(() => null);
141
+ const geo = blockRender ? null : await findGeoForItem(resources, id).catch(() => null);
142
+ const geoRender = geo ? await renderGeo(geo.geo, geo.texture, textures, {
143
+ size,
144
+ quality,
145
+ lightingProfile
146
+ }).catch(() => null) : null;
147
+ let fallbackRender = blockRender ?? (geoRender && quality !== 1 ? boxDownscale(geoRender, quality) : geoRender);
148
+ fallbackRender ??= builtin ? await renderEntityFallback(id, models, textures, resources, {
149
+ size,
150
+ quality,
151
+ lightingProfile,
152
+ resources
153
+ }).catch(() => null) : null;
154
+ fallbackRender ??= await renderDyedVariantFallback(id, models, textures, {
155
+ size,
156
+ quality,
157
+ lightingProfile,
158
+ resources
159
+ }).catch(() => null);
160
+ if (fallbackRender) return {
161
+ frames: [fallbackRender],
162
+ delaysMs: [50],
163
+ usedFallback: false
164
+ };
165
+ const sprite = await this.spriteFallback(id).catch(() => null);
166
+ if (!sprite) throw err;
167
+ return {
168
+ frames: [sprite],
169
+ delaysMs: [50],
170
+ usedFallback: true
171
+ };
172
+ }
173
+ }
174
+ /** Render, encode and write one item. Throws when the item cannot be drawn. */
175
+ async renderItem(id) {
176
+ const { frames, delaysMs, usedFallback } = await this.renderFramesWithFallback(id);
177
+ const encoded = await encodeAnimatedIcon(frames, delaysMs, {
178
+ webp: true,
179
+ gif: this.settings.gif,
180
+ avif: this.settings.avif || this.settings.format === "avif"
181
+ });
182
+ const [namespace, itemPath] = id.split(":", 2);
183
+ const imageBase = path.join(this.settings.output, "items", namespace, itemPath);
184
+ const directory = path.dirname(imageBase);
185
+ if (!this.createdDirectories.has(directory)) {
186
+ await mkdir(directory, { recursive: true });
187
+ this.createdDirectories.add(directory);
188
+ }
189
+ await writeFile(`${imageBase}.png`, encoded.png);
190
+ let written = 1;
191
+ for (const [extension, buffer] of [
192
+ ["webp", encoded.webp],
193
+ ["gif", encoded.gif],
194
+ ["avif", encoded.avif]
195
+ ]) {
196
+ if (!buffer) continue;
197
+ await writeFile(`${imageBase}.${extension}`, buffer);
198
+ written++;
199
+ }
200
+ return {
201
+ written,
202
+ usedFallback
203
+ };
204
+ }
205
+ };
206
+ function blankFrame(size) {
207
+ return {
208
+ width: size,
209
+ height: size,
210
+ data: new Float64Array(size * size * 4)
211
+ };
212
+ }
213
+ function blendOver(base, layer) {
214
+ for (let offset = 0; offset < base.data.length; offset += 4) {
215
+ const alpha = layer.data[offset + 3];
216
+ if (alpha > 0) blendPixel(base.data, offset, layer.data[offset], layer.data[offset + 1], layer.data[offset + 2], alpha);
217
+ }
218
+ return base;
219
+ }
220
+ //#endregion
221
+ export { ItemPipeline as t };
@@ -0,0 +1,58 @@
1
+ import { a as ResourceManager } from "./manager-Cv1NeFyF.js";
2
+ import { t as FloatTexture } from "./blend-CyDIxCne.js";
3
+ import { a as LightingProfileName } from "./lighting-3FGRC3Af.js";
4
+ import { n as AnimationLimits } from "./renderer-DVsZHAvf.js";
5
+ //#region src/render/pipeline.d.ts
6
+ interface PipelineSettings {
7
+ builtin?: string;
8
+ output: string;
9
+ size: number;
10
+ quality: number;
11
+ format: 'webp' | 'avif';
12
+ gif?: boolean;
13
+ avif?: boolean;
14
+ lightingProfile: LightingProfileName;
15
+ animation?: AnimationLimits;
16
+ }
17
+ interface ItemRenderOutcome {
18
+ /** Image files written for the item. */
19
+ written: number;
20
+ /** Whether the item fell back to its flat sprite instead of its model. */
21
+ usedFallback: boolean;
22
+ }
23
+ interface RenderedFrames {
24
+ frames: FloatTexture[];
25
+ delaysMs: number[];
26
+ }
27
+ declare class ItemPipeline {
28
+ static create(base: ResourceManager, settings: PipelineSettings): Promise<ItemPipeline>;
29
+ private readonly settings;
30
+ private readonly models;
31
+ private readonly textures;
32
+ private readonly createdDirectories;
33
+ /** The layered tree actually rendered (builtin + dynamic above the input). */
34
+ readonly resources: ResourceManager;
35
+ /** Dynamic vanilla items (trims, pots, crossbows), or null without a builtin pack. */
36
+ readonly dynamicItems: string[] | null;
37
+ private constructor();
38
+ /**
39
+ * Code-rendered models often still ship a flat item sprite (the old
40
+ * pipeline's black-icon audit checked exactly this). A real sprite beats
41
+ * no icon; the fallback is recorded so it is never a silent substitution.
42
+ */
43
+ private spriteFallback;
44
+ /** Render an item's frames from its own model, without any fallback. */
45
+ renderFrames(id: string): Promise<RenderedFrames>;
46
+ /**
47
+ * Frames for an item, falling back through the same-name block model,
48
+ * GeckoLib geometry, builtin entity geometry, dyed variants, and finally the
49
+ * flat sprite. Rethrows the item's own error when nothing draws it.
50
+ */
51
+ renderFramesWithFallback(id: string): Promise<RenderedFrames & {
52
+ usedFallback: boolean;
53
+ }>;
54
+ /** Render, encode and write one item. Throws when the item cannot be drawn. */
55
+ renderItem(id: string): Promise<ItemRenderOutcome>;
56
+ }
57
+ //#endregion
58
+ export { RenderedFrames as i, ItemRenderOutcome as n, PipelineSettings as r, ItemPipeline as t };
@@ -0,0 +1,131 @@
1
+ import { Worker } from "node:worker_threads";
2
+ //#region src/workers/pool.ts
3
+ /**
4
+ * Worker-thread pool for parallel item rendering. Design goals born from the
5
+ * PHP tool's failure modes (docs/research/mod-failure-taxonomy.md): errors
6
+ * cross the worker boundary as structured data, a dead worker's in-flight items
7
+ * are reported rather than orphaned, and results stream back so output lands
8
+ * incrementally.
9
+ *
10
+ * Workers spawn as soon as the pool is constructed, so their asset indexing
11
+ * overlaps the caller's own discovery pass; each holds several tasks at once so
12
+ * native encoding overlaps the next item's rasterization; and the queue drains
13
+ * through a cursor, because shift() on a 400k-item array is quadratic.
14
+ */
15
+ var WorkerRenderPool = class {
16
+ options;
17
+ inFlightLimit;
18
+ workers = /* @__PURE__ */ new Set();
19
+ tasks = [];
20
+ cursor = 0;
21
+ started = false;
22
+ onResult = null;
23
+ settle = null;
24
+ settled = false;
25
+ /** Workers dying before completing anything mean a bad config, not bad luck. */
26
+ startupFailures = 0;
27
+ constructor(options) {
28
+ this.options = options;
29
+ this.inFlightLimit = Math.max(1, options.inFlight ?? 2);
30
+ for (let i = 0; i < Math.max(1, options.workers); i++) this.spawn();
31
+ }
32
+ spawn() {
33
+ const entry = {
34
+ worker: new Worker(this.options.workerUrl, { workerData: this.options.workerData }),
35
+ inFlight: /* @__PURE__ */ new Set(),
36
+ ready: false,
37
+ stopped: false
38
+ };
39
+ this.workers.add(entry);
40
+ entry.worker.on("message", (message) => {
41
+ if ("ready" in message) entry.ready = true;
42
+ else {
43
+ entry.inFlight.delete(message.id);
44
+ this.startupFailures = 0;
45
+ this.onResult?.(message);
46
+ }
47
+ this.pump(entry);
48
+ });
49
+ entry.worker.on("error", (err) => {
50
+ if (entry.inFlight.size === 0) {
51
+ this.startupFailures++;
52
+ if (this.startupFailures > 3) {
53
+ this.abort(err);
54
+ return;
55
+ }
56
+ }
57
+ this.fail(entry, `Worker crashed: ${err.message}`);
58
+ });
59
+ entry.worker.on("exit", (code) => {
60
+ if (code !== 0) this.fail(entry, `Worker exited with code ${code}`);
61
+ });
62
+ }
63
+ pump(entry) {
64
+ if (!this.started || entry.stopped || !entry.ready) return;
65
+ while (entry.inFlight.size < this.inFlightLimit && this.cursor < this.tasks.length) {
66
+ const task = this.tasks[this.cursor++];
67
+ entry.inFlight.add(task.id);
68
+ entry.worker.postMessage(task);
69
+ }
70
+ if (entry.inFlight.size === 0 && this.cursor >= this.tasks.length) this.retire(entry);
71
+ }
72
+ retire(entry) {
73
+ if (entry.stopped) return;
74
+ entry.stopped = true;
75
+ this.workers.delete(entry);
76
+ entry.worker.terminate().catch(() => {});
77
+ this.finish();
78
+ }
79
+ fail(entry, reason) {
80
+ if (entry.stopped) return;
81
+ for (const id of entry.inFlight) this.onResult?.({
82
+ id,
83
+ error: reason
84
+ });
85
+ entry.inFlight.clear();
86
+ entry.stopped = true;
87
+ this.workers.delete(entry);
88
+ if (this.started && this.cursor < this.tasks.length) this.spawn();
89
+ else this.finish();
90
+ }
91
+ finish() {
92
+ if (!this.settled && this.started && this.workers.size === 0) {
93
+ this.settled = true;
94
+ this.settle?.resolve();
95
+ }
96
+ }
97
+ abort(err) {
98
+ if (this.settled) return;
99
+ this.settled = true;
100
+ for (const entry of this.workers) {
101
+ entry.stopped = true;
102
+ entry.worker.terminate().catch(() => {});
103
+ }
104
+ this.workers.clear();
105
+ this.settle?.reject(err);
106
+ }
107
+ /** Run every task, calling back as results arrive. Terminates the pool after. */
108
+ async run(tasks, onResult) {
109
+ this.tasks = tasks;
110
+ this.onResult = onResult;
111
+ this.started = true;
112
+ try {
113
+ if (tasks.length > 0) await new Promise((resolve, reject) => {
114
+ this.settle = {
115
+ resolve,
116
+ reject
117
+ };
118
+ for (const entry of this.workers) this.pump(entry);
119
+ });
120
+ } finally {
121
+ await this.close();
122
+ }
123
+ }
124
+ async close() {
125
+ const workers = [...this.workers];
126
+ this.workers.clear();
127
+ await Promise.all(workers.map((entry) => entry.worker.terminate().catch(() => {})));
128
+ }
129
+ };
130
+ //#endregion
131
+ export { WorkerRenderPool as t };
@@ -1,6 +1,7 @@
1
- import { n as LayeredResourceManager } from "./manager-dfI7b9N4.js";
2
- import { n as UnsafeJarError, t as JarResourceManager } from "./jar-Bt3i0ls4.js";
3
- import { t as synthesizeItemModels } from "./synthesize-CRgSEmEp.js";
1
+ import { n as LayeredResourceManager } from "./manager-CkletR5K.js";
2
+ import { n as harvestTintOverrides, t as curatedTintOverrides } from "./tints-B8UJmZMu.js";
3
+ import { n as UnsafeJarError, t as JarResourceManager } from "./jar-D9z-X7tA.js";
4
+ import { t as synthesizeItemModels } from "./synthesize-3Q0aaOnK.js";
4
5
  //#region src/mods/prepare.ts
5
6
  /**
6
7
  * Mod asset composition: mod jars layered under the vanilla base (vanilla wins
@@ -43,7 +44,11 @@ async function prepareModResources(options) {
43
44
  await manager.close().catch(() => {});
44
45
  }
45
46
  }
46
- const base = new LayeredResourceManager([options.vanilla, ...jarLayers]);
47
+ const base = new LayeredResourceManager([
48
+ options.vanilla,
49
+ curatedTintOverrides(),
50
+ ...jarLayers
51
+ ]);
47
52
  const namespaces = [];
48
53
  let synthesized = 0;
49
54
  const synthLayers = [];
@@ -57,10 +62,33 @@ async function prepareModResources(options) {
57
62
  options.onProgress?.(`Synthesized ${result.synthesized} item models for ${namespace}`);
58
63
  }
59
64
  }
65
+ let tintOverrides = 0;
66
+ const tintLayers = [];
67
+ if (options.tintDonors?.length) {
68
+ const donorJars = options.tintDonors.map((jar) => new JarResourceManager(jar));
69
+ try {
70
+ const harvest = await harvestTintOverrides({
71
+ donors: new LayeredResourceManager(donorJars),
72
+ target: base
73
+ });
74
+ if (harvest.harvested > 0) {
75
+ tintLayers.push(harvest.overrides);
76
+ tintOverrides = harvest.harvested;
77
+ options.onProgress?.(`Harvested tints for ${harvest.harvested} items from ${donorJars.length} donor jars`);
78
+ }
79
+ } finally {
80
+ await Promise.all(donorJars.map((jar) => jar.close().catch(() => {})));
81
+ }
82
+ }
60
83
  return {
61
- resources: new LayeredResourceManager([base, ...synthLayers]),
84
+ resources: new LayeredResourceManager([
85
+ base,
86
+ ...synthLayers,
87
+ ...tintLayers
88
+ ]),
62
89
  namespaces,
63
90
  synthesized,
91
+ tintOverrides,
64
92
  rejected
65
93
  };
66
94
  }
@@ -1,4 +1,4 @@
1
- import { a as ResourceManager } from "./manager-CoZaeQTu.js";
1
+ import { a as ResourceManager } from "./manager-Cv1NeFyF.js";
2
2
  //#region src/mods/prepare.d.ts
3
3
  /** Loader-owned namespaces that never contain renderable mod items. */
4
4
  declare const LOADER_NAMESPACES: Set<string>;
@@ -8,6 +8,8 @@ interface ModPreparation {
8
8
  namespaces: string[];
9
9
  /** Models synthesized for texture-only namespaces. */
10
10
  synthesized: number;
11
+ /** Items given a tint harvested from a donor jar. */
12
+ tintOverrides: number;
11
13
  /** Jars rejected (unsafe entries) or unreadable, with reasons. */
12
14
  rejected: Array<{
13
15
  jar: string;
@@ -18,6 +20,11 @@ interface PrepareModsOptions {
18
20
  jars: string[];
19
21
  /** Vanilla assets for the target version (wins the minecraft namespace). */
20
22
  vanilla: ResourceManager;
23
+ /**
24
+ * Jars from 1.21.4+ releases of the same mods, read only for the tints their
25
+ * item definitions state as data (see src/mods/tints.ts).
26
+ */
27
+ tintDonors?: string[];
21
28
  onProgress?: (message: string) => void;
22
29
  }
23
30
  declare function prepareModResources(options: PrepareModsOptions): Promise<ModPreparation>;
@@ -1,8 +1,8 @@
1
1
  import { n as ResourceLocator } from "./locator-ycZzh_6z.js";
2
- import { n as LayeredResourceManager, t as FolderResourceManager } from "./manager-dfI7b9N4.js";
3
- import { t as JarResourceManager } from "./jar-Bt3i0ls4.js";
4
- import { n as MapResourceManager, t as KindFilteredResourceManager } from "./synthetic-DC2bSnGe.js";
5
- import { t as reshapeLegacyAssets } from "./legacy-reshape-CewShfF3.js";
2
+ import { n as LayeredResourceManager, t as FolderResourceManager } from "./manager-CkletR5K.js";
3
+ import { n as MapResourceManager, t as KindFilteredResourceManager } from "./synthetic-ZAUjXT7E.js";
4
+ import { t as JarResourceManager } from "./jar-D9z-X7tA.js";
5
+ import { t as reshapeLegacyAssets } from "./legacy-reshape-C05SfAPR.js";
6
6
  //#region src/versions/prepare.ts
7
7
  /**
8
8
  * Version-era detection and asset preparation, replacing the file-tree surgery
@@ -1,4 +1,4 @@
1
- import { a as ResourceManager } from "./manager-CoZaeQTu.js";
1
+ import { a as ResourceManager } from "./manager-Cv1NeFyF.js";
2
2
  //#region src/versions/prepare.d.ts
3
3
  /** Dotted-numeric compare handling both `1.21.8` and the `26.2` scheme. */
4
4
  declare function compareVersions(left: string, right: string): number;
@@ -6,10 +6,33 @@ function createFaceBuffer(width, height) {
6
6
  width,
7
7
  height,
8
8
  colors: new Float64Array(width * height * 4),
9
- depths: new Float64Array(width * height)
9
+ depths: new Float64Array(width * height),
10
+ dirty: {
11
+ minX: 0,
12
+ minY: 0,
13
+ maxX: width - 1,
14
+ maxY: height - 1
15
+ }
10
16
  };
11
17
  }
12
18
  /**
19
+ * Zero the written region and mark the buffer empty, so one scratch buffer can
20
+ * serve every face of a model. Allocating an image-sized color+depth pair per
21
+ * face is what made complex models allocate hundreds of megabytes transiently.
22
+ */
23
+ function clearFaceBuffer(buffer) {
24
+ const { dirty } = buffer;
25
+ for (let y = dirty.minY; y <= dirty.maxY; y++) {
26
+ const row = y * buffer.width;
27
+ buffer.colors.fill(0, (row + dirty.minX) * 4, (row + dirty.maxX + 1) * 4);
28
+ buffer.depths.fill(0, row + dirty.minX, row + dirty.maxX + 1);
29
+ }
30
+ dirty.minX = buffer.width;
31
+ dirty.minY = buffer.height;
32
+ dirty.maxX = -1;
33
+ dirty.maxY = -1;
34
+ }
35
+ /**
13
36
  * Retain only the rectangle containing visible pixels. Complex block models can
14
37
  * contain thousands of tiny faces; keeping a full icon-sized color/depth buffer
15
38
  * for each one turns a 64px render into gigabytes of transient memory.
@@ -19,7 +42,7 @@ function compactFaceBuffer(buffer) {
19
42
  let minY = buffer.height;
20
43
  let maxX = -1;
21
44
  let maxY = -1;
22
- for (let y = 0; y < buffer.height; y++) for (let x = 0; x < buffer.width; x++) {
45
+ for (let y = Math.max(buffer.dirty.minY, 0); y <= Math.min(buffer.dirty.maxY, buffer.height - 1); y++) for (let x = Math.max(buffer.dirty.minX, 0); x <= Math.min(buffer.dirty.maxX, buffer.width - 1); x++) {
23
46
  if (buffer.colors[(y * buffer.width + x) * 4 + 3] <= 0) continue;
24
47
  minX = Math.min(minX, x);
25
48
  minY = Math.min(minY, y);
@@ -43,7 +66,13 @@ function compactFaceBuffer(buffer) {
43
66
  width,
44
67
  height,
45
68
  colors,
46
- depths
69
+ depths,
70
+ dirty: {
71
+ minX: 0,
72
+ minY: 0,
73
+ maxX: width - 1,
74
+ maxY: height - 1
75
+ }
47
76
  };
48
77
  }
49
78
  /**
@@ -136,6 +165,15 @@ function drawTexturedTriangle(p1in, p2in, p3in, buffer, texture, cursor = new Ro
136
165
  const textureStep = lineLength ? 1 / lineLength : 0;
137
166
  let textureX = textureStep * .3;
138
167
  const rowOffset = row * width;
168
+ const spanMinX = Math.max(startX - 1, 0);
169
+ const spanMaxX = Math.min(endX + 1, width - 1);
170
+ if (spanMinX <= spanMaxX) {
171
+ const { dirty } = buffer;
172
+ dirty.minX = Math.min(dirty.minX, spanMinX);
173
+ dirty.maxX = Math.max(dirty.maxX, spanMaxX);
174
+ dirty.minY = Math.min(dirty.minY, row);
175
+ dirty.maxY = Math.max(dirty.maxY, row);
176
+ }
139
177
  for (let x = startX - 1; x <= endX + 1; x++) {
140
178
  const currentU = (1 - textureX) * startU + textureX * endU;
141
179
  const currentV = (1 - textureX) * startV + textureX * endV;
@@ -175,4 +213,4 @@ function drawTexturedTriangle(p1in, p2in, p3in, buffer, texture, cursor = new Ro
175
213
  }
176
214
  }
177
215
  //#endregion
178
- export { phpRound as a, drawTexturedTriangle as i, compactFaceBuffer as n, createFaceBuffer as r, RowCursor as t };
216
+ export { drawTexturedTriangle as a, createFaceBuffer as i, clearFaceBuffer as n, phpRound as o, compactFaceBuffer as r, RowCursor as t };
@@ -1,5 +1,12 @@
1
- import { t as FloatTexture } from "./blend-Qvrn6YgK.js";
1
+ import { t as FloatTexture } from "./blend-CyDIxCne.js";
2
2
  //#region src/render/rasterizer.d.ts
3
+ /** The rectangle of a buffer written since it was last cleared. */
4
+ interface DirtyRect {
5
+ minX: number;
6
+ minY: number;
7
+ maxX: number;
8
+ maxY: number;
9
+ }
3
10
  /** Per-face private color + depth buffers, image sized. */
4
11
  interface FaceBuffer {
5
12
  /** Position within the final image (full-size scratch buffers start at 0,0). */
@@ -11,8 +18,20 @@ interface FaceBuffer {
11
18
  colors: Float64Array;
12
19
  /** Depth per pixel; 0 where never written — the overdraw band relies on that. */
13
20
  depths: Float64Array;
21
+ /**
22
+ * Where the rasterizer has written. A fresh buffer claims its whole extent
23
+ * so callers that fill it by hand still compact correctly; clearFaceBuffer
24
+ * resets it to empty, which is what makes buffer reuse cheap.
25
+ */
26
+ dirty: DirtyRect;
14
27
  }
15
28
  declare function createFaceBuffer(width: number, height: number): FaceBuffer;
29
+ /**
30
+ * Zero the written region and mark the buffer empty, so one scratch buffer can
31
+ * serve every face of a model. Allocating an image-sized color+depth pair per
32
+ * face is what made complex models allocate hundreds of megabytes transiently.
33
+ */
34
+ declare function clearFaceBuffer(buffer: FaceBuffer): void;
16
35
  /**
17
36
  * Retain only the rectangle containing visible pixels. Complex block models can
18
37
  * contain thousands of tiny faces; keeping a full icon-sized color/depth buffer
@@ -47,4 +66,4 @@ declare class RowCursor {
47
66
  declare function phpRound(value: number): number;
48
67
  declare function drawTexturedTriangle(p1in: RasterPoint, p2in: RasterPoint, p3in: RasterPoint, buffer: FaceBuffer, texture: FloatTexture, cursor?: RowCursor): void;
49
68
  //#endregion
50
- export { createFaceBuffer as a, compactFaceBuffer as i, RasterPoint as n, drawTexturedTriangle as o, RowCursor as r, phpRound as s, FaceBuffer as t };
69
+ export { clearFaceBuffer as a, drawTexturedTriangle as c, RowCursor as i, phpRound as l, FaceBuffer as n, compactFaceBuffer as o, RasterPoint as r, createFaceBuffer as s, DirtyRect as t };
@@ -1,2 +1,2 @@
1
- import { a as q16, c as toFloatTexture, i as darkenTexture, n as blendPixel, o as tintTexture, r as cloneFloatTexture, s as toByteTexture, t as FloatTexture } from "../blend-Qvrn6YgK.js";
1
+ import { a as q16, c as toFloatTexture, i as darkenTexture, n as blendPixel, o as tintTexture, r as cloneFloatTexture, s as toByteTexture, t as FloatTexture } from "../blend-CyDIxCne.js";
2
2
  export { FloatTexture, blendPixel, cloneFloatTexture, darkenTexture, q16, tintTexture, toByteTexture, toFloatTexture };
@@ -1,5 +1,5 @@
1
- import { t as FloatTexture } from "../blend-Qvrn6YgK.js";
2
- import { t as FaceBuffer } from "../rasterizer-Dh35qXbn.js";
1
+ import { t as FloatTexture } from "../blend-CyDIxCne.js";
2
+ import { n as FaceBuffer } from "../rasterizer-yqJkicm1.js";
3
3
  //#region src/render/compose.d.ts
4
4
  declare function combineFaceBuffers(width: number, height: number, faces: FaceBuffer[]): FloatTexture;
5
5
  //#endregion
@@ -1,2 +1,2 @@
1
- import { t as combineFaceBuffers } from "../compose-qjhyBvnS.js";
1
+ import { t as combineFaceBuffers } from "../compose-CQCb2LVv.js";
2
2
  export { combineFaceBuffers };
@@ -1,9 +1,9 @@
1
- import { a as ResourceManager } from "../manager-CoZaeQTu.js";
2
- import { i as TextureLoader } from "../loader-TFFNV4Jl.js";
3
- import { t as ModelLoader } from "../loader-C-6QN3xv.js";
4
- import { t as FloatTexture } from "../blend-Qvrn6YgK.js";
5
- import { n as TintColor } from "../face-CpVdnf-w.js";
6
- import { n as RenderOptions } from "../renderer-nbMCw1Pl.js";
1
+ import { a as ResourceManager } from "../manager-Cv1NeFyF.js";
2
+ import { i as TextureLoader } from "../loader-Myy22TDL.js";
3
+ import { t as ModelLoader } from "../loader-D22zZME7.js";
4
+ import { t as FloatTexture } from "../blend-CyDIxCne.js";
5
+ import { r as TintColor } from "../face-BIYg14TE.js";
6
+ import { i as RenderOptions } from "../renderer-DVsZHAvf.js";
7
7
  //#region src/render/entity-fallback.d.ts
8
8
  /**
9
9
  * Dyed variants of vanilla items whose models mods generate at runtime (quark's
@@ -1,2 +1,2 @@
1
- import { n as renderEntityFallback, r as spawnEggTints, t as renderDyedVariantFallback } from "../entity-fallback-CdCVEG6X.js";
1
+ import { n as renderEntityFallback, r as spawnEggTints, t as renderDyedVariantFallback } from "../entity-fallback-BRCeGKBT.js";
2
2
  export { renderDyedVariantFallback, renderEntityFallback, spawnEggTints };