commit 9f9b85e1f2afe7fd7683d4b11dae577b9635c961 Author: George Date: Sun Jun 7 12:18:45 2026 +0100 Initial commit diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..97652dc Binary files /dev/null and b/.DS_Store differ diff --git a/.gradle/9.5.1/checksums/checksums.lock b/.gradle/9.5.1/checksums/checksums.lock new file mode 100644 index 0000000..6c1e89f Binary files /dev/null and b/.gradle/9.5.1/checksums/checksums.lock differ diff --git a/.gradle/9.5.1/checksums/md5-checksums.bin b/.gradle/9.5.1/checksums/md5-checksums.bin new file mode 100644 index 0000000..72a6cdc Binary files /dev/null and b/.gradle/9.5.1/checksums/md5-checksums.bin differ diff --git a/.gradle/9.5.1/checksums/sha1-checksums.bin b/.gradle/9.5.1/checksums/sha1-checksums.bin new file mode 100644 index 0000000..c242146 Binary files /dev/null and b/.gradle/9.5.1/checksums/sha1-checksums.bin differ diff --git a/.gradle/9.5.1/executionHistory/executionHistory.bin b/.gradle/9.5.1/executionHistory/executionHistory.bin new file mode 100644 index 0000000..af3dd4a Binary files /dev/null and b/.gradle/9.5.1/executionHistory/executionHistory.bin differ diff --git a/.gradle/9.5.1/executionHistory/executionHistory.lock b/.gradle/9.5.1/executionHistory/executionHistory.lock new file mode 100644 index 0000000..0788ddb Binary files /dev/null and b/.gradle/9.5.1/executionHistory/executionHistory.lock differ diff --git a/.gradle/9.5.1/fileChanges/last-build.bin b/.gradle/9.5.1/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/.gradle/9.5.1/fileChanges/last-build.bin differ diff --git a/.gradle/9.5.1/fileHashes/fileHashes.bin b/.gradle/9.5.1/fileHashes/fileHashes.bin new file mode 100644 index 0000000..985fc6d Binary files /dev/null and b/.gradle/9.5.1/fileHashes/fileHashes.bin differ diff --git a/.gradle/9.5.1/fileHashes/fileHashes.lock b/.gradle/9.5.1/fileHashes/fileHashes.lock new file mode 100644 index 0000000..999c0af Binary files /dev/null and b/.gradle/9.5.1/fileHashes/fileHashes.lock differ diff --git a/.gradle/9.5.1/fileHashes/resourceHashesCache.bin b/.gradle/9.5.1/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000..88b6bec Binary files /dev/null and b/.gradle/9.5.1/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/9.5.1/gc.properties b/.gradle/9.5.1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000..8ec8afc Binary files /dev/null and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 0000000..69cad0a --- /dev/null +++ b/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Sat Jun 06 20:22:47 BST 2026 +gradle.version=9.5.1 diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin new file mode 100644 index 0000000..51df46f Binary files /dev/null and b/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe new file mode 100644 index 0000000..fb01239 Binary files /dev/null and b/.gradle/file-system.probe differ diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/Docs/Living World Mod - Studio Development Plan.md b/Docs/Living World Mod - Studio Development Plan.md new file mode 100644 index 0000000..4831325 --- /dev/null +++ b/Docs/Living World Mod - Studio Development Plan.md @@ -0,0 +1,4815 @@ +Living World Mod — Studio Development Plan +Volume 1: Core Architecture & Simulation Engine + +Document status: Working draft +Purpose: Build the real technical foundation for an Eco-style living Minecraft world mod. + +1\. What This Project Is + +This mod is not a normal content mod. It is a simulation layer that sits above Minecraft and gives the world memory, momentum, and consequences. The goal is to make the server feel alive even when players are not actively touching every part of it. + +Minecraft normally changes because a player loads an area, a random tick fires, an entity AI runs, or a vanilla system updates. This project adds a higher-level world simulation that tracks regions, ecology, settlements, factions, trade, roads, climate, resources, danger, and historical events. + +The first volume exists to define the engine layer. No ecology, settlements, factions, wars, roads, or economy systems should be built until this foundation is working. + +2\. Non-Negotiable Design Rules + +Rule 1: The world must continue without players nearby. + +The mod must not depend on loaded chunks for its main simulation. Loaded chunks are only used when the simulation needs to visibly apply changes to terrain, structures, roads, or entities. Most world evolution must happen in abstract region data. + +Rule 2: Simulate regions, not chunks. + +Chunks are too small and too numerous. The simulation uses regions as its main unit of world state. A region starts as 8 by 8 chunks, equal to 128 by 128 blocks. This size is large enough to reduce processing cost and small enough to let different parts of the world evolve differently. + +Rule 3: Everything must be modular. + +Ecology, settlements, factions, roads, economy, climate, wildlife, history, and events must be separate modules. A module can depend on the core API, services, region data, and events. A module must not directly depend on another gameplay module. + +Rule 4: Save data must be versioned from day one. + +Every saved object must include a schema version. No system is allowed to save unversioned data. Migrations must be part of the architecture before the first playable release. + +Rule 5: Debugging tools are not optional. + +A simulation this large is impossible to balance by guesswork. The mod must include commands to inspect regions, force ticks, simulate time, show history, dump save data, and profile module cost. + +3\. Target Mod Loader and Version Strategy + +Primary target: NeoForge or Forge for modern Minecraft. + +The project should avoid hard-coding version-specific logic into simulation classes. Minecraft-facing adapters should live in dedicated packages so the simulation core can survive porting between Minecraft versions. + +Recommended approach: + +\- Core simulation code should be plain Java where possible. +\- Minecraft integration should be isolated. +\- Save schemas should avoid depending directly on Minecraft runtime classes. +\- BlockPos, ResourceLocation, Level, ServerLevel, and entity classes should be converted into internal data objects at the boundary. + +4\. High-Level Package Structure + +com.livingworld + +api +Public interfaces and data contracts that other modules or addons may use. + +bootstrap +Startup code. Owns system creation order. No gameplay module should self-bootstrap. + +config +Configuration files and validation. + +core +Engine-level systems such as lifecycle, service registry, simulation control, and global state. + +data +Save data, serialization, migration, schema validation, and export tooling. + +events +Living World event bus and event definitions. + +regions +Region identity, region state, region manager, region cache, and region query logic. + +modules +Gameplay simulation modules. Each module lives in its own subpackage. + +commands +Server commands for inspection and control. + +debug +Profiling, dumps, overlays, diagnostics, and test helpers. + +networking +Packets and client synchronization. + +testing +Game tests, simulation tests, mock services, and long-run validation tools. + +5\. Core Startup Sequence + +Startup order must be deterministic. The bootstrap system owns all major system creation. + +Startup flow: + +1\. Minecraft loads the mod entrypoint. +2\. LivingWorldMod creates the bootstrap context. +3\. Config files are loaded. +4\. Config values are validated. +5\. Core services are created. +6\. The Living World event bus is created. +7\. Data migrations are registered. +8\. Saved data services are prepared. +9\. Module registry is created. +10\. Built-in modules are registered. +11\. Commands are registered. +12\. Networking packets are registered. +13\. Server-start hooks wait for world access. +14\. On server start, world save data is loaded. +15\. Simulation manager starts ticking. + +The main mod entrypoint should not contain gameplay logic. Its job is to call bootstrap and register Minecraft-facing hooks. + +6\. Core Services + +The project uses a service layer so modules do not need to know how everything is implemented. + +Required services: + +SimulationService +Owns simulation ticking, update queues, module update order, and performance budgets. + +RegionService +Provides region lookup, region creation, coordinate conversion, region queries, and active region iteration. + +PersistenceService +Owns save/load requests, dirty tracking, schema versioning, migration calls, and disk writes. + +EventService +Owns Living World event publishing and subscription. + +ModuleService +Owns module registry, lifecycle calls, module enable/disable state, and module dependency validation. + +TimeService +Owns Living World calendar time, conversion between Minecraft ticks and simulation time, and time acceleration. + +DebugService +Owns diagnostics, profiling, debug dumps, and command helper output. + +ConfigService +Owns validated configuration access. + +HistoryService +Owns historical record creation and lookup. + +7\. Service Access Rules + +Modules may request services through a controlled service locator or dependency injection during initialization. + +Allowed: + +\- EcologyModule receives RegionService, EventService, ConfigService, and DebugService. +\- SettlementModule receives RegionService, EventService, TimeService, and HistoryService. + +Not allowed: + +\- EcologyModule directly creates SettlementModule. +\- RoadModule directly reads private settlement internals. +\- FactionModule writes directly to region save files. + +All cross-system communication must happen through services, public data contracts, or events. + +8\. Region Architecture + +A region is the main unit of simulation. It is not a chunk and it is not a biome. It is an abstract simulation cell covering multiple chunks. + +Default region size: + +\- Width: 8 chunks +\- Depth: 8 chunks +\- Block footprint: 128 by 128 blocks + +A region stores simulation state even when no chunk in that area is loaded. + +Region identity: + +RegionCoordinate +\- regionX +\- regionZ +\- dimensionId + +Dimension must be included from the beginning. Even if v1 only simulates the Overworld, the architecture must not assume a single dimension forever. + +9\. RegionCoordinate Design + +Class name: +RegionCoordinate + +Type: +Immutable value object. + +Fields: +\- String dimensionId +\- int x +\- int z + +Required methods: +\- equals +\- hashCode +\- toString +\- fromBlockPosition +\- fromChunkPosition +\- toMinimumChunkX +\- toMinimumChunkZ +\- toCenterBlockX +\- toCenterBlockZ + +Rules: + +\- RegionCoordinate must be immutable. +\- It must be safe as a HashMap key. +\- It must never store a direct Level or ServerLevel reference. +\- Dimension identity must be saved as a stable string or resource key value. + +10\. Region Object Design + +Class name: +Region + +Purpose: +Stores all simulation state for one region. + +Core fields: +\- UUID id +\- RegionCoordinate coordinate +\- RegionLifecycleState lifecycleState +\- long createdAtSimulationTick +\- long lastUpdatedSimulationTick +\- boolean dirty +\- RegionFlags flags +\- RegionMetrics metrics +\- RegionModuleData moduleData + +RegionMetrics fields: +\- double ecologyScore +\- double pollutionScore +\- double prosperityScore +\- double dangerScore +\- double stabilityScore +\- double developmentScore +\- double resourcePressureScore + +RegionModuleData: +A container mapping module IDs to module-specific region data. + +Example: +\- ecology \-\> EcologyRegionData +\- climate \-\> ClimateRegionData +\- settlements \-\> SettlementRegionData +\- roads \-\> RoadRegionData + +The core Region class must not contain every future field directly. It should contain common metrics plus a structured module data map. + +11\. Region Lifecycle + +Every region exists in exactly one lifecycle state. + +States: + +UNLOADED +Region is not currently in memory. + +LOADING +Region data is being loaded or created. + +ACTIVE +Region is available for simulation. + +DIRTY +Region has changed and must be saved. + +SAVING +Persistence service is writing region data. + +FAILED +Region failed to load, save, or validate. + +UNLOADING +Region is being removed from memory after save. + +Rules: + +\- A region cannot move from UNLOADED directly to DIRTY. +\- A region cannot be saved unless it is DIRTY or explicitly force-saved. +\- A failed region must not be simulated until recovery succeeds. +\- Region transitions must be logged when debug mode is enabled. + +12\. RegionManager Responsibilities + +Class name: +RegionManager + +Responsibilities: + +\- Convert block and chunk positions to region coordinates. +\- Return active regions. +\- Create missing regions. +\- Load regions from persistence. +\- Mark regions dirty. +\- Queue regions for saving. +\- Unload low-priority regions from memory. +\- Provide region query APIs. +\- Protect against duplicate region instances. + +Internal fields: + +\- Map\ activeRegions +\- RegionFactory regionFactory +\- RegionStorage regionStorage +\- RegionCache regionCache +\- RegionQueryEngine queryEngine +\- RegionLifecycleController lifecycleController +\- DebugService debugService + +Threading rule: +For v1, RegionManager is main-thread owned. Async save preparation may be added later, but direct mutation of active region objects must happen on the server thread until a safe threading model exists. + +13\. RegionFactory + +Class name: +RegionFactory + +Purpose: +Creates new region objects with default values. + +Responsibilities: + +\- Assign UUIDs. +\- Assign coordinate. +\- Initialize lifecycle state. +\- Initialize common metrics. +\- Ask each enabled module to create default module data. +\- Validate the finished region. + +Method: +createNewRegion(RegionCoordinate coordinate) + +Creation rules: + +\- New regions start with lifecycle state ACTIVE. +\- New regions are marked dirty immediately so they are saved. +\- Module default data must be deterministic based on world seed and region coordinate where randomness is needed. + +14\. RegionStorage + +Class name: +RegionStorage + +Purpose: +Bridge between RegionManager and PersistenceService. + +Responsibilities: + +\- Load serialized region data. +\- Save serialized region data. +\- Validate schema version. +\- Trigger migration if needed. +\- Return load failures in a structured way. + +RegionStorage must not contain simulation logic. + +15\. RegionCache + +Class name: +RegionCache + +Purpose: +Reduce memory pressure while keeping important regions available. + +Cache policy v1: + +\- Always keep regions near online players. +\- Always keep regions containing settlements. +\- Always keep regions containing active events. +\- Unload quiet regions after a configurable idle time. + +Cache metrics: + +\- active region count +\- dirty region count +\- failed region count +\- memory estimate +\- average load time +\- average save time + +16\. Region Query Engine + +Class name: +RegionQueryEngine + +Purpose: +Provide safe ways to search regions. + +Required queries: + +\- getRegionAtBlockPos +\- getRegionAtChunkPos +\- getNeighbourRegions +\- getRegionsInRadius +\- getRegionsWithSettlement +\- getRegionsAbovePollution +\- getRegionsBelowEcology +\- getRegionsByPriority +\- getRegionsWithActiveEvent + +Rules: + +\- Queries must avoid loading chunks. +\- Queries may load region data if explicitly requested. +\- Expensive queries must have limits. +\- Debug commands must use query APIs rather than scanning internal maps directly. + +17\. Simulation Scheduler + +Class name: +SimulationScheduler + +Purpose: +Controls when regions update and how much work happens per cycle. + +Minecraft runs at 20 ticks per second. The Living World simulation must not run heavy logic every tick. + +Default timing: + +\- Simulation interval: every 100 Minecraft ticks +\- Equivalent: every 5 seconds +\- Configurable: yes + +Scheduler fields: + +\- long minecraftTickCounter +\- long simulationTickCounter +\- int maxRegionsPerCycle +\- int maxMillisecondsPerCycle +\- Queue\ updateQueue +\- SimulationProfiler profiler +\- RegionPriorityCalculator priorityCalculator + +18\. Region Update Job + +Class name: +RegionUpdateJob + +Fields: + +\- RegionCoordinate coordinate +\- int priority +\- long queuedAtSimulationTick +\- Set\ requestedModules +\- UpdateReason reason + +UpdateReason examples: + +\- NORMAL\_ROLLING\_UPDATE +\- PLAYER\_NEARBY +\- SETTLEMENT\_PRESENT +\- ACTIVE\_EVENT +\- FORCED\_DEBUG\_COMMAND +\- SAVE\_MIGRATION\_REQUIRED + +19\. Simulation Budgeting + +The scheduler must prevent lag spikes. + +Budget rules: + +\- Never update all regions in one cycle unless explicitly forced by debug command. +\- Stop processing when max region count is reached. +\- Stop processing when time budget is reached. +\- Continue remaining jobs next cycle. +\- Log budget overruns. + +Default budget: + +\- Max regions per cycle: 50 +\- Max time per cycle: 25 milliseconds +\- Emergency stop: 40 milliseconds + +If a module exceeds its budget repeatedly, the profiler flags it. + +20\. Region Priority + +Not all regions deserve equal update frequency. + +Priority sources: + +\- Player nearby: very high +\- Active settlement: high +\- Active event: high +\- Road or trade route: medium +\- High pollution: medium +\- High danger: medium +\- Empty wilderness: low + +Priority formula v1: + +priority \= base \+ playerBonus \+ settlementBonus \+ eventBonus \+ roadBonus \+ dangerBonus \+ pollutionBonus \- idlePenalty + +Priority must be deterministic and inspectable through debug commands. + +21\. Simulation Manager + +Class name: +SimulationManager + +Purpose: +Main coordinator for the simulation. + +Responsibilities: + +\- Receive server tick callbacks. +\- Ask SimulationScheduler whether a simulation cycle should run. +\- Ask RegionManager for update candidates. +\- Ask ModuleRegistry for enabled modules. +\- Run module updates in the correct order. +\- Publish events generated by modules. +\- Mark changed regions dirty. +\- Ask PersistenceService to process save queue. +\- Send profiler data to DebugService. + +The SimulationManager should be boring and predictable. It should coordinate, not contain gameplay rules. + +22\. Module Interface + +Interface name: +SimulationModule + +Required methods: + +\- String getModuleId() +\- ModuleMetadata getMetadata() +\- void initialize(ModuleContext context) +\- void onServerStarted(ServerContext context) +\- void createDefaultRegionData(Region region) +\- ModuleUpdateResult updateRegion(RegionUpdateContext context) +\- void onLivingWorldEvent(LivingWorldEvent event) +\- void saveModuleData(PersistenceWriter writer) +\- void loadModuleData(PersistenceReader reader) +\- void shutdown() + +Rules: + +\- Modules must report whether they changed a region. +\- Modules must not save files directly. +\- Modules must not load Minecraft chunks for abstract simulation. +\- Modules must validate their config during initialize. + +23\. Module Metadata + +Class name: +ModuleMetadata + +Fields: + +\- moduleId +\- displayName +\- version +\- description +\- requiredCoreVersion +\- dependencies +\- optionalDependencies +\- defaultEnabled +\- serverOnly +\- experimental + +Purpose: +The ModuleRegistry uses metadata to validate load order and reject incompatible modules. + +24\. Module Context + +Class name: +ModuleContext + +Purpose: +Dependency container passed to modules at initialization. + +Contains: + +\- ConfigService +\- RegionService +\- EventService +\- TimeService +\- PersistenceService +\- DebugService +\- HistoryService +\- RandomService + +Modules receive only the services they are allowed to use. + +25\. Module Update Result + +Class name: +ModuleUpdateResult + +Fields: + +\- boolean changedRegion +\- List\ generatedEvents +\- List\ historyRecords +\- int estimatedCost +\- List\ warnings + +Purpose: +Prevents modules from mutating global systems directly during update. They return results, and SimulationManager applies them in a controlled way. + +26\. Module Update Order + +Default order: + +1\. Climate +2\. Ecology +3\. Wildlife +4\. Resources +5\. Settlements +6\. Economy +7\. Roads +8\. Factions +9\. Events +10\. History + +Reasoning: + +Climate affects ecology. Ecology affects wildlife and resources. Resources affect settlements. Settlements affect economy and roads. Economy affects factions. Events and history record the outcome. + +27\. Event Architecture + +There are three event layers. + +Layer 1: Minecraft events +Examples: block break, block place, entity death, player login, world tick. + +Layer 2: Living World simulation events +Examples: forest expanded, pollution increased, settlement founded, trade route opened. + +Layer 3: Historical events +Examples: town founded, famine began, war declared, city abandoned. + +Minecraft events should be translated into Living World events at the boundary. Modules should mainly listen to Living World events, not raw Minecraft events. + +28\. Living World Event Bus + +Class name: +LivingWorldEventBus + +Responsibilities: + +\- Register listeners. +\- Publish events. +\- Queue events safely during simulation updates. +\- Prevent recursive event storms. +\- Log event counts for debugging. +\- Allow modules to subscribe by event type. + +Rules: + +\- Events are immutable. +\- Events include timestamp and source module ID. +\- Events may be cancellable only when explicitly designed as cancellable. +\- Historical events are created from selected simulation events, not every minor event. + +29\. Persistence Service + +Class name: +PersistenceService + +Responsibilities: + +\- Own all save and load operations. +\- Track dirty regions. +\- Track dirty global module data. +\- Validate save schemas. +\- Run migrations. +\- Write save files. +\- Recover from partial save failures. +\- Provide debug export commands. + +Persistence rule: +Modules never call file write APIs directly. + +30\. Save File Layout + +Recommended layout: + +world/livingworld/ + +core.json +regions/ +region\_overworld\_0\_0.json +region\_overworld\_0\_1.json +settlements.json +factions.json +events.json +history.json +migrations.log +profiling/ +latest\_profile.json + +Early development may use JSON for readability. Later versions may switch to compressed NBT or SQLite if performance requires it. The persistence layer must hide this decision from modules. + +31\. Save Schema Versioning + +Every save file includes: + +\- schemaVersion +\- modVersion +\- createdAt +\- updatedAt +\- data + +Example: + +{ + "schemaVersion": 1, + "modVersion": "0.1.0", + "data": {} +} + +No unversioned save file is valid. + +32\. Migration System + +Class name: +MigrationManager + +Responsibilities: + +\- Detect save schema version. +\- Find migration path. +\- Apply migrations in order. +\- Validate migrated data. +\- Backup old data before migration. +\- Record migration in migrations.log. + +Example migration: + +V1 stores ecology as one number. +V2 splits ecology into forestDensity, soilHealth, waterQuality, and biodiversity. + +The migration converts the old value into new fields using deterministic defaults. + +33\. Time Service + +Class name: +TimeService + +Purpose: +The Living World needs its own calendar. + +Fields: + +\- long simulationTick +\- int day +\- int month +\- int year +\- double timeScale + +Default mapping: + +\- 1 Minecraft day equals 1 Living World month. +\- 12 Minecraft days equals 1 Living World year. + +The mapping must be configurable. + +34\. Debug Commands + +Command root: +/lw + +Required commands: + +/lw region info +Shows region coordinate, lifecycle state, metrics, module data summary, dirty state, and priority. + +/lw region dump +Exports full region data to a debug file. + +/lw simulate 100 +Runs 100 simulation ticks as fast as budget allows. + +/lw simulate 1000 unsafe +Runs forced simulation without normal delay. Requires operator permission. + +/lw modules list +Shows loaded modules, enabled state, version, and update cost. + +/lw profile start +Starts profiler capture. + +/lw profile stop +Stops profiler capture and writes report. + +/lw history recent +Shows recent historical records. + +/lw event trigger +Triggers a test Living World event. + +35\. Profiling Framework + +Class name: +SimulationProfiler + +Tracks: + +\- total simulation cycle time +\- per-module update time +\- per-region update time +\- event count +\- dirty region count +\- save queue size +\- memory estimate +\- budget overruns + +Profiler output should be readable by humans and exportable as JSON. + +36\. Testing Requirements for Volume 1 + +Minimum tests before Volume 2 begins: + +Test 1: Region coordinate conversion +Given block positions across positive and negative coordinates, region coordinates must be correct. + +Test 2: Region creation +Requesting a missing region creates one with valid default data. + +Test 3: Region persistence +A region changed, saved, unloaded, and reloaded must keep identical data. + +Test 4: Scheduler budget +Given 5000 regions, scheduler only processes the configured number per cycle. + +Test 5: Module lifecycle +A fake module receives initialize, update, save, load, and shutdown calls in correct order. + +Test 6: Event bus +Publishing a test event reaches the correct listeners once. + +Test 7: Migration +A v1 save migrates to v2 without data loss. + +Test 8: Long run +The simulation runs 100,000 ticks without crash, memory leak, or corrupted save data. + +37\. Definition of Done for Volume 1 + +Volume 1 is complete when the following exist in code: + +\- Bootstrap system +\- Config loading +\- Core service registry +\- RegionCoordinate +\- Region +\- RegionManager +\- RegionFactory +\- RegionStorage +\- RegionCache +\- RegionQueryEngine +\- SimulationScheduler +\- SimulationManager +\- ModuleRegistry +\- SimulationModule interface +\- LivingWorldEventBus +\- PersistenceService +\- MigrationManager +\- TimeService +\- Debug commands +\- SimulationProfiler +\- Test module +\- Persistence tests +\- Scheduler tests +\- Event tests +\- Long-run simulation test + +Only after these are passing should Volume 2 systems be implemented. + +38\. Implementation Rule for AI Agents + +AI agents must not skip foundation tasks. + +Every implementation task must include: + +\- Purpose +\- File path +\- Class name +\- Inputs +\- Outputs +\- Dependencies +\- Exact method signatures +\- Expected tests +\- Definition of done + +Vague tasks are banned. + +Bad task: +Build region manager. + +Good task: +Create src/main/java/com/livingworld/regions/RegionCoordinate.java as an immutable record with dimensionId, x, and z fields. Add fromBlockPosition and fromChunkPosition factory methods. Add unit tests for positive and negative coordinates. Done when all tests pass and RegionCoordinate can be used as a HashMap key. + +End of current Volume 1 draft section. + +Next section to add: exact file-by-file implementation backlog for the Volume 1 foundation. + +39\. Volume 1 File-by-File Implementation Backlog + +This section turns the architecture into buildable work. The tasks below are intentionally small, explicit, and ordered. A developer or AI coding agent should complete them in sequence unless a task explicitly says it can be done later. + +Backlog rules: + +\- Do not create gameplay modules yet. +\- Do not implement ecology, settlements, roads, factions, or economy yet. +\- Do not start with mobs, structures, or terrain changes. +\- Build the engine first. +\- Each task must compile before moving on. +\- Each task must either add tests or explain why tests are not yet possible. +\- Every class should have a single clear responsibility. + +40\. Milestone 1 — Project Skeleton + +Goal: +Create the base mod project and folder structure. + +Definition of done: +The mod launches in a development environment, logs a startup message, and has empty packages prepared for the core architecture. + +Task 1.1 — Create base mod package + +Purpose: +Establish the root Java package. + +File path: +src/main/java/com/livingworld/LivingWorldMod.java + +Class name: +LivingWorldMod + +Dependencies: +Forge or NeoForge mod loader entrypoint. + +Implementation notes: +This class should contain only mod entrypoint wiring. It must not contain simulation logic. It should create or call a bootstrap class. + +Required behaviour: +\- Defines public static final String MOD\_ID \= "livingworld". +\- Logs that Living World is loading. +\- Registers mod lifecycle listeners. +\- Delegates setup to LivingWorldBootstrap. + +Definition of done: +The mod loads without crashing and the log shows a clear startup message. + +Task 1.2 — Create bootstrap package + +Purpose: +Create startup ownership boundary. + +File path: +src/main/java/com/livingworld/bootstrap/LivingWorldBootstrap.java + +Class name: +LivingWorldBootstrap + +Required methods: +\- void initialize() +\- void onCommonSetup() +\- void onServerStarting() +\- void onServerStarted() +\- void onServerStopping() + +Implementation notes: +For now, methods can log their lifecycle stage. Later tasks will fill them with service creation and startup ordering. + +Definition of done: +All lifecycle methods exist and can be called from the mod entrypoint. + +Task 1.3 — Create package placeholders + +Purpose: +Create the long-term source tree. + +Packages to create: +\- com.livingworld.api +\- com.livingworld.bootstrap +\- com.livingworld.config +\- com.livingworld.core +\- com.livingworld.core.lifecycle +\- com.livingworld.core.registry +\- com.livingworld.core.services +\- com.livingworld.core.simulation +\- com.livingworld.data +\- com.livingworld.data.saved +\- com.livingworld.data.migration +\- com.livingworld.data.serialization +\- com.livingworld.events +\- com.livingworld.regions +\- com.livingworld.regions.cache +\- com.livingworld.regions.query +\- com.livingworld.commands +\- com.livingworld.debug +\- com.livingworld.networking +\- com.livingworld.testing +\- com.livingworld.modules + +Definition of done: +The packages exist and the project still compiles. + +Task 1.4 — Create build constants + +Purpose: +Centralize mod constants. + +File path: +src/main/java/com/livingworld/core/LivingWorldConstants.java + +Class name: +LivingWorldConstants + +Fields: +\- MOD\_ID +\- MOD\_NAME +\- CURRENT\_CORE\_SCHEMA\_VERSION +\- DEFAULT\_REGION\_SIZE\_CHUNKS +\- DEFAULT\_SIMULATION\_INTERVAL\_TICKS + +Initial values: +\- MOD\_ID \= "livingworld" +\- MOD\_NAME \= "Living World" +\- CURRENT\_CORE\_SCHEMA\_VERSION \= 1 +\- DEFAULT\_REGION\_SIZE\_CHUNKS \= 8 +\- DEFAULT\_SIMULATION\_INTERVAL\_TICKS \= 100 + +Definition of done: +LivingWorldMod uses LivingWorldConstants.MOD\_ID instead of duplicating the string. + +41\. Milestone 2 — Logging and Diagnostics Foundation + +Goal: +Create consistent logging before more systems exist. + +Definition of done: +Every core system can log through one shared logging helper. + +Task 2.1 — Create logger wrapper + +File path: +src/main/java/com/livingworld/debug/LivingWorldLogger.java + +Class name: +LivingWorldLogger + +Purpose: +Provide a central logging wrapper so logs are consistently formatted. + +Required methods: +\- info(String message) +\- warn(String message) +\- error(String message) +\- error(String message, Throwable throwable) +\- debug(String message) + +Rules: +\- Debug logs should respect a future debug config flag. +\- Do not print directly to System.out. + +Definition of done: +Bootstrap uses LivingWorldLogger for all startup logs. + +Task 2.2 — Create diagnostic category enum + +File path: +src/main/java/com/livingworld/debug/DiagnosticCategory.java + +Enum values: +\- BOOTSTRAP +\- CONFIG +\- REGIONS +\- SIMULATION +\- PERSISTENCE +\- EVENTS +\- MODULES +\- COMMANDS +\- NETWORKING +\- TESTING + +Purpose: +Allow future logs and profiler entries to be grouped. + +Definition of done: +Logger can optionally accept a DiagnosticCategory. + +42\. Milestone 3 — Configuration Foundation + +Goal: +Create config objects and validation before systems depend on hard-coded values. + +Task 3.1 — Create simulation config model + +File path: +src/main/java/com/livingworld/config/SimulationConfig.java + +Class name: +SimulationConfig + +Fields: +\- int regionSizeChunks +\- int simulationIntervalTicks +\- int maxRegionsPerCycle +\- int maxMillisecondsPerCycle +\- int emergencyStopMilliseconds +\- boolean enableDebugCommands +\- boolean enableProfiler + +Default values: +\- regionSizeChunks \= 8 +\- simulationIntervalTicks \= 100 +\- maxRegionsPerCycle \= 50 +\- maxMillisecondsPerCycle \= 25 +\- emergencyStopMilliseconds \= 40 +\- enableDebugCommands \= true +\- enableProfiler \= true + +Required methods: +\- validate() + +Validation rules: +\- regionSizeChunks must be at least 1\. +\- simulationIntervalTicks must be at least 1\. +\- maxRegionsPerCycle must be at least 1\. +\- maxMillisecondsPerCycle must be at least 1\. +\- emergencyStopMilliseconds must be greater than or equal to maxMillisecondsPerCycle. + +Definition of done: +Invalid values throw a clear validation exception. + +Task 3.2 — Create config service interface + +File path: +src/main/java/com/livingworld/core/services/ConfigService.java + +Interface name: +ConfigService + +Required methods: +\- SimulationConfig getSimulationConfig() +\- void reload() +\- void validate() + +Definition of done: +Interface compiles and is referenced by ModuleContext later. + +Task 3.3 — Create default config service implementation + +File path: +src/main/java/com/livingworld/config/DefaultConfigService.java + +Class name: +DefaultConfigService + +Implements: +ConfigService + +Purpose: +Provide config values before full Forge/NeoForge config wiring is completed. + +Definition of done: +Bootstrap can create DefaultConfigService, validate it, and log the active simulation interval. + +43\. Milestone 4 — Core Service Registry + +Goal: +Make service access controlled and predictable. + +Task 4.1 — Create ServiceKey + +File path: +src/main/java/com/livingworld/core/services/ServiceKey.java + +Class name: +ServiceKey + +Type: +Generic immutable value object. + +Fields: +\- String id +\- Class\ serviceType + +Rules: +\- Must be immutable. +\- Must validate non-empty ID. +\- Must validate serviceType is not null. + +Definition of done: +Can be used as a key for registered services. + +Task 4.2 — Create ServiceRegistry + +File path: +src/main/java/com/livingworld/core/services/ServiceRegistry.java + +Class name: +ServiceRegistry + +Responsibilities: +\- Register services. +\- Reject duplicate service IDs. +\- Retrieve services by key. +\- Check if service exists. +\- Lock registry after bootstrap. + +Required methods: +\- \ void register(ServiceKey\ key, T service) +\- \ T get(ServiceKey\ key) +\- \ Optional\ find(ServiceKey\ key) +\- boolean isRegistered(ServiceKey\ key) +\- void lock() +\- boolean isLocked() + +Definition of done: +Duplicate registration fails. Getting a missing service fails with a clear error. + +Task 4.3 — Create CoreServices keys + +File path: +src/main/java/com/livingworld/core/services/CoreServices.java + +Class name: +CoreServices + +Purpose: +Central list of built-in service keys. + +Keys to define: +\- CONFIG +\- REGIONS +\- SIMULATION +\- PERSISTENCE +\- EVENTS +\- MODULES +\- TIME +\- DEBUG +\- HISTORY + +Definition of done: +ServiceRegistry can register and retrieve ConfigService using CoreServices.CONFIG. + +44\. Milestone 5 — Region Identity + +Goal: +Implement coordinate conversion correctly before any region storage exists. + +Task 5.1 — Create RegionCoordinate + +File path: +src/main/java/com/livingworld/regions/RegionCoordinate.java + +Class name: +RegionCoordinate + +Recommended type: +Java record. + +Fields: +\- String dimensionId +\- int x +\- int z + +Required factory methods: +\- static RegionCoordinate fromChunk(String dimensionId, int chunkX, int chunkZ, int regionSizeChunks) +\- static RegionCoordinate fromBlock(String dimensionId, int blockX, int blockZ, int regionSizeChunks) + +Required instance methods: +\- int minChunkX(int regionSizeChunks) +\- int minChunkZ(int regionSizeChunks) +\- int maxChunkX(int regionSizeChunks) +\- int maxChunkZ(int regionSizeChunks) +\- int centerBlockX(int regionSizeChunks) +\- int centerBlockZ(int regionSizeChunks) +\- String stableId() + +Important rule: +Use floor division, not normal truncating division, so negative coordinates work correctly. + +Test cases: +\- block 0,0 maps to region 0,0. +\- block 127,127 maps to region 0,0 when region size is 8 chunks. +\- block 128,0 maps to region 1,0. +\- block \-1,0 maps to region \-1,0. +\- block \-128,0 maps to region \-1,0. +\- block \-129,0 maps to region \-2,0. + +Definition of done: +All coordinate tests pass and RegionCoordinate is safe as a map key. + +Task 5.2 — Create RegionLifecycleState + +File path: +src/main/java/com/livingworld/regions/RegionLifecycleState.java + +Enum values: +\- UNLOADED +\- LOADING +\- ACTIVE +\- DIRTY +\- SAVING +\- FAILED +\- UNLOADING + +Definition of done: +Enum exists and is used by Region in the next milestone. + +Task 5.3 — Create RegionFlags + +File path: +src/main/java/com/livingworld/regions/RegionFlags.java + +Class name: +RegionFlags + +Fields: +\- boolean hasPlayerActivity +\- boolean hasSettlement +\- boolean hasRoad +\- boolean hasActiveEvent +\- boolean forceLoadedBySimulation +\- boolean corrupted + +Required methods: +\- copy() +\- clearTransientFlags() + +Definition of done: +Flags can be created, copied, and reset. + +45\. Milestone 6 — Region Core Data + +Goal: +Create the in-memory region object and common metrics. + +Task 6.1 — Create RegionMetrics + +File path: +src/main/java/com/livingworld/regions/RegionMetrics.java + +Class name: +RegionMetrics + +Fields: +\- double ecologyScore +\- double pollutionScore +\- double prosperityScore +\- double dangerScore +\- double stabilityScore +\- double developmentScore +\- double resourcePressureScore + +Rules: +\- Values should generally be clamped between 0 and 100\. +\- Clamping should happen through setter methods or normalize(). + +Required methods: +\- normalize() +\- copy() +\- static RegionMetrics defaults() + +Definition of done: +Default metrics create a neutral region and normalize clamps out-of-range values. + +Task 6.2 — Create RegionModuleData + +File path: +src/main/java/com/livingworld/regions/RegionModuleData.java + +Class name: +RegionModuleData + +Purpose: +Store module-specific data without bloating the core Region class. + +Fields: +\- Map\ moduleData + +Required methods: +\- put(String moduleId, Object data) +\- \ Optional\ get(String moduleId, Class\ type) +\- boolean contains(String moduleId) +\- Set\ moduleIds() +\- RegionModuleData copyShallow() + +V1 note: +Using Object is acceptable for the first architecture pass. Later, replace with a typed serialization contract. + +Definition of done: +Can store and retrieve typed module data safely. + +Task 6.3 — Create Region + +File path: +src/main/java/com/livingworld/regions/Region.java + +Class name: +Region + +Fields: +\- UUID id +\- RegionCoordinate coordinate +\- RegionLifecycleState lifecycleState +\- long createdAtSimulationTick +\- long lastUpdatedSimulationTick +\- boolean dirty +\- RegionFlags flags +\- RegionMetrics metrics +\- RegionModuleData moduleData + +Required methods: +\- markDirty() +\- clearDirty() +\- isDirty() +\- updateLastSimulatedTick(long tick) +\- setLifecycleState(RegionLifecycleState state) +\- validate() + +Validation rules: +\- id must not be null. +\- coordinate must not be null. +\- lifecycleState must not be null. +\- metrics must not be null. +\- moduleData must not be null. + +Definition of done: +Region can be constructed, validated, marked dirty, and updated. + +46\. Milestone 7 — Region Creation and Lifecycle Control + +Goal: +Create safe lifecycle transitions and deterministic region creation. + +Task 7.1 — Create RegionLifecycleController + +File path: +src/main/java/com/livingworld/regions/RegionLifecycleController.java + +Class name: +RegionLifecycleController + +Purpose: +Validate state transitions. + +Required methods: +\- boolean canTransition(RegionLifecycleState from, RegionLifecycleState to) +\- void transition(Region region, RegionLifecycleState target) + +Allowed transitions: +\- UNLOADED to LOADING +\- LOADING to ACTIVE +\- LOADING to FAILED +\- ACTIVE to DIRTY +\- DIRTY to SAVING +\- SAVING to ACTIVE +\- SAVING to FAILED +\- ACTIVE to UNLOADING +\- UNLOADING to UNLOADED +\- FAILED to LOADING + +Definition of done: +Invalid transitions throw clear exceptions. + +Task 7.2 — Create RegionFactory + +File path: +src/main/java/com/livingworld/regions/RegionFactory.java + +Class name: +RegionFactory + +Dependencies: +\- ModuleRegistry later, but initial version can accept no modules. + +Required methods: +\- Region createNewRegion(RegionCoordinate coordinate, long simulationTick) + +Rules: +\- New region receives random UUID. +\- New region starts ACTIVE. +\- New region has default metrics. +\- New region is dirty immediately. +\- New region validates before return. + +Definition of done: +Factory creates valid regions that pass Region.validate(). + +47\. Milestone 8 — Module Contracts + +Goal: +Define module contracts before modules exist. + +Task 8.1 — Create SimulationModule interface + +File path: +src/main/java/com/livingworld/modules/SimulationModule.java + +Interface name: +SimulationModule + +Required methods: +\- String getModuleId() +\- ModuleMetadata getMetadata() +\- void initialize(ModuleContext context) +\- void onServerStarted(ServerContext context) +\- void createDefaultRegionData(Region region) +\- ModuleUpdateResult updateRegion(RegionUpdateContext context) +\- void onLivingWorldEvent(LivingWorldEvent event) +\- void saveModuleData(PersistenceWriter writer) +\- void loadModuleData(PersistenceReader reader) +\- void shutdown() + +Temporary placeholders: +If ServerContext, LivingWorldEvent, PersistenceWriter, or PersistenceReader do not exist yet, create minimal placeholder interfaces in the appropriate packages. + +Definition of done: +The interface compiles and can be implemented by a fake test module. + +Task 8.2 — Create ModuleMetadata + +File path: +src/main/java/com/livingworld/modules/ModuleMetadata.java + +Recommended type: +Record or immutable class. + +Fields: +\- String moduleId +\- String displayName +\- String version +\- String description +\- String requiredCoreVersion +\- List\ dependencies +\- List\ optionalDependencies +\- boolean defaultEnabled +\- boolean serverOnly +\- boolean experimental + +Definition of done: +ModuleMetadata validates moduleId and displayName are present. + +Task 8.3 — Create ModuleContext + +File path: +src/main/java/com/livingworld/modules/ModuleContext.java + +Class name: +ModuleContext + +Fields: +\- ServiceRegistry services + +Required methods: +\- \ T getService(ServiceKey\ key) + +Definition of done: +A module can request ConfigService through ModuleContext. + +Task 8.4 — Create ModuleUpdateResult + +File path: +src/main/java/com/livingworld/modules/ModuleUpdateResult.java + +Fields: +\- boolean changedRegion +\- List\ generatedEvents +\- List\ historyRecords +\- int estimatedCost +\- List\ warnings + +Required static constructors: +\- noChange() +\- changed() + +Definition of done: +A fake module can return noChange or changed results. + +Task 8.5 — Create ModuleRegistry + +File path: +src/main/java/com/livingworld/modules/ModuleRegistry.java + +Responsibilities: +\- Register modules. +\- Reject duplicate module IDs. +\- Resolve module order. +\- Return enabled modules. +\- Initialize modules. +\- Shutdown modules. + +Required methods: +\- void register(SimulationModule module) +\- List\ getAllModules() +\- List\ getEnabledModules() +\- Optional\ find(String moduleId) +\- void initializeAll(ModuleContext context) +\- void shutdownAll() + +Definition of done: +A fake module can be registered, initialized, found, and shut down. + +48\. Milestone 9 — Event Foundation + +Goal: +Create the Living World event layer. + +Task 9.1 — Create LivingWorldEvent + +File path: +src/main/java/com/livingworld/events/LivingWorldEvent.java + +Class name: +LivingWorldEvent + +Recommended type: +Interface. + +Required methods: +\- String eventType() +\- long simulationTick() +\- String sourceModuleId() + +Definition of done: +Test events can implement this interface. + +Task 9.2 — Create BaseLivingWorldEvent + +File path: +src/main/java/com/livingworld/events/BaseLivingWorldEvent.java + +Class name: +BaseLivingWorldEvent + +Fields: +\- String eventType +\- long simulationTick +\- String sourceModuleId + +Definition of done: +Can be used for simple test events. + +Task 9.3 — Create LivingWorldEventListener + +File path: +src/main/java/com/livingworld/events/LivingWorldEventListener.java + +Interface method: +\- void onEvent(LivingWorldEvent event) + +Definition of done: +Listener interface compiles. + +Task 9.4 — Create LivingWorldEventBus + +File path: +src/main/java/com/livingworld/events/LivingWorldEventBus.java + +Responsibilities: +\- Register listener by event type. +\- Publish event. +\- Queue event during active dispatch. +\- Prevent endless recursive dispatch. + +Required methods: +\- void register(String eventType, LivingWorldEventListener listener) +\- void publish(LivingWorldEvent event) +\- int getPublishedEventCount() +\- int getListenerCount(String eventType) + +Definition of done: +A published event reaches its listener once. + +49\. Milestone 10 — Time Service + +Goal: +Create internal simulation time independent from Minecraft time. + +Task 10.1 — Create LivingWorldCalendar + +File path: +src/main/java/com/livingworld/core/simulation/LivingWorldCalendar.java + +Fields: +\- long simulationTick +\- int day +\- int month +\- int year + +Required methods: +\- advanceTicks(long ticks) +\- copy() +\- toDisplayString() + +Definition of done: +Advancing ticks updates the calendar in a predictable way. + +Task 10.2 — Create TimeService interface + +File path: +src/main/java/com/livingworld/core/services/TimeService.java + +Methods: +\- long getSimulationTick() +\- LivingWorldCalendar getCalendar() +\- void advanceSimulationTick() +\- void advanceSimulationTicks(long ticks) + +Definition of done: +Interface exists and is used by DefaultTimeService. + +Task 10.3 — Create DefaultTimeService + +File path: +src/main/java/com/livingworld/core/simulation/DefaultTimeService.java + +Implements: +TimeService + +Definition of done: +Simulation tick can advance and calendar state can be read. + +50\. Milestone 11 — Simulation Scheduler and Manager + +Goal: +Create the heartbeat of the simulation. + +Task 11.1 — Create UpdateReason + +File path: +src/main/java/com/livingworld/core/simulation/UpdateReason.java + +Enum values: +\- NORMAL\_ROLLING\_UPDATE +\- PLAYER\_NEARBY +\- SETTLEMENT\_PRESENT +\- ACTIVE\_EVENT +\- FORCED\_DEBUG\_COMMAND +\- SAVE\_MIGRATION\_REQUIRED + +Definition of done: +Enum compiles and is used by RegionUpdateJob. + +Task 11.2 — Create RegionUpdateJob + +File path: +src/main/java/com/livingworld/core/simulation/RegionUpdateJob.java + +Fields: +\- RegionCoordinate coordinate +\- int priority +\- long queuedAtSimulationTick +\- Set\ requestedModules +\- UpdateReason reason + +Definition of done: +Jobs can be sorted by descending priority. + +Task 11.3 — Create RegionPriorityCalculator + +File path: +src/main/java/com/livingworld/core/simulation/RegionPriorityCalculator.java + +Method: +\- int calculatePriority(Region region) + +Initial scoring: +\- base \= 5 +\- player activity bonus \= 100 +\- settlement bonus \= 75 +\- active event bonus \= 75 +\- road bonus \= 50 +\- danger bonus \= dangerScore / 2 +\- pollution bonus \= pollutionScore / 2 + +Definition of done: +Priority score is deterministic and testable. + +Task 11.4 — Create SimulationScheduler + +File path: +src/main/java/com/livingworld/core/simulation/SimulationScheduler.java + +Responsibilities: +\- Track Minecraft tick count. +\- Decide when simulation should run. +\- Maintain update queue. +\- Respect max region budget. +\- Respect time budget. + +Required methods: +\- void onMinecraftTick() +\- boolean shouldRunSimulationCycle() +\- void queueRegion(RegionUpdateJob job) +\- List\ pollJobsForCycle() + +Definition of done: +Given 500 queued jobs and maxRegionsPerCycle of 50, one cycle returns 50 jobs. + +Task 11.5 — Create SimulationManager + +File path: +src/main/java/com/livingworld/core/simulation/SimulationManager.java + +Responsibilities: +\- Receive tick calls. +\- Ask scheduler whether to run. +\- Get update jobs. +\- Resolve regions. +\- Run enabled modules. +\- Publish generated events. +\- Mark changed regions dirty. + +Definition of done: +A fake module can update a fake region through SimulationManager. + +51\. Milestone 12 — Persistence Foundation + +Goal: +Build save/load abstraction before data becomes complicated. + +Task 12.1 — Create PersistenceWriter + +File path: +src/main/java/com/livingworld/data/serialization/PersistenceWriter.java + +Purpose: +Abstraction for writing save data. + +Initial methods: +\- void writeString(String key, String value) +\- void writeInt(String key, int value) +\- void writeLong(String key, long value) +\- void writeDouble(String key, double value) +\- void writeBoolean(String key, boolean value) + +Definition of done: +Interface compiles. + +Task 12.2 — Create PersistenceReader + +File path: +src/main/java/com/livingworld/data/serialization/PersistenceReader.java + +Purpose: +Abstraction for reading save data. + +Initial methods: +\- String readString(String key, String defaultValue) +\- int readInt(String key, int defaultValue) +\- long readLong(String key, long defaultValue) +\- double readDouble(String key, double defaultValue) +\- boolean readBoolean(String key, boolean defaultValue) + +Definition of done: +Interface compiles. + +Task 12.3 — Create SaveMetadata + +File path: +src/main/java/com/livingworld/data/saved/SaveMetadata.java + +Fields: +\- int schemaVersion +\- String modVersion +\- long createdAt +\- long updatedAt + +Definition of done: +Save metadata validates schemaVersion is greater than zero. + +Task 12.4 — Create PersistenceService interface + +File path: +src/main/java/com/livingworld/core/services/PersistenceService.java + +Methods: +\- void markRegionDirty(Region region) +\- void saveDirtyRegions() +\- Optional\ loadRegion(RegionCoordinate coordinate) +\- void saveRegion(Region region) +\- void flushAll() + +Definition of done: +Interface compiles and RegionStorage can depend on it. + +52\. Milestone 13 — Region Storage and Manager + +Goal: +Connect region creation, lookup, cache, and storage. + +Task 13.1 — Create RegionStorage + +File path: +src/main/java/com/livingworld/regions/RegionStorage.java + +Dependencies: +\- PersistenceService + +Methods: +\- Optional\ load(RegionCoordinate coordinate) +\- void save(Region region) + +Definition of done: +Can delegate load/save to PersistenceService. + +Task 13.2 — Create RegionCache + +File path: +src/main/java/com/livingworld/regions/cache/RegionCache.java + +Fields: +\- Map\ activeRegions + +Methods: +\- Optional\ get(RegionCoordinate coordinate) +\- void put(Region region) +\- void remove(RegionCoordinate coordinate) +\- Collection\ allActive() +\- int size() + +Definition of done: +Can store and retrieve regions by coordinate. + +Task 13.3 — Create RegionQueryEngine + +File path: +src/main/java/com/livingworld/regions/query/RegionQueryEngine.java + +Methods: +\- Optional\ getRegion(RegionCoordinate coordinate) +\- List\ getRegionsInRadius(RegionCoordinate center, int radius) +\- List\ getRegionsWithActiveEvent() +\- List\ getRegionsWithSettlement() + +Definition of done: +Queries operate on RegionCache without touching chunks. + +Task 13.4 — Create RegionManager + +File path: +src/main/java/com/livingworld/regions/RegionManager.java + +Dependencies: +\- RegionFactory +\- RegionStorage +\- RegionCache +\- RegionQueryEngine +\- RegionLifecycleController +\- SimulationConfig + +Methods: +\- Region getOrCreateRegion(RegionCoordinate coordinate) +\- Optional\ findRegion(RegionCoordinate coordinate) +\- Region getOrCreateRegionAtBlock(String dimensionId, int blockX, int blockZ) +\- Collection\ getActiveRegions() +\- void markDirty(Region region) +\- void unloadRegion(RegionCoordinate coordinate) + +Definition of done: +Requesting a region first checks cache, then storage, then factory. + +53\. Milestone 14 — Debug Commands + +Goal: +Expose inspection tools early. + +Task 14.1 — Create LivingWorldCommandRoot + +File path: +src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java + +Purpose: +Register /lw command tree. + +Initial subcommands: +\- /lw status +\- /lw region info +\- /lw modules list +\- /lw simulate \ + +Definition of done: +Commands register and return placeholder output if systems are not fully connected. + +Task 14.2 — Create RegionInfoCommand + +File path: +src/main/java/com/livingworld/commands/RegionInfoCommand.java + +Output: +\- Region coordinate +\- Lifecycle state +\- Dirty state +\- Metrics +\- Flags +\- Module IDs present + +Definition of done: +Standing in a world and running /lw region info prints region state. + +Task 14.3 — Create SimulateCommand + +File path: +src/main/java/com/livingworld/commands/SimulateCommand.java + +Purpose: +Force simulation ticks for testing. + +Rules: +\- Operator-only. +\- Maximum safe default: 1000 ticks. +\- Unsafe mode can bypass limit later. + +Definition of done: +Command advances TimeService and runs SimulationManager for requested ticks. + +54\. Milestone 15 — Profiling + +Goal: +Measure simulation cost before real systems are added. + +Task 15.1 — Create SimulationProfiler + +File path: +src/main/java/com/livingworld/debug/SimulationProfiler.java + +Tracks: +\- cycle start time +\- cycle end time +\- per-module timings +\- per-region timings +\- event count +\- save count +\- budget overruns + +Methods: +\- beginCycle() +\- endCycle() +\- beginModule(String moduleId) +\- endModule(String moduleId) +\- recordEvent() +\- recordSave() +\- createSnapshot() + +Definition of done: +Profiler can produce a snapshot after a simulation cycle. + +Task 15.2 — Create SimulationProfileSnapshot + +File path: +src/main/java/com/livingworld/debug/SimulationProfileSnapshot.java + +Fields: +\- long totalCycleNanos +\- Map\ moduleTimings +\- int eventsPublished +\- int regionsUpdated +\- int savesPerformed +\- boolean budgetExceeded + +Definition of done: +Snapshot can be printed in debug command output. + +55\. Milestone 16 — Test Module and Long Run Test + +Goal: +Prove the engine can run before real gameplay systems exist. + +Task 16.1 — Create TestSimulationModule + +File path: +src/main/java/com/livingworld/testing/TestSimulationModule.java + +Purpose: +Fake module used to verify lifecycle and update flow. + +Behaviour: +\- On initialize, record initialized \= true. +\- On updateRegion, increment a counter in test module data. +\- Every 10 updates, return changedRegion \= true. + +Definition of done: +SimulationManager can run this module against test regions. + +Task 16.2 — Create RegionCoordinateTest + +File path: +src/test/java/com/livingworld/regions/RegionCoordinateTest.java + +Must test: +\- Positive coordinate conversion. +\- Negative coordinate conversion. +\- Stable ID generation. +\- HashMap key usage. + +Definition of done: +All tests pass. + +Task 16.3 — Create RegionLifecycleControllerTest + +File path: +src/test/java/com/livingworld/regions/RegionLifecycleControllerTest.java + +Must test: +\- Allowed transitions succeed. +\- Invalid transitions fail. +\- Failed regions can return to loading. + +Definition of done: +All tests pass. + +Task 16.4 — Create SchedulerBudgetTest + +File path: +src/test/java/com/livingworld/core/simulation/SchedulerBudgetTest.java + +Must test: +\- 500 jobs with limit 50 returns 50\. +\- Higher priority jobs are returned first. +\- Jobs not processed remain queued. + +Definition of done: +All tests pass. + +Task 16.5 — Create EventBusTest + +File path: +src/test/java/com/livingworld/events/EventBusTest.java + +Must test: +\- Listener receives event. +\- Listener receives event once. +\- Multiple listeners receive event. +\- Unknown event type does not crash. + +Definition of done: +All tests pass. + +Task 16.6 — Create LongRunSimulationTest + +File path: +src/test/java/com/livingworld/testing/LongRunSimulationTest.java + +Purpose: +Prove the foundation does not collapse under time. + +Test setup: +\- Create 1000 fake regions. +\- Register TestSimulationModule. +\- Run 100,000 simulation ticks. +\- Save dirty regions periodically. + +Pass conditions: +\- No crash. +\- No unhandled exception. +\- No invalid lifecycle transition. +\- Dirty region count does not grow forever. +\- Profiler reports cycle data. + +Definition of done: +Long-run test completes reliably. + +56\. Milestone 17 — Volume 1 Completion Gate + +Volume 1 cannot be considered complete until all items below are true: + +\- The mod launches. +\- Bootstrap sequence logs all stages. +\- Config service loads and validates defaults. +\- Service registry registers all core services. +\- RegionCoordinate handles negative coordinates correctly. +\- Region objects validate their internal state. +\- RegionFactory creates valid dirty regions. +\- RegionLifecycleController rejects invalid transitions. +\- ModuleRegistry can register and initialize a test module. +\- LivingWorldEventBus publishes events once. +\- TimeService advances simulation time. +\- SimulationScheduler respects region budget. +\- SimulationManager runs at least one module over at least one region. +\- RegionManager can get, create, cache, mark dirty, and unload regions. +\- Debug commands exist. +\- Profiler creates snapshots. +\- Unit tests pass. +\- Long-run simulation test passes. + +Only then should development move into Volume 2: Ecology, Climate, Wildlife, and Environmental Change. + +57\. Next Volume 1 Expansion + +The next expansion should add implementation-level pseudocode for the most important classes: + +\- RegionCoordinate +\- Region +\- RegionManager +\- SimulationScheduler +\- SimulationManager +\- ModuleRegistry +\- LivingWorldEventBus +\- PersistenceService + +That section should include method bodies in pseudocode, expected edge cases, and exact failure handling. + +58\. Platform Decision Update — Minecraft Java 26.1 + +Date added: 05 June 2026 + +Decision: +This project is a Minecraft Java Edition NeoForge-first mod. It is not a Bukkit, Spigot, or Paper plugin. + +Current verified target: +The current verified documentation target is Minecraft Java Edition 26.1. The official Minecraft 26.1 release article was published on 24 March 2026 and describes the Tiny Takeover drop. NeoForge documentation is also currently available for version 26.1. + +Primary implementation target: +Minecraft Java Edition 26.1 with NeoForge 26.1. + +Forward compatibility target: +When the next Minecraft Java release becomes stable, the project should not immediately jump to it. First, verify that NeoForge or Forge support has stabilised, common dependencies have updated, and the mod can still pass its long-run simulation tests. + +Loader preference: +1\. NeoForge first. +2\. Forge-compatible architecture where practical. +3\. Fabric is a possible future port after the core simulation engine is stable. +4\. Bukkit, Spigot, and Paper are not valid primary targets for this project. + +Why this is a mod, not a Bukkit plugin: + +The Living World project needs deep world simulation, persistent custom world data, server tick integration, modular simulation systems, possible custom blocks, possible custom entities, custom networking, dedicated server testing, and long-term modded-server compatibility. + +A Bukkit, Spigot, or Paper plugin is better suited to server-side behaviour on mostly vanilla servers: commands, moderation tools, permissions, minigames, simple economy features, region protection, and server rule changes. + +This project is different. It is closer to an engine-level simulation mod. It needs access to mod-loader systems such as registries, events, data storage, networking, and client/server separation. Therefore, it belongs on NeoForge or Forge. + +Architecture rule: +Core simulation code must remain as plain Java wherever possible. Minecraft-specific code must sit behind a platform adapter so future ports are possible. + +Required platform packages: + +com.livingworld.platform +Common platform interfaces. + +com.livingworld.platform.neoforge +NeoForge implementation. + +com.livingworld.platform.forge +Optional future Forge compatibility layer. + +com.livingworld.integration.minecraft +Boundary code that converts Minecraft classes into internal Living World data objects. + +Boundary conversion examples: + +\- BlockPos becomes an internal block coordinate object. +\- ChunkPos becomes an internal chunk coordinate object. +\- ResourceKey or dimension ID becomes a stable dimension string. +\- ServerLevel is used only at the boundary, never stored inside core simulation objects. +\- Entity references become stable IDs or event data, not long-lived direct object references. + +Version policy: + +\- Do not hard-code the architecture to one Minecraft minor version. +\- Do not let gameplay modules depend directly on NeoForge classes. +\- Keep mod-loader code in the platform layer. +\- Keep region simulation, scheduler, persistence rules, and event logic in plain Java where possible. +\- Before every Minecraft version upgrade, run all unit tests, migration tests, dedicated server tests, and long-run simulation tests. + +Volume 1 backlog addition — Platform Adapter Foundation + +Task P1 — Create PlatformAdapter interface + +File path: +src/main/java/com/livingworld/platform/PlatformAdapter.java + +Purpose: +Define the boundary between the Living World core engine and the active Minecraft mod loader. + +Required methods: +\- String getPlatformName() +\- String getMinecraftVersion() +\- String getLoaderVersion() +\- boolean isDedicatedServer() +\- Path getWorldSaveDirectory() +\- void registerCommands() +\- void registerServerTickHook() +\- void registerPlayerEventHooks() + +Definition of done: +The core bootstrap can ask the platform adapter what loader and Minecraft version are active without directly importing NeoForge classes. + +Task P2 — Create NeoForgePlatformAdapter + +File path: +src/main/java/com/livingworld/platform/neoforge/NeoForgePlatformAdapter.java + +Purpose: +Implement PlatformAdapter using NeoForge APIs. + +Rules: +\- This class may import NeoForge and Minecraft classes. +\- Core simulation classes must not import NeoForge classes. +\- The adapter should forward server tick events to SimulationManager. + +Definition of done: +The mod logs active platform name, Minecraft version, and loader version during bootstrap. + +Task P3 — Create MinecraftCoordinateMapper + +File path: +src/main/java/com/livingworld/integration/minecraft/MinecraftCoordinateMapper.java + +Purpose: +Convert Minecraft positions into Living World coordinates. + +Required methods: +\- RegionCoordinate fromBlockPos(String dimensionId, int blockX, int blockZ, int regionSizeChunks) +\- RegionCoordinate fromChunkPos(String dimensionId, int chunkX, int chunkZ, int regionSizeChunks) + +Definition of done: +Minecraft boundary code can convert positions without leaking Minecraft classes into region-core classes. + +Task P4 — Update project build policy + +File path: +build.gradle or gradle.properties + +Purpose: +Record the chosen Minecraft and NeoForge versions explicitly when implementation starts. + +Required values: +\- minecraft\_version +\- neoforge\_version +\- mod\_id +\- mod\_version +\- java\_version + +Definition of done: +The project build file clearly targets Minecraft Java 26.1 and NeoForge 26.1 unless a later verified stable target is chosen. + +Task P5 — Add version verification checklist + +File path: +docs/version-verification-checklist.md + +Purpose: +Prevent blind upgrading. + +Checklist: +\- Confirm latest stable Minecraft Java release. +\- Confirm matching NeoForge version exists. +\- Confirm required Java version. +\- Confirm mappings/toolchain changes. +\- Confirm dedicated server launches. +\- Confirm test mod loads. +\- Confirm long-run simulation test passes. +\- Confirm save files migrate correctly. + +Definition of done: +Every Minecraft version bump must complete this checklist before becoming the main target. + +59\. Scope Correction — Ecosystem-Only Mod Direction + +Date added: 05 June 2026 + +Decision: +The Living World mod is now defined as an ecosystem-focused world evolution mod. It must not become an NPC civilisation simulator. + +This means the mod should focus on environmental systems, landscape change, resource pressure, pollution, soil health, water quality, biome pressure, vegetation spread, decay, recovery, erosion-style effects, and long-term ecological consequences. + +Removed from core scope: + +\- No custom NPC civilisation simulation. +\- No autonomous villagers. +\- No village growth system. +\- No settlement AI. +\- No faction diplomacy. +\- No wars. +\- No NPC economy. +\- No wildlife population simulation as a primary system. +\- No direct modification of vanilla villager behaviour. +\- No requirement to alter passive mobs, hostile mobs, or animal spawning for the core mod. + +Allowed only as optional future addons: + +\- Compatibility hooks for existing village mods. +\- Optional data export that other civilisation mods can read. +\- Optional environmental effects that indirectly influence mob spawning if the server owner enables them. +\- Optional addon modules for settlements or factions, kept outside the ecosystem core. + +New primary focus: + +1\. Region ecosystem state +Each region stores ecosystem health values such as soil quality, moisture, vegetation density, water quality, pollution, decay, recovery pressure, temperature pressure, and resource depletion. + +2\. Vegetation and plant pressure +The world can slowly gain or lose vegetation based on region conditions. This should include grass spread, sapling success chance, forest pressure, dead-zone formation, invasive overgrowth, and recovery after damage. + +3\. Soil system +Soil should not be treated as permanent perfect dirt. Regions track soil fertility, compaction, contamination, moisture, and exhaustion. Farming, deforestation, overuse, fire, pollution, and lack of vegetation can reduce soil quality. + +4\. Water system +Water quality is tracked at region level. Pollution near water, industrial blocks, mass farming, decay, and runoff can lower water quality. Clean neighbouring regions and rainfall-style recovery can improve it. + +5\. Pollution system +Pollution is one of the main simulation drivers. It should spread slowly through neighbouring regions, decay over time, and create visible consequences when thresholds are crossed. + +6\. Decomposition and decay +Organic material, fallen leaves, dead vegetation, and abandoned player impact should feed back into soil quality or disease/rot pressure. The world should feel like matter cycles through the environment. + +7\. Resource depletion +Mining, quarrying, logging, farming, and repeated harvesting should reduce local resource scores. Regions should remember that they have been heavily exploited. + +8\. Recovery and succession +Damaged areas should recover through ecological succession: barren ground becomes sparse grass, sparse grass becomes scrub, scrub becomes young woodland, and young woodland becomes mature forest where conditions allow. + +9\. Climate and seasonal pressure +The core ecosystem may use seasonal modifiers and climate pressure, but this should remain environmental. It should not require animal or NPC simulation. + +10\. Visible world feedback +The mod should eventually apply changes to loaded chunks, such as grass becoming coarse dirt, dirt becoming barren dirt, vegetation spreading, polluted water indicators, moss growth, dead vegetation, regrowth zones, and biome-edge transformation. + +Updated module list: + +Core required modules: + +\- Core Simulation +\- Regions +\- Persistence +\- Event Bus +\- Time System +\- Debug Tools +\- Ecosystem Metrics +\- Soil System +\- Water Quality System +\- Pollution System +\- Vegetation System +\- Resource Depletion System +\- Recovery/Succession System +\- Visible World Effects System +\- Configuration +\- Profiling and Testing + +Removed or postponed modules: + +\- Settlements +\- Factions +\- NPC population +\- Villager behaviour +\- War system +\- NPC economy +\- Road generation +\- Trade networks +\- Wildlife simulation + +Architecture impact: + +The existing modular architecture remains valid, but the module package list must be simplified. The project should not spend time designing settlement AI, diplomacy, NPC jobs, village growth, or faction systems. Those are distractions from the actual goal. + +New recommended package structure: + +com.livingworld + +api +bootstrap +config +core +data +events +regions +commands +debug +networking +platform +integration.minecraft +modules.ecosystem +modules.soil +modules.water +modules.pollution +modules.vegetation +modules.resources +modules.recovery +modules.worldeffects +testing + +Updated Volume 1 rule: +The foundation must support modules, but Volume 1 examples should stop assuming future settlement, faction, economy, or wildlife modules. Future examples should use ecosystem modules instead. + +Example module update order: + +1\. Climate pressure +2\. Water quality +3\. Soil quality +4\. Pollution spread +5\. Resource depletion +6\. Vegetation pressure +7\. Recovery and succession +8\. Visible world effects +9\. History/logging + +Updated first playable prototype: + +The first playable version should do this: + +\- Divide the world into regions. +\- Track ecosystem health per region. +\- Track pollution per region. +\- Track soil quality per region. +\- Track vegetation pressure per region. +\- Save and load those values. +\- Provide /lw region info. +\- Allow /lw simulate ticks. +\- Apply one visible effect in loaded chunks. + +First visible effect: +If region pollution is high and soil quality is low, grass blocks in loaded chunks slowly degrade into dirt or coarse dirt. + +Second visible effect: +If region vegetation pressure is high and soil quality is healthy, grass, moss, flowers, or saplings have a small chance to spread in loaded chunks. + +Third visible effect: +If a region has been heavily logged, natural tree regrowth slows until soil and vegetation pressure recover. + +New project identity: +This mod is not Minecraft Eco with villagers. It is Minecraft as a changing ecosystem. The player should feel that the land itself remembers damage, recovers slowly, spreads life where conditions are good, and degrades where conditions are abused. + +Design sentence: +The Living World mod makes the environment evolve over time without adding civilisation simulation. + +60\. AI Prompt Pack — Local Model Workflow for Each Build Step + +Date added: 05 June 2026 + +Purpose: +This section gives copy-and-paste prompts for local AI development using Qwen3.5-9B as the main coding worker and Qwen3.6-27B as the planner, reviewer, debugger, and architecture guard. + +Model usage rule: + +\- Use Qwen3.5-9B for small, isolated implementation tasks. +\- Use Qwen3.6-27B for architecture decisions, reviewing several files at once, diagnosing difficult errors, and checking that the code still follows the design. +\- Do not ask Qwen3.5-9B to build broad systems. +\- Do not waste Qwen3.6-27B on tiny one-class tasks unless Qwen3.5-9B fails twice. + +General 9B worker prompt template: + +Use this when asking Qwen3.5-9B to create or edit one file. + +MODEL: Qwen3.5-9B + +PROMPT: +You are implementing one small Java task for a Minecraft NeoForge mod called Living World. + +Important project rules: +\- Core simulation code must be plain Java wherever possible. +\- Do not import Minecraft or NeoForge classes unless this specific task says to. +\- Do not create extra systems beyond the task. +\- Keep the code small and testable. +\- Follow the package path exactly. +\- Use clear names. +\- Add validation where requested. +\- If tests are requested, write the tests too. + +Task: +\[PASTE TASK HERE\] + +Output required: +1\. Brief explanation of what files you will create or edit. +2\. Full code for each file. +3\. Any test code required. +4\. Notes about assumptions. + +Do not skip requirements. +Do not add unrelated features. + +General 27B architect/reviewer prompt template: + +Use this when asking Qwen3.6-27B to review multiple files, fix design drift, or plan the next batch. + +MODEL: Qwen3.6-27B + +PROMPT: +You are the senior architect reviewing the Living World Minecraft NeoForge mod. + +Project direction: +This is an ecosystem-only world evolution mod. It does not simulate villagers, NPC civilisations, settlements, factions, wars, trade, roads, or wildlife AI. + +Architecture rules: +\- Core simulation must remain plain Java where possible. +\- Minecraft and NeoForge code must stay in platform or integration packages. +\- Regions are the main simulation unit, not chunks. +\- Modules must communicate through services, events, or public data contracts. +\- Save data must be versioned. +\- Debug and tests are mandatory. + +Review these files: +\[PASTE FILES OR SUMMARIES\] + +Check for: +1\. Incorrect Minecraft/NeoForge imports in core classes. +2\. Scope creep into NPCs, factions, roads, or settlements. +3\. Broken package boundaries. +4\. Missing validation. +5\. Bad lifecycle handling. +6\. Save/versioning issues. +7\. Test gaps. +8\. Overly broad or fragile code. + +Output required: +1\. Pass/fail summary. +2\. Specific problems by file. +3\. Exact fixes. +4\. Revised code only where necessary. +5\. Recommended next task. + +61\. Milestone 1 AI Prompts — Project Skeleton + +Task 1.1 prompt — Create base mod package + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/LivingWorldMod.java for a Minecraft NeoForge mod called Living World. + +Requirements: +\- Package: com.livingworld +\- Class name: LivingWorldMod +\- Define public static final String MOD\_ID using LivingWorldConstants.MOD\_ID if that class already exists. If it does not exist yet, temporarily define MOD\_ID \= "livingworld" and add a TODO to replace it after constants are created. +\- This class must contain only entrypoint wiring. +\- It must not contain simulation logic. +\- It must log a startup message. +\- It must delegate startup work to com.livingworld.bootstrap.LivingWorldBootstrap if available. +\- If NeoForge annotations/imports are needed, keep them only in this entrypoint file. + +Output: +\- Full Java file. +\- Any assumptions about the NeoForge version. +\- Do not create unrelated classes. + +Task 1.2 prompt — Create bootstrap class + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/bootstrap/LivingWorldBootstrap.java. + +Requirements: +\- Package: com.livingworld.bootstrap +\- Class name: LivingWorldBootstrap +\- Methods: + \- void initialize() + \- void onCommonSetup() + \- void onServerStarting() + \- void onServerStarted() + \- void onServerStopping() +\- Each method should log its lifecycle stage. +\- Use LivingWorldLogger if available. If not available yet, use a minimal temporary logger comment and no System.out. +\- Do not add gameplay logic. +\- Do not create region, ecosystem, or persistence systems yet. + +Output: +\- Full Java file. +\- Short explanation of where it should be called from. + +Task 1.3 prompt — Create package placeholders + +MODEL: Qwen3.5-9B + +PROMPT: +Create the package placeholder structure for the Living World mod. + +Packages: +\- com.livingworld.api +\- com.livingworld.bootstrap +\- com.livingworld.config +\- com.livingworld.core +\- com.livingworld.core.lifecycle +\- com.livingworld.core.registry +\- com.livingworld.core.services +\- com.livingworld.core.simulation +\- com.livingworld.data +\- com.livingworld.data.saved +\- com.livingworld.data.migration +\- com.livingworld.data.serialization +\- com.livingworld.events +\- com.livingworld.regions +\- com.livingworld.regions.cache +\- com.livingworld.regions.query +\- com.livingworld.commands +\- com.livingworld.debug +\- com.livingworld.networking +\- com.livingworld.platform +\- com.livingworld.platform.neoforge +\- com.livingworld.integration.minecraft +\- com.livingworld.modules +\- com.livingworld.modules.ecosystem +\- com.livingworld.modules.soil +\- com.livingworld.modules.water +\- com.livingworld.modules.pollution +\- com.livingworld.modules.vegetation +\- com.livingworld.modules.resources +\- com.livingworld.modules.recovery +\- com.livingworld.modules.worldeffects +\- com.livingworld.testing + +If Java requires at least one file per package in this project setup, create a package-info.java file with a one-line comment for each package. + +Do not add implementation logic. + +Task 1.4 prompt — Create constants + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/LivingWorldConstants.java. + +Requirements: +\- Package: com.livingworld.core +\- Final utility class. +\- Private constructor. +\- Constants: + \- public static final String MOD\_ID \= "livingworld"; + \- public static final String MOD\_NAME \= "Living World"; + \- public static final int CURRENT\_CORE\_SCHEMA\_VERSION \= 1; + \- public static final int DEFAULT\_REGION\_SIZE\_CHUNKS \= 8; + \- public static final int DEFAULT\_SIMULATION\_INTERVAL\_TICKS \= 100; +\- Do not import Minecraft or NeoForge classes. +\- Add short comments explaining each constant. + +Output: +\- Full Java file. + +Milestone 1 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the completed Milestone 1 project skeleton for the Living World ecosystem mod. + +Files to review: +\[PASTE LivingWorldMod.java, LivingWorldBootstrap.java, LivingWorldConstants.java, and package list\] + +Check: +\- Entrypoint contains no simulation logic. +\- Bootstrap owns startup flow. +\- Constants are centralised. +\- No ecosystem/NPC/settlement/faction systems were added early. +\- Minecraft/NeoForge imports are restricted to entrypoint or platform boundary. + +Output: +\- Pass/fail. +\- Required fixes. +\- Next safe milestone. + +62\. Milestone 2 AI Prompts — Logging and Diagnostics + +Task 2.1 prompt — Create logger wrapper + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/debug/LivingWorldLogger.java. + +Requirements: +\- Package: com.livingworld.debug +\- Class name: LivingWorldLogger +\- Provide static methods: + \- info(String message) + \- warn(String message) + \- error(String message) + \- error(String message, Throwable throwable) + \- debug(String message) + \- info(DiagnosticCategory category, String message) + \- warn(DiagnosticCategory category, String message) + \- error(DiagnosticCategory category, String message) + \- debug(DiagnosticCategory category, String message) +\- Use a standard logger appropriate for the mod environment if available. +\- Do not use System.out. +\- If debug config does not exist yet, allow debug messages for now and add a TODO to wire config later. +\- Do not import ecosystem module classes. + +Output: +\- Full Java file. + +Task 2.2 prompt — Create diagnostic category enum + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/debug/DiagnosticCategory.java. + +Requirements: +\- Package: com.livingworld.debug +\- Enum values: + \- BOOTSTRAP + \- CONFIG + \- REGIONS + \- SIMULATION + \- PERSISTENCE + \- EVENTS + \- MODULES + \- COMMANDS + \- NETWORKING + \- PLATFORM + \- ECOSYSTEM + \- TESTING +\- Add a method displayName() that returns a readable name. +\- Do not import Minecraft or NeoForge classes. + +Output: +\- Full Java enum. + +Milestone 2 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the logging and diagnostics foundation. + +Files: +\[PASTE LivingWorldLogger.java and DiagnosticCategory.java\] + +Check: +\- No System.out usage. +\- Logger methods are consistent. +\- Categories match the ecosystem-only architecture. +\- No unnecessary Minecraft imports. +\- Debug behaviour can later connect to config. + +Output: +\- Problems found. +\- Recommended fixes. +\- Whether Milestone 3 can begin. + +63\. Milestone 3 AI Prompts — Configuration Foundation + +Task 3.1 prompt — Create SimulationConfig + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/config/SimulationConfig.java. + +Requirements: +\- Plain Java class. +\- Fields: + \- int regionSizeChunks \= 8 + \- int simulationIntervalTicks \= 100 + \- int maxRegionsPerCycle \= 50 + \- int maxMillisecondsPerCycle \= 25 + \- int emergencyStopMilliseconds \= 40 + \- boolean enableDebugCommands \= true + \- boolean enableProfiler \= true +\- Add getters. +\- Add setters only if needed for tests. +\- Add validate() method. +\- Validation rules: + \- regionSizeChunks \>= 1 + \- simulationIntervalTicks \>= 1 + \- maxRegionsPerCycle \>= 1 + \- maxMillisecondsPerCycle \>= 1 + \- emergencyStopMilliseconds \>= maxMillisecondsPerCycle +\- Throw IllegalArgumentException with clear messages on invalid values. +\- Do not import Minecraft or NeoForge classes. + +Output: +\- Full Java file. +\- Basic test suggestions. + +Task 3.2 prompt — Create ConfigService interface + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/services/ConfigService.java. + +Requirements: +\- Interface. +\- Methods: + \- SimulationConfig getSimulationConfig() + \- void reload() + \- void validate() +\- Import SimulationConfig from com.livingworld.config. +\- Do not reference NeoForge config APIs here. + +Output: +\- Full Java interface. + +Task 3.3 prompt — Create DefaultConfigService + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/config/DefaultConfigService.java. + +Requirements: +\- Implements ConfigService. +\- Holds a SimulationConfig instance. +\- Constructor creates default SimulationConfig and validates it. +\- getSimulationConfig returns the config. +\- reload can be a safe placeholder for now, but it must revalidate. +\- validate delegates to SimulationConfig.validate(). +\- Log validation success using LivingWorldLogger if available. +\- Do not use NeoForge config classes yet. + +Output: +\- Full Java file. + +Milestone 3 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the configuration foundation for the Living World ecosystem mod. + +Files: +\[PASTE SimulationConfig.java, ConfigService.java, DefaultConfigService.java\] + +Check: +\- Config is plain Java. +\- Validation is strict and clear. +\- No direct NeoForge dependency yet. +\- Defaults match the design document. +\- Future Forge/NeoForge config wiring can be added without rewriting core code. + +Output: +\- Pass/fail. +\- Required fixes. +\- Any missing config values for ecosystem-only MVP. + +64\. Milestone 4 AI Prompts — Core Service Registry + +Task 4.1 prompt — Create ServiceKey + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/services/ServiceKey.java. + +Requirements: +\- Generic immutable value object. +\- Prefer Java record if project Java version supports it. +\- Fields: + \- String id + \- Class\ serviceType +\- Validate id is not null or blank. +\- Validate serviceType is not null. +\- Add stable toString(). +\- Do not import Minecraft or NeoForge classes. + +Output: +\- Full Java file. + +Task 4.2 prompt — Create ServiceRegistry + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/services/ServiceRegistry.java. + +Requirements: +\- Plain Java class. +\- Internally store services by ServiceKey. +\- Methods: + \- \ void register(ServiceKey\ key, T service) + \- \ T get(ServiceKey\ key) + \- \ Optional\ find(ServiceKey\ key) + \- boolean isRegistered(ServiceKey\ key) + \- void lock() + \- boolean isLocked() +\- Reject duplicate registrations. +\- Reject null keys and null services. +\- If locked, reject new registrations. +\- Missing get() should throw clear IllegalStateException. +\- No Minecraft imports. + +Output: +\- Full Java file. +\- Suggested unit tests. + +Task 4.3 prompt — Create CoreServices + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/services/CoreServices.java. + +Requirements: +\- Final utility class with private constructor. +\- Define ServiceKey constants for: + \- CONFIG + \- REGIONS + \- SIMULATION + \- PERSISTENCE + \- EVENTS + \- MODULES + \- TIME + \- DEBUG + \- HISTORY +\- If some service interfaces do not exist yet, add TODO comments and create keys only for existing interfaces, or use Object temporarily with clear TODOs. +\- Do not create fake gameplay services. +\- No Minecraft imports. + +Output: +\- Full Java file. +\- Note any TODOs caused by missing interfaces. + +Milestone 4 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the core service registry. + +Files: +\[PASTE ServiceKey.java, ServiceRegistry.java, CoreServices.java\] + +Check: +\- Type safety is acceptable. +\- Registry cannot be mutated after lock. +\- Errors are clear. +\- No hidden global state problems. +\- No Minecraft imports. +\- Design is suitable for module dependency injection later. + +Output: +\- Pass/fail. +\- Fixes. +\- Risk notes. + +65\. Milestone 5 AI Prompts — Region Identity + +Task 5.1 prompt — Create RegionCoordinate + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionCoordinate.java. + +Requirements: +\- Java record if available. +\- Fields: + \- String dimensionId + \- int x + \- int z +\- Validate dimensionId is not null or blank. +\- Static methods: + \- fromChunk(String dimensionId, int chunkX, int chunkZ, int regionSizeChunks) + \- fromBlock(String dimensionId, int blockX, int blockZ, int regionSizeChunks) +\- Instance methods: + \- int minChunkX(int regionSizeChunks) + \- int minChunkZ(int regionSizeChunks) + \- int maxChunkX(int regionSizeChunks) + \- int maxChunkZ(int regionSizeChunks) + \- int centerBlockX(int regionSizeChunks) + \- int centerBlockZ(int regionSizeChunks) + \- String stableId() +\- Use Math.floorDiv so negative coordinates work correctly. +\- Validate regionSizeChunks \>= 1\. +\- Do not import Minecraft classes. + +Test requirements: +Create RegionCoordinateTest if test framework exists. +Test: +\- block 0,0 maps to region 0,0 +\- block 127,127 maps to region 0,0 with region size 8 chunks +\- block 128,0 maps to region 1,0 +\- block \-1,0 maps to region \-1,0 +\- block \-128,0 maps to region \-1,0 +\- block \-129,0 maps to region \-2,0 +\- stableId is stable +\- works as HashMap key + +Output: +\- Full Java file. +\- Full test file if possible. + +Task 5.2 prompt — Create RegionLifecycleState + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionLifecycleState.java. + +Requirements: +\- Enum values: + \- UNLOADED + \- LOADING + \- ACTIVE + \- DIRTY + \- SAVING + \- FAILED + \- UNLOADING +\- Add comments explaining each state. +\- Do not add methods unless useful. +\- No Minecraft imports. + +Output: +\- Full Java enum. + +Task 5.3 prompt — Create RegionFlags + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionFlags.java. + +Requirements: +\- Plain Java class. +\- Fields: + \- boolean hasPlayerActivity + \- boolean hasHighPollution + \- boolean hasLowSoilQuality + \- boolean hasActiveEcosystemEvent + \- boolean forceLoadedBySimulation + \- boolean corrupted +\- Do not include settlement, road, faction, or NPC flags. +\- Add getters and setters. +\- Add copy(). +\- Add clearTransientFlags(). This should clear hasPlayerActivity and hasActiveEcosystemEvent, but not corrupted. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Milestone 5 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review region identity classes. + +Files: +\[PASTE RegionCoordinate.java, RegionLifecycleState.java, RegionFlags.java, and tests\] + +Check: +\- Negative coordinate math is correct. +\- No Minecraft classes leaked into region core. +\- Flags match ecosystem-only scope. +\- RegionCoordinate is safe as a HashMap key. +\- Tests cover edge cases. + +Output: +\- Pass/fail. +\- Exact fixes if needed. + +66\. Milestone 6 AI Prompts — Region Core Data + +Task 6.1 prompt — Create RegionMetrics + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionMetrics.java. + +Requirements: +\- Plain Java class. +\- Ecosystem-only fields: + \- double ecosystemHealth + \- double pollutionScore + \- double soilQuality + \- double waterQuality + \- double vegetationPressure + \- double resourceDepletion + \- double recoveryPressure +\- Values should normally be clamped between 0 and 100\. +\- Add normalize(). +\- Add copy(). +\- Add static defaults(). +\- Defaults: + \- ecosystemHealth \= 60 + \- pollutionScore \= 0 + \- soilQuality \= 60 + \- waterQuality \= 60 + \- vegetationPressure \= 50 + \- resourceDepletion \= 0 + \- recoveryPressure \= 50 +\- Add applyDelta methods if simple and clean. +\- No Minecraft imports. + +Output: +\- Full Java file. +\- Suggested tests. + +Task 6.2 prompt — Create RegionModuleData + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionModuleData.java. + +Requirements: +\- Stores module-specific data by module ID. +\- Field: + \- Map\ moduleData +\- Methods: + \- void put(String moduleId, Object data) + \- \ Optional\ get(String moduleId, Class\ type) + \- boolean contains(String moduleId) + \- Set\ moduleIds() + \- RegionModuleData copyShallow() +\- Validate moduleId is not null or blank. +\- Reject null data. +\- If get type does not match, return Optional.empty instead of throwing. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 6.3 prompt — Create Region + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/Region.java. + +Requirements: +\- Plain Java class. +\- Fields: + \- UUID id + \- RegionCoordinate coordinate + \- RegionLifecycleState lifecycleState + \- long createdAtSimulationTick + \- long lastUpdatedSimulationTick + \- boolean dirty + \- RegionFlags flags + \- RegionMetrics metrics + \- RegionModuleData moduleData +\- Constructor validates required fields. +\- Methods: + \- markDirty() + \- clearDirty() + \- isDirty() + \- updateLastSimulatedTick(long tick) + \- setLifecycleState(RegionLifecycleState state) + \- validate() +\- validate() checks id, coordinate, lifecycleState, flags, metrics, and moduleData are not null. +\- Do not include settlement, faction, road, or NPC fields. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Milestone 6 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the region core data classes. + +Files: +\[PASTE RegionMetrics.java, RegionModuleData.java, Region.java\] + +Check: +\- Metrics match ecosystem-only scope. +\- Region is not bloated with future unrelated systems. +\- Validation is strong. +\- Dirty state behaviour is sensible. +\- No Minecraft imports. +\- No direct persistence logic inside Region. + +Output: +\- Pass/fail. +\- Exact corrections. + +67\. Milestone 7 AI Prompts — Region Creation and Lifecycle + +Task 7.1 prompt — Create RegionLifecycleController + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionLifecycleController.java. + +Requirements: +\- Plain Java class. +\- Methods: + \- boolean canTransition(RegionLifecycleState from, RegionLifecycleState to) + \- void transition(Region region, RegionLifecycleState target) +\- Allowed transitions: + \- UNLOADED \-\> LOADING + \- LOADING \-\> ACTIVE + \- LOADING \-\> FAILED + \- ACTIVE \-\> DIRTY + \- DIRTY \-\> SAVING + \- SAVING \-\> ACTIVE + \- SAVING \-\> FAILED + \- ACTIVE \-\> UNLOADING + \- UNLOADING \-\> UNLOADED + \- FAILED \-\> LOADING +\- Invalid transitions throw IllegalStateException with clear message. +\- transition() must validate region is not null. +\- No Minecraft imports. + +Output: +\- Full Java file. +\- Suggested tests. + +Task 7.2 prompt — Create RegionFactory + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionFactory.java. + +Requirements: +\- Plain Java class. +\- Method: + \- Region createNewRegion(RegionCoordinate coordinate, long simulationTick) +\- New region rules: + \- random UUID + \- coordinate from argument + \- lifecycleState ACTIVE + \- createdAtSimulationTick \= simulationTick + \- lastUpdatedSimulationTick \= simulationTick + \- dirty \= true + \- default RegionFlags + \- RegionMetrics.defaults() + \- empty RegionModuleData + \- validate before return +\- No Minecraft imports. +\- Do not initialise ecology modules here yet; that comes after ModuleRegistry is connected. + +Output: +\- Full Java file. + +Milestone 7 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review lifecycle and region creation. + +Files: +\[PASTE RegionLifecycleController.java and RegionFactory.java\] + +Check: +\- Invalid transitions are rejected. +\- New regions are immediately dirty. +\- Factory does not import Minecraft classes. +\- Factory does not start implementing ecosystem gameplay prematurely. +\- Tests cover valid and invalid transitions. + +Output: +\- Pass/fail. +\- Fixes. + +68\. Milestone 8 AI Prompts — Module Contracts + +Task 8.1 prompt — Create SimulationModule interface + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/SimulationModule.java. + +Requirements: +\- Interface. +\- Methods: + \- String getModuleId() + \- ModuleMetadata getMetadata() + \- void initialize(ModuleContext context) + \- void onServerStarted(ServerContext context) + \- void createDefaultRegionData(Region region) + \- ModuleUpdateResult updateRegion(RegionUpdateContext context) + \- void onLivingWorldEvent(LivingWorldEvent event) + \- void saveModuleData(PersistenceWriter writer) + \- void loadModuleData(PersistenceReader reader) + \- void shutdown() +\- Create minimal placeholder interfaces/classes if missing: +n \- ServerContext + \- RegionUpdateContext + \- LivingWorldEvent + \- PersistenceWriter + \- PersistenceReader +\- Do not implement any real ecosystem module yet. +\- No Minecraft imports in this interface. + +Output: +\- Full Java interface. +\- Any minimal placeholder files required. + +Task 8.2 prompt — Create ModuleMetadata + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/ModuleMetadata.java. + +Requirements: +\- Immutable class or record. +\- Fields: + \- String moduleId + \- String displayName + \- String version + \- String description + \- String requiredCoreVersion + \- List\ dependencies + \- List\ optionalDependencies + \- boolean defaultEnabled + \- boolean serverOnly + \- boolean experimental +\- Validate moduleId and displayName are not blank. +\- Replace null dependency lists with empty lists. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 8.3 prompt — Create ModuleContext + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/ModuleContext.java. + +Requirements: +\- Plain Java class. +\- Field: + \- ServiceRegistry services +\- Constructor validates services is not null. +\- Method: + \- \ T getService(ServiceKey\ key) +\- Do not expose the whole registry for mutation if avoidable. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 8.4 prompt — Create ModuleUpdateResult + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/ModuleUpdateResult.java. + +Requirements: +\- Immutable class or record. +\- Fields: + \- boolean changedRegion + \- List\ generatedEvents + \- List\ historyRecords + \- int estimatedCost + \- List\ warnings +\- Static constructors: + \- noChange() + \- changed() +\- Null lists become empty lists. +\- estimatedCost must not be negative. +\- No Minecraft imports. + +If HistoryRecord does not exist, create a minimal placeholder in com.livingworld.modules or com.livingworld.events with TODO to move later. + +Output: +\- Full Java file. +\- Any placeholder file required. + +Task 8.5 prompt — Create ModuleRegistry + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/ModuleRegistry.java. + +Requirements: +\- Plain Java class. +\- Responsibilities: + \- register modules + \- reject duplicate module IDs + \- return all modules + \- return enabled modules + \- find by module ID + \- initialize all + \- shutdown all +\- Methods: + \- void register(SimulationModule module) + \- List\ getAllModules() + \- List\ getEnabledModules() + \- Optional\ find(String moduleId) + \- void initializeAll(ModuleContext context) + \- void shutdownAll() +\- For v1, enabled modules are modules where metadata.defaultEnabled is true. +\- Do not implement dependency sorting yet; add TODO. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Milestone 8 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review module contract architecture. + +Files: +\[PASTE SimulationModule.java, ModuleMetadata.java, ModuleContext.java, ModuleUpdateResult.java, ModuleRegistry.java\] + +Check: +\- Modules are generic and not tied to settlements/factions/NPCs. +\- Core module contracts do not import Minecraft classes. +\- ModuleRegistry is simple enough for v1. +\- Missing placeholders are sensible and marked as TODO. +\- Architecture supports ecosystem modules later. + +Output: +\- Pass/fail. +\- Required fixes. +\- Next implementation task. + +69\. Milestone 9 AI Prompts — Event Foundation + +Task 9.1 prompt — Create LivingWorldEvent + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/events/LivingWorldEvent.java. + +Requirements: +\- Interface. +\- Methods: + \- String eventType() + \- long simulationTick() + \- String sourceModuleId() +\- No Minecraft imports. +\- Events must be usable by ecosystem modules later. + +Output: +\- Full Java interface. + +Task 9.2 prompt — Create BaseLivingWorldEvent + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/events/BaseLivingWorldEvent.java. + +Requirements: +\- Immutable class or record implementing LivingWorldEvent. +\- Fields: + \- String eventType + \- long simulationTick + \- String sourceModuleId +\- Validate eventType is not blank. +\- Validate simulationTick is not negative. +\- sourceModuleId may be "core" for core events but must not be blank. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 9.3 prompt — Create LivingWorldEventListener + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/events/LivingWorldEventListener.java. + +Requirements: +\- Functional interface. +\- Method: + \- void onEvent(LivingWorldEvent event) +\- No Minecraft imports. + +Output: +\- Full Java interface. + +Task 9.4 prompt — Create LivingWorldEventBus + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/events/LivingWorldEventBus.java. + +Requirements: +\- Plain Java class. +\- Methods: + \- void register(String eventType, LivingWorldEventListener listener) + \- void publish(LivingWorldEvent event) + \- int getPublishedEventCount() + \- int getListenerCount(String eventType) +\- Validate eventType is not blank. +\- Validate listener is not null. +\- Event publishing should send the event to listeners registered for that event type. +\- Unknown event type should not crash. +\- Prevent obvious recursive event storms with a simple dispatching guard or TODO if not implemented. +\- No Minecraft imports. + +Output: +\- Full Java file. +\- Suggested tests. + +Milestone 9 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the Living World event foundation. + +Files: +\[PASTE LivingWorldEvent.java, BaseLivingWorldEvent.java, LivingWorldEventListener.java, LivingWorldEventBus.java\] + +Check: +\- Events are plain Java. +\- EventBus is not overcomplicated. +\- Unknown events are safe. +\- Recursion risk is noted or controlled. +\- Event types can support pollution/soil/vegetation events later. + +Output: +\- Pass/fail. +\- Fixes. + +70\. Milestone 10 AI Prompts — Time Service + +Task 10.1 prompt — Create LivingWorldCalendar + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/simulation/LivingWorldCalendar.java. + +Requirements: +\- Plain Java class. +\- Fields: + \- long simulationTick + \- int day + \- int month + \- int year +\- Default start: day 1, month 1, year 1, simulationTick 0\. +\- Methods: + \- advanceTicks(long ticks) + \- copy() + \- toDisplayString() +\- Keep the calendar simple for now. +\- Use 30 days per month and 12 months per year unless config is added later. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 10.2 prompt — Create TimeService interface + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/services/TimeService.java. + +Requirements: +\- Interface. +\- Methods: + \- long getSimulationTick() + \- LivingWorldCalendar getCalendar() + \- void advanceSimulationTick() + \- void advanceSimulationTicks(long ticks) +\- No Minecraft imports. + +Output: +\- Full Java interface. + +Task 10.3 prompt — Create DefaultTimeService + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/simulation/DefaultTimeService.java. + +Requirements: +\- Implements TimeService. +\- Owns a LivingWorldCalendar. +\- advanceSimulationTick advances by 1\. +\- advanceSimulationTicks validates ticks \>= 0 and advances calendar. +\- getCalendar returns a copy, not the internal mutable instance. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Milestone 10 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the Living World time system. + +Files: +\[PASTE LivingWorldCalendar.java, TimeService.java, DefaultTimeService.java\] + +Check: +\- Time is independent of Minecraft classes. +\- Calendar cannot be mutated externally by accident. +\- Behaviour is deterministic. +\- Good enough for ecosystem simulation v1. + +Output: +\- Pass/fail. +\- Fixes. + +71\. Milestone 11 AI Prompts — Scheduler and Simulation Manager + +Task 11.1 prompt — Create UpdateReason + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/simulation/UpdateReason.java. + +Requirements: +\- Enum values: + \- NORMAL\_ROLLING\_UPDATE + \- PLAYER\_NEARBY + \- HIGH\_POLLUTION + \- LOW\_SOIL\_QUALITY + \- ACTIVE\_ECOSYSTEM\_EVENT + \- FORCED\_DEBUG\_COMMAND + \- SAVE\_MIGRATION\_REQUIRED +\- Do not include settlement, road, faction, or NPC update reasons. +\- No Minecraft imports. + +Output: +\- Full Java enum. + +Task 11.2 prompt — Create RegionUpdateJob + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/simulation/RegionUpdateJob.java. + +Requirements: +\- Immutable class or record. +\- Fields: + \- RegionCoordinate coordinate + \- int priority + \- long queuedAtSimulationTick + \- Set\ requestedModules + \- UpdateReason reason +\- Validate coordinate and reason are not null. +\- priority must not be negative. +\- queuedAtSimulationTick must not be negative. +\- Null requestedModules becomes empty set. +\- Add comparator for descending priority if clean. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 11.3 prompt — Create RegionPriorityCalculator + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/simulation/RegionPriorityCalculator.java. + +Requirements: +\- Plain Java class. +\- Method: + \- int calculatePriority(Region region) +\- Initial ecosystem-only scoring: + \- base \= 5 + \- hasPlayerActivity bonus \= 100 + \- hasActiveEcosystemEvent bonus \= 75 + \- hasHighPollution bonus \= 50 + \- hasLowSoilQuality bonus \= 50 + \- pollution bonus \= pollutionScore / 2 + \- resource depletion bonus \= resourceDepletion / 3 + \- recovery pressure bonus \= recoveryPressure / 4 +\- Validate region is not null. +\- Result should be deterministic. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 11.4 prompt — Create SimulationScheduler + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/simulation/SimulationScheduler.java. + +Requirements: +\- Plain Java class. +\- Constructor accepts SimulationConfig. +\- Fields: + \- long minecraftTickCounter + \- long simulationTickCounter + \- PriorityQueue\ updateQueue +\- Methods: + \- void onMinecraftTick() + \- boolean shouldRunSimulationCycle() + \- void queueRegion(RegionUpdateJob job) + \- List\ pollJobsForCycle() + \- long getMinecraftTickCounter() + \- long getSimulationTickCounter() +\- shouldRunSimulationCycle true when minecraftTickCounter reaches the configured interval. +\- pollJobsForCycle returns at most maxRegionsPerCycle jobs. +\- Higher priority jobs first. +\- No Minecraft imports. + +Output: +\- Full Java file. +\- Suggested scheduler budget tests. + +Task 11.5 prompt — Create SimulationManager skeleton + +MODEL: Qwen3.6-27B + +PROMPT: +Create the first version of src/main/java/com/livingworld/core/simulation/SimulationManager.java. + +This is an architecture-sensitive class. Keep it boring and coordinator-only. + +Requirements: +\- Constructor dependencies: + \- SimulationScheduler scheduler + \- RegionManager regionManager + \- ModuleRegistry moduleRegistry + \- LivingWorldEventBus eventBus + \- TimeService timeService + \- PersistenceService persistenceService + \- SimulationProfiler profiler, if available. If not available, make it optional or TODO. +\- Methods: + \- void onMinecraftServerTick() + \- void runSimulationCycle() + \- void runForcedSimulationTicks(int ticks) +\- Responsibilities: + \- Advance scheduler on Minecraft tick. + \- If scheduler says cycle should run, run one cycle. + \- Poll region update jobs. + \- Resolve regions from RegionManager. + \- Run enabled modules in order. + \- Publish generated events. + \- Mark changed regions dirty. + \- Advance TimeService. +\- Do not implement ecosystem rules here. +\- Do not import Minecraft or NeoForge classes. +\- Do not scan chunks. + +Output: +\- Full Java file. +\- Any missing interface TODOs. +\- Explanation of control flow. + +Milestone 11 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review scheduler and SimulationManager. + +Files: +\[PASTE UpdateReason.java, RegionUpdateJob.java, RegionPriorityCalculator.java, SimulationScheduler.java, SimulationManager.java\] + +Check: +\- No Minecraft imports in scheduler/manager. +\- Budgeting is respected. +\- SimulationManager contains no gameplay rules. +\- Ecosystem-only priority values are correct. +\- Forced simulation cannot accidentally lock the server forever. +\- Good enough before persistence and region manager are finished. + +Output: +\- Pass/fail. +\- Fixes. +\- Next safest step. + +72\. Milestone 12 AI Prompts — Persistence Foundation + +Task 12.1 prompt — Create PersistenceWriter + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/data/serialization/PersistenceWriter.java. + +Requirements: +\- Interface. +\- Methods: + \- void writeString(String key, String value) + \- void writeInt(String key, int value) + \- void writeLong(String key, long value) + \- void writeDouble(String key, double value) + \- void writeBoolean(String key, boolean value) +\- No Minecraft imports. + +Output: +\- Full Java interface. + +Task 12.2 prompt — Create PersistenceReader + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/data/serialization/PersistenceReader.java. + +Requirements: +\- Interface. +\- Methods: + \- String readString(String key, String defaultValue) + \- int readInt(String key, int defaultValue) + \- long readLong(String key, long defaultValue) + \- double readDouble(String key, double defaultValue) + \- boolean readBoolean(String key, boolean defaultValue) +\- No Minecraft imports. + +Output: +\- Full Java interface. + +Task 12.3 prompt — Create SaveMetadata + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/data/saved/SaveMetadata.java. + +Requirements: +\- Immutable class or record. +\- Fields: + \- int schemaVersion + \- String modVersion + \- long createdAt + \- long updatedAt +\- Validate schemaVersion \> 0\. +\- Validate modVersion is not blank. +\- Validate updatedAt \>= createdAt. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 12.4 prompt — Create PersistenceService interface + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/services/PersistenceService.java. + +Requirements: +\- Interface. +\- Methods: + \- void markRegionDirty(Region region) + \- void saveDirtyRegions() + \- Optional\ loadRegion(RegionCoordinate coordinate) + \- void saveRegion(Region region) + \- void flushAll() +\- No Minecraft imports. +\- Do not implement file storage yet. + +Output: +\- Full Java interface. + +Milestone 12 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review persistence foundation. + +Files: +\[PASTE PersistenceWriter.java, PersistenceReader.java, SaveMetadata.java, PersistenceService.java\] + +Check: +\- Persistence interfaces are plain Java. +\- Region persistence is abstracted. +\- Save metadata is versioned. +\- No module writes files directly. +\- Good enough before JSON/NBT implementation. + +Output: +\- Pass/fail. +\- Fixes. + +73\. Milestone 13 AI Prompts — Region Storage and Manager + +Task 13.1 prompt — Create RegionStorage + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionStorage.java. + +Requirements: +\- Plain Java class. +\- Constructor dependency: + \- PersistenceService persistenceService +\- Methods: + \- Optional\ load(RegionCoordinate coordinate) + \- void save(Region region) +\- load delegates to persistenceService.loadRegion. +\- save delegates to persistenceService.saveRegion. +\- Validate dependencies and arguments. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 13.2 prompt — Create RegionCache + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/cache/RegionCache.java. + +Requirements: +\- Plain Java class. +\- Field: + \- Map\ activeRegions +\- Methods: + \- Optional\ get(RegionCoordinate coordinate) + \- void put(Region region) + \- void remove(RegionCoordinate coordinate) + \- Collection\ allActive() + \- int size() +\- Validate inputs. +\- allActive should return an unmodifiable copy or safe view. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 13.3 prompt — Create RegionQueryEngine + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/query/RegionQueryEngine.java. + +Requirements: +\- Plain Java class. +\- Constructor dependency: + \- RegionCache cache +\- Methods: + \- Optional\ getRegion(RegionCoordinate coordinate) + \- List\ getRegionsInRadius(RegionCoordinate center, int radius) + \- List\ getRegionsWithActiveEcosystemEvent() + \- List\ getRegionsAbovePollution(double threshold) + \- List\ getRegionsBelowSoilQuality(double threshold) +\- Do not touch Minecraft chunks. +\- Radius means region-coordinate radius. +\- Validate radius \>= 0\. +\- No settlement, road, faction, or NPC queries. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 13.4 prompt — Create RegionManager + +MODEL: Qwen3.6-27B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionManager.java. + +This is a core architecture class. Keep it clean and plain Java. + +Dependencies: +\- RegionFactory regionFactory +\- RegionStorage regionStorage +\- RegionCache regionCache +\- RegionQueryEngine queryEngine +\- RegionLifecycleController lifecycleController +\- SimulationConfig simulationConfig + +Methods: +\- Region getOrCreateRegion(RegionCoordinate coordinate) +\- Optional\ findRegion(RegionCoordinate coordinate) +\- Region getOrCreateRegionAtBlock(String dimensionId, int blockX, int blockZ) +\- Collection\ getActiveRegions() +\- void markDirty(Region region) +\- void unloadRegion(RegionCoordinate coordinate) + +Rules: +\- getOrCreate first checks cache. +\- If missing, try storage. +\- If storage missing, use factory. +\- New or modified regions must be marked dirty. +\- Do not import Minecraft or NeoForge classes. +\- Do not scan chunks. +\- Do not apply visible world effects. + +Output: +\- Full Java file. +\- Explanation of load/create flow. +\- Edge cases. + +Milestone 13 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review RegionStorage, RegionCache, RegionQueryEngine, and RegionManager. + +Files: +\[PASTE FILES\] + +Check: +\- RegionManager load/create flow is correct. +\- No duplicate region instances are likely. +\- No Minecraft imports. +\- Queries are ecosystem-focused. +\- Dirty handling is sensible. +\- Region lifecycle transitions are not abused. + +Output: +\- Pass/fail. +\- Fixes. +\- Tests to add. + +74\. Milestone 14 AI Prompts — Debug Commands + +Task 14.1 prompt — Create command root + +MODEL: Qwen3.6-27B + +PROMPT: +Create src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java for a NeoForge Minecraft mod. + +This file is allowed to use Minecraft/NeoForge command classes because it is in the commands boundary. + +Requirements: +\- Register /lw command tree. +\- Initial subcommands: + \- /lw status + \- /lw region info + \- /lw modules list + \- /lw simulate \ +\- If exact NeoForge command registration API is uncertain, create a clean skeleton with TODO comments at API-specific points. +\- Command implementation should delegate to service classes where possible. +\- Do not place ecosystem simulation rules inside command classes. + +Output: +\- Full Java file. +\- Notes about API assumptions. + +Task 14.2 prompt — Create RegionInfoCommand + +MODEL: Qwen3.6-27B + +PROMPT: +Create src/main/java/com/livingworld/commands/RegionInfoCommand.java. + +This class may use Minecraft command/source classes if necessary, but region logic must stay in RegionManager. + +Output should include: +\- Region coordinate +\- Lifecycle state +\- Dirty state +\- Ecosystem metrics +\- Flags +\- Module IDs present + +Requirements: +\- Use RegionManager to get region data. +\- Use coordinate mapper or boundary conversion instead of putting Minecraft classes into RegionCoordinate. +\- Do not reference settlements, factions, NPCs, roads, or economy. + +Output: +\- Full Java file. +\- API assumptions. + +Task 14.3 prompt — Create SimulateCommand + +MODEL: Qwen3.6-27B + +PROMPT: +Create src/main/java/com/livingworld/commands/SimulateCommand.java. + +Requirements: +\- Operator-only command. +\- Accept tick count. +\- Safe maximum default: 1000 ticks. +\- Calls SimulationManager.runForcedSimulationTicks(ticks). +\- Rejects negative or zero ticks. +\- Prints clear success/failure output. +\- Does not implement simulation rules itself. + +Output: +\- Full Java file. +\- Notes on NeoForge/Minecraft command API assumptions. + +Milestone 14 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review command implementation. + +Files: +\[PASTE LivingWorldCommandRoot.java, RegionInfoCommand.java, SimulateCommand.java\] + +Check: +\- Minecraft/NeoForge imports are limited to command boundary. +\- Commands delegate to services. +\- /lw simulate has safety limits. +\- Output is useful for debugging ecosystem state. +\- No gameplay logic hidden in commands. + +Output: +\- Pass/fail. +\- Fixes. + +75\. Milestone 15 AI Prompts — Profiling + +Task 15.1 prompt — Create SimulationProfiler + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/debug/SimulationProfiler.java. + +Requirements: +\- Plain Java class. +\- Tracks: + \- cycle start time + \- cycle end time + \- per-module timings + \- per-region timings if simple + \- event count + \- save count + \- budget overruns +\- Methods: + \- beginCycle() + \- endCycle() + \- beginModule(String moduleId) + \- endModule(String moduleId) + \- recordEvent() + \- recordSave() + \- recordBudgetOverrun() + \- SimulationProfileSnapshot createSnapshot() +\- Use System.nanoTime internally for timing. +\- Do not use System.out. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 15.2 prompt — Create SimulationProfileSnapshot + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/debug/SimulationProfileSnapshot.java. + +Requirements: +\- Immutable class or record. +\- Fields: + \- long totalCycleNanos + \- Map\ moduleTimings + \- int eventsPublished + \- int regionsUpdated + \- int savesPerformed + \- boolean budgetExceeded +\- Add method toHumanReadableString(). +\- No Minecraft imports. + +Output: +\- Full Java file. + +Milestone 15 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review simulation profiling classes. + +Files: +\[PASTE SimulationProfiler.java and SimulationProfileSnapshot.java\] + +Check: +\- Profiler can measure module cost. +\- Snapshot is immutable or safely copied. +\- No server-spam logging built in. +\- Usable by debug commands later. +\- No Minecraft imports. + +Output: +\- Pass/fail. +\- Fixes. + +76\. Milestone 16 AI Prompts — Tests and Long-Run Validation + +Task 16.1 prompt — Create TestSimulationModule + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/testing/TestSimulationModule.java. + +Requirements: +\- Implements SimulationModule. +\- Module ID: test +\- Metadata display name: Test Simulation Module +\- On initialize, set initialized \= true. +\- On updateRegion, increment an internal update counter. +\- Every 10 updates, return ModuleUpdateResult.changed(). +\- Otherwise return ModuleUpdateResult.noChange(). +\- No Minecraft imports. +\- This is for engine testing only, not gameplay. + +Output: +\- Full Java file. + +Task 16.2 prompt — Create RegionCoordinateTest + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/test/java/com/livingworld/regions/RegionCoordinateTest.java. + +Tests: +\- block 0,0 maps to region 0,0 +\- block 127,127 maps to region 0,0 with region size 8 chunks +\- block 128,0 maps to region 1,0 +\- block \-1,0 maps to region \-1,0 +\- block \-128,0 maps to region \-1,0 +\- block \-129,0 maps to region \-2,0 +\- stableId remains stable +\- RegionCoordinate works as HashMap key + +Use the project’s test framework. If unknown, assume JUnit 5\. + +Output: +\- Full test file. + +Task 16.3 prompt — Create RegionLifecycleControllerTest + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/test/java/com/livingworld/regions/RegionLifecycleControllerTest.java. + +Tests: +\- Allowed transitions succeed. +\- Invalid transitions throw IllegalStateException. +\- FAILED can transition to LOADING. +\- ACTIVE cannot transition directly to SAVING. + +Use JUnit 5 unless project uses something else. + +Output: +\- Full test file. + +Task 16.4 prompt — Create SchedulerBudgetTest + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/test/java/com/livingworld/core/simulation/SchedulerBudgetTest.java. + +Tests: +\- Given 500 jobs and maxRegionsPerCycle 50, one cycle returns 50 jobs. +\- Higher priority jobs are returned first. +\- Jobs not processed remain queued. +\- shouldRunSimulationCycle respects simulationIntervalTicks. + +Use JUnit 5 unless project uses something else. + +Output: +\- Full test file. + +Task 16.5 prompt — Create EventBusTest + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/test/java/com/livingworld/events/EventBusTest.java. + +Tests: +\- Listener receives event. +\- Listener receives event once. +\- Multiple listeners receive the event. +\- Unknown event type does not crash. + +Use BaseLivingWorldEvent for test events. +Use JUnit 5 unless project uses something else. + +Output: +\- Full test file. + +Task 16.6 prompt — Create LongRunSimulationTest + +MODEL: Qwen3.6-27B + +PROMPT: +Create src/test/java/com/livingworld/testing/LongRunSimulationTest.java. + +Purpose: +Prove the foundation can run for a long time without collapsing. + +Test setup: +\- Create 1000 fake regions. +\- Register TestSimulationModule. +\- Run 100,000 simulation ticks or a reduced count if local test runtime is too high. +\- Save dirty regions periodically using a fake/in-memory PersistenceService. + +Pass conditions: +\- No crash. +\- No unhandled exception. +\- No invalid lifecycle transition. +\- Dirty region count does not grow forever. +\- Profiler reports cycle data. + +Requirements: +\- Keep it plain Java where possible. +\- Do not require a full Minecraft client. +\- If full implementation requires missing services, create small in-memory fakes inside the test. + +Output: +\- Full test file. +\- Explanation of any fake services. + +Milestone 16 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the engine test suite. + +Files: +\[PASTE all test files and TestSimulationModule.java\] + +Check: +\- Tests cover foundation risks. +\- Tests do not require Minecraft client unless absolutely necessary. +\- Long-run test is realistic for local hardware. +\- Tests catch coordinate, lifecycle, scheduler, event, and simulation failures. +\- No NPC/settlement/faction/wildlife scope creep. + +Output: +\- Pass/fail. +\- Missing tests. +\- Recommended final fixes before Volume 2\. + +77\. Platform Adapter AI Prompts + +Task P1 prompt — Create PlatformAdapter interface + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/platform/PlatformAdapter.java. + +Requirements: +\- Interface. +\- Methods: + \- String getPlatformName() + \- String getMinecraftVersion() + \- String getLoaderVersion() + \- boolean isDedicatedServer() + \- Path getWorldSaveDirectory() + \- void registerCommands() + \- void registerServerTickHook() + \- void registerPlayerEventHooks() +\- Import java.nio.file.Path only. +\- No NeoForge imports here. + +Output: +\- Full Java interface. + +Task P2 prompt — Create NeoForgePlatformAdapter skeleton + +MODEL: Qwen3.6-27B + +PROMPT: +Create src/main/java/com/livingworld/platform/neoforge/NeoForgePlatformAdapter.java. + +Purpose: +Implement PlatformAdapter using NeoForge as the active loader. + +Rules: +\- This class may import NeoForge and Minecraft classes. +\- Core simulation classes must not import NeoForge or Minecraft classes. +\- If exact NeoForge API names are uncertain for the target version, add clear TODOs at those points instead of inventing unsafe code. +\- Must log platform name, Minecraft version, and loader version during bootstrap. +\- Must provide hooks that can forward server ticks to SimulationManager later. + +Output: +\- Full Java file or safe skeleton. +\- API assumptions. +\- TODOs requiring verification. + +Task P3 prompt — Create MinecraftCoordinateMapper + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/integration/minecraft/MinecraftCoordinateMapper.java. + +Requirements: +\- Boundary class for converting Minecraft coordinates into Living World coordinates. +\- For now, avoid importing Minecraft classes unless necessary. +\- Methods: + \- RegionCoordinate fromBlockPos(String dimensionId, int blockX, int blockZ, int regionSizeChunks) + \- RegionCoordinate fromChunkPos(String dimensionId, int chunkX, int chunkZ, int regionSizeChunks) +\- Delegate to RegionCoordinate.fromBlock and RegionCoordinate.fromChunk. +\- No gameplay logic. + +Output: +\- Full Java file. + +Platform review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the platform adapter layer. + +Files: +\[PASTE PlatformAdapter.java, NeoForgePlatformAdapter.java, MinecraftCoordinateMapper.java\] + +Check: +\- NeoForge imports are isolated to platform/neoforge or command boundary. +\- Core simulation remains plain Java. +\- Platform API is not too wide. +\- Version reporting is possible. +\- Server tick hook can eventually call SimulationManager. + +Output: +\- Pass/fail. +\- Fixes. +\- Any NeoForge API calls that need checking against current docs. + +78\. Ecosystem MVP AI Prompts — To Use After Volume 1 Passes + +Do not use these until the Volume 1 completion gate passes. + +Ecosystem MVP prompt 1 — Create EcosystemRegionData + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/ecosystem/EcosystemRegionData.java. + +Requirements: +\- Plain Java class. +\- Fields: + \- double ecosystemHealth + \- double stress + \- double resilience + \- double recoveryRate +\- Values clamped 0 to 100\. +\- Methods: + \- defaults() + \- normalize() + \- copy() + \- applyStress(double amount) + \- applyRecovery(double amount) +\- No Minecraft imports. +\- No wildlife, NPC, village, faction, or economy logic. + +Output: +\- Full Java file. + +Ecosystem MVP prompt 2 — Create SoilRegionData + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/soil/SoilRegionData.java. + +Requirements: +\- Plain Java class. +\- Fields: + \- double fertility + \- double moisture + \- double contamination + \- double compaction + \- double erosion +\- Values clamped 0 to 100\. +\- Methods: + \- defaults() + \- normalize() + \- copy() + \- degrade(double amount) + \- recover(double amount) +\- No Minecraft imports. + +Output: +\- Full Java file. + +Ecosystem MVP prompt 3 — Create PollutionRegionData + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/pollution/PollutionRegionData.java. + +Requirements: +\- Plain Java class. +\- Fields: + \- double airPollution + \- double groundPollution + \- double waterPollution + \- double decayResistance +\- Values clamped 0 to 100\. +\- Methods: + \- defaults() + \- normalize() + \- copy() + \- addPollution(double air, double ground, double water) + \- decay(double amount) +\- No Minecraft imports. + +Output: +\- Full Java file. + +Ecosystem MVP prompt 4 — Create VegetationRegionData + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/vegetation/VegetationRegionData.java. + +Requirements: +\- Plain Java class. +\- Fields: + \- double grassPressure + \- double flowerPressure + \- double shrubPressure + \- double treePressure + \- double deadVegetation +\- Values clamped 0 to 100\. +\- Methods: + \- defaults() + \- normalize() + \- copy() + \- reduceFromLogging(double amount) + \- recover(double amount) +\- No Minecraft imports. +\- Do not simulate animal populations. + +Output: +\- Full Java file. + +Ecosystem MVP prompt 5 — Create simple ecosystem update formulas + +MODEL: Qwen3.6-27B + +PROMPT: +Design the first simple ecosystem update formulas for the Living World mod. + +Scope: +\- Ecosystem-only. +\- No animals. +\- No villagers. +\- No settlements. +\- No factions. + +Inputs per region: +\- ecosystemHealth +\- soil fertility +\- soil moisture +\- soil contamination +\- water quality +\- air/ground/water pollution +\- vegetation pressure +\- resource depletion +\- recovery pressure + +Output required: +1\. Simple formulas for one simulation tick. +2\. Pseudocode for update order. +3\. Edge cases. +4\. Values that should be configurable. +5\. Safety caps so values stay between 0 and 100\. +6\. Explanation simple enough to turn into Java classes. + +Do not write Minecraft block-changing code yet. + +79\. Daily Local AI Working Method + +Use this method every coding session. + +Step 1: +Use Qwen3.6-27B for planning the next 3 to 5 tiny tasks. + +Prompt: +MODEL: Qwen3.6-27B + +Given the current Living World project state below, choose the next 3 to 5 safest tasks. + +Current state: +\[PASTE WHAT EXISTS\] + +Rules: +\- Ecosystem-only mod. +\- No NPCs, factions, settlements, roads, or wildlife simulation. +\- Core simulation remains plain Java. +\- Minecraft/NeoForge imports stay in platform, integration, or command packages. +\- Each task must be completable by Qwen3.5-9B in one file or one test file. + +Output: +For each task give: +\- task name +\- target model +\- file path +\- exact prompt to give Qwen3.5-9B +\- tests required +\- definition of done + +Step 2: +Give one task at a time to Qwen3.5-9B. + +Step 3: +Run build/tests. + +Step 4: +If build fails, give the exact error to Qwen3.5-9B once. + +Repair prompt: +MODEL: Qwen3.5-9B + +The previous code failed to compile. + +Error: +\[PASTE ERROR\] + +Relevant files: +\[PASTE FILES\] + +Fix only the error. +Do not rewrite unrelated code. +Do not add new systems. +Return the corrected file only. + +Step 5: +If the second 9B attempt fails, escalate to Qwen3.6-27B. + +Escalation prompt: +MODEL: Qwen3.6-27B + +Qwen3.5-9B failed twice to fix this issue. + +Goal: +\[PASTE TASK GOAL\] + +Error: +\[PASTE ERROR\] + +Files: +\[PASTE RELEVANT FILES\] + +Find the root cause and provide the smallest safe fix. +Do not redesign the system unless the design is the root cause. +Do not add unrelated features. + +Output: +\- root cause +\- corrected code +\- why the fix works +\- any follow-up test needed + +Step 6: +After every successful task, ask Qwen3.6-27B to review the milestone when enough files exist. + +80\. Prompt Discipline Rules + +These rules are mandatory when working with local AI. + +Rule 1: +Never ask a 9B model to build a whole subsystem. + +Rule 2: +Never give vague prompts such as "make the ecosystem" or "fix the mod". + +Rule 3: +Always include the file path. + +Rule 4: +Always include the class name. + +Rule 5: +Always state whether Minecraft or NeoForge imports are allowed. + +Rule 6: +Always state what is out of scope. + +Rule 7: +Always ask for tests when the code is plain Java. + +Rule 8: +Always run tests before moving on. + +Rule 9: +After two failed repair attempts by Qwen3.5-9B, escalate to Qwen3.6-27B. + +Rule 10: +If Qwen3.6-27B says the task is too broad, split it into smaller files. + +81\. Local Model Role Summary + +Qwen3.5-9B role: +\- one class +\- one enum +\- one record +\- one interface +\- one test file +\- simple bug fix +\- simple refactor +\- simple documentation + +Qwen3.6-27B role: +\- architecture review +\- multi-file debugging +\- NeoForge uncertainty +\- save design +\- scheduler design +\- module boundary review +\- version upgrade review +\- difficult compile errors +\- planning next batches + +Do not swap these roles unless necessary. diff --git a/Docs/prompts.md b/Docs/prompts.md new file mode 100644 index 0000000..836b853 --- /dev/null +++ b/Docs/prompts.md @@ -0,0 +1,2285 @@ +60\. AI Prompt Pack — Local Model Workflow for Each Build Step + +Date added: 05 June 2026 + +Purpose: +This section gives copy-and-paste prompts for local AI development using Qwen3.5-9B as the main coding worker and Qwen3.6-27B as the planner, reviewer, debugger, and architecture guard. + +Model usage rule: + +\- Use Qwen3.5-9B for small, isolated implementation tasks. +\- Use Qwen3.6-27B for architecture decisions, reviewing several files at once, diagnosing difficult errors, and checking that the code still follows the design. +\- Do not ask Qwen3.5-9B to build broad systems. +\- Do not waste Qwen3.6-27B on tiny one-class tasks unless Qwen3.5-9B fails twice. + +General 9B worker prompt template: + +Use this when asking Qwen3.5-9B to create or edit one file. + +MODEL: Qwen3.5-9B + +PROMPT: +You are implementing one small Java task for a Minecraft NeoForge mod called Living World. + +Important project rules: +\- Core simulation code must be plain Java wherever possible. +\- Do not import Minecraft or NeoForge classes unless this specific task says to. +\- Do not create extra systems beyond the task. +\- Keep the code small and testable. +\- Follow the package path exactly. +\- Use clear names. +\- Add validation where requested. +\- If tests are requested, write the tests too. + +Task: +\[PASTE TASK HERE\] + +Output required: +1\. Brief explanation of what files you will create or edit. +2\. Full code for each file. +3\. Any test code required. +4\. Notes about assumptions. + +Do not skip requirements. +Do not add unrelated features. + +General 27B architect/reviewer prompt template: + +Use this when asking Qwen3.6-27B to review multiple files, fix design drift, or plan the next batch. + +MODEL: Qwen3.6-27B + +PROMPT: +You are the senior architect reviewing the Living World Minecraft NeoForge mod. + +Project direction: +This is an ecosystem-only world evolution mod. It does not simulate villagers, NPC civilisations, settlements, factions, wars, trade, roads, or wildlife AI. + +Architecture rules: +\- Core simulation must remain plain Java where possible. +\- Minecraft and NeoForge code must stay in platform or integration packages. +\- Regions are the main simulation unit, not chunks. +\- Modules must communicate through services, events, or public data contracts. +\- Save data must be versioned. +\- Debug and tests are mandatory. + +Review these files: +\[PASTE FILES OR SUMMARIES\] + +Check for: +1\. Incorrect Minecraft/NeoForge imports in core classes. +2\. Scope creep into NPCs, factions, roads, or settlements. +3\. Broken package boundaries. +4\. Missing validation. +5\. Bad lifecycle handling. +6\. Save/versioning issues. +7\. Test gaps. +8\. Overly broad or fragile code. + +Output required: +1\. Pass/fail summary. +2\. Specific problems by file. +3\. Exact fixes. +4\. Revised code only where necessary. +5\. Recommended next task. + +61\. Milestone 1 AI Prompts — Project Skeleton + +Task 1.1 prompt — Create base mod package + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/LivingWorldMod.java for a Minecraft NeoForge mod called Living World. + +Requirements: +\- Package: com.livingworld +\- Class name: LivingWorldMod +\- Define public static final String MOD\_ID using LivingWorldConstants.MOD\_ID if that class already exists. If it does not exist yet, temporarily define MOD\_ID \= "livingworld" and add a TODO to replace it after constants are created. +\- This class must contain only entrypoint wiring. +\- It must not contain simulation logic. +\- It must log a startup message. +\- It must delegate startup work to com.livingworld.bootstrap.LivingWorldBootstrap if available. +\- If NeoForge annotations/imports are needed, keep them only in this entrypoint file. + +Output: +\- Full Java file. +\- Any assumptions about the NeoForge version. +\- Do not create unrelated classes. + +Task 1.2 prompt — Create bootstrap class + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/bootstrap/LivingWorldBootstrap.java. + +Requirements: +\- Package: com.livingworld.bootstrap +\- Class name: LivingWorldBootstrap +\- Methods: + \- void initialize() + \- void onCommonSetup() + \- void onServerStarting() + \- void onServerStarted() + \- void onServerStopping() +\- Each method should log its lifecycle stage. +\- Use LivingWorldLogger if available. If not available yet, use a minimal temporary logger comment and no System.out. +\- Do not add gameplay logic. +\- Do not create region, ecosystem, or persistence systems yet. + +Output: +\- Full Java file. +\- Short explanation of where it should be called from. + +Task 1.3 prompt — Create package placeholders + +MODEL: Qwen3.5-9B + +PROMPT: +Create the package placeholder structure for the Living World mod. + +Packages: +\- com.livingworld.api +\- com.livingworld.bootstrap +\- com.livingworld.config +\- com.livingworld.core +\- com.livingworld.core.lifecycle +\- com.livingworld.core.registry +\- com.livingworld.core.services +\- com.livingworld.core.simulation +\- com.livingworld.data +\- com.livingworld.data.saved +\- com.livingworld.data.migration +\- com.livingworld.data.serialization +\- com.livingworld.events +\- com.livingworld.regions +\- com.livingworld.regions.cache +\- com.livingworld.regions.query +\- com.livingworld.commands +\- com.livingworld.debug +\- com.livingworld.networking +\- com.livingworld.platform +\- com.livingworld.platform.neoforge +\- com.livingworld.integration.minecraft +\- com.livingworld.modules +\- com.livingworld.modules.ecosystem +\- com.livingworld.modules.soil +\- com.livingworld.modules.water +\- com.livingworld.modules.pollution +\- com.livingworld.modules.vegetation +\- com.livingworld.modules.resources +\- com.livingworld.modules.recovery +\- com.livingworld.modules.worldeffects +\- com.livingworld.testing + +If Java requires at least one file per package in this project setup, create a package-info.java file with a one-line comment for each package. + +Do not add implementation logic. + +Task 1.4 prompt — Create constants + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/LivingWorldConstants.java. + +Requirements: +\- Package: com.livingworld.core +\- Final utility class. +\- Private constructor. +\- Constants: + \- public static final String MOD\_ID \= "livingworld"; + \- public static final String MOD\_NAME \= "Living World"; + \- public static final int CURRENT\_CORE\_SCHEMA\_VERSION \= 1; + \- public static final int DEFAULT\_REGION\_SIZE\_CHUNKS \= 8; + \- public static final int DEFAULT\_SIMULATION\_INTERVAL\_TICKS \= 100; +\- Do not import Minecraft or NeoForge classes. +\- Add short comments explaining each constant. + +Output: +\- Full Java file. + +Milestone 1 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the completed Milestone 1 project skeleton for the Living World ecosystem mod. + +Files to review: +\[LivingWorldMod.java, LivingWorldBootstrap.java, LivingWorldConstants.java, and package list] + +Check: +\- Entrypoint contains no simulation logic. +\- Bootstrap owns startup flow. +\- Constants are centralised. +\- No ecosystem/NPC/settlement/faction systems were added early. +\- Minecraft/NeoForge imports are restricted to entrypoint or platform boundary. + +Output: +\- Pass/fail. +\- Required fixes. +\- Next safe milestone. + +62\. Milestone 2 AI Prompts — Logging and Diagnostics + +Task 2.1 prompt — Create logger wrapper + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/debug/LivingWorldLogger.java. + +Requirements: +\- Package: com.livingworld.debug +\- Class name: LivingWorldLogger +\- Provide static methods: + \- info(String message) + \- warn(String message) + \- error(String message) + \- error(String message, Throwable throwable) + \- debug(String message) + \- info(DiagnosticCategory category, String message) + \- warn(DiagnosticCategory category, String message) + \- error(DiagnosticCategory category, String message) + \- debug(DiagnosticCategory category, String message) +\- Use a standard logger appropriate for the mod environment if available. +\- Do not use System.out. +\- If debug config does not exist yet, allow debug messages for now and add a TODO to wire config later. +\- Do not import ecosystem module classes. + +Output: +\- Full Java file. + +Task 2.2 prompt — Create diagnostic category enum + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/debug/DiagnosticCategory.java. + +Requirements: +\- Package: com.livingworld.debug +\- Enum values: + \- BOOTSTRAP + \- CONFIG + \- REGIONS + \- SIMULATION + \- PERSISTENCE + \- EVENTS + \- MODULES + \- COMMANDS + \- NETWORKING + \- PLATFORM + \- ECOSYSTEM + \- TESTING +\- Add a method displayName() that returns a readable name. +\- Do not import Minecraft or NeoForge classes. + +Output: +\- Full Java enum. + +Milestone 2 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the logging and diagnostics foundation. + +Files: +\[PASTE LivingWorldLogger.java and DiagnosticCategory.java\] + +Check: +\- No System.out usage. +\- Logger methods are consistent. +\- Categories match the ecosystem-only architecture. +\- No unnecessary Minecraft imports. +\- Debug behaviour can later connect to config. + +Output: +\- Problems found. +\- Recommended fixes. +\- Whether Milestone 3 can begin. + +63\. Milestone 3 AI Prompts — Configuration Foundation + +Task 3.1 prompt — Create SimulationConfig + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/config/SimulationConfig.java. + +Requirements: +\- Plain Java class. +\- Fields: + \- int regionSizeChunks \= 8 + \- int simulationIntervalTicks \= 100 + \- int maxRegionsPerCycle \= 50 + \- int maxMillisecondsPerCycle \= 25 + \- int emergencyStopMilliseconds \= 40 + \- boolean enableDebugCommands \= true + \- boolean enableProfiler \= true +\- Add getters. +\- Add setters only if needed for tests. +\- Add validate() method. +\- Validation rules: + \- regionSizeChunks \>= 1 + \- simulationIntervalTicks \>= 1 + \- maxRegionsPerCycle \>= 1 + \- maxMillisecondsPerCycle \>= 1 + \- emergencyStopMilliseconds \>= maxMillisecondsPerCycle +\- Throw IllegalArgumentException with clear messages on invalid values. +\- Do not import Minecraft or NeoForge classes. + +Output: +\- Full Java file. +\- Basic test suggestions. + +Task 3.2 prompt — Create ConfigService interface + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/services/ConfigService.java. + +Requirements: +\- Interface. +\- Methods: + \- SimulationConfig getSimulationConfig() + \- void reload() + \- void validate() +\- Import SimulationConfig from com.livingworld.config. +\- Do not reference NeoForge config APIs here. + +Output: +\- Full Java interface. + +Task 3.3 prompt — Create DefaultConfigService + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/config/DefaultConfigService.java. + +Requirements: +\- Implements ConfigService. +\- Holds a SimulationConfig instance. +\- Constructor creates default SimulationConfig and validates it. +\- getSimulationConfig returns the config. +\- reload can be a safe placeholder for now, but it must revalidate. +\- validate delegates to SimulationConfig.validate(). +\- Log validation success using LivingWorldLogger if available. +\- Do not use NeoForge config classes yet. + +Output: +\- Full Java file. + +Milestone 3 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the configuration foundation for the Living World ecosystem mod. + +Files: +\[PASTE SimulationConfig.java, ConfigService.java, DefaultConfigService.java\] + +Check: +\- Config is plain Java. +\- Validation is strict and clear. +\- No direct NeoForge dependency yet. +\- Defaults match the design document. +\- Future Forge/NeoForge config wiring can be added without rewriting core code. + +Output: +\- Pass/fail. +\- Required fixes. +\- Any missing config values for ecosystem-only MVP. + +64\. Milestone 4 AI Prompts — Core Service Registry + +Task 4.1 prompt — Create ServiceKey + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/services/ServiceKey.java. + +Requirements: +\- Generic immutable value object. +\- Prefer Java record if project Java version supports it. +\- Fields: + \- String id + \- Class\ serviceType +\- Validate id is not null or blank. +\- Validate serviceType is not null. +\- Add stable toString(). +\- Do not import Minecraft or NeoForge classes. + +Output: +\- Full Java file. + +Task 4.2 prompt — Create ServiceRegistry + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/services/ServiceRegistry.java. + +Requirements: +\- Plain Java class. +\- Internally store services by ServiceKey. +\- Methods: + \- \ void register(ServiceKey\ key, T service) + \- \ T get(ServiceKey\ key) + \- \ Optional\ find(ServiceKey\ key) + \- boolean isRegistered(ServiceKey\ key) + \- void lock() + \- boolean isLocked() +\- Reject duplicate registrations. +\- Reject null keys and null services. +\- If locked, reject new registrations. +\- Missing get() should throw clear IllegalStateException. +\- No Minecraft imports. + +Output: +\- Full Java file. +\- Suggested unit tests. + +Task 4.3 prompt — Create CoreServices + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/services/CoreServices.java. + +Requirements: +\- Final utility class with private constructor. +\- Define ServiceKey constants for: + \- CONFIG + \- REGIONS + \- SIMULATION + \- PERSISTENCE + \- EVENTS + \- MODULES + \- TIME + \- DEBUG + \- HISTORY +\- If some service interfaces do not exist yet, add TODO comments and create keys only for existing interfaces, or use Object temporarily with clear TODOs. +\- Do not create fake gameplay services. +\- No Minecraft imports. + +Output: +\- Full Java file. +\- Note any TODOs caused by missing interfaces. + +Milestone 4 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the core service registry. + +Files: +\[ServiceKey.java, ServiceRegistry.java, CoreServices.java\] + +Check: +\- Type safety is acceptable. +\- Registry cannot be mutated after lock. +\- Errors are clear. +\- No hidden global state problems. +\- No Minecraft imports. +\- Design is suitable for module dependency injection later. + +Output: +\- Pass/fail. +\- Fixes. +\- Risk notes. + +65\. Milestone 5 AI Prompts — Region Identity + +Task 5.1 prompt — Create RegionCoordinate + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionCoordinate.java. + +Requirements: +\- Java record if available. +\- Fields: + \- String dimensionId + \- int x + \- int z +\- Validate dimensionId is not null or blank. +\- Static methods: + \- fromChunk(String dimensionId, int chunkX, int chunkZ, int regionSizeChunks) + \- fromBlock(String dimensionId, int blockX, int blockZ, int regionSizeChunks) +\- Instance methods: + \- int minChunkX(int regionSizeChunks) + \- int minChunkZ(int regionSizeChunks) + \- int maxChunkX(int regionSizeChunks) + \- int maxChunkZ(int regionSizeChunks) + \- int centerBlockX(int regionSizeChunks) + \- int centerBlockZ(int regionSizeChunks) + \- String stableId() +\- Use Math.floorDiv so negative coordinates work correctly. +\- Validate regionSizeChunks \>= 1\. +\- Do not import Minecraft classes. + +Test requirements: +Create RegionCoordinateTest if test framework exists. +Test: +\- block 0,0 maps to region 0,0 +\- block 127,127 maps to region 0,0 with region size 8 chunks +\- block 128,0 maps to region 1,0 +\- block \-1,0 maps to region \-1,0 +\- block \-128,0 maps to region \-1,0 +\- block \-129,0 maps to region \-2,0 +\- stableId is stable +\- works as HashMap key + +Output: +\- Full Java file. +\- Full test file if possible. + +Task 5.2 prompt — Create RegionLifecycleState + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionLifecycleState.java. + +Requirements: +\- Enum values: + \- UNLOADED + \- LOADING + \- ACTIVE + \- DIRTY + \- SAVING + \- FAILED + \- UNLOADING +\- Add comments explaining each state. +\- Do not add methods unless useful. +\- No Minecraft imports. + +Output: +\- Full Java enum. + +Task 5.3 prompt — Create RegionFlags + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionFlags.java. + +Requirements: +\- Plain Java class. +\- Fields: + \- boolean hasPlayerActivity + \- boolean hasHighPollution + \- boolean hasLowSoilQuality + \- boolean hasActiveEcosystemEvent + \- boolean forceLoadedBySimulation + \- boolean corrupted +\- Do not include settlement, road, faction, or NPC flags. +\- Add getters and setters. +\- Add copy(). +\- Add clearTransientFlags(). This should clear hasPlayerActivity and hasActiveEcosystemEvent, but not corrupted. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Milestone 5 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review region identity classes. + +Files: +\[RegionCoordinate.java, RegionLifecycleState.java, RegionFlags.java, and tests\] + +Check: +\- Negative coordinate math is correct. +\- No Minecraft classes leaked into region core. +\- Flags match ecosystem-only scope. +\- RegionCoordinate is safe as a HashMap key. +\- Tests cover edge cases. + +Output: +\- Pass/fail. +\- Exact fixes if needed. + +66\. Milestone 6 AI Prompts — Region Core Data + +Task 6.1 prompt — Create RegionMetrics + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionMetrics.java. + +Requirements: +\- Plain Java class. +\- Ecosystem-only fields: + \- double ecosystemHealth + \- double pollutionScore + \- double soilQuality + \- double waterQuality + \- double vegetationPressure + \- double resourceDepletion + \- double recoveryPressure +\- Values should normally be clamped between 0 and 100\. +\- Add normalize(). +\- Add copy(). +\- Add static defaults(). +\- Defaults: + \- ecosystemHealth \= 60 + \- pollutionScore \= 0 + \- soilQuality \= 60 + \- waterQuality \= 60 + \- vegetationPressure \= 50 + \- resourceDepletion \= 0 + \- recoveryPressure \= 50 +\- Add applyDelta methods if simple and clean. +\- No Minecraft imports. + +Output: +\- Full Java file. +\- Suggested tests. + +Task 6.2 prompt — Create RegionModuleData + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionModuleData.java. + +Requirements: +\- Stores module-specific data by module ID. +\- Field: + \- Map\ moduleData +\- Methods: + \- void put(String moduleId, Object data) + \- \ Optional\ get(String moduleId, Class\ type) + \- boolean contains(String moduleId) + \- Set\ moduleIds() + \- RegionModuleData copyShallow() +\- Validate moduleId is not null or blank. +\- Reject null data. +\- If get type does not match, return Optional.empty instead of throwing. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 6.3 prompt — Create Region + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/Region.java. + +Requirements: +\- Plain Java class. +\- Fields: + \- UUID id + \- RegionCoordinate coordinate + \- RegionLifecycleState lifecycleState + \- long createdAtSimulationTick + \- long lastUpdatedSimulationTick + \- boolean dirty + \- RegionFlags flags + \- RegionMetrics metrics + \- RegionModuleData moduleData +\- Constructor validates required fields. +\- Methods: + \- markDirty() + \- clearDirty() + \- isDirty() + \- updateLastSimulatedTick(long tick) + \- setLifecycleState(RegionLifecycleState state) + \- validate() +\- validate() checks id, coordinate, lifecycleState, flags, metrics, and moduleData are not null. +\- Do not include settlement, faction, road, or NPC fields. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Milestone 6 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the region core data classes. + +Files: +\[PASTE RegionMetrics.java, RegionModuleData.java, Region.java\] + +Check: +\- Metrics match ecosystem-only scope. +\- Region is not bloated with future unrelated systems. +\- Validation is strong. +\- Dirty state behaviour is sensible. +\- No Minecraft imports. +\- No direct persistence logic inside Region. + +Output: +\- Pass/fail. +\- Exact corrections. + +67\. Milestone 7 AI Prompts — Region Creation and Lifecycle + +Task 7.1 prompt — Create RegionLifecycleController + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionLifecycleController.java. + +Requirements: +\- Plain Java class. +\- Methods: + \- boolean canTransition(RegionLifecycleState from, RegionLifecycleState to) + \- void transition(Region region, RegionLifecycleState target) +\- Allowed transitions: + \- UNLOADED \-\> LOADING + \- LOADING \-\> ACTIVE + \- LOADING \-\> FAILED + \- ACTIVE \-\> DIRTY + \- DIRTY \-\> SAVING + \- SAVING \-\> ACTIVE + \- SAVING \-\> FAILED + \- ACTIVE \-\> UNLOADING + \- UNLOADING \-\> UNLOADED + \- FAILED \-\> LOADING +\- Invalid transitions throw IllegalStateException with clear message. +\- transition() must validate region is not null. +\- No Minecraft imports. + +Output: +\- Full Java file. +\- Suggested tests. + +Task 7.2 prompt — Create RegionFactory + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionFactory.java. + +Requirements: +\- Plain Java class. +\- Method: + \- Region createNewRegion(RegionCoordinate coordinate, long simulationTick) +\- New region rules: + \- random UUID + \- coordinate from argument + \- lifecycleState ACTIVE + \- createdAtSimulationTick \= simulationTick + \- lastUpdatedSimulationTick \= simulationTick + \- dirty \= true + \- default RegionFlags + \- RegionMetrics.defaults() + \- empty RegionModuleData + \- validate before return +\- No Minecraft imports. +\- Do not initialise ecology modules here yet; that comes after ModuleRegistry is connected. + +Output: +\- Full Java file. + +Milestone 7 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review lifecycle and region creation. + +Files: +\[PASTE RegionLifecycleController.java and RegionFactory.java\] + +Check: +\- Invalid transitions are rejected. +\- New regions are immediately dirty. +\- Factory does not import Minecraft classes. +\- Factory does not start implementing ecosystem gameplay prematurely. +\- Tests cover valid and invalid transitions. + +Output: +\- Pass/fail. +\- Fixes. + +68\. Milestone 8 AI Prompts — Module Contracts + +Task 8.1 prompt — Create SimulationModule interface + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/SimulationModule.java. + +Requirements: +\- Interface. +\- Methods: + \- String getModuleId() + \- ModuleMetadata getMetadata() + \- void initialize(ModuleContext context) + \- void onServerStarted(ServerContext context) + \- void createDefaultRegionData(Region region) + \- ModuleUpdateResult updateRegion(RegionUpdateContext context) + \- void onLivingWorldEvent(LivingWorldEvent event) + \- void saveModuleData(PersistenceWriter writer) + \- void loadModuleData(PersistenceReader reader) + \- void shutdown() +\- Create minimal placeholder interfaces/classes if missing: +n \- ServerContext + \- RegionUpdateContext + \- LivingWorldEvent + \- PersistenceWriter + \- PersistenceReader +\- Do not implement any real ecosystem module yet. +\- No Minecraft imports in this interface. + +Output: +\- Full Java interface. +\- Any minimal placeholder files required. + +Task 8.2 prompt — Create ModuleMetadata + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/ModuleMetadata.java. + +Requirements: +\- Immutable class or record. +\- Fields: + \- String moduleId + \- String displayName + \- String version + \- String description + \- String requiredCoreVersion + \- List\ dependencies + \- List\ optionalDependencies + \- boolean defaultEnabled + \- boolean serverOnly + \- boolean experimental +\- Validate moduleId and displayName are not blank. +\- Replace null dependency lists with empty lists. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 8.3 prompt — Create ModuleContext + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/ModuleContext.java. + +Requirements: +\- Plain Java class. +\- Field: + \- ServiceRegistry services +\- Constructor validates services is not null. +\- Method: + \- \ T getService(ServiceKey\ key) +\- Do not expose the whole registry for mutation if avoidable. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 8.4 prompt — Create ModuleUpdateResult + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/ModuleUpdateResult.java. + +Requirements: +\- Immutable class or record. +\- Fields: + \- boolean changedRegion + \- List\ generatedEvents + \- List\ historyRecords + \- int estimatedCost + \- List\ warnings +\- Static constructors: + \- noChange() + \- changed() +\- Null lists become empty lists. +\- estimatedCost must not be negative. +\- No Minecraft imports. + +If HistoryRecord does not exist, create a minimal placeholder in com.livingworld.modules or com.livingworld.events with TODO to move later. + +Output: +\- Full Java file. +\- Any placeholder file required. + +Task 8.5 prompt — Create ModuleRegistry + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/ModuleRegistry.java. + +Requirements: +\- Plain Java class. +\- Responsibilities: + \- register modules + \- reject duplicate module IDs + \- return all modules + \- return enabled modules + \- find by module ID + \- initialize all + \- shutdown all +\- Methods: + \- void register(SimulationModule module) + \- List\ getAllModules() + \- List\ getEnabledModules() + \- Optional\ find(String moduleId) + \- void initializeAll(ModuleContext context) + \- void shutdownAll() +\- For v1, enabled modules are modules where metadata.defaultEnabled is true. +\- Do not implement dependency sorting yet; add TODO. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Milestone 8 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review module contract architecture. + +Files: +\[PASTE SimulationModule.java, ModuleMetadata.java, ModuleContext.java, ModuleUpdateResult.java, ModuleRegistry.java\] + +Check: +\- Modules are generic and not tied to settlements/factions/NPCs. +\- Core module contracts do not import Minecraft classes. +\- ModuleRegistry is simple enough for v1. +\- Missing placeholders are sensible and marked as TODO. +\- Architecture supports ecosystem modules later. + +Output: +\- Pass/fail. +\- Required fixes. +\- Next implementation task. + +69\. Milestone 9 AI Prompts — Event Foundation + +Task 9.1 prompt — Create LivingWorldEvent + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/events/LivingWorldEvent.java. + +Requirements: +\- Interface. +\- Methods: + \- String eventType() + \- long simulationTick() + \- String sourceModuleId() +\- No Minecraft imports. +\- Events must be usable by ecosystem modules later. + +Output: +\- Full Java interface. + +Task 9.2 prompt — Create BaseLivingWorldEvent + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/events/BaseLivingWorldEvent.java. + +Requirements: +\- Immutable class or record implementing LivingWorldEvent. +\- Fields: + \- String eventType + \- long simulationTick + \- String sourceModuleId +\- Validate eventType is not blank. +\- Validate simulationTick is not negative. +\- sourceModuleId may be "core" for core events but must not be blank. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 9.3 prompt — Create LivingWorldEventListener + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/events/LivingWorldEventListener.java. + +Requirements: +\- Functional interface. +\- Method: + \- void onEvent(LivingWorldEvent event) +\- No Minecraft imports. + +Output: +\- Full Java interface. + +Task 9.4 prompt — Create LivingWorldEventBus + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/events/LivingWorldEventBus.java. + +Requirements: +\- Plain Java class. +\- Methods: + \- void register(String eventType, LivingWorldEventListener listener) + \- void publish(LivingWorldEvent event) + \- int getPublishedEventCount() + \- int getListenerCount(String eventType) +\- Validate eventType is not blank. +\- Validate listener is not null. +\- Event publishing should send the event to listeners registered for that event type. +\- Unknown event type should not crash. +\- Prevent obvious recursive event storms with a simple dispatching guard or TODO if not implemented. +\- No Minecraft imports. + +Output: +\- Full Java file. +\- Suggested tests. + +Milestone 9 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the Living World event foundation. + +Files: +\[PASTE LivingWorldEvent.java, BaseLivingWorldEvent.java, LivingWorldEventListener.java, LivingWorldEventBus.java\] + +Check: +\- Events are plain Java. +\- EventBus is not overcomplicated. +\- Unknown events are safe. +\- Recursion risk is noted or controlled. +\- Event types can support pollution/soil/vegetation events later. + +Output: +\- Pass/fail. +\- Fixes. + +70\. Milestone 10 AI Prompts — Time Service + +Task 10.1 prompt — Create LivingWorldCalendar + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/simulation/LivingWorldCalendar.java. + +Requirements: +\- Plain Java class. +\- Fields: + \- long simulationTick + \- int day + \- int month + \- int year +\- Default start: day 1, month 1, year 1, simulationTick 0\. +\- Methods: + \- advanceTicks(long ticks) + \- copy() + \- toDisplayString() +\- Keep the calendar simple for now. +\- Use 30 days per month and 12 months per year unless config is added later. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 10.2 prompt — Create TimeService interface + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/services/TimeService.java. + +Requirements: +\- Interface. +\- Methods: + \- long getSimulationTick() + \- LivingWorldCalendar getCalendar() + \- void advanceSimulationTick() + \- void advanceSimulationTicks(long ticks) +\- No Minecraft imports. + +Output: +\- Full Java interface. + +Task 10.3 prompt — Create DefaultTimeService + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/simulation/DefaultTimeService.java. + +Requirements: +\- Implements TimeService. +\- Owns a LivingWorldCalendar. +\- advanceSimulationTick advances by 1\. +\- advanceSimulationTicks validates ticks \>= 0 and advances calendar. +\- getCalendar returns a copy, not the internal mutable instance. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Milestone 10 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the Living World time system. + +Files: +\[PASTE LivingWorldCalendar.java, TimeService.java, DefaultTimeService.java\] + +Check: +\- Time is independent of Minecraft classes. +\- Calendar cannot be mutated externally by accident. +\- Behaviour is deterministic. +\- Good enough for ecosystem simulation v1. + +Output: +\- Pass/fail. +\- Fixes. + +71\. Milestone 11 AI Prompts — Scheduler and Simulation Manager + +Task 11.1 prompt — Create UpdateReason + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/simulation/UpdateReason.java. + +Requirements: +\- Enum values: + \- NORMAL\_ROLLING\_UPDATE + \- PLAYER\_NEARBY + \- HIGH\_POLLUTION + \- LOW\_SOIL\_QUALITY + \- ACTIVE\_ECOSYSTEM\_EVENT + \- FORCED\_DEBUG\_COMMAND + \- SAVE\_MIGRATION\_REQUIRED +\- Do not include settlement, road, faction, or NPC update reasons. +\- No Minecraft imports. + +Output: +\- Full Java enum. + +Task 11.2 prompt — Create RegionUpdateJob + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/simulation/RegionUpdateJob.java. + +Requirements: +\- Immutable class or record. +\- Fields: + \- RegionCoordinate coordinate + \- int priority + \- long queuedAtSimulationTick + \- Set\ requestedModules + \- UpdateReason reason +\- Validate coordinate and reason are not null. +\- priority must not be negative. +\- queuedAtSimulationTick must not be negative. +\- Null requestedModules becomes empty set. +\- Add comparator for descending priority if clean. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 11.3 prompt — Create RegionPriorityCalculator + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/simulation/RegionPriorityCalculator.java. + +Requirements: +\- Plain Java class. +\- Method: + \- int calculatePriority(Region region) +\- Initial ecosystem-only scoring: + \- base \= 5 + \- hasPlayerActivity bonus \= 100 + \- hasActiveEcosystemEvent bonus \= 75 + \- hasHighPollution bonus \= 50 + \- hasLowSoilQuality bonus \= 50 + \- pollution bonus \= pollutionScore / 2 + \- resource depletion bonus \= resourceDepletion / 3 + \- recovery pressure bonus \= recoveryPressure / 4 +\- Validate region is not null. +\- Result should be deterministic. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 11.4 prompt — Create SimulationScheduler + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/simulation/SimulationScheduler.java. + +Requirements: +\- Plain Java class. +\- Constructor accepts SimulationConfig. +\- Fields: + \- long minecraftTickCounter + \- long simulationTickCounter + \- PriorityQueue\ updateQueue +\- Methods: + \- void onMinecraftTick() + \- boolean shouldRunSimulationCycle() + \- void queueRegion(RegionUpdateJob job) + \- List\ pollJobsForCycle() + \- long getMinecraftTickCounter() + \- long getSimulationTickCounter() +\- shouldRunSimulationCycle true when minecraftTickCounter reaches the configured interval. +\- pollJobsForCycle returns at most maxRegionsPerCycle jobs. +\- Higher priority jobs first. +\- No Minecraft imports. + +Output: +\- Full Java file. +\- Suggested scheduler budget tests. + +Task 11.5 prompt — Create SimulationManager skeleton + +MODEL: Qwen3.6-27B + +PROMPT: +Create the first version of src/main/java/com/livingworld/core/simulation/SimulationManager.java. + +This is an architecture-sensitive class. Keep it boring and coordinator-only. + +Requirements: +\- Constructor dependencies: + \- SimulationScheduler scheduler + \- RegionManager regionManager + \- ModuleRegistry moduleRegistry + \- LivingWorldEventBus eventBus + \- TimeService timeService + \- PersistenceService persistenceService + \- SimulationProfiler profiler, if available. If not available, make it optional or TODO. +\- Methods: + \- void onMinecraftServerTick() + \- void runSimulationCycle() + \- void runForcedSimulationTicks(int ticks) +\- Responsibilities: + \- Advance scheduler on Minecraft tick. + \- If scheduler says cycle should run, run one cycle. + \- Poll region update jobs. + \- Resolve regions from RegionManager. + \- Run enabled modules in order. + \- Publish generated events. + \- Mark changed regions dirty. + \- Advance TimeService. +\- Do not implement ecosystem rules here. +\- Do not import Minecraft or NeoForge classes. +\- Do not scan chunks. + +Output: +\- Full Java file. +\- Any missing interface TODOs. +\- Explanation of control flow. + +Milestone 11 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review scheduler and SimulationManager. + +Files: +\[PASTE UpdateReason.java, RegionUpdateJob.java, RegionPriorityCalculator.java, SimulationScheduler.java, SimulationManager.java\] + +Check: +\- No Minecraft imports in scheduler/manager. +\- Budgeting is respected. +\- SimulationManager contains no gameplay rules. +\- Ecosystem-only priority values are correct. +\- Forced simulation cannot accidentally lock the server forever. +\- Good enough before persistence and region manager are finished. + +Output: +\- Pass/fail. +\- Fixes. +\- Next safest step. + +72\. Milestone 12 AI Prompts — Persistence Foundation + +Task 12.1 prompt — Create PersistenceWriter + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/data/serialization/PersistenceWriter.java. + +Requirements: +\- Interface. +\- Methods: + \- void writeString(String key, String value) + \- void writeInt(String key, int value) + \- void writeLong(String key, long value) + \- void writeDouble(String key, double value) + \- void writeBoolean(String key, boolean value) +\- No Minecraft imports. + +Output: +\- Full Java interface. + +Task 12.2 prompt — Create PersistenceReader + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/data/serialization/PersistenceReader.java. + +Requirements: +\- Interface. +\- Methods: + \- String readString(String key, String defaultValue) + \- int readInt(String key, int defaultValue) + \- long readLong(String key, long defaultValue) + \- double readDouble(String key, double defaultValue) + \- boolean readBoolean(String key, boolean defaultValue) +\- No Minecraft imports. + +Output: +\- Full Java interface. + +Task 12.3 prompt — Create SaveMetadata + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/data/saved/SaveMetadata.java. + +Requirements: +\- Immutable class or record. +\- Fields: + \- int schemaVersion + \- String modVersion + \- long createdAt + \- long updatedAt +\- Validate schemaVersion \> 0\. +\- Validate modVersion is not blank. +\- Validate updatedAt \>= createdAt. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 12.4 prompt — Create PersistenceService interface + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/core/services/PersistenceService.java. + +Requirements: +\- Interface. +\- Methods: + \- void markRegionDirty(Region region) + \- void saveDirtyRegions() + \- Optional\ loadRegion(RegionCoordinate coordinate) + \- void saveRegion(Region region) + \- void flushAll() +\- No Minecraft imports. +\- Do not implement file storage yet. + +Output: +\- Full Java interface. + +Milestone 12 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review persistence foundation. + +Files: +\[PASTE PersistenceWriter.java, PersistenceReader.java, SaveMetadata.java, PersistenceService.java\] + +Check: +\- Persistence interfaces are plain Java. +\- Region persistence is abstracted. +\- Save metadata is versioned. +\- No module writes files directly. +\- Good enough before JSON/NBT implementation. + +Output: +\- Pass/fail. +\- Fixes. + +73\. Milestone 13 AI Prompts — Region Storage and Manager + +Task 13.1 prompt — Create RegionStorage + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionStorage.java. + +Requirements: +\- Plain Java class. +\- Constructor dependency: + \- PersistenceService persistenceService +\- Methods: + \- Optional\ load(RegionCoordinate coordinate) + \- void save(Region region) +\- load delegates to persistenceService.loadRegion. +\- save delegates to persistenceService.saveRegion. +\- Validate dependencies and arguments. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 13.2 prompt — Create RegionCache + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/cache/RegionCache.java. + +Requirements: +\- Plain Java class. +\- Field: + \- Map\ activeRegions +\- Methods: + \- Optional\ get(RegionCoordinate coordinate) + \- void put(Region region) + \- void remove(RegionCoordinate coordinate) + \- Collection\ allActive() + \- int size() +\- Validate inputs. +\- allActive should return an unmodifiable copy or safe view. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 13.3 prompt — Create RegionQueryEngine + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/regions/query/RegionQueryEngine.java. + +Requirements: +\- Plain Java class. +\- Constructor dependency: + \- RegionCache cache +\- Methods: + \- Optional\ getRegion(RegionCoordinate coordinate) + \- List\ getRegionsInRadius(RegionCoordinate center, int radius) + \- List\ getRegionsWithActiveEcosystemEvent() + \- List\ getRegionsAbovePollution(double threshold) + \- List\ getRegionsBelowSoilQuality(double threshold) +\- Do not touch Minecraft chunks. +\- Radius means region-coordinate radius. +\- Validate radius \>= 0\. +\- No settlement, road, faction, or NPC queries. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 13.4 prompt — Create RegionManager + +MODEL: Qwen3.6-27B + +PROMPT: +Create src/main/java/com/livingworld/regions/RegionManager.java. + +This is a core architecture class. Keep it clean and plain Java. + +Dependencies: +\- RegionFactory regionFactory +\- RegionStorage regionStorage +\- RegionCache regionCache +\- RegionQueryEngine queryEngine +\- RegionLifecycleController lifecycleController +\- SimulationConfig simulationConfig + +Methods: +\- Region getOrCreateRegion(RegionCoordinate coordinate) +\- Optional\ findRegion(RegionCoordinate coordinate) +\- Region getOrCreateRegionAtBlock(String dimensionId, int blockX, int blockZ) +\- Collection\ getActiveRegions() +\- void markDirty(Region region) +\- void unloadRegion(RegionCoordinate coordinate) + +Rules: +\- getOrCreate first checks cache. +\- If missing, try storage. +\- If storage missing, use factory. +\- New or modified regions must be marked dirty. +\- Do not import Minecraft or NeoForge classes. +\- Do not scan chunks. +\- Do not apply visible world effects. + +Output: +\- Full Java file. +\- Explanation of load/create flow. +\- Edge cases. + +Milestone 13 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review RegionStorage, RegionCache, RegionQueryEngine, and RegionManager. + +Files: +\[PASTE FILES\] + +Check: +\- RegionManager load/create flow is correct. +\- No duplicate region instances are likely. +\- No Minecraft imports. +\- Queries are ecosystem-focused. +\- Dirty handling is sensible. +\- Region lifecycle transitions are not abused. + +Output: +\- Pass/fail. +\- Fixes. +\- Tests to add. + +74\. Milestone 14 AI Prompts — Debug Commands + +Task 14.1 prompt — Create command root + +MODEL: Qwen3.6-27B + +PROMPT: +Create src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java for a NeoForge Minecraft mod. + +This file is allowed to use Minecraft/NeoForge command classes because it is in the commands boundary. + +Requirements: +\- Register /lw command tree. +\- Initial subcommands: + \- /lw status + \- /lw region info + \- /lw modules list + \- /lw simulate \ +\- If exact NeoForge command registration API is uncertain, create a clean skeleton with TODO comments at API-specific points. +\- Command implementation should delegate to service classes where possible. +\- Do not place ecosystem simulation rules inside command classes. + +Output: +\- Full Java file. +\- Notes about API assumptions. + +Task 14.2 prompt — Create RegionInfoCommand + +MODEL: Qwen3.6-27B + +PROMPT: +Create src/main/java/com/livingworld/commands/RegionInfoCommand.java. + +This class may use Minecraft command/source classes if necessary, but region logic must stay in RegionManager. + +Output should include: +\- Region coordinate +\- Lifecycle state +\- Dirty state +\- Ecosystem metrics +\- Flags +\- Module IDs present + +Requirements: +\- Use RegionManager to get region data. +\- Use coordinate mapper or boundary conversion instead of putting Minecraft classes into RegionCoordinate. +\- Do not reference settlements, factions, NPCs, roads, or economy. + +Output: +\- Full Java file. +\- API assumptions. + +Task 14.3 prompt — Create SimulateCommand + +MODEL: Qwen3.6-27B + +PROMPT: +Create src/main/java/com/livingworld/commands/SimulateCommand.java. + +Requirements: +\- Operator-only command. +\- Accept tick count. +\- Safe maximum default: 1000 ticks. +\- Calls SimulationManager.runForcedSimulationTicks(ticks). +\- Rejects negative or zero ticks. +\- Prints clear success/failure output. +\- Does not implement simulation rules itself. + +Output: +\- Full Java file. +\- Notes on NeoForge/Minecraft command API assumptions. + +Milestone 14 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review command implementation. + +Files: +\[PASTE LivingWorldCommandRoot.java, RegionInfoCommand.java, SimulateCommand.java\] + +Check: +\- Minecraft/NeoForge imports are limited to command boundary. +\- Commands delegate to services. +\- /lw simulate has safety limits. +\- Output is useful for debugging ecosystem state. +\- No gameplay logic hidden in commands. + +Output: +\- Pass/fail. +\- Fixes. + +75\. Milestone 15 AI Prompts — Profiling + +Task 15.1 prompt — Create SimulationProfiler + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/debug/SimulationProfiler.java. + +Requirements: +\- Plain Java class. +\- Tracks: + \- cycle start time + \- cycle end time + \- per-module timings + \- per-region timings if simple + \- event count + \- save count + \- budget overruns +\- Methods: + \- beginCycle() + \- endCycle() + \- beginModule(String moduleId) + \- endModule(String moduleId) + \- recordEvent() + \- recordSave() + \- recordBudgetOverrun() + \- SimulationProfileSnapshot createSnapshot() +\- Use System.nanoTime internally for timing. +\- Do not use System.out. +\- No Minecraft imports. + +Output: +\- Full Java file. + +Task 15.2 prompt — Create SimulationProfileSnapshot + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/debug/SimulationProfileSnapshot.java. + +Requirements: +\- Immutable class or record. +\- Fields: + \- long totalCycleNanos + \- Map\ moduleTimings + \- int eventsPublished + \- int regionsUpdated + \- int savesPerformed + \- boolean budgetExceeded +\- Add method toHumanReadableString(). +\- No Minecraft imports. + +Output: +\- Full Java file. + +Milestone 15 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review simulation profiling classes. + +Files: +\[PASTE SimulationProfiler.java and SimulationProfileSnapshot.java\] + +Check: +\- Profiler can measure module cost. +\- Snapshot is immutable or safely copied. +\- No server-spam logging built in. +\- Usable by debug commands later. +\- No Minecraft imports. + +Output: +\- Pass/fail. +\- Fixes. + +76\. Milestone 16 AI Prompts — Tests and Long-Run Validation + +Task 16.1 prompt — Create TestSimulationModule + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/testing/TestSimulationModule.java. + +Requirements: +\- Implements SimulationModule. +\- Module ID: test +\- Metadata display name: Test Simulation Module +\- On initialize, set initialized \= true. +\- On updateRegion, increment an internal update counter. +\- Every 10 updates, return ModuleUpdateResult.changed(). +\- Otherwise return ModuleUpdateResult.noChange(). +\- No Minecraft imports. +\- This is for engine testing only, not gameplay. + +Output: +\- Full Java file. + +Task 16.2 prompt — Create RegionCoordinateTest + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/test/java/com/livingworld/regions/RegionCoordinateTest.java. + +Tests: +\- block 0,0 maps to region 0,0 +\- block 127,127 maps to region 0,0 with region size 8 chunks +\- block 128,0 maps to region 1,0 +\- block \-1,0 maps to region \-1,0 +\- block \-128,0 maps to region \-1,0 +\- block \-129,0 maps to region \-2,0 +\- stableId remains stable +\- RegionCoordinate works as HashMap key + +Use the project’s test framework. If unknown, assume JUnit 5\. + +Output: +\- Full test file. + +Task 16.3 prompt — Create RegionLifecycleControllerTest + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/test/java/com/livingworld/regions/RegionLifecycleControllerTest.java. + +Tests: +\- Allowed transitions succeed. +\- Invalid transitions throw IllegalStateException. +\- FAILED can transition to LOADING. +\- ACTIVE cannot transition directly to SAVING. + +Use JUnit 5 unless project uses something else. + +Output: +\- Full test file. + +Task 16.4 prompt — Create SchedulerBudgetTest + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/test/java/com/livingworld/core/simulation/SchedulerBudgetTest.java. + +Tests: +\- Given 500 jobs and maxRegionsPerCycle 50, one cycle returns 50 jobs. +\- Higher priority jobs are returned first. +\- Jobs not processed remain queued. +\- shouldRunSimulationCycle respects simulationIntervalTicks. + +Use JUnit 5 unless project uses something else. + +Output: +\- Full test file. + +Task 16.5 prompt — Create EventBusTest + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/test/java/com/livingworld/events/EventBusTest.java. + +Tests: +\- Listener receives event. +\- Listener receives event once. +\- Multiple listeners receive the event. +\- Unknown event type does not crash. + +Use BaseLivingWorldEvent for test events. +Use JUnit 5 unless project uses something else. + +Output: +\- Full test file. + +Task 16.6 prompt — Create LongRunSimulationTest + +MODEL: Qwen3.6-27B + +PROMPT: +Create src/test/java/com/livingworld/testing/LongRunSimulationTest.java. + +Purpose: +Prove the foundation can run for a long time without collapsing. + +Test setup: +\- Create 1000 fake regions. +\- Register TestSimulationModule. +\- Run 100,000 simulation ticks or a reduced count if local test runtime is too high. +\- Save dirty regions periodically using a fake/in-memory PersistenceService. + +Pass conditions: +\- No crash. +\- No unhandled exception. +\- No invalid lifecycle transition. +\- Dirty region count does not grow forever. +\- Profiler reports cycle data. + +Requirements: +\- Keep it plain Java where possible. +\- Do not require a full Minecraft client. +\- If full implementation requires missing services, create small in-memory fakes inside the test. + +Output: +\- Full test file. +\- Explanation of any fake services. + +Milestone 16 review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the engine test suite. + +Files: +\[PASTE all test files and TestSimulationModule.java\] + +Check: +\- Tests cover foundation risks. +\- Tests do not require Minecraft client unless absolutely necessary. +\- Long-run test is realistic for local hardware. +\- Tests catch coordinate, lifecycle, scheduler, event, and simulation failures. +\- No NPC/settlement/faction/wildlife scope creep. + +Output: +\- Pass/fail. +\- Missing tests. +\- Recommended final fixes before Volume 2\. + +77\. Platform Adapter AI Prompts + +Task P1 prompt — Create PlatformAdapter interface + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/platform/PlatformAdapter.java. + +Requirements: +\- Interface. +\- Methods: + \- String getPlatformName() + \- String getMinecraftVersion() + \- String getLoaderVersion() + \- boolean isDedicatedServer() + \- Path getWorldSaveDirectory() + \- void registerCommands() + \- void registerServerTickHook() + \- void registerPlayerEventHooks() +\- Import java.nio.file.Path only. +\- No NeoForge imports here. + +Output: +\- Full Java interface. + +Task P2 prompt — Create NeoForgePlatformAdapter skeleton + +MODEL: Qwen3.6-27B + +PROMPT: +Create src/main/java/com/livingworld/platform/neoforge/NeoForgePlatformAdapter.java. + +Purpose: +Implement PlatformAdapter using NeoForge as the active loader. + +Rules: +\- This class may import NeoForge and Minecraft classes. +\- Core simulation classes must not import NeoForge or Minecraft classes. +\- If exact NeoForge API names are uncertain for the target version, add clear TODOs at those points instead of inventing unsafe code. +\- Must log platform name, Minecraft version, and loader version during bootstrap. +\- Must provide hooks that can forward server ticks to SimulationManager later. + +Output: +\- Full Java file or safe skeleton. +\- API assumptions. +\- TODOs requiring verification. + +Task P3 prompt — Create MinecraftCoordinateMapper + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/integration/minecraft/MinecraftCoordinateMapper.java. + +Requirements: +\- Boundary class for converting Minecraft coordinates into Living World coordinates. +\- For now, avoid importing Minecraft classes unless necessary. +\- Methods: + \- RegionCoordinate fromBlockPos(String dimensionId, int blockX, int blockZ, int regionSizeChunks) + \- RegionCoordinate fromChunkPos(String dimensionId, int chunkX, int chunkZ, int regionSizeChunks) +\- Delegate to RegionCoordinate.fromBlock and RegionCoordinate.fromChunk. +\- No gameplay logic. + +Output: +\- Full Java file. + +Platform review prompt + +MODEL: Qwen3.6-27B + +PROMPT: +Review the platform adapter layer. + +Files: +\[PASTE PlatformAdapter.java, NeoForgePlatformAdapter.java, MinecraftCoordinateMapper.java\] + +Check: +\- NeoForge imports are isolated to platform/neoforge or command boundary. +\- Core simulation remains plain Java. +\- Platform API is not too wide. +\- Version reporting is possible. +\- Server tick hook can eventually call SimulationManager. + +Output: +\- Pass/fail. +\- Fixes. +\- Any NeoForge API calls that need checking against current docs. + +78\. Ecosystem MVP AI Prompts — To Use After Volume 1 Passes + +Do not use these until the Volume 1 completion gate passes. + +Ecosystem MVP prompt 1 — Create EcosystemRegionData + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/ecosystem/EcosystemRegionData.java. + +Requirements: +\- Plain Java class. +\- Fields: + \- double ecosystemHealth + \- double stress + \- double resilience + \- double recoveryRate +\- Values clamped 0 to 100\. +\- Methods: + \- defaults() + \- normalize() + \- copy() + \- applyStress(double amount) + \- applyRecovery(double amount) +\- No Minecraft imports. +\- No wildlife, NPC, village, faction, or economy logic. + +Output: +\- Full Java file. + +Ecosystem MVP prompt 2 — Create SoilRegionData + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/soil/SoilRegionData.java. + +Requirements: +\- Plain Java class. +\- Fields: + \- double fertility + \- double moisture + \- double contamination + \- double compaction + \- double erosion +\- Values clamped 0 to 100\. +\- Methods: + \- defaults() + \- normalize() + \- copy() + \- degrade(double amount) + \- recover(double amount) +\- No Minecraft imports. + +Output: +\- Full Java file. + +Ecosystem MVP prompt 3 — Create PollutionRegionData + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/pollution/PollutionRegionData.java. + +Requirements: +\- Plain Java class. +\- Fields: + \- double airPollution + \- double groundPollution + \- double waterPollution + \- double decayResistance +\- Values clamped 0 to 100\. +\- Methods: + \- defaults() + \- normalize() + \- copy() + \- addPollution(double air, double ground, double water) + \- decay(double amount) +\- No Minecraft imports. + +Output: +\- Full Java file. + +Ecosystem MVP prompt 4 — Create VegetationRegionData + +MODEL: Qwen3.5-9B + +PROMPT: +Create src/main/java/com/livingworld/modules/vegetation/VegetationRegionData.java. + +Requirements: +\- Plain Java class. +\- Fields: + \- double grassPressure + \- double flowerPressure + \- double shrubPressure + \- double treePressure + \- double deadVegetation +\- Values clamped 0 to 100\. +\- Methods: + \- defaults() + \- normalize() + \- copy() + \- reduceFromLogging(double amount) + \- recover(double amount) +\- No Minecraft imports. +\- Do not simulate animal populations. + +Output: +\- Full Java file. + +Ecosystem MVP prompt 5 — Create simple ecosystem update formulas + +MODEL: Qwen3.6-27B + +PROMPT: +Design the first simple ecosystem update formulas for the Living World mod. + +Scope: +\- Ecosystem-only. +\- No animals. +\- No villagers. +\- No settlements. +\- No factions. + +Inputs per region: +\- ecosystemHealth +\- soil fertility +\- soil moisture +\- soil contamination +\- water quality +\- air/ground/water pollution +\- vegetation pressure +\- resource depletion +\- recovery pressure + +Output required: +1\. Simple formulas for one simulation tick. +2\. Pseudocode for update order. +3\. Edge cases. +4\. Values that should be configurable. +5\. Safety caps so values stay between 0 and 100\. +6\. Explanation simple enough to turn into Java classes. + +Do not write Minecraft block-changing code yet. + +79\. Daily Local AI Working Method + +Use this method every coding session. + +Step 1: +Use Qwen3.6-27B for planning the next 3 to 5 tiny tasks. + +Prompt: +MODEL: Qwen3.6-27B + +Given the current Living World project state below, choose the next 3 to 5 safest tasks. + +Current state: +\[PASTE WHAT EXISTS\] + +Rules: +\- Ecosystem-only mod. +\- No NPCs, factions, settlements, roads, or wildlife simulation. +\- Core simulation remains plain Java. +\- Minecraft/NeoForge imports stay in platform, integration, or command packages. +\- Each task must be completable by Qwen3.5-9B in one file or one test file. + +Output: +For each task give: +\- task name +\- target model +\- file path +\- exact prompt to give Qwen3.5-9B +\- tests required +\- definition of done + +Step 2: +Give one task at a time to Qwen3.5-9B. + +Step 3: +Run build/tests. + +Step 4: +If build fails, give the exact error to Qwen3.5-9B once. + +Repair prompt: +MODEL: Qwen3.5-9B + +The previous code failed to compile. + +Error: +\[PASTE ERROR\] + +Relevant files: +\[PASTE FILES\] + +Fix only the error. +Do not rewrite unrelated code. +Do not add new systems. +Return the corrected file only. + +Step 5: +If the second 9B attempt fails, escalate to Qwen3.6-27B. + +Escalation prompt: +MODEL: Qwen3.6-27B + +Qwen3.5-9B failed twice to fix this issue. + +Goal: +\[PASTE TASK GOAL\] + +Error: +\[PASTE ERROR\] + +Files: +\[PASTE RELEVANT FILES\] + +Find the root cause and provide the smallest safe fix. +Do not redesign the system unless the design is the root cause. +Do not add unrelated features. + +Output: +\- root cause +\- corrected code +\- why the fix works +\- any follow-up test needed + +Step 6: +After every successful task, ask Qwen3.6-27B to review the milestone when enough files exist. + +80\. Prompt Discipline Rules + +These rules are mandatory when working with local AI. + +Rule 1: +Never ask a 9B model to build a whole subsystem. + +Rule 2: +Never give vague prompts such as "make the ecosystem" or "fix the mod". + +Rule 3: +Always include the file path. + +Rule 4: +Always include the class name. + +Rule 5: +Always state whether Minecraft or NeoForge imports are allowed. + +Rule 6: +Always state what is out of scope. + +Rule 7: +Always ask for tests when the code is plain Java. + +Rule 8: +Always run tests before moving on. + +Rule 9: +After two failed repair attempts by Qwen3.5-9B, escalate to Qwen3.6-27B. + +Rule 10: +If Qwen3.6-27B says the task is too broad, split it into smaller files. + +81\. Local Model Role Summary + +Qwen3.5-9B role: +\- one class +\- one enum +\- one record +\- one interface +\- one test file +\- simple bug fix +\- simple refactor +\- simple documentation + +Qwen3.6-27B role: +\- architecture review +\- multi-file debugging +\- NeoForge uncertainty +\- save design +\- scheduler design +\- module boundary review +\- version upgrade review +\- difficult compile errors +\- planning next batches + +Do not swap these roles unless necessary. \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..1205924 --- /dev/null +++ b/build.gradle @@ -0,0 +1,47 @@ +plugins { + id 'java-library' + id 'net.neoforged.moddev' version '2.0.107' +} + +group = 'com.livingworld' +version = '0.1.0' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +neoForge { + version = '21.1.172' + + runs { + client { + client() + } + + server { + server() + } + } + + mods { + living_world { + sourceSet sourceSets.main + } + } +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.2' +} + +test { + useJUnitPlatform() +} diff --git a/build/classes/java/main/com/livingworld/LivingWorldMod.class b/build/classes/java/main/com/livingworld/LivingWorldMod.class new file mode 100644 index 0000000..4647a95 Binary files /dev/null and b/build/classes/java/main/com/livingworld/LivingWorldMod.class differ diff --git a/build/classes/java/main/com/livingworld/bootstrap/LivingWorldBootstrap.class b/build/classes/java/main/com/livingworld/bootstrap/LivingWorldBootstrap.class new file mode 100644 index 0000000..da4adb2 Binary files /dev/null and b/build/classes/java/main/com/livingworld/bootstrap/LivingWorldBootstrap.class differ diff --git a/build/classes/java/main/com/livingworld/config/DefaultConfigService.class b/build/classes/java/main/com/livingworld/config/DefaultConfigService.class new file mode 100644 index 0000000..c15b192 Binary files /dev/null and b/build/classes/java/main/com/livingworld/config/DefaultConfigService.class differ diff --git a/build/classes/java/main/com/livingworld/config/SimulationConfig.class b/build/classes/java/main/com/livingworld/config/SimulationConfig.class new file mode 100644 index 0000000..f72ed59 Binary files /dev/null and b/build/classes/java/main/com/livingworld/config/SimulationConfig.class differ diff --git a/build/classes/java/main/com/livingworld/core/LivingWorldConstants.class b/build/classes/java/main/com/livingworld/core/LivingWorldConstants.class new file mode 100644 index 0000000..043432d Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/LivingWorldConstants.class differ diff --git a/build/classes/java/main/com/livingworld/core/services/ConfigService.class b/build/classes/java/main/com/livingworld/core/services/ConfigService.class new file mode 100644 index 0000000..be797df Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/services/ConfigService.class differ diff --git a/build/classes/java/main/com/livingworld/core/services/CoreServices.class b/build/classes/java/main/com/livingworld/core/services/CoreServices.class new file mode 100644 index 0000000..658d8e3 Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/services/CoreServices.class differ diff --git a/build/classes/java/main/com/livingworld/core/services/ServiceKey.class b/build/classes/java/main/com/livingworld/core/services/ServiceKey.class new file mode 100644 index 0000000..babaf1c Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/services/ServiceKey.class differ diff --git a/build/classes/java/main/com/livingworld/core/services/ServiceRegistry.class b/build/classes/java/main/com/livingworld/core/services/ServiceRegistry.class new file mode 100644 index 0000000..731fd30 Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/services/ServiceRegistry.class differ diff --git a/build/classes/java/main/com/livingworld/core/services/TimeService.class b/build/classes/java/main/com/livingworld/core/services/TimeService.class new file mode 100644 index 0000000..af82fe9 Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/services/TimeService.class differ diff --git a/build/classes/java/main/com/livingworld/core/simulation/DefaultTimeService.class b/build/classes/java/main/com/livingworld/core/simulation/DefaultTimeService.class new file mode 100644 index 0000000..859446a Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/simulation/DefaultTimeService.class differ diff --git a/build/classes/java/main/com/livingworld/core/simulation/LivingWorldCalendar.class b/build/classes/java/main/com/livingworld/core/simulation/LivingWorldCalendar.class new file mode 100644 index 0000000..ee04ba7 Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/simulation/LivingWorldCalendar.class differ diff --git a/build/classes/java/main/com/livingworld/core/simulation/PersistenceService.class b/build/classes/java/main/com/livingworld/core/simulation/PersistenceService.class new file mode 100644 index 0000000..e162aaf Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/simulation/PersistenceService.class differ diff --git a/build/classes/java/main/com/livingworld/core/simulation/RegionManager.class b/build/classes/java/main/com/livingworld/core/simulation/RegionManager.class new file mode 100644 index 0000000..70db7ee Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/simulation/RegionManager.class differ diff --git a/build/classes/java/main/com/livingworld/core/simulation/RegionPriorityCalculator.class b/build/classes/java/main/com/livingworld/core/simulation/RegionPriorityCalculator.class new file mode 100644 index 0000000..0017408 Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/simulation/RegionPriorityCalculator.class differ diff --git a/build/classes/java/main/com/livingworld/core/simulation/RegionUpdateJob.class b/build/classes/java/main/com/livingworld/core/simulation/RegionUpdateJob.class new file mode 100644 index 0000000..31649ae Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/simulation/RegionUpdateJob.class differ diff --git a/build/classes/java/main/com/livingworld/core/simulation/SimulationManager.class b/build/classes/java/main/com/livingworld/core/simulation/SimulationManager.class new file mode 100644 index 0000000..474bc8d Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/simulation/SimulationManager.class differ diff --git a/build/classes/java/main/com/livingworld/core/simulation/SimulationProfiler.class b/build/classes/java/main/com/livingworld/core/simulation/SimulationProfiler.class new file mode 100644 index 0000000..3ada266 Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/simulation/SimulationProfiler.class differ diff --git a/build/classes/java/main/com/livingworld/core/simulation/SimulationScheduler$TestSuggestions.class b/build/classes/java/main/com/livingworld/core/simulation/SimulationScheduler$TestSuggestions.class new file mode 100644 index 0000000..bde38c0 Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/simulation/SimulationScheduler$TestSuggestions.class differ diff --git a/build/classes/java/main/com/livingworld/core/simulation/SimulationScheduler.class b/build/classes/java/main/com/livingworld/core/simulation/SimulationScheduler.class new file mode 100644 index 0000000..2559962 Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/simulation/SimulationScheduler.class differ diff --git a/build/classes/java/main/com/livingworld/core/simulation/UpdateReason.class b/build/classes/java/main/com/livingworld/core/simulation/UpdateReason.class new file mode 100644 index 0000000..ba27a56 Binary files /dev/null and b/build/classes/java/main/com/livingworld/core/simulation/UpdateReason.class differ diff --git a/build/classes/java/main/com/livingworld/data/serialization/PersistenceReader.class b/build/classes/java/main/com/livingworld/data/serialization/PersistenceReader.class new file mode 100644 index 0000000..2f6e719 Binary files /dev/null and b/build/classes/java/main/com/livingworld/data/serialization/PersistenceReader.class differ diff --git a/build/classes/java/main/com/livingworld/data/serialization/PersistenceWriter.class b/build/classes/java/main/com/livingworld/data/serialization/PersistenceWriter.class new file mode 100644 index 0000000..79a3d8f Binary files /dev/null and b/build/classes/java/main/com/livingworld/data/serialization/PersistenceWriter.class differ diff --git a/build/classes/java/main/com/livingworld/debug/DiagnosticCategory.class b/build/classes/java/main/com/livingworld/debug/DiagnosticCategory.class new file mode 100644 index 0000000..91c8a90 Binary files /dev/null and b/build/classes/java/main/com/livingworld/debug/DiagnosticCategory.class differ diff --git a/build/classes/java/main/com/livingworld/debug/LivingWorldLogger.class b/build/classes/java/main/com/livingworld/debug/LivingWorldLogger.class new file mode 100644 index 0000000..9fd64fa Binary files /dev/null and b/build/classes/java/main/com/livingworld/debug/LivingWorldLogger.class differ diff --git a/build/classes/java/main/com/livingworld/events/BaseLivingWorldEvent.class b/build/classes/java/main/com/livingworld/events/BaseLivingWorldEvent.class new file mode 100644 index 0000000..a1b048b Binary files /dev/null and b/build/classes/java/main/com/livingworld/events/BaseLivingWorldEvent.class differ diff --git a/build/classes/java/main/com/livingworld/events/LivingWorldEvent.class b/build/classes/java/main/com/livingworld/events/LivingWorldEvent.class new file mode 100644 index 0000000..3561283 Binary files /dev/null and b/build/classes/java/main/com/livingworld/events/LivingWorldEvent.class differ diff --git a/build/classes/java/main/com/livingworld/events/LivingWorldEventBus.class b/build/classes/java/main/com/livingworld/events/LivingWorldEventBus.class new file mode 100644 index 0000000..13efda6 Binary files /dev/null and b/build/classes/java/main/com/livingworld/events/LivingWorldEventBus.class differ diff --git a/build/classes/java/main/com/livingworld/events/LivingWorldEventListener.class b/build/classes/java/main/com/livingworld/events/LivingWorldEventListener.class new file mode 100644 index 0000000..dabfb99 Binary files /dev/null and b/build/classes/java/main/com/livingworld/events/LivingWorldEventListener.class differ diff --git a/build/classes/java/main/com/livingworld/modules/HistoryRecord.class b/build/classes/java/main/com/livingworld/modules/HistoryRecord.class new file mode 100644 index 0000000..3b5e709 Binary files /dev/null and b/build/classes/java/main/com/livingworld/modules/HistoryRecord.class differ diff --git a/build/classes/java/main/com/livingworld/modules/ModuleContext.class b/build/classes/java/main/com/livingworld/modules/ModuleContext.class new file mode 100644 index 0000000..3a196b4 Binary files /dev/null and b/build/classes/java/main/com/livingworld/modules/ModuleContext.class differ diff --git a/build/classes/java/main/com/livingworld/modules/ModuleMetadata.class b/build/classes/java/main/com/livingworld/modules/ModuleMetadata.class new file mode 100644 index 0000000..bfeecbc Binary files /dev/null and b/build/classes/java/main/com/livingworld/modules/ModuleMetadata.class differ diff --git a/build/classes/java/main/com/livingworld/modules/ModuleRegistry.class b/build/classes/java/main/com/livingworld/modules/ModuleRegistry.class new file mode 100644 index 0000000..dfcc5f7 Binary files /dev/null and b/build/classes/java/main/com/livingworld/modules/ModuleRegistry.class differ diff --git a/build/classes/java/main/com/livingworld/modules/ModuleUpdateResult.class b/build/classes/java/main/com/livingworld/modules/ModuleUpdateResult.class new file mode 100644 index 0000000..58e9e76 Binary files /dev/null and b/build/classes/java/main/com/livingworld/modules/ModuleUpdateResult.class differ diff --git a/build/classes/java/main/com/livingworld/modules/RegionUpdateContext.class b/build/classes/java/main/com/livingworld/modules/RegionUpdateContext.class new file mode 100644 index 0000000..261144a Binary files /dev/null and b/build/classes/java/main/com/livingworld/modules/RegionUpdateContext.class differ diff --git a/build/classes/java/main/com/livingworld/modules/ServerContext.class b/build/classes/java/main/com/livingworld/modules/ServerContext.class new file mode 100644 index 0000000..562faba Binary files /dev/null and b/build/classes/java/main/com/livingworld/modules/ServerContext.class differ diff --git a/build/classes/java/main/com/livingworld/modules/SimulationModule.class b/build/classes/java/main/com/livingworld/modules/SimulationModule.class new file mode 100644 index 0000000..a433777 Binary files /dev/null and b/build/classes/java/main/com/livingworld/modules/SimulationModule.class differ diff --git a/build/classes/java/main/com/livingworld/regions/Region.class b/build/classes/java/main/com/livingworld/regions/Region.class new file mode 100644 index 0000000..f899d8e Binary files /dev/null and b/build/classes/java/main/com/livingworld/regions/Region.class differ diff --git a/build/classes/java/main/com/livingworld/regions/RegionCoordinate.class b/build/classes/java/main/com/livingworld/regions/RegionCoordinate.class new file mode 100644 index 0000000..610343a Binary files /dev/null and b/build/classes/java/main/com/livingworld/regions/RegionCoordinate.class differ diff --git a/build/classes/java/main/com/livingworld/regions/RegionFactory.class b/build/classes/java/main/com/livingworld/regions/RegionFactory.class new file mode 100644 index 0000000..e2d3706 Binary files /dev/null and b/build/classes/java/main/com/livingworld/regions/RegionFactory.class differ diff --git a/build/classes/java/main/com/livingworld/regions/RegionFlags.class b/build/classes/java/main/com/livingworld/regions/RegionFlags.class new file mode 100644 index 0000000..2a26cd5 Binary files /dev/null and b/build/classes/java/main/com/livingworld/regions/RegionFlags.class differ diff --git a/build/classes/java/main/com/livingworld/regions/RegionLifecycleController.class b/build/classes/java/main/com/livingworld/regions/RegionLifecycleController.class new file mode 100644 index 0000000..7892a52 Binary files /dev/null and b/build/classes/java/main/com/livingworld/regions/RegionLifecycleController.class differ diff --git a/build/classes/java/main/com/livingworld/regions/RegionLifecycleState.class b/build/classes/java/main/com/livingworld/regions/RegionLifecycleState.class new file mode 100644 index 0000000..8e99154 Binary files /dev/null and b/build/classes/java/main/com/livingworld/regions/RegionLifecycleState.class differ diff --git a/build/classes/java/main/com/livingworld/regions/RegionMetrics.class b/build/classes/java/main/com/livingworld/regions/RegionMetrics.class new file mode 100644 index 0000000..7013dfc Binary files /dev/null and b/build/classes/java/main/com/livingworld/regions/RegionMetrics.class differ diff --git a/build/classes/java/main/com/livingworld/regions/RegionModuleData.class b/build/classes/java/main/com/livingworld/regions/RegionModuleData.class new file mode 100644 index 0000000..c083b39 Binary files /dev/null and b/build/classes/java/main/com/livingworld/regions/RegionModuleData.class differ diff --git a/build/classes/java/test/com/livingworld/config/SimulationConfigTest$DefaultValues.class b/build/classes/java/test/com/livingworld/config/SimulationConfigTest$DefaultValues.class new file mode 100644 index 0000000..c0190ea Binary files /dev/null and b/build/classes/java/test/com/livingworld/config/SimulationConfigTest$DefaultValues.class differ diff --git a/build/classes/java/test/com/livingworld/config/SimulationConfigTest$InvalidValues.class b/build/classes/java/test/com/livingworld/config/SimulationConfigTest$InvalidValues.class new file mode 100644 index 0000000..2f4663e Binary files /dev/null and b/build/classes/java/test/com/livingworld/config/SimulationConfigTest$InvalidValues.class differ diff --git a/build/classes/java/test/com/livingworld/config/SimulationConfigTest$Setters.class b/build/classes/java/test/com/livingworld/config/SimulationConfigTest$Setters.class new file mode 100644 index 0000000..f25c7ad Binary files /dev/null and b/build/classes/java/test/com/livingworld/config/SimulationConfigTest$Setters.class differ diff --git a/build/classes/java/test/com/livingworld/config/SimulationConfigTest$ValidValues.class b/build/classes/java/test/com/livingworld/config/SimulationConfigTest$ValidValues.class new file mode 100644 index 0000000..1597561 Binary files /dev/null and b/build/classes/java/test/com/livingworld/config/SimulationConfigTest$ValidValues.class differ diff --git a/build/classes/java/test/com/livingworld/config/SimulationConfigTest.class b/build/classes/java/test/com/livingworld/config/SimulationConfigTest.class new file mode 100644 index 0000000..1b42e87 Binary files /dev/null and b/build/classes/java/test/com/livingworld/config/SimulationConfigTest.class differ diff --git a/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$DuplicateRegistrationRejection.class b/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$DuplicateRegistrationRejection.class new file mode 100644 index 0000000..c3789d4 Binary files /dev/null and b/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$DuplicateRegistrationRejection.class differ diff --git a/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$GetMissingThrows.class b/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$GetMissingThrows.class new file mode 100644 index 0000000..b513b74 Binary files /dev/null and b/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$GetMissingThrows.class differ diff --git a/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$LockMechanism.class b/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$LockMechanism.class new file mode 100644 index 0000000..b78c353 Binary files /dev/null and b/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$LockMechanism.class differ diff --git a/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$NullArgumentValidation.class b/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$NullArgumentValidation.class new file mode 100644 index 0000000..6df1e4c Binary files /dev/null and b/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$NullArgumentValidation.class differ diff --git a/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$PlainJava.class b/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$PlainJava.class new file mode 100644 index 0000000..a2d55ed Binary files /dev/null and b/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$PlainJava.class differ diff --git a/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$RegistrationAndRetrieval.class b/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$RegistrationAndRetrieval.class new file mode 100644 index 0000000..3e19a8b Binary files /dev/null and b/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest$RegistrationAndRetrieval.class differ diff --git a/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest.class b/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest.class new file mode 100644 index 0000000..6d35f86 Binary files /dev/null and b/build/classes/java/test/com/livingworld/core/services/ServiceRegistryTest.class differ diff --git a/build/classes/java/test/com/livingworld/events/LivingWorldEventBusTest.class b/build/classes/java/test/com/livingworld/events/LivingWorldEventBusTest.class new file mode 100644 index 0000000..e100f48 Binary files /dev/null and b/build/classes/java/test/com/livingworld/events/LivingWorldEventBusTest.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$CenterBlockMethods.class b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$CenterBlockMethods.class new file mode 100644 index 0000000..401ee1d Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$CenterBlockMethods.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$ChunkBoundaryMethods.class b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$ChunkBoundaryMethods.class new file mode 100644 index 0000000..c119f9e Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$ChunkBoundaryMethods.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$DimensionIdValidation.class b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$DimensionIdValidation.class new file mode 100644 index 0000000..42221ed Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$DimensionIdValidation.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$FromBlockMapping.class b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$FromBlockMapping.class new file mode 100644 index 0000000..e20ee06 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$FromBlockMapping.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$FromChunkMapping.class b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$FromChunkMapping.class new file mode 100644 index 0000000..be5b5f8 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$FromChunkMapping.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$HashMapKeyTests.class b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$HashMapKeyTests.class new file mode 100644 index 0000000..2100757 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$HashMapKeyTests.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$RegionSizeValidation.class b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$RegionSizeValidation.class new file mode 100644 index 0000000..490d82a Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$RegionSizeValidation.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$StableIdTests.class b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$StableIdTests.class new file mode 100644 index 0000000..4178428 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$StableIdTests.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$ToStringTests.class b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$ToStringTests.class new file mode 100644 index 0000000..7271e65 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest$ToStringTests.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest.class b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest.class new file mode 100644 index 0000000..3a9c743 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionCoordinateTest.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionFactoryTest.class b/build/classes/java/test/com/livingworld/regions/RegionFactoryTest.class new file mode 100644 index 0000000..771c951 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionFactoryTest.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionFlagsTest$ClearTransientFlagTests.class b/build/classes/java/test/com/livingworld/regions/RegionFlagsTest$ClearTransientFlagTests.class new file mode 100644 index 0000000..6db3f59 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionFlagsTest$ClearTransientFlagTests.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionFlagsTest$CopyTests.class b/build/classes/java/test/com/livingworld/regions/RegionFlagsTest$CopyTests.class new file mode 100644 index 0000000..4e583c5 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionFlagsTest$CopyTests.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionFlagsTest$DefaultValues.class b/build/classes/java/test/com/livingworld/regions/RegionFlagsTest$DefaultValues.class new file mode 100644 index 0000000..6b43b4e Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionFlagsTest$DefaultValues.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionFlagsTest$GetterSetterTests.class b/build/classes/java/test/com/livingworld/regions/RegionFlagsTest$GetterSetterTests.class new file mode 100644 index 0000000..c3e952b Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionFlagsTest$GetterSetterTests.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionFlagsTest$ToStringTests.class b/build/classes/java/test/com/livingworld/regions/RegionFlagsTest$ToStringTests.class new file mode 100644 index 0000000..0420169 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionFlagsTest$ToStringTests.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionFlagsTest.class b/build/classes/java/test/com/livingworld/regions/RegionFlagsTest.class new file mode 100644 index 0000000..45a0c72 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionFlagsTest.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionLifecycleControllerTest$AllowedTransitions.class b/build/classes/java/test/com/livingworld/regions/RegionLifecycleControllerTest$AllowedTransitions.class new file mode 100644 index 0000000..3ff6ded Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionLifecycleControllerTest$AllowedTransitions.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionLifecycleControllerTest$DisallowedTransitions.class b/build/classes/java/test/com/livingworld/regions/RegionLifecycleControllerTest$DisallowedTransitions.class new file mode 100644 index 0000000..fbb44d9 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionLifecycleControllerTest$DisallowedTransitions.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionLifecycleControllerTest$NullHandling.class b/build/classes/java/test/com/livingworld/regions/RegionLifecycleControllerTest$NullHandling.class new file mode 100644 index 0000000..9cdb361 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionLifecycleControllerTest$NullHandling.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionLifecycleControllerTest$TransitionSideEffects.class b/build/classes/java/test/com/livingworld/regions/RegionLifecycleControllerTest$TransitionSideEffects.class new file mode 100644 index 0000000..2c42110 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionLifecycleControllerTest$TransitionSideEffects.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionLifecycleControllerTest.class b/build/classes/java/test/com/livingworld/regions/RegionLifecycleControllerTest.class new file mode 100644 index 0000000..f301a15 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionLifecycleControllerTest.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$ApplyDeltaTests.class b/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$ApplyDeltaTests.class new file mode 100644 index 0000000..d90e83a Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$ApplyDeltaTests.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$ConstantsTests.class b/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$ConstantsTests.class new file mode 100644 index 0000000..9bbb8c6 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$ConstantsTests.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$CopyTests.class b/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$CopyTests.class new file mode 100644 index 0000000..c33be5c Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$CopyTests.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$DefaultValues.class b/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$DefaultValues.class new file mode 100644 index 0000000..9f9f754 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$DefaultValues.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$GetterSetterClamping.class b/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$GetterSetterClamping.class new file mode 100644 index 0000000..d647963 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$GetterSetterClamping.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$NormalizeTests.class b/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$NormalizeTests.class new file mode 100644 index 0000000..d1e232b Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$NormalizeTests.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$ToStringTests.class b/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$ToStringTests.class new file mode 100644 index 0000000..ca82919 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionMetricsTest$ToStringTests.class differ diff --git a/build/classes/java/test/com/livingworld/regions/RegionMetricsTest.class b/build/classes/java/test/com/livingworld/regions/RegionMetricsTest.class new file mode 100644 index 0000000..0792259 Binary files /dev/null and b/build/classes/java/test/com/livingworld/regions/RegionMetricsTest.class differ diff --git a/build/moddev/artifacts/neoforge-21.1.172-client-extra-aka-minecraft-resources.jar b/build/moddev/artifacts/neoforge-21.1.172-client-extra-aka-minecraft-resources.jar new file mode 100644 index 0000000..b26c422 Binary files /dev/null and b/build/moddev/artifacts/neoforge-21.1.172-client-extra-aka-minecraft-resources.jar differ diff --git a/build/moddev/artifacts/neoforge-21.1.172-merged.jar b/build/moddev/artifacts/neoforge-21.1.172-merged.jar new file mode 100644 index 0000000..5bcdf0a Binary files /dev/null and b/build/moddev/artifacts/neoforge-21.1.172-merged.jar differ diff --git a/build/moddev/artifacts/neoforge-21.1.172-sources.jar b/build/moddev/artifacts/neoforge-21.1.172-sources.jar new file mode 100644 index 0000000..514786b Binary files /dev/null and b/build/moddev/artifacts/neoforge-21.1.172-sources.jar differ diff --git a/build/moddev/artifacts/neoforge-21.1.172.jar b/build/moddev/artifacts/neoforge-21.1.172.jar new file mode 100644 index 0000000..4796b2d Binary files /dev/null and b/build/moddev/artifacts/neoforge-21.1.172.jar differ diff --git a/build/reports/problems/problems-report.html b/build/reports/problems/problems-report.html new file mode 100644 index 0000000..17a27a4 --- /dev/null +++ b/build/reports/problems/problems-report.html @@ -0,0 +1,666 @@ + + + + + + + + + + + + + Gradle Configuration Cache + + + +
+ +
+ Loading... +
+ + + + + + diff --git a/build/reports/tests/test/0AqmkxadOMA/KRbSxPvXiZE/index.html b/build/reports/tests/test/0AqmkxadOMA/KRbSxPvXiZE/index.html new file mode 100644 index 0000000..14e07ac --- /dev/null +++ b/build/reports/tests/test/0AqmkxadOMA/KRbSxPvXiZE/index.html @@ -0,0 +1,127 @@ + + + + + +Test results - com.livingworld.regions.RegionFlagsTest$DefaultValues + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Default values

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
2
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
allFlagsDefaultToFalse()1000s100%
instanceIsNotNull()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/0AqmkxadOMA/U8vZG2vDMUE/index.html b/build/reports/tests/test/0AqmkxadOMA/U8vZG2vDMUE/index.html new file mode 100644 index 0000000..aa9014f --- /dev/null +++ b/build/reports/tests/test/0AqmkxadOMA/U8vZG2vDMUE/index.html @@ -0,0 +1,118 @@ + + + + + +Test results - com.livingworld.regions.RegionFlagsTest$ToStringTests + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

toString

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
1
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
toStringContainsFlagNames()1000.001s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/0AqmkxadOMA/index.html b/build/reports/tests/test/0AqmkxadOMA/index.html new file mode 100644 index 0000000..9ac4942 --- /dev/null +++ b/build/reports/tests/test/0AqmkxadOMA/index.html @@ -0,0 +1,163 @@ + + + + + +Test results - com.livingworld.regions.RegionFlagsTest + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

RegionFlagsTest

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
14
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.004s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
+clearTransientFlags() +com.livingworld.regions.RegionFlagsTest$ClearTransientFlagTests2000.001s100%
+copy() +com.livingworld.regions.RegionFlagsTest$CopyTests3000s100%
+Default values +com.livingworld.regions.RegionFlagsTest$DefaultValues2000s100%
+Getters and Setters +com.livingworld.regions.RegionFlagsTest$GetterSetterTests6000.001s100%
+toString +com.livingworld.regions.RegionFlagsTest$ToStringTests1000.001s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/0AqmkxadOMA/mCiO1hhyR9g/index.html b/build/reports/tests/test/0AqmkxadOMA/mCiO1hhyR9g/index.html new file mode 100644 index 0000000..843e884 --- /dev/null +++ b/build/reports/tests/test/0AqmkxadOMA/mCiO1hhyR9g/index.html @@ -0,0 +1,136 @@ + + + + + +Test results - com.livingworld.regions.RegionFlagsTest$CopyTests + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

copy()

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
3
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
copyPreservesAllFlagValues()1000s100%
copyReturnsIndependentInstance()1000s100%
modifyingCopyDoesNotAffectOriginal()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/0AqmkxadOMA/ujfUrcEzkEM/index.html b/build/reports/tests/test/0AqmkxadOMA/ujfUrcEzkEM/index.html new file mode 100644 index 0000000..76efde0 --- /dev/null +++ b/build/reports/tests/test/0AqmkxadOMA/ujfUrcEzkEM/index.html @@ -0,0 +1,127 @@ + + + + + +Test results - com.livingworld.regions.RegionFlagsTest$ClearTransientFlagTests + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

clearTransientFlags()

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
2
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
clearsTransientFlagsOnly()1000s100%
safeToCallWhenAllFlagsAlreadyFalse()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/0AqmkxadOMA/uz8gdzh6X54/index.html b/build/reports/tests/test/0AqmkxadOMA/uz8gdzh6X54/index.html new file mode 100644 index 0000000..fc5fb92 --- /dev/null +++ b/build/reports/tests/test/0AqmkxadOMA/uz8gdzh6X54/index.html @@ -0,0 +1,163 @@ + + + + + +Test results - com.livingworld.regions.RegionFlagsTest$GetterSetterTests + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Getters and Setters

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
6
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
corrupted()1000s100%
forceLoadedBySimulation()1000s100%
hasActiveEcosystemEvent()1000s100%
hasHighPollution()1000s100%
hasLowSoilQuality()1000s100%
hasPlayerActivity()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/CfqLFmBCDsE/-ol6TQejZyw/index.html b/build/reports/tests/test/CfqLFmBCDsE/-ol6TQejZyw/index.html new file mode 100644 index 0000000..431313a --- /dev/null +++ b/build/reports/tests/test/CfqLFmBCDsE/-ol6TQejZyw/index.html @@ -0,0 +1,136 @@ + + + + + +Test results - com.livingworld.regions.RegionMetricsTest$CopyTests + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Copy

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
3
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
copy() copies all fieldscopyAllFields()1000s100%
copy() returns a different objectcopyReturnsDifferentObject()1000s100%
copy() returns independent instancecopyReturnsIndependentInstance()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/CfqLFmBCDsE/1nelgGAXr9Y/index.html b/build/reports/tests/test/CfqLFmBCDsE/1nelgGAXr9Y/index.html new file mode 100644 index 0000000..aab5bf1 --- /dev/null +++ b/build/reports/tests/test/CfqLFmBCDsE/1nelgGAXr9Y/index.html @@ -0,0 +1,154 @@ + + + + + +Test results - com.livingworld.regions.RegionMetricsTest$GetterSetterClamping + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Getter and Setter Clamping

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
5
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
all setters clamp correctlyallSettersClamp()1000s100%
setter accepts exact boundary valuessetterAcceptsBoundaries()1000.001s100%
setter accepts normal valuessetterAcceptsNormalValues()1000s100%
setter clamps values above 100 to 100setterClampsAboveMax()1000s100%
setter clamps values below 0 to 0setterClampsBelowMin()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/CfqLFmBCDsE/Pm5eI7E4DBM/index.html b/build/reports/tests/test/CfqLFmBCDsE/Pm5eI7E4DBM/index.html new file mode 100644 index 0000000..7321626 --- /dev/null +++ b/build/reports/tests/test/CfqLFmBCDsE/Pm5eI7E4DBM/index.html @@ -0,0 +1,127 @@ + + + + + +Test results - com.livingworld.regions.RegionMetricsTest$ConstantsTests + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Constants

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
2
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
MAX_VALUE is 100maxValue()1000s100%
MIN_VALUE is 0minValue()1000.001s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/CfqLFmBCDsE/Yld3tGN_1Gc/index.html b/build/reports/tests/test/CfqLFmBCDsE/Yld3tGN_1Gc/index.html new file mode 100644 index 0000000..b7e083b --- /dev/null +++ b/build/reports/tests/test/CfqLFmBCDsE/Yld3tGN_1Gc/index.html @@ -0,0 +1,181 @@ + + + + + +Test results - com.livingworld.regions.RegionMetricsTest$DefaultValues + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Default Values

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
8
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
default constructor initializes all fields to 0defaultConstructorAllZeros()1000.001s100%
defaults() returns correct ecosystem healthdefaultsEcosystemHealth()1000s100%
defaults() returns correct pollution scoredefaultsPollutionScore()1000s100%
defaults() returns correct recovery pressuredefaultsRecoveryPressure()1000s100%
defaults() returns correct resource depletiondefaultsResourceDepletion()1000s100%
defaults() returns correct soil qualitydefaultsSoilQuality()1000s100%
defaults() returns correct vegetation pressuredefaultsVegetationPressure()1000s100%
defaults() returns correct water qualitydefaultsWaterQuality()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/CfqLFmBCDsE/index.html b/build/reports/tests/test/CfqLFmBCDsE/index.html new file mode 100644 index 0000000..9a266f2 --- /dev/null +++ b/build/reports/tests/test/CfqLFmBCDsE/index.html @@ -0,0 +1,185 @@ + + + + + +Test results - com.livingworld.regions.RegionMetricsTest + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

RegionMetricsTest

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
36
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.009s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
+Apply Delta +com.livingworld.regions.RegionMetricsTest$ApplyDeltaTests12000.002s100%
+Constants +com.livingworld.regions.RegionMetricsTest$ConstantsTests2000.001s100%
+Copy +com.livingworld.regions.RegionMetricsTest$CopyTests3000.001s100%
+Default Values +com.livingworld.regions.RegionMetricsTest$DefaultValues8000.001s100%
+Getter and Setter Clamping +com.livingworld.regions.RegionMetricsTest$GetterSetterClamping5000.001s100%
+Normalize +com.livingworld.regions.RegionMetricsTest$NormalizeTests4000.001s100%
+toString +com.livingworld.regions.RegionMetricsTest$ToStringTests2000.001s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/CfqLFmBCDsE/jtyL4qp334Q/index.html b/build/reports/tests/test/CfqLFmBCDsE/jtyL4qp334Q/index.html new file mode 100644 index 0000000..f2e6d58 --- /dev/null +++ b/build/reports/tests/test/CfqLFmBCDsE/jtyL4qp334Q/index.html @@ -0,0 +1,127 @@ + + + + + +Test results - com.livingworld.regions.RegionMetricsTest$ToStringTests + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

toString

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
2
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
toString() includes all field names and valuestoStringIncludesAllFields()1000s100%
toString() starts with class nametoStringStartsWithClassName()1000.001s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/CfqLFmBCDsE/ng-a-A_JsXY/index.html b/build/reports/tests/test/CfqLFmBCDsE/ng-a-A_JsXY/index.html new file mode 100644 index 0000000..dfff0f6 --- /dev/null +++ b/build/reports/tests/test/CfqLFmBCDsE/ng-a-A_JsXY/index.html @@ -0,0 +1,217 @@ + + + + + +Test results - com.livingworld.regions.RegionMetricsTest$ApplyDeltaTests + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Apply Delta

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
12
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.002s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
applyDelta() modifies all fields correctlyapplyDeltaAllFields()1000s100%
applyDelta() clamps results above maxapplyDeltaClampsAboveMax()1000s100%
applyDelta() clamps results below minapplyDeltaClampsBelowMin()1000s100%
applyDelta() returns this for chainingapplyDeltaReturnsThis()1000s100%
applyRecoveryPressureDelta() works correctlyapplyRecoveryPressureDelta()1000s100%
applyResourceDepletionDelta() works correctlyapplyResourceDepletionDelta()1000.001s100%
applyEcosystemHealthDelta() modifies only ecosystem healthapplySingleDelta()1000s100%
applyPollutionScoreDelta() clamps correctlyapplySingleDeltaClamps()1000s100%
applySoilQualityDelta() clamps below minapplySingleDeltaClampsBelow()1000s100%
applyVegetationPressureDelta() works correctlyapplyVegetationPressureDelta()1000s100%
applyWaterQualityDelta() works correctlyapplyWaterQualityDelta()1000s100%
individual apply methods return this for chainingindividualApplyMethodsReturnThis()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/CfqLFmBCDsE/yuOw7X7HuWE/index.html b/build/reports/tests/test/CfqLFmBCDsE/yuOw7X7HuWE/index.html new file mode 100644 index 0000000..a2134ca --- /dev/null +++ b/build/reports/tests/test/CfqLFmBCDsE/yuOw7X7HuWE/index.html @@ -0,0 +1,145 @@ + + + + + +Test results - com.livingworld.regions.RegionMetricsTest$NormalizeTests + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Normalize

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
4
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
normalize() clamps values above 100normalizeClampsAboveMax()1000s100%
normalize() clamps values below 0normalizeClampsBelowMin()1000s100%
normalize() leaves valid values unchangednormalizeLeavesValidUnchanged()1000s100%
normalize() returns this for chainingnormalizeReturnsThis()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/HH_SphoVWTI/01dkLzE0xDA/index.html b/build/reports/tests/test/HH_SphoVWTI/01dkLzE0xDA/index.html new file mode 100644 index 0000000..0caabe0 --- /dev/null +++ b/build/reports/tests/test/HH_SphoVWTI/01dkLzE0xDA/index.html @@ -0,0 +1,145 @@ + + + + + +Test results - com.livingworld.regions.RegionCoordinateTest$DimensionIdValidation + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

dimensionId validation

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
4
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
blankDimensionIdThrows()1000s100%
nullDimensionIdThrows()1000s100%
validDimensionIdAccepted()1000.001s100%
whitespaceOnlyDimensionIdThrows()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/HH_SphoVWTI/0PgZhNHkpAQ/index.html b/build/reports/tests/test/HH_SphoVWTI/0PgZhNHkpAQ/index.html new file mode 100644 index 0000000..bbdeded --- /dev/null +++ b/build/reports/tests/test/HH_SphoVWTI/0PgZhNHkpAQ/index.html @@ -0,0 +1,136 @@ + + + + + +Test results - com.livingworld.regions.RegionCoordinateTest$RegionSizeValidation + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

regionSizeChunks validation

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
3
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
negativeRegionSizeThrows()1000s100%
oneRegionSizeAccepted()1000s100%
zeroRegionSizeThrows()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/HH_SphoVWTI/KTaVipytIBc/index.html b/build/reports/tests/test/HH_SphoVWTI/KTaVipytIBc/index.html new file mode 100644 index 0000000..6349452 --- /dev/null +++ b/build/reports/tests/test/HH_SphoVWTI/KTaVipytIBc/index.html @@ -0,0 +1,145 @@ + + + + + +Test results - com.livingworld.regions.RegionCoordinateTest$FromChunkMapping + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

fromChunk coordinate mapping

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
4
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
chunkEightEightMapsToRegionOneOne()1000s100%
chunkMinusOneMinusOneMapsToRegionMinusOneMinusOne()1000s100%
chunkSevenSevenMapsToRegionZeroZero()1000s100%
chunkZeroZeroMapsToRegionZeroZero()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/HH_SphoVWTI/L31zJMjVKcM/index.html b/build/reports/tests/test/HH_SphoVWTI/L31zJMjVKcM/index.html new file mode 100644 index 0000000..9e8a635 --- /dev/null +++ b/build/reports/tests/test/HH_SphoVWTI/L31zJMjVKcM/index.html @@ -0,0 +1,136 @@ + + + + + +Test results - com.livingworld.regions.RegionCoordinateTest$StableIdTests + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

stableId

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
3
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
negativeCoordinatesInStableId()1000s100%
stableIdFormat()1000s100%
stableIdIsStable()1000.001s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/HH_SphoVWTI/f5IN-N2HxAI/index.html b/build/reports/tests/test/HH_SphoVWTI/f5IN-N2HxAI/index.html new file mode 100644 index 0000000..3e1fe9e --- /dev/null +++ b/build/reports/tests/test/HH_SphoVWTI/f5IN-N2HxAI/index.html @@ -0,0 +1,127 @@ + + + + + +Test results - com.livingworld.regions.RegionCoordinateTest$ToStringTests + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

toString

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
2
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.003s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
toStringContainsAllFields()1000.002s100%
toStringShouldNotThrow()1000.001s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/HH_SphoVWTI/g6u7VEbfR44/index.html b/build/reports/tests/test/HH_SphoVWTI/g6u7VEbfR44/index.html new file mode 100644 index 0000000..921d6fe --- /dev/null +++ b/build/reports/tests/test/HH_SphoVWTI/g6u7VEbfR44/index.html @@ -0,0 +1,145 @@ + + + + + +Test results - com.livingworld.regions.RegionCoordinateTest$CenterBlockMethods + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

centerBlockX / centerBlockZ

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
4
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
centerOfRegionMinusOne()1000s100%
centerOfRegionOne()1000s100%
centerOfRegionZero()1000s100%
invalidRegionSizeThrowsInCenterMethods()1000.001s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/HH_SphoVWTI/index.html b/build/reports/tests/test/HH_SphoVWTI/index.html new file mode 100644 index 0000000..c71335f --- /dev/null +++ b/build/reports/tests/test/HH_SphoVWTI/index.html @@ -0,0 +1,207 @@ + + + + + +Test results - com.livingworld.regions.RegionCoordinateTest + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

RegionCoordinateTest

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
35
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.015s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
+centerBlockX / centerBlockZ +com.livingworld.regions.RegionCoordinateTest$CenterBlockMethods4000.001s100%
+minChunkX / minChunkZ / maxChunkX / maxChunkZ +com.livingworld.regions.RegionCoordinateTest$ChunkBoundaryMethods4000.001s100%
+dimensionId validation +com.livingworld.regions.RegionCoordinateTest$DimensionIdValidation4000.001s100%
+fromBlock coordinate mapping +com.livingworld.regions.RegionCoordinateTest$FromBlockMapping6000.001s100%
+fromChunk coordinate mapping +com.livingworld.regions.RegionCoordinateTest$FromChunkMapping4000.001s100%
+HashMap key compatibility +com.livingworld.regions.RegionCoordinateTest$HashMapKeyTests5000.004s100%
+regionSizeChunks validation +com.livingworld.regions.RegionCoordinateTest$RegionSizeValidation3000.001s100%
+stableId +com.livingworld.regions.RegionCoordinateTest$StableIdTests3000.001s100%
+toString +com.livingworld.regions.RegionCoordinateTest$ToStringTests2000.003s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/HH_SphoVWTI/jxSl3Nv_-vA/index.html b/build/reports/tests/test/HH_SphoVWTI/jxSl3Nv_-vA/index.html new file mode 100644 index 0000000..cc78142 --- /dev/null +++ b/build/reports/tests/test/HH_SphoVWTI/jxSl3Nv_-vA/index.html @@ -0,0 +1,145 @@ + + + + + +Test results - com.livingworld.regions.RegionCoordinateTest$ChunkBoundaryMethods + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

minChunkX / minChunkZ / maxChunkX / maxChunkZ

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
4
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
invalidRegionSizeThrowsInBoundaryMethods()1000s100%
regionMinusOneBoundariesWithSizeEight()1000s100%
regionOneBoundariesWithSizeEight()1000s100%
regionZeroBoundariesWithSizeEight()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/HH_SphoVWTI/xvej3HcBxjc/index.html b/build/reports/tests/test/HH_SphoVWTI/xvej3HcBxjc/index.html new file mode 100644 index 0000000..7c86a30 --- /dev/null +++ b/build/reports/tests/test/HH_SphoVWTI/xvej3HcBxjc/index.html @@ -0,0 +1,163 @@ + + + + + +Test results - com.livingworld.regions.RegionCoordinateTest$FromBlockMapping + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

fromBlock coordinate mapping

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
6
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
block127_127MapsToRegionZeroZero()1000s100%
block128ZeroMapsToRegionOneZero()1000s100%
blockMinus128ZeroMapsToRegionMinusOneZero()1000s100%
blockMinus129ZeroMapsToRegionMinusTwoZero()1000.001s100%
blockMinusOneZeroMapsToRegionMinusOneZero()1000s100%
blockZeroZeroMapsToRegionZeroZero()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/HH_SphoVWTI/z51CUjHDBz8/index.html b/build/reports/tests/test/HH_SphoVWTI/z51CUjHDBz8/index.html new file mode 100644 index 0000000..2a06989 --- /dev/null +++ b/build/reports/tests/test/HH_SphoVWTI/z51CUjHDBz8/index.html @@ -0,0 +1,154 @@ + + + + + +Test results - com.livingworld.regions.RegionCoordinateTest$HashMapKeyTests + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

HashMap key compatibility

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
5
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.004s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
equalsAndHashCodeContract()1000s100%
notEqualToDifferentDimension()1000.002s100%
notEqualToDifferentType()1000s100%
nullSafeEquals()1000s100%
worksAsHashMapKey()1000.001s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/HZOjeN4ldAQ/bsQw9rhlQVQ.html b/build/reports/tests/test/HZOjeN4ldAQ/bsQw9rhlQVQ.html new file mode 100644 index 0000000..4346aaf --- /dev/null +++ b/build/reports/tests/test/HZOjeN4ldAQ/bsQw9rhlQVQ.html @@ -0,0 +1,114 @@ + + + + + +Test results - getPublishedEventCountIncrementsOnPublish() + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

getPublishedEventCountIncrementsOnPublish()

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
1
+

tests

+
+
+
+
1
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.003s
+

duration

+
+
+
+
+
+
0%
+

successful

+
+
+
+
+

Failure details

+ +
org.opentest4j.AssertionFailedError: expected: <1> but was: <0>
+	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
+	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
+	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
+	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:150)
+	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:145)
+	at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:531)
+	at com.livingworld.events.LivingWorldEventBusTest.getPublishedEventCountIncrementsOnPublish(LivingWorldEventBusTest.java:114)
+	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
+	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
+	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
+
+ +
+
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/HZOjeN4ldAQ/index.html b/build/reports/tests/test/HZOjeN4ldAQ/index.html new file mode 100644 index 0000000..1da7ec4 --- /dev/null +++ b/build/reports/tests/test/HZOjeN4ldAQ/index.html @@ -0,0 +1,231 @@ + + + + + +Test results - com.livingworld.events.LivingWorldEventBusTest + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

LivingWorldEventBusTest

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
9
+

tests

+
+
+
+
1
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.016s
+

duration

+
+
+
+
+
+
88%
+

successful

+
+
+
+
+ +
+

Failed

+ + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
+getPublishedEventCountIncrementsOnPublish() +1100.003s0%
+
+
+

All

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
getListenerCountReturnsCorrectNumberOfListeners()1000.005s100%
+getPublishedEventCountIncrementsOnPublish() +1100.003s0%
publishDispatchesToRegisteredListeners()1000s100%
publishNullEventThrows()1000.001s100%
publishUnknownEventTypeDoesNotCrash()1000s100%
registerAddsListenerForEventType()1000s100%
registerBlankEventTypeThrows()1000.001s100%
registerNullListenerThrows()1000s100%
registerPreservesRegistrationOrder()1000.001s100%
+
+
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/XKL-YsVnnr4/B3eFfEFBclk/index.html b/build/reports/tests/test/XKL-YsVnnr4/B3eFfEFBclk/index.html new file mode 100644 index 0000000..45b8a2e --- /dev/null +++ b/build/reports/tests/test/XKL-YsVnnr4/B3eFfEFBclk/index.html @@ -0,0 +1,172 @@ + + + + + +Test results - com.livingworld.config.SimulationConfigTest$ValidValues + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Validation with valid values

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
7
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.002s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
defaultsAreValid()1000s100%
emergencyStopEqualsMaxMilliseconds()1000s100%
emergencyStopGreaterThanMaxMilliseconds()1000s100%
minimumMaxMillisecondsPerCycle()1000.001s100%
minimumMaxRegionsPerCycle()1000s100%
minimumRegionSizeChunks()1000s100%
minimumSimulationIntervalTicks()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/XKL-YsVnnr4/BlCeWkCHYUs/index.html b/build/reports/tests/test/XKL-YsVnnr4/BlCeWkCHYUs/index.html new file mode 100644 index 0000000..ef0c6a7 --- /dev/null +++ b/build/reports/tests/test/XKL-YsVnnr4/BlCeWkCHYUs/index.html @@ -0,0 +1,199 @@ + + + + + +Test results - com.livingworld.config.SimulationConfigTest$InvalidValues + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Validation with invalid values

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
10
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.005s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
emergencyStopLessThanMaxMilliseconds()1000.001s100%
emergencyStopMessageContainsValues()1000.001s100%
negativeMaxMillisecondsPerCycle()1000.001s100%
negativeMaxRegionsPerCycle()1000s100%
negativeRegionSizeChunks()1000s100%
negativeSimulationIntervalTicks()1000s100%
zeroMaxMillisecondsPerCycle()1000s100%
zeroMaxRegionsPerCycle()1000s100%
zeroRegionSizeChunks()1000.001s100%
zeroSimulationIntervalTicks()1000.001s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/XKL-YsVnnr4/MFoGN7dpQ6s/index.html b/build/reports/tests/test/XKL-YsVnnr4/MFoGN7dpQ6s/index.html new file mode 100644 index 0000000..08b493d --- /dev/null +++ b/build/reports/tests/test/XKL-YsVnnr4/MFoGN7dpQ6s/index.html @@ -0,0 +1,172 @@ + + + + + +Test results - com.livingworld.config.SimulationConfigTest$DefaultValues + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Default values

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
7
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.002s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
defaultEmergencyStopMilliseconds()1000s100%
defaultEnableDebugCommands()1000s100%
defaultEnableProfiler()1000s100%
defaultMaxMillisecondsPerCycle()1000s100%
defaultMaxRegionsPerCycle()1000s100%
defaultRegionSizeChunks()1000s100%
defaultSimulationIntervalTicks()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/XKL-YsVnnr4/hMCtl_0FolY/index.html b/build/reports/tests/test/XKL-YsVnnr4/hMCtl_0FolY/index.html new file mode 100644 index 0000000..1150b5b --- /dev/null +++ b/build/reports/tests/test/XKL-YsVnnr4/hMCtl_0FolY/index.html @@ -0,0 +1,172 @@ + + + + + +Test results - com.livingworld.config.SimulationConfigTest$Setters + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Setters

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
7
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.002s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
setEmergencyStopMilliseconds()1000s100%
setEnableDebugCommandsFalse()1000s100%
setEnableProfilerFalse()1000s100%
setMaxMillisecondsPerCycle()1000.001s100%
setMaxRegionsPerCycle()1000s100%
setRegionSizeChunks()1000s100%
setSimulationIntervalTicks()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/XKL-YsVnnr4/index.html b/build/reports/tests/test/XKL-YsVnnr4/index.html new file mode 100644 index 0000000..abe9005 --- /dev/null +++ b/build/reports/tests/test/XKL-YsVnnr4/index.html @@ -0,0 +1,161 @@ + + + + + +Test results - com.livingworld.config.SimulationConfigTest + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

SimulationConfigTest

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
32
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.016s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
+Default values +com.livingworld.config.SimulationConfigTest$DefaultValues7000.002s100%
+Validation with invalid values +com.livingworld.config.SimulationConfigTest$InvalidValues10000.005s100%
+Setters +com.livingworld.config.SimulationConfigTest$Setters7000.002s100%
+Validation with valid values +com.livingworld.config.SimulationConfigTest$ValidValues7000.002s100%
toString should not throwtoStringShouldNotThrow()1000.003s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/__kHqSRNkSM/-h6hYru-Qpo/index.html b/build/reports/tests/test/__kHqSRNkSM/-h6hYru-Qpo/index.html new file mode 100644 index 0000000..9d246c8 --- /dev/null +++ b/build/reports/tests/test/__kHqSRNkSM/-h6hYru-Qpo/index.html @@ -0,0 +1,154 @@ + + + + + +Test results - com.livingworld.core.services.ServiceRegistryTest$NullArgumentValidation + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Null argument validation

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
5
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.002s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
findWithNullKeyThrows()1000.001s100%
getWithNullKeyThrows()1000s100%
isRegisteredWithNullKeyThrows()1000s100%
registerWithNullKeyThrows()1000.001s100%
registerWithNullServiceThrows()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/__kHqSRNkSM/1Uy2KnnwXLI/index.html b/build/reports/tests/test/__kHqSRNkSM/1Uy2KnnwXLI/index.html new file mode 100644 index 0000000..e5ca228 --- /dev/null +++ b/build/reports/tests/test/__kHqSRNkSM/1Uy2KnnwXLI/index.html @@ -0,0 +1,154 @@ + + + + + +Test results - com.livingworld.core.services.ServiceRegistryTest$RegistrationAndRetrieval + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Registration and retrieval

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
5
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
findReturnsEmptyForUnregistered()1000s100%
findReturnsPresentService()1000s100%
isRegisteredReturnsFalseForUnregistered()1000s100%
isRegisteredReturnsTrueAfterRegistration()1000s100%
registerAndGet()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/__kHqSRNkSM/CKYtEX4y5Mo/index.html b/build/reports/tests/test/__kHqSRNkSM/CKYtEX4y5Mo/index.html new file mode 100644 index 0000000..79b0073 --- /dev/null +++ b/build/reports/tests/test/__kHqSRNkSM/CKYtEX4y5Mo/index.html @@ -0,0 +1,127 @@ + + + + + +Test results - com.livingworld.core.services.ServiceRegistryTest$DuplicateRegistrationRejection + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Duplicate registration rejection

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
2
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
differentKeysDoNotConflict()1000s100%
duplicateKeyThrows()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/__kHqSRNkSM/SSBvUYwBcDk/index.html b/build/reports/tests/test/__kHqSRNkSM/SSBvUYwBcDk/index.html new file mode 100644 index 0000000..922c1fc --- /dev/null +++ b/build/reports/tests/test/__kHqSRNkSM/SSBvUYwBcDk/index.html @@ -0,0 +1,118 @@ + + + + + +Test results - com.livingworld.core.services.ServiceRegistryTest$GetMissingThrows + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

get() throws IllegalStateException for missing service

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
1
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.002s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
getUnregisteredServiceThrows()1000.001s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/__kHqSRNkSM/UtjdLSlny0U/index.html b/build/reports/tests/test/__kHqSRNkSM/UtjdLSlny0U/index.html new file mode 100644 index 0000000..2fadb0f --- /dev/null +++ b/build/reports/tests/test/__kHqSRNkSM/UtjdLSlny0U/index.html @@ -0,0 +1,154 @@ + + + + + +Test results - com.livingworld.core.services.ServiceRegistryTest$LockMechanism + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Lock mechanism

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
5
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.002s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
getAndFindWorkAfterLock()1000s100%
isLockedReturnsFalseByDefault()1000.001s100%
lockSetsLockedState()1000s100%
registerAfterLockThrows()1000s100%
registerBeforeLockThenRegisterAfterThrows()1000.001s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/__kHqSRNkSM/h-Lf2zlWtdY/index.html b/build/reports/tests/test/__kHqSRNkSM/h-Lf2zlWtdY/index.html new file mode 100644 index 0000000..f10fd40 --- /dev/null +++ b/build/reports/tests/test/__kHqSRNkSM/h-Lf2zlWtdY/index.html @@ -0,0 +1,118 @@ + + + + + +Test results - com.livingworld.core.services.ServiceRegistryTest$PlainJava + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Plain Java - no Minecraft imports

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
1
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.003s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
registryIsPureJava()1000.003s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/__kHqSRNkSM/index.html b/build/reports/tests/test/__kHqSRNkSM/index.html new file mode 100644 index 0000000..46b7443 --- /dev/null +++ b/build/reports/tests/test/__kHqSRNkSM/index.html @@ -0,0 +1,174 @@ + + + + + +Test results - com.livingworld.core.services.ServiceRegistryTest + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

ServiceRegistryTest

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
19
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.012s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
+Duplicate registration rejection +com.livingworld.core.services.ServiceRegistryTest$DuplicateRegistrationRejection2000.001s100%
+get() throws IllegalStateException for missing service +com.livingworld.core.services.ServiceRegistryTest$GetMissingThrows1000.002s100%
+Lock mechanism +com.livingworld.core.services.ServiceRegistryTest$LockMechanism5000.002s100%
+Null argument validation +com.livingworld.core.services.ServiceRegistryTest$NullArgumentValidation5000.002s100%
+Plain Java - no Minecraft imports +com.livingworld.core.services.ServiceRegistryTest$PlainJava1000.003s100%
+Registration and retrieval +com.livingworld.core.services.ServiceRegistryTest$RegistrationAndRetrieval5000.001s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/css/base-style.css b/build/reports/tests/test/css/base-style.css new file mode 100644 index 0000000..4253d9a --- /dev/null +++ b/build/reports/tests/test/css/base-style.css @@ -0,0 +1,177 @@ + +body { + margin: 0; + padding: 0; + font-family: sans-serif; + font-size: 12pt; +} + +body, a, a:visited { + color: #303030; +} + +#content { + padding: 30px 50px; +} + +#content h1 { + font-size: 160%; + margin-bottom: 10px; +} + +#footer { + margin-top: 100px; + font-size: 80%; + white-space: nowrap; +} + +#footer, #footer a { + color: #a0a0a0; +} + +#line-wrapping-toggle { + vertical-align: middle; +} + +#label-for-line-wrapping-toggle { + vertical-align: middle; +} + +ul { + margin-left: 0; +} + +h1, h2, h3 { + white-space: nowrap; +} + +h2 { + font-size: 120%; +} + +.tab-container .tab-container { + margin-left: 8px; +} + +ul.tabLinks { + padding: 0; + margin-bottom: 0; + overflow: auto; + min-width: 800px; + width: auto; + border-bottom: solid 1px #aaa; +} + +ul.tabLinks li { + float: left; + height: 100%; + list-style: none; + padding: 5px 10px; + border-radius: 7px 7px 0 0; + border: solid 1px transparent; + border-bottom: none; + margin-right: 6px; + background-color: #f0f0f0; + cursor: pointer; +} + +ul.tabLinks li.deselected > a { + color: #6d6d6d; +} + +ul.tabLinks li:hover { + background-color: #fafafa; +} + +ul.tabLinks li.selected { + background-color: #c5f0f5; + border-color: #aaa; +} + +ul.tabLinks a { + font-size: 120%; + display: block; + outline: none; + text-decoration: none; + margin: 0; + padding: 0; +} + +ul.tabLinks li h2 { + margin: 0; + padding: 0; +} + +div.tab { +} + +div.selected { + display: block; +} + +div.deselected { + display: none; +} + +div.tab table { + min-width: 350px; + width: auto; + border-collapse: collapse; +} + +div.tab th, div.tab table { + border-bottom: solid 1px #d0d0d0; +} + +div.tab th { + text-align: left; + white-space: nowrap; + padding-left: 6em; +} + +div.tab th:first-child { + padding-left: 0; +} + +div.tab td { + white-space: nowrap; + padding-left: 6em; + padding-top: 5px; + padding-bottom: 5px; +} + +div.tab td:first-child { + padding-left: 0; +} + +div.tab td.numeric, div.tab th.numeric { + text-align: right; +} + +span.code { + display: block; + margin-top: 0; + margin-bottom: 1em; + position: relative; +} + +span.code pre { + font-size: 11pt; + padding: 10px; + margin: 0; + background-color: #f7f7f7; + border: solid 1px #d0d0d0; + min-width: 700px; + width: auto; + overflow: auto; +} + +span.wrapped pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: break-all; +} + +label.hidden { + display: none; +} diff --git a/build/reports/tests/test/css/style.css b/build/reports/tests/test/css/style.css new file mode 100644 index 0000000..0c644cb --- /dev/null +++ b/build/reports/tests/test/css/style.css @@ -0,0 +1,150 @@ +.summary { + margin-top: 30px; + margin-bottom: 40px; +} + +.summary table { + border-collapse: collapse; +} + +.summary td { + vertical-align: top; +} + +div.tab table.test-results td { + padding-left: 1em; +} + +div.tab table.test-results th { + padding-top: 0.5em; + padding-left: 1em; +} + +.breadcrumbs, .breadcrumbs a { + color: #606060; +} + +.infoBox { + width: 110px; + padding-top: 15px; + padding-bottom: 15px; + text-align: center; +} + +.infoBox p { + margin: 0; +} + +.counter, .percent { + font-size: 120%; + font-weight: bold; + margin-bottom: 8px; +} + +.duration { + width: 125px; +} + +.successRate, .summaryGroup { + border: solid 2px #d0d0d0; + -moz-border-radius: 10px; + border-radius: 10px; +} + +.successRate { + width: 140px; + margin-left: 35px; +} + +.successRate .percent { + font-size: 180%; +} + +.success, .success a { + color: #008000; +} + +div.success, .successRate.success { + background-color: #bbd9bb; + border-color: #008000; +} + +.failures, .failures a { + color: #b60808; +} + +.skipped, .skipped a { + color: #c09853; +} + +div.failures, .successRate.failures { + background-color: #ecdada; + border-color: #b60808; +} + +ul.linkList { + padding-left: 0; +} + +ul.linkList li { + list-style: none; + margin-bottom: 5px; +} + +div.metadata td:first-child { + padding-left: 5px; +} + +div.metadata td { + padding-left: 5px; +} + +.metadata tr.odd { + background-color: #f7f7f7; + border: solid 1px #d0d0d0; +} + +.metadata tr.even { + border: solid 1px #d0d0d0; +} + +.metadata th, .metadata td { + padding: 5px; + text-align: left; +} + +.metadata a { + color: blue; +} + +.metadata .unrenderable { + color: darkred; +} + +.clipboard-copy-btn { + position: absolute; + top: 8px; + right: 8px; + padding: 4px 8px; + font-size: 0.9em; + cursor: pointer; +} + +.successGroup::before { + content: "\23FA"; + margin-right: 8px; + color: #008000; + display: inline-block; +} +.failureGroup::before { + content: "\2297"; + margin-right: 8px; + color: #b60808; + display: inline-block; +} +.skippedGroup::before { + content: "\2296"; + margin-right: 8px; + color: #c09853; + display: inline-block; +} diff --git a/build/reports/tests/test/f7r_QS73u_E/68xXvWTyhcA/index.html b/build/reports/tests/test/f7r_QS73u_E/68xXvWTyhcA/index.html new file mode 100644 index 0000000..43a0a74 --- /dev/null +++ b/build/reports/tests/test/f7r_QS73u_E/68xXvWTyhcA/index.html @@ -0,0 +1,163 @@ + + + + + +Test results - com.livingworld.regions.RegionLifecycleControllerTest$DisallowedTransitions + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Disallowed transitions

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
6
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.002s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
ACTIVE -> LOADING is not allowedactiveToLoading()1000.001s100%
ACTIVE -> SAVING is not allowedactiveToSaving()1000s100%
FAILED -> UNLOADED is not allowedfailedToUnloaded()1000s100%
All invalid transitions throw IllegalStateException on transition()invalidTransitionsThrowException()1000s100%
Same state is not allowed (no self-transitions)sameStateNotAllowed()1000s100%
UNLOADED -> ACTIVE is not allowedunloadedToActive()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/f7r_QS73u_E/8ds9T0PzWVw/index.html b/build/reports/tests/test/f7r_QS73u_E/8ds9T0PzWVw/index.html new file mode 100644 index 0000000..a96ccad --- /dev/null +++ b/build/reports/tests/test/f7r_QS73u_E/8ds9T0PzWVw/index.html @@ -0,0 +1,199 @@ + + + + + +Test results - com.livingworld.regions.RegionLifecycleControllerTest$AllowedTransitions + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Allowed transitions

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
10
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
ACTIVE -> DIRTY is allowedactiveToDirty()1000s100%
ACTIVE -> UNLOADING is allowedactiveToUnloading()1000s100%
DIRTY -> SAVING is alloweddirtyToSaving()1000s100%
FAILED -> LOADING is allowedfailedToLoading()1000s100%
LOADING -> ACTIVE is allowedloadingToActive()1000s100%
LOADING -> FAILED is allowedloadingToFailed()1000s100%
SAVING -> ACTIVE is allowedsavingToActive()1000s100%
SAVING -> FAILED is allowedsavingToFailed()1000s100%
UNLOADED -> LOADING is allowedunloadedToLoading()1000s100%
UNLOADING -> UNLOADED is allowedunloadingToUnloaded()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/f7r_QS73u_E/f29JMpxV2Xc/index.html b/build/reports/tests/test/f7r_QS73u_E/f29JMpxV2Xc/index.html new file mode 100644 index 0000000..eccff3e --- /dev/null +++ b/build/reports/tests/test/f7r_QS73u_E/f29JMpxV2Xc/index.html @@ -0,0 +1,145 @@ + + + + + +Test results - com.livingworld.regions.RegionLifecycleControllerTest$TransitionSideEffects + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

transition() side effects

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
4
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.002s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
transition() allows FAILED -> LOADING recoveryfailedToLoadingRecovery()1000s100%
transition() updates state through full load-save-unload cyclefullCycleTransition()1000s100%
transition() allows SAVING -> FAILED error pathsavingToFailedErrorPath()1000s100%
transition() updates lifecycle state and marks dirtytransitionUpdatesStateAndMarksDirty()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/f7r_QS73u_E/index.html b/build/reports/tests/test/f7r_QS73u_E/index.html new file mode 100644 index 0000000..9a59d8a --- /dev/null +++ b/build/reports/tests/test/f7r_QS73u_E/index.html @@ -0,0 +1,152 @@ + + + + + +Test results - com.livingworld.regions.RegionLifecycleControllerTest + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

RegionLifecycleControllerTest

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
24
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.006s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
+Allowed transitions +com.livingworld.regions.RegionLifecycleControllerTest$AllowedTransitions10000.001s100%
+Disallowed transitions +com.livingworld.regions.RegionLifecycleControllerTest$DisallowedTransitions6000.002s100%
+Null handling +com.livingworld.regions.RegionLifecycleControllerTest$NullHandling4000.001s100%
+transition() side effects +com.livingworld.regions.RegionLifecycleControllerTest$TransitionSideEffects4000.002s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/f7r_QS73u_E/vZlBvqJjNmY/index.html b/build/reports/tests/test/f7r_QS73u_E/vZlBvqJjNmY/index.html new file mode 100644 index 0000000..4e3e612 --- /dev/null +++ b/build/reports/tests/test/f7r_QS73u_E/vZlBvqJjNmY/index.html @@ -0,0 +1,145 @@ + + + + + +Test results - com.livingworld.regions.RegionLifecycleControllerTest$NullHandling + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

Null handling

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
4
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.001s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
canTransition throws IllegalArgumentException for null fromcanTransitionNullFrom()1000s100%
canTransition throws IllegalArgumentException for null tocanTransitionNullTo()1000s100%
transition throws IllegalArgumentException for null regiontransitionNullRegion()1000s100%
transition throws IllegalArgumentException for null targettransitionNullTarget()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/h1ZmsUomSQE/index.html b/build/reports/tests/test/h1ZmsUomSQE/index.html new file mode 100644 index 0000000..1040fb5 --- /dev/null +++ b/build/reports/tests/test/h1ZmsUomSQE/index.html @@ -0,0 +1,225 @@ + + + + + +Test results - com.livingworld.regions.RegionFactoryTest + + + + + +
+ +
+ +
+

Gradle Test Run :test

+

RegionFactoryTest

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
13
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.005s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildTestsFailuresSkippedDurationSuccess rate
createNewRegionAssignsRandomUUID()1000.001s100%
createNewRegionHasDefaultFlags()1000.001s100%
createNewRegionHasDefaultMetrics()1000s100%
createNewRegionHasEmptyModuleData()1000s100%
createNewRegionIsDirty()1000s100%
createNewRegionReturnsValidRegion()1000s100%
createNewRegionSetsCreatedAtSimulationTick()1000.001s100%
createNewRegionSetsLastUpdatedSimulationTick()1000s100%
createNewRegionStartsActive()1000s100%
createNewRegionUsesArgumentCoordinate()1000s100%
createNewRegionUUIDIsNotNull()1000s100%
createNewRegionWithNullCoordinateThrows()1000s100%
createNewRegionWorksForDifferentDimensions()1000s100%
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/index.html b/build/reports/tests/test/index.html new file mode 100644 index 0000000..b69fbb4 --- /dev/null +++ b/build/reports/tests/test/index.html @@ -0,0 +1,233 @@ + + + + + +Test results - All Results + + + + + +
+

All Results

+
+ +
+

Gradle Test Run :test

+

Gradle Test Run :test

+
+ +
+

summary

+
+
+ + + + + +
+
+ + + + + + + +
+
+
182
+

tests

+
+
+
+
1
+

failures

+
+
+
+
0
+

skipped

+
+
+
+
0.366s
+

duration

+
+
+
+
+
+
99%
+

successful

+
+
+
+
+ +
+

Failed

+ + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
+LivingWorldEventBusTest +com.livingworld.events.LivingWorldEventBusTest9100.016s88%
+
+
+

All

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChildNameTestsFailuresSkippedDurationSuccess rate
+SimulationConfigTest +com.livingworld.config.SimulationConfigTest32000.016s100%
+ServiceRegistryTest +com.livingworld.core.services.ServiceRegistryTest19000.012s100%
+LivingWorldEventBusTest +com.livingworld.events.LivingWorldEventBusTest9100.016s88%
+RegionCoordinateTest +com.livingworld.regions.RegionCoordinateTest35000.015s100%
+RegionFactoryTest +com.livingworld.regions.RegionFactoryTest13000.005s100%
+RegionFlagsTest +com.livingworld.regions.RegionFlagsTest14000.004s100%
+RegionLifecycleControllerTest +com.livingworld.regions.RegionLifecycleControllerTest24000.006s100%
+RegionMetricsTest +com.livingworld.regions.RegionMetricsTest36000.009s100%
+
+
+
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/js/report.js b/build/reports/tests/test/js/report.js new file mode 100644 index 0000000..3c37c39 --- /dev/null +++ b/build/reports/tests/test/js/report.js @@ -0,0 +1,228 @@ +(function (window, document) { + "use strict"; + + function changeElementClass(element, classValue) { + if (element.getAttribute("className")) { + element.setAttribute("className", classValue); + } else { + element.setAttribute("class", classValue); + } + } + + function getClassAttribute(element) { + if (element.getAttribute("className")) { + return element.getAttribute("className"); + } else { + return element.getAttribute("class"); + } + } + + function addClass(element, classValue) { + changeElementClass(element, getClassAttribute(element) + " " + classValue); + } + + function removeClass(element, classValue) { + changeElementClass(element, getClassAttribute(element).replace(classValue, "")); + } + + function getCheckBox() { + return document.getElementById("line-wrapping-toggle"); + } + + function getLabelForCheckBox() { + return document.getElementById("label-for-line-wrapping-toggle"); + } + + function findCodeBlocks() { + const codeBlocks = []; + const tabContainers = getTabContainers(); + for (let i = 0; i < tabContainers.length; i++) { + const spans = tabContainers[i].getElementsByTagName("span"); + for (let i = 0; i < spans.length; ++i) { + if (spans[i].className.indexOf("code") >= 0) { + codeBlocks.push(spans[i]); + } + } + } + return codeBlocks; + } + + function forAllCodeBlocks(operation) { + const codeBlocks = findCodeBlocks(); + + for (let i = 0; i < codeBlocks.length; ++i) { + operation(codeBlocks[i], "wrapped"); + } + } + + function toggleLineWrapping() { + const checkBox = getCheckBox(); + + if (checkBox.checked) { + forAllCodeBlocks(addClass); + } else { + forAllCodeBlocks(removeClass); + } + } + + function initClipboardCopyButton() { + document.querySelectorAll(".clipboard-copy-btn").forEach((button) => { + const copyElementId = button.getAttribute("data-copy-element-id"); + const elementWithCodeToSelect = document.getElementById(copyElementId); + + button.addEventListener("click", () => { + const text = elementWithCodeToSelect.innerText.trim(); + navigator.clipboard + .writeText(text) + .then(() => { + button.textContent = "Copied!"; + setTimeout(() => { + button.textContent = "Copy"; + }, 1500); + }) + .catch((err) => { + alert("Failed to copy to the clipboard: '" + err.message + "'. Check JavaScript console for more details.") + console.warn("Failed to copy to the clipboard", err); + }); + }); + }); + } + + function initControls() { + if (findCodeBlocks().length > 0) { + const checkBox = getCheckBox(); + const label = getLabelForCheckBox(); + + checkBox.onclick = toggleLineWrapping; + checkBox.checked = false; + + removeClass(label, "hidden"); + } + + initClipboardCopyButton() + } + + class TabManager { + baseId; + tabs; + titles; + headers; + + constructor(baseId, tabs, titles, headers) { + this.baseId = baseId; + this.tabs = tabs; + this.titles = titles; + this.headers = headers; + this.init(); + } + + init() { + for (let i = 0; i < this.headers.length; i++) { + const header = this.headers[i]; + header.onclick = () => { + this.select(i); + return false; + }; + } + } + + select(i) { + this.deselectAll(); + + changeElementClass(this.tabs[i], "tab selected"); + changeElementClass(this.headers[i], "selected"); + } + + deselectAll() { + for (let i = 0; i < this.tabs.length; i++) { + changeElementClass(this.tabs[i], "tab deselected"); + changeElementClass(this.headers[i], "deselected"); + } + } + } + + function getTabContainers() { + const tabContainers = Array.from(document.getElementsByClassName("tab-container")); + + // Used by existing TabbedPageRenderer users, which have not adjusted to use TabsRenderer yet. + const legacyContainer = document.getElementById("tabs"); + if (legacyContainer) { + tabContainers.push(legacyContainer); + } + + return tabContainers; + } + + function initTabs() { + let tabGroups = 0; + + function createTab(num, container) { + const tabElems = findTabs(container); + const tabManager = new TabManager("tabs" + num, tabElems, findTitles(tabElems), findHeaders(container)); + tabManager.select(0); + } + + const tabContainers = getTabContainers(); + + for (let i = 0; i < tabContainers.length; i++) { + createTab(tabGroups, tabContainers[i]); + tabGroups++; + } + + return true; + } + + function findTabs(container) { + return findChildElements(container, "DIV", "tab"); + } + + function findHeaders(container) { + const owner = findChildElements(container, "UL", "tabLinks"); + return findChildElements(owner[0], "LI", null); + } + + function findTitles(tabs) { + const titles = []; + + for (let i = 0; i < tabs.length; i++) { + const tab = tabs[i]; + const header = findChildElements(tab, "H2", null)[0]; + + header.parentNode.removeChild(header); + + if (header.innerText) { + titles.push(header.innerText); + } else { + titles.push(header.textContent); + } + } + + return titles; + } + + function findChildElements(container, name, targetClass) { + const elements = []; + const children = container.childNodes; + + for (let i = 0; i < children.length; i++) { + const child = children.item(i); + + if (child.nodeType === 1 && child.nodeName === name) { + if (targetClass && child.className.indexOf(targetClass) < 0) { + continue; + } + + elements.push(child); + } + } + + return elements; + } + + // Entry point. + + window.onload = function() { + initTabs(); + initControls(); + }; +} (window, window.document)); diff --git a/build/test-results/test/TEST-com.livingworld.config.SimulationConfigTest$DefaultValues.xml b/build/test-results/test/TEST-com.livingworld.config.SimulationConfigTest$DefaultValues.xml new file mode 100644 index 0000000..c975b81 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.config.SimulationConfigTest$DefaultValues.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.config.SimulationConfigTest$InvalidValues.xml b/build/test-results/test/TEST-com.livingworld.config.SimulationConfigTest$InvalidValues.xml new file mode 100644 index 0000000..307c1cd --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.config.SimulationConfigTest$InvalidValues.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.config.SimulationConfigTest$Setters.xml b/build/test-results/test/TEST-com.livingworld.config.SimulationConfigTest$Setters.xml new file mode 100644 index 0000000..534429c --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.config.SimulationConfigTest$Setters.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.config.SimulationConfigTest$ValidValues.xml b/build/test-results/test/TEST-com.livingworld.config.SimulationConfigTest$ValidValues.xml new file mode 100644 index 0000000..1bec022 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.config.SimulationConfigTest$ValidValues.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.config.SimulationConfigTest.xml b/build/test-results/test/TEST-com.livingworld.config.SimulationConfigTest.xml new file mode 100644 index 0000000..4bb6934 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.config.SimulationConfigTest.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$DuplicateRegistrationRejection.xml b/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$DuplicateRegistrationRejection.xml new file mode 100644 index 0000000..29540d6 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$DuplicateRegistrationRejection.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$GetMissingThrows.xml b/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$GetMissingThrows.xml new file mode 100644 index 0000000..2c71267 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$GetMissingThrows.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$LockMechanism.xml b/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$LockMechanism.xml new file mode 100644 index 0000000..239d6e4 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$LockMechanism.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$NullArgumentValidation.xml b/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$NullArgumentValidation.xml new file mode 100644 index 0000000..b824a09 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$NullArgumentValidation.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$PlainJava.xml b/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$PlainJava.xml new file mode 100644 index 0000000..2e03714 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$PlainJava.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$RegistrationAndRetrieval.xml b/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$RegistrationAndRetrieval.xml new file mode 100644 index 0000000..6f913d1 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.core.services.ServiceRegistryTest$RegistrationAndRetrieval.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.events.LivingWorldEventBusTest.xml b/build/test-results/test/TEST-com.livingworld.events.LivingWorldEventBusTest.xml new file mode 100644 index 0000000..0d3d11c --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.events.LivingWorldEventBusTest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + org.opentest4j.AssertionFailedError: expected: <1> but was: <0> + at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151) + at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132) + at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197) + at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:150) + at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:145) + at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:531) + at com.livingworld.events.LivingWorldEventBusTest.getPublishedEventCountIncrementsOnPublish(LivingWorldEventBusTest.java:114) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$CenterBlockMethods.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$CenterBlockMethods.xml new file mode 100644 index 0000000..6968cc0 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$CenterBlockMethods.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$ChunkBoundaryMethods.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$ChunkBoundaryMethods.xml new file mode 100644 index 0000000..803b44a --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$ChunkBoundaryMethods.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$DimensionIdValidation.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$DimensionIdValidation.xml new file mode 100644 index 0000000..9fc6bcd --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$DimensionIdValidation.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$FromBlockMapping.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$FromBlockMapping.xml new file mode 100644 index 0000000..adaa930 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$FromBlockMapping.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$FromChunkMapping.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$FromChunkMapping.xml new file mode 100644 index 0000000..fd7469b --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$FromChunkMapping.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$HashMapKeyTests.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$HashMapKeyTests.xml new file mode 100644 index 0000000..8488628 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$HashMapKeyTests.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$RegionSizeValidation.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$RegionSizeValidation.xml new file mode 100644 index 0000000..f452510 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$RegionSizeValidation.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$StableIdTests.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$StableIdTests.xml new file mode 100644 index 0000000..3f0c318 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$StableIdTests.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$ToStringTests.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$ToStringTests.xml new file mode 100644 index 0000000..0bd7d79 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionCoordinateTest$ToStringTests.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionFactoryTest.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionFactoryTest.xml new file mode 100644 index 0000000..e79de06 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionFactoryTest.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionFlagsTest$ClearTransientFlagTests.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionFlagsTest$ClearTransientFlagTests.xml new file mode 100644 index 0000000..9f95322 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionFlagsTest$ClearTransientFlagTests.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionFlagsTest$CopyTests.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionFlagsTest$CopyTests.xml new file mode 100644 index 0000000..8af521f --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionFlagsTest$CopyTests.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionFlagsTest$DefaultValues.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionFlagsTest$DefaultValues.xml new file mode 100644 index 0000000..255c2b7 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionFlagsTest$DefaultValues.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionFlagsTest$GetterSetterTests.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionFlagsTest$GetterSetterTests.xml new file mode 100644 index 0000000..2e803db --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionFlagsTest$GetterSetterTests.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionFlagsTest$ToStringTests.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionFlagsTest$ToStringTests.xml new file mode 100644 index 0000000..a87b686 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionFlagsTest$ToStringTests.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionLifecycleControllerTest$AllowedTransitions.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionLifecycleControllerTest$AllowedTransitions.xml new file mode 100644 index 0000000..92b3c97 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionLifecycleControllerTest$AllowedTransitions.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionLifecycleControllerTest$DisallowedTransitions.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionLifecycleControllerTest$DisallowedTransitions.xml new file mode 100644 index 0000000..fe144f5 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionLifecycleControllerTest$DisallowedTransitions.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionLifecycleControllerTest$NullHandling.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionLifecycleControllerTest$NullHandling.xml new file mode 100644 index 0000000..fda1f50 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionLifecycleControllerTest$NullHandling.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionLifecycleControllerTest$TransitionSideEffects.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionLifecycleControllerTest$TransitionSideEffects.xml new file mode 100644 index 0000000..8687af5 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionLifecycleControllerTest$TransitionSideEffects.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$ApplyDeltaTests.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$ApplyDeltaTests.xml new file mode 100644 index 0000000..59a6f25 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$ApplyDeltaTests.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$ConstantsTests.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$ConstantsTests.xml new file mode 100644 index 0000000..2a7c1bc --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$ConstantsTests.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$CopyTests.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$CopyTests.xml new file mode 100644 index 0000000..33c5986 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$CopyTests.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$DefaultValues.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$DefaultValues.xml new file mode 100644 index 0000000..921d350 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$DefaultValues.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$GetterSetterClamping.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$GetterSetterClamping.xml new file mode 100644 index 0000000..029ea4a --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$GetterSetterClamping.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$NormalizeTests.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$NormalizeTests.xml new file mode 100644 index 0000000..e848348 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$NormalizeTests.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$ToStringTests.xml b/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$ToStringTests.xml new file mode 100644 index 0000000..f671972 --- /dev/null +++ b/build/test-results/test/TEST-com.livingworld.regions.RegionMetricsTest$ToStringTests.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build/test-results/test/binary/output-events.bin b/build/test-results/test/binary/output-events.bin new file mode 100644 index 0000000..e69de29 diff --git a/build/test-results/test/binary/results-generic.bin b/build/test-results/test/binary/results-generic.bin new file mode 100644 index 0000000..938ea77 Binary files /dev/null and b/build/test-results/test/binary/results-generic.bin differ diff --git a/build/tmp/compileJava/compileTransaction/stash-dir/ConfigService.class.uniqueId2 b/build/tmp/compileJava/compileTransaction/stash-dir/ConfigService.class.uniqueId2 new file mode 100644 index 0000000..be797df Binary files /dev/null and b/build/tmp/compileJava/compileTransaction/stash-dir/ConfigService.class.uniqueId2 differ diff --git a/build/tmp/compileJava/compileTransaction/stash-dir/CoreServices.class.uniqueId3 b/build/tmp/compileJava/compileTransaction/stash-dir/CoreServices.class.uniqueId3 new file mode 100644 index 0000000..658d8e3 Binary files /dev/null and b/build/tmp/compileJava/compileTransaction/stash-dir/CoreServices.class.uniqueId3 differ diff --git a/build/tmp/compileJava/compileTransaction/stash-dir/DefaultConfigService.class.uniqueId1 b/build/tmp/compileJava/compileTransaction/stash-dir/DefaultConfigService.class.uniqueId1 new file mode 100644 index 0000000..c15b192 Binary files /dev/null and b/build/tmp/compileJava/compileTransaction/stash-dir/DefaultConfigService.class.uniqueId1 differ diff --git a/build/tmp/compileJava/compileTransaction/stash-dir/SimulationConfig.class.uniqueId0 b/build/tmp/compileJava/compileTransaction/stash-dir/SimulationConfig.class.uniqueId0 new file mode 100644 index 0000000..bd2c74f Binary files /dev/null and b/build/tmp/compileJava/compileTransaction/stash-dir/SimulationConfig.class.uniqueId0 differ diff --git a/build/tmp/compileJava/previous-compilation-data.bin b/build/tmp/compileJava/previous-compilation-data.bin new file mode 100644 index 0000000..92b3aea Binary files /dev/null and b/build/tmp/compileJava/previous-compilation-data.bin differ diff --git a/build/tmp/compileTestJava/compileTransaction/stash-dir/LivingWorldEventBusTest.class.uniqueId0 b/build/tmp/compileTestJava/compileTransaction/stash-dir/LivingWorldEventBusTest.class.uniqueId0 new file mode 100644 index 0000000..c5c9404 Binary files /dev/null and b/build/tmp/compileTestJava/compileTransaction/stash-dir/LivingWorldEventBusTest.class.uniqueId0 differ diff --git a/build/tmp/compileTestJava/previous-compilation-data.bin b/build/tmp/compileTestJava/previous-compilation-data.bin new file mode 100644 index 0000000..8ed5343 Binary files /dev/null and b/build/tmp/compileTestJava/previous-compilation-data.bin differ diff --git a/build/tmp/createMinecraftArtifacts/nfrt-problem-report.json b/build/tmp/createMinecraftArtifacts/nfrt-problem-report.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/build/tmp/createMinecraftArtifacts/nfrt-problem-report.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/build/tmp/createMinecraftArtifacts/nfrt_artifact_manifest.properties b/build/tmp/createMinecraftArtifacts/nfrt_artifact_manifest.properties new file mode 100644 index 0000000..bb31dfb --- /dev/null +++ b/build/tmp/createMinecraftArtifacts/nfrt_artifact_manifest.properties @@ -0,0 +1,124 @@ +# +#Sat Jun 06 22:53:44 BST 2026 +ca.weblite\:java-objc-bridge\:1.1=/Users/george/.gradle/caches/modules-2/files-2.1/ca.weblite/java-objc-bridge/1.1/1227f9e0666314f9de41477e3ec277e542ed7f7b/java-objc-bridge-1.1.jar +com.electronwill.night-config\:core\:3.8.2=/Users/george/.gradle/caches/modules-2/files-2.1/com.electronwill.night-config/core/3.8.2/94eee571e1524129804d08c75f6416a3be380ad4/core-3.8.2.jar +com.electronwill.night-config\:toml\:3.8.2=/Users/george/.gradle/caches/modules-2/files-2.1/com.electronwill.night-config/toml/3.8.2/c128842f759692f613ea6c3ef0ef052273bbb38c/toml-3.8.2.jar +com.github.oshi\:oshi-core\:6.4.10=/Users/george/.gradle/caches/modules-2/files-2.1/com.github.oshi/oshi-core/6.4.10/b1d8ab82d11d92fd639b56d639f8f46f739dd5fa/oshi-core-6.4.10.jar +com.google.code.findbugs\:jsr305\:3.0.2=/Users/george/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/3.0.2/25ea2e8b0c338a877313bd4672d3fe056ea78f0d/jsr305-3.0.2.jar +com.google.code.gson\:gson\:2.10.1=/Users/george/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.10.1/b3add478d4382b78ea20b1671390a858002feb6c/gson-2.10.1.jar +com.google.errorprone\:error_prone_annotations\:2.18.0=/Users/george/.gradle/caches/modules-2/files-2.1/com.google.errorprone/error_prone_annotations/2.18.0/89b684257096f548fa39a7df9fdaa409d4d4df91/error_prone_annotations-2.18.0.jar +com.google.guava\:failureaccess\:1.0.1=/Users/george/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0.1/1dcf1de382a0bf95a3d8b0849546c88bac1292c9/failureaccess-1.0.1.jar +com.google.guava\:guava\:32.1.2-jre=/Users/george/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/32.1.2-jre/5e64ec7e056456bef3a4bc4c6fdaef71e8ab6318/guava-32.1.2-jre.jar +com.google.guava\:listenablefuture\:9999.0-empty-to-avoid-conflict-with-guava=/Users/george/.gradle/caches/modules-2/files-2.1/com.google.guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/b421526c5f297295adef1c886e5246c39d4ac629/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar +com.google.j2objc\:j2objc-annotations\:2.8=/Users/george/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/2.8/c85270e307e7b822f1086b93689124b89768e273/j2objc-annotations-2.8.jar +com.ibm.icu\:icu4j\:73.2=/Users/george/.gradle/caches/modules-2/files-2.1/com.ibm.icu/icu4j/73.2/61ad4ef7f9131fcf6d25c34b817f90d6da06c9e9/icu4j-73.2.jar +com.machinezoo.noexception\:noexception\:1.7.1=/Users/george/.gradle/caches/modules-2/files-2.1/com.machinezoo.noexception/noexception/1.7.1/b65330c98e38a1f915fa54a6e5eca496505e3f0a/noexception-1.7.1.jar +com.mojang\:authlib\:6.0.54=/Users/george/.gradle/caches/modules-2/files-2.1/com.mojang/authlib/6.0.54/de8bc95660e1b2fe8793fd427a7a10dcec5b3ea7/authlib-6.0.54.jar +com.mojang\:blocklist\:1.0.10=/Users/george/.gradle/caches/modules-2/files-2.1/com.mojang/blocklist/1.0.10/5c685c5ffa94c4cd39496c7184c1d122e515ecef/blocklist-1.0.10.jar +com.mojang\:brigadier\:1.3.10=/Users/george/.gradle/caches/modules-2/files-2.1/com.mojang/brigadier/1.3.10/d15b53a14cf20fdcaa98f731af5dda654452c010/brigadier-1.3.10.jar +com.mojang\:datafixerupper\:8.0.16=/Users/george/.gradle/caches/modules-2/files-2.1/com.mojang/datafixerupper/8.0.16/67d4de6d7f95d89bcf5862995fb854ebaec02a34/datafixerupper-8.0.16.jar +com.mojang\:logging\:1.2.7=/Users/george/.gradle/caches/modules-2/files-2.1/com.mojang/logging/1.2.7/24cb95ffb0e3433fd6e844c04e68009e504ca1c0/logging-1.2.7.jar +com.mojang\:patchy\:2.2.10=/Users/george/.gradle/caches/modules-2/files-2.1/com.mojang/patchy/2.2.10/da05971b07cbb379d002cf7eaec6a2048211fefc/patchy-2.2.10.jar +com.mojang\:text2speech\:1.17.9=/Users/george/.gradle/caches/modules-2/files-2.1/com.mojang/text2speech/1.17.9/3cad216e3a7f0c19b4b394388bc9ffc446f13b14/text2speech-1.17.9.jar +commons-codec\:commons-codec\:1.16.0=/Users/george/.gradle/caches/modules-2/files-2.1/commons-codec/commons-codec/1.16.0/4e3eb3d79888d76b54e28b350915b5dc3919c9de/commons-codec-1.16.0.jar +commons-io\:commons-io\:2.15.1=/Users/george/.gradle/caches/modules-2/files-2.1/commons-io/commons-io/2.15.1/f11560da189ab563a5c8e351941415430e9304ea/commons-io-2.15.1.jar +commons-logging\:commons-logging\:1.2=/Users/george/.gradle/caches/modules-2/files-2.1/commons-logging/commons-logging/1.2/4bfc12adfe4842bf07b657f0369c4cb522955686/commons-logging-1.2.jar +cpw.mods\:bootstraplauncher\:2.0.2=/Users/george/.gradle/caches/modules-2/files-2.1/cpw.mods/bootstraplauncher/2.0.2/1a2d076cbc33b0520cbacd591224427b2a20047d/bootstraplauncher-2.0.2.jar +cpw.mods\:modlauncher\:11.0.4=/Users/george/.gradle/caches/modules-2/files-2.1/cpw.mods/modlauncher/11.0.4/6619d4812a3c092310d521ebc4c9503727563df7/modlauncher-11.0.4.jar +cpw.mods\:securejarhandler\:3.0.8=/Users/george/.gradle/caches/modules-2/files-2.1/cpw.mods/securejarhandler/3.0.8/c0ef95cecd8699a0449053ac7d9c160748d902cd/securejarhandler-3.0.8.jar +io.codechicken\:DiffPatch\:2.0.0.36\:all=/Users/george/.gradle/caches/modules-2/files-2.1/io.codechicken/DiffPatch/2.0.0.36/5b32f9baa0c0371c0dc80a3b88794960af0c6b44/DiffPatch-2.0.0.36-all.jar +io.github.llamalad7\:mixinextras-neoforge\:0.4.1=/Users/george/.gradle/caches/modules-2/files-2.1/io.github.llamalad7/mixinextras-neoforge/0.4.1/eff97772ecd0dcf8e52cdbafd3b3ccec425f8314/mixinextras-neoforge-0.4.1.jar +io.netty\:netty-buffer\:4.1.97.Final=/Users/george/.gradle/caches/modules-2/files-2.1/io.netty/netty-buffer/4.1.97.Final/f8f3d8644afa5e6e1a40a3a6aeb9d9aa970ecb4f/netty-buffer-4.1.97.Final.jar +io.netty\:netty-codec\:4.1.97.Final=/Users/george/.gradle/caches/modules-2/files-2.1/io.netty/netty-codec/4.1.97.Final/384ba4d75670befbedb45c4d3b497a93639c206d/netty-codec-4.1.97.Final.jar +io.netty\:netty-common\:4.1.97.Final=/Users/george/.gradle/caches/modules-2/files-2.1/io.netty/netty-common/4.1.97.Final/7cceacaf11df8dc63f23d0fb58e9d4640fc88404/netty-common-4.1.97.Final.jar +io.netty\:netty-handler\:4.1.97.Final=/Users/george/.gradle/caches/modules-2/files-2.1/io.netty/netty-handler/4.1.97.Final/abb86c6906bf512bf2b797a41cd7d2e8d3cd7c36/netty-handler-4.1.97.Final.jar +io.netty\:netty-resolver\:4.1.97.Final=/Users/george/.gradle/caches/modules-2/files-2.1/io.netty/netty-resolver/4.1.97.Final/cec8348108dc76c47cf87c669d514be52c922144/netty-resolver-4.1.97.Final.jar +io.netty\:netty-transport-classes-epoll\:4.1.97.Final=/Users/george/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport-classes-epoll/4.1.97.Final/795da37ded759e862457a82d9d92c4d39ce8ecee/netty-transport-classes-epoll-4.1.97.Final.jar +io.netty\:netty-transport-native-unix-common\:4.1.97.Final=/Users/george/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport-native-unix-common/4.1.97.Final/d469d84265ab70095b01b40886cabdd433b6e664/netty-transport-native-unix-common-4.1.97.Final.jar +io.netty\:netty-transport\:4.1.97.Final=/Users/george/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport/4.1.97.Final/f37380d23c9bb079bc702910833b2fd532c9abd0/netty-transport-4.1.97.Final.jar +it.unimi.dsi\:fastutil\:8.3.1=/Users/george/.gradle/caches/modules-2/files-2.1/it.unimi.dsi/fastutil/8.3.1/ff33b340d60b81f4c1015fa8008bb30ff0a0d53b/fastutil-8.3.1.jar +it.unimi.dsi\:fastutil\:8.5.12=/Users/george/.gradle/caches/modules-2/files-2.1/it.unimi.dsi/fastutil/8.5.12/c24946d46824bd528054bface3231d2ecb7e95e8/fastutil-8.5.12.jar +net.covers1624\:Quack\:0.4.10.101=/Users/george/.gradle/caches/modules-2/files-2.1/net.covers1624/Quack/0.4.10.101/970b7f652b5c40c893ef9a4bf13a97a291587d74/Quack-0.4.10.101.jar +net.fabricmc\:sponge-mixin\:0.15.2+mixin.0.8.7=/Users/george/.gradle/caches/modules-2/files-2.1/net.fabricmc/sponge-mixin/0.15.2+mixin.0.8.7/2af2f021d8e02a0220dc27a7a72b4666d66d44ca/sponge-mixin-0.15.2+mixin.0.8.7.jar +net.java.dev.jna\:jna-platform\:5.14.0=/Users/george/.gradle/caches/modules-2/files-2.1/net.java.dev.jna/jna-platform/5.14.0/28934d48aed814f11e4c584da55c49fa7032b31b/jna-platform-5.14.0.jar +net.java.dev.jna\:jna\:5.14.0=/Users/george/.gradle/caches/modules-2/files-2.1/net.java.dev.jna/jna/5.14.0/67bf3eaea4f0718cb376a181a629e5f88fa1c9dd/jna-5.14.0.jar +net.jodah\:typetools\:0.6.3=/Users/george/.gradle/caches/modules-2/files-2.1/net.jodah/typetools/0.6.3/a01aaa6ddaea9ec07ec4f209487b7a46a526283a/typetools-0.6.3.jar +net.minecraftforge\:mergetool\:1.1.7\:fatjar=/Users/george/.gradle/caches/modules-2/files-2.1/net.minecraftforge/mergetool/1.1.7/3d41564f78f123d64573cae7948014baf3aaba3f/mergetool-1.1.7-fatjar.jar +net.minecraftforge\:srgutils\:0.4.15=/Users/george/.gradle/caches/modules-2/files-2.1/net.minecraftforge/srgutils/0.4.15/ca408b131759478f164e010fae0d73997e125fb5/srgutils-0.4.15.jar +net.minecrell\:terminalconsoleappender\:1.3.0=/Users/george/.gradle/caches/modules-2/files-2.1/net.minecrell/terminalconsoleappender/1.3.0/b562e9bb61235c9520e26282cdee71f8f802d1fc/terminalconsoleappender-1.3.0.jar +net.neoforged.accesstransformers\:at-modlauncher\:10.0.1=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged.accesstransformers/at-modlauncher/10.0.1/5aba50202aceead086bc09fbc2751c9f05cb4890/at-modlauncher-10.0.1.jar +net.neoforged.fancymodloader\:earlydisplay\:4.0.39=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged.fancymodloader/earlydisplay/4.0.39/4388fd457faa6f04f8858d92ead5f33a354693a0/earlydisplay-4.0.39.jar +net.neoforged.fancymodloader\:loader\:4.0.39=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged.fancymodloader/loader/4.0.39/3721e43a7254e2861a63ab4a4ce716e4150d2640/loader-4.0.39.jar +net.neoforged.installertools\:installertools\:2.1.2\:fatjar=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged.installertools/installertools/2.1.2/62740d5aa8f75f4a4d542d363bdda5b64a198f7a/installertools-2.1.2-fatjar.jar +net.neoforged.jst\:jst-cli-bundle\:1.0.74=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged.jst/jst-cli-bundle/1.0.74/4b55f9a755ff69d480d3fb5d68c40662f5265b7e/jst-cli-bundle-1.0.74.jar +net.neoforged\:AutoRenamingTool\:2.0.3\:all=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged/AutoRenamingTool/2.0.3/d9890c71b4366f886c2b1006782043a6a6816eb6/AutoRenamingTool-2.0.3-all.jar +net.neoforged\:JarJarFileSystems\:0.4.1=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged/JarJarFileSystems/0.4.1/78f59f89defcd032ed788b151ca6a0d40ace796a/JarJarFileSystems-0.4.1.jar +net.neoforged\:JarJarMetadata\:0.4.1=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged/JarJarMetadata/0.4.1/f8da03683dc81694556dc3e177c5e3bb77ae6fcb/JarJarMetadata-0.4.1.jar +net.neoforged\:JarJarSelector\:0.4.1=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged/JarJarSelector/0.4.1/fb3cc7a58af22ad2880adb98af6d518128c47dae/JarJarSelector-0.4.1.jar +net.neoforged\:accesstransformers\:10.0.1=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged/accesstransformers/10.0.1/fd83b5725f76eae9115e9355fa1c456a6a441400/accesstransformers-10.0.1.jar +net.neoforged\:bus\:8.0.2=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged/bus/8.0.2/163c9ef7c4bcca6d850c34e95261b606af91fe06/bus-8.0.2.jar +net.neoforged\:coremods\:7.0.3=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged/coremods/7.0.3/9147e6f638b4272b3bd5fc8f92ad37802512c6c/coremods-7.0.3.jar +net.neoforged\:mergetool\:2.0.0\:api=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged/mergetool/2.0.0/52fe1949be64e3303aabaaa21e315f551db9c9f4/mergetool-2.0.0-api.jar +net.neoforged\:mergetool\:2.0.3\:api=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged/mergetool/2.0.3/1275202cbbb248f49345cf6787ce299afd9a02fb/mergetool-2.0.3-api.jar +net.neoforged\:mergetool\:2.0.3\:fatjar=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged/mergetool/2.0.3/85c096b0155715275f84922d41462484c9a0285c/mergetool-2.0.3-fatjar.jar +net.neoforged\:neoforge\:21.1.172\:sources=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged/neoforge/21.1.172/a268a6f13e327001fe1bc7094ed3959416213b4f/neoforge-21.1.172-sources.jar +net.neoforged\:neoforge\:21.1.172\:universal=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged/neoforge/21.1.172/736d9d34606de7ddcbb24ee6a23c7a6bf1662388/neoforge-21.1.172-universal.jar +net.neoforged\:neoforge\:21.1.172\:userdev=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged/neoforge/21.1.172/53eccd2305cbbfc3d7057aab68c78b0fb113adc8/neoforge-21.1.172-userdev.jar +net.neoforged\:neoform\:1.21.1-20240808.144430@zip=/Users/george/.gradle/caches/modules-2/files-2.1/net.neoforged/neoform/1.21.1-20240808.144430/811e2bd86fa2cda2812e5e8e51d718ea8bd6d3f4/neoform-1.21.1-20240808.144430.zip +net.sf.jopt-simple\:jopt-simple\:5.0.4=/Users/george/.gradle/caches/modules-2/files-2.1/net.sf.jopt-simple/jopt-simple/5.0.4/4fdac2fbe92dfad86aa6e9301736f6b4342a3f5c/jopt-simple-5.0.4.jar +org.antlr\:antlr4-runtime\:4.13.1=/Users/george/.gradle/caches/modules-2/files-2.1/org.antlr/antlr4-runtime/4.13.1/17125bae1d965624e265ef49552f6465a2bfa307/antlr4-runtime-4.13.1.jar +org.apache.commons\:commons-compress\:1.18=/Users/george/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-compress/1.18/1191f9f2bc0c47a8cce69193feb1ff0a8bcb37d5/commons-compress-1.18.jar +org.apache.commons\:commons-compress\:1.26.0=/Users/george/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-compress/1.26.0/659feffdd12280201c8aacb8f7be94f9a883c824/commons-compress-1.26.0.jar +org.apache.commons\:commons-lang3\:3.14.0=/Users/george/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.14.0/1ed471194b02f2c6cb734a0cd6f6f107c673afae/commons-lang3-3.14.0.jar +org.apache.commons\:commons-lang3\:3.9=/Users/george/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.9/122c7cee69b53ed4a7681c03d4ee4c0e2765da5/commons-lang3-3.9.jar +org.apache.httpcomponents\:httpclient\:4.5.13=/Users/george/.gradle/caches/modules-2/files-2.1/org.apache.httpcomponents/httpclient/4.5.13/e5f6cae5ca7ecaac1ec2827a9e2d65ae2869cada/httpclient-4.5.13.jar +org.apache.httpcomponents\:httpcore\:4.4.16=/Users/george/.gradle/caches/modules-2/files-2.1/org.apache.httpcomponents/httpcore/4.4.16/51cf043c87253c9f58b539c9f7e44c8894223850/httpcore-4.4.16.jar +org.apache.logging.log4j\:log4j-api\:2.22.1=/Users/george/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.22.1/bea6fede6328fabafd7e68363161a7ea6605abd1/log4j-api-2.22.1.jar +org.apache.logging.log4j\:log4j-core\:2.22.1=/Users/george/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-core/2.22.1/7183a25510a02ad00cc6a95d3b3d2a7d3c5a8dc4/log4j-core-2.22.1.jar +org.apache.logging.log4j\:log4j-slf4j2-impl\:2.22.1=/Users/george/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-slf4j2-impl/2.22.1/d7e6693c2606cb7e7335047d7bb96dec52db5665/log4j-slf4j2-impl-2.22.1.jar +org.apache.maven\:maven-artifact\:3.8.5=/Users/george/.gradle/caches/modules-2/files-2.1/org.apache.maven/maven-artifact/3.8.5/4433f50c07debefaed0553bd0068f4f48d449313/maven-artifact-3.8.5.jar +org.checkerframework\:checker-qual\:3.33.0=/Users/george/.gradle/caches/modules-2/files-2.1/org.checkerframework/checker-qual/3.33.0/de2b60b62da487644fc11f734e73c8b0b431238f/checker-qual-3.33.0.jar +org.codehaus.plexus\:plexus-utils\:3.3.0=/Users/george/.gradle/caches/modules-2/files-2.1/org.codehaus.plexus/plexus-utils/3.3.0/cf43b5391de623b36fe066a21127baef82c64022/plexus-utils-3.3.0.jar +org.jcraft\:jorbis\:0.0.17=/Users/george/.gradle/caches/modules-2/files-2.1/org.jcraft/jorbis/0.0.17/8872d22b293e8f5d7d56ff92be966e6dc28ebdc6/jorbis-0.0.17.jar +org.jetbrains\:annotations\:24.1.0=/Users/george/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/24.1.0/7af6a669488450c4a07c2c3254e2151df42d7d04/annotations-24.1.0.jar +org.jline\:jline-reader\:3.20.0=/Users/george/.gradle/caches/modules-2/files-2.1/org.jline/jline-reader/3.20.0/8f15415b022a25b473e8e16c28ae913186ffb9c4/jline-reader-3.20.0.jar +org.jline\:jline-terminal\:3.20.0=/Users/george/.gradle/caches/modules-2/files-2.1/org.jline/jline-terminal/3.20.0/d0ddcc708ddf527a3454c941b7b9225cc83a15ff/jline-terminal-3.20.0.jar +org.joml\:joml\:1.10.5=/Users/george/.gradle/caches/modules-2/files-2.1/org.joml/joml/1.10.5/22566d58af70ad3d72308bab63b8339906deb649/joml-1.10.5.jar +org.lwjgl\:lwjgl-freetype\:3.3.3=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-freetype/3.3.3/a0db6c84a8becc8ca05f9dbfa985edc348a824c7/lwjgl-freetype-3.3.3.jar +org.lwjgl\:lwjgl-freetype\:3.3.3\:natives-macos-arm64=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-freetype/3.3.3/b0a8c9baa9d1f54ac61e1ab9640c7659e7fa700c/lwjgl-freetype-3.3.3-natives-macos-arm64.jar +org.lwjgl\:lwjgl-freetype\:3.3.3\:natives-macos-patch=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-freetype/3.3.3/806d869f37ce0df388a24e17aaaf5ca0894d851b/lwjgl-freetype-3.3.3-natives-macos-patch.jar +org.lwjgl\:lwjgl-glfw\:3.3.3=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-glfw/3.3.3/efa1eb78c5ccd840e9f329717109b5e892d72f8e/lwjgl-glfw-3.3.3.jar +org.lwjgl\:lwjgl-glfw\:3.3.3\:natives-macos=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-glfw/3.3.3/a1bf400f6bc64e6195596cb1430dafda46090751/lwjgl-glfw-3.3.3-natives-macos.jar +org.lwjgl\:lwjgl-glfw\:3.3.3\:natives-macos-arm64=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-glfw/3.3.3/ee8cc78d0a4a5b3b4600fade6d927c9fc320c858/lwjgl-glfw-3.3.3-natives-macos-arm64.jar +org.lwjgl\:lwjgl-jemalloc\:3.3.3=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-jemalloc/3.3.3/b543467b7ff3c6920539a88ee602d34098628be5/lwjgl-jemalloc-3.3.3.jar +org.lwjgl\:lwjgl-jemalloc\:3.3.3\:natives-macos=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-jemalloc/3.3.3/2906637657a57579847238c9c72d2c4bde7083f8/lwjgl-jemalloc-3.3.3-natives-macos.jar +org.lwjgl\:lwjgl-jemalloc\:3.3.3\:natives-macos-arm64=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-jemalloc/3.3.3/e9412c3ff8cb3a3bad1d3f52909ad74d8a5bdad1/lwjgl-jemalloc-3.3.3-natives-macos-arm64.jar +org.lwjgl\:lwjgl-openal\:3.3.3=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-openal/3.3.3/daada81ceb5fc0c291fbfdd4433cb8d9423577f2/lwjgl-openal-3.3.3.jar +org.lwjgl\:lwjgl-openal\:3.3.3\:natives-macos=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-openal/3.3.3/8df8338bfa77f2ebabef4e58964bd04d24805cbf/lwjgl-openal-3.3.3-natives-macos.jar +org.lwjgl\:lwjgl-openal\:3.3.3\:natives-macos-arm64=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-openal/3.3.3/c78b078de2fb52f45aa55d04db889a560f3544f/lwjgl-openal-3.3.3-natives-macos-arm64.jar +org.lwjgl\:lwjgl-opengl\:3.3.3=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-opengl/3.3.3/2f6b0147078396a58979125a4c947664e98293a/lwjgl-opengl-3.3.3.jar +org.lwjgl\:lwjgl-opengl\:3.3.3\:natives-macos=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-opengl/3.3.3/1bd45997551ae8a28469f3a2b678f4b7289e12c0/lwjgl-opengl-3.3.3-natives-macos.jar +org.lwjgl\:lwjgl-opengl\:3.3.3\:natives-macos-arm64=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-opengl/3.3.3/d213ddef27637b1af87961ffa94d6b27036becc8/lwjgl-opengl-3.3.3-natives-macos-arm64.jar +org.lwjgl\:lwjgl-stb\:3.3.3=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-stb/3.3.3/25dd6161988d7e65f71d5065c99902402ee32746/lwjgl-stb-3.3.3.jar +org.lwjgl\:lwjgl-stb\:3.3.3\:natives-macos=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-stb/3.3.3/472792c98fb2c1557c060cb9da5fca6a9773621f/lwjgl-stb-3.3.3-natives-macos.jar +org.lwjgl\:lwjgl-stb\:3.3.3\:natives-macos-arm64=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-stb/3.3.3/51c6955571fbcdb7bb538c6aa589b953b584c6af/lwjgl-stb-3.3.3-natives-macos-arm64.jar +org.lwjgl\:lwjgl-tinyfd\:3.3.3=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-tinyfd/3.3.3/82d755ca94b102e9ca77283b9e2dc46d1b15fbe5/lwjgl-tinyfd-3.3.3.jar +org.lwjgl\:lwjgl-tinyfd\:3.3.3\:natives-macos=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-tinyfd/3.3.3/6598081e346a03038a8be68eb2de614a1c2eac68/lwjgl-tinyfd-3.3.3-natives-macos.jar +org.lwjgl\:lwjgl-tinyfd\:3.3.3\:natives-macos-arm64=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl-tinyfd/3.3.3/406feedb977372085a61eb0fee358183f4f4c67a/lwjgl-tinyfd-3.3.3-natives-macos-arm64.jar +org.lwjgl\:lwjgl\:3.3.3=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl/3.3.3/29589b5f87ed335a6c7e7ee6a5775f81f97ecb84/lwjgl-3.3.3.jar +org.lwjgl\:lwjgl\:3.3.3\:natives-macos=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl/3.3.3/33a6efa288390490ce6eb6c3df47ac21ecf648cf/lwjgl-3.3.3-natives-macos.jar +org.lwjgl\:lwjgl\:3.3.3\:natives-macos-arm64=/Users/george/.gradle/caches/modules-2/files-2.1/org.lwjgl/lwjgl/3.3.3/226246e75f6bd8d4e1895bdce8638ef87808d114/lwjgl-3.3.3-natives-macos-arm64.jar +org.lz4\:lz4-java\:1.8.0=/Users/george/.gradle/caches/modules-2/files-2.1/org.lz4/lz4-java/1.8.0/4b986a99445e49ea5fbf5d149c4b63f6ed6c6780/lz4-java-1.8.0.jar +org.openjdk.nashorn\:nashorn-core\:15.4=/Users/george/.gradle/caches/modules-2/files-2.1/org.openjdk.nashorn/nashorn-core/15.4/f67f5ffaa5f5130cf6fb9b133da00c7df3b532a5/nashorn-core-15.4.jar +org.ow2.asm\:asm-analysis\:9.5=/Users/george/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm-analysis/9.5/490bacc77de7cbc0be1a30bb3471072d705be4a4/asm-analysis-9.5.jar +org.ow2.asm\:asm-analysis\:9.7=/Users/george/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm-analysis/9.7/e4a258b7eb96107106c0599f0061cfc1832fe07a/asm-analysis-9.7.jar +org.ow2.asm\:asm-commons\:9.7=/Users/george/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm-commons/9.7/e86dda4696d3c185fcc95d8d311904e7ce38a53f/asm-commons-9.7.jar +org.ow2.asm\:asm-tree\:9.5=/Users/george/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm-tree/9.5/fd33c8b6373abaa675be407082fdfda35021254a/asm-tree-9.5.jar +org.ow2.asm\:asm-tree\:9.7=/Users/george/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm-tree/9.7/e446a17b175bfb733b87c5c2560ccb4e57d69f1a/asm-tree-9.7.jar +org.ow2.asm\:asm-util\:9.5=/Users/george/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm-util/9.5/64b5a1fc8c1b15ed2efd6a063e976bc8d3dc5ffe/asm-util-9.5.jar +org.ow2.asm\:asm-util\:9.7=/Users/george/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm-util/9.7/c0655519f24d92af2202cb681cd7c1569df6ead6/asm-util-9.7.jar +org.ow2.asm\:asm\:9.5=/Users/george/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm/9.5/dc6ea1875f4d64fbc85e1691c95b96a3d8569c90/asm-9.5.jar +org.ow2.asm\:asm\:9.7=/Users/george/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm/9.7/73d7b3086e14beb604ced229c302feff6449723/asm-9.7.jar +org.slf4j\:slf4j-api\:2.0.9=/Users/george/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/2.0.9/7cf2726fdcfbc8610f9a71fb3ed639871f315340/slf4j-api-2.0.9.jar +org.tukaani\:xz\:1.8=/Users/george/.gradle/caches/modules-2/files-2.1/org.tukaani/xz/1.8/c4f7d054303948eb6a4066194253886c8af07128/xz-1.8.jar +org.vineflower\:vineflower\:1.10.1=/Users/george/.gradle/caches/modules-2/files-2.1/org.vineflower/vineflower/1.10.1/4f48c5947b21f9ebc743e7c80215ee839d3dc668/vineflower-1.10.1.jar diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..b1b8ef5 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..df6a6ad --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,9 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..b9bb139 --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..aa5f10b --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/run/config/fml.toml b/run/config/fml.toml new file mode 100644 index 0000000..f126de7 --- /dev/null +++ b/run/config/fml.toml @@ -0,0 +1,32 @@ +#Disables File Watcher. Used to automatically update config if its file has been modified. +disableConfigWatcher = false +#Should we control the window. Disabling this disables new GL features and can be bad for mods that rely on them. +earlyWindowControl = true +#Max threads for early initialization parallelism, -1 is based on processor count +maxThreads = -1 +#Enable NeoForge global version checking +versionCheck = true +#Default config path for servers +defaultConfigPath = "defaultconfigs" +#Disables Optimized DFU client-side - already disabled on servers +disableOptimizedDFU = true +#Early window provider +earlyWindowProvider = "fmlearlywindow" +#Early window width +earlyWindowWidth = 854 +#Early window height +earlyWindowHeight = 480 +#Early window framebuffer scale +earlyWindowFBScale = 1 +#Early window starts maximized +earlyWindowMaximized = false +#Skip specific GL versions, may help with buggy graphics card drivers +earlyWindowSkipGLVersions = [] +#Squir? +earlyWindowSquir = false +#Define dependency overrides below +#Dependency overrides can be used to forcibly remove a dependency constraint from a mod or to force a mod to load AFTER another mod +#Using dependency overrides can cause issues. Use at your own risk. +#Example dependency override for the mod with the id 'targetMod': dependency constraints (incompatibility clauses or restrictive version ranges) against mod 'dep1' are removed, and the mod will now load after the mod 'dep2' +#dependencyOverrides.targetMod = ["-dep1", "+dep2"] +dependencyOverrides = {} diff --git a/run/crash-reports/crash-2026-06-06_20.56.04-fml.txt b/run/crash-reports/crash-2026-06-06_20.56.04-fml.txt new file mode 100644 index 0000000..ba64ec8 --- /dev/null +++ b/run/crash-reports/crash-2026-06-06_20.56.04-fml.txt @@ -0,0 +1,72 @@ +---- Minecraft Crash Report ---- +// Oops. + +Time: 2026-06-06 20:56:04 +Description: Mod loading failures have occurred; consult the issue messages for more details + +net.neoforged.neoforge.logging.CrashReportExtender$ModLoadingCrashException: Mod loading has failed + + +A detailed walkthrough of the error, its code path and all known details is as follows: +--------------------------------------------------------------------------------------- + +-- Mod loading issue -- +Details: + Mod file: + Failure message: File /Users/george/Documents/python/inProgress/minecraft_mods/living_world/build/classes/java/main is not a valid mod file + Mod version: + Mod issues URL: + Exception message: + +-- System Details -- +Details: + Minecraft Version: 1.21.1 + Minecraft Version ID: 1.21.1 + Operating System: Mac OS X (aarch64) version 26.5 + Java Version: 21.0.11, Homebrew + Java VM Version: OpenJDK 64-Bit Server VM (mixed mode, sharing), Homebrew + Memory: 242263888 bytes (231 MiB) / 532676608 bytes (508 MiB) up to 6442450944 bytes (6144 MiB) + CPUs: 12 + Processor Vendor: Apple Inc. + Processor Name: Apple M4 Pro + Identifier: Apple Inc. Family 0x1b588bb3 Model 0 Stepping 0 + Microarchitecture: ARM64 SoC: Firestorm + Icestorm + Frequency (GHz): 0.00 + Number of physical packages: 1 + Number of physical CPUs: 12 + Number of logical CPUs: 12 + Graphics card #0 name: Apple M4 Pro + Graphics card #0 vendor: Apple (0x106b) + Graphics card #0 VRAM (MiB): 0.00 + Graphics card #0 deviceId: unknown + Graphics card #0 versionInfo: unknown + Memory slot #0 capacity (MiB): 0.00 + Memory slot #0 clockSpeed (GHz): 0.00 + Memory slot #0 type: unknown + Virtual memory max (MiB): 27648.00 + Virtual memory used (MiB): 16251.97 + Swap memory total (MiB): 3072.00 + Swap memory used (MiB): 1770.94 + Space in storage for jna.tmpdir (MiB): + Space in storage for org.lwjgl.system.SharedLibraryExtractPath (MiB): + Space in storage for io.netty.native.workdir (MiB): + Space in storage for java.io.tmpdir (MiB): available: 198623.16, total: 471482.09 + Space in storage for workdir (MiB): available: 198623.16, total: 471482.09 + JVM Flags: 0 total; + ModLauncher: 11.0.4+main.d2e20e43 + ModLauncher launch target: forgeclientdev + ModLauncher services: + sponge-mixin-0.15.2+mixin.0.8.7.jar mixin PLUGINSERVICE + loader-4.0.39.jar slf4jfixer PLUGINSERVICE + loader-4.0.39.jar runtime_enum_extender PLUGINSERVICE + at-modlauncher-10.0.1.jar accesstransformer PLUGINSERVICE + loader-4.0.39.jar runtimedistcleaner PLUGINSERVICE + modlauncher-11.0.4.jar mixin TRANSFORMATIONSERVICE + modlauncher-11.0.4.jar fml TRANSFORMATIONSERVICE + FML Language Providers: + javafml@4.0 + lowcodefml@4.0 + minecraft@4.0 + Mod List: + neoforge-21.1.172.jar |Minecraft |minecraft |1.21.1 |Manifest: NOSIGNATURE + neoforge-21.1.172.jar |NeoForge |neoforge |21.1.172 |Manifest: NOSIGNATURE \ No newline at end of file diff --git a/run/downloads/log.json b/run/downloads/log.json new file mode 100644 index 0000000..e69de29 diff --git a/run/logs/debug.log b/run/logs/debug.log new file mode 100644 index 0000000..d9c0be9 --- /dev/null +++ b/run/logs/debug.log @@ -0,0 +1,129 @@ +[06Jun2026 20:55:58.597] [main/INFO] [cpw.mods.modlauncher.Launcher/MODLAUNCHER]: ModLauncher running: args [--launchTarget, forgeclientdev, --version, 21.1.172, --assetIndex, 17, --assetsDir, /Users/george/.gradle/caches/neoformruntime/assets, --gameDir, ., --fml.fmlVersion, 4.0.39, --fml.mcVersion, 1.21.1, --fml.neoForgeVersion, 21.1.172, --fml.neoFormVersion, 20240808.144430] +[06Jun2026 20:55:58.598] [main/INFO] [cpw.mods.modlauncher.Launcher/MODLAUNCHER]: JVM identified as Homebrew OpenJDK 64-Bit Server VM 21.0.11 +[06Jun2026 20:55:58.599] [main/INFO] [cpw.mods.modlauncher.Launcher/MODLAUNCHER]: ModLauncher 11.0.4+main.d2e20e43 starting: java version 21.0.11 by Homebrew; OS Mac OS X arch aarch64 version 26.5 +[06Jun2026 20:55:58.625] [main/INFO] [net.neoforged.fml.loading.ImmediateWindowHandler/]: Loading ImmediateWindowProvider fmlearlywindow +[06Jun2026 20:55:59.218] [main/INFO] [EARLYDISPLAY/]: Trying GL version 4.6 +[06Jun2026 20:55:59.274] [main/INFO] [EARLYDISPLAY/]: Trying GL version 4.5 +[06Jun2026 20:55:59.284] [main/INFO] [EARLYDISPLAY/]: Trying GL version 4.4 +[06Jun2026 20:55:59.294] [main/INFO] [EARLYDISPLAY/]: Trying GL version 4.3 +[06Jun2026 20:55:59.302] [main/INFO] [EARLYDISPLAY/]: Trying GL version 4.2 +[06Jun2026 20:55:59.311] [main/INFO] [EARLYDISPLAY/]: Trying GL version 4.1 +[06Jun2026 20:55:59.371] [main/INFO] [EARLYDISPLAY/]: Requested GL version 4.1 got version 4.1 +[06Jun2026 20:55:59.574] [main/INFO] [mixin/]: SpongePowered MIXIN Subsystem Version=0.8.7 Source=union:/Users/george/.gradle/caches/modules-2/files-2.1/net.fabricmc/sponge-mixin/0.15.2+mixin.0.8.7/2af2f021d8e02a0220dc27a7a72b4666d66d44ca/sponge-mixin-0.15.2+mixin.0.8.7.jar%23132!/ Service=ModLauncher Env=CLIENT +[06Jun2026 20:55:59.625] [main/ERROR] [net.neoforged.fml.loading.moddiscovery.ModDiscoverer/SCAN]: Skipping jar. File /Users/george/Documents/python/inProgress/minecraft_mods/living_world/build/classes/java/main is not a valid mod file +[06Jun2026 20:55:59.653] [main/INFO] [net.neoforged.fml.loading.moddiscovery.locators.JarInJarDependencyLocator/]: Found 2 dependencies adding them to mods collection +[06Jun2026 20:55:59.654] [main/INFO] [net.neoforged.fml.loading.moddiscovery.ModDiscoverer/]: + Mod List: + Name Version (Mod Id) + + Minecraft 1.21.1 (minecraft) + NeoForge 21.1.172 (neoforge) +[06Jun2026 20:55:59.744] [pool-2-thread-1/INFO] [EARLYDISPLAY/]: GL info: Apple M4 Pro GL version 4.1 Metal - 90.5, Apple +[06Jun2026 20:55:59.875] [main/INFO] [cpw.mods.modlauncher.LaunchServiceHandler/MODLAUNCHER]: Launching target 'forgeclientdev' with arguments [--version, 21.1.172, --gameDir, ., --assetsDir, /Users/george/.gradle/caches/neoformruntime/assets, --assetIndex, 17] +[06Jun2026 20:56:00.288] [Datafixer Bootstrap/INFO] [com.mojang.datafixers.DataFixerBuilder/]: 229 Datafixer optimizations took 77 milliseconds +[06Jun2026 20:56:00.629] [pool-6-thread-1/INFO] [MixinExtras|Service/]: Initializing MixinExtras via com.llamalad7.mixinextras.service.MixinExtrasServiceImpl(version=0.4.1). +[06Jun2026 20:56:02.112] [main/WARN] [net.minecraft.commands.Commands/]: Ambiguity between arguments [teleport, location] and [teleport, destination] with inputs: [0.1 -0.5 .9, 0 0 0] +[06Jun2026 20:56:02.113] [main/WARN] [net.minecraft.commands.Commands/]: Ambiguity between arguments [teleport, location] and [teleport, targets] with inputs: [0.1 -0.5 .9, 0 0 0] +[06Jun2026 20:56:02.114] [main/WARN] [net.minecraft.commands.Commands/]: Ambiguity between arguments [teleport, destination] and [teleport, targets] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498] +[06Jun2026 20:56:02.114] [main/WARN] [net.minecraft.commands.Commands/]: Ambiguity between arguments [teleport, targets] and [teleport, destination] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498] +[06Jun2026 20:56:02.115] [main/WARN] [net.minecraft.commands.Commands/]: Ambiguity between arguments [teleport, targets, location] and [teleport, targets, destination] with inputs: [0.1 -0.5 .9, 0 0 0] +[06Jun2026 20:56:02.153] [Render thread/WARN] [net.minecraft.server.packs.VanillaPackResourcesBuilder/]: Assets URL 'union:/Users/george/Documents/python/inProgress/minecraft_mods/living_world/build/moddev/artifacts/neoforge-21.1.172.jar%23184!/assets/.mcassetsroot' uses unexpected schema +[06Jun2026 20:56:02.153] [Render thread/WARN] [net.minecraft.server.packs.VanillaPackResourcesBuilder/]: Assets URL 'union:/Users/george/Documents/python/inProgress/minecraft_mods/living_world/build/moddev/artifacts/neoforge-21.1.172.jar%23184!/data/.mcassetsroot' uses unexpected schema +[06Jun2026 20:56:02.161] [Render thread/INFO] [com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService/]: Environment: Environment[sessionHost=https://sessionserver.mojang.com, servicesHost=https://api.minecraftservices.com, name=PROD] +[06Jun2026 20:56:02.163] [Render thread/INFO] [net.minecraft.client.Minecraft/]: Setting user: Dev +[06Jun2026 20:56:02.188] [Render thread/INFO] [net.minecraft.client.Minecraft/]: Backend library: LWJGL version 3.3.3+5 +[06Jun2026 20:56:02.418] [Render thread/FATAL] [net.neoforged.fml.ModLoader/CORE]: Error during pre-loading phase: File /Users/george/Documents/python/inProgress/minecraft_mods/living_world/build/classes/java/main is not a valid mod file +[06Jun2026 20:56:02.615] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.sound.SoundEngineLoadEvent to a broken mod state +[06Jun2026 20:56:02.649] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterColorHandlersEvent$Block to a broken mod state +[06Jun2026 20:56:02.650] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterColorHandlersEvent$Item to a broken mod state +[06Jun2026 20:56:02.660] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterMaterialAtlasesEvent to a broken mod state +[06Jun2026 20:56:02.671] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterRenderBuffersEvent to a broken mod state +[06Jun2026 20:56:02.692] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterParticleProvidersEvent to a broken mod state +[06Jun2026 20:56:02.704] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RenderLevelStageEvent$RegisterStageEvent to a broken mod state +[06Jun2026 20:56:02.723] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent to a broken mod state +[06Jun2026 20:56:02.723] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterSpriteSourceTypesEvent to a broken mod state +[06Jun2026 20:56:02.731] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterMenuScreensEvent to a broken mod state +[06Jun2026 20:56:02.731] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent to a broken mod state +[06Jun2026 20:56:02.731] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$RegisterLayerDefinitions to a broken mod state +[06Jun2026 20:56:02.731] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$RegisterRenderers to a broken mod state +[06Jun2026 20:56:02.732] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterClientTooltipComponentFactoriesEvent to a broken mod state +[06Jun2026 20:56:02.732] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterEntitySpectatorShadersEvent to a broken mod state +[06Jun2026 20:56:02.732] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent to a broken mod state +[06Jun2026 20:56:02.733] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterRecipeBookCategoriesEvent to a broken mod state +[06Jun2026 20:56:02.733] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterGuiLayersEvent to a broken mod state +[06Jun2026 20:56:02.734] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterDimensionSpecialEffectsEvent to a broken mod state +[06Jun2026 20:56:02.734] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterNamedRenderTypesEvent to a broken mod state +[06Jun2026 20:56:02.734] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterColorHandlersEvent$ColorResolvers to a broken mod state +[06Jun2026 20:56:02.734] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterItemDecorationsEvent to a broken mod state +[06Jun2026 20:56:02.737] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterPresetEditorsEvent to a broken mod state +[06Jun2026 20:56:02.737] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.gui.map.RegisterMapDecorationRenderersEvent to a broken mod state +[06Jun2026 20:56:02.737] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterDimensionTransitionScreenEvent to a broken mod state +[06Jun2026 20:56:02.741] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterJsonAnimationTypesEvent to a broken mod state +[06Jun2026 20:56:03.142] [Render thread/INFO] [net.minecraft.server.packs.resources.ReloadableResourceManager/]: Reloading ResourceManager: vanilla +[06Jun2026 20:56:03.153] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.ModelEvent$RegisterGeometryLoaders to a broken mod state +[06Jun2026 20:56:03.169] [Worker-Main-6/INFO] [net.minecraft.client.gui.font.providers.UnihexProvider/]: Found unifont_all_no_pua-15.1.05.hex, loading +[06Jun2026 20:56:03.221] [Worker-Main-5/INFO] [net.minecraft.client.gui.font.providers.UnihexProvider/]: Found unifont_jp_patch-15.1.05.hex, loading +[06Jun2026 20:56:03.484] [Worker-Main-2/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.ModelEvent$RegisterAdditional to a broken mod state +[06Jun2026 20:56:03.696] [Worker-Main-2/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.ModelEvent$ModifyBakingResult to a broken mod state +[06Jun2026 20:56:03.743] [Render thread/WARN] [net.minecraft.client.sounds.SoundEngine/]: Missing sound for event: minecraft:item.goat_horn.play +[06Jun2026 20:56:03.743] [Render thread/WARN] [net.minecraft.client.sounds.SoundEngine/]: Missing sound for event: minecraft:entity.goat.screaming.horn_break +[06Jun2026 20:56:03.800] [Render thread/INFO] [com.mojang.blaze3d.audio.Library/]: OpenAL initialized on device MacBook Pro Speakers +[06Jun2026 20:56:03.802] [Render thread/INFO] [net.minecraft.client.sounds.SoundEngine/SOUNDS]: Sound engine started +[06Jun2026 20:56:03.802] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.sound.SoundEngineLoadEvent to a broken mod state +[06Jun2026 20:56:03.850] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 1024x512x4 minecraft:textures/atlas/blocks.png-atlas +[06Jun2026 20:56:03.857] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.857] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 256x256x4 minecraft:textures/atlas/signs.png-atlas +[06Jun2026 20:56:03.857] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.857] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 512x512x4 minecraft:textures/atlas/banner_patterns.png-atlas +[06Jun2026 20:56:03.858] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.858] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 512x512x4 minecraft:textures/atlas/shield_patterns.png-atlas +[06Jun2026 20:56:03.858] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.859] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 1024x1024x4 minecraft:textures/atlas/armor_trims.png-atlas +[06Jun2026 20:56:03.861] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.861] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 128x64x4 minecraft:textures/atlas/decorated_pot.png-atlas +[06Jun2026 20:56:03.861] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.862] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 256x256x4 minecraft:textures/atlas/chest.png-atlas +[06Jun2026 20:56:03.862] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.862] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 512x256x4 minecraft:textures/atlas/shulker_boxes.png-atlas +[06Jun2026 20:56:03.862] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.862] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 512x256x4 minecraft:textures/atlas/beds.png-atlas +[06Jun2026 20:56:03.862] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.862] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.ModelEvent$BakingCompleted to a broken mod state +[06Jun2026 20:56:03.888] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.888] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.916] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.917] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.918] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.918] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.918] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.919] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.919] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.920] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.920] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.922] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.923] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.923] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.923] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.924] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.924] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.924] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.925] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.925] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.926] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.927] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.927] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$AddLayers to a broken mod state +[06Jun2026 20:56:03.927] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 512x256x0 minecraft:textures/atlas/particles.png-atlas +[06Jun2026 20:56:03.927] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.928] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 512x256x0 minecraft:textures/atlas/paintings.png-atlas +[06Jun2026 20:56:03.928] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.928] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 256x128x0 minecraft:textures/atlas/mob_effects.png-atlas +[06Jun2026 20:56:03.928] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.928] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 64x64x0 minecraft:textures/atlas/map_decorations.png-atlas +[06Jun2026 20:56:03.928] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.928] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 1024x512x0 minecraft:textures/atlas/gui.png-atlas +[06Jun2026 20:56:03.929] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.951] [Render thread/WARN] [net.minecraft.client.renderer.ShaderInstance/]: Shader rendertype_entity_translucent_emissive could not find sampler named Sampler2 in the specified shader program. +[06Jun2026 20:56:03.986] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterShadersEvent to a broken mod state +[06Jun2026 20:56:04.352] [Render thread/ERROR] [net.minecraft.CrashReport/]: Negative index in crash report handler (0/31) +[06Jun2026 20:56:04.355] [Render thread/FATAL] [net.neoforged.neoforge.client.loading.ClientModLoader/]: Crash report saved to ./crash-reports/crash-2026-06-06_20.56.04-fml.txt +[06Jun2026 20:56:13.560] [Render thread/INFO] [net.minecraft.client.Minecraft/]: Stopping! diff --git a/run/logs/latest.log b/run/logs/latest.log new file mode 100644 index 0000000..d9c0be9 --- /dev/null +++ b/run/logs/latest.log @@ -0,0 +1,129 @@ +[06Jun2026 20:55:58.597] [main/INFO] [cpw.mods.modlauncher.Launcher/MODLAUNCHER]: ModLauncher running: args [--launchTarget, forgeclientdev, --version, 21.1.172, --assetIndex, 17, --assetsDir, /Users/george/.gradle/caches/neoformruntime/assets, --gameDir, ., --fml.fmlVersion, 4.0.39, --fml.mcVersion, 1.21.1, --fml.neoForgeVersion, 21.1.172, --fml.neoFormVersion, 20240808.144430] +[06Jun2026 20:55:58.598] [main/INFO] [cpw.mods.modlauncher.Launcher/MODLAUNCHER]: JVM identified as Homebrew OpenJDK 64-Bit Server VM 21.0.11 +[06Jun2026 20:55:58.599] [main/INFO] [cpw.mods.modlauncher.Launcher/MODLAUNCHER]: ModLauncher 11.0.4+main.d2e20e43 starting: java version 21.0.11 by Homebrew; OS Mac OS X arch aarch64 version 26.5 +[06Jun2026 20:55:58.625] [main/INFO] [net.neoforged.fml.loading.ImmediateWindowHandler/]: Loading ImmediateWindowProvider fmlearlywindow +[06Jun2026 20:55:59.218] [main/INFO] [EARLYDISPLAY/]: Trying GL version 4.6 +[06Jun2026 20:55:59.274] [main/INFO] [EARLYDISPLAY/]: Trying GL version 4.5 +[06Jun2026 20:55:59.284] [main/INFO] [EARLYDISPLAY/]: Trying GL version 4.4 +[06Jun2026 20:55:59.294] [main/INFO] [EARLYDISPLAY/]: Trying GL version 4.3 +[06Jun2026 20:55:59.302] [main/INFO] [EARLYDISPLAY/]: Trying GL version 4.2 +[06Jun2026 20:55:59.311] [main/INFO] [EARLYDISPLAY/]: Trying GL version 4.1 +[06Jun2026 20:55:59.371] [main/INFO] [EARLYDISPLAY/]: Requested GL version 4.1 got version 4.1 +[06Jun2026 20:55:59.574] [main/INFO] [mixin/]: SpongePowered MIXIN Subsystem Version=0.8.7 Source=union:/Users/george/.gradle/caches/modules-2/files-2.1/net.fabricmc/sponge-mixin/0.15.2+mixin.0.8.7/2af2f021d8e02a0220dc27a7a72b4666d66d44ca/sponge-mixin-0.15.2+mixin.0.8.7.jar%23132!/ Service=ModLauncher Env=CLIENT +[06Jun2026 20:55:59.625] [main/ERROR] [net.neoforged.fml.loading.moddiscovery.ModDiscoverer/SCAN]: Skipping jar. File /Users/george/Documents/python/inProgress/minecraft_mods/living_world/build/classes/java/main is not a valid mod file +[06Jun2026 20:55:59.653] [main/INFO] [net.neoforged.fml.loading.moddiscovery.locators.JarInJarDependencyLocator/]: Found 2 dependencies adding them to mods collection +[06Jun2026 20:55:59.654] [main/INFO] [net.neoforged.fml.loading.moddiscovery.ModDiscoverer/]: + Mod List: + Name Version (Mod Id) + + Minecraft 1.21.1 (minecraft) + NeoForge 21.1.172 (neoforge) +[06Jun2026 20:55:59.744] [pool-2-thread-1/INFO] [EARLYDISPLAY/]: GL info: Apple M4 Pro GL version 4.1 Metal - 90.5, Apple +[06Jun2026 20:55:59.875] [main/INFO] [cpw.mods.modlauncher.LaunchServiceHandler/MODLAUNCHER]: Launching target 'forgeclientdev' with arguments [--version, 21.1.172, --gameDir, ., --assetsDir, /Users/george/.gradle/caches/neoformruntime/assets, --assetIndex, 17] +[06Jun2026 20:56:00.288] [Datafixer Bootstrap/INFO] [com.mojang.datafixers.DataFixerBuilder/]: 229 Datafixer optimizations took 77 milliseconds +[06Jun2026 20:56:00.629] [pool-6-thread-1/INFO] [MixinExtras|Service/]: Initializing MixinExtras via com.llamalad7.mixinextras.service.MixinExtrasServiceImpl(version=0.4.1). +[06Jun2026 20:56:02.112] [main/WARN] [net.minecraft.commands.Commands/]: Ambiguity between arguments [teleport, location] and [teleport, destination] with inputs: [0.1 -0.5 .9, 0 0 0] +[06Jun2026 20:56:02.113] [main/WARN] [net.minecraft.commands.Commands/]: Ambiguity between arguments [teleport, location] and [teleport, targets] with inputs: [0.1 -0.5 .9, 0 0 0] +[06Jun2026 20:56:02.114] [main/WARN] [net.minecraft.commands.Commands/]: Ambiguity between arguments [teleport, destination] and [teleport, targets] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498] +[06Jun2026 20:56:02.114] [main/WARN] [net.minecraft.commands.Commands/]: Ambiguity between arguments [teleport, targets] and [teleport, destination] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498] +[06Jun2026 20:56:02.115] [main/WARN] [net.minecraft.commands.Commands/]: Ambiguity between arguments [teleport, targets, location] and [teleport, targets, destination] with inputs: [0.1 -0.5 .9, 0 0 0] +[06Jun2026 20:56:02.153] [Render thread/WARN] [net.minecraft.server.packs.VanillaPackResourcesBuilder/]: Assets URL 'union:/Users/george/Documents/python/inProgress/minecraft_mods/living_world/build/moddev/artifacts/neoforge-21.1.172.jar%23184!/assets/.mcassetsroot' uses unexpected schema +[06Jun2026 20:56:02.153] [Render thread/WARN] [net.minecraft.server.packs.VanillaPackResourcesBuilder/]: Assets URL 'union:/Users/george/Documents/python/inProgress/minecraft_mods/living_world/build/moddev/artifacts/neoforge-21.1.172.jar%23184!/data/.mcassetsroot' uses unexpected schema +[06Jun2026 20:56:02.161] [Render thread/INFO] [com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService/]: Environment: Environment[sessionHost=https://sessionserver.mojang.com, servicesHost=https://api.minecraftservices.com, name=PROD] +[06Jun2026 20:56:02.163] [Render thread/INFO] [net.minecraft.client.Minecraft/]: Setting user: Dev +[06Jun2026 20:56:02.188] [Render thread/INFO] [net.minecraft.client.Minecraft/]: Backend library: LWJGL version 3.3.3+5 +[06Jun2026 20:56:02.418] [Render thread/FATAL] [net.neoforged.fml.ModLoader/CORE]: Error during pre-loading phase: File /Users/george/Documents/python/inProgress/minecraft_mods/living_world/build/classes/java/main is not a valid mod file +[06Jun2026 20:56:02.615] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.sound.SoundEngineLoadEvent to a broken mod state +[06Jun2026 20:56:02.649] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterColorHandlersEvent$Block to a broken mod state +[06Jun2026 20:56:02.650] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterColorHandlersEvent$Item to a broken mod state +[06Jun2026 20:56:02.660] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterMaterialAtlasesEvent to a broken mod state +[06Jun2026 20:56:02.671] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterRenderBuffersEvent to a broken mod state +[06Jun2026 20:56:02.692] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterParticleProvidersEvent to a broken mod state +[06Jun2026 20:56:02.704] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RenderLevelStageEvent$RegisterStageEvent to a broken mod state +[06Jun2026 20:56:02.723] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent to a broken mod state +[06Jun2026 20:56:02.723] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterSpriteSourceTypesEvent to a broken mod state +[06Jun2026 20:56:02.731] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterMenuScreensEvent to a broken mod state +[06Jun2026 20:56:02.731] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent to a broken mod state +[06Jun2026 20:56:02.731] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$RegisterLayerDefinitions to a broken mod state +[06Jun2026 20:56:02.731] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$RegisterRenderers to a broken mod state +[06Jun2026 20:56:02.732] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterClientTooltipComponentFactoriesEvent to a broken mod state +[06Jun2026 20:56:02.732] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterEntitySpectatorShadersEvent to a broken mod state +[06Jun2026 20:56:02.732] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent to a broken mod state +[06Jun2026 20:56:02.733] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterRecipeBookCategoriesEvent to a broken mod state +[06Jun2026 20:56:02.733] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterGuiLayersEvent to a broken mod state +[06Jun2026 20:56:02.734] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterDimensionSpecialEffectsEvent to a broken mod state +[06Jun2026 20:56:02.734] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterNamedRenderTypesEvent to a broken mod state +[06Jun2026 20:56:02.734] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterColorHandlersEvent$ColorResolvers to a broken mod state +[06Jun2026 20:56:02.734] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterItemDecorationsEvent to a broken mod state +[06Jun2026 20:56:02.737] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterPresetEditorsEvent to a broken mod state +[06Jun2026 20:56:02.737] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.gui.map.RegisterMapDecorationRenderersEvent to a broken mod state +[06Jun2026 20:56:02.737] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterDimensionTransitionScreenEvent to a broken mod state +[06Jun2026 20:56:02.741] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterJsonAnimationTypesEvent to a broken mod state +[06Jun2026 20:56:03.142] [Render thread/INFO] [net.minecraft.server.packs.resources.ReloadableResourceManager/]: Reloading ResourceManager: vanilla +[06Jun2026 20:56:03.153] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.ModelEvent$RegisterGeometryLoaders to a broken mod state +[06Jun2026 20:56:03.169] [Worker-Main-6/INFO] [net.minecraft.client.gui.font.providers.UnihexProvider/]: Found unifont_all_no_pua-15.1.05.hex, loading +[06Jun2026 20:56:03.221] [Worker-Main-5/INFO] [net.minecraft.client.gui.font.providers.UnihexProvider/]: Found unifont_jp_patch-15.1.05.hex, loading +[06Jun2026 20:56:03.484] [Worker-Main-2/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.ModelEvent$RegisterAdditional to a broken mod state +[06Jun2026 20:56:03.696] [Worker-Main-2/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.ModelEvent$ModifyBakingResult to a broken mod state +[06Jun2026 20:56:03.743] [Render thread/WARN] [net.minecraft.client.sounds.SoundEngine/]: Missing sound for event: minecraft:item.goat_horn.play +[06Jun2026 20:56:03.743] [Render thread/WARN] [net.minecraft.client.sounds.SoundEngine/]: Missing sound for event: minecraft:entity.goat.screaming.horn_break +[06Jun2026 20:56:03.800] [Render thread/INFO] [com.mojang.blaze3d.audio.Library/]: OpenAL initialized on device MacBook Pro Speakers +[06Jun2026 20:56:03.802] [Render thread/INFO] [net.minecraft.client.sounds.SoundEngine/SOUNDS]: Sound engine started +[06Jun2026 20:56:03.802] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.sound.SoundEngineLoadEvent to a broken mod state +[06Jun2026 20:56:03.850] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 1024x512x4 minecraft:textures/atlas/blocks.png-atlas +[06Jun2026 20:56:03.857] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.857] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 256x256x4 minecraft:textures/atlas/signs.png-atlas +[06Jun2026 20:56:03.857] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.857] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 512x512x4 minecraft:textures/atlas/banner_patterns.png-atlas +[06Jun2026 20:56:03.858] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.858] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 512x512x4 minecraft:textures/atlas/shield_patterns.png-atlas +[06Jun2026 20:56:03.858] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.859] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 1024x1024x4 minecraft:textures/atlas/armor_trims.png-atlas +[06Jun2026 20:56:03.861] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.861] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 128x64x4 minecraft:textures/atlas/decorated_pot.png-atlas +[06Jun2026 20:56:03.861] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.862] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 256x256x4 minecraft:textures/atlas/chest.png-atlas +[06Jun2026 20:56:03.862] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.862] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 512x256x4 minecraft:textures/atlas/shulker_boxes.png-atlas +[06Jun2026 20:56:03.862] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.862] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 512x256x4 minecraft:textures/atlas/beds.png-atlas +[06Jun2026 20:56:03.862] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.862] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.ModelEvent$BakingCompleted to a broken mod state +[06Jun2026 20:56:03.888] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.888] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.916] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.917] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.918] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.918] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.918] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.919] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.919] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.920] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.920] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.922] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.923] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.923] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.923] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.924] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.924] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.924] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.925] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.925] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.926] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.927] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$CreateSkullModels to a broken mod state +[06Jun2026 20:56:03.927] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.EntityRenderersEvent$AddLayers to a broken mod state +[06Jun2026 20:56:03.927] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 512x256x0 minecraft:textures/atlas/particles.png-atlas +[06Jun2026 20:56:03.927] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.928] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 512x256x0 minecraft:textures/atlas/paintings.png-atlas +[06Jun2026 20:56:03.928] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.928] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 256x128x0 minecraft:textures/atlas/mob_effects.png-atlas +[06Jun2026 20:56:03.928] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.928] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 64x64x0 minecraft:textures/atlas/map_decorations.png-atlas +[06Jun2026 20:56:03.928] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.928] [Render thread/INFO] [net.minecraft.client.renderer.texture.TextureAtlas/]: Created: 1024x512x0 minecraft:textures/atlas/gui.png-atlas +[06Jun2026 20:56:03.929] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.TextureAtlasStitchedEvent to a broken mod state +[06Jun2026 20:56:03.951] [Render thread/WARN] [net.minecraft.client.renderer.ShaderInstance/]: Shader rendertype_entity_translucent_emissive could not find sampler named Sampler2 in the specified shader program. +[06Jun2026 20:56:03.986] [Render thread/ERROR] [net.neoforged.fml.ModLoader/]: Cowardly refusing to send event net.neoforged.neoforge.client.event.RegisterShadersEvent to a broken mod state +[06Jun2026 20:56:04.352] [Render thread/ERROR] [net.minecraft.CrashReport/]: Negative index in crash report handler (0/31) +[06Jun2026 20:56:04.355] [Render thread/FATAL] [net.neoforged.neoforge.client.loading.ClientModLoader/]: Crash report saved to ./crash-reports/crash-2026-06-06_20.56.04-fml.txt +[06Jun2026 20:56:13.560] [Render thread/INFO] [net.minecraft.client.Minecraft/]: Stopping! diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..0c0700b --- /dev/null +++ b/settings.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { + name = 'NeoForged' + url = 'https://maven.neoforged.net/releases' + } + mavenCentral() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_PROJECT) + repositories { + maven { + name = 'NeoForged' + url = 'https://maven.neoforged.net/releases' + } + mavenCentral() + } +} + +rootProject.name = 'living_world' diff --git a/src/main/java/com/livingworld/LivingWorldMod.java b/src/main/java/com/livingworld/LivingWorldMod.java new file mode 100644 index 0000000..a9afd97 --- /dev/null +++ b/src/main/java/com/livingworld/LivingWorldMod.java @@ -0,0 +1,29 @@ +package com.livingworld; + +import net.neoforged.fml.common.Mod; +import net.neoforged.bus.api.IEventBus; + +import com.livingworld.bootstrap.LivingWorldBootstrap; +import com.livingworld.core.LivingWorldConstants; +import com.livingworld.debug.DiagnosticCategory; +import com.livingworld.debug.LivingWorldLogger; + +/** + * Mod entrypoint for Living World. + *

+ * This class contains only mod loader wiring. All simulation logic is + * delegated to the bootstrap system and core services. + */ +@Mod(LivingWorldConstants.MOD_ID) +public class LivingWorldMod { + + public static final String MOD_ID = LivingWorldConstants.MOD_ID; + + public LivingWorldMod(IEventBus eventBus) { + LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "Living World mod starting..."); + + // Delegate startup work to the bootstrap system. + new LivingWorldBootstrap().initialize(); + LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "Living World Bootstrap initialized successfully."); + } +} diff --git a/src/main/java/com/livingworld/api/package-info.java b/src/main/java/com/livingworld/api/package-info.java new file mode 100644 index 0000000..fdbcde3 --- /dev/null +++ b/src/main/java/com/livingworld/api/package-info.java @@ -0,0 +1,2 @@ +/** Living World API package — public interfaces and utilities. */ +package com.livingworld.api; \ No newline at end of file diff --git a/src/main/java/com/livingworld/bootstrap/LivingWorldBootstrap.java b/src/main/java/com/livingworld/bootstrap/LivingWorldBootstrap.java new file mode 100644 index 0000000..2bc0eb1 --- /dev/null +++ b/src/main/java/com/livingworld/bootstrap/LivingWorldBootstrap.java @@ -0,0 +1,48 @@ +package com.livingworld.bootstrap; + +import com.livingworld.debug.DiagnosticCategory; +import com.livingworld.debug.LivingWorldLogger; + +/** + * Bootstrap class for Living World mod startup lifecycle. + * + * Each method logs its lifecycle stage. No gameplay logic, region/ecosystem, + * or persistence systems are implemented yet. + */ +public class LivingWorldBootstrap { + + /** + * Called once during mod construction. + */ + public void initialize() { + LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "initialize — mod bootstrap initialized."); + } + + /** + * Called during common (dedicated & integrated server) setup phase. + */ + public void onCommonSetup() { + LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "onCommonSetup — common setup phase reached."); + } + + /** + * Called when the server is starting. + */ + public void onServerStarting() { + LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "onServerStarting — server starting."); + } + + /** + * Called when the server has finished starting. + */ + public void onServerStarted() { + LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "onServerStarted — server started."); + } + + /** + * Called when the server is stopping. + */ + public void onServerStopping() { + LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "onServerStopping — server stopping."); + } +} diff --git a/src/main/java/com/livingworld/bootstrap/package-info.java b/src/main/java/com/livingworld/bootstrap/package-info.java new file mode 100644 index 0000000..a8cf2eb --- /dev/null +++ b/src/main/java/com/livingworld/bootstrap/package-info.java @@ -0,0 +1,2 @@ +/** Bootstrap package — mod initialization and lifecycle wiring. */ +package com.livingworld.bootstrap; \ No newline at end of file diff --git a/src/main/java/com/livingworld/commands/package-info.java b/src/main/java/com/livingworld/commands/package-info.java new file mode 100644 index 0000000..245c5d2 --- /dev/null +++ b/src/main/java/com/livingworld/commands/package-info.java @@ -0,0 +1,2 @@ +/** Commands package — mod command definitions. */ +package com.livingworld.commands; \ No newline at end of file diff --git a/src/main/java/com/livingworld/config/DefaultConfigService.java b/src/main/java/com/livingworld/config/DefaultConfigService.java new file mode 100644 index 0000000..ddb836f --- /dev/null +++ b/src/main/java/com/livingworld/config/DefaultConfigService.java @@ -0,0 +1,60 @@ +package com.livingworld.config; + +import com.livingworld.core.services.ConfigService; +import com.livingworld.debug.DiagnosticCategory; +import com.livingworld.debug.LivingWorldLogger; + +/** + * Default implementation of {@link ConfigService}. + * + *

Holds a single {@link SimulationConfig} instance. The constructor creates + * a fresh default configuration and validates it immediately so that invalid + * defaults are caught at startup rather than at runtime.

+ */ +public class DefaultConfigService implements ConfigService { + + private SimulationConfig config; + + /** + * Creates a new service with default simulation settings. + * + * @throws RuntimeException if the default configuration fails validation + */ + public DefaultConfigService() { + this.config = new SimulationConfig(); + validate(); // validates and logs success + } + + @Override + public SimulationConfig getSimulationConfig() { + return config; + } + + /** + * Reloads configuration by creating a fresh default {@link SimulationConfig} + * and revalidating it. + */ + @Override + public void reload() { + this.config = new SimulationConfig(); + validate(); // validates and logs success + } + + /** + * Validates the current configuration by delegating to + * {@link SimulationConfig#validate()}. On success logs an informational + * message via {@link LivingWorldLogger}. + * + * @throws RuntimeException if validation fails (wraps {@link IllegalArgumentException}) + */ + @Override + public void validate() { + try { + config.validate(); + LivingWorldLogger.info(DiagnosticCategory.CONFIG, "Configuration validated successfully: " + config); + } catch (IllegalArgumentException e) { + LivingWorldLogger.error(DiagnosticCategory.CONFIG, "Configuration validation failed", e); + throw new RuntimeException("Configuration validation failed", e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/config/SimulationConfig.java b/src/main/java/com/livingworld/config/SimulationConfig.java new file mode 100644 index 0000000..0329465 --- /dev/null +++ b/src/main/java/com/livingworld/config/SimulationConfig.java @@ -0,0 +1,166 @@ +package com.livingworld.config; + +/** + * Configuration class for simulation parameters. + * + * This class holds plain integer and boolean fields that control how the + * living world simulation behaves at runtime. It is intentionally free of + * any Minecraft or NeoForge dependencies so it can be instantiated in pure + * JUnit tests. + */ +public final class SimulationConfig { + + // ------------------------------------------------------------------ + // Field defaults + // ------------------------------------------------------------------ + + /** Size of a region in chunks (must be >= 1). */ + private int regionSizeChunks = 8; + + /** Interval between simulation cycles, in game ticks (must be >= 1). */ + private int simulationIntervalTicks = 100; + + /** Maximum number of regions processed per cycle (must be >= 1). */ + private int maxRegionsPerCycle = 50; + + /** Maximum time budget for a single cycle in milliseconds (must be >= 1). */ + private int maxMillisecondsPerCycle = 25; + + /** Emergency stop threshold when simulation is overrunning (>= maxMillisecondsPerCycle). */ + private int emergencyStopMilliseconds = 40; + + /** Enable diagnostic / debug commands. */ + private boolean enableDebugCommands = true; + + /** Enable the built-in profiler. */ + private boolean enableProfiler = true; + + // ------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------ + + /** Default constructor – uses all default values. */ + public SimulationConfig() { + // Intentionally empty; fields are initialised at declaration. + } + + // ------------------------------------------------------------------ + // Getters + // ------------------------------------------------------------------ + + public int getRegionSizeChunks() { + return regionSizeChunks; + } + + public int getSimulationIntervalTicks() { + return simulationIntervalTicks; + } + + public int getMaxRegionsPerCycle() { + return maxRegionsPerCycle; + } + + public int getMaxMillisecondsPerCycle() { + return maxMillisecondsPerCycle; + } + + public int getEmergencyStopMilliseconds() { + return emergencyStopMilliseconds; + } + + public boolean isEnableDebugCommands() { + return enableDebugCommands; + } + + public boolean isEnableProfiler() { + return enableProfiler; + } + + // ------------------------------------------------------------------ + // Setters (package-private for testability) + // ------------------------------------------------------------------ + + public void setRegionSizeChunks(final int regionSizeChunks) { + this.regionSizeChunks = regionSizeChunks; + } + + public void setSimulationIntervalTicks(final int simulationIntervalTicks) { + this.simulationIntervalTicks = simulationIntervalTicks; + } + + public void setMaxRegionsPerCycle(final int maxRegionsPerCycle) { + this.maxRegionsPerCycle = maxRegionsPerCycle; + } + + public void setMaxMillisecondsPerCycle(final int maxMillisecondsPerCycle) { + this.maxMillisecondsPerCycle = maxMillisecondsPerCycle; + } + + public void setEmergencyStopMilliseconds(final int emergencyStopMilliseconds) { + this.emergencyStopMilliseconds = emergencyStopMilliseconds; + } + + public void setEnableDebugCommands(final boolean enableDebugCommands) { + this.enableDebugCommands = enableDebugCommands; + } + + public void setEnableProfiler(final boolean enableProfiler) { + this.enableProfiler = enableProfiler; + } + + // ------------------------------------------------------------------ + // Validation + // ------------------------------------------------------------------ + + /** + * Validates every field against its constraints. Throws + * {@link IllegalArgumentException} with a descriptive message if any + * constraint is violated. + * + * @throws IllegalArgumentException if validation fails + */ + public void validate() { + if (regionSizeChunks < 1) { + throw new IllegalArgumentException( + "regionSizeChunks must be >= 1, got: " + regionSizeChunks); + } + + if (simulationIntervalTicks < 1) { + throw new IllegalArgumentException( + "simulationIntervalTicks must be >= 1, got: " + simulationIntervalTicks); + } + + if (maxRegionsPerCycle < 1) { + throw new IllegalArgumentException( + "maxRegionsPerCycle must be >= 1, got: " + maxRegionsPerCycle); + } + + if (maxMillisecondsPerCycle < 1) { + throw new IllegalArgumentException( + "maxMillisecondsPerCycle must be >= 1, got: " + maxMillisecondsPerCycle); + } + + if (emergencyStopMilliseconds < maxMillisecondsPerCycle) { + throw new IllegalArgumentException( + "emergencyStopMilliseconds (" + emergencyStopMilliseconds + + ") must be >= maxMillisecondsPerCycle (" + maxMillisecondsPerCycle + ")"); + } + } + + // ------------------------------------------------------------------ + // Utility / Debug + // ------------------------------------------------------------------ + + @Override + public String toString() { + return "SimulationConfig{" + + "regionSizeChunks=" + regionSizeChunks + + ", simulationIntervalTicks=" + simulationIntervalTicks + + ", maxRegionsPerCycle=" + maxRegionsPerCycle + + ", maxMillisecondsPerCycle=" + maxMillisecondsPerCycle + + ", emergencyStopMilliseconds=" + emergencyStopMilliseconds + + ", enableDebugCommands=" + enableDebugCommands + + ", enableProfiler=" + enableProfiler + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/config/package-info.java b/src/main/java/com/livingworld/config/package-info.java new file mode 100644 index 0000000..37a7046 --- /dev/null +++ b/src/main/java/com/livingworld/config/package-info.java @@ -0,0 +1,2 @@ +/** Configuration package — mod configuration classes and settings. */ +package com.livingworld.config; \ No newline at end of file diff --git a/src/main/java/com/livingworld/core/LivingWorldConstants.java b/src/main/java/com/livingworld/core/LivingWorldConstants.java new file mode 100644 index 0000000..158af9b --- /dev/null +++ b/src/main/java/com/livingworld/core/LivingWorldConstants.java @@ -0,0 +1,26 @@ +package com.livingworld.core; + +/** + * Constants used throughout the Living World mod. + */ +public final class LivingWorldConstants { + + private LivingWorldConstants() { + // Utility class — no instantiation. + } + + /** The unique mod identifier string. */ + public static final String MOD_ID = "livingworld"; + + /** Human-readable mod name displayed in the mod list. */ + public static final String MOD_NAME = "Living World"; + + /** Current schema version for data persistence and migration tracking. */ + public static final int CURRENT_CORE_SCHEMA_VERSION = 1; + + /** Default region size measured in chunks (8x8 chunks). */ + public static final int DEFAULT_REGION_SIZE_CHUNKS = 8; + + /** Default simulation update interval in ticks (100 ticks ≈ 5 seconds). */ + public static final int DEFAULT_SIMULATION_INTERVAL_TICKS = 100; +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/core/lifecycle/package-info.java b/src/main/java/com/livingworld/core/lifecycle/package-info.java new file mode 100644 index 0000000..b969d82 --- /dev/null +++ b/src/main/java/com/livingworld/core/lifecycle/package-info.java @@ -0,0 +1,2 @@ +/** Core lifecycle package — mod event lifecycle management. */ +package com.livingworld.core.lifecycle; \ No newline at end of file diff --git a/src/main/java/com/livingworld/core/package-info.java b/src/main/java/com/livingworld/core/package-info.java new file mode 100644 index 0000000..2370a3c --- /dev/null +++ b/src/main/java/com/livingworld/core/package-info.java @@ -0,0 +1,2 @@ +/** Core package — fundamental mod infrastructure and shared utilities. */ +package com.livingworld.core; \ No newline at end of file diff --git a/src/main/java/com/livingworld/core/registry/package-info.java b/src/main/java/com/livingworld/core/registry/package-info.java new file mode 100644 index 0000000..c69e538 --- /dev/null +++ b/src/main/java/com/livingworld/core/registry/package-info.java @@ -0,0 +1,2 @@ +/** Core registry package — block, item, and entity registration utilities. */ +package com.livingworld.core.registry; \ No newline at end of file diff --git a/src/main/java/com/livingworld/core/services/ConfigService.java b/src/main/java/com/livingworld/core/services/ConfigService.java new file mode 100644 index 0000000..35f7d07 --- /dev/null +++ b/src/main/java/com/livingworld/core/services/ConfigService.java @@ -0,0 +1,39 @@ +package com.livingworld.core.services; + +import com.livingworld.config.SimulationConfig; + +/** + * Service interface for managing simulation configuration. + * + * Implementations of this interface provide access to the current + * {@link SimulationConfig}, support reloading from external sources, + * and validate that all values are within acceptable ranges. + */ +public interface ConfigService { + + /** + * Returns the current simulation configuration. + * + * @return the active {@link SimulationConfig} instance + */ + SimulationConfig getSimulationConfig(); + + /** + * Reloads configuration from its external source (e.g., config file, mod settings). + * Implementations should handle I/O and deserialization internally. + */ + void reload(); + + /** + * Validates the current configuration by delegating to {@link SimulationConfig#validate()}. + * + * @throws RuntimeException if validation fails (wraps {@link IllegalArgumentException}) + */ + default void validate() { + try { + getSimulationConfig().validate(); + } catch (IllegalArgumentException e) { + throw new RuntimeException("Configuration validation failed", e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/core/services/CoreServices.java b/src/main/java/com/livingworld/core/services/CoreServices.java new file mode 100644 index 0000000..cc2e8ca --- /dev/null +++ b/src/main/java/com/livingworld/core/services/CoreServices.java @@ -0,0 +1,69 @@ +package com.livingworld.core.services; + +import com.livingworld.core.simulation.PersistenceService; +import com.livingworld.core.simulation.RegionManager; +import com.livingworld.core.simulation.SimulationManager; +import com.livingworld.events.LivingWorldEventBus; +import com.livingworld.modules.ModuleRegistry; + +/** + * Central list of built-in service keys for the Living World core services. + * + *

This class is a final utility class with a private constructor. Each field + * defines a {@link ServiceKey} that will be used to register and retrieve core + * services once their implementations exist.

+ * + *

Only CONFIG can use its real type (ConfigService) since that interface already exists. + * All other keys currently use Object as a placeholder with TODO comments pointing to the + * relevant milestone where the actual service implementation will be created.

+ * + *

Migration Guide

+ *

As each service interface is created during its milestone, migrate its key from + * {@code Object} to the real type. For example:

+ *
{@code
+ * // Before (placeholder):
+ * @SuppressWarnings("unchecked")
+ * public static final ServiceKey REGIONS = new ServiceKey<>("regions", Object.class);
+ *
+ * // After (typed - when RegionManager interface is created):
+ * public static final ServiceKey REGIONS = new ServiceKey<>("regions", RegionManager.class);
+ * }
+ * 

Benefits of typed keys: compile-time type safety, no unchecked casts in caller code, + * and the {@link ServiceRegistry#find(ServiceKey)} method returns {@code Optional} with + * the correct type.

+ */ +public final class CoreServices { + + private CoreServices() { + // Utility class — no instantiation allowed. + } + + /** ConfigService key — interface already exists. */ + public static final ServiceKey CONFIG = new ServiceKey<>("config", ConfigService.class); + + public static final ServiceKey REGIONS = + new ServiceKey<>("regions", RegionManager.class); + + public static final ServiceKey SIMULATION = + new ServiceKey<>("simulation", SimulationManager.class); + + public static final ServiceKey PERSISTENCE = + new ServiceKey<>("persistence", PersistenceService.class); + + public static final ServiceKey EVENTS = + new ServiceKey<>("events", LivingWorldEventBus.class); + + public static final ServiceKey MODULES = + new ServiceKey<>("modules", ModuleRegistry.class); + + public static final ServiceKey TIME = + new ServiceKey<>("time", TimeService.class); + + // TODO: Replace Object with DebugService when created (Milestone 15) + @SuppressWarnings("unchecked") + public static final ServiceKey DEBUG = new ServiceKey<>("debug", Object.class); + + // TODO: Replace Object with HistoryService when created (post-Volume 1) + @SuppressWarnings("unchecked") + public static final ServiceKey HISTORY = new ServiceKey<>("history", Object.class); +} diff --git a/src/main/java/com/livingworld/core/services/ServiceKey.java b/src/main/java/com/livingworld/core/services/ServiceKey.java new file mode 100644 index 0000000..83a1fd0 --- /dev/null +++ b/src/main/java/com/livingworld/core/services/ServiceKey.java @@ -0,0 +1,35 @@ +package com.livingworld.core.services; + +/** + * Immutable key used to identify and look up services in a service registry. + * + * @param the type of the service this key identifies + */ +public record ServiceKey(String id, Class serviceType) { + + /** + * Creates a new service key with validation. + * + * @param id the unique identifier for the service (must not be null or blank) + * @param serviceType the class type of the service (must not be null) + * @throws IllegalArgumentException if {@code id} is null or blank, or {@code serviceType} is null + */ + public ServiceKey { + if (id == null || id.isBlank()) { + throw new IllegalArgumentException("Service key id must not be null or blank"); + } + if (serviceType == null) { + throw new IllegalArgumentException("Service key serviceType must not be null"); + } + } + + /** + * Returns a stable string representation of this service key. + * + * @return formatted string with id and fully-qualified service type name + */ + @Override + public String toString() { + return "ServiceKey{id='" + id + "', type='" + serviceType.getName() + "'}"; + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/core/services/ServiceRegistry.java b/src/main/java/com/livingworld/core/services/ServiceRegistry.java new file mode 100644 index 0000000..c16913d --- /dev/null +++ b/src/main/java/com/livingworld/core/services/ServiceRegistry.java @@ -0,0 +1,129 @@ +package com.livingworld.core.services; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * A plain Java service registry that stores and retrieves services by their {@link ServiceKey}. + * + *

The registry supports registration, lookup, and a lock mechanism to prevent further + * modifications once all services have been registered.

+ * + *

This class is not thread-safe. If multiple threads access the registry concurrently, + * external synchronization is required.

+ */ +public final class ServiceRegistry { + + private Map, Object> services = new HashMap<>(); + private boolean locked = false; + + /** + * Registers a service with the given key. + * + * @param key the service key identifying the service (must not be null) + * @param service the service instance to register (must not be null) + * @throws IllegalArgumentException if {@code key} is null, {@code service} is null + * @throws IllegalStateException if this registry is already locked, or a service with the same key is already registered + */ + @SuppressWarnings("unchecked") + public void register(ServiceKey key, T service) { + if (key == null) { + throw new IllegalArgumentException("Service key must not be null"); + } + if (service == null) { + throw new IllegalArgumentException("Service instance must not be null for key: " + key); + } + if (locked) { + throw new IllegalStateException("Cannot register services after the registry is locked"); + } + // Use the key itself as the map key. Since ServiceKey overrides equals/hashCode, + // duplicate registration of the same key will be detected. + if (services.containsKey(key)) { + throw new IllegalStateException("A service with key " + key + " is already registered"); + } + services.put(key, service); + } + + /** + * Returns the service identified by the given key. + * + * @param the type of the service + * @param key the service key identifying the service (must not be null) + * @return the registered service instance + * @throws IllegalArgumentException if {@code key} is null + * @throws IllegalStateException if no service is registered for the given key + */ + @SuppressWarnings("unchecked") + public T get(ServiceKey key) { + if (key == null) { + throw new IllegalArgumentException("Service key must not be null"); + } + Object service = services.get(key); + if (service == null) { + throw new IllegalStateException("No service registered for key: " + key); + } + return (T) service; + } + + /** + * Returns an {@link Optional} containing the service identified by the given key, + * or empty if no service is registered. + * + * @param the type of the service + * @param key the service key identifying the service (must not be null) + * @return an optional containing the service, or empty if not found + * @throws IllegalArgumentException if {@code key} is null + */ + @SuppressWarnings("unchecked") + public Optional find(ServiceKey key) { + if (key == null) { + throw new IllegalArgumentException("Service key must not be null"); + } + return Optional.ofNullable((T) services.get(key)); + } + + /** + * Returns whether a service is registered for the given key. + * + * @param key the service key to check (must not be null) + * @return true if a service is registered for the key, false otherwise + * @throws IllegalArgumentException if {@code key} is null + */ + public boolean isRegistered(ServiceKey key) { + if (key == null) { + throw new IllegalArgumentException("Service key must not be null"); + } + return services.containsKey(key); + } + + /** + * Returns the number of services currently registered. + * + * @return the count of registered services + */ + public int getServiceCount() { + return services.size(); + } + + /** + * Locks this registry, preventing any further registrations. + * + *

After locking, the internal service map is replaced with an unmodifiable + * view to guarantee no external code can mutate registered services.

+ */ + public void lock() { + locked = true; + this.services = Collections.unmodifiableMap(new HashMap<>(services)); + } + + /** + * Returns whether this registry is locked. + * + * @return true if the registry is locked, false otherwise + */ + public boolean isLocked() { + return locked; + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/core/services/TimeService.java b/src/main/java/com/livingworld/core/services/TimeService.java new file mode 100644 index 0000000..c2c2319 --- /dev/null +++ b/src/main/java/com/livingworld/core/services/TimeService.java @@ -0,0 +1,44 @@ +package com.livingworld.core.services; + +import com.livingworld.core.simulation.LivingWorldCalendar; + +/** + * Service interface for managing simulation time. + * + *

This service provides access to the simulation tick counter and calendar, + * as well as methods to advance the simulation clock.

+ */ +public interface TimeService { + + /** + * Returns the current simulation tick counter value. + * + * @return the current simulation tick (non-negative) + */ + long getSimulationTick(); + + /** + * Returns the current {@link LivingWorldCalendar} instance. + * + *

The calendar tracks day, month, year based on the simulation tick.

+ * + * @return the current living world calendar + */ + LivingWorldCalendar getCalendar(); + + /** + * Advances the simulation clock by a single tick. + * + *

This method increments the simulation tick counter and updates the + * corresponding calendar date.

+ */ + void advanceSimulationTick(); + + /** + * Advances the simulation clock by the specified number of ticks. + * + * @param ticks number of ticks to advance (must be non-negative) + * @throws IllegalArgumentException if ticks is negative + */ + void advanceSimulationTicks(long ticks); +} diff --git a/src/main/java/com/livingworld/core/services/package-info.java b/src/main/java/com/livingworld/core/services/package-info.java new file mode 100644 index 0000000..c2eeff3 --- /dev/null +++ b/src/main/java/com/livingworld/core/services/package-info.java @@ -0,0 +1,2 @@ +/** Core services package — shared service layer abstractions. */ +package com.livingworld.core.services; \ No newline at end of file diff --git a/src/main/java/com/livingworld/core/simulation/DefaultTimeService.java b/src/main/java/com/livingworld/core/simulation/DefaultTimeService.java new file mode 100644 index 0000000..69f14d9 --- /dev/null +++ b/src/main/java/com/livingworld/core/simulation/DefaultTimeService.java @@ -0,0 +1,60 @@ +package com.livingworld.core.simulation; + +import com.livingworld.core.services.TimeService; + +/** + * Default implementation of {@link TimeService}. + * + *

This service owns an internal {@link LivingWorldCalendar} instance and provides + * thread-safe access to it by returning defensive copies via {@link #getCalendar()}.

+ */ +public class DefaultTimeService implements TimeService { + + // ------------------------------------------------------------------ + // Internal state + // ------------------------------------------------------------------ + + /** Internal mutable calendar instance. Used for tracking simulation time. */ + private final LivingWorldCalendar internalCalendar; + + // ------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------ + + /** + * Creates a new {@code DefaultTimeService} with a calendar starting at epoch. + * + *

The initial state is: day=1, month=1, year=1, simulationTick=0.

+ */ + public DefaultTimeService() { + this.internalCalendar = new LivingWorldCalendar(); + } + + // ------------------------------------------------------------------ + // TimeService interface implementation + // ------------------------------------------------------------------ + + @Override + public long getSimulationTick() { + return internalCalendar.getSimulationTick(); + } + + @Override + public LivingWorldCalendar getCalendar() { + // Return a defensive copy to prevent external mutation of internal state + return internalCalendar.copy(); + } + + @Override + public void advanceSimulationTick() { + internalCalendar.advanceTicks(1); + } + + @Override + public void advanceSimulationTicks(long ticks) { + if (ticks < 0L) { + throw new IllegalArgumentException("ticks must be >= 0, got: " + ticks); + } + internalCalendar.advanceTicks(ticks); + } +} diff --git a/src/main/java/com/livingworld/core/simulation/LivingWorldCalendar.java b/src/main/java/com/livingworld/core/simulation/LivingWorldCalendar.java new file mode 100644 index 0000000..63b5b0c --- /dev/null +++ b/src/main/java/com/livingworld/core/simulation/LivingWorldCalendar.java @@ -0,0 +1,171 @@ +package com.livingworld.core.simulation; + +/** + * Simple calendar for tracking simulation time in the living world. + * + *

Uses a fixed 30-day month / 12-month year structure unless configured otherwise later. + * The {@code simulationTick} field serves as a monotonic counter that advances + * with each tick operation and can be used for persistence or external tracking.

+ */ +public class LivingWorldCalendar { + + /** Days per month (30 days/month). */ + private static final int DAYS_PER_MONTH = 30; + + /** Months per year (12 months/year). */ + private static final int MONTHS_PER_YEAR = 12; + + // ------------------------------------------------------------------ + // Fields + // ------------------------------------------------------------------ + + /** Monotonic simulation tick counter. Starts at 0 by default. */ + private long simulationTick; + + /** Current day of the month (1-30). */ + private int day; + + /** Current month of the year (1-12). */ + private int month; + + /** Current year number (starts at 1). */ + private int year; + + // ------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------ + + /** + * Default constructor – starts at the epoch date. + * + *

Initial values: day=1, month=1, year=1, simulationTick=0.

+ */ + public LivingWorldCalendar() { + this.simulationTick = 0L; + this.day = 1; + this.month = 1; + this.year = 1; + } + + // ------------------------------------------------------------------ + // State getters (read-only for calendar access) + // ------------------------------------------------------------------ + + /** Returns the current simulation tick counter. */ + public long getSimulationTick() { + return simulationTick; + } + + /** Returns the current day of the month. */ + public int getDay() { + return day; + } + + /** Returns the current month of the year. */ + public int getMonth() { + return month; + } + + /** Returns the current year number. */ + public int getYear() { + return year; + } + + // ------------------------------------------------------------------ + // Calendar operations + // ------------------------------------------------------------------ + + /** + * Advances the simulation tick by the specified amount and updates the calendar. + * + *

The calendar progresses day-by-day as ticks are added. Days overflow into months, + * and months overflow into years. Uses a fixed 30 days per month.

+ * + * @param ticks number of simulation ticks to advance (must be non-negative) + * @throws IllegalArgumentException if ticks is negative + */ + public void advanceTicks(long ticks) { + if (ticks < 0L) { + throw new IllegalArgumentException("ticks must be >= 0, got: " + ticks); + } + + this.simulationTick += ticks; + + // Advance day-by-day based on the number of ticks + long totalDaysFromEpoch = ((long) year - 1) * (MONTHS_PER_YEAR * DAYS_PER_MONTH) + + ((long) month - 1) * DAYS_PER_MONTH + (day - 1); + long targetTotalDaysFromEpoch = totalDaysFromEpoch + ticks; + + // Recalculate date from absolute index + year = 1 + (int) (targetTotalDaysFromEpoch / (MONTHS_PER_YEAR * DAYS_PER_MONTH)); + + long remainingAfterYears = targetTotalDaysFromEpoch % (MONTHS_PER_YEAR * DAYS_PER_MONTH); + month = 1 + (int) (remainingAfterYears / DAYS_PER_MONTH); + + day = 1 + (int) (remainingAfterYears % DAYS_PER_MONTH); + } + + // ------------------------------------------------------------------ + // Deep copy implementation + // ------------------------------------------------------------------ + + /** + * Returns a deep copy of this calendar instance. + * + *

All fields are copied to a new {@code LivingWorldCalendar} object.

+ * + * @return a new {@code LivingWorldCalendar} with the same state + */ + public LivingWorldCalendar copy() { + LivingWorldCalendar copy = new LivingWorldCalendar(); + copy.simulationTick = this.simulationTick; + copy.day = this.day; + copy.month = this.month; + copy.year = this.year; + return copy; + } + + // ------------------------------------------------------------------ + // Display / Formatting + // ------------------------------------------------------------------ + + /** + * Returns a human-readable string representation of the calendar date. + * + *

Format: {@code "YYYY-MM-DD"} (e.g., "2025-06-15").

+ * + * @return formatted date string in "YYYY-MM-DD" format + */ + public String toDisplayString() { + return year + "-" + String.format("%02d", month) + "-" + + String.format("%02d", day); + } + + /** + * Returns a string representation of this calendar's state. + * + * @return {@code "LivingWorldCalendar{simulationTick=..., day=..., month=..., year=...}"}} + */ + @Override + public String toString() { + return "LivingWorldCalendar{" + + "simulationTick=" + simulationTick + + ", day=" + day + + ", month=" + month + + ", year=" + year + + '}'; + } + + // ------------------------------------------------------------------ + // Utility + // ------------------------------------------------------------------ + + /** + * Returns the total number of days that have elapsed since epoch. + * + * @return total days elapsed (simulationTick / 1, assuming 1 tick = 1 day) + */ + public long getDaysElapsed() { + return simulationTick; + } +} diff --git a/src/main/java/com/livingworld/core/simulation/PersistenceService.java b/src/main/java/com/livingworld/core/simulation/PersistenceService.java new file mode 100644 index 0000000..91cf469 --- /dev/null +++ b/src/main/java/com/livingworld/core/simulation/PersistenceService.java @@ -0,0 +1,39 @@ +package com.livingworld.core.simulation; + +import java.util.Optional; + +import com.livingworld.regions.Region; +import com.livingworld.regions.RegionCoordinate; + +/** + * Handles region persistence (save/load) and migration support. + * + *

TODO (Milestone 8): Implement actual file or database I/O, + * platform-specific save paths, and schema versioning.

+ */ +public interface PersistenceService { + + /** + * Saves a region to persistent storage. + * + * @param region the region to save (must not be null) + */ + void save(Region region); + + /** + * Loads a region from persistent storage. + * + * @param coordinate the region coordinate to load (must not be null) + * @return an optional containing the loaded region if found, otherwise empty + */ + Optional load(RegionCoordinate coordinate); + + /** + * Returns whether this persistence service supports data migration. + * + *

TODO: Implement schema version detection and migration logic.

+ * + * @return true if migration is supported + */ + boolean supportsMigration(); +} diff --git a/src/main/java/com/livingworld/core/simulation/RegionManager.java b/src/main/java/com/livingworld/core/simulation/RegionManager.java new file mode 100644 index 0000000..671d1b6 --- /dev/null +++ b/src/main/java/com/livingworld/core/simulation/RegionManager.java @@ -0,0 +1,39 @@ +package com.livingworld.core.simulation; + +import java.util.List; +import java.util.Optional; + +import com.livingworld.regions.Region; +import com.livingworld.regions.RegionCoordinate; + +/** + * Manages region resolution and dirty-state tracking. + * + *

TODO (Milestone 8): Implement actual region loading, unloading, + * chunk scanning, and persistence integration.

+ */ +public interface RegionManager { + + /** + * Resolves a region by its coordinate. + * + * @param coordinate the region coordinate (must not be null) + * @return an optional containing the region if found, otherwise empty + */ + Optional resolve(RegionCoordinate coordinate); + + /** + * Resolves multiple regions by their coordinates. + * + * @param coordinates list of region coordinates to resolve (must not be null) + * @return a list of resolved regions (may contain fewer than requested if some are missing) + */ + List resolveAll(List coordinates); + + /** + * Marks a region as dirty, indicating unsaved changes. + * + * @param region the region to mark dirty (must not be null) + */ + void markDirty(Region region); +} diff --git a/src/main/java/com/livingworld/core/simulation/RegionPriorityCalculator.java b/src/main/java/com/livingworld/core/simulation/RegionPriorityCalculator.java new file mode 100644 index 0000000..19e1d08 --- /dev/null +++ b/src/main/java/com/livingworld/core/simulation/RegionPriorityCalculator.java @@ -0,0 +1,75 @@ +package com.livingworld.core.simulation; + +import com.livingworld.regions.Region; +import com.livingworld.regions.RegionFlags; +import com.livingworld.regions.RegionMetrics; + +/** + * Utility class for computing update priority scores based on region ecosystem state. + * + *

This calculator uses a deterministic formula that considers player activity, + * ecosystem events, pollution, soil quality, resource depletion, and recovery pressure.

+ */ +public final class RegionPriorityCalculator { + + private static final int BASE_PRIORITY = 5; + private static final int PLAYER_ACTIVITY_BONUS = 100; + private static final int ACTIVE_ECOSYSTEM_EVENT_BONUS = 75; + private static final int HIGH_POLLUTION_BONUS = 50; + private static final int LOW_SOIL_QUALITY_BONUS = 50; + + /** + * Calculates the update priority for a region based on its ecosystem state. + * + *

Priority score formula: + * base = 5 + * + hasPlayerActivity ? 100 : 0 + * + hasActiveEcosystemEvent ? 75 : 0 + * + isHighPollution ? 50 : 0 + * + isLowSoilQuality ? 50 : 0 + * + pollutionScore / 2 + * + resourceDepletion / 3 + * + recoveryPressure / 4

+ * + * @param region the region to calculate priority for (must not be null) + * @return the computed integer priority score + */ + public static int calculatePriority(Region region) { + if (region == null) { + throw new IllegalArgumentException("region must not be null"); + } + + RegionMetrics metrics = region.getMetrics(); + RegionFlags flags = region.getFlags(); + + int priority = BASE_PRIORITY; + + // Discrete bonuses for flagged conditions + if (flags.isHasPlayerActivity()) { + priority += PLAYER_ACTIVITY_BONUS; + } + if (flags.isHasActiveEcosystemEvent()) { + priority += ACTIVE_ECOSYSTEM_EVENT_BONUS; + } + if (flags.isHasHighPollution()) { + priority += HIGH_POLLUTION_BONUS; + } + if (flags.isHasLowSoilQuality()) { + priority += LOW_SOIL_QUALITY_BONUS; + } + + // Continuous bonuses from metrics + priority += (int) (metrics.getPollutionScore() / 2.0); + priority += (int) (metrics.getResourceDepletion() / 3.0); + priority += (int) (metrics.getRecoveryPressure() / 4.0); + + return priority; + } + + /** + * Private constructor to prevent instantiation. + */ + private RegionPriorityCalculator() { + throw new UnsupportedOperationException("Utility class cannot be instantiated"); + } +} diff --git a/src/main/java/com/livingworld/core/simulation/RegionUpdateJob.java b/src/main/java/com/livingworld/core/simulation/RegionUpdateJob.java new file mode 100644 index 0000000..252d52f --- /dev/null +++ b/src/main/java/com/livingworld/core/simulation/RegionUpdateJob.java @@ -0,0 +1,55 @@ +package com.livingworld.core.simulation; + +import java.util.Comparator; +import java.util.Set; + +import com.livingworld.regions.RegionCoordinate; + +/** + * Immutable job representing a pending region update request. + * + *

A RegionUpdateJob captures why an update is needed and when it was queued, + * along with which modules should be re-evaluated for that region.

+ */ +public record RegionUpdateJob( + RegionCoordinate coordinate, + int priority, + long queuedAtSimulationTick, + Set requestedModules, + UpdateReason reason +) { + + /** + * Creates a new region update job. + * + * @param coordinate the region coordinate (must not be null) + * @param priority priority for ordering jobs (higher = more urgent; must be non-negative) + * @param queuedAtSimulationTick timestamp of when this job was queued (must be non-negative) + * @param requestedModules set of module names to update (null becomes empty set) + * @param reason reason for the update request (must not be null) + */ + public RegionUpdateJob { + if (coordinate == null) { + throw new IllegalArgumentException("coordinate must not be null"); + } + if (priority < 0) { + throw new IllegalArgumentException("priority must be non-negative"); + } + if (queuedAtSimulationTick < 0L) { + throw new IllegalArgumentException("queuedAtSimulationTick must be non-negative"); + } + if (reason == null) { + throw new IllegalArgumentException("reason must not be null"); + } + requestedModules = requestedModules == null ? Set.of() : Set.copyOf(requestedModules); + } + + /** + * Comparator for sorting jobs by priority in descending order. + * Higher priority values are processed first. + */ + public static final Comparator BY_PRIORITY_DESC = + Comparator.comparingInt(RegionUpdateJob::priority) + .reversed() + .thenComparingLong(RegionUpdateJob::queuedAtSimulationTick); +} diff --git a/src/main/java/com/livingworld/core/simulation/SimulationManager.java b/src/main/java/com/livingworld/core/simulation/SimulationManager.java new file mode 100644 index 0000000..52a7dff --- /dev/null +++ b/src/main/java/com/livingworld/core/simulation/SimulationManager.java @@ -0,0 +1,180 @@ +package com.livingworld.core.simulation; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import com.livingworld.core.services.TimeService; +import com.livingworld.debug.DiagnosticCategory; +import com.livingworld.debug.LivingWorldLogger; +import com.livingworld.events.LivingWorldEventBus; +import com.livingworld.modules.ModuleUpdateResult; +import com.livingworld.modules.ModuleRegistry; +import com.livingworld.modules.RegionUpdateContext; +import com.livingworld.modules.SimulationModule; +import com.livingworld.regions.Region; + +/** + * Central coordinator for the living world simulation lifecycle. + * + *

This class orchestrates tick processing, region updates, module execution, + * event publishing, and time advancement. It does not implement ecosystem rules — + * those live in individual {@link com.livingworld.modules.SimulationModule} classes.

+ */ +public final class SimulationManager { + + private final SimulationScheduler scheduler; + private final RegionManager regionManager; + private final ModuleRegistry moduleRegistry; + private final LivingWorldEventBus eventBus; + private final TimeService timeService; + private final PersistenceService persistenceService; + private final SimulationProfiler profiler; + + public SimulationManager(SimulationScheduler scheduler, + RegionManager regionManager, + ModuleRegistry moduleRegistry, + LivingWorldEventBus eventBus, + TimeService timeService, + PersistenceService persistenceService, + SimulationProfiler profiler) { + this.scheduler = requireNonNull(scheduler, "scheduler"); + this.regionManager = requireNonNull(regionManager, "regionManager"); + this.moduleRegistry = requireNonNull(moduleRegistry, "moduleRegistry"); + this.eventBus = requireNonNull(eventBus, "eventBus"); + this.timeService = requireNonNull(timeService, "timeService"); + this.persistenceService = requireNonNull(persistenceService, "persistenceService"); + this.profiler = profiler; + } + + private static T requireNonNull(T value, String name) { + if (value == null) { + throw new IllegalArgumentException(name + " must not be null"); + } + return value; + } + + public void onMinecraftServerTick() { + this.scheduler.onMinecraftTick(); + + if (this.scheduler.shouldRunSimulationCycle()) { + runSimulationCycle(); + } + } + + /** + * Runs a single simulation cycle. + */ + public void runSimulationCycle() { + long startTimeNanos = System.nanoTime(); + long simulationTick = this.timeService.getSimulationTick(); + if (this.profiler != null) { + this.profiler.startCycle(simulationTick); + } + + List jobs = this.scheduler.pollJobsForCycle(); + int nextJobIndex = 0; + try { + for (; nextJobIndex < jobs.size(); nextJobIndex++) { + if (hasExceededTimeBudget(startTimeNanos)) { + requeueJobs(jobs, nextJobIndex); + break; + } + + RegionUpdateJob job = jobs.get(nextJobIndex); + Optional region = this.regionManager.resolve(job.coordinate()); + if (region.isEmpty()) { + continue; + } + runModulesForRegion(region.get(), job, simulationTick); + } + + this.timeService.advanceSimulationTick(); + this.scheduler.recordCompletedSimulationCycle(); + } finally { + long durationMs = elapsedMilliseconds(startTimeNanos); + this.scheduler.endCycle(durationMs); + if (this.profiler != null) { + this.profiler.endCycle(durationMs); + } + } + } + + private boolean hasExceededTimeBudget(long startTimeNanos) { + return elapsedMilliseconds(startTimeNanos) >= this.scheduler.getMaxMillisecondsPerCycle(); + } + + private static long elapsedMilliseconds(long startTimeNanos) { + return (System.nanoTime() - startTimeNanos) / 1_000_000L; + } + + private void requeueJobs(List jobs, int firstUnprocessedIndex) { + for (int i = firstUnprocessedIndex; i < jobs.size(); i++) { + this.scheduler.queueRegion(jobs.get(i)); + } + } + + private void runModulesForRegion(Region region, RegionUpdateJob job, long simulationTick) { + Set requestedModules = job.requestedModules(); + boolean changed = false; + + for (SimulationModule module : this.moduleRegistry.getEnabledModules()) { + if (!requestedModules.isEmpty() && !requestedModules.contains(module.getModuleId())) { + continue; + } + + try { + ModuleUpdateResult result = module.updateRegion(new RegionUpdateContext(region)); + if (result == null) { + throw new IllegalStateException( + "Module " + module.getModuleId() + " returned a null update result"); + } + + changed |= result.changedRegion(); + result.generatedEvents().forEach(this.eventBus::publish); + result.warnings().forEach(warning -> LivingWorldLogger.warn( + DiagnosticCategory.SIMULATION, + "[" + module.getModuleId() + "] " + warning)); + } catch (Exception e) { + LivingWorldLogger.error(DiagnosticCategory.SIMULATION, + "Module update failed for " + module.getModuleId() + + " in region " + region.getCoordinate(), e); + } + } + + region.updateLastSimulatedTick(simulationTick); + if (changed) { + this.regionManager.markDirty(region); + } + } + + /** + * Runs forced simulation ticks for debugging or testing. + */ + public void runForcedSimulationTicks(int ticks) { + if (ticks <= 0 || ticks > 100) { + throw new IllegalArgumentException( + String.format("forced ticks must be in range [1, 100], got: %d", ticks)); + } + + for (int i = 0; i < ticks; i++) { + runSimulationCycle(); + } + } + + public long getMinecraftTickCounter() { + return this.scheduler.getMinecraftTickCounter(); + } + + public long getSimulationTickCounter() { + return this.scheduler.getSimulationTickCounter(); + } + + @Override + public String toString() { + return "SimulationManager{" + + "minecraftTick=" + getMinecraftTickCounter() + + ", simulationTick=" + getSimulationTickCounter() + + '}'; + } +} diff --git a/src/main/java/com/livingworld/core/simulation/SimulationProfiler.java b/src/main/java/com/livingworld/core/simulation/SimulationProfiler.java new file mode 100644 index 0000000..ab2e8be --- /dev/null +++ b/src/main/java/com/livingworld/core/simulation/SimulationProfiler.java @@ -0,0 +1,23 @@ +package com.livingworld.core.simulation; + +/** + * Optional profiler for tracking simulation cycle performance. + * + *

If no implementation is provided, all calls are no-ops.

+ */ +public interface SimulationProfiler { + + /** + * Called at the start of a simulation cycle. + * + * @param simulationTick the current simulation tick + */ + void startCycle(long simulationTick); + + /** + * Called when a simulation cycle completes. + * + * @param durationMs the wall-clock duration in milliseconds + */ + void endCycle(long durationMs); +} diff --git a/src/main/java/com/livingworld/core/simulation/SimulationScheduler.java b/src/main/java/com/livingworld/core/simulation/SimulationScheduler.java new file mode 100644 index 0000000..866ddfa --- /dev/null +++ b/src/main/java/com/livingworld/core/simulation/SimulationScheduler.java @@ -0,0 +1,125 @@ +package com.livingworld.core.simulation; + +import com.livingworld.config.SimulationConfig; +import com.livingworld.debug.LivingWorldLogger; +import java.util.ArrayList; +import java.util.List; +import java.util.PriorityQueue; + +/** + * Scheduler that drives the simulation tick cycle. + * + *

This class integrates Minecraft game ticks with living world simulation ticks, + * manages a priority queue of update jobs, and enforces per-cycle budget limits.

+ */ +public final class SimulationScheduler { + + private final SimulationConfig config; + private long minecraftTickCounter; + private long simulationTickCounter; + private final PriorityQueue updateQueue; + public SimulationScheduler(SimulationConfig config) { + if (config == null) { + throw new IllegalArgumentException("config must not be null"); + } + this.config = config; + this.minecraftTickCounter = 0L; + this.simulationTickCounter = 0L; + this.updateQueue = new PriorityQueue<>(RegionUpdateJob.BY_PRIORITY_DESC); + } + + public void onMinecraftTick() { + this.minecraftTickCounter++; + } + + void recordCompletedSimulationCycle() { + this.simulationTickCounter++; + } + + public boolean shouldRunSimulationCycle() { + return minecraftTickCounter % config.getSimulationIntervalTicks() == 0; + } + + public void queueRegion(RegionUpdateJob job) { + if (job == null) { + throw new IllegalArgumentException("job must not be null"); + } + this.updateQueue.add(job); + } + + /** + * Polls jobs for processing during the current simulation cycle. + * + * @return list of polled jobs, may be empty if no jobs are queued or budget exceeded + */ + public List pollJobsForCycle() { + final int maxCount = config.getMaxRegionsPerCycle(); + final ArrayList result = new ArrayList<>(maxCount); + + for (int i = 0; i < maxCount && !this.updateQueue.isEmpty(); i++) { + RegionUpdateJob job = this.updateQueue.poll(); + if (job != null) { + result.add(job); + } else { + break; + } + } + + return result; + } + + public void endCycle(long durationMs) { + if (durationMs > config.getMaxMillisecondsPerCycle()) { + LivingWorldLogger.warn(String.format( + "Simulation cycle exceeded time budget: %dms elapsed vs %dms allowed", + durationMs, config.getMaxMillisecondsPerCycle())); + } + final int emergencyMs = config.getEmergencyStopMilliseconds(); + if (durationMs > emergencyMs) { + LivingWorldLogger.warn(String.format("EMERGENCY STOP: Cycle duration %dms exceeded emergency threshold %dms", + durationMs, emergencyMs)); + } + } + + public long getMinecraftTickCounter() { + return this.minecraftTickCounter; + } + + public long getSimulationTickCounter() { + return this.simulationTickCounter; + } + + int getSimulationIntervalTicks() { + return config.getSimulationIntervalTicks(); + } + + int getMaxRegionsPerCycle() { + return config.getMaxRegionsPerCycle(); + } + + public int getMaxMillisecondsPerCycle() { + return config.getMaxMillisecondsPerCycle(); + } + + public int getEmergencyStopMilliseconds() { + return config.getEmergencyStopMilliseconds(); + } + + public void clearQueue() { + this.updateQueue.clear(); + } + + public int getQueuedJobCount() { + return this.updateQueue.size(); + } + + @Override + public String toString() { + return "SimulationScheduler{" + + "minecraftTickCounter=" + minecraftTickCounter + + ", simulationTickCounter=" + simulationTickCounter + + ", queuedJobs=" + updateQueue.size() + + '}'; + } + +} diff --git a/src/main/java/com/livingworld/core/simulation/UpdateReason.java b/src/main/java/com/livingworld/core/simulation/UpdateReason.java new file mode 100644 index 0000000..fcdaafc --- /dev/null +++ b/src/main/java/com/livingworld/core/simulation/UpdateReason.java @@ -0,0 +1,32 @@ +package com.livingworld.core.simulation; + +/** + * Enumeration of reasons that trigger an update in the simulation. + * + *

This enum captures why a particular region or module needs to be re-evaluated + * or refreshed during the simulation tick cycle.

+ */ +public enum UpdateReason { + + /** Normal periodic rolling update from the simulation tick counter. */ + NORMAL_ROLLING_UPDATE, + + /** A player has entered the proximity threshold of a region. */ + PLAYER_NEARBY, + + /** Pollution levels in a region have crossed a critical threshold. */ + HIGH_POLLUTION, + + /** Soil quality has degraded below acceptable thresholds. */ + LOW_SOIL_QUALITY, + + /** An active ecosystem event is currently affecting the region. */ + ACTIVE_ECOSYSTEM_EVENT, + + /** Debug trigger via command to force an immediate update. */ + FORCED_DEBUG_COMMAND, + + /** Save data requires migration due to schema changes. */ + SAVE_MIGRATION_REQUIRED; + +} diff --git a/src/main/java/com/livingworld/core/simulation/package-info.java b/src/main/java/com/livingworld/core/simulation/package-info.java new file mode 100644 index 0000000..2a214b9 --- /dev/null +++ b/src/main/java/com/livingworld/core/simulation/package-info.java @@ -0,0 +1,2 @@ +/** Core simulation package — simulation engine placeholders. */ +package com.livingworld.core.simulation; \ No newline at end of file diff --git a/src/main/java/com/livingworld/data/migration/package-info.java b/src/main/java/com/livingworld/data/migration/package-info.java new file mode 100644 index 0000000..7ce2f23 --- /dev/null +++ b/src/main/java/com/livingworld/data/migration/package-info.java @@ -0,0 +1,2 @@ +/** Data migration package — data versioning and migration utilities. */ +package com.livingworld.data.migration; \ No newline at end of file diff --git a/src/main/java/com/livingworld/data/package-info.java b/src/main/java/com/livingworld/data/package-info.java new file mode 100644 index 0000000..db13cc7 --- /dev/null +++ b/src/main/java/com/livingworld/data/package-info.java @@ -0,0 +1,2 @@ +/** Data package — data persistence and management layer. */ +package com.livingworld.data; \ No newline at end of file diff --git a/src/main/java/com/livingworld/data/saved/package-info.java b/src/main/java/com/livingworld/data/saved/package-info.java new file mode 100644 index 0000000..b214020 --- /dev/null +++ b/src/main/java/com/livingworld/data/saved/package-info.java @@ -0,0 +1,2 @@ +/** Data saved package — saved data structures and storage. */ +package com.livingworld.data.saved; \ No newline at end of file diff --git a/src/main/java/com/livingworld/data/serialization/PersistenceReader.java b/src/main/java/com/livingworld/data/serialization/PersistenceReader.java new file mode 100644 index 0000000..147dfc6 --- /dev/null +++ b/src/main/java/com/livingworld/data/serialization/PersistenceReader.java @@ -0,0 +1,61 @@ +package com.livingworld.data.serialization; + +/** + * Abstraction for reading save data from a persistence source. + * + *

This interface hides the underlying storage format (JSON, NBT, etc.) from + * modules. Modules read their data through this abstraction without knowing + * how or where it is stored.

+ */ +public interface PersistenceReader { + + /** + * Reads a string value for the given key. + * + * @param key the identifier for this value (must not be null or blank) + * @param defaultValue the default value to return if the key is missing + * @return the string value, or defaultValue if the key is missing + * @throws IllegalArgumentException if key is null or blank + */ + String readString(String key, String defaultValue); + + /** + * Reads an integer value for the given key. + * + * @param key the identifier for this value (must not be null or blank) + * @param defaultValue the default value to return if the key is missing + * @return the integer value, or defaultValue if the key is missing + * @throws IllegalArgumentException if key is null or blank + */ + int readInt(String key, int defaultValue); + + /** + * Reads a long value for the given key. + * + * @param key the identifier for this value (must not be null or blank) + * @param defaultValue the default value to return if the key is missing + * @return the long value, or defaultValue if the key is missing + * @throws IllegalArgumentException if key is null or blank + */ + long readLong(String key, long defaultValue); + + /** + * Reads a double value for the given key. + * + * @param key the identifier for this value (must not be null or blank) + * @param defaultValue the default value to return if the key is missing + * @return the double value, or defaultValue if the key is missing + * @throws IllegalArgumentException if key is null or blank + */ + double readDouble(String key, double defaultValue); + + /** + * Reads a boolean value for the given key. + * + * @param key the identifier for this value (must not be null or blank) + * @param defaultValue the default value to return if the key is missing + * @return the boolean value, or defaultValue if the key is missing + * @throws IllegalArgumentException if key is null or blank + */ + boolean readBoolean(String key, boolean defaultValue); +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/data/serialization/PersistenceWriter.java b/src/main/java/com/livingworld/data/serialization/PersistenceWriter.java new file mode 100644 index 0000000..6436b6c --- /dev/null +++ b/src/main/java/com/livingworld/data/serialization/PersistenceWriter.java @@ -0,0 +1,56 @@ +package com.livingworld.data.serialization; + +/** + * Abstraction for writing save data to a persistence target. + * + *

This interface hides the underlying storage format (JSON, NBT, etc.) from + * modules. Modules write their data through this abstraction without knowing + * how or where it is persisted.

+ */ +public interface PersistenceWriter { + + /** + * Writes a string value with the given key. + * + * @param key the identifier for this value (must not be null or blank) + * @param value the string to write + * @throws IllegalArgumentException if key is null or blank + */ + void writeString(String key, String value); + + /** + * Writes an integer value with the given key. + * + * @param key the identifier for this value (must not be null or blank) + * @param value the integer to write + * @throws IllegalArgumentException if key is null or blank + */ + void writeInt(String key, int value); + + /** + * Writes a long value with the given key. + * + * @param key the identifier for this value (must not be null or blank) + * @param value the long to write + * @throws IllegalArgumentException if key is null or blank + */ + void writeLong(String key, long value); + + /** + * Writes a double value with the given key. + * + * @param key the identifier for this value (must not be null or blank) + * @param value the double to write + * @throws IllegalArgumentException if key is null or blank + */ + void writeDouble(String key, double value); + + /** + * Writes a boolean value with the given key. + * + * @param key the identifier for this value (must not be null or blank) + * @param value the boolean to write + * @throws IllegalArgumentException if key is null or blank + */ + void writeBoolean(String key, boolean value); +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/data/serialization/package-info.java b/src/main/java/com/livingworld/data/serialization/package-info.java new file mode 100644 index 0000000..65f66c5 --- /dev/null +++ b/src/main/java/com/livingworld/data/serialization/package-info.java @@ -0,0 +1,2 @@ +/** Data serialization package — data serialization and deserialization utilities. */ +package com.livingworld.data.serialization; \ No newline at end of file diff --git a/src/main/java/com/livingworld/debug/DiagnosticCategory.java b/src/main/java/com/livingworld/debug/DiagnosticCategory.java new file mode 100644 index 0000000..7ae198e --- /dev/null +++ b/src/main/java/com/livingworld/debug/DiagnosticCategory.java @@ -0,0 +1,43 @@ +package com.livingworld.debug; + +/** + * Diagnostic categories for grouping logs and profiler entries. + *

+ * Categories map directly to the mod's architectural modules so that logs + * can be filtered by subsystem at runtime. + */ +public enum DiagnosticCategory { + // ── Core systems ────────────────────────────────────────── + BOOTSTRAP("Bootstrap"), + CONFIG("Config"), + REGIONS("Regions"), + SIMULATION("Simulation"), + PERSISTENCE("Persistence"), + EVENTS("Events"), + COMMANDS("Commands"), + NETWORKING("Networking"), + PLATFORM("Platform"), + + // ── Ecosystem sub-modules ───────────────────────────────── + ECOSYSTEM("Ecosystem"), + VEGETATION("Vegetation"), + SOIL("Soil"), + WATER("Water"), + RESOURCES("Resources"), + POLLUTION("Pollution"), + RECOVERY("Recovery"), + WORLD_EFFECTS("WorldEffects"), + + TESTING("Testing"); + + private final String displayName; + + DiagnosticCategory(String displayName) { + this.displayName = displayName; + } + + /** Returns a human-readable name for the category. */ + public String displayName() { + return displayName; + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/debug/LivingWorldLogger.java b/src/main/java/com/livingworld/debug/LivingWorldLogger.java new file mode 100644 index 0000000..81ef567 --- /dev/null +++ b/src/main/java/com/livingworld/debug/LivingWorldLogger.java @@ -0,0 +1,74 @@ +package com.livingworld.debug; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Central logging wrapper for the Living World mod. + *

+ * All core systems should log through this class to ensure consistent + * formatting and category grouping. Debug logs respect a future debug + * configuration flag (TODO: wire config later). + */ +public final class LivingWorldLogger { + + private static Logger logger = LoggerFactory.getLogger("LivingWorld"); + + // Prevent instantiation + private LivingWorldLogger() { + } + + /** Set the underlying SLF4J logger. */ + public static void setLogger(Logger newLogger) { + if (newLogger == null) { + throw new IllegalArgumentException("Logger must not be null"); + } + logger = newLogger; + } + + // ── Basic logging methods ─────────────────────────────── + + public static void info(String message) { + logger.info(message); + } + + public static void warn(String message) { + logger.warn(message); + } + + public static void error(String message) { + logger.error(message); + } + + public static void error(String message, Throwable throwable) { + logger.error(message, throwable); + } + + public static void debug(String message) { + // TODO: Respect debug config flag when ConfigService is available. + logger.debug(message); + } + + // ── Category-aware logging methods ────────────────────── + + public static void info(DiagnosticCategory category, String message) { + logger.info("[{}] {}", category.displayName(), message); + } + + public static void warn(DiagnosticCategory category, String message) { + logger.warn("[{}] {}", category.displayName(), message); + } + + public static void error(DiagnosticCategory category, String message) { + logger.error("[{}] {}", category.displayName(), message); + } + + public static void error(DiagnosticCategory category, String message, Throwable throwable) { + logger.error("[{}] {}", category.displayName(), message, throwable); + } + + public static void debug(DiagnosticCategory category, String message) { + // TODO: Respect debug config flag when ConfigService is available. + logger.debug("[{}] {}", category.displayName(), message); + } +} diff --git a/src/main/java/com/livingworld/debug/package-info.java b/src/main/java/com/livingworld/debug/package-info.java new file mode 100644 index 0000000..8811f3b --- /dev/null +++ b/src/main/java/com/livingworld/debug/package-info.java @@ -0,0 +1,2 @@ +/** Debug package — debugging utilities and diagnostic tools. */ +package com.livingworld.debug; \ No newline at end of file diff --git a/src/main/java/com/livingworld/events/BaseLivingWorldEvent.java b/src/main/java/com/livingworld/events/BaseLivingWorldEvent.java new file mode 100644 index 0000000..ef78b87 --- /dev/null +++ b/src/main/java/com/livingworld/events/BaseLivingWorldEvent.java @@ -0,0 +1,80 @@ +package com.livingworld.events; + +/** + * Immutable base implementation of {@link LivingWorldEvent}. + * + *

This class provides a simple, validated event object that ecosystem modules + * and other simulation components can instantiate directly. All fields are final + * and the class is immutable after construction.

+ */ +public final class BaseLivingWorldEvent implements LivingWorldEvent { + + private final String eventType; + private final long simulationTick; + private final String sourceModuleId; + + /** + * Creates a new base living world event. + * + * @param eventType the event type identifier (must not be null or blank) + * @param simulationTick the simulation tick at which this event occurred (must not be negative) + * @param sourceModuleId the module ID that generated this event (may be "core" for core events, must not be null or blank) + * @throws IllegalArgumentException if any field violates validation rules + */ + public BaseLivingWorldEvent(String eventType, long simulationTick, String sourceModuleId) { + if (eventType == null || eventType.isBlank()) { + throw new IllegalArgumentException("eventType must not be null or blank"); + } + if (simulationTick < 0) { + throw new IllegalArgumentException("simulationTick must not be negative"); + } + if (sourceModuleId == null || sourceModuleId.isBlank()) { + throw new IllegalArgumentException("sourceModuleId must not be null or blank"); + } + + this.eventType = eventType; + this.simulationTick = simulationTick; + this.sourceModuleId = sourceModuleId; + } + + /** + * Returns the event type identifier. + * + * @return the event type (never null or blank) + */ + @Override + public String eventType() { + return eventType; + } + + /** + * Returns the simulation tick at which this event occurred. + * + * @return the simulation tick (never negative) + */ + @Override + public long simulationTick() { + return simulationTick; + } + + /** + * Returns the module ID that generated this event. + * + * @return the source module ID (may be "core" for core-generated events, never null or blank) + */ + @Override + public String sourceModuleId() { + return sourceModuleId; + } + + /** + * Returns a string representation of this event. + * + * @return a formatted string with eventType, simulationTick, and sourceModuleId + */ + @Override + public String toString() { + return "BaseLivingWorldEvent{eventType='" + eventType + "', simulationTick=" + simulationTick + + ", sourceModuleId='" + sourceModuleId + "'}"; + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/events/LivingWorldEvent.java b/src/main/java/com/livingworld/events/LivingWorldEvent.java new file mode 100644 index 0000000..5d8574c --- /dev/null +++ b/src/main/java/com/livingworld/events/LivingWorldEvent.java @@ -0,0 +1,31 @@ +package com.livingworld.events; + +/** + * Represents a Living World simulation event. + * + *

Events are immutable data objects that carry information about changes, + * occurrences, or state transitions within the Living World simulation layer.

+ */ +public interface LivingWorldEvent { + + /** + * Returns the event type identifier. + * + * @return the event type string (must not be blank) + */ + String eventType(); + + /** + * Returns the simulation tick at which this event occurred. + * + * @return the simulation tick (never negative) + */ + long simulationTick(); + + /** + * Returns the module ID that generated or published this event. + * + * @return the source module ID (may be "core" for core-generated events, but must not be blank) + */ + String sourceModuleId(); +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/events/LivingWorldEventBus.java b/src/main/java/com/livingworld/events/LivingWorldEventBus.java new file mode 100644 index 0000000..94bf1ba --- /dev/null +++ b/src/main/java/com/livingworld/events/LivingWorldEventBus.java @@ -0,0 +1,172 @@ +package com.livingworld.events; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.livingworld.debug.DiagnosticCategory; +import com.livingworld.debug.LivingWorldLogger; + +/** + * Central event bus for publishing and subscribing to Living World simulation events. + * + *

This class routes events by type to registered listeners, provides metrics + * on published count and listener counts per event type, and includes a simple + * recursion guard to prevent event storms within the same tick.

+ */ +public final class LivingWorldEventBus { + + private static final int MAX_EVENTS_PER_DISPATCH = 10_000; + + private final Map> listeners; + private final Deque pendingEvents; + private int publishedCount; + private boolean dispatching; + + /** + * Creates a new empty event bus. + */ + public LivingWorldEventBus() { + this.listeners = new java.util.LinkedHashMap<>(); + this.pendingEvents = new ArrayDeque<>(); + } + + // ------------------------------------------------------------------ + // Registration + // ------------------------------------------------------------------ + + /** + * Registers a listener for events of the given type. + * + *

If listeners are already registered for the event type, this adds the + * new listener to the end of the list. Listeners are invoked in registration + * order.

+ * + * @param eventType the event type to listen for (must not be blank) + * @param listener the listener to register (must not be null) + * @throws IllegalArgumentException if eventType is null or blank, or listener is null + */ + public void register(String eventType, LivingWorldEventListener listener) { + if (eventType == null || eventType.isBlank()) { + throw new IllegalArgumentException("eventType must not be null or blank"); + } + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + + listeners.computeIfAbsent(eventType, k -> new ArrayList<>()).add(listener); + } + + // ------------------------------------------------------------------ + // Publishing + // ------------------------------------------------------------------ + + /** + * Publishes an event to all registered listeners for its type. + * + *

The event is dispatched in a loop to each listener. If the number of + * listeners grows very large, consider using {@code CopyOnWriteArrayList} + * instead of {@code ArrayList} for the listeners map (currently TODO).

+ * + * @param event the event to publish (must not be null) + * @throws IllegalArgumentException if event is null + */ + public void publish(LivingWorldEvent event) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + + pendingEvents.addLast(event); + if (dispatching) { + return; + } + + dispatching = true; + int dispatched = 0; + try { + while (!pendingEvents.isEmpty()) { + if (dispatched >= MAX_EVENTS_PER_DISPATCH) { + int dropped = pendingEvents.size(); + pendingEvents.clear(); + LivingWorldLogger.warn(DiagnosticCategory.EVENTS, + "Stopped recursive event dispatch after " + MAX_EVENTS_PER_DISPATCH + + " events; dropped " + dropped + " queued events"); + break; + } + + LivingWorldEvent nextEvent = pendingEvents.removeFirst(); + publishedCount++; + dispatched++; + + List listenersForType = listeners.get(nextEvent.eventType()); + if (listenersForType == null) { + continue; + } + + for (LivingWorldEventListener listener : List.copyOf(listenersForType)) { + try { + listener.onEvent(nextEvent); + } catch (Exception e) { + LivingWorldLogger.error(DiagnosticCategory.EVENTS, + "Event listener failed for type " + nextEvent.eventType(), e); + } + } + } + } finally { + dispatching = false; + } + } + + // ------------------------------------------------------------------ + // Metrics / Inspection + // ------------------------------------------------------------------ + + /** + * Returns the total number of events published so far. + * + * @return the published event count (never negative) + */ + public int getPublishedEventCount() { + return publishedCount; + } + + /** + * Returns the number of listeners registered for the given event type. + * + * @param eventType the event type to query (must not be null or blank) + * @return the listener count, or 0 if no listeners are registered for this type + * @throws IllegalArgumentException if eventType is null or blank + */ + public int getListenerCount(String eventType) { + if (eventType == null || eventType.isBlank()) { + throw new IllegalArgumentException("eventType must not be null or blank"); + } + + List typeListeners = this.listeners.get(eventType); + return typeListeners != null ? typeListeners.size() : 0; + } + + /** + * Returns a snapshot of all registered event types and their listener counts. + * + * @return an unmodifiable map from eventType to listener count (never null) + */ + public Map getAllListenerCounts() { + return listeners.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> e.getValue().size() + )); + } + + /** + * Retained for source compatibility. Dispatch state is now cleared + * automatically after each publication batch. + */ + public void clearSuppressionForCurrentTick() { + // No-op. + } +} diff --git a/src/main/java/com/livingworld/events/LivingWorldEventListener.java b/src/main/java/com/livingworld/events/LivingWorldEventListener.java new file mode 100644 index 0000000..d5cf308 --- /dev/null +++ b/src/main/java/com/livingworld/events/LivingWorldEventListener.java @@ -0,0 +1,22 @@ +package com.livingworld.events; + +/** + * Functional interface for receiving Living World simulation events. + * + *

This interface is implemented by components that need to react to events + * generated during the simulation tick cycle. Ecosystem modules and other + * simulation components can implement this to subscribe to specific event types.

+ */ +@FunctionalInterface +public interface LivingWorldEventListener { + + /** + * Callback invoked when a Living World event occurs. + * + *

The default implementation does nothing, allowing {@code @Override} + * implementations to handle only relevant events while ignoring others.

+ * + * @param event the event that occurred (never null) + */ + void onEvent(LivingWorldEvent event); +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/events/package-info.java b/src/main/java/com/livingworld/events/package-info.java new file mode 100644 index 0000000..64c3a8d --- /dev/null +++ b/src/main/java/com/livingworld/events/package-info.java @@ -0,0 +1,2 @@ +/** Events package — custom mod event definitions. */ +package com.livingworld.events; \ No newline at end of file diff --git a/src/main/java/com/livingworld/integration/minecraft/package-info.java b/src/main/java/com/livingworld/integration/minecraft/package-info.java new file mode 100644 index 0000000..0fafd0f --- /dev/null +++ b/src/main/java/com/livingworld/integration/minecraft/package-info.java @@ -0,0 +1,2 @@ +/** Integration Minecraft package — Minecraft integration layer. */ +package com.livingworld.integration.minecraft; \ No newline at end of file diff --git a/src/main/java/com/livingworld/modules/HistoryRecord.java b/src/main/java/com/livingworld/modules/HistoryRecord.java new file mode 100644 index 0000000..93a65a0 --- /dev/null +++ b/src/main/java/com/livingworld/modules/HistoryRecord.java @@ -0,0 +1,69 @@ +package com.livingworld.modules; + +/** + * Minimal placeholder for a historical record entry. + * + *

TODO: This class is temporary and will be moved to an appropriate package + * (e.g. {@code com.livingworld.core} or {@code com.livingworld.data}) once the + * data layer design is finalised.

+ */ +public final class HistoryRecord { + + private final String moduleId; + private final long tick; + private final String recordType; + private final String data; + + /** + * Creates a new HistoryRecord. + * + * @param moduleId the module that generated this record (must not be null or blank) + * @param tick the simulation tick at which this record was created + * @param recordType the type/category of this record (may be null, defaults to "generic") + * @param data free-form data string for this record (may be null, defaults to empty) + */ + public HistoryRecord(String moduleId, long tick, String recordType, String data) { + if (moduleId == null || moduleId.isBlank()) { + throw new IllegalArgumentException("moduleId must not be null or blank"); + } + + this.moduleId = moduleId; + this.tick = tick; + this.recordType = recordType != null ? recordType : "generic"; + this.data = data != null ? data : ""; + } + + /** + * Returns the module ID that generated this record. + */ + public String moduleId() { + return moduleId; + } + + /** + * Returns the simulation tick at which this record was created. + */ + public long tick() { + return tick; + } + + /** + * Returns the type/category of this record. + */ + public String recordType() { + return recordType; + } + + /** + * Returns the free-form data associated with this record. + */ + public String data() { + return data; + } + + @Override + public String toString() { + return "HistoryRecord{moduleId=" + moduleId + ", tick=" + tick + + ", recordType='" + recordType + "', data='" + data + "'}"; + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/modules/ModuleContext.java b/src/main/java/com/livingworld/modules/ModuleContext.java new file mode 100644 index 0000000..795efbd --- /dev/null +++ b/src/main/java/com/livingworld/modules/ModuleContext.java @@ -0,0 +1,51 @@ +package com.livingworld.modules; + +import com.livingworld.core.services.ServiceKey; +import com.livingworld.core.services.ServiceRegistry; + +/** + * Dependency container passed to modules during initialisation. + * + *

This class provides controlled access to registered services through the + * {@link ServiceRegistry}. Modules should only request the services they are + * allowed to use via this context.

+ */ +public final class ModuleContext { + + private final ServiceRegistry services; + + /** + * Creates a new ModuleContext with the given service registry. + * + * @param services the service registry (must not be null) + * @throws IllegalArgumentException if services is null + */ + public ModuleContext(ServiceRegistry services) { + if (services == null) { + throw new IllegalArgumentException("services must not be null"); + } + this.services = services; + } + + /** + * Returns the service associated with the given key. + * + * @param the service type + * @param key the service key (must not be null) + * @return the registered service + * @throws IllegalArgumentException if key is null or services are missing + */ + public T getService(ServiceKey key) { + return services.get(key); + } + + /** + * Returns whether a service with the given key has been registered. + * + * @param key the service key (must not be null) + * @return true if the service is registered + */ + public boolean hasService(ServiceKey key) { + return services.isRegistered(key); + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/modules/ModuleMetadata.java b/src/main/java/com/livingworld/modules/ModuleMetadata.java new file mode 100644 index 0000000..fd358d7 --- /dev/null +++ b/src/main/java/com/livingworld/modules/ModuleMetadata.java @@ -0,0 +1,137 @@ +package com.livingworld.modules; + +import java.util.Collections; +import java.util.ArrayList; +import java.util.List; + +/** + * Immutable metadata describing a simulation module. + * + *

This class validates that essential fields are present and normalises + * null dependency lists into empty lists for safe downstream usage.

+ */ +public final class ModuleMetadata { + + private final String moduleId; + private final String displayName; + private final String version; + private final String description; + private final String requiredCoreVersion; + private final List dependencies; + private final List optionalDependencies; + private final boolean defaultEnabled; + private final boolean serverOnly; + private final boolean experimental; + + /** + * Creates new ModuleMetadata with the given values. + * + * @param moduleId unique module identifier (must not be null or blank) + * @param displayName human-readable name (must not be null or blank) + * @param version semantic version string (may be null, defaults to "0.0.0") + * @param description short description of the module (may be null, defaults to empty) + * @param requiredCoreVersion minimum core schema version required (may be null, defaults to "1") + * @param dependencies list of required module IDs (null becomes empty list) + * @param optionalDependencies list of optional module IDs (null becomes empty list) + * @param defaultEnabled whether the module is enabled by default + * @param serverOnly whether this module only runs on dedicated servers + * @param experimental whether this module is experimental and may change behaviour + */ + public ModuleMetadata(String moduleId, String displayName, String version, + String description, String requiredCoreVersion, + List dependencies, List optionalDependencies, + boolean defaultEnabled, boolean serverOnly, boolean experimental) { + + if (moduleId == null || moduleId.isBlank()) { + throw new IllegalArgumentException("moduleId must not be null or blank"); + } + if (displayName == null || displayName.isBlank()) { + throw new IllegalArgumentException("displayName must not be null or blank"); + } + + this.moduleId = moduleId; + this.displayName = displayName; + this.version = version != null ? version : "0.0.0"; + this.description = description != null ? description : ""; + this.requiredCoreVersion = requiredCoreVersion != null ? requiredCoreVersion : "1"; + this.dependencies = dependencies != null + ? Collections.unmodifiableList(new ArrayList<>(dependencies)) + : Collections.emptyList(); + this.optionalDependencies = optionalDependencies != null + ? Collections.unmodifiableList(new ArrayList<>(optionalDependencies)) + : Collections.emptyList(); + this.defaultEnabled = defaultEnabled; + this.serverOnly = serverOnly; + this.experimental = experimental; + } + + /** + * Returns the unique module identifier. + */ + public String moduleId() { + return moduleId; + } + + /** + * Returns the human-readable display name. + */ + public String displayName() { + return displayName; + } + + /** + * Returns the semantic version string. + */ + public String version() { + return version; + } + + /** + * Returns the description of this module. + */ + public String description() { + return description; + } + + /** + * Returns the minimum core schema version required by this module. + */ + public String requiredCoreVersion() { + return requiredCoreVersion; + } + + /** + * Returns an unmodifiable list of required dependency module IDs. + */ + public List dependencies() { + return dependencies; + } + + /** + * Returns an unmodifiable list of optional dependency module IDs. + */ + public List optionalDependencies() { + return optionalDependencies; + } + + /** + * Returns whether this module is enabled by default. + */ + public boolean defaultEnabled() { + return defaultEnabled; + } + + /** + * Returns whether this module only runs on dedicated servers. + */ + public boolean serverOnly() { + return serverOnly; + } + + /** + * Returns whether this module is experimental and may change behaviour. + */ + public boolean experimental() { + return experimental; + } +} diff --git a/src/main/java/com/livingworld/modules/ModuleRegistry.java b/src/main/java/com/livingworld/modules/ModuleRegistry.java new file mode 100644 index 0000000..7cdd2cf --- /dev/null +++ b/src/main/java/com/livingworld/modules/ModuleRegistry.java @@ -0,0 +1,160 @@ +package com.livingworld.modules; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Central registry for simulation modules. + * + *

This class manages module registration, lookup, lifecycle coordination, + * and filtering. It is intentionally simple for v1 — dependency sorting + * and advanced lifecycle management are deferred to later milestones.

+ * + *

Thread safety: This class is not thread-safe. All operations must + * be performed on the server main thread.

+ */ +public final class ModuleRegistry { + + private final Map modules; + + /** + * Creates a new empty module registry. + */ + public ModuleRegistry() { + this.modules = new LinkedHashMap<>(); + } + + // ------------------------------------------------------------------ + // Registration + // ------------------------------------------------------------------ + + /** + * Registers a simulation module. + * + *

If a module with the same ID is already registered, an + * {@link IllegalArgumentException} is thrown to prevent silent overwrites.

+ * + * @param module the module to register (must not be null) + * @throws IllegalArgumentException if module is null or a module with the same ID is already registered + */ + public void register(SimulationModule module) { + if (module == null) { + throw new IllegalArgumentException("module must not be null"); + } + + ModuleMetadata metadata = module.getMetadata(); + if (metadata == null) { + throw new IllegalArgumentException("module metadata must not be null"); + } + String moduleId = module.getModuleId(); + if (moduleId == null || moduleId.isBlank()) { + throw new IllegalArgumentException("module ID must not be null or blank"); + } + if (!moduleId.equals(metadata.moduleId())) { + throw new IllegalArgumentException( + "module ID '" + moduleId + "' does not match metadata ID '" + metadata.moduleId() + "'"); + } + if (modules.containsKey(moduleId)) { + throw new IllegalArgumentException( + "A module with ID '" + moduleId + "' is already registered"); + } + + modules.put(moduleId, module); + } + + // ------------------------------------------------------------------ + // Lookup + // ------------------------------------------------------------------ + + /** + * Returns all registered modules in an unmodifiable list. + * + * @return an unmodifiable list of all registered modules (never null) + */ + public List getAllModules() { + return Collections.unmodifiableList(new ArrayList<>(modules.values())); + } + + /** + * Returns enabled modules — those where {@code metadata.defaultEnabled()} is true. + * + * @return an unmodifiable list of enabled modules (never null) + */ + public List getEnabledModules() { + List enabled = new ArrayList<>(); + for (SimulationModule module : modules.values()) { + if (module.getMetadata().defaultEnabled()) { + enabled.add(module); + } + } + return Collections.unmodifiableList(enabled); + } + + /** + * Finds a module by its unique identifier. + * + * @param moduleId the module ID to look up (must not be null or blank) + * @return an optional containing the module if found, otherwise empty + * @throws IllegalArgumentException if moduleId is null or blank + */ + public Optional find(String moduleId) { + if (moduleId == null || moduleId.isBlank()) { + throw new IllegalArgumentException("moduleId must not be null or blank"); + } + return Optional.ofNullable(modules.get(moduleId)); + } + + // ------------------------------------------------------------------ + // Lifecycle coordination + // ------------------------------------------------------------------ + + /** + * Initialises all registered modules. + * + *

Each module is initialised in registration order. If one module's + * {@code initialize()} call throws an exception, the remaining modules + * are still attempted so that partial initialisation diagnostics can be + * collected.

+ * + *

TODO (Milestone 9): Implement dependency-aware topological sort + * so that modules are initialised in the correct order based on their + * {@code ModuleMetadata.dependencies()} list.

+ * + * @param context the module context providing service access (must not be null) + */ + public void initializeAll(ModuleContext context) { + if (context == null) { + throw new IllegalArgumentException("context must not be null"); + } + for (SimulationModule module : modules.values()) { + module.initialize(context); + } + } + + /** + * Shuts down all registered modules. + * + *

Each module's {@code shutdown()} method is called in reverse registration + * order. Exceptions from one module do not prevent other modules from + * shutting down.

+ */ + public void shutdownAll() { + // Iterate in reverse to shut down in opposite order of initialisation + List reversed = new ArrayList<>(modules.values()); + Collections.reverse(reversed); + + for (SimulationModule module : reversed) { + try { + module.shutdown(); + } catch (Exception e) { + com.livingworld.debug.LivingWorldLogger.error( + com.livingworld.debug.DiagnosticCategory.SIMULATION, + "Module shutdown failed: " + module.getModuleId(), e); + } + } + } +} diff --git a/src/main/java/com/livingworld/modules/ModuleUpdateResult.java b/src/main/java/com/livingworld/modules/ModuleUpdateResult.java new file mode 100644 index 0000000..09597b1 --- /dev/null +++ b/src/main/java/com/livingworld/modules/ModuleUpdateResult.java @@ -0,0 +1,95 @@ +package com.livingworld.modules; + +import com.livingworld.events.LivingWorldEvent; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Immutable result returned by a module after updating a region. + * + *

This class prevents modules from mutating global systems directly during + * update. Instead, they return results which SimulationManager applies in a + * controlled way.

+ */ +public final class ModuleUpdateResult { + + private final boolean changedRegion; + private final List generatedEvents; + private final List historyRecords; + private final int estimatedCost; + private final List warnings; + + /** + * Creates a new ModuleUpdateResult with the given values. + * + * @param changedRegion whether this update modified the region + * @param generatedEvents list of events generated during the update (null becomes empty list) + * @param historyRecords list of historical records to persist (null becomes empty list) + * @param estimatedCost estimated computational cost (must not be negative) + * @param warnings list of warning messages from this update (null becomes empty list) + */ + public ModuleUpdateResult(boolean changedRegion, List generatedEvents, + List historyRecords, int estimatedCost, List warnings) { + + if (estimatedCost < 0) { + throw new IllegalArgumentException("estimatedCost must not be negative"); + } + + this.changedRegion = changedRegion; + this.generatedEvents = generatedEvents != null ? Collections.unmodifiableList(new ArrayList<>(generatedEvents)) : Collections.emptyList(); + this.historyRecords = historyRecords != null ? Collections.unmodifiableList(new ArrayList<>(historyRecords)) : Collections.emptyList(); + this.estimatedCost = estimatedCost; + this.warnings = warnings != null ? Collections.unmodifiableList(new ArrayList<>(warnings)) : Collections.emptyList(); + } + + /** + * Returns a result indicating no changes were made to the region. + */ + public static ModuleUpdateResult noChange() { + return new ModuleUpdateResult(false, Collections.emptyList(), Collections.emptyList(), 0, Collections.emptyList()); + } + + /** + * Returns a result indicating the region was changed. + */ + public static ModuleUpdateResult changed() { + return new ModuleUpdateResult(true, Collections.emptyList(), Collections.emptyList(), 0, Collections.emptyList()); + } + + /** + * Returns whether this update modified the region. + */ + public boolean changedRegion() { + return changedRegion; + } + + /** + * Returns an unmodifiable list of events generated during the update. + */ + public List generatedEvents() { + return generatedEvents; + } + + /** + * Returns an unmodifiable list of historical records to persist. + */ + public List historyRecords() { + return historyRecords; + } + + /** + * Returns the estimated computational cost of this update. + */ + public int estimatedCost() { + return estimatedCost; + } + + /** + * Returns an unmodifiable list of warning messages from this update. + */ + public List warnings() { + return warnings; + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/modules/RegionUpdateContext.java b/src/main/java/com/livingworld/modules/RegionUpdateContext.java new file mode 100644 index 0000000..daed124 --- /dev/null +++ b/src/main/java/com/livingworld/modules/RegionUpdateContext.java @@ -0,0 +1,37 @@ +package com.livingworld.modules; + +import com.livingworld.regions.Region; + +/** + * Minimal context passed to modules during a region update call. + * + *

This is a placeholder class for Milestone 8. It provides access to the + * region being updated and can be extended later with additional information + * such as time service, profiler data, or event bus references.

+ */ +public final class RegionUpdateContext { + + private final Region region; + + /** + * Creates a new RegionUpdateContext for the given region. + * + * @param region the region being updated (must not be null) + * @throws IllegalArgumentException if region is null + */ + public RegionUpdateContext(Region region) { + if (region == null) { + throw new IllegalArgumentException("region must not be null"); + } + this.region = region; + } + + /** + * Returns the region being updated. + * + * @return the region (never null) + */ + public Region getRegion() { + return region; + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/modules/ServerContext.java b/src/main/java/com/livingworld/modules/ServerContext.java new file mode 100644 index 0000000..6037398 --- /dev/null +++ b/src/main/java/com/livingworld/modules/ServerContext.java @@ -0,0 +1,18 @@ +package com.livingworld.modules; + +/** + * Minimal context passed to modules when the server starts. + * + *

This is a placeholder class for Milestone 8. It provides a simple container + * that can be extended later with additional server-side information such as + * world access, player lists, or configuration state.

+ */ +public final class ServerContext { + + /** + * Creates a new ServerContext instance. + */ + public ServerContext() { + // minimal placeholder for Milestone 8; extend as needed + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/modules/SimulationModule.java b/src/main/java/com/livingworld/modules/SimulationModule.java new file mode 100644 index 0000000..a9dbf74 --- /dev/null +++ b/src/main/java/com/livingworld/modules/SimulationModule.java @@ -0,0 +1,141 @@ +package com.livingworld.modules; + +import com.livingworld.events.LivingWorldEvent; +import com.livingworld.regions.Region; +import com.livingworld.data.serialization.PersistenceReader; +import com.livingworld.data.serialization.PersistenceWriter; + +/** + * Interface that all simulation modules must implement. + * + *

A SimulationModule is a self-contained unit of simulation logic that operates + * on {@link Region} instances. Modules communicate through services, public data + * contracts, or events -- never by directly mutating other systems.

+ * + *

Modules receive a {@link ModuleContext} during initialisation which provides + * access to registered core services. They must not create or depend on other + * modules directly.

+ */ +public interface SimulationModule { + + // ------------------------------------------------------------------ + // Identity + // ------------------------------------------------------------------ + + /** + * Returns the unique module identifier. + * + * @return the module ID (must match {@code ModuleMetadata.moduleId()}) + */ + String getModuleId(); + + /** + * Returns the metadata describing this module. + * + * @return the module metadata (never null) + */ + ModuleMetadata getMetadata(); + + // ------------------------------------------------------------------ + // Lifecycle methods + // ------------------------------------------------------------------ + + /** + * Initialises this module with access to core services. + * + *

This method is called once during module registration. Modules should + * validate their configuration and prepare any internal state here.

+ * + * @param context the module context providing service access (never null) + * @throws IllegalArgumentException if context is null + */ + void initialize(ModuleContext context); + + /** + * Called when the server has fully started. + * + *

This method allows modules to perform any server-startup tasks such as + * loading saved data or registering hooks.

+ * + * @param context the server context (never null) + */ + void onServerStarted(ServerContext context); + + // ------------------------------------------------------------------ + // Region operations + // ------------------------------------------------------------------ + + /** + * Creates default module-specific data for a new region. + * + *

This method is called when a new region is created via {@link com.livingworld.regions.RegionFactory}. + * The module should populate the region's {@code moduleData} with deterministic + * defaults based on world seed and region coordinate where randomness is needed.

+ * + * @param region the newly created region (never null) + */ + void createDefaultRegionData(Region region); + + /** + * Updates this module's logic for a specific region. + * + *

This method is called during each simulation cycle for regions that + * are selected for update. Modules should return a {@link ModuleUpdateResult} + * summarising any changes, events, or warnings rather than mutating global + * systems directly.

+ * + * @param context the region update context (never null) + * @return the module update result (never null) + */ + ModuleUpdateResult updateRegion(RegionUpdateContext context); + + // ------------------------------------------------------------------ + // Event handling + // ------------------------------------------------------------------ + + /** + * Handles a Living World simulation event. + * + *

Modules should react to relevant events and return results through + * {@link #updateRegion} rather than mutating systems directly.

+ * + * @param event the event (never null) + */ + void onLivingWorldEvent(LivingWorldEvent event); + + // ------------------------------------------------------------------ + // Persistence + // ------------------------------------------------------------------ + + /** + * Saves this module's data to a persistence writer. + * + *

Modules must never write files directly. All serialisation goes through + * the provided {@link PersistenceWriter} abstraction.

+ * + * @param writer the persistence writer (never null) + */ + void saveModuleData(PersistenceWriter writer); + + /** + * Loads this module's data from a persistence reader. + * + *

This method is called during region loading to restore saved state.

+ * + * @param reader the persistence reader (never null) + */ + void loadModuleData(PersistenceReader reader); + + // ------------------------------------------------------------------ + // Shutdown + // ------------------------------------------------------------------ + + /** + * Shuts down this module. + * + *

This method is called when the mod is being disabled or the server is + * stopping. Modules should release any resources, close connections, and + * perform cleanup here.

+ */ + void shutdown(); +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/modules/ecosystem/package-info.java b/src/main/java/com/livingworld/modules/ecosystem/package-info.java new file mode 100644 index 0000000..b8b2095 --- /dev/null +++ b/src/main/java/com/livingworld/modules/ecosystem/package-info.java @@ -0,0 +1,2 @@ +/** Modules ecosystem package — ecosystem simulation module. */ +package com.livingworld.modules.ecosystem; \ No newline at end of file diff --git a/src/main/java/com/livingworld/modules/package-info.java b/src/main/java/com/livingworld/modules/package-info.java new file mode 100644 index 0000000..b485b19 --- /dev/null +++ b/src/main/java/com/livingworld/modules/package-info.java @@ -0,0 +1,2 @@ +/** Modules package — modular system components base. */ +package com.livingworld.modules; \ No newline at end of file diff --git a/src/main/java/com/livingworld/modules/pollution/package-info.java b/src/main/java/com/livingworld/modules/pollution/package-info.java new file mode 100644 index 0000000..c14b35d --- /dev/null +++ b/src/main/java/com/livingworld/modules/pollution/package-info.java @@ -0,0 +1,2 @@ +/** Modules pollution package — pollution simulation module. */ +package com.livingworld.modules.pollution; \ No newline at end of file diff --git a/src/main/java/com/livingworld/modules/recovery/package-info.java b/src/main/java/com/livingworld/modules/recovery/package-info.java new file mode 100644 index 0000000..9f19f25 --- /dev/null +++ b/src/main/java/com/livingworld/modules/recovery/package-info.java @@ -0,0 +1,2 @@ +/** Modules recovery package — natural recovery simulation module. */ +package com.livingworld.modules.recovery; \ No newline at end of file diff --git a/src/main/java/com/livingworld/modules/resources/package-info.java b/src/main/java/com/livingworld/modules/resources/package-info.java new file mode 100644 index 0000000..940ca40 --- /dev/null +++ b/src/main/java/com/livingworld/modules/resources/package-info.java @@ -0,0 +1,2 @@ +/** Modules resources package — resource simulation module. */ +package com.livingworld.modules.resources; \ No newline at end of file diff --git a/src/main/java/com/livingworld/modules/soil/package-info.java b/src/main/java/com/livingworld/modules/soil/package-info.java new file mode 100644 index 0000000..d46e18a --- /dev/null +++ b/src/main/java/com/livingworld/modules/soil/package-info.java @@ -0,0 +1,2 @@ +/** Modules soil package — soil simulation module. */ +package com.livingworld.modules.soil; \ No newline at end of file diff --git a/src/main/java/com/livingworld/modules/vegetation/package-info.java b/src/main/java/com/livingworld/modules/vegetation/package-info.java new file mode 100644 index 0000000..de4f6a1 --- /dev/null +++ b/src/main/java/com/livingworld/modules/vegetation/package-info.java @@ -0,0 +1,2 @@ +/** Modules vegetation package — vegetation simulation module. */ +package com.livingworld.modules.vegetation; \ No newline at end of file diff --git a/src/main/java/com/livingworld/modules/water/package-info.java b/src/main/java/com/livingworld/modules/water/package-info.java new file mode 100644 index 0000000..d39427b --- /dev/null +++ b/src/main/java/com/livingworld/modules/water/package-info.java @@ -0,0 +1,2 @@ +/** Modules water package — water simulation module. */ +package com.livingworld.modules.water; \ No newline at end of file diff --git a/src/main/java/com/livingworld/modules/worldeffects/package-info.java b/src/main/java/com/livingworld/modules/worldeffects/package-info.java new file mode 100644 index 0000000..26ffa6c --- /dev/null +++ b/src/main/java/com/livingworld/modules/worldeffects/package-info.java @@ -0,0 +1,2 @@ +/** Modules world effects package — world effects simulation module. */ +package com.livingworld.modules.worldeffects; \ No newline at end of file diff --git a/src/main/java/com/livingworld/networking/package-info.java b/src/main/java/com/livingworld/networking/package-info.java new file mode 100644 index 0000000..c77fc24 --- /dev/null +++ b/src/main/java/com/livingworld/networking/package-info.java @@ -0,0 +1,2 @@ +/** Networking package — network protocol and message handling. */ +package com.livingworld.networking; \ No newline at end of file diff --git a/src/main/java/com/livingworld/platform/neoforge/package-info.java b/src/main/java/com/livingworld/platform/neoforge/package-info.java new file mode 100644 index 0000000..3f14fa0 --- /dev/null +++ b/src/main/java/com/livingworld/platform/neoforge/package-info.java @@ -0,0 +1,2 @@ +/** Platform NeoForge package — NeoForge-specific platform implementations. */ +package com.livingworld.platform.neoforge; \ No newline at end of file diff --git a/src/main/java/com/livingworld/platform/package-info.java b/src/main/java/com/livingworld/platform/package-info.java new file mode 100644 index 0000000..34518c1 --- /dev/null +++ b/src/main/java/com/livingworld/platform/package-info.java @@ -0,0 +1,2 @@ +/** Platform package — platform-agnostic abstractions and interfaces. */ +package com.livingworld.platform; \ No newline at end of file diff --git a/src/main/java/com/livingworld/regions/Region.java b/src/main/java/com/livingworld/regions/Region.java new file mode 100644 index 0000000..8bd66f6 --- /dev/null +++ b/src/main/java/com/livingworld/regions/Region.java @@ -0,0 +1,241 @@ +package com.livingworld.regions; + +import java.util.UUID; + +/** + * Represents a region within the game world. + * + *

A Region encapsulates identity, spatial position, lifecycle state, + * simulation metadata, flags, metrics, and module-specific data.

+ */ +public class Region { + + private UUID id; + private RegionCoordinate coordinate; + private RegionLifecycleState lifecycleState; + private long createdAtSimulationTick; + private long lastUpdatedSimulationTick; + private boolean dirty; + private RegionFlags flags; + private RegionMetrics metrics; + private RegionModuleData moduleData; + + /** + * Creates a new Region with the specified fields. + * + * @param id the unique identifier (must not be null) + * @param coordinate the spatial position (must not be null) + * @param lifecycleState the current lifecycle state (must not be null) + * @param createdAtSimulationTick the simulation tick when this region was created + * @param lastUpdatedSimulationTick the simulation tick of the last update + * @param dirty whether the region has unsaved changes + * @param flags the region flag state (must not be null) + * @param metrics the region metric data (must not be null) + * @param moduleData the module-specific data store (must not be null) + * @throws IllegalArgumentException if any required field is null + */ + public Region(UUID id, RegionCoordinate coordinate, RegionLifecycleState lifecycleState, + long createdAtSimulationTick, long lastUpdatedSimulationTick, boolean dirty, + RegionFlags flags, RegionMetrics metrics, RegionModuleData moduleData) { + if (id == null) { + throw new IllegalArgumentException("id must not be null"); + } + if (coordinate == null) { + throw new IllegalArgumentException("coordinate must not be null"); + } + if (lifecycleState == null) { + throw new IllegalArgumentException("lifecycleState must not be null"); + } + if (flags == null) { + throw new IllegalArgumentException("flags must not be null"); + } + if (metrics == null) { + throw new IllegalArgumentException("metrics must not be null"); + } + if (moduleData == null) { + throw new IllegalArgumentException("moduleData must not be null"); + } + + this.id = id; + this.coordinate = coordinate; + this.lifecycleState = lifecycleState; + this.createdAtSimulationTick = createdAtSimulationTick; + this.lastUpdatedSimulationTick = lastUpdatedSimulationTick; + this.dirty = dirty; + this.flags = flags; + this.metrics = metrics; + this.moduleData = moduleData; + } + + // ------------------------------------------------------------------ + // Dirty tracking + // ------------------------------------------------------------------ + + /** + * Marks this region as dirty, indicating unsaved changes. + */ + public void markDirty() { + this.dirty = true; + } + + /** + * Clears the dirty flag, indicating changes have been saved. + */ + public void clearDirty() { + this.dirty = false; + } + + /** + * Returns whether this region has unsaved changes. + * + * @return true if the region is dirty + */ + public boolean isDirty() { + return this.dirty; + } + + // ------------------------------------------------------------------ + // Simulation tick management + // ------------------------------------------------------------------ + + /** + * Updates the last simulated tick to the given value. + * + * @param tick the current simulation tick + */ + public void updateLastSimulatedTick(long tick) { + this.lastUpdatedSimulationTick = tick; + } + + // ------------------------------------------------------------------ + // Lifecycle state management + // ------------------------------------------------------------------ + + /** + * Sets the lifecycle state of this region. + * + * @param state the new lifecycle state (must not be null) + * @throws IllegalArgumentException if state is null + */ + public void setLifecycleState(RegionLifecycleState state) { + if (state == null) { + throw new IllegalArgumentException("lifecycleState must not be null"); + } + this.lifecycleState = state; + } + + // ------------------------------------------------------------------ + // Validation + // ------------------------------------------------------------------ + + /** + * Validates that all required fields are non-null. + * + * @throws IllegalStateException if any required field is null + */ + public void validate() { + if (this.id == null) { + throw new IllegalStateException("id must not be null"); + } + if (this.coordinate == null) { + throw new IllegalStateException("coordinate must not be null"); + } + if (this.lifecycleState == null) { + throw new IllegalStateException("lifecycleState must not be null"); + } + if (this.flags == null) { + throw new IllegalStateException("flags must not be null"); + } + if (this.metrics == null) { + throw new IllegalStateException("metrics must not be null"); + } + if (this.moduleData == null) { + throw new IllegalStateException("moduleData must not be null"); + } + } + + // ------------------------------------------------------------------ + // Getters and setters + // ------------------------------------------------------------------ + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + if (id == null) { + throw new IllegalArgumentException("id must not be null"); + } + this.id = id; + } + + public RegionCoordinate getCoordinate() { + return coordinate; + } + + public void setCoordinate(RegionCoordinate coordinate) { + if (coordinate == null) { + throw new IllegalArgumentException("coordinate must not be null"); + } + this.coordinate = coordinate; + } + + public RegionLifecycleState getLifecycleState() { + return lifecycleState; + } + + public long getCreatedAtSimulationTick() { + return createdAtSimulationTick; + } + + public void setCreatedAtSimulationTick(long createdAtSimulationTick) { + this.createdAtSimulationTick = createdAtSimulationTick; + } + + public long getLastUpdatedSimulationTick() { + return lastUpdatedSimulationTick; + } + + public RegionFlags getFlags() { + return flags; + } + + public void setFlags(RegionFlags flags) { + if (flags == null) { + throw new IllegalArgumentException("flags must not be null"); + } + this.flags = flags; + } + + public RegionMetrics getMetrics() { + return metrics; + } + + public void setMetrics(RegionMetrics metrics) { + if (metrics == null) { + throw new IllegalArgumentException("metrics must not be null"); + } + this.metrics = metrics; + } + + public RegionModuleData getModuleData() { + return moduleData; + } + + public void setModuleData(RegionModuleData moduleData) { + if (moduleData == null) { + throw new IllegalArgumentException("moduleData must not be null"); + } + this.moduleData = moduleData; + } + + // ------------------------------------------------------------------ + // Standard overrides + // ------------------------------------------------------------------ + + @Override + public String toString() { + return "Region{id=" + id + ", coordinate=" + coordinate + + ", lifecycleState=" + lifecycleState + "}"; + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/regions/RegionCoordinate.java b/src/main/java/com/livingworld/regions/RegionCoordinate.java new file mode 100644 index 0000000..01e43cb --- /dev/null +++ b/src/main/java/com/livingworld/regions/RegionCoordinate.java @@ -0,0 +1,182 @@ +package com.livingworld.regions; + +/** + * Immutable representation of a region coordinate within a dimension. + * + *

A RegionCoordinate stores the region index (x, z) rather than raw chunk or block + * coordinates. The region index identifies which 8×8-chunk region file contains a given + * position.

+ * + *

Negative coordinates are handled correctly using {@code Math#floorDiv}, matching + * Minecraft's own coordinate system where block -1 belongs to chunk -1, not chunk 0.

+ */ +public record RegionCoordinate(String dimensionId, int x, int z) { + + private static final int CHUNK_SIZE = 16; + + /** + * Creates a region coordinate for the given dimension and region indices. + * + * @param dimensionId the dimension identifier (must not be null or blank) + * @param x the region X index + * @param z the region Z index + */ + public RegionCoordinate { + if (dimensionId == null || dimensionId.isBlank()) { + throw new IllegalArgumentException("dimensionId must not be null or blank"); + } + } + + // ------------------------------------------------------------------ + // Static factory methods + // ------------------------------------------------------------------ + + /** + * Creates a RegionCoordinate from chunk coordinates. + * + *

The chunk coordinates are divided by {@code regionSizeChunks} using floor division + * to determine the region index.

+ * + * @param dimensionId the dimension identifier (must not be null or blank) + * @param chunkX the chunk X coordinate + * @param chunkZ the chunk Z coordinate + * @param regionSizeChunks the number of chunks along one axis in a region (must be >= 1) + * @return a RegionCoordinate representing the region that contains the given chunk + */ + public static RegionCoordinate fromChunk(String dimensionId, int chunkX, int chunkZ, int regionSizeChunks) { + validateRegionSize(regionSizeChunks); + int regionX = Math.floorDiv(chunkX, regionSizeChunks); + int regionZ = Math.floorDiv(chunkZ, regionSizeChunks); + return new RegionCoordinate(dimensionId, regionX, regionZ); + } + + /** + * Creates a RegionCoordinate from block coordinates. + * + *

Block coordinates are first converted to chunk coordinates using floor division + * by {@link #CHUNK_SIZE}, then the resulting chunk coordinates are passed to + * {@link #fromChunk(String, int, int, int)}.

+ * + * @param dimensionId the dimension identifier (must not be null or blank) + * @param blockX the block X coordinate + * @param blockZ the block Z coordinate + * @param regionSizeChunks the number of chunks along one axis in a region (must be >= 1) + * @return a RegionCoordinate representing the region that contains the given block + */ + public static RegionCoordinate fromBlock(String dimensionId, int blockX, int blockZ, int regionSizeChunks) { + validateRegionSize(regionSizeChunks); + int chunkX = Math.floorDiv(blockX, CHUNK_SIZE); + int chunkZ = Math.floorDiv(blockZ, CHUNK_SIZE); + return fromChunk(dimensionId, chunkX, chunkZ, regionSizeChunks); + } + + // ------------------------------------------------------------------ + // Validation helpers + // ------------------------------------------------------------------ + + private static void validateRegionSize(int regionSizeChunks) { + if (regionSizeChunks < 1) { + throw new IllegalArgumentException("regionSizeChunks must be >= 1, got: " + regionSizeChunks); + } + } + + // ------------------------------------------------------------------ + // Chunk boundary methods + // ------------------------------------------------------------------ + + /** + * Returns the minimum chunk X coordinate covered by this region. + * + * @param regionSizeChunks the number of chunks along one axis in a region (must be >= 1) + */ + public int minChunkX(int regionSizeChunks) { + validateRegionSize(regionSizeChunks); + return x() * regionSizeChunks; + } + + /** + * Returns the minimum chunk Z coordinate covered by this region. + * + * @param regionSizeChunks the number of chunks along one axis in a region (must be >= 1) + */ + public int minChunkZ(int regionSizeChunks) { + validateRegionSize(regionSizeChunks); + return z() * regionSizeChunks; + } + + /** + * Returns the maximum chunk X coordinate covered by this region. + * + * @param regionSizeChunks the number of chunks along one axis in a region (must be >= 1) + */ + public int maxChunkX(int regionSizeChunks) { + validateRegionSize(regionSizeChunks); + return (x() + 1) * regionSizeChunks - 1; + } + + /** + * Returns the maximum chunk Z coordinate covered by this region. + * + * @param regionSizeChunks the number of chunks along one axis in a region (must be >= 1) + */ + public int maxChunkZ(int regionSizeChunks) { + validateRegionSize(regionSizeChunks); + return (z() + 1) * regionSizeChunks - 1; + } + + // ------------------------------------------------------------------ + // Block center methods + // ------------------------------------------------------------------ + + /** + * Returns the X block coordinate of the centre of this region. + * + *

The centre is calculated as {@code floorDiv(minBlockX + maxBlockX, 2)} so that + * negative coordinates behave correctly.

+ * + * @param regionSizeChunks the number of chunks along one axis in a region (must be >= 1) + */ + public int centerBlockX(int regionSizeChunks) { + validateRegionSize(regionSizeChunks); + int minBlockX = minChunkX(regionSizeChunks) * CHUNK_SIZE; + int maxBlockX = (maxChunkX(regionSizeChunks) + 1) * CHUNK_SIZE - 1; + return Math.floorDiv(minBlockX + maxBlockX, 2); + } + + /** + * Returns the Z block coordinate of the centre of this region. + * + * @param regionSizeChunks the number of chunks along one axis in a region (must be >= 1) + */ + public int centerBlockZ(int regionSizeChunks) { + validateRegionSize(regionSizeChunks); + int minBlockZ = minChunkZ(regionSizeChunks) * CHUNK_SIZE; + int maxBlockZ = (maxChunkZ(regionSizeChunks) + 1) * CHUNK_SIZE - 1; + return Math.floorDiv(minBlockZ + maxBlockZ, 2); + } + + // ------------------------------------------------------------------ + // Stable identifier + // ------------------------------------------------------------------ + + /** + * Returns a stable string representation suitable for use as a HashMap key. + * + *

The format is {@code "dimensionId:x:z"} which is deterministic and does not + * depend on object identity.

+ */ + public String stableId() { + return dimensionId() + ":" + x() + ":" + z(); + } + + // ------------------------------------------------------------------ + // Standard overrides + // ------------------------------------------------------------------ + // equals() and hashCode() are provided by the record compiler. + // Only toString is overridden for a more readable debug format. + + @Override + public String toString() { + return "RegionCoordinate{dimensionId='" + dimensionId() + "', x=" + x() + ", z=" + z() + "}"; + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/regions/RegionFactory.java b/src/main/java/com/livingworld/regions/RegionFactory.java new file mode 100644 index 0000000..defd553 --- /dev/null +++ b/src/main/java/com/livingworld/regions/RegionFactory.java @@ -0,0 +1,66 @@ +package com.livingworld.regions; + +import java.util.UUID; + +/** + * Creates new region objects with default values. + * + *

This factory ensures that every newly created region is valid, + * immediately dirty (so it will be saved), and initialised with sensible + * defaults before any ecology modules are connected.

+ */ +public class RegionFactory { + + /** + * Creates a new region with default values. + * + *

The returned region has the following properties: + *

    + *
  • A randomly generated UUID as its identifier
  • + *
  • The specified coordinate from the argument
  • + *
  • Lifecycle state set to {@link RegionLifecycleState#ACTIVE ACTIVE}
  • + *
  • {@code createdAtSimulationTick} and {@code lastUpdatedSimulationTick} both equal to {@code simulationTick}
  • + *
  • The dirty flag set to {@code true}
  • + *
  • Default {@link RegionFlags} (all flags false)
  • + *
  • Default {@link RegionMetrics} via {@link RegionMetrics#defaults()}
  • + *
  • An empty {@link RegionModuleData} container
  • + *

+ * + *

The region is validated before being returned. If validation fails, + * an {@link IllegalStateException} is thrown.

+ * + * @param coordinate the spatial position of the new region (must not be null) + * @param simulationTick the current simulation tick + * @return a valid new Region instance + * @throws IllegalArgumentException if coordinate is null + */ + public Region createNewRegion(RegionCoordinate coordinate, long simulationTick) { + if (coordinate == null) { + throw new IllegalArgumentException("coordinate must not be null"); + } + + UUID id = UUID.randomUUID(); + RegionLifecycleState lifecycleState = RegionLifecycleState.ACTIVE; + boolean dirty = true; + RegionFlags flags = new RegionFlags(); + RegionMetrics metrics = RegionMetrics.defaults(); + RegionModuleData moduleData = new RegionModuleData(); + + Region region = new Region( + id, + coordinate, + lifecycleState, + simulationTick, + simulationTick, + dirty, + flags, + metrics, + moduleData + ); + + // Validate before returning; throws IllegalStateException if invalid + region.validate(); + + return region; + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/regions/RegionFlags.java b/src/main/java/com/livingworld/regions/RegionFlags.java new file mode 100644 index 0000000..81a5f83 --- /dev/null +++ b/src/main/java/com/livingworld/regions/RegionFlags.java @@ -0,0 +1,162 @@ +package com.livingworld.regions; + +/** + * Boolean flags that describe the current state of a region. + * + *

This class tracks transient and persistent conditions affecting a region, + * such as player activity, pollution levels, soil quality, active ecosystem events, + * simulation force-loading, and data corruption.

+ */ +public class RegionFlags { + + private boolean hasPlayerActivity; + private boolean hasHighPollution; + private boolean hasLowSoilQuality; + private boolean hasActiveEcosystemEvent; + private boolean forceLoadedBySimulation; + private boolean corrupted; + + /** + * Default constructor. All flags are initialized to false. + */ + public RegionFlags() { + // all fields default to false + } + + // ------------------------------------------------------------------ + // Getters and Setters + // ------------------------------------------------------------------ + + /** + * Returns true if players have interacted with this region. + */ + public boolean isHasPlayerActivity() { + return hasPlayerActivity; + } + + /** + * Sets whether players have interacted with this region. + */ + public void setHasPlayerActivity(boolean hasPlayerActivity) { + this.hasPlayerActivity = hasPlayerActivity; + } + + /** + * Returns true if pollution levels in this region are high. + */ + public boolean isHasHighPollution() { + return hasHighPollution; + } + + /** + * Sets whether pollution levels are high. + */ + public void setHasHighPollution(boolean hasHighPollution) { + this.hasHighPollution = hasHighPollution; + } + + /** + * Returns true if soil quality in this region is low. + */ + public boolean isHasLowSoilQuality() { + return hasLowSoilQuality; + } + + /** + * Sets whether soil quality is low. + */ + public void setHasLowSoilQuality(boolean hasLowSoilQuality) { + this.hasLowSoilQuality = hasLowSoilQuality; + } + + /** + * Returns true if an active ecosystem event (fire, flood, etc.) is occurring. + */ + public boolean isHasActiveEcosystemEvent() { + return hasActiveEcosystemEvent; + } + + /** + * Sets whether an active ecosystem event is occurring. + */ + public void setHasActiveEcosystemEvent(boolean hasActiveEcosystemEvent) { + this.hasActiveEcosystemEvent = hasActiveEcosystemEvent; + } + + /** + * Returns true if the region is force-loaded by simulation and cannot be + * unloaded by normal garbage collection. + */ + public boolean isForceLoadedBySimulation() { + return forceLoadedBySimulation; + } + + /** + * Sets whether the region is force-loaded by simulation. + */ + public void setForceLoadedBySimulation(boolean forceLoadedBySimulation) { + this.forceLoadedBySimulation = forceLoadedBySimulation; + } + + /** + * Returns true if this region has corrupted or inconsistent data. + */ + public boolean isCorrupted() { + return corrupted; + } + + /** + * Sets whether this region is corrupted. + */ + public void setCorrupted(boolean corrupted) { + this.corrupted = corrupted; + } + + // ------------------------------------------------------------------ + // Copy and Clear + // ------------------------------------------------------------------ + + /** + * Returns a new {@link RegionFlags} instance with identical field values. + * + *

The returned instance is independent; modifying it will not affect + * the original flags.

+ */ + public RegionFlags copy() { + RegionFlags copy = new RegionFlags(); + copy.hasPlayerActivity = this.hasPlayerActivity; + copy.hasHighPollution = this.hasHighPollution; + copy.hasLowSoilQuality = this.hasLowSoilQuality; + copy.hasActiveEcosystemEvent = this.hasActiveEcosystemEvent; + copy.forceLoadedBySimulation = this.forceLoadedBySimulation; + copy.corrupted = this.corrupted; + return copy; + } + + /** + * Clears transient flags: {@code hasPlayerActivity} and {@code hasActiveEcosystemEvent}. + * + *

Persistent flags such as {@code corrupted}, {@code hasHighPollution}, + * {@code hasLowSoilQuality}, and {@code forceLoadedBySimulation} are preserved.

+ */ + public void clearTransientFlags() { + this.hasPlayerActivity = false; + this.hasActiveEcosystemEvent = false; + } + + // ------------------------------------------------------------------ + // Standard overrides + // ------------------------------------------------------------------ + + @Override + public String toString() { + return "RegionFlags{" + + "hasPlayerActivity=" + hasPlayerActivity + + ", hasHighPollution=" + hasHighPollution + + ", hasLowSoilQuality=" + hasLowSoilQuality + + ", hasActiveEcosystemEvent=" + hasActiveEcosystemEvent + + ", forceLoadedBySimulation=" + forceLoadedBySimulation + + ", corrupted=" + corrupted + + "}"; + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/regions/RegionLifecycleController.java b/src/main/java/com/livingworld/regions/RegionLifecycleController.java new file mode 100644 index 0000000..ef0791d --- /dev/null +++ b/src/main/java/com/livingworld/regions/RegionLifecycleController.java @@ -0,0 +1,115 @@ +package com.livingworld.regions; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Manages region lifecycle state transitions. + * + *

This is a plain Java utility class that provides static methods for validating + * and executing transitions between {@link RegionLifecycleState} values. All allowed + * transitions are hardcoded in an internal lookup table.

+ * + *

Allowed transitions: + *

    + *
  • UNLOADED → LOADING
  • + *
  • LOADING → ACTIVE
  • + *
  • LOADING → FAILED
  • + *
  • ACTIVE → DIRTY
  • + *
  • DIRTY → SAVING
  • + *
  • SAVING → ACTIVE
  • + *
  • SAVING → FAILED
  • + *
  • ACTIVE → UNLOADING
  • + *
  • UNLOADING → UNLOADED
  • + *
  • FAILED → LOADING
  • + *

+ */ +public class RegionLifecycleController { + + private static final String SEPARATOR = "->"; + + /** Unmodifiable set of allowed transition keys in the format "FROM->TO". */ + private static final Set ALLOWED_TRANSITIONS; + + static { + Set table = new HashSet<>(); + table.add("UNLOADED" + SEPARATOR + "LOADING"); + table.add("LOADING" + SEPARATOR + "ACTIVE"); + table.add("LOADING" + SEPARATOR + "FAILED"); + table.add("ACTIVE" + SEPARATOR + "DIRTY"); + table.add("DIRTY" + SEPARATOR + "SAVING"); + table.add("SAVING" + SEPARATOR + "ACTIVE"); + table.add("SAVING" + SEPARATOR + "FAILED"); + table.add("ACTIVE" + SEPARATOR + "UNLOADING"); + table.add("UNLOADING" + SEPARATOR + "UNLOADED"); + table.add("FAILED" + SEPARATOR + "LOADING"); + ALLOWED_TRANSITIONS = Collections.unmodifiableSet(table); + } + + /** + * Private constructor to prevent instantiation. + */ + private RegionLifecycleController() { + // utility class + } + + // ------------------------------------------------------------------ + // Transition validation + // ------------------------------------------------------------------ + + /** + * Returns whether the specified transition from {@code from} to {@code to} is allowed. + * + * @param from the current lifecycle state (must not be null) + * @param to the target lifecycle state (must not be null) + * @return true if this transition is permitted + * @throws IllegalArgumentException if either parameter is null + */ + public static boolean canTransition(RegionLifecycleState from, RegionLifecycleState to) { + if (from == null) { + throw new IllegalArgumentException("from state must not be null"); + } + if (to == null) { + throw new IllegalArgumentException("to state must not be null"); + } + + String key = from.name() + SEPARATOR + to.name(); + return ALLOWED_TRANSITIONS.contains(key); + } + + // ------------------------------------------------------------------ + // Transition execution + // ------------------------------------------------------------------ + + /** + * Transitions the given region to the target lifecycle state. + * + *

Validates that the transition from the region's current state to the + * target is allowed. If valid, updates the region's lifecycle state and marks + * it dirty.

+ * + * @param region the region to transition (must not be null) + * @param target the target lifecycle state (must not be null) + * @throws IllegalArgumentException if region or target is null + * @throws IllegalStateException if the transition is not allowed + */ + public static void transition(Region region, RegionLifecycleState target) { + if (region == null) { + throw new IllegalArgumentException("region must not be null"); + } + if (target == null) { + throw new IllegalArgumentException("target state must not be null"); + } + + RegionLifecycleState current = region.getLifecycleState(); + + if (!canTransition(current, target)) { + throw new IllegalStateException( + "Invalid lifecycle transition: " + current.name() + " -> " + target.name()); + } + + region.setLifecycleState(target); + region.markDirty(); + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/regions/RegionLifecycleState.java b/src/main/java/com/livingworld/regions/RegionLifecycleState.java new file mode 100644 index 0000000..3b1bcb1 --- /dev/null +++ b/src/main/java/com/livingworld/regions/RegionLifecycleState.java @@ -0,0 +1,52 @@ +package com.livingworld.regions; + +/** + * Lifecycle states for a region in the Living World simulation. + * + *

This enum tracks the state transitions of a region as it is loaded, + * simulated, saved, and unloaded during gameplay.

+ */ +public enum RegionLifecycleState { + + /** + * The region is not currently loaded in memory. + * No data is held; the region exists only on disk (or has never been created). + */ + UNLOADED, + + /** + * The region is being read from disk and initialized. + * Data structures are being constructed but the region is not yet ready for simulation. + */ + LOADING, + + /** + * The region is fully loaded and actively processing simulation ticks. + * Entities, blocks, and other systems within this region are updated each cycle. + */ + ACTIVE, + + /** + * The region has been modified since it was last saved. + * These changes must be persisted to disk before the region can be safely unloaded. + */ + DIRTY, + + /** + * The region's data is being written to disk. + * No modifications should occur while in this state. + */ + SAVING, + + /** + * An error occurred during loading, saving, or simulation. + * The region cannot continue normal operations and requires manual intervention or reset. + */ + FAILED, + + /** + * The region is being removed from memory after simulation. + * Data is being flushed to disk (if dirty) and internal structures are being released. + */ + UNLOADING; +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/regions/RegionMetrics.java b/src/main/java/com/livingworld/regions/RegionMetrics.java new file mode 100644 index 0000000..0084c30 --- /dev/null +++ b/src/main/java/com/livingworld/regions/RegionMetrics.java @@ -0,0 +1,387 @@ +package com.livingworld.regions; + +/** + * Metrics that describe the health and state of an ecosystem within a region. + * + *

All values are clamped between 0 and 100 to represent percentages or + * normalized scores. This class provides methods for normalizing, copying, + * and creating default instances.

+ */ +public class RegionMetrics { + + private double ecosystemHealth; + private double pollutionScore; + private double soilQuality; + private double waterQuality; + private double vegetationPressure; + private double resourceDepletion; + private double recoveryPressure; + + // ------------------------------------------------------------------ + // Constants + // ------------------------------------------------------------------ + + /** Minimum allowed value for all metrics. */ + public static final double MIN_VALUE = 0; + + /** Maximum allowed value for all metrics. */ + public static final double MAX_VALUE = 100; + + // ------------------------------------------------------------------ + // Default values + // ------------------------------------------------------------------ + + /** Default ecosystem health score. */ + private static final double DEFAULT_ECOSYSTEM_HEALTH = 60; + + /** Default pollution score. */ + private static final double DEFAULT_POLLUTION_SCORE = 0; + + /** Default soil quality score. */ + private static final double DEFAULT_SOIL_QUALITY = 60; + + /** Default water quality score. */ + private static final double DEFAULT_WATER_QUALITY = 60; + + /** Default vegetation pressure score. */ + private static final double DEFAULT_VEGETATION_PRESSURE = 50; + + /** Default resource depletion score. */ + private static final double DEFAULT_RESOURCE_DEPLETION = 0; + + /** Default recovery pressure score. */ + private static final double DEFAULT_RECOVERY_PRESSURE = 50; + + // ------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------ + + /** + * Default constructor. All fields are initialized to 0. + */ + public RegionMetrics() { + // all fields default to 0.0 + } + + // ------------------------------------------------------------------ + // Getters and Setters + // ------------------------------------------------------------------ + + /** + * Returns the ecosystem health score (0-100). + * + *

Higher values indicate a healthier, more balanced ecosystem.

+ */ + public double getEcosystemHealth() { + return ecosystemHealth; + } + + /** + * Sets the ecosystem health score. + * + *

The value is clamped between 0 and 100.

+ */ + public void setEcosystemHealth(double ecosystemHealth) { + this.ecosystemHealth = clamp(ecosystemHealth); + } + + /** + * Returns the pollution score (0-100). + * + *

Higher values indicate more severe pollution.

+ */ + public double getPollutionScore() { + return pollutionScore; + } + + /** + * Sets the pollution score. + * + *

The value is clamped between 0 and 100.

+ */ + public void setPollutionScore(double pollutionScore) { + this.pollutionScore = clamp(pollutionScore); + } + + /** + * Returns the soil quality score (0-100). + * + *

Higher values indicate better soil health.

+ */ + public double getSoilQuality() { + return soilQuality; + } + + /** + * Sets the soil quality score. + * + *

The value is clamped between 0 and 100.

+ */ + public void setSoilQuality(double soilQuality) { + this.soilQuality = clamp(soilQuality); + } + + /** + * Returns the water quality score (0-100). + * + *

Higher values indicate cleaner, more abundant water.

+ */ + public double getWaterQuality() { + return waterQuality; + } + + /** + * Sets the water quality score. + * + *

The value is clamped between 0 and 100.

+ */ + public void setWaterQuality(double waterQuality) { + this.waterQuality = clamp(waterQuality); + } + + /** + * Returns the vegetation pressure score (0-100). + * + *

Higher values indicate higher stress on vegetation resources.

+ */ + public double getVegetationPressure() { + return vegetationPressure; + } + + /** + * Sets the vegetation pressure score. + * + *

The value is clamped between 0 and 100.

+ */ + public void setVegetationPressure(double vegetationPressure) { + this.vegetationPressure = clamp(vegetationPressure); + } + + /** + * Returns the resource depletion score (0-100). + * + *

Higher values indicate more severe resource exhaustion.

+ */ + public double getResourceDepletion() { + return resourceDepletion; + } + + /** + * Sets the resource depletion score. + * + *

The value is clamped between 0 and 100.

+ */ + public void setResourceDepletion(double resourceDepletion) { + this.resourceDepletion = clamp(resourceDepletion); + } + + /** + * Returns the recovery pressure score (0-100). + * + *

Higher values indicate greater need for ecosystem recovery efforts.

+ */ + public double getRecoveryPressure() { + return recoveryPressure; + } + + /** + * Sets the recovery pressure score. + * + *

The value is clamped between 0 and 100.

+ */ + public void setRecoveryPressure(double recoveryPressure) { + this.recoveryPressure = clamp(recoveryPressure); + } + + // ------------------------------------------------------------------ + // Copy and Static Factory Methods + // ------------------------------------------------------------------ + + /** + * Returns a new {@link RegionMetrics} instance with identical field values. + * + *

The returned instance is independent; modifying it will not affect + * the original metrics.

+ */ + public RegionMetrics copy() { + RegionMetrics copy = new RegionMetrics(); + copy.ecosystemHealth = this.ecosystemHealth; + copy.pollutionScore = this.pollutionScore; + copy.soilQuality = this.soilQuality; + copy.waterQuality = this.waterQuality; + copy.vegetationPressure = this.vegetationPressure; + copy.resourceDepletion = this.resourceDepletion; + copy.recoveryPressure = this.recoveryPressure; + return copy; + } + + /** + * Returns a new {@link RegionMetrics} instance with default values. + * + *

Default values: + * ecosystemHealth=60, pollutionScore=0, soilQuality=60, + * waterQuality=60, vegetationPressure=50, resourceDepletion=0, + * recoveryPressure=50

+ */ + public static RegionMetrics defaults() { + RegionMetrics metrics = new RegionMetrics(); + metrics.ecosystemHealth = DEFAULT_ECOSYSTEM_HEALTH; + metrics.pollutionScore = DEFAULT_POLLUTION_SCORE; + metrics.soilQuality = DEFAULT_SOIL_QUALITY; + metrics.waterQuality = DEFAULT_WATER_QUALITY; + metrics.vegetationPressure = DEFAULT_VEGETATION_PRESSURE; + metrics.resourceDepletion = DEFAULT_RESOURCE_DEPLETION; + metrics.recoveryPressure = DEFAULT_RECOVERY_PRESSURE; + return metrics; + } + + // ------------------------------------------------------------------ + // Delta Application Methods + // ------------------------------------------------------------------ + + /** + * Applies deltas to all metric fields and returns this instance for chaining. + * + *

Each field is modified by its corresponding delta value, then clamped + * between 0 and 100.

+ * + * @param ecosystemHealthDelta the change to apply to ecosystem health + * @param pollutionScoreDelta the change to apply to pollution score + * @param soilQualityDelta the change to apply to soil quality + * @param waterQualityDelta the change to apply to water quality + * @param vegetationPressureDelta the change to apply to vegetation pressure + * @param resourceDepletionDelta the change to apply to resource depletion + * @param recoveryPressureDelta the change to apply to recovery pressure + * @return this metrics instance for method chaining + */ + public RegionMetrics applyDelta( + double ecosystemHealthDelta, + double pollutionScoreDelta, + double soilQualityDelta, + double waterQualityDelta, + double vegetationPressureDelta, + double resourceDepletionDelta, + double recoveryPressureDelta) { + + this.ecosystemHealth = clamp(this.ecosystemHealth + ecosystemHealthDelta); + this.pollutionScore = clamp(this.pollutionScore + pollutionScoreDelta); + this.soilQuality = clamp(this.soilQuality + soilQualityDelta); + this.waterQuality = clamp(this.waterQuality + waterQualityDelta); + this.vegetationPressure = clamp(this.vegetationPressure + vegetationPressureDelta); + this.resourceDepletion = clamp(this.resourceDepletion + resourceDepletionDelta); + this.recoveryPressure = clamp(this.recoveryPressure + recoveryPressureDelta); + + return this; + } + + /** + * Applies a delta to the ecosystem health score and returns this instance. + */ + public RegionMetrics applyEcosystemHealthDelta(double delta) { + this.ecosystemHealth = clamp(this.ecosystemHealth + delta); + return this; + } + + /** + * Applies a delta to the pollution score and returns this instance. + */ + public RegionMetrics applyPollutionScoreDelta(double delta) { + this.pollutionScore = clamp(this.pollutionScore + delta); + return this; + } + + /** + * Applies a delta to the soil quality score and returns this instance. + */ + public RegionMetrics applySoilQualityDelta(double delta) { + this.soilQuality = clamp(this.soilQuality + delta); + return this; + } + + /** + * Applies a delta to the water quality score and returns this instance. + */ + public RegionMetrics applyWaterQualityDelta(double delta) { + this.waterQuality = clamp(this.waterQuality + delta); + return this; + } + + /** + * Applies a delta to the vegetation pressure score and returns this instance. + */ + public RegionMetrics applyVegetationPressureDelta(double delta) { + this.vegetationPressure = clamp(this.vegetationPressure + delta); + return this; + } + + /** + * Applies a delta to the resource depletion score and returns this instance. + */ + public RegionMetrics applyResourceDepletionDelta(double delta) { + this.resourceDepletion = clamp(this.resourceDepletion + delta); + return this; + } + + /** + * Applies a delta to the recovery pressure score and returns this instance. + */ + public RegionMetrics applyRecoveryPressureDelta(double delta) { + this.recoveryPressure = clamp(this.recoveryPressure + delta); + return this; + } + + // ------------------------------------------------------------------ + // Normalization + // ------------------------------------------------------------------ + + /** + * Clamps all metric values between 0 and 100. + * + *

This method modifies the current instance in place and returns it + * for method chaining.

+ */ + public RegionMetrics normalize() { + this.ecosystemHealth = clamp(this.ecosystemHealth); + this.pollutionScore = clamp(this.pollutionScore); + this.soilQuality = clamp(this.soilQuality); + this.waterQuality = clamp(this.waterQuality); + this.vegetationPressure = clamp(this.vegetationPressure); + this.resourceDepletion = clamp(this.resourceDepletion); + this.recoveryPressure = clamp(this.recoveryPressure); + return this; + } + + // ------------------------------------------------------------------ + // Utility Methods + // ------------------------------------------------------------------ + + /** + * Clamps a value between 0 and 100. + */ + private static double clamp(double value) { + if (value < MIN_VALUE) { + return MIN_VALUE; + } + if (value > MAX_VALUE) { + return MAX_VALUE; + } + return value; + } + + // ------------------------------------------------------------------ + // Standard Overrides + // ------------------------------------------------------------------ + + @Override + public String toString() { + return "RegionMetrics{" + + "ecosystemHealth=" + ecosystemHealth + + ", pollutionScore=" + pollutionScore + + ", soilQuality=" + soilQuality + + ", waterQuality=" + waterQuality + + ", vegetationPressure=" + vegetationPressure + + ", resourceDepletion=" + resourceDepletion + + ", recoveryPressure=" + recoveryPressure + + "}"; + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/regions/RegionModuleData.java b/src/main/java/com/livingworld/regions/RegionModuleData.java new file mode 100644 index 0000000..c0cf87c --- /dev/null +++ b/src/main/java/com/livingworld/regions/RegionModuleData.java @@ -0,0 +1,105 @@ +package com.livingworld.regions; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * Stores module-specific data by module ID. + */ +public class RegionModuleData { + + private Map moduleData; + + /** + * Creates a new empty RegionModuleData instance. + */ + public RegionModuleData() { + this.moduleData = new HashMap<>(); + } + + /** + * Associates the given data with the specified module ID. + * + * @param moduleId the module identifier (must not be null or blank) + * @param data the data to store (must not be null) + * @throws IllegalArgumentException if moduleId is null or blank + * @throws IllegalArgumentException if data is null + */ + public void put(String moduleId, Object data) { + if (moduleId == null || moduleId.isBlank()) { + throw new IllegalArgumentException("moduleId must not be null or blank"); + } + if (data == null) { + throw new IllegalArgumentException("Data for module '" + moduleId + "' must not be null"); + } + this.moduleData.put(moduleId, data); + } + + /** + * Returns the data associated with the given module ID, cast to the requested type. + * + *

If the module ID is not present or the stored data does not match the + * requested type, this method returns {@link Optional#empty()} instead of + * throwing an exception.

+ * + * @param moduleId the module identifier (must not be null or blank) + * @param type the expected type of the stored data + * @param the target type + * @return the data cast to {@code type}, or {@link Optional#empty()} if not found or type mismatch + */ + @SuppressWarnings("unchecked") + public Optional get(String moduleId, Class type) { + if (moduleId == null || moduleId.isBlank()) { + throw new IllegalArgumentException("moduleId must not be null or blank"); + } + Object value = this.moduleData.get(moduleId); + if (value == null) { + return Optional.empty(); + } + if (!type.isInstance(value)) { + return Optional.empty(); + } + return Optional.of((T) value); + } + + /** + * Returns whether this instance contains data for the given module ID. + * + * @param moduleId the module identifier (must not be null or blank) + * @return true if data exists for the given module ID + * @throws IllegalArgumentException if moduleId is null or blank + */ + public boolean contains(String moduleId) { + if (moduleId == null || moduleId.isBlank()) { + throw new IllegalArgumentException("moduleId must not be null or blank"); + } + return this.moduleData.containsKey(moduleId); + } + + /** + * Returns all module IDs currently stored. + * + * @return an unmodifiable set of module identifiers + */ + public Set moduleIds() { + return Collections.unmodifiableSet(new HashSet<>(this.moduleData.keySet())); + } + + /** + * Returns a shallow copy of this instance. + * + *

The returned {@link RegionModuleData} has its own map, but the values + * inside are shared references.

+ * + * @return a new {@code RegionModuleData} with the same entries + */ + public RegionModuleData copyShallow() { + RegionModuleData copy = new RegionModuleData(); + copy.moduleData.putAll(this.moduleData); + return copy; + } +} \ No newline at end of file diff --git a/src/main/java/com/livingworld/regions/cache/package-info.java b/src/main/java/com/livingworld/regions/cache/package-info.java new file mode 100644 index 0000000..23247c8 --- /dev/null +++ b/src/main/java/com/livingworld/regions/cache/package-info.java @@ -0,0 +1,2 @@ +/** Regions cache package — region caching utilities. */ +package com.livingworld.regions.cache; \ No newline at end of file diff --git a/src/main/java/com/livingworld/regions/package-info.java b/src/main/java/com/livingworld/regions/package-info.java new file mode 100644 index 0000000..a583ac7 --- /dev/null +++ b/src/main/java/com/livingworld/regions/package-info.java @@ -0,0 +1,2 @@ +/** Regions package — region system core and management. */ +package com.livingworld.regions; \ No newline at end of file diff --git a/src/main/java/com/livingworld/regions/query/package-info.java b/src/main/java/com/livingworld/regions/query/package-info.java new file mode 100644 index 0000000..7e754b1 --- /dev/null +++ b/src/main/java/com/livingworld/regions/query/package-info.java @@ -0,0 +1,2 @@ +/** Regions query package — region querying and filtering utilities. */ +package com.livingworld.regions.query; \ No newline at end of file diff --git a/src/main/java/com/livingworld/testing/package-info.java b/src/main/java/com/livingworld/testing/package-info.java new file mode 100644 index 0000000..074a4df --- /dev/null +++ b/src/main/java/com/livingworld/testing/package-info.java @@ -0,0 +1,2 @@ +/** Testing package — testing utilities and test fixtures. */ +package com.livingworld.testing; \ No newline at end of file diff --git a/src/main/resources/META-INF/neoforge.mods.toml b/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..77083a8 --- /dev/null +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,25 @@ +modLoader="javafml" +loaderVersion="[4,)" +license="All Rights Reserved" + +[[mods]] +modId="livingworld" +version="0.1.0" +displayName="Living World" +description=''' +An ecosystem-focused world evolution simulation for Minecraft. +''' + +[[dependencies.livingworld]] +modId="neoforge" +type="required" +versionRange="[21.1,)" +ordering="NONE" +side="BOTH" + +[[dependencies.livingworld]] +modId="minecraft" +type="required" +versionRange="[1.21.1,1.22)" +ordering="NONE" +side="BOTH" diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..488a38c --- /dev/null +++ b/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "Living World resources", + "pack_format": 34 + } +} diff --git a/src/test/java/com/livingworld/config/SimulationConfigTest.java b/src/test/java/com/livingworld/config/SimulationConfigTest.java new file mode 100644 index 0000000..9d76f70 --- /dev/null +++ b/src/test/java/com/livingworld/config/SimulationConfigTest.java @@ -0,0 +1,281 @@ +package com.livingworld.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link SimulationConfig}. + */ +class SimulationConfigTest { + + // ------------------------------------------------------------------ + // Default values + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Default values") + class DefaultValues { + + @Test + void defaultRegionSizeChunks() { + final SimulationConfig config = new SimulationConfig(); + assertEquals(8, config.getRegionSizeChunks()); + } + + @Test + void defaultSimulationIntervalTicks() { + final SimulationConfig config = new SimulationConfig(); + assertEquals(100, config.getSimulationIntervalTicks()); + } + + @Test + void defaultMaxRegionsPerCycle() { + final SimulationConfig config = new SimulationConfig(); + assertEquals(50, config.getMaxRegionsPerCycle()); + } + + @Test + void defaultMaxMillisecondsPerCycle() { + final SimulationConfig config = new SimulationConfig(); + assertEquals(25, config.getMaxMillisecondsPerCycle()); + } + + @Test + void defaultEmergencyStopMilliseconds() { + final SimulationConfig config = new SimulationConfig(); + assertEquals(40, config.getEmergencyStopMilliseconds()); + } + + @Test + void defaultEnableDebugCommands() { + final SimulationConfig config = new SimulationConfig(); + assertTrue(config.isEnableDebugCommands()); + } + + @Test + void defaultEnableProfiler() { + final SimulationConfig config = new SimulationConfig(); + assertTrue(config.isEnableProfiler()); + } + } + + // ------------------------------------------------------------------ + // Validation – valid values should pass + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Validation with valid values") + class ValidValues { + + @Test + void defaultsAreValid() { + final SimulationConfig config = new SimulationConfig(); + config.validate(); // Should not throw + } + + @Test + void minimumRegionSizeChunks() { + final SimulationConfig config = new SimulationConfig(); + config.setRegionSizeChunks(1); + config.validate(); // Should not throw + } + + @Test + void minimumSimulationIntervalTicks() { + final SimulationConfig config = new SimulationConfig(); + config.setSimulationIntervalTicks(1); + config.validate(); // Should not throw + } + + @Test + void minimumMaxRegionsPerCycle() { + final SimulationConfig config = new SimulationConfig(); + config.setMaxRegionsPerCycle(1); + config.validate(); // Should not throw + } + + @Test + void minimumMaxMillisecondsPerCycle() { + final SimulationConfig config = new SimulationConfig(); + config.setMaxMillisecondsPerCycle(1); + config.validate(); // Should not throw + } + + @Test + void emergencyStopEqualsMaxMilliseconds() { + final SimulationConfig config = new SimulationConfig(); + config.setEmergencyStopMilliseconds(25); + config.setMaxMillisecondsPerCycle(25); + config.validate(); // Should not throw (equal is allowed) + } + + @Test + void emergencyStopGreaterThanMaxMilliseconds() { + final SimulationConfig config = new SimulationConfig(); + config.setEmergencyStopMilliseconds(50); + config.setMaxMillisecondsPerCycle(25); + config.validate(); // Should not throw + } + } + + // ------------------------------------------------------------------ + // Validation – invalid values should fail + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Validation with invalid values") + class InvalidValues { + + @Test + void zeroRegionSizeChunks() { + final SimulationConfig config = new SimulationConfig(); + config.setRegionSizeChunks(0); + assertThrows(IllegalArgumentException.class, () -> config.validate()); + } + + @Test + void negativeRegionSizeChunks() { + final SimulationConfig config = new SimulationConfig(); + config.setRegionSizeChunks(-5); + assertThrows(IllegalArgumentException.class, () -> config.validate()); + } + + @Test + void zeroSimulationIntervalTicks() { + final SimulationConfig config = new SimulationConfig(); + config.setSimulationIntervalTicks(0); + assertThrows(IllegalArgumentException.class, () -> config.validate()); + } + + @Test + void negativeSimulationIntervalTicks() { + final SimulationConfig config = new SimulationConfig(); + config.setSimulationIntervalTicks(-10); + assertThrows(IllegalArgumentException.class, () -> config.validate()); + } + + @Test + void zeroMaxRegionsPerCycle() { + final SimulationConfig config = new SimulationConfig(); + config.setMaxRegionsPerCycle(0); + assertThrows(IllegalArgumentException.class, () -> config.validate()); + } + + @Test + void negativeMaxRegionsPerCycle() { + final SimulationConfig config = new SimulationConfig(); + config.setMaxRegionsPerCycle(-1); + assertThrows(IllegalArgumentException.class, () -> config.validate()); + } + + @Test + void zeroMaxMillisecondsPerCycle() { + final SimulationConfig config = new SimulationConfig(); + config.setMaxMillisecondsPerCycle(0); + assertThrows(IllegalArgumentException.class, () -> config.validate()); + } + + @Test + void negativeMaxMillisecondsPerCycle() { + final SimulationConfig config = new SimulationConfig(); + config.setMaxMillisecondsPerCycle(-10); + assertThrows(IllegalArgumentException.class, () -> config.validate()); + } + + @Test + void emergencyStopLessThanMaxMilliseconds() { + final SimulationConfig config = new SimulationConfig(); + config.setEmergencyStopMilliseconds(20); + config.setMaxMillisecondsPerCycle(25); + assertThrows(IllegalArgumentException.class, () -> config.validate()); + } + + @Test + void emergencyStopMessageContainsValues() { + final SimulationConfig config = new SimulationConfig(); + config.setEmergencyStopMilliseconds(10); + config.setMaxMillisecondsPerCycle(30); + final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> config.validate()); + assertTrue(exception.getMessage().contains("emergencyStopMilliseconds")); + assertTrue(exception.getMessage().contains("maxMillisecondsPerCycle")); + } + } + + // ------------------------------------------------------------------ + // Setters + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Setters") + class Setters { + + @Test + void setRegionSizeChunks() { + final SimulationConfig config = new SimulationConfig(); + config.setRegionSizeChunks(16); + assertEquals(16, config.getRegionSizeChunks()); + } + + @Test + void setSimulationIntervalTicks() { + final SimulationConfig config = new SimulationConfig(); + config.setSimulationIntervalTicks(50); + assertEquals(50, config.getSimulationIntervalTicks()); + } + + @Test + void setMaxRegionsPerCycle() { + final SimulationConfig config = new SimulationConfig(); + config.setMaxRegionsPerCycle(100); + assertEquals(100, config.getMaxRegionsPerCycle()); + } + + @Test + void setMaxMillisecondsPerCycle() { + final SimulationConfig config = new SimulationConfig(); + config.setMaxMillisecondsPerCycle(50); + assertEquals(50, config.getMaxMillisecondsPerCycle()); + } + + @Test + void setEmergencyStopMilliseconds() { + final SimulationConfig config = new SimulationConfig(); + config.setEmergencyStopMilliseconds(60); + assertEquals(60, config.getEmergencyStopMilliseconds()); + } + + @Test + void setEnableDebugCommandsFalse() { + final SimulationConfig config = new SimulationConfig(); + config.setEnableDebugCommands(false); + assertFalse(config.isEnableDebugCommands()); + } + + @Test + void setEnableProfilerFalse() { + final SimulationConfig config = new SimulationConfig(); + config.setEnableProfiler(false); + assertFalse(config.isEnableProfiler()); + } + } + + // ------------------------------------------------------------------ + // toString + // ------------------------------------------------------------------ + + @Test + @DisplayName("toString should not throw") + void toStringShouldNotThrow() { + final SimulationConfig config = new SimulationConfig(); + final String result = config.toString(); + assertTrue(result.contains("SimulationConfig")); + assertTrue(result.contains("regionSizeChunks=8")); + assertTrue(result.contains("simulationIntervalTicks=100")); + } +} \ No newline at end of file diff --git a/src/test/java/com/livingworld/core/services/ServiceRegistryTest.java b/src/test/java/com/livingworld/core/services/ServiceRegistryTest.java new file mode 100644 index 0000000..425332c --- /dev/null +++ b/src/test/java/com/livingworld/core/services/ServiceRegistryTest.java @@ -0,0 +1,221 @@ +package com.livingworld.core.services; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link ServiceRegistry}. + */ +class ServiceRegistryTest { + + // ------------------------------------------------------------------ + // Registration and retrieval + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Registration and retrieval") + class RegistrationAndRetrieval { + + @Test + void registerAndGet() { + var registry = new ServiceRegistry(); + var key = new ServiceKey<>("test-service", String.class); + registry.register(key, "hello"); + String result = registry.get(key); + assertEquals("hello", result); + } + + @Test + void findReturnsPresentService() { + var registry = new ServiceRegistry(); + var key = new ServiceKey<>("test-service-2", Integer.class); + registry.register(key, 42); + var found = registry.find(key); + assertTrue(found.isPresent()); + assertEquals(42, found.get()); + } + + @Test + void findReturnsEmptyForUnregistered() { + var registry = new ServiceRegistry(); + var key = new ServiceKey<>("unregistered", Boolean.class); + var found = registry.find(key); + assertFalse(found.isPresent()); + } + + @Test + void isRegisteredReturnsTrueAfterRegistration() { + var registry = new ServiceRegistry(); + var key = new ServiceKey<>("test-service-3", Object.class); + registry.register(key, new Object()); + assertTrue(registry.isRegistered(key)); + } + + @Test + void isRegisteredReturnsFalseForUnregistered() { + var registry = new ServiceRegistry(); + var key = new ServiceKey<>("unregistered-2", Number.class); + assertFalse(registry.isRegistered(key)); + } + } + + // ------------------------------------------------------------------ + // Null argument validation + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Null argument validation") + class NullArgumentValidation { + + @Test + void registerWithNullKeyThrows() { + var registry = new ServiceRegistry(); + assertThrows(IllegalArgumentException.class, () -> registry.register(null, "service")); + } + + @Test + void registerWithNullServiceThrows() { + var registry = new ServiceRegistry(); + var key = new ServiceKey<>("null-service", String.class); + assertThrows(IllegalArgumentException.class, () -> registry.register(key, null)); + } + + @Test + void getWithNullKeyThrows() { + var registry = new ServiceRegistry(); + assertThrows(IllegalArgumentException.class, () -> registry.get(null)); + } + + @Test + void findWithNullKeyThrows() { + var registry = new ServiceRegistry(); + assertThrows(IllegalArgumentException.class, () -> registry.find(null)); + } + + @Test + void isRegisteredWithNullKeyThrows() { + var registry = new ServiceRegistry(); + assertThrows(IllegalArgumentException.class, () -> registry.isRegistered(null)); + } + } + + // ------------------------------------------------------------------ + // Duplicate registration rejection + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Duplicate registration rejection") + class DuplicateRegistrationRejection { + + @Test + void duplicateKeyThrows() { + var registry = new ServiceRegistry(); + var key = new ServiceKey<>("duplicate", String.class); + registry.register(key, "first"); + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> registry.register(key, "second")); + assertTrue(exception.getMessage().contains("already registered")); + } + + @Test + void differentKeysDoNotConflict() { + var registry = new ServiceRegistry(); + var key1 = new ServiceKey<>("key-1", String.class); + var key2 = new ServiceKey<>("key-2", Integer.class); + registry.register(key1, "hello"); + registry.register(key2, 42); + assertEquals("hello", registry.get(key1)); + assertEquals(42, registry.get(key2)); + } + } + + // ------------------------------------------------------------------ + // Lock mechanism + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Lock mechanism") + class LockMechanism { + + @Test + void isLockedReturnsFalseByDefault() { + var registry = new ServiceRegistry(); + assertFalse(registry.isLocked()); + } + + @Test + void lockSetsLockedState() { + var registry = new ServiceRegistry(); + registry.lock(); + assertTrue(registry.isLocked()); + } + + @Test + void registerAfterLockThrows() { + var registry = new ServiceRegistry(); + registry.lock(); + var key = new ServiceKey<>("after-lock", String.class); + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> registry.register(key, "service")); + assertTrue(exception.getMessage().contains("locked")); + } + + @Test + void registerBeforeLockThenRegisterAfterThrows() { + var registry = new ServiceRegistry(); + var key1 = new ServiceKey<>("before-lock", String.class); + registry.register(key1, "before"); + registry.lock(); + var key2 = new ServiceKey<>("after-lock-2", Integer.class); + assertThrows(IllegalStateException.class, () -> registry.register(key2, 42)); + } + + @Test + void getAndFindWorkAfterLock() { + var registry = new ServiceRegistry(); + var key = new ServiceKey<>("locked-service", String.class); + registry.register(key, "locked"); + registry.lock(); + assertEquals("locked", registry.get(key)); + assertTrue(registry.find(key).isPresent()); + } + } + + // ------------------------------------------------------------------ + // get() throws IllegalStateException for missing service + // ------------------------------------------------------------------ + + @Nested + @DisplayName("get() throws IllegalStateException for missing service") + class GetMissingThrows { + + @Test + void getUnregisteredServiceThrows() { + var registry = new ServiceRegistry(); + var key = new ServiceKey<>("missing", String.class); + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> registry.get(key)); + assertTrue(exception.getMessage().contains("No service registered")); + } + } + + // ------------------------------------------------------------------ + // No Minecraft dependencies + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Plain Java - no Minecraft imports") + class PlainJava { + + @Test + void registryIsPureJava() { + var registry = new ServiceRegistry(); + var key = new ServiceKey<>("plain", String.class); + registry.register(key, "test"); + assertEquals("test", registry.get(key)); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/livingworld/events/LivingWorldEventBusTest.java b/src/test/java/com/livingworld/events/LivingWorldEventBusTest.java new file mode 100644 index 0000000..3c6baf5 --- /dev/null +++ b/src/test/java/com/livingworld/events/LivingWorldEventBusTest.java @@ -0,0 +1,181 @@ +package com.livingworld.events; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link LivingWorldEventBus}. + */ +public class LivingWorldEventBusTest { + + // ------------------------------------------------------------------ + // Registration tests + // ------------------------------------------------------------------ + + @Test + void registerAddsListenerForEventType() { + LivingWorldEventBus bus = new LivingWorldEventBus(); + LivingWorldEventListener listener = event -> {}; + + assertDoesNotThrow(() -> bus.register("weather_change", listener)); + } + + @Test + void registerPreservesRegistrationOrder() { + LivingWorldEventBus bus = new LivingWorldEventBus(); + AtomicBoolean firstCalled = new AtomicBoolean(false); + AtomicBoolean secondCalled = new AtomicBoolean(false); + + LivingWorldEventListener first = event -> firstCalled.set(true); + LivingWorldEventListener second = event -> secondCalled.set(true); + + bus.register("weather_change", first); + bus.register("weather_change", second); + + BaseLivingWorldEvent event = new BaseLivingWorldEvent("weather_change", 0L, "core"); + publishEvent(bus, "weather_change", event); + + assertTrue(firstCalled.get()); + assertTrue(secondCalled.get()); + } + + @Test + void registerBlankEventTypeThrows() { + LivingWorldEventBus bus = new LivingWorldEventBus(); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + bus.register(" ", null); + }); + + assertEquals("eventType must not be null or blank", exception.getMessage()); + } + + @Test + void registerNullListenerThrows() { + LivingWorldEventBus bus = new LivingWorldEventBus(); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + bus.register("weather_change", null); + }); + + assertEquals("listener must not be null", exception.getMessage()); + } + + // ------------------------------------------------------------------ + // Publishing tests + // ------------------------------------------------------------------ + + @Test + void publishDispatchesToRegisteredListeners() { + LivingWorldEventBus bus = new LivingWorldEventBus(); + AtomicBoolean listenerCalled = new AtomicBoolean(false); + + bus.register("weather_change", event -> listenerCalled.set(true)); + + BaseLivingWorldEvent event = new BaseLivingWorldEvent( + "weather_change", 100L, "core" + ); + + publishEvent(bus, "weather_change", event); + + assertTrue(listenerCalled.get()); + } + + @Test + void publishUnknownEventTypeDoesNotCrash() { + LivingWorldEventBus bus = new LivingWorldEventBus(); + BaseLivingWorldEvent event = new BaseLivingWorldEvent("unknown_type_123", 100L, "core"); + + assertDoesNotThrow(() -> publishEvent(bus, "unknown_type_123", event)); + } + + @Test + void publishNullEventThrows() { + LivingWorldEventBus bus = new LivingWorldEventBus(); + + assertThrowsWithMessage(() -> { try { publishEvent(bus, "weather_change", null); } catch (RuntimeException e) { throw e; } }, + IllegalArgumentException.class, "event must not be null"); + } + + // ------------------------------------------------------------------ + // Metrics tests + // ------------------------------------------------------------------ + + @Test + void getPublishedEventCountIncrementsOnPublish() { + LivingWorldEventBus bus = new LivingWorldEventBus(); + + BaseLivingWorldEvent event1 = new BaseLivingWorldEvent("weather_change", 100L, "core"); + bus.publish(event1); + + assertEquals(1, bus.getPublishedEventCount()); + + // Clear suppression to allow publishing events with different ticks + bus.clearSuppressionForCurrentTick(); + + BaseLivingWorldEvent event2 = new BaseLivingWorldEvent("soil_moisture", 200L, "core"); + bus.publish(event2); + + assertEquals(2, bus.getPublishedEventCount()); + } + + @Test + void getListenerCountReturnsCorrectNumberOfListeners() { + LivingWorldEventBus bus = new LivingWorldEventBus(); + + bus.register("weather_change", event -> {}); + bus.register("weather_change", event -> {}); + bus.register("pollution_level", event -> {}); + + assertEquals(2, bus.getListenerCount("weather_change")); + assertEquals(1, bus.getListenerCount("pollution_level")); + assertEquals(0, bus.getListenerCount("unknown_event")); + } + + // ------------------------------------------------------------------ + // Helper methods (kept in test file for simplicity) + // ------------------------------------------------------------------ + + private void publishEvent(LivingWorldEventBus bus, String eventType, LivingWorldEvent event) { + // Use reflection to call the package-private publish method + try { + java.lang.reflect.Method method = LivingWorldEventBus.class.getDeclaredMethod( + "publish", LivingWorldEvent.class + ); + method.setAccessible(true); + if (event == null) { + method.invoke(bus, new Object[]{null}); + } else { + method.invoke(bus, event); + } + } catch (java.lang.reflect.InvocationTargetException e) { + // Unwrap the target exception for proper test assertions + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else if (cause != null) { + throw new RuntimeException(cause); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void assertThrowsWithMessage(Runnable runnable, Class expectedType, String expectedMessage) { + try { + runnable.run(); + fail("Expected " + expectedType.getSimpleName() + " but no exception was thrown"); + } catch (AssertionError e) { + throw e; + } catch (Throwable t) { + if (expectedType.isInstance(t)) { + assertEquals(expectedMessage, t.getMessage()); + } else { + throw new AssertionError("Expected " + expectedType.getSimpleName() + " but got " + t.getClass().getSimpleName() + ": " + t.getMessage(), t); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/com/livingworld/regions/RegionCoordinateTest.java b/src/test/java/com/livingworld/regions/RegionCoordinateTest.java new file mode 100644 index 0000000..50569d9 --- /dev/null +++ b/src/test/java/com/livingworld/regions/RegionCoordinateTest.java @@ -0,0 +1,378 @@ +package com.livingworld.regions; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link RegionCoordinate}. + */ +class RegionCoordinateTest { + + // ------------------------------------------------------------------ + // Validation – dimensionId + // ------------------------------------------------------------------ + + @Nested + @DisplayName("dimensionId validation") + class DimensionIdValidation { + + @Test + void nullDimensionIdThrows() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> new RegionCoordinate(null, 0, 0)); + assertTrue(ex.getMessage().contains("dimensionId")); + } + + @Test + void blankDimensionIdThrows() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> new RegionCoordinate("", 0, 0)); + assertTrue(ex.getMessage().contains("dimensionId")); + } + + @Test + void whitespaceOnlyDimensionIdThrows() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> new RegionCoordinate(" ", 0, 0)); + assertTrue(ex.getMessage().contains("dimensionId")); + } + + @Test + void validDimensionIdAccepted() { + RegionCoordinate rc = new RegionCoordinate("overworld", 0, 0); + assertEquals("overworld", rc.dimensionId()); + } + } + + // ------------------------------------------------------------------ + // Validation – regionSizeChunks + // ------------------------------------------------------------------ + + @Nested + @DisplayName("regionSizeChunks validation") + class RegionSizeValidation { + + @Test + void zeroRegionSizeThrows() { + assertThrows(IllegalArgumentException.class, + () -> RegionCoordinate.fromChunk("overworld", 0, 0, 0)); + } + + @Test + void negativeRegionSizeThrows() { + assertThrows(IllegalArgumentException.class, + () -> RegionCoordinate.fromChunk("overworld", 0, 0, -1)); + } + + @Test + void oneRegionSizeAccepted() { + RegionCoordinate rc = RegionCoordinate.fromChunk("overworld", 5, 5, 1); + assertEquals(5, rc.x()); + assertEquals(5, rc.z()); + } + } + + // ------------------------------------------------------------------ + // fromBlock – specified test cases + // ------------------------------------------------------------------ + + @Nested + @DisplayName("fromBlock coordinate mapping") + class FromBlockMapping { + + /** Block 0,0 maps to region 0,0 */ + @Test + void blockZeroZeroMapsToRegionZeroZero() { + RegionCoordinate rc = RegionCoordinate.fromBlock("overworld", 0, 0, 8); + assertEquals(0, rc.x()); + assertEquals(0, rc.z()); + } + + /** Block 127,127 maps to region 0,0 with region size 8 chunks */ + @Test + void block127_127MapsToRegionZeroZero() { + // chunk = floorDiv(block, 16) → floorDiv(127,16) = 7 + // region = floorDiv(chunk, 8) → floorDiv(7,8) = 0 + RegionCoordinate rc = RegionCoordinate.fromBlock("overworld", 127, 127, 8); + assertEquals(0, rc.x()); + assertEquals(0, rc.z()); + } + + /** Block 128,0 maps to region 1,0 */ + @Test + void block128ZeroMapsToRegionOneZero() { + // chunk = floorDiv(128,16) = 8 + // region = floorDiv(8,8) = 1 + RegionCoordinate rc = RegionCoordinate.fromBlock("overworld", 128, 0, 8); + assertEquals(1, rc.x()); + assertEquals(0, rc.z()); + } + + /** Block -1,0 maps to region -1,0 */ + @Test + void blockMinusOneZeroMapsToRegionMinusOneZero() { + // chunk = floorDiv(-1,16) = -1 + // region = floorDiv(-1,8) = -1 + RegionCoordinate rc = RegionCoordinate.fromBlock("overworld", -1, 0, 8); + assertEquals(-1, rc.x()); + assertEquals(0, rc.z()); + } + + /** Block -128,0 maps to region -1,0 */ + @Test + void blockMinus128ZeroMapsToRegionMinusOneZero() { + // chunk = floorDiv(-128,16) = -8 + // region = floorDiv(-8,8) = -1 + RegionCoordinate rc = RegionCoordinate.fromBlock("overworld", -128, 0, 8); + assertEquals(-1, rc.x()); + assertEquals(0, rc.z()); + } + + /** Block -129,0 maps to region -2,0 */ + @Test + void blockMinus129ZeroMapsToRegionMinusTwoZero() { + // chunk = floorDiv(-129,16) = -9 + // region = floorDiv(-9,8) = -2 + RegionCoordinate rc = RegionCoordinate.fromBlock("overworld", -129, 0, 8); + assertEquals(-2, rc.x()); + assertEquals(0, rc.z()); + } + } + + // ------------------------------------------------------------------ + // fromChunk – basic checks + // ------------------------------------------------------------------ + + @Nested + @DisplayName("fromChunk coordinate mapping") + class FromChunkMapping { + + @Test + void chunkZeroZeroMapsToRegionZeroZero() { + RegionCoordinate rc = RegionCoordinate.fromChunk("overworld", 0, 0, 8); + assertEquals(0, rc.x()); + assertEquals(0, rc.z()); + } + + @Test + void chunkSevenSevenMapsToRegionZeroZero() { + RegionCoordinate rc = RegionCoordinate.fromChunk("overworld", 7, 7, 8); + assertEquals(0, rc.x()); + assertEquals(0, rc.z()); + } + + @Test + void chunkEightEightMapsToRegionOneOne() { + RegionCoordinate rc = RegionCoordinate.fromChunk("overworld", 8, 8, 8); + assertEquals(1, rc.x()); + assertEquals(1, rc.z()); + } + + @Test + void chunkMinusOneMinusOneMapsToRegionMinusOneMinusOne() { + RegionCoordinate rc = RegionCoordinate.fromChunk("overworld", -1, -1, 8); + assertEquals(-1, rc.x()); + assertEquals(-1, rc.z()); + } + } + + // ------------------------------------------------------------------ + // Boundary methods – min/max chunk coordinates + // ------------------------------------------------------------------ + + @Nested + @DisplayName("minChunkX / minChunkZ / maxChunkX / maxChunkZ") + class ChunkBoundaryMethods { + + @Test + void regionZeroBoundariesWithSizeEight() { + RegionCoordinate rc = new RegionCoordinate("overworld", 0, 0); + assertEquals(0, rc.minChunkX(8)); + assertEquals(0, rc.minChunkZ(8)); + assertEquals(7, rc.maxChunkX(8)); + assertEquals(7, rc.maxChunkZ(8)); + } + + @Test + void regionOneBoundariesWithSizeEight() { + RegionCoordinate rc = new RegionCoordinate("overworld", 1, 1); + assertEquals(8, rc.minChunkX(8)); + assertEquals(8, rc.minChunkZ(8)); + assertEquals(15, rc.maxChunkX(8)); + assertEquals(15, rc.maxChunkZ(8)); + } + + @Test + void regionMinusOneBoundariesWithSizeEight() { + RegionCoordinate rc = new RegionCoordinate("overworld", -1, -1); + assertEquals(-8, rc.minChunkX(8)); + assertEquals(-8, rc.minChunkZ(8)); + assertEquals(-1, rc.maxChunkX(8)); + assertEquals(-1, rc.maxChunkZ(8)); + } + + @Test + void invalidRegionSizeThrowsInBoundaryMethods() { + RegionCoordinate rc = new RegionCoordinate("overworld", 0, 0); + assertThrows(IllegalArgumentException.class, () -> rc.minChunkX(0)); + assertThrows(IllegalArgumentException.class, () -> rc.minChunkZ(-1)); + assertThrows(IllegalArgumentException.class, () -> rc.maxChunkX(0)); + assertThrows(IllegalArgumentException.class, () -> rc.maxChunkZ(-5)); + } + } + + // ------------------------------------------------------------------ + // centerBlock methods + // ------------------------------------------------------------------ + + @Nested + @DisplayName("centerBlockX / centerBlockZ") + class CenterBlockMethods { + + /** Region 0 with size 8: chunks 0-7, blocks 0-127 → centre = floorDiv(0+127,2) = 63 */ + @Test + void centerOfRegionZero() { + RegionCoordinate rc = new RegionCoordinate("overworld", 0, 0); + assertEquals(63, rc.centerBlockX(8)); + assertEquals(63, rc.centerBlockZ(8)); + } + + /** Region 1 with size 8: chunks 8-15, blocks 128-255 → centre = floorDiv(128+255,2) = 191 */ + @Test + void centerOfRegionOne() { + RegionCoordinate rc = new RegionCoordinate("overworld", 1, 1); + assertEquals(191, rc.centerBlockX(8)); + assertEquals(191, rc.centerBlockZ(8)); + } + + /** Region -1 with size 8: chunks -8 to -1, blocks -128 to -1 → centre = floorDiv(-128+-1,2) = -65 */ + @Test + void centerOfRegionMinusOne() { + RegionCoordinate rc = new RegionCoordinate("overworld", -1, -1); + assertEquals(-65, rc.centerBlockX(8)); + assertEquals(-65, rc.centerBlockZ(8)); + } + + @Test + void invalidRegionSizeThrowsInCenterMethods() { + RegionCoordinate rc = new RegionCoordinate("overworld", 0, 0); + assertThrows(IllegalArgumentException.class, () -> rc.centerBlockX(0)); + assertThrows(IllegalArgumentException.class, () -> rc.centerBlockZ(-1)); + } + } + + // ------------------------------------------------------------------ + // stableId + // ------------------------------------------------------------------ + + @Nested + @DisplayName("stableId") + class StableIdTests { + + @Test + void stableIdIsStable() { + RegionCoordinate rc = new RegionCoordinate("overworld", 1, -2); + String id1 = rc.stableId(); + String id2 = rc.stableId(); + assertEquals(id1, id2); + } + + @Test + void stableIdFormat() { + RegionCoordinate rc = new RegionCoordinate("the_nether", 3, 5); + assertEquals("the_nether:3:5", rc.stableId()); + } + + @Test + void negativeCoordinatesInStableId() { + RegionCoordinate rc = new RegionCoordinate("overworld", -1, -2); + assertEquals("overworld:-1:-2", rc.stableId()); + } + } + + // ------------------------------------------------------------------ + // HashMap key usage + // ------------------------------------------------------------------ + + @Nested + @DisplayName("HashMap key compatibility") + class HashMapKeyTests { + + @Test + void worksAsHashMapKey() { + Map map = new HashMap<>(); + RegionCoordinate key1 = new RegionCoordinate("overworld", 0, 0); + RegionCoordinate key2 = new RegionCoordinate("overworld", 0, 0); + RegionCoordinate key3 = new RegionCoordinate("overworld", 1, 0); + + map.put(key1, "value1"); + assertEquals("value1", map.get(key2)); // same logical key + assertNotEquals("value1", map.get(key3)); // different key + } + + @Test + void equalsAndHashCodeContract() { + RegionCoordinate rc1 = new RegionCoordinate("overworld", 0, 0); + RegionCoordinate rc2 = new RegionCoordinate("overworld", 0, 0); + + assertEquals(rc1, rc2); + assertEquals(rc1.hashCode(), rc2.hashCode()); + } + + @Test + void notEqualToDifferentDimension() { + RegionCoordinate rc1 = new RegionCoordinate("overworld", 0, 0); + RegionCoordinate rc2 = new RegionCoordinate("nether", 0, 0); + + assertFalse(rc1.equals(rc2)); + } + + @Test + void notEqualToDifferentType() { + RegionCoordinate rc = new RegionCoordinate("overworld", 0, 0); + assertFalse(rc.equals("not a region coordinate")); + } + + @Test + void nullSafeEquals() { + RegionCoordinate rc = new RegionCoordinate("overworld", 0, 0); + assertFalse(rc.equals(null)); + } + } + + // ------------------------------------------------------------------ + // toString + // ------------------------------------------------------------------ + + @Nested + @DisplayName("toString") + class ToStringTests { + + @Test + void toStringContainsAllFields() { + RegionCoordinate rc = new RegionCoordinate("overworld", 1, -3); + String s = rc.toString(); + assertTrue(s.contains("RegionCoordinate")); + assertTrue(s.contains("overworld")); + assertTrue(s.contains("x=1")); + assertTrue(s.contains("z=-3")); + } + + @Test + void toStringShouldNotThrow() { + RegionCoordinate rc = new RegionCoordinate("the_end", 0, 0); + assertDoesNotThrow(() -> rc.toString()); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/livingworld/regions/RegionFactoryTest.java b/src/test/java/com/livingworld/regions/RegionFactoryTest.java new file mode 100644 index 0000000..9cadecc --- /dev/null +++ b/src/test/java/com/livingworld/regions/RegionFactoryTest.java @@ -0,0 +1,200 @@ +package com.livingworld.regions; + +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for {@link RegionFactory}. + */ +class RegionFactoryTest { + + private final RegionFactory factory = new RegionFactory(); + + // ------------------------------------------------------------------ + // Null handling + // ------------------------------------------------------------------ + + @Test + void createNewRegionWithNullCoordinateThrows() { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> factory.createNewRegion(null, 0L) + ); + assertEquals("coordinate must not be null", exception.getMessage()); + } + + // ------------------------------------------------------------------ + // Basic creation + // ------------------------------------------------------------------ + + @Test + void createNewRegionReturnsValidRegion() { + RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0); + long tick = 42L; + + Region region = factory.createNewRegion(coordinate, tick); + + assertNotNull(region); + assertDoesNotThrow(() -> region.validate()); + } + + // ------------------------------------------------------------------ + // UUID assignment + // ------------------------------------------------------------------ + + @Test + void createNewRegionAssignsRandomUUID() { + RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0); + + Region region1 = factory.createNewRegion(coordinate, 0L); + Region region2 = factory.createNewRegion(coordinate, 0L); + + assertNotEquals(region1.getId(), region2.getId()); + } + + @Test + void createNewRegionUUIDIsNotNull() { + RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0); + Region region = factory.createNewRegion(coordinate, 0L); + + assertNotNull(region.getId()); + assertInstanceOf(UUID.class, region.getId()); + } + + // ------------------------------------------------------------------ + // Coordinate assignment + // ------------------------------------------------------------------ + + @Test + void createNewRegionUsesArgumentCoordinate() { + RegionCoordinate coordinate = new RegionCoordinate("overworld", 5, -3); + Region region = factory.createNewRegion(coordinate, 0L); + + assertEquals(coordinate, region.getCoordinate()); + } + + // ------------------------------------------------------------------ + // Lifecycle state + // ------------------------------------------------------------------ + + @Test + void createNewRegionStartsActive() { + RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0); + Region region = factory.createNewRegion(coordinate, 0L); + + assertEquals(RegionLifecycleState.ACTIVE, region.getLifecycleState()); + } + + // ------------------------------------------------------------------ + // Simulation tick values + // ------------------------------------------------------------------ + + @Test + void createNewRegionSetsCreatedAtSimulationTick() { + RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0); + long tick = 12345L; + Region region = factory.createNewRegion(coordinate, tick); + + assertEquals(tick, region.getCreatedAtSimulationTick()); + } + + @Test + void createNewRegionSetsLastUpdatedSimulationTick() { + RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0); + long tick = 12345L; + Region region = factory.createNewRegion(coordinate, tick); + + assertEquals(tick, region.getLastUpdatedSimulationTick()); + } + + // ------------------------------------------------------------------ + // Dirty flag + // ------------------------------------------------------------------ + + @Test + void createNewRegionIsDirty() { + RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0); + Region region = factory.createNewRegion(coordinate, 0L); + + assertTrue(region.isDirty()); + } + + // ------------------------------------------------------------------ + // Default flags + // ------------------------------------------------------------------ + + @Test + void createNewRegionHasDefaultFlags() { + RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0); + Region region = factory.createNewRegion(coordinate, 0L); + + RegionFlags flags = region.getFlags(); + assertNotNull(flags); + assertFalse(flags.isHasPlayerActivity()); + assertFalse(flags.isHasHighPollution()); + assertFalse(flags.isHasLowSoilQuality()); + assertFalse(flags.isHasActiveEcosystemEvent()); + assertFalse(flags.isForceLoadedBySimulation()); + assertFalse(flags.isCorrupted()); + } + + // ------------------------------------------------------------------ + // Default metrics + // ------------------------------------------------------------------ + + @Test + void createNewRegionHasDefaultMetrics() { + RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0); + Region region = factory.createNewRegion(coordinate, 0L); + + RegionMetrics metrics = region.getMetrics(); + assertNotNull(metrics); + + assertEquals(60.0, metrics.getEcosystemHealth()); + assertEquals(0.0, metrics.getPollutionScore()); + assertEquals(60.0, metrics.getSoilQuality()); + assertEquals(60.0, metrics.getWaterQuality()); + assertEquals(50.0, metrics.getVegetationPressure()); + assertEquals(0.0, metrics.getResourceDepletion()); + assertEquals(50.0, metrics.getRecoveryPressure()); + } + + // ------------------------------------------------------------------ + // Empty module data + // ------------------------------------------------------------------ + + @Test + void createNewRegionHasEmptyModuleData() { + RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0); + Region region = factory.createNewRegion(coordinate, 0L); + + RegionModuleData moduleData = region.getModuleData(); + assertNotNull(moduleData); + assertTrue(moduleData.moduleIds().isEmpty()); + } + + // ------------------------------------------------------------------ + // Different dimensions + // ------------------------------------------------------------------ + + @Test + void createNewRegionWorksForDifferentDimensions() { + RegionCoordinate coordOverworld = new RegionCoordinate("overworld", 0, 0); + RegionCoordinate coordNether = new RegionCoordinate("the_nether", 1, 2); + RegionCoordinate coordEnd = new RegionCoordinate("end", -3, -4); + + Region regionOverworld = factory.createNewRegion(coordOverworld, 0L); + Region regionNether = factory.createNewRegion(coordNether, 0L); + Region regionEnd = factory.createNewRegion(coordEnd, 0L); + + assertEquals(coordOverworld, regionOverworld.getCoordinate()); + assertEquals(coordNether, regionNether.getCoordinate()); + assertEquals(coordEnd, regionEnd.getCoordinate()); + + assertNotEquals(regionOverworld.getId(), regionNether.getId()); + assertNotEquals(regionNether.getId(), regionEnd.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/com/livingworld/regions/RegionFlagsTest.java b/src/test/java/com/livingworld/regions/RegionFlagsTest.java new file mode 100644 index 0000000..2533304 --- /dev/null +++ b/src/test/java/com/livingworld/regions/RegionFlagsTest.java @@ -0,0 +1,217 @@ +package com.livingworld.regions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link RegionFlags}. + */ +class RegionFlagsTest { + + // ------------------------------------------------------------------ + // Default values + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Default values") + class DefaultValues { + + @Test + void allFlagsDefaultToFalse() { + RegionFlags flags = new RegionFlags(); + assertFalse(flags.isHasPlayerActivity()); + assertFalse(flags.isHasHighPollution()); + assertFalse(flags.isHasLowSoilQuality()); + assertFalse(flags.isHasActiveEcosystemEvent()); + assertFalse(flags.isForceLoadedBySimulation()); + assertFalse(flags.isCorrupted()); + } + + @Test + void instanceIsNotNull() { + assertNotNull(new RegionFlags()); + } + } + + // ------------------------------------------------------------------ + // Getters and Setters + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Getters and Setters") + class GetterSetterTests { + + @Test + void hasPlayerActivity() { + RegionFlags flags = new RegionFlags(); + flags.setHasPlayerActivity(true); + assertTrue(flags.isHasPlayerActivity()); + flags.setHasPlayerActivity(false); + assertFalse(flags.isHasPlayerActivity()); + } + + @Test + void hasHighPollution() { + RegionFlags flags = new RegionFlags(); + flags.setHasHighPollution(true); + assertTrue(flags.isHasHighPollution()); + flags.setHasHighPollution(false); + assertFalse(flags.isHasHighPollution()); + } + + @Test + void hasLowSoilQuality() { + RegionFlags flags = new RegionFlags(); + flags.setHasLowSoilQuality(true); + assertTrue(flags.isHasLowSoilQuality()); + flags.setHasLowSoilQuality(false); + assertFalse(flags.isHasLowSoilQuality()); + } + + @Test + void hasActiveEcosystemEvent() { + RegionFlags flags = new RegionFlags(); + flags.setHasActiveEcosystemEvent(true); + assertTrue(flags.isHasActiveEcosystemEvent()); + flags.setHasActiveEcosystemEvent(false); + assertFalse(flags.isHasActiveEcosystemEvent()); + } + + @Test + void forceLoadedBySimulation() { + RegionFlags flags = new RegionFlags(); + flags.setForceLoadedBySimulation(true); + assertTrue(flags.isForceLoadedBySimulation()); + flags.setForceLoadedBySimulation(false); + assertFalse(flags.isForceLoadedBySimulation()); + } + + @Test + void corrupted() { + RegionFlags flags = new RegionFlags(); + flags.setCorrupted(true); + assertTrue(flags.isCorrupted()); + flags.setCorrupted(false); + assertFalse(flags.isCorrupted()); + } + } + + // ------------------------------------------------------------------ + // copy() + // ------------------------------------------------------------------ + + @Nested + @DisplayName("copy()") + class CopyTests { + + @Test + void copyReturnsIndependentInstance() { + RegionFlags original = new RegionFlags(); + original.setHasPlayerActivity(true); + original.setCorrupted(true); + + RegionFlags copy = original.copy(); + assertNotNull(copy); + assertNotSame(original, copy); + } + + @Test + void copyPreservesAllFlagValues() { + RegionFlags original = new RegionFlags(); + original.setHasPlayerActivity(true); + original.setHasHighPollution(true); + original.setHasLowSoilQuality(true); + original.setHasActiveEcosystemEvent(true); + original.setForceLoadedBySimulation(true); + original.setCorrupted(true); + + RegionFlags copy = original.copy(); + assertTrue(copy.isHasPlayerActivity()); + assertTrue(copy.isHasHighPollution()); + assertTrue(copy.isHasLowSoilQuality()); + assertTrue(copy.isHasActiveEcosystemEvent()); + assertTrue(copy.isForceLoadedBySimulation()); + assertTrue(copy.isCorrupted()); + } + + @Test + void modifyingCopyDoesNotAffectOriginal() { + RegionFlags original = new RegionFlags(); + original.setHasPlayerActivity(true); + original.setCorrupted(false); + + RegionFlags copy = original.copy(); + copy.setHasPlayerActivity(false); + copy.setCorrupted(true); + + assertTrue(original.isHasPlayerActivity()); + assertFalse(original.isCorrupted()); + } + } + + // ------------------------------------------------------------------ + // clearTransientFlags() + // ------------------------------------------------------------------ + + @Nested + @DisplayName("clearTransientFlags()") + class ClearTransientFlagTests { + + @Test + void clearsTransientFlagsOnly() { + RegionFlags flags = new RegionFlags(); + flags.setHasPlayerActivity(true); + flags.setHasActiveEcosystemEvent(true); + flags.setHasHighPollution(true); + flags.setHasLowSoilQuality(true); + flags.setForceLoadedBySimulation(true); + flags.setCorrupted(true); + + flags.clearTransientFlags(); + + // Transient flags cleared + assertFalse(flags.isHasPlayerActivity()); + assertFalse(flags.isHasActiveEcosystemEvent()); + + // Persistent flags preserved + assertTrue(flags.isHasHighPollution()); + assertTrue(flags.isHasLowSoilQuality()); + assertTrue(flags.isForceLoadedBySimulation()); + assertTrue(flags.isCorrupted()); + } + + @Test + void safeToCallWhenAllFlagsAlreadyFalse() { + RegionFlags flags = new RegionFlags(); + flags.clearTransientFlags(); // should not throw + assertFalse(flags.isHasPlayerActivity()); + assertFalse(flags.isHasActiveEcosystemEvent()); + } + } + + // ------------------------------------------------------------------ + // toString + // ------------------------------------------------------------------ + + @Nested + @DisplayName("toString") + class ToStringTests { + + @Test + void toStringContainsFlagNames() { + RegionFlags flags = new RegionFlags(); + flags.setHasPlayerActivity(true); + String s = flags.toString(); + assertTrue(s.contains("hasPlayerActivity=true")); + assertTrue(s.contains("corrupted=false")); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/livingworld/regions/RegionLifecycleControllerTest.java b/src/test/java/com/livingworld/regions/RegionLifecycleControllerTest.java new file mode 100644 index 0000000..4887639 --- /dev/null +++ b/src/test/java/com/livingworld/regions/RegionLifecycleControllerTest.java @@ -0,0 +1,308 @@ +package com.livingworld.regions; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link RegionLifecycleController}. + */ +class RegionLifecycleControllerTest { + + // ------------------------------------------------------------------ + // Allowed transitions + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Allowed transitions") + class AllowedTransitions { + + @Test + @DisplayName("UNLOADED -> LOADING is allowed") + void unloadedToLoading() { + assertTrue(RegionLifecycleController.canTransition( + RegionLifecycleState.UNLOADED, RegionLifecycleState.LOADING)); + } + + @Test + @DisplayName("LOADING -> ACTIVE is allowed") + void loadingToActive() { + assertTrue(RegionLifecycleController.canTransition( + RegionLifecycleState.LOADING, RegionLifecycleState.ACTIVE)); + } + + @Test + @DisplayName("LOADING -> FAILED is allowed") + void loadingToFailed() { + assertTrue(RegionLifecycleController.canTransition( + RegionLifecycleState.LOADING, RegionLifecycleState.FAILED)); + } + + @Test + @DisplayName("ACTIVE -> DIRTY is allowed") + void activeToDirty() { + assertTrue(RegionLifecycleController.canTransition( + RegionLifecycleState.ACTIVE, RegionLifecycleState.DIRTY)); + } + + @Test + @DisplayName("DIRTY -> SAVING is allowed") + void dirtyToSaving() { + assertTrue(RegionLifecycleController.canTransition( + RegionLifecycleState.DIRTY, RegionLifecycleState.SAVING)); + } + + @Test + @DisplayName("SAVING -> ACTIVE is allowed") + void savingToActive() { + assertTrue(RegionLifecycleController.canTransition( + RegionLifecycleState.SAVING, RegionLifecycleState.ACTIVE)); + } + + @Test + @DisplayName("SAVING -> FAILED is allowed") + void savingToFailed() { + assertTrue(RegionLifecycleController.canTransition( + RegionLifecycleState.SAVING, RegionLifecycleState.FAILED)); + } + + @Test + @DisplayName("ACTIVE -> UNLOADING is allowed") + void activeToUnloading() { + assertTrue(RegionLifecycleController.canTransition( + RegionLifecycleState.ACTIVE, RegionLifecycleState.UNLOADING)); + } + + @Test + @DisplayName("UNLOADING -> UNLOADED is allowed") + void unloadingToUnloaded() { + assertTrue(RegionLifecycleController.canTransition( + RegionLifecycleState.UNLOADING, RegionLifecycleState.UNLOADED)); + } + + @Test + @DisplayName("FAILED -> LOADING is allowed") + void failedToLoading() { + assertTrue(RegionLifecycleController.canTransition( + RegionLifecycleState.FAILED, RegionLifecycleState.LOADING)); + } + } + + // ------------------------------------------------------------------ + // Disallowed transitions + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Disallowed transitions") + class DisallowedTransitions { + + private static final String[] STATES = { + "UNLOADED", "LOADING", "ACTIVE", "DIRTY", "SAVING", "FAILED", "UNLOADING" + }; + + @Test + @DisplayName("Same state is not allowed (no self-transitions)") + void sameStateNotAllowed() { + for (String state : STATES) { + assertFalse(RegionLifecycleController.canTransition( + RegionLifecycleState.valueOf(state), RegionLifecycleState.valueOf(state))); + } + } + + @Test + @DisplayName("UNLOADED -> ACTIVE is not allowed") + void unloadedToActive() { + assertFalse(RegionLifecycleController.canTransition( + RegionLifecycleState.UNLOADED, RegionLifecycleState.ACTIVE)); + } + + @Test + @DisplayName("ACTIVE -> LOADING is not allowed") + void activeToLoading() { + assertFalse(RegionLifecycleController.canTransition( + RegionLifecycleState.ACTIVE, RegionLifecycleState.LOADING)); + } + + @Test + @DisplayName("FAILED -> UNLOADED is not allowed") + void failedToUnloaded() { + assertFalse(RegionLifecycleController.canTransition( + RegionLifecycleState.FAILED, RegionLifecycleState.UNLOADED)); + } + + @Test + @DisplayName("ACTIVE -> SAVING is not allowed") + void activeToSaving() { + assertFalse(RegionLifecycleController.canTransition( + RegionLifecycleState.ACTIVE, RegionLifecycleState.SAVING)); + } + + @Test + @DisplayName("All invalid transitions throw IllegalStateException on transition()") + void invalidTransitionsThrowException() { + Region region1 = new Region( + java.util.UUID.randomUUID(), + new RegionCoordinate("minecraft:overworld", 0, 0), + RegionLifecycleState.ACTIVE, 0, 0, false, + new RegionFlags(), new RegionMetrics(), new RegionModuleData()); + + // ACTIVE -> LOADING (invalid) + assertThrows(IllegalStateException.class, () -> + RegionLifecycleController.transition(region1, RegionLifecycleState.LOADING)); + + // ACTIVE -> SAVING (invalid) + Region region2 = new Region( + java.util.UUID.randomUUID(), + new RegionCoordinate("minecraft:overworld", 0, 0), + RegionLifecycleState.ACTIVE, 0, 0, false, + new RegionFlags(), new RegionMetrics(), new RegionModuleData()); + + assertThrows(IllegalStateException.class, () -> + RegionLifecycleController.transition(region2, RegionLifecycleState.SAVING)); + } + } + + // ------------------------------------------------------------------ + // Null handling + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Null handling") + class NullHandling { + + @Test + @DisplayName("canTransition throws IllegalArgumentException for null from") + void canTransitionNullFrom() { + assertThrows(IllegalArgumentException.class, () -> + RegionLifecycleController.canTransition(null, RegionLifecycleState.ACTIVE)); + } + + @Test + @DisplayName("canTransition throws IllegalArgumentException for null to") + void canTransitionNullTo() { + assertThrows(IllegalArgumentException.class, () -> + RegionLifecycleController.canTransition(RegionLifecycleState.UNLOADED, null)); + } + + @Test + @DisplayName("transition throws IllegalArgumentException for null region") + void transitionNullRegion() { + assertThrows(IllegalArgumentException.class, () -> + RegionLifecycleController.transition(null, RegionLifecycleState.LOADING)); + } + + @Test + @DisplayName("transition throws IllegalArgumentException for null target") + void transitionNullTarget() { + Region region = new Region( + java.util.UUID.randomUUID(), + new RegionCoordinate("minecraft:overworld", 0, 0), + RegionLifecycleState.UNLOADED, 0, 0, false, + new RegionFlags(), new RegionMetrics(), new RegionModuleData()); + + assertThrows(IllegalArgumentException.class, () -> + RegionLifecycleController.transition(region, null)); + } + } + + // ------------------------------------------------------------------ + // transition() side effects + // ------------------------------------------------------------------ + + @Nested + @DisplayName("transition() side effects") + class TransitionSideEffects { + + @Test + @DisplayName("transition() updates lifecycle state and marks dirty") + void transitionUpdatesStateAndMarksDirty() { + Region region = new Region( + java.util.UUID.randomUUID(), + new RegionCoordinate("minecraft:overworld", 0, 0), + RegionLifecycleState.UNLOADED, 0, 0, false, + new RegionFlags(), new RegionMetrics(), new RegionModuleData()); + + assertFalse(region.isDirty()); + assertEquals(RegionLifecycleState.UNLOADED, region.getLifecycleState()); + + RegionLifecycleController.transition(region, RegionLifecycleState.LOADING); + + assertEquals(RegionLifecycleState.LOADING, region.getLifecycleState()); + assertTrue(region.isDirty()); + } + + @Test + @DisplayName("transition() updates state through full load-save-unload cycle") + void fullCycleTransition() { + Region region = new Region( + java.util.UUID.randomUUID(), + new RegionCoordinate("minecraft:overworld", 0, 0), + RegionLifecycleState.UNLOADED, 0, 0, false, + new RegionFlags(), new RegionMetrics(), new RegionModuleData()); + + // UNLOADED -> LOADING + RegionLifecycleController.transition(region, RegionLifecycleState.LOADING); + assertEquals(RegionLifecycleState.LOADING, region.getLifecycleState()); + + // LOADING -> ACTIVE + RegionLifecycleController.transition(region, RegionLifecycleState.ACTIVE); + assertEquals(RegionLifecycleState.ACTIVE, region.getLifecycleState()); + + // ACTIVE -> DIRTY + RegionLifecycleController.transition(region, RegionLifecycleState.DIRTY); + assertEquals(RegionLifecycleState.DIRTY, region.getLifecycleState()); + + // DIRTY -> SAVING + RegionLifecycleController.transition(region, RegionLifecycleState.SAVING); + assertEquals(RegionLifecycleState.SAVING, region.getLifecycleState()); + + // SAVING -> ACTIVE (successful save) + RegionLifecycleController.transition(region, RegionLifecycleState.ACTIVE); + assertEquals(RegionLifecycleState.ACTIVE, region.getLifecycleState()); + + // ACTIVE -> UNLOADING + RegionLifecycleController.transition(region, RegionLifecycleState.UNLOADING); + assertEquals(RegionLifecycleState.UNLOADING, region.getLifecycleState()); + + // UNLOADING -> UNLOADED + RegionLifecycleController.transition(region, RegionLifecycleState.UNLOADED); + assertEquals(RegionLifecycleState.UNLOADED, region.getLifecycleState()); + } + + @Test + @DisplayName("transition() allows FAILED -> LOADING recovery") + void failedToLoadingRecovery() { + Region region = new Region( + java.util.UUID.randomUUID(), + new RegionCoordinate("minecraft:overworld", 0, 0), + RegionLifecycleState.FAILED, 0, 0, false, + new RegionFlags(), new RegionMetrics(), new RegionModuleData()); + + RegionLifecycleController.transition(region, RegionLifecycleState.LOADING); + assertEquals(RegionLifecycleState.LOADING, region.getLifecycleState()); + } + + @Test + @DisplayName("transition() allows SAVING -> FAILED error path") + void savingToFailedErrorPath() { + Region region = new Region( + java.util.UUID.randomUUID(), + new RegionCoordinate("minecraft:overworld", 0, 0), + RegionLifecycleState.ACTIVE, 0, 0, false, + new RegionFlags(), new RegionMetrics(), new RegionModuleData()); + + // ACTIVE -> DIRTY + RegionLifecycleController.transition(region, RegionLifecycleState.DIRTY); + assertEquals(RegionLifecycleState.DIRTY, region.getLifecycleState()); + + // DIRTY -> SAVING + RegionLifecycleController.transition(region, RegionLifecycleState.SAVING); + assertEquals(RegionLifecycleState.SAVING, region.getLifecycleState()); + + // SAVING -> FAILED (save error) + RegionLifecycleController.transition(region, RegionLifecycleState.FAILED); + assertEquals(RegionLifecycleState.FAILED, region.getLifecycleState()); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/livingworld/regions/RegionMetricsTest.java b/src/test/java/com/livingworld/regions/RegionMetricsTest.java new file mode 100644 index 0000000..4316d49 --- /dev/null +++ b/src/test/java/com/livingworld/regions/RegionMetricsTest.java @@ -0,0 +1,435 @@ +package com.livingworld.regions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Comprehensive tests for {@link RegionMetrics}. + */ +class RegionMetricsTest { + + // ------------------------------------------------------------------ + // Default Values Tests + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Default Values") + class DefaultValues { + + @Test + @DisplayName("defaults() returns correct ecosystem health") + void defaultsEcosystemHealth() { + RegionMetrics metrics = RegionMetrics.defaults(); + assertEquals(60, metrics.getEcosystemHealth(), 0.001); + } + + @Test + @DisplayName("defaults() returns correct pollution score") + void defaultsPollutionScore() { + RegionMetrics metrics = RegionMetrics.defaults(); + assertEquals(0, metrics.getPollutionScore(), 0.001); + } + + @Test + @DisplayName("defaults() returns correct soil quality") + void defaultsSoilQuality() { + RegionMetrics metrics = RegionMetrics.defaults(); + assertEquals(60, metrics.getSoilQuality(), 0.001); + } + + @Test + @DisplayName("defaults() returns correct water quality") + void defaultsWaterQuality() { + RegionMetrics metrics = RegionMetrics.defaults(); + assertEquals(60, metrics.getWaterQuality(), 0.001); + } + + @Test + @DisplayName("defaults() returns correct vegetation pressure") + void defaultsVegetationPressure() { + RegionMetrics metrics = RegionMetrics.defaults(); + assertEquals(50, metrics.getVegetationPressure(), 0.001); + } + + @Test + @DisplayName("defaults() returns correct resource depletion") + void defaultsResourceDepletion() { + RegionMetrics metrics = RegionMetrics.defaults(); + assertEquals(0, metrics.getResourceDepletion(), 0.001); + } + + @Test + @DisplayName("defaults() returns correct recovery pressure") + void defaultsRecoveryPressure() { + RegionMetrics metrics = RegionMetrics.defaults(); + assertEquals(50, metrics.getRecoveryPressure(), 0.001); + } + + @Test + @DisplayName("default constructor initializes all fields to 0") + void defaultConstructorAllZeros() { + RegionMetrics metrics = new RegionMetrics(); + assertEquals(0, metrics.getEcosystemHealth(), 0.001); + assertEquals(0, metrics.getPollutionScore(), 0.001); + assertEquals(0, metrics.getSoilQuality(), 0.001); + assertEquals(0, metrics.getWaterQuality(), 0.001); + assertEquals(0, metrics.getVegetationPressure(), 0.001); + assertEquals(0, metrics.getResourceDepletion(), 0.001); + assertEquals(0, metrics.getRecoveryPressure(), 0.001); + } + } + + // ------------------------------------------------------------------ + // Getter/Setter Tests + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Getter and Setter Clamping") + class GetterSetterClamping { + + @Test + @DisplayName("setter clamps values above 100 to 100") + void setterClampsAboveMax() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setEcosystemHealth(150); + assertEquals(100, metrics.getEcosystemHealth(), 0.001); + } + + @Test + @DisplayName("setter clamps values below 0 to 0") + void setterClampsBelowMin() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setPollutionScore(-50); + assertEquals(0, metrics.getPollutionScore(), 0.001); + } + + @Test + @DisplayName("setter accepts exact boundary values") + void setterAcceptsBoundaries() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setSoilQuality(0); + assertEquals(0, metrics.getSoilQuality(), 0.001); + metrics.setSoilQuality(100); + assertEquals(100, metrics.getSoilQuality(), 0.001); + } + + @Test + @DisplayName("setter accepts normal values") + void setterAcceptsNormalValues() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setWaterQuality(42.5); + assertEquals(42.5, metrics.getWaterQuality(), 0.001); + } + + @Test + @DisplayName("all setters clamp correctly") + void allSettersClamp() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setVegetationPressure(-10); + assertEquals(0, metrics.getVegetationPressure(), 0.001); + + metrics.setResourceDepletion(200); + assertEquals(100, metrics.getResourceDepletion(), 0.001); + + metrics.setRecoveryPressure(-5); + assertEquals(0, metrics.getRecoveryPressure(), 0.001); + } + } + + // ------------------------------------------------------------------ + // Normalize Tests + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Normalize") + class NormalizeTests { + + @Test + @DisplayName("normalize() clamps values above 100") + void normalizeClampsAboveMax() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setEcosystemHealth(150); + metrics.setPollutionScore(-20); + metrics.normalize(); + assertEquals(100, metrics.getEcosystemHealth(), 0.001); + assertEquals(0, metrics.getPollutionScore(), 0.001); + } + + @Test + @DisplayName("normalize() clamps values below 0") + void normalizeClampsBelowMin() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setEcosystemHealth(-50); + metrics.setPollutionScore(200); + metrics.normalize(); + assertEquals(0, metrics.getEcosystemHealth(), 0.001); + assertEquals(100, metrics.getPollutionScore(), 0.001); + } + + @Test + @DisplayName("normalize() leaves valid values unchanged") + void normalizeLeavesValidUnchanged() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setEcosystemHealth(50); + metrics.setPollutionScore(30); + metrics.normalize(); + assertEquals(50, metrics.getEcosystemHealth(), 0.001); + assertEquals(30, metrics.getPollutionScore(), 0.001); + } + + @Test + @DisplayName("normalize() returns this for chaining") + void normalizeReturnsThis() { + RegionMetrics metrics = new RegionMetrics(); + RegionMetrics result = metrics.normalize(); + assertTrue(result == metrics, "normalize() should return the same instance"); + } + } + + // ------------------------------------------------------------------ + // Copy Tests + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Copy") + class CopyTests { + + @Test + @DisplayName("copy() returns independent instance") + void copyReturnsIndependentInstance() { + RegionMetrics original = new RegionMetrics(); + original.setEcosystemHealth(80); + original.setPollutionScore(30); + + RegionMetrics copy = original.copy(); + + assertEquals(80, copy.getEcosystemHealth(), 0.001); + assertEquals(30, copy.getPollutionScore(), 0.001); + + // Modify the copy + copy.setEcosystemHealth(90); + + // Original should be unchanged + assertEquals(80, original.getEcosystemHealth(), 0.001); + } + + @Test + @DisplayName("copy() returns a different object") + void copyReturnsDifferentObject() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setSoilQuality(75); + RegionMetrics copy = metrics.copy(); + assertNotSame(metrics, copy); + } + + @Test + @DisplayName("copy() copies all fields") + void copyAllFields() { + RegionMetrics original = new RegionMetrics(); + original.setEcosystemHealth(10); + original.setPollutionScore(20); + original.setSoilQuality(30); + original.setWaterQuality(40); + original.setVegetationPressure(50); + original.setResourceDepletion(60); + original.setRecoveryPressure(70); + + RegionMetrics copy = original.copy(); + + assertEquals(original.getEcosystemHealth(), copy.getEcosystemHealth(), 0.001); + assertEquals(original.getPollutionScore(), copy.getPollutionScore(), 0.001); + assertEquals(original.getSoilQuality(), copy.getSoilQuality(), 0.001); + assertEquals(original.getWaterQuality(), copy.getWaterQuality(), 0.001); + assertEquals(original.getVegetationPressure(), copy.getVegetationPressure(), 0.001); + assertEquals(original.getResourceDepletion(), copy.getResourceDepletion(), 0.001); + assertEquals(original.getRecoveryPressure(), copy.getRecoveryPressure(), 0.001); + } + } + + // ------------------------------------------------------------------ + // ApplyDelta Tests + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Apply Delta") + class ApplyDeltaTests { + + @Test + @DisplayName("applyDelta() modifies all fields correctly") + void applyDeltaAllFields() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setEcosystemHealth(50); + metrics.setPollutionScore(20); + metrics.setSoilQuality(80); + + metrics.applyDelta(10, -5, 15, 0, 0, 0, 0); + + assertEquals(60, metrics.getEcosystemHealth(), 0.001); + assertEquals(15, metrics.getPollutionScore(), 0.001); + assertEquals(95, metrics.getSoilQuality(), 0.001); + } + + @Test + @DisplayName("applyDelta() clamps results above max") + void applyDeltaClampsAboveMax() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setEcosystemHealth(95); + metrics.applyDelta(20, 0, 0, 0, 0, 0, 0); + assertEquals(100, metrics.getEcosystemHealth(), 0.001); + } + + @Test + @DisplayName("applyDelta() clamps results below min") + void applyDeltaClampsBelowMin() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setPollutionScore(5); + metrics.applyDelta(-10, 0, 0, 0, 0, 0, 0); + assertEquals(0, metrics.getEcosystemHealth(), 0.001); + } + + @Test + @DisplayName("applyDelta() returns this for chaining") + void applyDeltaReturnsThis() { + RegionMetrics metrics = new RegionMetrics(); + RegionMetrics result = metrics.applyDelta(0, 0, 0, 0, 0, 0, 0); + assertTrue(result == metrics); + } + + @Test + @DisplayName("applyEcosystemHealthDelta() modifies only ecosystem health") + void applySingleDelta() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setEcosystemHealth(50); + metrics.setPollutionScore(30); + metrics.applyEcosystemHealthDelta(10); + assertEquals(60, metrics.getEcosystemHealth(), 0.001); + assertEquals(30, metrics.getPollutionScore(), 0.001); + } + + @Test + @DisplayName("applyPollutionScoreDelta() clamps correctly") + void applySingleDeltaClamps() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setPollutionScore(95); + metrics.applyPollutionScoreDelta(20); + assertEquals(100, metrics.getPollutionScore(), 0.001); + } + + @Test + @DisplayName("applySoilQualityDelta() clamps below min") + void applySingleDeltaClampsBelow() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setSoilQuality(5); + metrics.applySoilQualityDelta(-10); + assertEquals(0, metrics.getSoilQuality(), 0.001); + } + + @Test + @DisplayName("applyWaterQualityDelta() works correctly") + void applyWaterQualityDelta() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setWaterQuality(50); + metrics.applyWaterQualityDelta(25); + assertEquals(75, metrics.getWaterQuality(), 0.001); + } + + @Test + @DisplayName("applyVegetationPressureDelta() works correctly") + void applyVegetationPressureDelta() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setVegetationPressure(30); + metrics.applyVegetationPressureDelta(-10); + assertEquals(20, metrics.getVegetationPressure(), 0.001); + } + + @Test + @DisplayName("applyResourceDepletionDelta() works correctly") + void applyResourceDepletionDelta() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setResourceDepletion(40); + metrics.applyResourceDepletionDelta(60); + assertEquals(100, metrics.getResourceDepletion(), 0.001); + } + + @Test + @DisplayName("applyRecoveryPressureDelta() works correctly") + void applyRecoveryPressureDelta() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setRecoveryPressure(25); + metrics.applyRecoveryPressureDelta(-15); + assertEquals(10, metrics.getRecoveryPressure(), 0.001); + } + + @Test + @DisplayName("individual apply methods return this for chaining") + void individualApplyMethodsReturnThis() { + RegionMetrics metrics = new RegionMetrics(); + assertTrue(metrics.applyEcosystemHealthDelta(5) == metrics); + assertTrue(metrics.applyPollutionScoreDelta(5) == metrics); + assertTrue(metrics.applySoilQualityDelta(5) == metrics); + assertTrue(metrics.applyWaterQualityDelta(5) == metrics); + assertTrue(metrics.applyVegetationPressureDelta(5) == metrics); + assertTrue(metrics.applyResourceDepletionDelta(5) == metrics); + assertTrue(metrics.applyRecoveryPressureDelta(5) == metrics); + } + } + + // ------------------------------------------------------------------ + // Constants Tests + // ------------------------------------------------------------------ + + @Nested + @DisplayName("Constants") + class ConstantsTests { + + @Test + @DisplayName("MIN_VALUE is 0") + void minValue() { + assertEquals(0, RegionMetrics.MIN_VALUE); + } + + @Test + @DisplayName("MAX_VALUE is 100") + void maxValue() { + assertEquals(100, RegionMetrics.MAX_VALUE); + } + } + + // ------------------------------------------------------------------ + // toString Tests + // ------------------------------------------------------------------ + + @Nested + @DisplayName("toString") + class ToStringTests { + + @Test + @DisplayName("toString() includes all field names and values") + void toStringIncludesAllFields() { + RegionMetrics metrics = new RegionMetrics(); + metrics.setEcosystemHealth(60); + metrics.setPollutionScore(10); + String str = metrics.toString(); + + assertTrue(str.contains("ecosystemHealth")); + assertTrue(str.contains("pollutionScore")); + assertTrue(str.contains("soilQuality")); + assertTrue(str.contains("waterQuality")); + assertTrue(str.contains("vegetationPressure")); + assertTrue(str.contains("resourceDepletion")); + assertTrue(str.contains("recoveryPressure")); + } + + @Test + @DisplayName("toString() starts with class name") + void toStringStartsWithClassName() { + RegionMetrics metrics = new RegionMetrics(); + assertTrue(metrics.toString().startsWith("RegionMetrics{")); + } + } +} \ No newline at end of file