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'.
- 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
- 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)
- 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
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.
- 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.
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.
- 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.
- 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.
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.
- 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
- 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
- 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'
- 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)
- 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.
- 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).
- GravityFlipRegion holds Name (String) + Box (Hytale AABB) + Enabled (boolean),
serialised through a BuilderCodec keyed on "Name"/"Box"/"Enabled".
- Validators.nonNull() applied to Name and Box (correct package is
com.hypixel.hytale.codec.validation.Validators, not codec.builder).
- Server jar 2026.03.26 uses com.hypixel.hytale.math.vector.Vector3d for Box.min/max
(NOT org.joml.Vector3d, which only appeared in newer builds).
- 3 JUnit 5 round-trip tests cover name/box/enabled, enabled=false, and empty name.
- testImplementation added for the Server artifact so codec encode/decode is
reachable from unit tests.