fix(phase-3): resolve PHYSICAL damage cause index dynamically + verbose logs

Bug: ChainDamageApplier hardcoded causeIndex=0 assuming Physical was
loaded first. IndexedLookupTableAssetMap assigns indexes by filesystem
load order — non-deterministic. Index 0 was not Physical at runtime,
damage.getCause() returned null, NPE silently swallowed in
ArmorDamageReduction before any HP was deducted.

Fix: physicalDamageCauseIndex() resolves the real index at call time
via DamageCause.PHYSICAL.getId() + getAssetMap().getIndex(...). Test
contexts where PHYSICAL is null fall back to 0 (neutral, not asserted).

Also: replace getLogger().log(Level, fmt, args) with String.format
upstream — the Hytale log handler does not interpolate {0}/{1}/{2}
placeholders. Add per-step verbose logs throughout the pipeline
(firstRun 1/9..9/9, RayCast hit/miss + snapshot, EntitySource per-ref,
ChainDamageApplier per-hit before/after) to make future runtime
diagnostics trivial.

UAT confirmed in-game: 5 sheep chained at 8/6/4/3/2 HP, cooldown gates
the second click within 4s, no exceptions, causeIndex resolves to 6
(stable across restarts).
This commit is contained in:
2026-04-27 12:31:23 +02:00
parent 8725b8a1c7
commit 6f8efa94c5
4 changed files with 113 additions and 31 deletions
@@ -12,6 +12,7 @@ import com.mythlane.chainlightning.chain.Vec3;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
/**
* Implementation Phase 3 de {@link EntitySource} qui delegue a
@@ -24,6 +25,8 @@ import java.util.List;
*/
public final class HytaleEntitySource implements EntitySource {
private static final Logger LOGGER = Logger.getLogger(HytaleEntitySource.class.getName());
private final ComponentAccessor<EntityStore> accessor;
public HytaleEntitySource(@Nonnull ComponentAccessor<EntityStore> accessor) {
@@ -33,11 +36,21 @@ public final class HytaleEntitySource implements EntitySource {
@Override
public List<ChainEntity> nearby(Vec3 origin, double radius) {
Vector3d hytaleOrigin = new Vector3d(origin.x(), origin.y(), origin.z());
LOGGER.info(String.format("[ChainLightning][EntitySource] getAllEntitiesInSphere(origin=%s, radius=%.1f)",
origin, radius));
List<Ref<EntityStore>> refs = TargetUtil.getAllEntitiesInSphere(hytaleOrigin, radius, accessor);
LOGGER.info(String.format("[ChainLightning][EntitySource] Hytale returned %d refs", refs == null ? -1 : refs.size()));
if (refs == null) {
return new ArrayList<>();
}
List<ChainEntity> snapshots = new ArrayList<>(refs.size());
for (Ref<EntityStore> ref : refs) {
snapshots.add(HytaleEntityAdapter.snapshot(ref, accessor));
ChainEntity adapter = HytaleEntityAdapter.snapshot(ref, accessor);
LOGGER.info(String.format("[ChainLightning][EntitySource] ref:%d -> id=%s pos=%s alive=%s",
ref.getIndex(), adapter.id(), adapter.position(), adapter.isAlive()));
snapshots.add(adapter);
}
LOGGER.info(String.format("[ChainLightning][EntitySource] returning %d snapshots", snapshots.size()));
return snapshots;
}
}