| """ |
| Tests for Phase 5: Spelke's Core Knowledge Systems |
| |
| Validates that innate priors produce biologically correct behavior: |
| - Object permanence tracking |
| - Physics predictions (gravity, bounce, support) |
| - Numerosity discrimination (Weber ratio) |
| - Agent detection and social evaluation |
| |
| Author: Algorembrant, Rembrant Oyangoren Albeos (2026) |
| """ |
|
|
| import numpy as np |
|
|
| from hippocampaif.core_knowledge.object_system import ObjectSystem |
| from hippocampaif.core_knowledge.agent_system import AgentSystem |
| from hippocampaif.core_knowledge.number_system import NumberSystem |
| from hippocampaif.core_knowledge.geometry_system import GeometrySystem |
| from hippocampaif.core_knowledge.social_system import SocialSystem |
| from hippocampaif.core_knowledge.physics_system import PhysicsSystem, PhysicsState |
|
|
|
|
| def test_object_permanence(): |
| """Objects persist when occluded — they don't vanish.""" |
| obj_sys = ObjectSystem(max_objects=10, max_occlusion_frames=30) |
| |
| |
| obj_id = obj_sys.register_object(np.array([5.0, 5.0]), size=1.0) |
| |
| |
| obj_sys.update([{'position': np.array([5.0, 5.0]), 'size': 1.0}]) |
| assert obj_sys.num_objects == 1 |
| |
| |
| obj_sys.update([]) |
| |
| |
| assert obj_sys.num_objects == 1, "Object permanence violated — object was deleted!" |
| |
| |
| pred = obj_sys.predict_occluded(obj_id) |
| assert pred is not None, "System lost track of occluded object" |
| print(" PASS Object Permanence (objects persist when occluded)") |
|
|
|
|
| def test_object_continuity_violation(): |
| """Objects cannot teleport — continuity violations generate surprise.""" |
| obj_sys = ObjectSystem() |
| |
| |
| obj_sys.register_object(np.array([0.0, 0.0])) |
| obj_sys.update([{'position': np.array([1.0, 0.0])}]) |
| obj_sys.update([{'position': np.array([2.0, 0.0])}]) |
| |
| |
| violations = obj_sys.update([{'position': np.array([100.0, 0.0])}]) |
| |
| continuity_violations = [v for v in violations if v['type'] == 'continuity_violation'] |
| assert len(continuity_violations) > 0, "System failed to detect teleportation!" |
| print(" PASS Object Continuity (teleportation detected)") |
|
|
|
|
| def test_physics_gravity(): |
| """Unsupported objects fall downward.""" |
| phys = PhysicsSystem(gravity=9.8, friction=0.0, dt=0.1) |
| |
| |
| state = PhysicsState( |
| position=np.array([5.0, 0.0]), |
| velocity=np.array([0.0, 0.0]), |
| mass=1.0 |
| ) |
| |
| trajectory = phys.predict_trajectory(state, steps=20) |
| |
| |
| assert trajectory[-1][1] > trajectory[0][1], "Object did not fall under gravity!" |
| print(" PASS Physics Gravity (objects fall downward)") |
|
|
|
|
| def test_physics_bounce(): |
| """Objects bounce off walls (elasticity prior).""" |
| phys = PhysicsSystem(gravity=0.0, friction=0.0, dt=0.1) |
| |
| |
| state = PhysicsState( |
| position=np.array([8.0, 5.0]), |
| velocity=np.array([5.0, 0.0]), |
| elasticity=1.0, |
| radius=0.5 |
| ) |
| |
| bounds = (np.array([0.0, 0.0]), np.array([10.0, 10.0])) |
| trajectory = phys.predict_trajectory(state, steps=20, bounds=bounds) |
| |
| |
| x_positions = [t[0] for t in trajectory] |
| went_right = any(x > 8.0 for x in x_positions) |
| came_back = any(x < 8.0 for x in x_positions[5:]) |
| |
| assert went_right or came_back, "Ball did not bounce off wall!" |
| print(" PASS Physics Bounce (elastic collision with boundary)") |
|
|
|
|
| def test_physics_support(): |
| """Unsupported objects should fall; supported objects should not.""" |
| phys = PhysicsSystem() |
| |
| |
| surfaces = [{'y': 10.0, 'x_min': 0, 'x_max': 20}] |
| |
| supported = phys.check_support(np.array([5.0, 9.7]), 0.5, surfaces) |
| assert supported, "Object on surface should be supported" |
| |
| not_supported = phys.check_support(np.array([5.0, 5.0]), 0.5, surfaces) |
| assert not not_supported, "Object in air should NOT be supported" |
| print(" PASS Physics Support (support detection)") |
|
|
|
|
| def test_number_subitizing(): |
| """Exact enumeration for 1-4 items.""" |
| num_sys = NumberSystem(weber_fraction=0.15, subitize_limit=4) |
| |
| for n in range(1, 5): |
| result = num_sys.perceive_numerosity(n) |
| assert result['exact'] is True, f"Should subitize {n} items exactly" |
| assert result['estimate'] == n, f"Subitized count wrong for {n}" |
| |
| |
| result = num_sys.perceive_numerosity(20) |
| assert result['exact'] is False, "20 items should not be subitized" |
| print(" PASS Number Subitizing (exact 1-4, approximate >4)") |
|
|
|
|
| def test_number_weber_ratio(): |
| """Discrimination follows Weber's law: ratio matters, not difference.""" |
| num_sys = NumberSystem(weber_fraction=0.15) |
| |
| |
| easy = num_sys.compare(10, 20) |
| assert easy['discriminability'] > 2.0, "1:2 ratio should be easy to discriminate" |
| |
| |
| hard = num_sys.compare(9, 10) |
| assert hard['discriminability'] < easy['discriminability'], \ |
| "9:10 should be harder than 10:20" |
| |
| print(" PASS Number Weber Ratio (ratio-dependent discrimination)") |
|
|
|
|
| def test_geometry_spatial_relations(): |
| """Basic spatial relation computations.""" |
| geo = GeometrySystem() |
| |
| rel = geo.spatial_relation(np.array([0.0, 0.0]), np.array([5.0, -3.0])) |
| assert rel['right_of'] is True |
| assert rel['above'] is True |
| assert rel['distance'] > 0 |
| print(" PASS Geometry Spatial Relations") |
|
|
|
|
| def test_geometry_deformation(): |
| """Smooth deformation fields from Distortable Canvas paper.""" |
| geo = GeometrySystem() |
| |
| |
| image = np.random.rand(28, 28) |
| |
| |
| u, v = geo.create_deformation_field((28, 28), smoothness=3.0) |
| |
| |
| warped = geo.apply_deformation(image, u, v) |
| assert warped.shape == image.shape, "Warped image shape mismatch" |
| |
| |
| dist = geo.canvas_distance(u, v) |
| assert dist > 0, "Canvas distance should be positive for non-zero deformation" |
| |
| |
| dual = geo.dual_distance(image, image, u * 0, v * 0) |
| assert dual == 0.0 or abs(dual) < 1e-10, \ |
| "Zero deformation of image to itself should have near-zero distance" |
| print(" PASS Geometry Deformation (Distortable Canvas)") |
|
|
|
|
| def test_agent_detection(): |
| """Self-propelled entities with direction changes should be classified as agents.""" |
| agent_sys = AgentSystem(self_propulsion_threshold=0.1) |
| |
| |
| positions = [ |
| np.array([0.0, 0.0]), |
| np.array([1.0, 0.0]), |
| np.array([2.0, 0.0]), |
| np.array([2.0, 1.0]), |
| np.array([1.0, 1.0]), |
| np.array([0.0, 2.0]), |
| ] |
| |
| for pos in positions: |
| agent_sys.update_entity(entity_id=0, position=pos, was_contacted=False) |
| |
| score = agent_sys.get_agency_score(0) |
| assert score > 0.3, f"Self-propelled entity with direction changes should have agency score > 0.3, got {score}" |
| print(" PASS Agent Detection (self-propulsion + direction change)") |
|
|
|
|
| def test_social_helper_preference(): |
| """Helpers should be preferred over hinderers.""" |
| soc = SocialSystem() |
| |
| |
| soc.observe_interaction(actor_id=1, target_id=0, outcome='help') |
| soc.observe_interaction(actor_id=1, target_id=0, outcome='help') |
| |
| |
| soc.observe_interaction(actor_id=2, target_id=0, outcome='hinder') |
| soc.observe_interaction(actor_id=2, target_id=0, outcome='hinder') |
| |
| preferred = soc.evaluate_preference(1, 2) |
| assert preferred == 1, "Helper should be preferred over hinderer!" |
| |
| score_helper = soc.get_prosocial_score(1) |
| score_hinderer = soc.get_prosocial_score(2) |
| assert score_helper > score_hinderer, "Helper score should exceed hinderer score" |
| print(" PASS Social Helper Preference (prosocial > antisocial)") |
|
|
|
|
| def run_all_tests(): |
| print("============================================================") |
| print("HippocampAIF Phase 5: Core Knowledge Tests") |
| print("============================================================") |
| |
| test_object_permanence() |
| test_object_continuity_violation() |
| test_physics_gravity() |
| test_physics_bounce() |
| test_physics_support() |
| test_number_subitizing() |
| test_number_weber_ratio() |
| test_geometry_spatial_relations() |
| test_geometry_deformation() |
| test_agent_detection() |
| test_social_helper_preference() |
| |
| print("\n============================================================") |
| print("ALL PHASE 5 TESTS PASSED") |
| print("============================================================") |
|
|
|
|
| if __name__ == "__main__": |
| run_all_tests() |
|
|