Implement gradual evolution mechanics and simulation time handling in Owniverse.GI
- Added `blastability` property to entities for vulnerability tracking. - Enhanced planet string parsing with validation and error handling. - Introduced `simulation_rules.md` to document evolution mechanics and rules. - Created tests for time progression and stellar evolution rules, ensuring correct handling of fractional years and resource production. - Implemented `simulation_time.gd` for managing accumulated time and year processing. - Updated tests to validate legacy save compatibility and migration to new time schema.
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
extends SceneTree
|
||||
const Clock = preload("res://world/owniverse/simulation_time.gd")
|
||||
var checks: int = 0
|
||||
var failures: int = 0
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(condition: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
push_error(message)
|
||||
|
||||
func fixture():
|
||||
var world = load("res://world/owniverse/owniverse.gd").new()
|
||||
var global: Node = root.get_node("Global")
|
||||
for id in global.OwniverseEntities:
|
||||
var entity = load("res://world/owniverse/owniverse_entity.gd").new(global.OwniverseEntities[id])
|
||||
world.entities[id] = entity
|
||||
world.state.amount[id] = 0.0
|
||||
world.state.stats.total_amount[id] = 0.0
|
||||
if entity.group_as != "":
|
||||
world.state.amount[entity.group_as] = 0.0
|
||||
world.state.stats.total_amount[entity.group_as] = 0.0
|
||||
world.entities["N"].life_product = {}
|
||||
world.entities["N"].product_per_year_type = "H"
|
||||
world.entities["N"].product_per_year_amount = 10.0
|
||||
world.entities["N"].auto = 0.0
|
||||
world.state.amount.N = 1.0
|
||||
return world
|
||||
|
||||
func dispose(world) -> void:
|
||||
for entity in world.entities.values():
|
||||
entity.free()
|
||||
world.free()
|
||||
|
||||
func run() -> void:
|
||||
var saves: Node = root.get_node("SaveManager")
|
||||
var world = fixture()
|
||||
world.evolute(0.09)
|
||||
check(world.state.time.year == 0.0, "Do not process before a tenth boundary")
|
||||
check(world.state.amount.H == 0.0, "No premature production")
|
||||
world.evolute(0.01)
|
||||
check(world.state.time.year == 0.1, "Process the first tenth")
|
||||
check(world.state.amount.H == 1.0, "Production runs at 0.1 year")
|
||||
world.evolute(0.2)
|
||||
check(world.state.time.year == 0.3 and world.state.amount.H == 3.0, "Do not reprocess previous tenths")
|
||||
var before: Dictionary = world.state.duplicate(true)
|
||||
for invalid in [0.0, -1.0, NAN, INF, 1e308]:
|
||||
world.evolute(invalid)
|
||||
check(world.state == before, "Invalid/overflow interval rejected")
|
||||
check(saves.validate_data({"state": world.state}) == "", "Fractional save is valid")
|
||||
var invalid_save: Dictionary = world.state.duplicate(true)
|
||||
invalid_save.time.year = 0.25
|
||||
check(saves.validate_data({"state": invalid_save}) != "", "Reject inconsistent fractional clock")
|
||||
dispose(world)
|
||||
|
||||
for fps in [30, 60, 144]:
|
||||
world = fixture()
|
||||
for frame in fps:
|
||||
world.evolute(1.0 / fps)
|
||||
check(world.state.time.year == 1.0, "One second at %d fps" % fps)
|
||||
check(world.state.amount.H == 10.0, "Production across frames at %d fps" % fps)
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.evolute(0.05)
|
||||
world.speed = 2
|
||||
world.evolute(0.005)
|
||||
check(world.state.time.year == 0.1, "Speed change preserves accumulated fraction")
|
||||
world.speed = 100
|
||||
check(world.speed == 5 and world.state.speed_in_year == 10000.0, "Upper speed limit unchanged")
|
||||
world.speed = -10
|
||||
check(world.speed == 1 and world.state.speed_in_year == 1.0, "Lower speed limit unchanged")
|
||||
dispose(world)
|
||||
|
||||
world = fixture()
|
||||
world.state.version = "0.1.0"
|
||||
world.state.time = {"datetime": 0.45, "year": 0.0}
|
||||
check(saves.validate_data({"state": world.state}) == "", "Accept legacy integer-year save")
|
||||
world.migrate_time_precision()
|
||||
check(world.state.version == "0.1.1", "Migrate schema version")
|
||||
check(world.state.time.year == 0.4 and world.state.time.datetime == 0.45, "Keep unprocessed sub-tenth remainder")
|
||||
check(world.state.amount.H == 4.0, "Migration processes pending legacy time")
|
||||
check(saves.validate_data({"state": world.state}) == "", "Migrated state validates")
|
||||
before = world.state.duplicate(true)
|
||||
world.migrate_time_precision()
|
||||
check(world.state == before, "Migration is idempotent")
|
||||
# Verify in-memory save roundtrip without touching the player's save.
|
||||
var restored = fixture()
|
||||
restored.state = bytes_to_var(var_to_bytes(world.state))
|
||||
world.evolute(0.05)
|
||||
restored.evolute(0.05)
|
||||
check(world.state == restored.state, "Save continuation preserves time and production")
|
||||
check(world.state.time.year == 0.5 and world.state.amount.H == 5.0, "No double-counted migration time")
|
||||
dispose(world)
|
||||
dispose(restored)
|
||||
|
||||
world = fixture()
|
||||
world.entities.N.output_and = {"He": 1.0}
|
||||
world.entities.N.output_or = {}
|
||||
world.state.batches.N = [{"start_year": 0.1, "end_year": 0.1, "amount": 1.0}]
|
||||
world.evolute(0.1)
|
||||
check(world.state.amount.N == 0.0 and world.state.amount.He == 1.0, "Existing evolution executes at fractional boundary")
|
||||
dispose(world)
|
||||
check(Clock.processed_year(0.099) == 0.0, "Do not round a genuine incomplete tenth forward")
|
||||
check(Clock.processed_year(1e9 + 0.45) == 1e9 + 0.4, "Large ages preserve fractional precision")
|
||||
print("Time migration: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
Reference in New Issue
Block a user