@nodecraft/rendermite 1.5.0 → 1.6.1

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 (83) hide show
  1. package/README.md +2 -0
  2. package/dist/blocks/cli.js +1 -1
  3. package/dist/blocks/entities.d.ts +30 -0
  4. package/dist/blocks/entities.js +2 -0
  5. package/dist/blocks/entity-geo.d.ts +18 -0
  6. package/dist/blocks/entity-geo.js +2 -0
  7. package/dist/blocks/entity-models.d.ts +45 -0
  8. package/dist/blocks/entity-models.js +2 -0
  9. package/dist/blocks/export.d.ts +1 -1
  10. package/dist/blocks/export.js +1 -1
  11. package/dist/blocks/item-sprites.d.ts +1 -1
  12. package/dist/blocks/obj-mesh.js +1 -1
  13. package/dist/blocks/pack-format.d.ts +2 -2
  14. package/dist/blocks/sprite-sheet.d.ts +1 -1
  15. package/dist/classfile-BdLIk4Pe.d.ts +67 -0
  16. package/dist/classfile-Bo7FUrrD.js +287 -0
  17. package/dist/{cli-DMTaIbDI.js → cli-D2KJc_yl.js} +28 -4
  18. package/dist/cli.js +4 -4
  19. package/dist/entities/classfile.d.ts +2 -0
  20. package/dist/entities/classfile.js +2 -0
  21. package/dist/entities/geo-model.d.ts +13 -0
  22. package/dist/entities/geo-model.js +2 -0
  23. package/dist/entities/inherit.d.ts +29 -0
  24. package/dist/entities/inherit.js +2 -0
  25. package/dist/entities/jar-classes.d.ts +2 -0
  26. package/dist/entities/jar-classes.js +2 -0
  27. package/dist/entities/link.d.ts +71 -0
  28. package/dist/entities/link.js +2 -0
  29. package/dist/entities/mappings.d.ts +2 -0
  30. package/dist/entities/mappings.js +2 -0
  31. package/dist/entities/merge.d.ts +18 -0
  32. package/dist/entities/merge.js +2 -0
  33. package/dist/entities/model-bytecode.d.ts +31 -0
  34. package/dist/entities/model-bytecode.js +2 -0
  35. package/dist/entities/model-code.d.ts +2 -0
  36. package/dist/entities/model-code.js +2 -0
  37. package/dist/entities/model.d.ts +2 -0
  38. package/dist/entities/model.js +1 -0
  39. package/dist/entities-DvmA9fEV.js +175 -0
  40. package/dist/{entity-fallback-DutR9Hdz.js → entity-fallback-Bth0fa7m.js} +1 -1
  41. package/dist/entity-geo-B2CkuMWx.js +100 -0
  42. package/dist/entity-models-_EZ3PVtN.js +127 -0
  43. package/dist/{export-wvt0Fryo.d.ts → export-B8tPsf4K.d.ts} +22 -1
  44. package/dist/{export-sadR0pWp.js → export-BfEIwSxY.js} +93 -1
  45. package/dist/geo-CmjrXu0G.d.ts +59 -0
  46. package/dist/{geo-BC8jtG5Q.js → geo-Desq-gRC.js} +6 -5
  47. package/dist/geo-model-Bo1gpEwX.js +163 -0
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.js +8 -8
  50. package/dist/inherit-ByHjOJX3.js +77 -0
  51. package/dist/jar-classes-CSRrPAUy.d.ts +41 -0
  52. package/dist/jar-classes-DCtELr1i.js +102 -0
  53. package/dist/link-5GmBLTzk.js +355 -0
  54. package/dist/mappings-BEPVaiwE.js +139 -0
  55. package/dist/mappings-DxX4KoZl.d.ts +75 -0
  56. package/dist/merge-NDYxpmL8.js +48 -0
  57. package/dist/model-PnQ4gV0-.d.ts +49 -0
  58. package/dist/model-bytecode-gWy5_uCj.js +742 -0
  59. package/dist/model-code-1Vuk-zj_.js +49 -0
  60. package/dist/model-code-BY44ao27.d.ts +16 -0
  61. package/dist/mods/prepare.js +1 -1
  62. package/dist/{obj-BaGvPUbb.js → obj-CJHEHpki.js} +5 -5
  63. package/dist/{obj-mesh-CazmV8ya.js → obj-mesh-JwPkZEWL.js} +1 -1
  64. package/dist/{pack-format-oRExIeD3.d.ts → pack-format-QcVFxG_b.d.ts} +100 -1
  65. package/dist/pack.d.ts +1 -1
  66. package/dist/{pipeline-AAfohoHU.js → pipeline-BUIMzMzg.js} +4 -4
  67. package/dist/render/entity-fallback.js +1 -1
  68. package/dist/render/face.js +1 -1
  69. package/dist/render/geo.d.ts +2 -54
  70. package/dist/render/geo.js +2 -2
  71. package/dist/render/obj.js +1 -1
  72. package/dist/render/pipeline.js +1 -1
  73. package/dist/render/renderer.js +1 -1
  74. package/dist/resources/descriptor.js +1 -1
  75. package/dist/{run-UKgVb-kW.js → run-BQJkqc5f.js} +2 -2
  76. package/dist/run.js +1 -1
  77. package/dist/versions/prepare.js +1 -1
  78. package/dist/workers/render-worker.js +2 -2
  79. package/package.json +1 -1
  80. package/dist/{descriptor-D7VKgeGT.js → descriptor-JyXhfd-c.js} +1 -1
  81. package/dist/{face-CMJ3b3Ae.js → face-B2JjWT46.js} +1 -1
  82. package/dist/{prepare-8LswFoS-.js → prepare-D49Gu4Lv.js} +1 -1
  83. package/dist/{prepare-B_tRy9lB.js → prepare-Myd_ec4v.js} +1 -1
@@ -0,0 +1,742 @@
1
+ import { t as parseClassFile } from "./classfile-Bo7FUrrD.js";
2
+ import { i as mergeModels, r as cubesOf, t as buildsOwnMesh } from "./merge-NDYxpmL8.js";
3
+ //#region src/entities/model-bytecode.ts
4
+ /**
5
+ * Abstract interpretation of a boxy-model builder, enough to recover the geometry.
6
+ *
7
+ * The whole premise of the entity-geometry extractor: entity models are Java code, but the code is a
8
+ * straight line of constant-argument calls into a tiny builder vocabulary. Vanilla 1.17+ builds a
9
+ * `MeshDefinition` of `PartDefinition`s from `CubeListBuilder`s; pre-1.17 vanilla and most mods poke
10
+ * `ModelRenderer`-alikes directly. Both are the same eight operations.
11
+ *
12
+ * Calls are classified by **descriptor shape, not class name**. That is what makes one interpreter
13
+ * cover obfuscated vanilla (`fyp.a(FFFFFF)Lfyp;`) and a mod shipping its own copy of the API
14
+ * (`AdvancedModelBox.addBox(FFFFFFFZ)V`) without a mapping file or a per-mod table.
15
+ *
16
+ * Conditionals are taken and recorded. A model builder's branches are all on a constructor argument —
17
+ * a `CubeDeformation`, a baby-variant flag — so every arm builds a real shape and the fall-through is
18
+ * one of them; the note says so. A *loop* is refused outright, because following it would emit the
19
+ * same boxes twice or spin, and a wrong shape is worse than a recorded gap.
20
+ */
21
+ /** Argument slot count and shape of a descriptor's parameter list. */
22
+ function parseDescriptor(descriptor) {
23
+ const args = [];
24
+ let at = 1;
25
+ while (descriptor[at] !== ")") {
26
+ const start = at;
27
+ while (descriptor[at] === "[") at++;
28
+ if (descriptor[at] === "L") at = descriptor.indexOf(";", at) + 1;
29
+ else at++;
30
+ args.push(descriptor.slice(start, at));
31
+ }
32
+ return {
33
+ args,
34
+ returns: descriptor.slice(at + 1)
35
+ };
36
+ }
37
+ const NUMERIC = /* @__PURE__ */ new Set([
38
+ "F",
39
+ "D",
40
+ "I",
41
+ "J",
42
+ "S",
43
+ "B",
44
+ "C"
45
+ ]);
46
+ /** Every entity sheet is a power of two, at least 16 in each direction. A texture offset rarely is. */
47
+ function isSheetSize(width, height) {
48
+ const power = (value) => value >= 16 && (value & value - 1) === 0;
49
+ return power(width) && power(height);
50
+ }
51
+ /** `ldc`, `ldc_w`, `ldc2_w`: a constant, narrow or wide index. */
52
+ const LOAD_CONSTANT = /* @__PURE__ */ new Set([
53
+ 18,
54
+ 19,
55
+ 20
56
+ ]);
57
+ /** `lconst_0`/`lconst_1` and `dconst_0`/`dconst_1`, which push 0 and 1. */
58
+ const LONG_OR_DOUBLE_CONSTANT = /* @__PURE__ */ new Set([
59
+ 9,
60
+ 10,
61
+ 14,
62
+ 15
63
+ ]);
64
+ /** Every `return` that ends a method, with or without a value. */
65
+ const RETURNS = /* @__PURE__ */ new Set([
66
+ 172,
67
+ 173,
68
+ 174,
69
+ 175,
70
+ 176,
71
+ 177
72
+ ]);
73
+ /**
74
+ * Every array load: reading back an element of an array the builder filled. All eight, because
75
+ * missing one costs a whole model — `daload` alone was the ender dragon.
76
+ */
77
+ const ARRAY_LOADS = /* @__PURE__ */ new Set([
78
+ 46,
79
+ 47,
80
+ 48,
81
+ 49,
82
+ 50,
83
+ 51,
84
+ 52,
85
+ 53
86
+ ]);
87
+ /** Every array store, the mirror of `ARRAY_LOADS`: `dastore` alone was the silverfish. */
88
+ const ARRAY_STORES = /* @__PURE__ */ new Set([
89
+ 79,
90
+ 80,
91
+ 81,
92
+ 82,
93
+ 83,
94
+ 84,
95
+ 85,
96
+ 86
97
+ ]);
98
+ /** The four invoke forms. `invokeinterface` carries two extra bytes; `invokestatic` has no receiver. */
99
+ const INVOKES = /* @__PURE__ */ new Set([
100
+ 182,
101
+ 183,
102
+ 184,
103
+ 185
104
+ ]);
105
+ /** Field names a model writes its texture dimensions to, across the eras and the loader APIs. */
106
+ const TEXTURE_WIDTH_FIELDS = /* @__PURE__ */ new Set(["texWidth", "textureWidth"]);
107
+ const TEXTURE_HEIGHT_FIELDS = /* @__PURE__ */ new Set(["texHeight", "textureHeight"]);
108
+ /** Deep enough for `<init>` → `super()` → `super()` and the static factories that mirror it. */
109
+ const MAX_INLINE_DEPTH = 5;
110
+ /** Frames per method. A model spends a handful; the cap is there so a strange jar cannot spend all day. */
111
+ const INLINE_BUDGET = 96;
112
+ /** How far up a hierarchy to look for an inherited method, which is as deep as a model hierarchy goes. */
113
+ const MAX_METHOD_HOPS = 6;
114
+ function newShared(resolve) {
115
+ return {
116
+ handles: [],
117
+ fields: /* @__PURE__ */ new Map(),
118
+ notes: [],
119
+ textureSize: [64, 32],
120
+ branched: false,
121
+ nextHandle: 1,
122
+ resolve,
123
+ budget: INLINE_BUDGET
124
+ };
125
+ }
126
+ /** The method a call names, looked up where the JVM would look: the owner, then up its ancestors. */
127
+ function findMethod(resolve, owner, name, descriptor) {
128
+ let at = owner;
129
+ for (let hops = 0; at && hops < MAX_METHOD_HOPS; hops++) {
130
+ const classFile = resolve(at);
131
+ if (!classFile) return null;
132
+ const method = classFile.methods.find((candidate) => candidate.name === name && candidate.descriptor === descriptor && candidate.code);
133
+ if (method?.code) return {
134
+ code: method.code,
135
+ classFile
136
+ };
137
+ at = classFile.superClass;
138
+ }
139
+ return null;
140
+ }
141
+ var Interpreter = class Interpreter {
142
+ stack = [];
143
+ locals = /* @__PURE__ */ new Map();
144
+ shared;
145
+ depth;
146
+ /** What this frame returned, for the caller that inlined it. */
147
+ returned = null;
148
+ constructor(shared, depth = 0) {
149
+ this.shared = shared;
150
+ this.depth = depth;
151
+ }
152
+ handle() {
153
+ const created = {
154
+ id: this.shared.nextHandle++,
155
+ cubes: [],
156
+ children: [],
157
+ pendingUv: [0, 0],
158
+ pendingInflate: 0,
159
+ pendingMirror: false
160
+ };
161
+ this.shared.handles.push(created);
162
+ return created;
163
+ }
164
+ /** Puts the receiver and arguments where the callee's bytecode reads them; longs take two slots. */
165
+ seed(receiver, descriptor, args) {
166
+ let slot = 0;
167
+ if (receiver) this.locals.set(slot++, receiver);
168
+ for (const [index, shape] of parseDescriptor(descriptor).args.entries()) {
169
+ this.locals.set(slot, args[index] ?? { kind: "unknown" });
170
+ slot += shape === "J" || shape === "D" ? 2 : 1;
171
+ }
172
+ }
173
+ /**
174
+ * Interprets a call into the model's own code, with the arguments it was actually given.
175
+ *
176
+ * This is what makes an inherited part come out right. `CowModel` calls `super(12, 0)` and the legs
177
+ * it inherits are `addBox(-2, 0, -2, 4, cubeHeight, 4)`: without the 12 they are boxes of height
178
+ * zero, and a cow with four invisible legs looks exactly like a bug in the mesher.
179
+ *
180
+ * Restricted to model code on purpose. The builder API is in the same jar, and `CubeListBuilder`
181
+ * must keep its abstract treatment rather than being followed into.
182
+ */
183
+ inline(owner, name, descriptor, receiver, args) {
184
+ if (this.depth >= MAX_INLINE_DEPTH || this.shared.budget <= 0) return null;
185
+ const target = findMethod(this.shared.resolve, owner, name, descriptor);
186
+ if (!target) return null;
187
+ this.shared.budget--;
188
+ const frame = new Interpreter(this.shared, this.depth + 1);
189
+ frame.seed(receiver, descriptor, args);
190
+ return frame.run(target.code, target.classFile) ? frame.returned : null;
191
+ }
192
+ pop() {
193
+ return this.stack.pop() ?? { kind: "unknown" };
194
+ }
195
+ number(value) {
196
+ return value.kind === "number" ? value.value : 0;
197
+ }
198
+ /**
199
+ * Applies one call. Everything is decided by the descriptor: how many arguments to take, whether
200
+ * there is a receiver, and which of the builder operations the shape corresponds to.
201
+ */
202
+ call(owner, name, descriptor, hasReceiver) {
203
+ const { args, returns } = parseDescriptor(descriptor);
204
+ const taken = [];
205
+ for (let index = args.length - 1; index >= 0; index--) taken.unshift(this.pop());
206
+ const receiver = hasReceiver ? this.pop() : null;
207
+ const target = receiver?.kind === "object" ? receiver.handle : null;
208
+ const numbers = taken.map((value) => this.number(value));
209
+ const floats = args.filter((arg) => NUMERIC.has(arg)).length;
210
+ if (args.length === 2 && args.every((arg) => arg === "I") && target) {
211
+ const known = taken.every((value) => value.kind === "number");
212
+ if (known && target.uvFromConstructor && isSheetSize(numbers[0], numbers[1])) this.shared.textureSize = [numbers[0], numbers[1]];
213
+ else if (known) target.pendingUv = [numbers[0], numbers[1]];
214
+ this.push(returns === "V" ? null : receiver);
215
+ return;
216
+ }
217
+ const named = args[0] === "Ljava/lang/String;" && floats >= 6 && args.length >= 7 && NUMERIC.has(args[6]);
218
+ const first = named ? 1 : 0;
219
+ if (target && (named || floats >= 6 && args.length >= 6 && NUMERIC.has(args[0]) && NUMERIC.has(args[5]))) {
220
+ const extra = args.slice(first + 6);
221
+ target.cubes.push({
222
+ origin: [
223
+ numbers[first],
224
+ numbers[first + 1],
225
+ numbers[first + 2]
226
+ ],
227
+ size: [
228
+ numbers[first + 3],
229
+ numbers[first + 4],
230
+ numbers[first + 5]
231
+ ],
232
+ uv: [...target.pendingUv],
233
+ inflate: extra.some((arg) => NUMERIC.has(arg)) ? this.number(taken[first + 6]) : target.pendingInflate,
234
+ mirror: target.pendingMirror || extra.includes("Z") && this.number(taken[args.length - 1]) === 1
235
+ });
236
+ this.push(returns === "V" ? null : receiver);
237
+ return;
238
+ }
239
+ if (args.length === 3 && args.every((arg) => NUMERIC.has(arg))) {
240
+ if (target) {
241
+ target.pivot = [
242
+ numbers[0],
243
+ numbers[1],
244
+ numbers[2]
245
+ ];
246
+ this.push(returns === "V" ? null : receiver);
247
+ } else {
248
+ const pose = this.handle();
249
+ pose.pivot = [
250
+ numbers[0],
251
+ numbers[1],
252
+ numbers[2]
253
+ ];
254
+ this.push({
255
+ kind: "object",
256
+ handle: pose
257
+ });
258
+ }
259
+ return;
260
+ }
261
+ if (args.length === 6 && args.every((arg) => NUMERIC.has(arg)) && !target) {
262
+ const pose = this.handle();
263
+ pose.pivot = [
264
+ numbers[0],
265
+ numbers[1],
266
+ numbers[2]
267
+ ];
268
+ pose.rotation = [
269
+ numbers[3],
270
+ numbers[4],
271
+ numbers[5]
272
+ ];
273
+ this.push({
274
+ kind: "object",
275
+ handle: pose
276
+ });
277
+ return;
278
+ }
279
+ if (args.length === 3 && args[0] === "Ljava/lang/String;" && taken[1].kind === "object") {
280
+ const child = this.handle();
281
+ child.name = taken[0].kind === "string" ? taken[0].value : `part${child.id}`;
282
+ const builder = taken[1].handle;
283
+ const pose = taken[2].kind === "object" ? taken[2].handle : null;
284
+ child.cubes = [...builder.cubes];
285
+ child.pivot = pose?.pivot ?? [
286
+ 0,
287
+ 0,
288
+ 0
289
+ ];
290
+ child.rotation = pose?.rotation ?? [
291
+ 0,
292
+ 0,
293
+ 0
294
+ ];
295
+ builder.consumed = true;
296
+ if (pose) pose.consumed = true;
297
+ if (target) {
298
+ const existing = target.children.findIndex((sibling) => sibling.name === child.name);
299
+ if (existing === -1) target.children.push(child);
300
+ else {
301
+ target.children[existing].consumed = true;
302
+ target.children[existing] = child;
303
+ }
304
+ } else child.orphaned = true;
305
+ this.push({
306
+ kind: "object",
307
+ handle: child
308
+ });
309
+ return;
310
+ }
311
+ const lambda = taken.find((value) => value.kind === "lambda");
312
+ if (lambda?.kind === "lambda" && receiver?.kind === "object" && taken.length === 1) {
313
+ const applied = this.inline(lambda.owner, lambda.name, lambda.descriptor, null, [receiver]);
314
+ this.push(returns === "V" ? null : applied ?? receiver);
315
+ return;
316
+ }
317
+ if (args.length === 1 && args[0] === "Ljava/lang/String;" && returns.startsWith("L") && target) {
318
+ const name_ = taken[0].kind === "string" ? taken[0].value : "";
319
+ const found = target.children.find((child) => child.name === name_);
320
+ if (found) this.push({
321
+ kind: "object",
322
+ handle: found
323
+ });
324
+ else {
325
+ const child = this.handle();
326
+ child.name = name_ || `part${child.id}`;
327
+ target.children.push(child);
328
+ this.push({
329
+ kind: "object",
330
+ handle: child
331
+ });
332
+ }
333
+ return;
334
+ }
335
+ if (args.length === 1 && args[0].startsWith("L") && target && taken[0].kind === "object" && (taken[0].handle.cubes.length > 0 || taken[0].handle.children.length > 0)) {
336
+ target.children.push(taken[0].handle);
337
+ this.push(returns === "V" ? null : receiver);
338
+ return;
339
+ }
340
+ const objects = taken.filter((value) => value.kind === "object");
341
+ if (!target && objects.length === 1 && taken.length - objects.length === args.length - 1 && args.length >= 2 && args.slice(1).every((arg) => NUMERIC.has(arg))) {
342
+ if (args.length === 3) this.shared.textureSize = [numbers[1], numbers[2]];
343
+ this.push(objects[0]);
344
+ return;
345
+ }
346
+ if (args.length === 1 && args[0] === "Z" && target) {
347
+ target.pendingMirror = this.number(taken[0]) === 1;
348
+ this.push(returns === "V" ? null : receiver);
349
+ return;
350
+ }
351
+ if (args.length === 0 && returns.startsWith("L")) {
352
+ if (target) {
353
+ target.isRoot = true;
354
+ this.push(receiver);
355
+ return;
356
+ }
357
+ const built = this.inline(owner, name, descriptor, null, taken);
358
+ this.push(built ?? {
359
+ kind: "object",
360
+ handle: this.handle()
361
+ });
362
+ return;
363
+ }
364
+ const inlined = this.inline(owner, name, descriptor, receiver, taken);
365
+ this.push(returns === "V" ? null : inlined ?? { kind: "unknown" });
366
+ }
367
+ push(value) {
368
+ if (value) this.stack.push(value);
369
+ }
370
+ /** Runs one method's code. Returns false when it hit something it will not guess at. */
371
+ run(code, constants) {
372
+ let at = 0;
373
+ while (at < code.length) {
374
+ const op = code[at++];
375
+ switch (true) {
376
+ case op === 1:
377
+ this.push({ kind: "unknown" });
378
+ break;
379
+ case op >= 2 && op <= 8:
380
+ this.push({
381
+ kind: "number",
382
+ value: op - 3
383
+ });
384
+ break;
385
+ case LONG_OR_DOUBLE_CONSTANT.has(op):
386
+ this.push({
387
+ kind: "number",
388
+ value: op === 10 || op === 15 ? 1 : 0
389
+ });
390
+ break;
391
+ case op >= 11 && op <= 13:
392
+ this.push({
393
+ kind: "number",
394
+ value: op - 11
395
+ });
396
+ break;
397
+ case op === 16:
398
+ this.push({
399
+ kind: "number",
400
+ value: code[at++] << 24 >> 24
401
+ });
402
+ break;
403
+ case op === 17:
404
+ this.push({
405
+ kind: "number",
406
+ value: (code[at] << 8 | code[at + 1]) << 16 >> 16
407
+ });
408
+ at += 2;
409
+ break;
410
+ case LOAD_CONSTANT.has(op): {
411
+ let index;
412
+ if (op === 18) index = code[at++];
413
+ else {
414
+ index = code[at] << 8 | code[at + 1];
415
+ at += 2;
416
+ }
417
+ const value = constants.literal(index);
418
+ if (typeof value === "string") this.push({
419
+ kind: "string",
420
+ value
421
+ });
422
+ else if (typeof value === "number") this.push({
423
+ kind: "number",
424
+ value
425
+ });
426
+ else if (typeof value === "bigint") this.push({
427
+ kind: "number",
428
+ value: Number(value)
429
+ });
430
+ else this.push({ kind: "unknown" });
431
+ break;
432
+ }
433
+ case op >= 21 && op <= 25:
434
+ this.push(this.locals.get(code[at++]) ?? { kind: "unknown" });
435
+ break;
436
+ case op >= 26 && op <= 45:
437
+ this.push(this.locals.get((op - 26) % 4) ?? { kind: "unknown" });
438
+ break;
439
+ case op >= 54 && op <= 58:
440
+ this.locals.set(code[at++], this.pop());
441
+ break;
442
+ case op >= 59 && op <= 78:
443
+ this.locals.set((op - 59) % 4, this.pop());
444
+ break;
445
+ case ARRAY_LOADS.has(op): {
446
+ this.pop();
447
+ const array = this.pop();
448
+ this.push(array.kind === "array" ? array.items[0] ?? { kind: "unknown" } : { kind: "unknown" });
449
+ break;
450
+ }
451
+ case op >= 120 && op <= 131:
452
+ this.pop();
453
+ this.pop();
454
+ this.push({
455
+ kind: "number",
456
+ value: 0
457
+ });
458
+ break;
459
+ case op === 193:
460
+ at += 2;
461
+ this.pop();
462
+ this.push({
463
+ kind: "number",
464
+ value: 0
465
+ });
466
+ break;
467
+ case op === 191: return true;
468
+ case op === 190:
469
+ this.pop();
470
+ this.push({
471
+ kind: "number",
472
+ value: 0
473
+ });
474
+ break;
475
+ case ARRAY_STORES.has(op): {
476
+ const value = this.pop();
477
+ this.pop();
478
+ const array = this.pop();
479
+ if (array.kind === "array") array.items.push(value);
480
+ break;
481
+ }
482
+ case op === 87:
483
+ this.pop();
484
+ break;
485
+ case op === 88:
486
+ this.pop();
487
+ this.pop();
488
+ break;
489
+ case op === 89: {
490
+ const top = this.pop();
491
+ this.push(top);
492
+ this.push(top);
493
+ break;
494
+ }
495
+ case op === 90: {
496
+ const top = this.pop();
497
+ const under = this.pop();
498
+ this.push(top);
499
+ this.push(under);
500
+ this.push(top);
501
+ break;
502
+ }
503
+ case op >= 96 && op <= 111: {
504
+ const right = this.number(this.pop());
505
+ const left = this.number(this.pop());
506
+ const operate = [
507
+ () => left + right,
508
+ () => left - right,
509
+ () => left * right,
510
+ () => left / right
511
+ ][Math.floor((op - 96) / 4)];
512
+ this.push({
513
+ kind: "number",
514
+ value: operate()
515
+ });
516
+ break;
517
+ }
518
+ case op >= 112 && op <= 115: {
519
+ const right = this.number(this.pop());
520
+ const left = this.number(this.pop());
521
+ this.push({
522
+ kind: "number",
523
+ value: right === 0 ? 0 : left % right
524
+ });
525
+ break;
526
+ }
527
+ case op >= 116 && op <= 119:
528
+ this.push({
529
+ kind: "number",
530
+ value: -this.number(this.pop())
531
+ });
532
+ break;
533
+ case op >= 133 && op <= 147: break;
534
+ case op === 132:
535
+ at += 2;
536
+ break;
537
+ case RETURNS.has(op):
538
+ if (op !== 177) this.returned = this.pop();
539
+ return true;
540
+ case op === 178:
541
+ at += 2;
542
+ this.push({ kind: "unknown" });
543
+ break;
544
+ case op === 179:
545
+ at += 2;
546
+ this.pop();
547
+ break;
548
+ case op === 180: {
549
+ const index = code[at] << 8 | code[at + 1];
550
+ at += 2;
551
+ this.pop();
552
+ const reference = constants.reference(index);
553
+ this.push(this.shared.fields.get(reference?.name ?? "") ?? { kind: "unknown" });
554
+ break;
555
+ }
556
+ case op === 181: {
557
+ const index = code[at] << 8 | code[at + 1];
558
+ at += 2;
559
+ const value = this.pop();
560
+ this.pop();
561
+ const reference = constants.reference(index);
562
+ if (reference) {
563
+ const previous = this.shared.fields.get(reference.name);
564
+ if (previous?.kind === "object" && value.kind === "object" && previous.handle !== value.handle) previous.handle.consumed = true;
565
+ this.shared.fields.set(reference.name, value);
566
+ if (value.kind === "object" && !value.handle.name) value.handle.name = reference.name;
567
+ if (TEXTURE_WIDTH_FIELDS.has(reference.name)) this.shared.textureSize[0] = this.number(value);
568
+ else if (TEXTURE_HEIGHT_FIELDS.has(reference.name)) this.shared.textureSize[1] = this.number(value);
569
+ }
570
+ break;
571
+ }
572
+ case INVOKES.has(op): {
573
+ const index = code[at] << 8 | code[at + 1];
574
+ at += 2;
575
+ if (op === 185) at += 2;
576
+ const reference = constants.reference(index);
577
+ if (!reference) break;
578
+ if (op === 183 && reference.name === "<init>") {
579
+ const { args } = parseDescriptor(reference.descriptor);
580
+ const taken = [];
581
+ for (let count = 0; count < args.length; count++) taken.unshift(this.pop());
582
+ const objectRef = this.pop();
583
+ if (objectRef.kind === "object" && args.length >= 2 && args.at(-1) === "I" && args.at(-2) === "I") {
584
+ objectRef.handle.pendingUv = [this.number(taken[args.length - 2]), this.number(taken[args.length - 1])];
585
+ objectRef.handle.uvFromConstructor = true;
586
+ }
587
+ this.inline(reference.owner, reference.name, reference.descriptor, objectRef, taken);
588
+ break;
589
+ }
590
+ this.call(reference.owner, reference.name, reference.descriptor, op !== 184);
591
+ break;
592
+ }
593
+ case op === 187:
594
+ at += 2;
595
+ this.push({
596
+ kind: "object",
597
+ handle: this.handle()
598
+ });
599
+ break;
600
+ case op === 188:
601
+ at += 1;
602
+ this.pop();
603
+ this.push({
604
+ kind: "array",
605
+ items: []
606
+ });
607
+ break;
608
+ case op === 189:
609
+ at += 2;
610
+ this.pop();
611
+ this.push({
612
+ kind: "array",
613
+ items: []
614
+ });
615
+ break;
616
+ case op === 192:
617
+ at += 2;
618
+ break;
619
+ case op === 186: {
620
+ const index = code[at] << 8 | code[at + 1];
621
+ at += 4;
622
+ const reference = constants.reference(index);
623
+ const { args, returns } = reference ? parseDescriptor(reference.descriptor) : {
624
+ args: [],
625
+ returns: "V"
626
+ };
627
+ for (let count = 0; count < args.length; count++) this.pop();
628
+ const target = constants.lambdaTarget(index);
629
+ this.push(returns === "V" ? null : target ? {
630
+ kind: "lambda",
631
+ ...target
632
+ } : { kind: "unknown" });
633
+ break;
634
+ }
635
+ case op >= 153 && op <= 166: {
636
+ const comparisons = op >= 159 ? 2 : 1;
637
+ for (let count = 0; count < comparisons; count++) this.pop();
638
+ at += 2;
639
+ this.shared.branched = true;
640
+ break;
641
+ }
642
+ case op === 198 || op === 199:
643
+ at += 2;
644
+ this.pop();
645
+ this.shared.branched = true;
646
+ break;
647
+ case op === 167: {
648
+ const offset = (code[at] << 8 | code[at + 1]) << 16 >> 16;
649
+ if (offset <= 0) {
650
+ this.shared.notes.push("backward goto: the builder loops, so its geometry is not recoverable");
651
+ return false;
652
+ }
653
+ at += offset - 1;
654
+ this.shared.branched = true;
655
+ break;
656
+ }
657
+ case op >= 148 && op <= 152:
658
+ this.pop();
659
+ this.pop();
660
+ this.push({
661
+ kind: "number",
662
+ value: 0
663
+ });
664
+ break;
665
+ default:
666
+ this.shared.notes.push(`unsupported opcode 0x${op.toString(16)} at ${at - 1}`);
667
+ return false;
668
+ }
669
+ }
670
+ return true;
671
+ }
672
+ };
673
+ /** Turns a handle tree into the exported shape. */
674
+ function toPart(handle) {
675
+ return {
676
+ name: handle.name ?? `part${handle.id}`,
677
+ pivot: handle.pivot ?? [
678
+ 0,
679
+ 0,
680
+ 0
681
+ ],
682
+ rotation: handle.rotation ?? [
683
+ 0,
684
+ 0,
685
+ 0
686
+ ],
687
+ cubes: handle.cubes,
688
+ children: handle.children.map((child) => toPart(child))
689
+ };
690
+ }
691
+ function extractModel(bytes, following) {
692
+ const classFile = parseClassFile(bytes);
693
+ const notes = [];
694
+ const candidates = [];
695
+ const outward = following?.isModelCode ?? true;
696
+ const own = (internalName) => {
697
+ if (internalName === classFile.thisClass) return classFile;
698
+ return outward ? following?.resolve(internalName) ?? null : null;
699
+ };
700
+ for (const method of classFile.methods) {
701
+ if (!method.code) continue;
702
+ const shared = newShared(own);
703
+ let ok;
704
+ try {
705
+ ok = new Interpreter(shared).run(method.code, classFile);
706
+ } catch (err) {
707
+ notes.push(`${method.name}: ${err.message}`);
708
+ continue;
709
+ }
710
+ if (!ok) notes.push(`${method.name}: ${shared.notes.join("; ")}`);
711
+ const adopted = new Set(shared.handles.flatMap((handle) => handle.children));
712
+ const roots = shared.handles.filter((handle) => !adopted.has(handle) && !handle.consumed && (handle.cubes.length > 0 || handle.children.length > 0));
713
+ if (roots.length === 0) continue;
714
+ const orphans = roots.filter((root) => root.orphaned).length;
715
+ const model = {
716
+ textureSize: shared.textureSize,
717
+ parts: roots.map((root) => toPart(root)),
718
+ notes: [
719
+ ...shared.notes,
720
+ ...orphans > 0 ? [`${orphans} disconnected roots: built for a parent in another class, so each sits at the model origin`] : [],
721
+ ...shared.branched ? ["a conditional was taken; the shape is one of the variants this builder makes"] : []
722
+ ]
723
+ };
724
+ candidates.push({
725
+ model,
726
+ ownMesh: buildsOwnMesh(model),
727
+ cubes: cubesOf(model)
728
+ });
729
+ }
730
+ const byCubes = candidates.filter((candidate) => candidate.cubes > 0).toSorted((left, right) => right.cubes - left.cubes);
731
+ const base = byCubes.find((candidate) => candidate.ownMesh) ?? byCubes[0];
732
+ if (!base) return {
733
+ textureSize: [64, 32],
734
+ parts: [],
735
+ notes
736
+ };
737
+ let model = base.model;
738
+ for (const helper of byCubes) if (helper !== base && !helper.ownMesh) model = mergeModels(model, helper.model);
739
+ return model;
740
+ }
741
+ //#endregion
742
+ export { extractModel as t };