Commit Graph

7 Commits

Author SHA1 Message Date
kayjaydee ac4ed623b9 fix(04-00): move particle texture to Common/Particles per validator rule
CommonAssetValidator.TEXTURE_PARTICLES (CommonAssetValidator.java:25)
requires PNGs to live under Common/Particles/, not Server/Particles/.
Server crash log confirmed: "Common Asset 'Particles/chain_spark.png'
doesn't exist!" → cascading ParticleSpawner + ParticleSystem failure.

Spike-relevant finding: assets split into Server/ (engine-side configs:
.particlesystem, .particlespawner) and Common/ (shared textures: PNG).
2026-04-27 13:07:02 +02:00
kayjaydee a4427d91a7 feat(04-00): minimal chain_spark particle assets for codec spike
- Author chain_spark.particlesystem (Spawners[1] -> SpawnerId=chain_spark)
- Author chain_spark.particlespawner (required co-asset, Sphere/BlendAdd, embedded Particle with Animation[0,100])
- Bundle 4x4 placeholder chain_spark.png in Particles/ to satisfy CommonAssetValidator.TEXTURE_PARTICLES
- ShadowJar packages all three at exact case-sensitive path Server/Particles/

Deviation from plan: research assumed only one .particlesystem file needed. Decompiled codec
shows ParticleSpawner is a SEPARATE asset (.particlespawner) referenced by SpawnerId, plus
Particle.Texture is validated against CommonAssetRegistry (must exist as Particles/<name>.png).
Both required for the spike to validate end-to-end. Documented in SPIKE-FINDINGS.
2026-04-27 13:01:40 +02:00
kayjaydee 6f8efa94c5 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).
2026-04-27 12:31:23 +02:00
kayjaydee 8725b8a1c7 feat(phase-3): runtime integration — chain damage application + cooldown
Wires the pure ChainResolver (Phase 2) to the live Hytale runtime via
four sceptre/ adapters:
- HytaleEntityAdapter — eager snapshot Ref<EntityStore> -> ChainEntity
  via TransformComponent.getPosition() and EntityStatMap.get(health) > 0
- HytalePlayerRayCaster — captures playerRef, delegates to
  TargetUtil.getTargetEntity (auto eye-origin + head-rotation)
- HytaleEntitySource — wraps TargetUtil.getAllEntitiesInSphere
- ChainDamageApplier — fires DamageSystems.executeDamage per hit;
  injectable DamageExecutor SAM keeps the helper unit-testable.

ChainLightningSceptreInteraction.firstRun is rewritten end-to-end:
cooldown gate (hasCooldown(false) -> deductCharge() only on success),
ray-cast -> BFS -> damage application, structured logging, try/catch
wrapper to keep a runtime fault from killing the server tick.

API corrections discovered against the decompiled jar:
- Ref has no uuid() — use "ref:" + getIndex() for the chain id
- DamageCause.PHYSICAL is @Nullable until runtime — use the int-index
  overload of Damage with index 0
- Static mock of DamageSystems crashes class init — abstracted behind
  a DamageExecutor SAM with a default lazy holder

Tests: 33/33 green (25 from Phase 2 + 4 ChainDamageApplier tests +
4 fixture sanity). ./gradlew build SUCCESSFUL, JAR auto-deployed.
MANUAL UAT (10 items) pending in-game.
2026-04-27 12:14:58 +02:00
kayjaydee cd5d0bedd3 feat(phase-2): pure-Java chain resolution algorithm
Adds com.mythlane.chainlightning.chain package with 7 types (Vec3,
ChainEntity, EntitySource, RayCaster, ChainParameters, ChainHit,
ChainResolver). Algorithm: ray-cast primary target then BFS hops with
distanceSquared closest-neighbor selection, deterministic lexicographic
tie-breaker on entity id, max 5 targets, 8-block radius, damage curve
[8,6,4,3,2]. Strict no-Hytale-imports boundary — runtime adapters land
in Phase 3.

JUnit 5 suite: 25 tests green (Vec3 5 + ChainParameters 10 +
ChainResolver 10). All 10 mandatory cases covered (no primary,
primary-only, full chain, overflow, out-of-radius, no-double-hit,
closest, tie-breaker determinism, dead entity, custom maxTargets).
2026-04-26 19:29:20 +02:00
kayjaydee edca00fa4a feat(phase-1): scaffold ChainLightning Sceptre plugin
Build infrastructure (Gradle wrapper, shadow, Java 25), manifest, item
JSON + interactions, and ChainLightningPlugin entry with stub handler
that logs sceptre clicks. Cooldown wiring deferred to Phase 3 (real
Hytale API is getCooldown(id).setCooldownMax — not cooldown(Duration)).
2026-04-26 19:22:18 +02:00
kayjaydee d06743acbe chore: initial project setup 2026-04-26 18:33:39 +02:00