57 Commits

Author SHA1 Message Date
kayjaydee cc6c022e15 docs: update contact information in README.md
- Changed the contact email for inquiries regarding the project license from socials@mythlane.com to contact@mythlane.com for better accessibility.
2026-04-28 09:00:13 +02:00
kayjaydee 7b67410698 docs: remove demo section and screenshots from README.md
- Eliminated the demo section and associated YouTube video placeholder for a more concise README.
- Removed screenshots table to streamline content and focus on essential project information.
2026-04-26 18:18:41 +02:00
kayjaydee 2b23d87c80 docs: update README.md for clarity and structure
- Simplified project description and improved readability.
- Changed section titles for better organization (e.g., "Showcase" to "Demo").
- Reformatted screenshots into a markdown table for a cleaner layout.
- Enhanced the "What Does It Do?" section to clearly outline features.
- Streamlined the Quick Start and Commands sections for easier navigation.
2026-04-24 18:11:02 +02:00
kayjaydee c20bf42c36 refactor(command): simplify region lookup in GravityFlip commands
- Replaced manual region search loops with a new `find` method in RegionRegistry for improved readability and efficiency.
- Updated `GravityFlipToggleSubCommand` and `GravityFlipTpSubCommand` to utilize the new method for finding regions by name.
- Enhanced the `add` method in RegionRegistry to use the `find` method for checking existing region names.
2026-04-24 18:07:34 +02:00
kayjaydee 24ee43b3a3 docs: replace SHOWCASE.md with comprehensive README.md
- Replace portfolio-only SHOWCASE with unified README as sole project entry page
- Add full user guide: commands table, wand flow, region anatomy (all 15 config fields sourced from GravityFlipRegion.CODEC), visual modes, regions.json path
- Add developer section: prerequisites (JDK 25, Hytale API), project structure, build/test/deploy commands, extension snippets
- Add architecture overview with ASCII diagram (tick loop, wand flow, persistence)
- Correct subcommand list (wand/define/list/delete/toggle/tp) to match GravityFlipCommand source
- Preserve showcase content: video placeholder, screenshot grid (HTML table), 12 showcase regions table, feature list
- Add shields.io badges, tech-stack, roadmap, credits, license sections
2026-04-24 17:35:14 +02:00
kayjaydee 0b191c6504 docs: add SHOWCASE.md portfolio page
- Portfolio-ready showcase with YouTube placeholder, screenshots gallery, feature list
- Command reference table for the 6 subcommands
- Inventory of 12 bundled showcase regions with coordinates and purpose
- Install / development / license sections
- docs/screenshots/.gitkeep to reserve asset location
2026-04-24 17:31:34 +02:00
kayjaydee 6a830ed285 refactor: clean GSD comments and translate remaining Java sources to English
- Reduce javadocs to one-liners across config/region/physics/tick/viz/plugin root

- Translate residual French comments; no behavioural change

- Tests adjusted where assertions referenced French strings
2026-04-24 17:25:38 +02:00
kayjaydee 6b28dc2d2a refactor(command): clean GSD comments and translate user-facing messages to English 2026-04-24 17:25:31 +02:00
kayjaydee 15fc0702f1 refactor(wand): clean GSD comments and translate to English
- Reduce javadocs to one-liners, remove REQ/plan/phase references

- No behavioural change
2026-04-24 17:25:24 +02:00
kayjaydee b8754617d4 refactor: clean GSD references + shrink comments in wand/ 2026-04-24 17:11:54 +02:00
kayjaydee 4a9602fee3 refactor(test): remove outdated comment in DefineValidationTest
- Removed a comment regarding the rejection of quoted arguments with spaces, as the validation logic is already covered by existing tests. This helps to keep the test code clean and focused.
2026-04-24 17:05:10 +02:00
kayjaydee a8818e0166 fix(04-bugfix): reject names with spaces in define subcommand
- Add explicit regression tests for "nom avec espaces", leading/trailing
  spaces, and tab characters in DefineValidationTest.
- Validation regex ^[a-zA-Z0-9_-]{1,32}$ already rejects these; tests
  seal B4 UAT observation.
2026-04-24 17:02:26 +02:00
kayjaydee 0d4cb2e687 fix(04-bugfix): wire wand via RootInteraction asset (NPE on click)
Root cause: Item.Interactions map value codec is RootInteraction.CHILD_ASSET_CODEC
(expects a String asset id referencing an asset under Item/RootInteractions/), not
an inline Interaction object. Previous JSON used "Primary": { "Type": "GravityFlipWand" }
which silently loaded a RootInteraction asset with no interactionIds -> operations[]
empty -> InteractionManager.serverTick NPE on operation.getWaitForDataFrom() when
the click chain ticked.

Fix:
- New Interaction asset Server/Item/Interactions/gravityflip_wand_click.json
  ({"Type": "GravityFlipWand"}) matching our registered Interaction subtype.
- New RootInteraction asset Server/Item/RootInteractions/gravityflip_wand_root.json
  wrapping the interaction id in its Interactions[] list (required by RootInteraction
  CODEC, see RootInteraction.build() -> OperationsBuilder).
- Item JSON now references the RootInteraction by id:
  "Interactions": { "Primary": "gravityflip_wand_root", "Secondary": "gravityflip_wand_root" }
  matching vanilla Weapon_Wand_Wood.json pattern ("Primary": "Wand_Primary").

Binding (Interaction.CODEC.register("GravityFlipWand", ...)) is unchanged and
correct -- subtype registration on the Interaction AssetCodecMapCodec, consistent
with InstancesPlugin/ExitInstanceInteraction pattern.
2026-04-24 16:34:37 +02:00
kayjaydee c2b1f3a73b fix(04-bugfix): move wand JSON to Server/Item/Items/ and clone Weapon_Wand_Root template
Empirical findings from HytaleServer.jar decompile + Assets.zip introspection:
- AssetRegistryLoader.loadAssets0() resolves each plugin asset-pack root to
  <root>/Server/, then iterates AssetStores and loads from
  Server/<assetStore.getPath()>/. For the Item AssetStore, path is
  'Item/Items'. So the wand JSON MUST live at Server/Item/Items/.
- The previous src/main/resources/Items/gravityflip_wand.json path was
  never scanned: the loader would look at <jar>/Server/Item/Items/.
- Key function is Item::getId with item.id = blockTypeKey (filename minus
  .json). So file 'gravityflip_wand.json' -> lookup id 'gravityflip_wand'.

JSON content cloned from vanilla Weapon_Wand_Root.json (extracted from
HYTALE SERVER/Assets.zip), which is the minimal wand template:
- Categories: ['Items.Weapons'] -> shows up in builder/creative menu.
- Icon/Model/Texture reuse vanilla wand assets (no custom art shipped).
- PlayerAnimationsId: Wand -> proper holding animation.
- ItemSoundSetId: ISS_Weapons_Wand -> pickup/drop SFX.
- Utility.Compatible=true and Weapon={} keep it functional in builder tools.
- Interactions.Primary/Secondary are inline {Type: GravityFlipWand} objects,
  which the plugin's CodecRegistry(Interaction.CODEC).register call binds
  to GravityFlipWandInteraction.CODEC (04-01 pattern, Finding 3).
2026-04-24 15:34:55 +02:00
kayjaydee 25b48a95e9 fix(04-bugfix): enable IncludesAssetPack=true so plugin asset-pack is registered
Without this flag, JavaPlugin.setup0() skips AssetModule.registerPack(),
so bundled Items/*.json are never loaded into Item.getAssetMap(), causing
/gravityflip wand to fail with 'Item gravityflip_wand introuvable'.
2026-04-24 15:34:16 +02:00
kayjaydee e254d3744f feat(04-04): wire list/delete/toggle/tp subcommands into GravityFlipCommand
Registers the 4 remaining Phase 4 subcommands in the root command ctor.
addSubCommand count: 6 (wand, define, list, delete, toggle, tp) — feature-complete
for the 11 v1 requirements (modulo UAT in 04-05).
2026-04-24 14:55:40 +02:00
kayjaydee c42ebbe0e9 feat(04-04): add tp subcommand teleporting to region AABB center (CMD-06)
- AbstractPlayerCommand (tp only makes sense for a player caller)
- Pattern mirrors TeleportToCoordinatesCommand:48-68 (read Transform+HeadRotation,
  build Teleport.createForPlayer(pos, bodyRotation).setHeadRotation(headRotation),
  store.addComponent)
- Center computed componentwise: c = (min+max)/2
- Preserves caller body+head rotation (teleport in place, no look change)
- Clear error on unknown region name
2026-04-24 14:55:12 +02:00
kayjaydee bfc53972b7 feat(04-04): add list/delete/toggle subcommands (CommandBase, CMD-03/04/05)
- GravityFlipListSubCommand: iterate registry.all(), format 'name : (min) -> (max) [state]'
- GravityFlipDeleteSubCommand: registry.remove + save, clear error on unknown name
- GravityFlipToggleSubCommand: read current enabled, flip via setEnabled, save, report new state
- All three extend CommandBase (works from server console, not just player)
- Save-failure path: keep in-memory change, truthful message (mirror define pattern)
2026-04-24 14:54:34 +02:00
kayjaydee 00e946a1f4 feat(04-03): add /gravityflip define <name> subcommand (CMD-02)
- GravityFlipDefineSubCommand: consume wand selection, componentwise min/max, inflate max +1, registry.add, save().join, clear selection
- Wire into GravityFlipCommand ctor
- Duplicate name → IllegalArgumentException → user message
- Save failure → in-memory region preserved, truthful message
2026-04-24 14:11:27 +02:00
kayjaydee 2fe0957a1c feat(04-03): implement DefineValidation helpers (name regex + componentwise min/max) 2026-04-24 14:10:39 +02:00
kayjaydee 4b10dbce6c test(04-03): add failing tests for DefineValidation (name regex + componentwise min/max) 2026-04-24 14:10:19 +02:00
kayjaydee 3070353579 feat(04-02): register /gravityflip command in start() (CMD-01)
- wire getCommandRegistry().registerCommand(new GravityFlipCommand(this))
- import com.mythlane.gravityflip.command.GravityFlipCommand
- no manual cleanup: CommandRegistry.register auto-adds shutdownTask
2026-04-24 14:07:44 +02:00
kayjaydee c172d4f84d feat(04-02): add GravityFlipCommand root + wand subcommand (CMD-01)
- GravityFlipCommand extends AbstractCommandCollection (pattern: TeleportCommand)
- GravityFlipWandSubCommand extends AbstractPlayerCommand (pattern: GiveCommand)
- resolves 'gravityflip_wand' via Item.getAssetMap().getAsset, giveItem, chat feedback
- null-safe on asset miss + inventory-full remainder check
2026-04-24 14:07:21 +02:00
kayjaydee 14538f875d chore(04-01): remove 04-00 WandAssetSpike documentation artifact (superseded) 2026-04-23 18:51:35 +02:00
kayjaydee ae0e2064dc feat(04-01): register GravityFlipWand interaction + item JSON (WAND-01, WAND-02)
- GravityFlipWandInteraction extends SimpleInstantInteraction; Primary/Secondary
  routed through firstRun(), writes to WandSelectionStore via volatile bindStore()
- Items/gravityflip_wand.json: Utility item (Icon Torch_Fire) whose Primary and
  Secondary Interactions reference Type=GravityFlipWand
- GravityFlipPlugin.setup(): constructs WandSelectionStore, injects it into the
  interaction class, then registers via getCodecRegistry(Interaction.CODEC)
  (pattern from InstancesPlugin:158 / ExitInstanceInteraction)
- Expose wandSelections() getter for Phase 04-02+ commands
2026-04-23 18:51:13 +02:00
kayjaydee e0a329480a feat(04-01): implement WandSelectionStore pure-data per-UUID selection store
- ConcurrentHashMap<UUID, Selection>, atomic compute() for set/get
- Selection immutable holder with isComplete()
- No Hytale imports; testable standalone
2026-04-23 18:49:46 +02:00
kayjaydee 7afdc5c1a6 test(04-01): add failing tests for WandSelectionStore pure-data store 2026-04-23 18:49:18 +02:00
kayjaydee 66536628ad docs(04-00): add WandAssetSpike documentation artifact
Documentation-only class capturing the 04-00 spike conclusions. Not wired
into GravityFlipPlugin. Private constructor prevents instantiation.

Records the locked VANILLA_COPY decision:
- Do NOT register a new Item asset store (unproven pattern).
- Subclass SimpleInstantInteraction and register via
  getCodecRegistry(Interaction.CODEC).register("GravityFlipWand", ...).
- Plan 04-02 will runtime-identify a vanilla hatchet/axe itemID for
  /gravityflip wand to hand out.

Delete this class at the start of Plan 04-01.
2026-04-23 18:46:38 +02:00
kayjaydee 5b2484be3b feat: add showcase region on first run to enhance user experience
- Implemented a mechanism to bootstrap a demo gravity flip region when regions.json is absent or empty.
- The showcase region features a 10×20×10 box with upward force and Torch_Fire particles, allowing immediate demonstration of functionality.
- Users can customize or remove this region via regions.json.
2026-04-23 16:52:23 +02:00
kayjaydee f5e6cc24f5 refactor: remove all debug code (DumpParticles, RegionEnterNotifier, dead ctors) 2026-04-23 16:45:37 +02:00
kayjaydee 1e47f4e846 refactor(03-06): remove debug logs + efficiency pass
Log cleanup:
- Drop redundant INFO "Gravity Flip enabled" in setup() (substantive version at
  start() remains as the single startup line).
- Drop INFO "debug enter/exit notifier ENABLED" (the notifier itself emits per
  region transition; extra startup noise not needed).
- Remove dead back-compat GravityApplier ctors that accepted an infoHandler
  Consumer (no callers post-03-06; debug logs already stripped).
- Clean stale javadoc referencing removed [DBG npc.woken]/[DBG npc.ctrlNull]
  one-shot logs.
- Also ship the pre-staged cleanup work: RegionEnterNotifier gated behind
  GRAVITYFLIP_DEBUG_NOTIFY env var, GravityApplier infoHandler field removed,
  tick logs stripped.

Efficiency:
- RegionVisualizer.resolveParticleId now memoises the requested->resolved
  mapping in a ConcurrentHashMap, eliminating the ParticleSystem.getAssetMap()
  lookup on every tick emission per region. Warn-once semantics preserved via
  warnedInvalidIds. Fail-open path (AssetMap unavailable in tests) intentionally
  does not populate the cache.
- Document in GravityApplier javadoc why Pass 1 + Pass 2 cannot be fused:
  restore requires the complete currentlyInRegion set before diffing against
  previouslyInverted.

Considered but not applied:
- ParticleEdgeEmitter.edgePoints caching per (box, density): throttled to
  >=100ms refresh and typical <20 regions => alloc pressure negligible;
  premature without measurement.
- Reusing RegionRegistry snapshot in RegionEnterNotifier: notifier is
  opt-in/off-by-default, so its independent ECS scan has zero prod cost.

Tests: ./gradlew clean build green, no test changes required.
2026-04-23 16:43:07 +02:00
kayjaydee beba729115 feat(03-06): add Particles visual mode with per-region id/density (Task 3) 2026-04-23 15:44:42 +02:00
kayjaydee ee1d1b9bdb feat(03-06): add ParticleEdgeEmitter + tests (Task 2) 2026-04-23 15:42:26 +02:00
kayjaydee 7bbd65dad2 feat(03-06): add particle-system dump for discovery (Task 1)
- New DumpParticlesCommand utility: enumerates loaded ParticleSystem
  asset-ids via ParticleSystem.getAssetMap().getAssetMap().keySet().
- Wired in GravityFlipPlugin.start() behind GRAVITYFLIP_DUMP_PARTICLES
  env var (or -Dgravityflip.dumpParticles sysprop). No-op when unset.
- Boot-time fallback approach (plan-allowed) -- proper CommandBase
  registration deferred to avoid i18n translation-key plumbing for a
  one-shot discovery dump.
- See .planning/phases/03-gravity-physics/03-06-DUMP-NOTES.md for
  trigger instructions and the user checkpoint before Task 3.
2026-04-23 15:32:55 +02:00
kayjaydee 3511493687 feat(03-05): cable RegionVisualizer dans RegionTickLoop + shutdown clearAll (Task 3)
- RegionTickLoop: nouveau ctor 4-arg (registry, gravityApplier, regionVisualizer,
  errorHandler). Les ctors 2-arg et 3-arg existants delegent (back-compat tests).
  Tick appelle regionVisualizer.visualize(w, snapshot) apres gravityApplier.apply()
  avec null-safe gate (meme pattern que gravityApplier).
- GravityFlipPlugin.start(): construit RegionVisualizer avec errorHandler log WARN,
  le passe au tickLoop.
- GravityFlipPlugin.shutdown(): appelle regionVisualizer.clearAll(world) AVANT
  tickLoop.stop() (si Universe encore vivante), emet ClearDebugShapes a tous
  les PlayerRefs. Si universe null, fallback TTL expiration.
- Aucun nouveau scheduler/thread cree.
2026-04-23 14:55:24 +02:00
kayjaydee 99b9072b78 feat(03-05): ajoute RegionVisualizer (parsing hex/mode, throttling, low-level DebugUtils.add) (Task 2)
- RegionVisualizer emet DisplayDebug cube par region via
  DebugUtils.add(World, DebugShape.Cube, Matrix4d, color, opacity, time, flags)
  (low-level => opacity custom respectee, vs addCube qui hardcode 0.8).
- Mapping modes: Outline=FLAG_NO_SOLID, Faces=FLAG_NO_WIREFRAME, Both=FLAG_NONE,
  None=skip, unknown=fallback Outline.
- parseColor(#RRGGBB) avec fallback COLOR_CYAN sur input invalide.
- Throttling par region via ConcurrentHashMap<name, lastEmitMs> + clock injecte.
- Clamp defensive: VisualRefreshMs plancher 100ms (anti-DoS T-03-05-03),
  VisualOpacity clamp [0,1] (T-03-05-04).
- TTL = refreshMs * 1.2 / 1000 (overlap 20% anti-flicker).
- Toute emission wrappee dans world.execute(...) via WorldExecutor injectable.
- Tests: parseColor valid/invalid, normalizeMode, flagsForMode, matrixFromBox
  non-cubique, throttling deterministe, skip None/disabled, clamp opacity/refresh.
2026-04-23 14:54:31 +02:00
kayjaydee 0069d4c47e feat(03-05): ajoute 4 champs Visual* optionnels + round-trip tests (Task 1)
- GravityFlipRegion: VisualColor/VisualMode/VisualRefreshMs/VisualOpacity
  avec defaults (#00FFFF, Outline, 1000, 0.5), getters/setters, javadoc.
- CODEC: 4 KeyedCodec optionnels (pas de validator nonNull) => back-compat
  legacy regions.json.
- 2 tests round-trip (custom + defaults quand champs absents).
2026-04-23 14:52:42 +02:00
kayjaydee d43aa4a98c feat(03-04): FallDamageSuppressorSystem + wiring + cleanup [DBG throttle] (Task 3)
- FallDamageSuppressorSystem: subclass DamageEventSystem, cancel Damage
  cause=Fall quand guard.shouldSuppressFallDamage; FALL_INDEX lazy via
  DamageCause.getAssetMap().getIndex; groupe inspectDamageGroup
- GravityApplier: 3-arg constructor avec FallDamageGuard; first-match
  region, filtres AffectPlayers/Npcs/Items avant wake, seed addForce
  paramétré par VerticalForce (remplace hardcode 0.1); notifie
  guard.markInRegion / markExit (via lastKnownRegion map pour Pass 2)
- Cleanup: retire les AtomicInteger counters + log [DBG tick=...]
  throttle 1s; conserve logs one-shot [DBG npc.woken]/[DBG npc.ctrlNull]
- GravityFlipPlugin.setup(): instancie FallDamageGuard, registerSystem
  sur entityStoreRegistry; start() passe guard au GravityApplier
- Imports Query + CommandBuffer alignés sur FlockMembershipSystems
2026-04-23 14:03:58 +02:00
kayjaydee f9d5595837 feat(03-04): ajoute FallDamageGuard tracker thread-safe (Task 2)
- ConcurrentHashMap<UUID, region> current + exit + regionAtExit
- shouldSuppressFallDamage: in-region FallDamage=false OU grace window
- Pas de static mutable, injecté via constructeur
- 7 tests pure-data couvrant entry/in-region/exit/grace/re-entry/override
2026-04-23 14:00:56 +02:00
kayjaydee a834c59b66 feat(03-04): ajoute 6 champs optionnels sur GravityFlipRegion (Task 1)
- POJO: FallDamage (false), GracePeriodMs (2500), VerticalForce (0.1),
  AffectPlayers/Npcs/Items (true) avec getters/setters
- CODEC: 6 .append sans nonNull validator (sémantique optionnelle)
- Tests: 6 round-trip + back-compat defaults (9 tests total)
2026-04-23 14:00:12 +02:00
kayjaydee ffb716ca1c build: auto-copy shadowJar vers le dossier mods du serveur dev Hytale
Ajoute une tâche Gradle `copyJarToDevServer` finalisant shadowJar :
le fat JAR est copié dans C:/Users/minit/Desktop/HYTALE SERVER/Server/mods
par défaut. Overridable via -PdevServerMods=... et désactivable avec
-PdevServerMods=disabled.
2026-04-23 12:35:37 +02:00
kayjaydee 210c93aee7 feat(03-02): wake-up MovementManager + MotionController on region flip
- GravityApplier: branches player (setDefaultSettings + applyDefaultSettings + update) et NPC (Role.getActiveMotionController().updatePhysicsValues) dans les 2 pass
- wakePlayerOrNpc() helper appelé depuis forEachEntityParallel (sous world.execute — WorldThread assert OK)
- PhysicsValues construit localement via buildPhysicsValuesWithFlag (évite relookup ECS pre-commit cmdBuf)
- Seam pure FlaggedDecision extraite pour tests unitaires hors runtime Hytale (Rule 3 — static init PhysicsValues nécessite PluginBase)
- 8 tests verts (6 existants + 2 nouveaux sur buildFlaggedDecision)
2026-04-23 12:29:00 +02:00
kayjaydee 4a02ceaa82 fix(region): wrap refreshFor(World) dans world.execute (WorldThread assert)
- RegionRegistry.refreshFor dispatche scan ECS + publishSnapshot via world.execute
- Publication devient asynchrone (1-tick staleness max, borne par tick loop @100ms)
- Null-path enabled.isEmpty publie directement (pas de travail ECS requis)
- Amendement D-04 du plan 03-01 : world.execute requis côté initiation de forEachEntityParallel
2026-04-23 11:49:00 +02:00
kayjaydee af02e19685 fix(gravity): wrap forEachEntityParallel dans world.execute (WorldThread assert)
- GravityApplier.apply dispatche via world.execute(Runnable) pour satisfaire assertThread de Store.forEachEntityParallel
- PASS 1, PASS 2 et update tracker exécutés dans le même Runnable (cohérence intra-tick)
- Null-guards conservés avant le dispatch
- Amendement D-04 du plan 03-01 : world.execute requis côté initiation
2026-04-23 11:48:29 +02:00
kayjaydee f7dcc7d59a feat(03-01): wire GravityApplier into RegionTickLoop + plugin (Task 2)
- RegionTickLoop gagne un constructeur 3-args (registry, gravityApplier, errorHandler)
- Ancien constructeur 2-args conservé pour rétrocompat des tests Phase 02
- Tick body appelle gravityApplier.apply(w, registry.currentSnapshot(w)) après refreshFor
- GravityFlipPlugin.start() construit l'applier et l'injecte dans le tickLoop
- Log de démarrage mis à jour: 'gravity inversion active'
2026-04-23 10:32:28 +02:00
kayjaydee ef3f398c55 feat(03-01): add GravityApplier with native PhysicsValues.invertedGravity toggle (Task 1)
- Service GravityApplier qui toggle PhysicsValues.invertedGravity via CommandBuffer.replaceComponent
- Thread-safe tracker ConcurrentHashMap.newKeySet<UUID> pour dedup entre ticks
- Second pass O(N) pour restaurer la gravité à la sortie de région (trade-off v1 documenté)
- Pure diff static helper + hooks package-private pour tests unitaires sans runtime Hytale
- 6 tests verts (diff + tracker semantics)
2026-04-23 10:31:43 +02:00
kayjaydee 562f60d343 chore: update .gitignore to include local tooling and decompiled sources 2026-04-23 10:06:09 +02:00
kayjaydee 53b43d83c7 feat(02-02): add RegionTickLoop + plugin wiring (Task 3)
- RegionTickLoop: single-thread daemon scheduler @100ms, 2s initial delay,
  shutdown -> awaitTermination(5s) -> shutdownNow lifecycle.
  Three start overloads: (World), (Supplier<World>), (Runnable). Errors are
  routed to a Consumer<Throwable> so an exception in one tick never kills
  the scheduler.
- GravityFlipPlugin.start():
  * builds RegionRegistry from configHolder.get() + holder for save()
  * starts the tick loop with a Supplier<World> that lazily resolves
    Universe.get().getDefaultWorld() (deviation: PrepareUniverseEvent in the
    pinned API only carries WorldConfigProvider — no Universe/World access,
    so we use the lazy-supplier pattern from MythWorld instead)
  * registers the ScheduledFuture with TaskRegistry (raw cast, try/catch fallback)
- GravityFlipPlugin.shutdown(): tickLoop.stop() BEFORE super.shutdown()
- RegionTickLoopTest: 3 timing tests pass (>=8 ticks/sec, stop within 5s,
  exception resilience).
- gradle build green; fat jar contains both region/ and tick/ class dirs.
2026-04-23 00:56:12 +02:00
kayjaydee 6574c05128 feat(02-02): add RegionRegistry + RegionSnapshot with ECS refreshFor (Tasks 1+2)
- RegionSnapshot: immutable per-world occupancy view (byRegion/tickId/world)
- RegionRegistry:
  * AtomicReference<List<GravityFlipRegion>> for tick-loop-safe region reads
  * CRUD (add/remove/setEnabled) under mutationLock + atomic snapshot republish
  * refreshFromConfig(cfg) hook for Phase 4 command handlers
  * refreshFor(World) iterates ECS via forEachEntityParallel +
    TransformComponent.getComponentType() (ComponentType IS-A Query, no builder) +
    Box.containsPosition(x,y,z); publishes per-world snapshot via AtomicReference.
  * Lazy ComponentType init (avoids Hytale PluginBase static init in tests)
  * Snapshots map keyed by Object (not World) so JDK 25 tests don't need Mockito
- 7/7 RegionRegistryTest pass: CRUD, snapshot read/publish, cross-thread visibility,
  refreshFromConfig atomic swap.

Probe results (recorded for SUMMARY):
- ArchetypeChunk.getRef(int) NOT present; actual method is getReferenceTo(int)
- TransformComponent.getPosition() returns com.hypixel.hytale.math.vector.Vector3d
  in pinned 2026.03.26 (Phase 02-01 deviation pattern recurs)
2026-04-23 00:54:32 +02:00
kayjaydee 216f544d9b feat(02-01): wire GravityFlipConfig to regions.json via named withConfig
- GravityFlipConfig wraps List<GravityFlipRegion> via ArrayCodec under the
  KeyedCodec("Regions", ...) entry. Decoded list is a mutable ArrayList
  (NOT List.of(arr)) so Phase 4 commands can mutate at runtime.
- GravityFlipPlugin.configHolder uses the named overload
  withConfig("regions", GravityFlipConfig.CODEC) — produces
  <dataDirectory>/regions.json. The 1-arg overload would hardcode config.json.
- configHolder() javadoc documents the save contract (no auto-save on
  shutdown; mutators must call configHolder.save() explicitly) and the shared
  mutable reference caveat that 02-02's RegionRegistry will resolve.
- 4 round-trip tests cover: empty default, two-region order preservation,
  empty list, and list mutability (regression guard against List.of).
2026-04-23 00:43:26 +02:00