Files
TiedUp-/src/test/java/com/tiedup/remake/v2/furniture/FurnitureAuthPredicateTest.java
NotEvil 355e2936c9 Refactor V2 animation, furniture, and GLTF rendering
Broad consolidation of the V2 bondage-item, furniture-entity, and
client-side GLTF pipeline.

Parsing and rendering
  - Shared GLB parsing helpers consolidated into GlbParserUtils
    (accessor reads, weight normalization, joint-index clamping,
    coordinate-space conversion, animation parse, primitive loop).
  - Grow-on-demand Matrix4f[] scratch pool in GltfSkinningEngine and
    GltfLiveBoneReader — removes per-frame joint-matrix allocation
    from the render hot path.
  - emitVertex helper dedups three parallel loops in GltfMeshRenderer.
  - TintColorResolver.resolve has a zero-alloc path when the item
    declares no tint channels.
  - itemAnimCache bounded to 256 entries (access-order LRU) with
    atomic get-or-compute under the map's monitor.

Animation correctness
  - First-in-joint-order wins when body and torso both map to the
    same PlayerAnimator slot; duplicate writes log a single WARN.
  - Multi-item composites honor the FullX / FullHeadX opt-in that
    the single-item path already recognized.
  - Seat transforms converted to Minecraft model-def space so
    asymmetric furniture renders passengers at the correct offset.
  - GlbValidator: IBM count / type / presence, JOINTS_0 presence,
    animation channel target validation, multi-skin support.

Furniture correctness and anti-exploit
  - Seat assignment synced via SynchedEntityData (server is
    authoritative; eliminates client-server divergence on multi-seat).
  - Force-mount authorization requires same dimension and a free
    seat; cross-dimension distance checks rejected.
  - Reconnection on login checks for seat takeover before re-mount
    and force-loads the target chunk for cross-dimension cases.
  - tiedup_furniture_lockpick_ctx carries a session UUID nonce so
    stale context can't misroute a body-item lockpick.
  - tiedup_locked_furniture survives death without keepInventory
    (Forge 1.20.1 does not auto-copy persistent data on respawn).

Lifecycle and memory
  - EntityCleanupHandler fans EntityLeaveLevelEvent out to every
    per-entity state map on the client.
  - DogPoseRenderHandler re-keyed by UUID (stable across dimension
    change; entity int ids are recycled).
  - PetBedRenderHandler, PlayerArmHideEventHandler, and
    HeldItemHideHandler use receiveCanceled + sentinel sets so
    Pre-time mutations are restored even when a downstream handler
    cancels the render.

Tests
  - JUnit harness with 76+ tests across GlbParserUtils, GltfPoseConverter,
    FurnitureSeatGeometry, and FurnitureAuthPredicate.
2026-04-18 17:34:03 +02:00

215 lines
6.0 KiB
Java

package com.tiedup.remake.v2.furniture;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
/**
* Tests for the pure-logic authorization core in {@link FurnitureAuthPredicate}.
*
* <p>These tests pin down the truth tables for the lock and force-mount gates
* without touching any Minecraft API. The MC-aware wrappers ({@code canLockUnlock},
* {@code canForceMount}) extract booleans from live entities and delegate here,
* so full-stack validation is manual (in-game).</p>
*
* <p>The 2026-04-17 audit documented three divergent auth paths (BUG-002, BUG-003)
* that this predicate now unifies. If anyone re-introduces the drift, the
* isAuthorizedForLock / isAuthorizedForForceMount methods will catch it here.</p>
*/
class FurnitureAuthPredicateTest {
@Nested
@DisplayName("isAuthorizedForLock — AND of 4 gates")
class LockAuthorization {
@Test
@DisplayName("all four gates true → authorized")
void allTrue() {
assertTrue(
FurnitureAuthPredicate.isAuthorizedForLock(
true,
true,
true,
true
)
);
}
@Test
@DisplayName("missing master key → denied")
void missingKey() {
assertFalse(
FurnitureAuthPredicate.isAuthorizedForLock(
false,
true,
true,
true
)
);
}
@Test
@DisplayName("seat not lockable → denied")
void seatNotLockable() {
assertFalse(
FurnitureAuthPredicate.isAuthorizedForLock(
true,
false,
true,
true
)
);
}
@Test
@DisplayName("seat not occupied → denied")
void seatNotOccupied() {
assertFalse(
FurnitureAuthPredicate.isAuthorizedForLock(
true,
true,
false,
true
)
);
}
@Test
@DisplayName("occupant has no collar (or sender not owner) → denied")
void noOwnedCollar() {
assertFalse(
FurnitureAuthPredicate.isAuthorizedForLock(
true,
true,
true,
false
)
);
}
@Test
@DisplayName("all gates false → denied")
void allFalse() {
assertFalse(
FurnitureAuthPredicate.isAuthorizedForLock(
false,
false,
false,
false
)
);
}
}
@Nested
@DisplayName("isAuthorizedForForceMount — AND of 5 gates")
class ForceMountAuthorization {
@Test
@DisplayName("all five gates true → authorized")
void allTrue() {
assertTrue(
FurnitureAuthPredicate.isAuthorizedForForceMount(
true,
true,
true,
true,
true
)
);
}
@Test
@DisplayName("captive not alive → denied")
void captiveDead() {
assertFalse(
FurnitureAuthPredicate.isAuthorizedForForceMount(
false,
true,
true,
true,
true
)
);
}
@Test
@DisplayName("captive out of range → denied")
void captiveOutOfRange() {
assertFalse(
FurnitureAuthPredicate.isAuthorizedForForceMount(
true,
false,
true,
true,
true
)
);
}
@Test
@DisplayName("no owned collar → denied")
void noOwnedCollar() {
assertFalse(
FurnitureAuthPredicate.isAuthorizedForForceMount(
true,
true,
false,
true,
true
)
);
}
@Test
@DisplayName("captive not tied up (leashed) → denied (audit BUG-003)")
void notTiedUp() {
// This is the exact regression guard for audit BUG-003: before the
// fix, the packet path did NOT check isTiedUp, so any collar-owning
// captor could force-mount a captive that had never been leashed.
assertFalse(
FurnitureAuthPredicate.isAuthorizedForForceMount(
true,
true,
true,
false,
true
)
);
}
@Test
@DisplayName("captive already passenger of another entity → denied (audit BUG-003)")
void alreadyPassenger() {
// BUG-003 regression guard: before the fix, the packet path used
// startRiding(force=true) which would break prior mounts silently.
assertFalse(
FurnitureAuthPredicate.isAuthorizedForForceMount(
true,
true,
true,
true,
false
)
);
}
@Test
@DisplayName("all gates false → denied")
void allFalse() {
assertFalse(
FurnitureAuthPredicate.isAuthorizedForForceMount(
false,
false,
false,
false,
false
)
);
}
}
}