"""Tests for the constraint resolver engine.""" from physcom.engine.constraint_resolver import ConstraintResolver from physcom.models.combination import Combination from physcom.models.entity import Entity, Dependency def test_compatible_ground_combo(bicycle, human_pedalling): """Bicycle + Human Pedalling should be valid.""" resolver = ConstraintResolver() combo = Combination(entities=[bicycle, human_pedalling]) result = resolver.resolve(combo) assert result.status != "blocked", f"Unexpected block: {result.violations}" def test_solar_sail_blocks_with_walking(walking, solar_sail): """Walking (ground) + Solar Sail (space) should be blocked by medium mutex.""" resolver = ConstraintResolver() combo = Combination(entities=[walking, solar_sail]) result = resolver.resolve(combo) assert result.status == "blocked" assert any("mutually exclusive" in v for v in result.violations) def test_spaceship_compatible_with_solar_sail(spaceship, solar_sail): """Spaceship + Solar Sail both need space/vacuum — should not conflict.""" resolver = ConstraintResolver() combo = Combination(entities=[spaceship, solar_sail]) result = resolver.resolve(combo) # Should not be blocked by atmosphere or medium medium_blocks = [v for v in result.violations if "mutually exclusive" in v] assert len(medium_blocks) == 0 def test_nuclear_reactor_blocks_with_bicycle(bicycle, nuclear_reactor): """Nuclear reactor min_mass=2000kg vs bicycle max_mass=30kg → range incompatibility.""" resolver = ConstraintResolver() combo = Combination(entities=[bicycle, nuclear_reactor]) result = resolver.resolve(combo) assert result.status == "blocked" assert any("mass" in v.lower() for v in result.violations) def test_force_scale_mismatch_blocks(): """A platform needing 1MW and a power source providing 1W → force deficit block.""" platform = Entity( name="HeavyPlatform", dimension="platform", dependencies=[ Dependency("force", "force_required_watts", "1000000", "watts", "range_min"), ], ) power = Entity( name="TinyPower", dimension="power_source", dependencies=[ Dependency("force", "force_output_watts", "1", "watts", "provides"), ], ) resolver = ConstraintResolver() combo = Combination(entities=[platform, power]) result = resolver.resolve(combo) assert result.status == "blocked" assert any("force deficit" in v for v in result.violations) def test_force_under_powered_warning(): """Power source slightly below requirement → warning, not block.""" platform = Entity( name="MedPlatform", dimension="platform", dependencies=[ Dependency("force", "force_required_watts", "1000", "watts", "range_min"), ], ) power = Entity( name="WeakPower", dimension="power_source", dependencies=[ Dependency("force", "force_output_watts", "500", "watts", "provides"), ], ) resolver = ConstraintResolver() combo = Combination(entities=[platform, power]) result = resolver.resolve(combo) # Under-powered but within 100x → warning, not block assert result.status != "blocked" assert any("under-powered" in w for w in result.warnings) def test_requires_vs_excludes(): """Direct requires/excludes contradiction.""" a = Entity( name="A", dimension="platform", dependencies=[Dependency("environment", "oxygen", "true", None, "requires")], ) b = Entity( name="B", dimension="power_source", dependencies=[Dependency("environment", "oxygen", "true", None, "excludes")], ) resolver = ConstraintResolver() combo = Combination(entities=[a, b]) result = resolver.resolve(combo) assert result.status == "blocked" assert any("excludes" in v for v in result.violations) def test_ice_engine_blocks_with_spaceship(spaceship, ice_engine): """ICE requires standard atmosphere, spaceship requires vacuum_or_thin → mutex.""" resolver = ConstraintResolver() combo = Combination(entities=[spaceship, ice_engine]) result = resolver.resolve(combo) assert result.status == "blocked" assert any("atmosphere" in v for v in result.violations) def test_hydrogen_bicycle_valid(bicycle, hydrogen_engine): """Hydrogen bike — the README's example of a plausible novel concept.""" resolver = ConstraintResolver() combo = Combination(entities=[bicycle, hydrogen_engine]) result = resolver.resolve(combo) # Should pass constraints (mass range is compatible: h2 min 30kg, bike max 30kg) # This is actually a borderline case — let's just verify no hard physics blocks range_blocks = [v for v in result.violations if "mutually exclusive" in v or "atmosphere" in v] assert len(range_blocks) == 0