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
This commit is contained in:
2026-04-23 11:48:29 +02:00
parent f7dcc7d59a
commit af02e19685
@@ -78,6 +78,15 @@ public final class GravityApplier {
/** Tick entry point. NO-OP si world ou snapshot est null. */ /** Tick entry point. NO-OP si world ou snapshot est null. */
public void apply(World world, RegionSnapshot snapshot) { public void apply(World world, RegionSnapshot snapshot) {
if (world == null || snapshot == null) return; if (world == null || snapshot == null) return;
// THREADING (fix WorldThread assert 2026-04-23) : `Store.forEachEntityParallel` exige
// d'être initié depuis la WorldThread (assertThread @Store.java:2362). Le tick loop tourne
// sur `GravityFlip-Detect` (ScheduledExecutorService) → on dispatch tout le travail ECS
// via `world.execute(Runnable)`. Le CommandBuffer reste utilisé côté mutations, mais
// ne résout PAS l'assert côté appelant (amendement D-04 du plan 03-01).
world.execute(() -> applyOnWorldThread(world, snapshot));
}
private void applyOnWorldThread(World world, RegionSnapshot snapshot) {
Collection<GravityFlipRegion> enabledRegions = snapshot.byRegion().keySet(); Collection<GravityFlipRegion> enabledRegions = snapshot.byRegion().keySet();
// Stratégie figée (cf. WARNING 1 résolu) : on accepte la duplication du containsPosition // Stratégie figée (cf. WARNING 1 résolu) : on accepte la duplication du containsPosition
// (cf. must_haves trade-off) plutôt que de coupler à un nouveau contrat RegionSnapshot // (cf. must_haves trade-off) plutôt que de coupler à un nouveau contrat RegionSnapshot