feat: add inverse button orientation lookup

This commit is contained in:
Algent committed 2026-09-21 23:23:47 +02:00
1 parent ebbc92378d
commit 9a6d1ec4fc
6 files changed
+71 -3

No files matched your search

+1
View File
@@ -55,6 +55,7 @@ Existing reflective binaries remain supported until consumer release and pack ad
| Read FMP's global render registration ID | `TileMultipart.getRenderID()` - [render-ID meaning, lifecycle and setter](api/RENDER_ID.md) |
| Inspect an existing tile or converted placeholder with a named result | `getOrConvertTileResult(World, BlockCoord)` - [conversion outcomes and placement lifecycle](api/TILE_CONVERSION.md) |
| Add or remap multipart button attachment orientations | `ButtonPart.setOrientation(int, ForgeDirection)` - [metadata, face mapping and Et Futurum migration](api/BUTTON_ORIENTATIONS.md) |
| Read a custom button's orientation metadata from its attachment face | `ButtonPart.metaForSide(int)` - [live inverse lookup and ProjectRed migration](api/BUTTON_ORIENTATIONS.md#reading-orientation-metadata) |
Each guide explains ownership, lifecycle, legacy replacements and limitations, and links to a compiling Java example
exercised by the test suite. The loading/setter APIs are advanced reconstruction operations; ordinary placement and
+22 -2
View File
@@ -35,6 +35,25 @@ This method does not define a vanilla button block's bounds. `ButtonPart` captur
class initialization, after normal class transformations; a mod adding metadata orientations must still make its
button block expose the matching bounds.
## Reading orientation metadata
`ButtonPart.metaForSide(int)` returns the current metadata for an attachment face index (`0` through `5`), including
`-1` for an unmapped face. It reads the live inverse mapping, so both `setOrientation` and legacy array mutations or
replacement remain visible. Invalid indices throw `IndexOutOfBoundsException`; the accessor does not mask indices,
check world support, construct a part or reject an unmapped result on the caller's behalf.
ProjectRed's `ItemPartButtonCommons.newPart` can replace `ButtonPart.sideMetaMap(side ^ 1)` with:
```scala
val b = getNewInst(ButtonPart.metaForSide(side ^ 1))
```
Keep its existing vertical-face rejection, support check, custom factory and `onPlaced` call. Here `side` is the
clicked face, so `side ^ 1` is the attachment face. `sideForMeta` performs the opposite lookup;
`ButtonPart.placement` is not a replacement because it constructs an ordinary button and applies its own validation.
The accessor is additive on this branch; rebuild/publish the development artifact before compiling a consumer
against it. It is not yet tied to a released minimum FMP version.
## Migrating Et Futurum Requiem
Replace the reflective reads, four array writes and reflective field assignments with the two calls above. Do not
@@ -43,11 +62,12 @@ Keep Et Futurum's existing FMP presence/version gate so older FMP versions never
The public mutable `metaSideMap` and `sideMetaMap` fields remain binary and reflective compatibility surfaces for old
releases. New integrations should not read, replace or mutate them directly. Their removal still requires a released
Et Futurum migration, target-pack adoption and a fresh consumer scan.
Et Futurum migration, ProjectRed's inverse-lookup migration, target-pack adoption and a fresh consumer scan.
## Validation
Forge characterization first freezes the old Et Futurum mutation and verifies exact metadata and placement on all six
faces. API tests verify the two direct calls produce the same arrays, displaced pairs remain one-to-one, invalid input
is atomic, and the example compiles. A physical client with Et Futurum must still complete the
is atomic, and the example compiles. Inverse-lookup coverage checks all faces, unmapped results, remapping, legacy
mutation/replacement and invalid indices. A physical client with Et Futurum must still complete the
[all-face button check](../migration/MANUAL_CHECKS.md) before release.
+1
View File
@@ -769,6 +769,7 @@ tied to a released minimum dependency version. Unlisted contracts remain governe
| Legacy contract | Consumer and inspected source | Supported replacement | Consumer migration/release | Target-pack adoption and removal gate |
| --- | --- | --- | --- | --- |
| `ButtonPart.sideMetaMap[side ^ 1]` live inverse lookup | ProjectRed `e173952e96a4` and separate preparatory migration checkout: `illumination/items.scala`, `ItemPartButtonCommons.newPart` | `ButtonPart.metaForSide(side ^ 1)`; [lookup contract](../api/BUTTON_ORIENTATIONS.md#reading-orientation-metadata) | FMP accessor/example and regression coverage added; consumer replacement requires a rebuilt FMP artifact; preparatory migration is not upstream adoption | Retain the array until ProjectRed and Et Futurum migrations are released/adopted and a fresh scan clears legacy use |
| `TRedstoneTile.openConnections(int)` | ProjectRed `e173952e96a4`: `transmission/redwires.scala` | Existing stable `IRedstoneTile.openConnections(int)` with the same mask/rotation logic; [guide](../api/TILE_TRAIT_ACCESS.md) | FMP source-access example and Forge coverage complete; consumer source patch/release pending | Retain the old runtime interface descriptor until adoption |
| `TSlottedTile.v_partMap()` array mutation | OpenComputers `2c00f79be24b`: `PrintPart.toggleState` | `TileMultipart.refreshPartSlots(part)`; [equality, cache and notification contract](../api/TILE_TRAIT_ACCESS.md#refreshing-a-changed-slot-mask) | FMP API/example complete; consumer source patch/release pending, preserving validation and following sound/notify/packet/schedule order | Keep the live array/accessor ABI until released pack adoption; do not replace it with reflection or a whole-tile reload |
| External Scala `LightMicroblock` registered through `MicroblockGenerator.registerTrait` | ProjectRed `e173952e96a4`: `illumination/lightmicroblocks.scala` | Java source trait registered by name, `IGeneratedMaterial`, typed sibling traversal and halo helper; [example](../api/MICROBLOCK_EXTENSIONS.md) | FMP example tested; consumer rewrite/release, existing config/halo wiring and physical-client checks pending | Retain external Scala trait ingestion until released-consumer adoption and internal Scala-trait removal |
@@ -14,4 +14,9 @@ public final class ButtonOrientationExample {
ButtonPart.setOrientation(0, ForgeDirection.UP);
ButtonPart.setOrientation(5, ForgeDirection.DOWN);
}
/** Looks up a custom button's metadata without changing its placement or construction policy. */
public static int metadataForClickedFace(int side) {
return ButtonPart.metaForSide(side ^ 1);
}
}
@@ -22,6 +22,35 @@ class ButtonOrientationFunctionalTest {
private static final BlockCoord BUTTON = new BlockCoord(64, 200, 48);
@Test
void inverseLookupReadsLiveMappingsWithoutPlacementValidation() {
int[] originalMetaSideMap = ButtonPart.metaSideMap;
int[] originalSideMetaMap = ButtonPart.sideMetaMap;
try {
ButtonPart.metaSideMap = new int[] { -1, 4, 5, 2, 3, -1, -1, -1 };
ButtonPart.sideMetaMap = new int[] { -1, -1, 3, 4, 1, 2 };
for (int side = 0; side < 6; side++) {
assertEquals(ButtonPart.sideMetaMap[side], ButtonPart.metaForSide(side));
assertEquals(ButtonPart.sideMetaMap[side ^ 1], ButtonOrientationExample.metadataForClickedFace(side));
}
assertEquals(-1, ButtonPart.metaForSide(0));
ButtonPart.setOrientation(5, ForgeDirection.DOWN);
assertEquals(5, ButtonPart.metaForSide(0));
ButtonPart.setOrientation(5, ForgeDirection.UP);
assertEquals(-1, ButtonPart.metaForSide(0));
assertEquals(5, ButtonPart.metaForSide(1));
ButtonPart.sideMetaMap[2] = 7;
assertEquals(7, ButtonPart.metaForSide(2));
ButtonPart.sideMetaMap = new int[] { 5, 0, 3, 4, 1, 2 };
assertEquals(3, ButtonPart.metaForSide(2));
assertThrows(IndexOutOfBoundsException.class, () -> ButtonPart.metaForSide(-1));
assertThrows(IndexOutOfBoundsException.class, () -> ButtonPart.metaForSide(6));
} finally {
ButtonPart.metaSideMap = originalMetaSideMap;
ButtonPart.sideMetaMap = originalSideMetaMap;
}
}
@Test
void etFuturumArrayMutationEnablesFloorAndCeilingPlacement() {
World world = MinecraftServer.getServer().worldServers[0];
@@ -25,7 +25,8 @@ public class ButtonPart extends McSidedMetaPart implements IFaceRedstonePart {
public static int[] metaSideMap = new int[] { -1, 4, 5, 2, 3, -1, -1, -1 };
/**
* Legacy mutable inverse mapping retained for existing integrations; new code should use {@link #setOrientation}.
* Legacy mutable inverse mapping retained for existing integrations; use {@link #metaForSide} to read and
* {@link #setOrientation} to configure it.
*/
public static int[] sideMetaMap = new int[] { -1, -1, 3, 4, 1, 2 };
public static Cuboid6[] cuboidRegions = setupCuboids();
@@ -84,6 +85,17 @@ public class ButtonPart extends McSidedMetaPart implements IFaceRedstonePart {
sideMetaMap[side] = metadata;
}
/**
* Returns the current orientation metadata for an attachment face, or -1 if that face is unmapped. Reads the live
* mapping, including changes made by legacy integrations. Does not check support or construct a part.
*
* @param side attachment face index, from 0 through 5; for a clicked face use {@code side ^ 1}
* @throws IndexOutOfBoundsException if the index is outside the mapping
*/
public static int metaForSide(int side) {
return sideMetaMap[side];
}
public static BlockButton getButton(int meta) {
return (meta & 0x10) > 0 ? woodenButton : stoneButton;
}