@nodecraft/rendermite 1.6.0 → 1.6.2

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 (53) hide show
  1. package/dist/{block-entities-xv3bQpEX.js → block-entities-XAynAVUE.js} +1 -1
  2. package/dist/blocks/block-entities.js +1 -1
  3. package/dist/blocks/cli.js +1 -1
  4. package/dist/blocks/entities.js +1 -1
  5. package/dist/blocks/entity-models.d.ts +45 -2
  6. package/dist/blocks/entity-models.js +1 -1
  7. package/dist/blocks/export.d.ts +1 -1
  8. package/dist/blocks/export.js +1 -1
  9. package/dist/{classfile-BwaQjnUM.d.ts → classfile-BdLIk4Pe.d.ts} +16 -0
  10. package/dist/{classfile-CAhS5qNf.js → classfile-Bo7FUrrD.js} +42 -10
  11. package/dist/{cli-BSKnXBPr.js → cli-Br0mY0LV.js} +6 -2
  12. package/dist/cli.js +2 -2
  13. package/dist/entities/classfile.d.ts +1 -1
  14. package/dist/entities/classfile.js +1 -1
  15. package/dist/entities/inherit.d.ts +2 -2
  16. package/dist/entities/inherit.js +1 -1
  17. package/dist/entities/jar-classes.d.ts +2 -26
  18. package/dist/entities/jar-classes.js +2 -2
  19. package/dist/entities/link.d.ts +2 -2
  20. package/dist/entities/link.js +1 -1
  21. package/dist/entities/merge.d.ts +16 -1
  22. package/dist/entities/merge.js +2 -2
  23. package/dist/entities/model-bytecode.d.ts +1 -1
  24. package/dist/entities/model-bytecode.js +1 -1
  25. package/dist/entities/model-code.d.ts +1 -1
  26. package/dist/entities/model-code.js +1 -1
  27. package/dist/{entities-DvmA9fEV.js → entities-DubtdM11.js} +6 -2
  28. package/dist/{entity-fallback-Bth0fa7m.js → entity-fallback-BcJWh91-.js} +1 -1
  29. package/dist/{entity-models-B6-0r2gR.js → entity-models-D_QGok9j.js} +9 -5
  30. package/dist/{entity-textures-DFRIVdF1.js → entity-textures-4XXT8jVI.js} +25 -2
  31. package/dist/{export-D8fLC1wS.d.ts → export-B8tPsf4K.d.ts} +1 -1
  32. package/dist/{export-DT_drDoI.js → export-CTKEwro6.js} +3 -3
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.js +2 -2
  35. package/dist/{inherit-KkDsM2N_.js → inherit-C5Ifg3SM.js} +4 -4
  36. package/dist/{jar-classes-Bj15hjdr.js → jar-classes-3GVCJZT7.js} +38 -1
  37. package/dist/jar-classes-CSRrPAUy.d.ts +41 -0
  38. package/dist/{link-Bm5oQYh6.js → link-5GmBLTzk.js} +25 -1
  39. package/dist/merge-DeHruviG.js +109 -0
  40. package/dist/{model-bytecode-C1nw2G8O.js → model-bytecode-BQKCLPze.js} +151 -39
  41. package/dist/{model-code-BKMEMXxF.js → model-code-1Vuk-zj_.js} +1 -1
  42. package/dist/{model-code-B7jSFQqd.d.ts → model-code-BY44ao27.d.ts} +1 -1
  43. package/dist/{pipeline-BUIMzMzg.js → pipeline-7rvL6vx1.js} +1 -1
  44. package/dist/render/entity-fallback.js +1 -1
  45. package/dist/render/pipeline.js +1 -1
  46. package/dist/resources/entity-textures.d.ts +13 -1
  47. package/dist/resources/entity-textures.js +2 -2
  48. package/dist/{run-BQJkqc5f.js → run-BXAqZ5qY.js} +1 -1
  49. package/dist/run.js +1 -1
  50. package/dist/workers/render-worker.js +1 -1
  51. package/package.json +1 -1
  52. package/dist/entity-models-c2HpfdWV.d.ts +0 -49
  53. package/dist/merge-3HkeQ_6A.js +0 -46
@@ -0,0 +1,41 @@
1
+ //#region src/entities/jar-classes.d.ts
2
+ /**
3
+ * Reading `.class` entries out of a jar.
4
+ *
5
+ * `JarResourceManager` deliberately indexes only `assets/` and `data/` — it is a *resource* manager,
6
+ * and a class file is not a resource. But entity geometry lives in the code (see
7
+ * `docs/entity-geometry.md`), so the extractor needs the other half of the archive, and it needs it
8
+ * by internal class name rather than by resource locator.
9
+ *
10
+ * Same shape as the resource manager for the same reason: index the central directory once, then read
11
+ * entries on demand. A client jar holds around eight thousand classes and the extractor touches a
12
+ * hundred of them.
13
+ */
14
+ /** What the extractor reads code out of: one jar, or several layered into one view. */
15
+ interface ClassSource {
16
+ list: () => Promise<string[]>;
17
+ read: (internalName: string) => Promise<Uint8Array | null>;
18
+ close: () => Promise<void>;
19
+ }
20
+ /**
21
+ * Several jars as one class view, first match winning — the same layering the resource managers use.
22
+ *
23
+ * A modded export needs this, and needed it before cross-jar linking did: the class source used to be
24
+ * the client jar alone, so a mod's own model classes were never read and its mobs came out as boxes
25
+ * however good the interpreter was. Layered, one view holds both, which is also what lets a mod entity
26
+ * link to a *vanilla* model — `ars_nouveau:summon_horse` is the game's own horse.
27
+ */
28
+ declare function layeredClasses(sources: readonly ClassSource[]): ClassSource;
29
+ /** Reads class files out of one jar, by internal name. */
30
+ declare class JarClassReader {
31
+ private readonly jarPath;
32
+ private index;
33
+ constructor(jarPath: string);
34
+ private getIndex;
35
+ /** Every class in the jar, for a sweep that does not know what it is looking for. */
36
+ list(): Promise<string[]>;
37
+ read(internalName: string): Promise<Uint8Array | null>;
38
+ close(): Promise<void>;
39
+ }
40
+ //#endregion
41
+ export { JarClassReader as n, layeredClasses as r, ClassSource as t };
@@ -1,4 +1,4 @@
1
- import { t as parseClassFile } from "./classfile-CAhS5qNf.js";
1
+ import { t as parseClassFile } from "./classfile-Bo7FUrrD.js";
2
2
  //#region src/entities/link.ts
3
3
  /**
4
4
  * Which class holds a given entity's geometry.
@@ -257,6 +257,7 @@ function buildModelNameIndex(internalNames) {
257
257
  */
258
258
  async function linkEntityModel(entityId, context) {
259
259
  const name = entityId.includes(":") ? entityId.slice(entityId.indexOf(":") + 1) : entityId;
260
+ const modded = entityId.includes(":") && !entityId.startsWith("minecraft:");
260
261
  const { mappings } = context;
261
262
  const accept = async (className, route) => {
262
263
  const obfuscated = mappings.obfuscatedOf.get(className);
@@ -309,8 +310,31 @@ async function linkEntityModel(entityId, context) {
309
310
  route: "mod-name"
310
311
  };
311
312
  }
313
+ if (modded) for (const shorter of withTokensDropped(name)) {
314
+ const found = await linkEntityModel(shorter, context);
315
+ if (found) return {
316
+ ...found,
317
+ route: "vanilla-name"
318
+ };
319
+ }
312
320
  return null;
313
321
  }
322
+ /**
323
+ * The name with some of its tokens dropped, longest first: `summon_horse` gives `summon` and `horse`.
324
+ *
325
+ * Capped at five tokens, because the subsets double with each one and no id is longer than that for a
326
+ * reason this would help with.
327
+ */
328
+ function withTokensDropped(name) {
329
+ const tokens = name.split("_").filter((token) => token.length > 0);
330
+ if (tokens.length < 2 || tokens.length > 5) return [];
331
+ const found = [];
332
+ for (let size = tokens.length - 1; size >= 1; size--) for (let mask = 0; mask < 2 ** tokens.length; mask++) {
333
+ const kept = tokens.filter((token, index) => (mask & 1 << index) !== 0);
334
+ if (kept.length === size) found.push(kept.join("_"));
335
+ }
336
+ return [...new Set(found)];
337
+ }
314
338
  /** Reads and caches classes out of an extracted jar directory or a jar reader. */
315
339
  function classReader(load) {
316
340
  const cache = /* @__PURE__ */ new Map();
@@ -0,0 +1,109 @@
1
+ //#region src/entities/merge.ts
2
+ /** A root the interpreter had to invent, because a `MeshDefinition`'s root part is anonymous. */
3
+ const SYNTHETIC_ROOT = /^part\d+$/;
4
+ /**
5
+ * Where a piece's parts sit: at the top, or inside the one anonymous root a `MeshDefinition` makes.
6
+ * Both merge into the same list of named slots and only one of them is nested.
7
+ */
8
+ function slotsOf(parts) {
9
+ const [only] = parts;
10
+ if (parts.length === 1 && only.cubes.length === 0 && SYNTHETIC_ROOT.test(only.name)) return {
11
+ container: only,
12
+ slots: only.children
13
+ };
14
+ return {
15
+ container: null,
16
+ slots: parts
17
+ };
18
+ }
19
+ /** True when this piece built its own mesh rather than adding to one it was handed. */
20
+ function buildsOwnMesh(model) {
21
+ return slotsOf(model.parts).container !== null;
22
+ }
23
+ /** `base` plus the parts of `extra` that `base` does not name. */
24
+ function mergeModels(base, extra) {
25
+ const target = slotsOf(base.parts);
26
+ const source = slotsOf(extra.parts);
27
+ const named = new Set(target.slots.map((part) => part.name));
28
+ const missing = source.slots.filter((part) => !named.has(part.name));
29
+ if (missing.length === 0) return base;
30
+ const parts = target.container ? [{
31
+ ...target.container,
32
+ children: [...target.container.children, ...missing]
33
+ }] : [...target.slots, ...missing];
34
+ const notes = [.../* @__PURE__ */ new Set([...base.notes, ...extra.notes])].filter((note) => parts.length > 1 || !note.includes("disconnected roots"));
35
+ return {
36
+ textureSize: base.textureSize[0] === 64 && base.textureSize[1] === 32 ? extra.textureSize : base.textureSize,
37
+ parts,
38
+ notes
39
+ };
40
+ }
41
+ /**
42
+ * Drops boxes that land exactly where one already is.
43
+ *
44
+ * A model builder's branches are all taken, because they turn on a constructor argument this cannot
45
+ * know — a `CubeDeformation`, a baby-variant flag — so a model that builds one shape *or* another
46
+ * emits both. Where the two arms differ, that is the honest answer and the model says so in a note.
47
+ * Where they agree, it is four legs drawn twice in the same place: `HorseModel` builds
48
+ * `left_hind_leg` and `left_hind_baby_leg` at identical coordinates, and two coplanar boxes are
49
+ * exactly what z-fighting is.
50
+ *
51
+ * Keyed on the accumulated pivot and rotation rather than on evaluated corners, since duplicates come
52
+ * from one builder running twice and land on identical frames. The first is kept: a builder states the
53
+ * ordinary shape before the variant.
54
+ */
55
+ function dedupeCubes(model) {
56
+ const seen = /* @__PURE__ */ new Set();
57
+ const round = (value) => value.toFixed(4);
58
+ const walk = (part, pivot, rotation) => {
59
+ const at = [
60
+ pivot[0] + part.pivot[0],
61
+ pivot[1] + part.pivot[1],
62
+ pivot[2] + part.pivot[2]
63
+ ];
64
+ const spun = [
65
+ rotation[0] + part.rotation[0],
66
+ rotation[1] + part.rotation[1],
67
+ rotation[2] + part.rotation[2]
68
+ ];
69
+ const cubes = part.cubes.filter((cube) => {
70
+ const key = [
71
+ ...at.map(round),
72
+ ...spun.map(round),
73
+ ...cube.origin.map(round),
74
+ ...cube.size.map(round),
75
+ ...cube.uv.map(round),
76
+ round(cube.inflate)
77
+ ].join("|");
78
+ if (seen.has(key)) return false;
79
+ seen.add(key);
80
+ return true;
81
+ });
82
+ const children = part.children.map((child) => walk(child, at, spun)).filter((child) => child.cubes.length > 0 || child.children.length > 0);
83
+ return {
84
+ ...part,
85
+ cubes,
86
+ children
87
+ };
88
+ };
89
+ return {
90
+ ...model,
91
+ parts: model.parts.map((part) => walk(part, [
92
+ 0,
93
+ 0,
94
+ 0
95
+ ], [
96
+ 0,
97
+ 0,
98
+ 0
99
+ ])).filter((part) => part.cubes.length > 0 || part.children.length > 0)
100
+ };
101
+ }
102
+ function countCubes(part) {
103
+ return part.cubes.length + part.children.reduce((total, child) => total + countCubes(child), 0);
104
+ }
105
+ function cubesOf(model) {
106
+ return model.parts.reduce((total, part) => total + countCubes(part), 0);
107
+ }
108
+ //#endregion
109
+ export { mergeModels as a, dedupeCubes as i, countCubes as n, slotsOf as o, cubesOf as r, buildsOwnMesh as t };
@@ -1,5 +1,5 @@
1
- import { t as parseClassFile } from "./classfile-CAhS5qNf.js";
2
- import { i as mergeModels, r as cubesOf, t as buildsOwnMesh } from "./merge-3HkeQ_6A.js";
1
+ import { t as parseClassFile } from "./classfile-Bo7FUrrD.js";
2
+ import { a as mergeModels, r as cubesOf, t as buildsOwnMesh } from "./merge-DeHruviG.js";
3
3
  //#region src/entities/model-bytecode.ts
4
4
  /**
5
5
  * Abstract interpretation of a boxy-model builder, enough to recover the geometry.
@@ -43,6 +43,28 @@ const NUMERIC = /* @__PURE__ */ new Set([
43
43
  "B",
44
44
  "C"
45
45
  ]);
46
+ /**
47
+ * Whether a conditional jump is taken, for the opcodes that compare numbers.
48
+ *
49
+ * The single-operand forms (`ifeq` … `ifle`) test against zero and the two-operand forms (`if_icmpeq`
50
+ * …) against each other, so both reduce to the sign of `left - right`.
51
+ */
52
+ function compare(op, left, right) {
53
+ const difference = left - right;
54
+ switch (op) {
55
+ case 153:
56
+ case 159: return difference === 0;
57
+ case 154:
58
+ case 160: return difference !== 0;
59
+ case 155:
60
+ case 161: return difference < 0;
61
+ case 156:
62
+ case 162: return difference >= 0;
63
+ case 157:
64
+ case 163: return difference > 0;
65
+ default: return difference <= 0;
66
+ }
67
+ }
46
68
  /** Every entity sheet is a power of two, at least 16 in each direction. A texture offset rarely is. */
47
69
  function isSheetSize(width, height) {
48
70
  const power = (value) => value >= 16 && (value & value - 1) === 0;
@@ -109,6 +131,18 @@ const TEXTURE_HEIGHT_FIELDS = /* @__PURE__ */ new Set(["texHeight", "textureHeig
109
131
  const MAX_INLINE_DEPTH = 5;
110
132
  /** Frames per method. A model spends a handful; the cap is there so a strange jar cannot spend all day. */
111
133
  const INLINE_BUDGET = 96;
134
+ /**
135
+ * Instructions per extraction, shared across every frame.
136
+ *
137
+ * A model builder is a few hundred instructions and the longest vanilla loop runs a few thousand, so
138
+ * this is two orders of magnitude of headroom — and it is what makes following a backward goto safe: a
139
+ * loop whose bound cannot be worked out stops here instead of spinning.
140
+ */
141
+ const STEP_BUDGET = 2e5;
142
+ /** Elements tracked per array. A table of segment sizes is a handful; nothing here needs thousands. */
143
+ const MAX_ARRAY = 1024;
144
+ /** Times round a loop. Vanilla's longest builds sixteen segments, so this is generous by four times. */
145
+ const MAX_ITERATIONS = 64;
112
146
  /** How far up a hierarchy to look for an inherited method, which is as deep as a model hierarchy goes. */
113
147
  const MAX_METHOD_HOPS = 6;
114
148
  function newShared(resolve) {
@@ -120,7 +154,10 @@ function newShared(resolve) {
120
154
  branched: false,
121
155
  nextHandle: 1,
122
156
  resolve,
123
- budget: INLINE_BUDGET
157
+ budget: INLINE_BUDGET,
158
+ statics: /* @__PURE__ */ new Map(),
159
+ initialised: /* @__PURE__ */ new Set(),
160
+ steps: STEP_BUDGET
124
161
  };
125
162
  }
126
163
  /** The method a call names, looked up where the JVM would look: the owner, then up its ancestors. */
@@ -143,6 +180,8 @@ var Interpreter = class Interpreter {
143
180
  locals = /* @__PURE__ */ new Map();
144
181
  shared;
145
182
  depth;
183
+ /** How many times each backward jump has been taken, so an undecidable loop cannot spin. */
184
+ loops = /* @__PURE__ */ new Map();
146
185
  /** What this frame returned, for the caller that inlined it. */
147
186
  returned = null;
148
187
  constructor(shared, depth = 0) {
@@ -171,6 +210,19 @@ var Interpreter = class Interpreter {
171
210
  }
172
211
  }
173
212
  /**
213
+ * Runs a class's `<clinit>` once, so its static tables can be read.
214
+ *
215
+ * Only for model code, and only once per class per extraction: the point is a table of numbers, and
216
+ * anything else a static initialiser does is of no interest here.
217
+ */
218
+ initialise(owner) {
219
+ if (this.shared.initialised.has(owner) || this.depth >= MAX_INLINE_DEPTH) return;
220
+ this.shared.initialised.add(owner);
221
+ const target = findMethod(this.shared.resolve, owner, "<clinit>", "()V");
222
+ if (!target) return;
223
+ new Interpreter(this.shared, this.depth + 1).run(target.code, target.classFile);
224
+ }
225
+ /**
174
226
  * Interprets a call into the model's own code, with the arguments it was actually given.
175
227
  *
176
228
  * This is what makes an inherited part come out right. `CowModel` calls `super(12, 0)` and the legs
@@ -189,6 +241,10 @@ var Interpreter = class Interpreter {
189
241
  frame.seed(receiver, descriptor, args);
190
242
  return frame.run(target.code, target.classFile) ? frame.returned : null;
191
243
  }
244
+ /** Records a note once, since every candidate method would otherwise repeat it. */
245
+ note(message) {
246
+ if (!this.shared.notes.includes(message)) this.shared.notes.push(message);
247
+ }
192
248
  pop() {
193
249
  return this.stack.pop() ?? { kind: "unknown" };
194
250
  }
@@ -218,6 +274,11 @@ var Interpreter = class Interpreter {
218
274
  const first = named ? 1 : 0;
219
275
  if (target && (named || floats >= 6 && args.length >= 6 && NUMERIC.has(args[0]) && NUMERIC.has(args[5]))) {
220
276
  const extra = args.slice(first + 6);
277
+ const floatAt = extra.findIndex((arg) => arg === "F" || arg === "D");
278
+ const deformed = taken.find((value) => value.kind === "object" && value.handle.deformation !== void 0);
279
+ let inflate = target.pendingInflate;
280
+ if (floatAt !== -1) inflate = this.number(taken[first + 6 + floatAt]);
281
+ else if (deformed?.kind === "object") inflate = deformed.handle.deformation ?? 0;
221
282
  target.cubes.push({
222
283
  origin: [
223
284
  numbers[first],
@@ -230,7 +291,7 @@ var Interpreter = class Interpreter {
230
291
  numbers[first + 5]
231
292
  ],
232
293
  uv: [...target.pendingUv],
233
- inflate: extra.some((arg) => NUMERIC.has(arg)) ? this.number(taken[first + 6]) : target.pendingInflate,
294
+ inflate,
234
295
  mirror: target.pendingMirror || extra.includes("Z") && this.number(taken[args.length - 1]) === 1
235
296
  });
236
297
  this.push(returns === "V" ? null : receiver);
@@ -308,6 +369,12 @@ var Interpreter = class Interpreter {
308
369
  });
309
370
  return;
310
371
  }
372
+ const lambda = taken.find((value) => value.kind === "lambda");
373
+ if (lambda?.kind === "lambda" && receiver?.kind === "object" && taken.length === 1) {
374
+ const applied = this.inline(lambda.owner, lambda.name, lambda.descriptor, null, [receiver]);
375
+ this.push(returns === "V" ? null : applied ?? receiver);
376
+ return;
377
+ }
311
378
  if (args.length === 1 && args[0] === "Ljava/lang/String;" && returns.startsWith("L") && target) {
312
379
  const name_ = taken[0].kind === "string" ? taken[0].value : "";
313
380
  const found = target.children.find((child) => child.name === name_);
@@ -342,6 +409,11 @@ var Interpreter = class Interpreter {
342
409
  this.push(returns === "V" ? null : receiver);
343
410
  return;
344
411
  }
412
+ if (args.length === 0 && target && returns === `L${owner};`) {
413
+ target.pendingMirror = true;
414
+ this.push(receiver);
415
+ return;
416
+ }
345
417
  if (args.length === 0 && returns.startsWith("L")) {
346
418
  if (target) {
347
419
  target.isRoot = true;
@@ -365,6 +437,10 @@ var Interpreter = class Interpreter {
365
437
  run(code, constants) {
366
438
  let at = 0;
367
439
  while (at < code.length) {
440
+ if (this.shared.steps-- <= 0) {
441
+ this.note("ran past the step budget, so the shape is only as far as it got");
442
+ return false;
443
+ }
368
444
  const op = code[at++];
369
445
  switch (true) {
370
446
  case op === 1:
@@ -437,9 +513,10 @@ var Interpreter = class Interpreter {
437
513
  this.locals.set((op - 59) % 4, this.pop());
438
514
  break;
439
515
  case ARRAY_LOADS.has(op): {
440
- this.pop();
516
+ const index = this.pop();
441
517
  const array = this.pop();
442
- this.push(array.kind === "array" ? array.items[0] ?? { kind: "unknown" } : { kind: "unknown" });
518
+ const at = index.kind === "number" ? index.value : 0;
519
+ this.push(array.kind === "array" ? array.items[at] ?? { kind: "unknown" } : { kind: "unknown" });
443
520
  break;
444
521
  }
445
522
  case op >= 120 && op <= 131:
@@ -459,18 +536,19 @@ var Interpreter = class Interpreter {
459
536
  });
460
537
  break;
461
538
  case op === 191: return true;
462
- case op === 190:
463
- this.pop();
464
- this.push({
539
+ case op === 190: {
540
+ const array = this.pop();
541
+ this.push(array.kind === "array" ? {
465
542
  kind: "number",
466
- value: 0
467
- });
543
+ value: array.items.length
544
+ } : { kind: "unknown" });
468
545
  break;
546
+ }
469
547
  case ARRAY_STORES.has(op): {
470
548
  const value = this.pop();
471
- this.pop();
549
+ const index = this.pop();
472
550
  const array = this.pop();
473
- if (array.kind === "array") array.items.push(value);
551
+ if (array.kind === "array") array.items[index.kind === "number" ? index.value : array.items.length] = value;
474
552
  break;
475
553
  }
476
554
  case op === 87:
@@ -525,20 +603,36 @@ var Interpreter = class Interpreter {
525
603
  });
526
604
  break;
527
605
  case op >= 133 && op <= 147: break;
528
- case op === 132:
606
+ case op === 132: {
607
+ const slot = code[at];
608
+ const step = code[at + 1] << 24 >> 24;
529
609
  at += 2;
610
+ this.locals.set(slot, {
611
+ kind: "number",
612
+ value: this.number(this.locals.get(slot) ?? { kind: "unknown" }) + step
613
+ });
530
614
  break;
615
+ }
531
616
  case RETURNS.has(op):
532
617
  if (op !== 177) this.returned = this.pop();
533
618
  return true;
534
- case op === 178:
619
+ case op === 178: {
620
+ const index = code[at] << 8 | code[at + 1];
535
621
  at += 2;
536
- this.push({ kind: "unknown" });
622
+ const reference = constants.reference(index);
623
+ const key = reference ? `${reference.owner}.${reference.name}` : "";
624
+ if (reference && !this.shared.statics.has(key)) this.initialise(reference.owner);
625
+ this.push(this.shared.statics.get(key) ?? { kind: "unknown" });
537
626
  break;
538
- case op === 179:
627
+ }
628
+ case op === 179: {
629
+ const index = code[at] << 8 | code[at + 1];
539
630
  at += 2;
540
- this.pop();
631
+ const value = this.pop();
632
+ const reference = constants.reference(index);
633
+ if (reference) this.shared.statics.set(`${reference.owner}.${reference.name}`, value);
541
634
  break;
635
+ }
542
636
  case op === 180: {
543
637
  const index = code[at] << 8 | code[at + 1];
544
638
  at += 2;
@@ -574,6 +668,7 @@ var Interpreter = class Interpreter {
574
668
  const taken = [];
575
669
  for (let count = 0; count < args.length; count++) taken.unshift(this.pop());
576
670
  const objectRef = this.pop();
671
+ if (objectRef.kind === "object" && args.length > 0 && args.length <= 3 && args.every((arg) => arg === "F")) objectRef.handle.deformation = Math.max(...taken.map((value) => this.number(value)));
577
672
  if (objectRef.kind === "object" && args.length >= 2 && args.at(-1) === "I" && args.at(-2) === "I") {
578
673
  objectRef.handle.pendingUv = [this.number(taken[args.length - 2]), this.number(taken[args.length - 1])];
579
674
  objectRef.handle.uvFromConstructor = true;
@@ -591,22 +686,18 @@ var Interpreter = class Interpreter {
591
686
  handle: this.handle()
592
687
  });
593
688
  break;
594
- case op === 188:
595
- at += 1;
596
- this.pop();
689
+ case op === 188 || op === 189: {
690
+ at += op === 188 ? 1 : 2;
691
+ const length = this.pop();
692
+ const size = length.kind === "number" ? Math.min(Math.max(length.value, 0), MAX_ARRAY) : 0;
597
693
  this.push({
598
694
  kind: "array",
599
- items: []
600
- });
601
- break;
602
- case op === 189:
603
- at += 2;
604
- this.pop();
605
- this.push({
606
- kind: "array",
607
- items: []
695
+ items: Array.from({ length: size }, () => {
696
+ return { kind: "unknown" };
697
+ })
608
698
  });
609
699
  break;
700
+ }
610
701
  case op === 192:
611
702
  at += 2;
612
703
  break;
@@ -619,14 +710,28 @@ var Interpreter = class Interpreter {
619
710
  returns: "V"
620
711
  };
621
712
  for (let count = 0; count < args.length; count++) this.pop();
622
- this.push(returns === "V" ? null : { kind: "unknown" });
713
+ const target = constants.lambdaTarget(index);
714
+ this.push(returns === "V" ? null : target ? {
715
+ kind: "lambda",
716
+ ...target
717
+ } : { kind: "unknown" });
623
718
  break;
624
719
  }
625
720
  case op >= 153 && op <= 166: {
626
- const comparisons = op >= 159 ? 2 : 1;
627
- for (let count = 0; count < comparisons; count++) this.pop();
721
+ const twoOperands = op >= 159;
722
+ const right = this.pop();
723
+ const left = twoOperands ? this.pop() : {
724
+ kind: "number",
725
+ value: 0
726
+ };
727
+ const offset = (code[at] << 8 | code[at + 1]) << 16 >> 16;
628
728
  at += 2;
629
- this.shared.branched = true;
729
+ const decided = left.kind === "number" && right.kind === "number" && op <= 164 ? compare(op, twoOperands ? left.value : right.value, twoOperands ? right.value : 0) : null;
730
+ if (decided === null) {
731
+ this.shared.branched = true;
732
+ break;
733
+ }
734
+ if (decided) at += offset - 3;
630
735
  break;
631
736
  }
632
737
  case op === 198 || op === 199:
@@ -636,12 +741,19 @@ var Interpreter = class Interpreter {
636
741
  break;
637
742
  case op === 167: {
638
743
  const offset = (code[at] << 8 | code[at + 1]) << 16 >> 16;
639
- if (offset <= 0) {
640
- this.shared.notes.push("backward goto: the builder loops, so its geometry is not recoverable");
641
- return false;
744
+ at += 2;
745
+ if (offset > 0) {
746
+ at += offset - 3;
747
+ break;
642
748
  }
643
- at += offset - 1;
644
- this.shared.branched = true;
749
+ const site = at - 3;
750
+ const round = (this.loops.get(site) ?? 0) + 1;
751
+ this.loops.set(site, round);
752
+ if (round > MAX_ITERATIONS) {
753
+ this.note(`a loop at ${site} went round more than ${MAX_ITERATIONS} times, so the shape is only as far as it got`);
754
+ break;
755
+ }
756
+ at += offset - 3;
645
757
  break;
646
758
  }
647
759
  case op >= 148 && op <= 152:
@@ -1,4 +1,4 @@
1
- import { t as parseClassFile } from "./classfile-CAhS5qNf.js";
1
+ import { t as parseClassFile } from "./classfile-Bo7FUrrD.js";
2
2
  //#region src/entities/model-code.ts
3
3
  /**
4
4
  * Which classes in a jar count as model code, and their parsed form.
@@ -1,4 +1,4 @@
1
- import { t as ClassFile } from "./classfile-BwaQjnUM.js";
1
+ import { t as ClassFile } from "./classfile-BdLIk4Pe.js";
2
2
  //#region src/entities/model-code.d.ts
3
3
  interface ModelCode {
4
4
  /** The parsed class, when it is model code a call may be followed into. */
@@ -11,7 +11,7 @@ import { n as LayeredResourceManager, t as FolderResourceManager } from "./manag
11
11
  import { n as TextureLoader } from "./loader-C18GpurU.js";
12
12
  import { t as encodeAnimatedIcon } from "./encode-LR9wxigC.js";
13
13
  import { a as builtinDynamicResources } from "./generators-BCZVlWig.js";
14
- import { a as spawnEggTints, i as renderEntityFallback, n as dyedVariantTint, r as renderDyedVariantFallback, t as dyeVariantName } from "./entity-fallback-Bth0fa7m.js";
14
+ import { a as spawnEggTints, i as renderEntityFallback, n as dyedVariantTint, r as renderDyedVariantFallback, t as dyeVariantName } from "./entity-fallback-BcJWh91-.js";
15
15
  import { t as legacyTintsForChain } from "./legacy-tints-l-541wgI.js";
16
16
  import { n as defaultBlockStateModel, t as blockStateTransform } from "./blockstate-DVb8Mhxc.js";
17
17
  import path from "node:path";
@@ -1,2 +1,2 @@
1
- import { a as spawnEggTints, i as renderEntityFallback, n as dyedVariantTint, r as renderDyedVariantFallback, t as dyeVariantName } from "../entity-fallback-Bth0fa7m.js";
1
+ import { a as spawnEggTints, i as renderEntityFallback, n as dyedVariantTint, r as renderDyedVariantFallback, t as dyeVariantName } from "../entity-fallback-BcJWh91-.js";
2
2
  export { dyeVariantName, dyedVariantTint, renderDyedVariantFallback, renderEntityFallback, spawnEggTints };
@@ -1,2 +1,2 @@
1
- import { t as ItemPipeline } from "../pipeline-BUIMzMzg.js";
1
+ import { t as ItemPipeline } from "../pipeline-7rvL6vx1.js";
2
2
  export { ItemPipeline };
@@ -12,6 +12,18 @@ declare const HEAD_LAYOUTS: Record<string, string>;
12
12
  * live at entity/<mob>.png or entity/<mob>/<mob>.png depending on the mod, and a
13
13
  * head only ever wants the base skin — never a blink or armour overlay.
14
14
  */
15
+ /**
16
+ * Directories that hold an *overlay* rather than a mob.
17
+ *
18
+ * Matched as whole path segments rather than as tokens, because a token would take `chest_boat` out
19
+ * along with `chest`. A trader llama is the case that needed it: its sheet is filed as
20
+ * `entity/llama/decor/trader_llama`, whose leaf is exactly the mob's name — so a search by name found
21
+ * the carpet it wears, which is a few opaque pixels on a transparent sheet, and the llama underneath
22
+ * came out invisible on most of its faces.
23
+ */
24
+ declare const OVERLAY_DIRECTORIES: Set<string>;
25
+ /** Whether a texture is filed inside one of those, whatever the file itself is called. */
26
+ declare function isOverlayPath(path: string): boolean;
15
27
  declare function findMobTexture(resources: ResourceManager, namespace: string, mob: string): Promise<ResourceLocator | null>;
16
28
  /**
17
29
  * Find the mod's own chest sheet for a chest-like item by token overlap over
@@ -48,4 +60,4 @@ declare function findFluidStill(resources: ResourceManager, namespace: string, b
48
60
  */
49
61
  declare function findFluidSibling(resources: ResourceManager, still: ResourceLocator, suffixes: string[]): Promise<ResourceLocator | null>;
50
62
  //#endregion
51
- export { DYE_COLORS, HEAD_LAYOUTS, coversSheet, findBedTexture, findChestTexture, findEntityTexture, findFluidSibling, findFluidStill, findMobTexture, tokens };
63
+ export { DYE_COLORS, HEAD_LAYOUTS, OVERLAY_DIRECTORIES, coversSheet, findBedTexture, findChestTexture, findEntityTexture, findFluidSibling, findFluidStill, findMobTexture, isOverlayPath, tokens };
@@ -1,2 +1,2 @@
1
- import { a as findChestTexture, c as findFluidStill, i as findBedTexture, l as findMobTexture, n as HEAD_LAYOUTS, o as findEntityTexture, r as coversSheet, s as findFluidSibling, t as DYE_COLORS, u as tokens } from "../entity-textures-DFRIVdF1.js";
2
- export { DYE_COLORS, HEAD_LAYOUTS, coversSheet, findBedTexture, findChestTexture, findEntityTexture, findFluidSibling, findFluidStill, findMobTexture, tokens };
1
+ import { a as findBedTexture, c as findFluidSibling, d as isOverlayPath, f as tokens, i as coversSheet, l as findFluidStill, n as HEAD_LAYOUTS, o as findChestTexture, r as OVERLAY_DIRECTORIES, s as findEntityTexture, t as DYE_COLORS, u as findMobTexture } from "../entity-textures-4XXT8jVI.js";
2
+ export { DYE_COLORS, HEAD_LAYOUTS, OVERLAY_DIRECTORIES, coversSheet, findBedTexture, findChestTexture, findEntityTexture, findFluidSibling, findFluidStill, findMobTexture, isOverlayPath, tokens };
@@ -6,7 +6,7 @@ import { r as encodeRasterAsset } from "./encode-LR9wxigC.js";
6
6
  import { t as classifyItemStyle } from "./item-styles-DjYRdyHi.js";
7
7
  import { t as minifyStylesheet } from "./minify-bjMRmr5J.js";
8
8
  import { t as generateStylesheet } from "./stylesheet-CovT3B2S.js";
9
- import { t as ItemPipeline } from "./pipeline-BUIMzMzg.js";
9
+ import { t as ItemPipeline } from "./pipeline-7rvL6vx1.js";
10
10
  import { t as describeResources } from "./descriptor-JyXhfd-c.js";
11
11
  import { t as WorkerRenderPool } from "./pool-CKGp1qup.js";
12
12
  import path from "node:path";
package/dist/run.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import { o as evaluateItemModel } from "./definition-ColsvWrH.js";
2
- import { t as run } from "./run-BQJkqc5f.js";
2
+ import { t as run } from "./run-BXAqZ5qY.js";
3
3
  export { evaluateItemModel, run };
@@ -1,4 +1,4 @@
1
- import { t as ItemPipeline } from "../pipeline-BUIMzMzg.js";
1
+ import { t as ItemPipeline } from "../pipeline-7rvL6vx1.js";
2
2
  import { n as restoreResources } from "../descriptor-JyXhfd-c.js";
3
3
  import sharp from "sharp";
4
4
  import { parentPort, workerData } from "node:worker_threads";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodecraft/rendermite",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
4
4
  "description": "Minecraft item icon renderer: renders inventory-identical item icons from vanilla and modded assets across game versions, outputting optimized PNG/WebP/AVIF plus CSS and a JSON item library.",
5
5
  "type": "module",
6
6
  "license": "MIT",