refactor: port post microblock client to Java

Retain Scala state and lifecycle bridges with ordered virtual dispatch.
This commit is contained in:
Algent committed 2026-09-03 22:57:12 +02:00
1 parent 47885d15a8
commit 556fd519bb
7 files changed
+135 -68

No files matched your search

+4 -4
View File
@@ -24,13 +24,13 @@ No trustworthy tool will produce a maintainable Java port automatically. A decom
## Current status
Work continues on `algent/java`. The low-coupling queue, core part/tile types, registries, handlers, factories,
placement/render helpers, both generators and all built-in tile traits are Java. Three files still contain generated
placement/render helpers, both generators and all built-in tile traits are Java. Two files still contain generated
microblock implementations in Scala, alongside the compiler/signature/analyser compatibility shells. The common,
face, corner, edge and post traits retain inheritance/bridge declarations over Java behavior. Composite generation,
Java-trait rewriting and compiler startup also delegate to Java helpers. Abstract Java mixins and Java-path side-only
filtering are complete; multiple Scala-trait inheritance still needs its metadata. The next bounded target is
`PostMicroblockClient` in `microblock/EdgeMicroblockTraits.scala`. Retain the post super-dispatch bridge and the
ScalaSignature path-dependent model bridges.
`HollowMicroblock` in `microblock/HollowMicroblockTraits.scala`, followed separately by its client trait. Retain the
post lifecycle/super-dispatch bridges and the ScalaSignature path-dependent model bridges.
Start with [JAVA_MIGRATION_HANDOFF.md](JAVA_MIGRATION_HANDOFF.md) for the exact source/test baseline, workflow and
Java-source limitations. Read both the [ABI inventory](JAVA_MIGRATION_ABI_INVENTORY.md) and
@@ -367,7 +367,7 @@ cache remain reference-identical; pass-through-interface coverage remains green.
### Phase 6 — Convert multipart core and microblocks
Status: core code, factories, ordinary helpers and common/face/corner/edge/post microblock trait behavior are Java.
Three files still contain generated trait implementations. Extract their behavior while retaining Scala declarations where
Two files still contain generated trait implementations. Extract their behavior while retaining Scala declarations where
multiple-trait inheritance still needs signature metadata; abstract Java mixins and side filtering alone do not
replace that metadata. The following gates continue to apply to those remaining units; completed per-type evidence
is in the history.
+2
View File
@@ -518,6 +518,8 @@ opaque draws. Its Forge companion pins generated face bounds and solidity. The `
tested by Extra Utilities' connected-texture renderer remains intact. Corner, edge and post tests freeze virtual
access, every supported geometry, packed shape/material NBT and descriptions, edge conduction and post occlusion
ordering. The post's frozen Scala superclass also pins its synthetic super dispatch, including short-circuiting.
Post-client fixtures additionally exercise render/lifecycle dispatch, equality receivers, repeated virtual reads and
ordered neighbour shrinking; Forge uses real cover/post geometry while client generation and GPU output remain manual.
Full downstream mixed tiles and client-side selection remain Phase 7 integration cases.
With these gates, the source audit is actionable: the port can simplify internals freely where no listed observation
+15 -16
View File
@@ -16,22 +16,22 @@ migration checkout; `codex/tile-compatibility-fixes` was deleted after its fixes
## Current state and next target
**357 plain-JVM tests and 226 Java 8 Forge tests pass, with zero failures/errors/skips.** Sources total **219 Java
files and 9 Scala files / 1,140 nonblank Scala lines**. The packaged inventory has 442 classes.
**365 plain-JVM tests and 227 Java 8 Forge tests pass, with zero failures/errors/skips.** Sources total **220 Java
files and 9 Scala files / 1,104 nonblank Scala lines**. The packaged inventory has 443 classes.
Latest bounded target: `PostMicroblock` in `EdgeMicroblockTraits.scala` delegates its implementation to
`PostMicroblockTraitLogic.java`. Its Scala declaration, inheritance metadata, `$class` bridges and singleton accessor
remain. The Scala occlusion bridge retains the synthetic super call after Java evaluates the special cases and box
test. Characterization was committed first as `afd6c69`, with frozen Scala post/face implementors and a real superclass
predecessor. Tests pin call/failure ordering, virtual dispatch, read-only list behavior, all nine supported bounds,
edge-item drops/picking, torch support, generated post/cover occlusion and NBT/description round-trips. Clean
verification after stopping Gradle matches all 441 original class/member APIs, all 17 ScalaSignature payloads,
3,696 non-target method bodies and all 116 generated dump names/hashes. Only the package-private helper is added.
Local evidence is in `run/migration-post-trait-reference/`; `clean` preserves it.
Latest bounded target: `PostMicroblockClient` delegates render/recalculation/shrink behavior to
`PostMicroblockClientLogic.java`. Scala retains its state, inheritance metadata, `$class` and lifecycle/super
forwarders. Characterization was committed first as `47885d1`: frozen Scala forwarders pin render pass selection,
material reuse, repeated getters, equality receivers, collection `foreach`, split/reset behavior and lifecycle
failure ordering. Forge exercises real cover/post geometry on all three axes through the client helper; actual
client generation/GPU output remains a manual gate. Clean verification after stopping Gradle preserves all 441
retained class/member APIs, all 17 ScalaSignature payloads, 3,703 unrelated method bodies and all 116 generated dumps.
One internal Scala callback is replaced by the Java helper/callback under the shared classfile policy. Local evidence
is in `run/migration-post-client-reference/`; `clean` preserves it.
**Next: port `PostMicroblockClient` in `microblock/EdgeMicroblockTraits.scala`.** Characterize render pass/bounds
dispatch, lifecycle super-call ordering, face/post shrinking, split-bound reset and the size/transparency/axis
tie-breaks before conversion. Retain equality semantics and observe virtual calls rather than caching them.
**Next: port `HollowMicroblock` in `microblock/HollowMicroblockTraits.scala`.** Characterize its center connector
lookup, hollow-size/slot geometry, normal/partial/collision boxes, subparts and occlusion super ordering before
conversion. Leave `HollowMicroblockClient` rendering for a separate bounded target.
Retain the Scala declarations where inheritance metadata is required, the external ProjectRed Scala-trait fixture
and the ScalaSignature model bridges; compiler changes stay separate.
@@ -42,8 +42,7 @@ Remaining Scala units:
| `multipart/asm/ASMMixinCompiler.scala` | Retained nested models, construction callbacks and Scala entry-point shell |
| `multipart/asm/ScalaSignature.scala` | Named models, primitive/erased bridges and five generic inner-construction branches |
| `multipart/asm/StackAnalyser.scala` | Class/companion/model shell over Java `StackAnalyserLogic` |
| `microblock/MicroblockTraits.scala`, `FaceMicroblockTraits.scala`, `CornerMicroblockTraits.scala` | Six retained inheritance/bridge shells over Java implementation |
| `microblock/EdgeMicroblockTraits.scala` | Edge/post shells over Java behavior, with retained post super dispatch; `PostMicroblockClient` implementation remains |
| `microblock/MicroblockTraits.scala`, `FaceMicroblockTraits.scala`, `CornerMicroblockTraits.scala`, `EdgeMicroblockTraits.scala` | Nine retained state/inheritance/bridge shells over Java implementation, including post lifecycle/super dispatch |
| `microblock/HollowMicroblockTraits.scala`, `TMicroOcclusion.scala` | Generated trait implementations; port in bounded groups while preserving inheritance metadata |
Both generators, both registries, core tile/part classes, ordinary microblock helpers/factories, handlers, networking,
+1 -1
View File
@@ -41,7 +41,7 @@ Lamp Cover**, **Hollow Inverted White Lamp Cover**, **Inverted White Lamp Nook**
| [ ] | Render several block micro-materials with Angelica shaders enabled, then render an unrelated block | Each face receives the matching block/meta shader material and the override is reset after the microblock draw | `MaterialRenderHelper.blockAndMeta` / `render` |
| [ ] | Inspect face, hollow, edge and corner microblocks in inventory and as dropped entities | Every size/material combination uses the correct localized name and centered shape/material render | `ItemMicroPart` / `ItemMicroPartRenderer` / `MicroblockRender.renderItem` |
| [ ] | Saw **Stone**, **Glass** and a ProjectRed **Inverted White Lamp** into face covers; place them on all six faces beside other microblocks, then inspect their inventory renders | Opaque covers retain their outer face while internal faces follow the shrink mask; transparent covers use the shrunk bounds, and inventory renders show the complete shape without missing or doubled faces | `FaceMicroblockClient.render` / `FaceMicroblockTraitLogic.render` |
| [ ] | Place crossing posts of different sizes/transparency beside face covers, then add and remove neighbours | Post segments shrink and split at intersections without missing sections or stale render bounds | `PostMicroblockClient.recalcBounds` / `shrinkFace` / `shrinkPost` |
| [ ] | Saw **Stone**, **Glass** and a ProjectRed **Inverted White Lamp** into strip/post shapes; place crossing posts on all three axes between face covers, then add and remove neighbours | Compare both split segments with the reference, including the second segment's use of physical bounds; removal resets the split without stale geometry or missing sections | `PostMicroblockClient.recalcBounds` / `PostMicroblockClientLogic` / `shrinkFace` / `shrinkPost` |
| [ ] | Run connected AE2 **ME Glass Cable**, **ME Covered Cable** and **ME Dense Smart Cable**, plus ProjectRed **Framed Red Alloy Wire**, through hollow stone/glass covers on all six faces | Each opening follows the connector size; opaque and transparent rims, breaking overlay and highlight have no missing, doubled or stale sections after neighbour changes | `HollowMicroblockClient.recalcBounds` / `renderHollow` / `drawBreaking` / `drawHighlight` |
| [ ] | Place touching face, corner and edge microblocks of mixed sizes/transparency, then add and remove neighbours | Intersections choose the same winner, opaque covered faces stay masked, and every surviving segment refreshes without gaps or stale bounds | `MicroOcclusion` / `TMicroOcclusionClient.recalcBounds` |
+22
View File
@@ -1141,3 +1141,25 @@ differences belong in the [divergence ledger](../../JAVA_MIGRATION_DIVERGENCES.m
versions remain verified. Sources total 219 Java files and 9 Scala files / 1,140 nonblank Scala lines.
- Next: `PostMicroblockClient`; characterize render dispatch, lifecycle super ordering, shrink/split/reset behavior
and size/transparency/axis tie-breaks before conversion. Client/GPU and full-pack checks remain manual.
### 2026-09-03 — Post microblock client implementation
- Committed eight JVM characterization tests and one Forge test first as `47885d1`. Frozen Scala 2.11.5 client forwarders
and lifecycle predecessors pin the exact `-1` render branch, material reuse, live second-segment changes, repeated
virtual size/transparency/axis reads, superclass ordering/exceptions and equality receiver dispatch (even on the
same reference). Tests also pin collection `foreach`, neighbour order, fresh bounds copies, split reuse/reset and
malformed face failures. Forge exercises real cover/post geometry across all three axes through the client helper.
- Extracted five behavior methods to `PostMicroblockClientLogic.java`; retained Scala state/inheritance metadata,
accessors and lifecycle/super forwarders. The named Java callback preserves `foreach` and replaces the internal
Scala anonymous callback under the existing shared classfile policy. No algorithm fix or new effective divergence
was introduced. In particular, the second split segment still copies physical bounds rather than the already
face-clipped first segment; the new Forge check records that reference behavior.
- Saved source/jar, reports and 116 generated dumps in ignored `run/migration-post-client-reference/`. Clean checks
preserve all 441 retained class/member APIs, all 17 ScalaSignature payloads, 3,703 non-target method bodies and all
116 generated names/hashes. Two Java classes replace one Scala callback, bringing the jar inventory to 443 classes.
Formatting, checkstyle, build and Forge pass normally and after stopping Gradle for a clean rebuild: 365 JVM /
227 Forge tests, zero failures/errors/skips. The forced Scala-compilation guard and all five packaged `@Mod` versions
remain verified. Sources total 220 Java files and 9 Scala files / 1,104 nonblank Scala lines.
- Expanded the existing manual post check with Stone, Glass and ProjectRed Inverted White Lamp examples. The
dedicated server strips the client factory entry point; helper/geometry coverage does not establish actual client
generation or GPU output. Next: `HollowMicroblock`, followed separately by `HollowMicroblockClient`.
@@ -25,16 +25,8 @@ trait PostMicroblockClient extends PostMicroblock with MicroblockClient {
var renderBounds1: Cuboid6 = _
var renderBounds2: Cuboid6 = _
override def render(pos: Vector3, pass: Int) {
val mat = getIMaterial
if (pass == -1)
MicroblockRender.renderCuboid(pos, mat, pass, getBounds, 0)
else {
MicroblockRender.renderCuboid(pos, mat, pass, renderBounds1, 0)
if (renderBounds2 != null)
MicroblockRender.renderCuboid(pos, mat, pass, renderBounds2, 0)
}
}
override def render(pos: Vector3, pass: Int) =
PostMicroblockClientLogic.render(this, pos, pass)
override def onPartChanged(part: TMultiPart) {
recalcBounds()
@@ -50,46 +42,15 @@ trait PostMicroblockClient extends PostMicroblock with MicroblockClient {
recalcBounds()
}
def recalcBounds() {
renderBounds1 = getBounds.copy
renderBounds2 = null
def recalcBounds() = PostMicroblockClientLogic.recalcBounds(this)
shrinkFace(getShape << 1)
shrinkFace(getShape << 1 | 1)
def shrinkFace(fside: Int) = PostMicroblockClientLogic.shrinkFace(this, fside)
tile.partList.foreach(p =>
if (p.isInstanceOf[PostMicroblock] && p != this)
shrinkPost(p.asInstanceOf[PostMicroblock])
)
}
def shrinkPost(post: PostMicroblock) =
PostMicroblockClientLogic.shrinkPost(this, post)
def shrinkFace(fside: Int) {
val part = tile.partMap(fside)
if (part != null && part.getType.equals("mcr_face"))
MicroOcclusion.shrink(
renderBounds1,
part.asInstanceOf[CommonMicroblock].getBounds,
fside
)
}
def shrinkPost(post: PostMicroblock) {
if (post == this)
return
if (thisShrinks(post)) {
if (renderBounds2 == null)
renderBounds2 = getBounds.copy
MicroOcclusion.shrink(renderBounds1, post.getBounds, getShape << 1 | 1)
MicroOcclusion.shrink(renderBounds2, post.getBounds, getShape << 1)
}
}
def thisShrinks(other: PostMicroblock): Boolean = {
if (getSize != other.getSize) return getSize < other.getSize
if (isTransparent != other.isTransparent) return isTransparent
return getShape > other.getShape
}
def thisShrinks(other: PostMicroblock): Boolean =
PostMicroblockClientLogic.thisShrinks(this, other)
}
trait PostMicroblock
@@ -0,0 +1,83 @@
package codechicken.microblock;
import codechicken.lib.vec.Vector3;
import codechicken.microblock.MicroMaterialRegistry.IMicroMaterial;
import codechicken.multipart.TMultiPart;
import scala.runtime.AbstractFunction1;
import scala.runtime.BoxedUnit;
/** Client behavior behind the retained Scala state, inheritance and lifecycle bridges. */
final class PostMicroblockClientLogic {
private PostMicroblockClientLogic() {}
static void render(PostMicroblockClient part, Vector3 pos, int pass) {
IMicroMaterial material = ((Microblock) part).getIMaterial();
if (pass == -1) {
MicroblockRender.renderCuboid(pos, material, pass, part.getBounds(), 0);
} else {
MicroblockRender.renderCuboid(pos, material, pass, part.renderBounds1(), 0);
if (part.renderBounds2() != null) {
MicroblockRender.renderCuboid(pos, material, pass, part.renderBounds2(), 0);
}
}
}
static void recalcBounds(PostMicroblockClient part) {
part.renderBounds1_$eq(part.getBounds().copy());
part.renderBounds2_$eq(null);
part.shrinkFace(((Microblock) part).getShape() << 1);
part.shrinkFace(((Microblock) part).getShape() << 1 | 1);
((TMultiPart) part).tile().partList().foreach(new ShrinkPosts(part));
}
static void shrinkFace(PostMicroblockClient part, int side) {
TMultiPart other = ((TMultiPart) part).tile().partMap(side);
if (other != null && other.getType().equals("mcr_face")) {
MicroOcclusion.shrink(part.renderBounds1(), ((CommonMicroblock) other).getBounds(), side);
}
}
static void shrinkPost(PostMicroblockClient part, PostMicroblock other) {
// Scala equality calls the left operand's equals even when the references are identical.
if (other == null ? part == null : other.equals(part)) {
return;
}
if (part.thisShrinks(other)) {
if (part.renderBounds2() == null) {
part.renderBounds2_$eq(part.getBounds().copy());
}
MicroOcclusion.shrink(part.renderBounds1(), other.getBounds(), ((Microblock) part).getShape() << 1 | 1);
MicroOcclusion.shrink(part.renderBounds2(), other.getBounds(), ((Microblock) part).getShape() << 1);
}
}
static boolean thisShrinks(PostMicroblockClient part, PostMicroblock other) {
if (((Microblock) part).getSize() != ((Microblock) other).getSize()) {
return ((Microblock) part).getSize() < ((Microblock) other).getSize();
}
if (((Microblock) part).isTransparent() != ((Microblock) other).isTransparent()) {
return ((Microblock) part).isTransparent();
}
return ((Microblock) part).getShape() > ((Microblock) other).getShape();
}
private static final class ShrinkPosts extends AbstractFunction1<TMultiPart, BoxedUnit>
implements scala.Serializable {
private static final long serialVersionUID = 0L;
private final PostMicroblockClient part;
ShrinkPosts(PostMicroblockClient part) {
this.part = part;
}
@Override
public BoxedUnit apply(TMultiPart other) {
if (other instanceof PostMicroblock && !other.equals(part)) {
part.shrinkPost((PostMicroblock) other);
}
return BoxedUnit.UNIT;
}
}
}