diff --git a/universe/components.py b/universe/components.py
index 34cf944..7d9a784 100644
--- a/universe/components.py
+++ b/universe/components.py
@@ -51,6 +51,7 @@ class Component(metaclass=MetaComponent):
 class MetadataComponent(Component):
     _name = 'metadata'
 
+    pk = fields.PrimaryKey()
     type = fields.CharField(required=True)
 
 
diff --git a/universe/engine.py b/universe/engine.py
index 1325317..1e0add8 100644
--- a/universe/engine.py
+++ b/universe/engine.py
@@ -61,6 +61,7 @@ class Entity:
 
 class Manager:
     def __init__(self):
+        self._seq = 0
         self._components = {}
         self._systems = []
         self._updates = {}
@@ -85,14 +86,17 @@ class Manager:
     def get_entity(self, _type, _id):
         return self._components.get(_type, {}).get(_id)
 
-    def set_entity(self, _type, _id, entity):
-        self._components.setdefault(_type, {})[_id] = entity
+    def set_entity(self, _type, entity):
+        self._components.setdefault(_type, {})[entity.pk] = entity
 
-    def register_entity(self, _id, entity):
-        entity_obj = Entity(**entity)
-        entity_obj.pk = _id
-        for component in entity_obj._components:
-            self.set_entity(component, _id, entity_obj)
+    def register_entity(self, entity):
+        if not isinstance(entity, Entity):
+            entity = Entity(**entity)
+        if entity.pk is None:
+            entity.pk = self._seq
+            self._seq += 1
+        for component in entity._components:
+            self.set_entity(component, entity)
 
     def process(self):
         for system_cls in self._systems:
@@ -100,16 +104,19 @@ class Manager:
             system.process(self)
 
     def import_data(self, data, updates):
-        for _id, entity in (data.get('entities') or {}).items():
-            self.register_entity(_id, entity)
+        if 'seq' in data:
+            self._seq = data['seq']
+        for entity in (data.get('entities') or ()):
+            self.register_entity(entity)
         for entity in self.get_entities('metadata').values():
             entity.validate()
         self._updates = updates
 
     def export_data(self):
-        data = {_id: entity for _id, entity in self.get_entities('metadata').items()}
-
-        return {'entities': {_id: entity.serialize() for _id, entity in data.items()}}
+        return {
+            'seq': self._seq,
+            'entities': [entity.serialize() for entity in self.get_entities('metadata').values()]
+        }
 
 
 class GameState:
diff --git a/universe/fields.py b/universe/fields.py
index 176aefd..3271091 100644
--- a/universe/fields.py
+++ b/universe/fields.py
@@ -75,6 +75,17 @@ class CharField(Field):
             raise exceptions.ValidationError(f"{self.data_name!r} must be a string.")
 
 
+class PrimaryKey(Field):
+    def __init__(self):
+        super().__init__()
+
+    def validate(self, data):
+        super().validate(data)
+        value = data[self.name]
+        if not isinstance(value, int):
+            raise exceptions.ValidationError(f"{self.name!r} must be an integer.")
+
+
 class Reference(Field):
     def __init__(self, types=None, **kwargs):
         super().__init__(**kwargs)

Add a way of providing a new unique id for freshly created entities
Currently we have been instantiating entities from serialized data, with ids already in place.  However, we will eventually need to have new objects come into the world (e.g. ship construction completes), so we need to keep track of and provide new unique ids for these new entities as needed.
diff --git a/tests/test_components.py b/tests/test_components.py
index c845fe0..ddb7c94 100644
--- a/tests/test_components.py
+++ b/tests/test_components.py
@@ -50,8 +50,17 @@ class ComponentTestCase(unittest.TestCase):
 
 
 class MetadataComponentTestCase(unittest.TestCase):
+    def setUp(self):
+        self.manager = engine.Manager()
+        self.manager._entity_registry = {
+            'something': {
+                'metadata': components.MetadataComponent(),
+            },
+        }
+        engine.Entity.register_manager(self.manager)
+
     def test_simple(self):
-        data = {'type': 'something'}
+        data = {'type': 'something', 'pk': 0}
         component = components.MetadataComponent()
         self.assertEqual(component.serialize(data), data)
 
@@ -213,24 +222,24 @@ class OwnershipComponentTestCase(unittest.TestCase):
         engine.Entity.register_manager(self.manager)
 
     def test_null_pointer(self):
-        self.manager.register_entity(0, {'type': 'planet'})
+        self.manager.register_entity({'pk': 0, 'type': 'planet'})
 
         planet = self.manager.get_entity('metadata', 0)
         self.assertIsNone(planet.owner)
 
     def test_entity_reference(self):
-        self.manager.register_entity(0, {'type': 'species'})
-        self.manager.register_entity(1, {'type': 'planet', 'owner_id': 0})
+        self.manager.register_entity({'pk': 0, 'type': 'species'})
+        self.manager.register_entity({'pk': 1, 'type': 'planet', 'owner_id': 0})
 
         planet = self.manager.get_entity('metadata', 1)
 
         self.assertIsInstance(planet.owner, engine.Entity)
-        self.assertEqual(planet.owner.serialize(), {'type': 'species'})
+        self.assertEqual(planet.owner.serialize(), {'pk': 0, 'type': 'species'})
 
     def test_set_reference(self):
-        self.manager.register_entity(0, {'type': 'species'})
-        self.manager.register_entity(1, {'type': 'species'})
-        self.manager.register_entity(2, {'type': 'planet'})
+        self.manager.register_entity({'pk': 0, 'type': 'species'})
+        self.manager.register_entity({'pk': 1, 'type': 'species'})
+        self.manager.register_entity({'pk': 2, 'type': 'planet'})
 
         planet = self.manager.get_entity('metadata', 2)
         self.assertIsNone(planet.owner)
@@ -253,8 +262,8 @@ class OwnershipComponentTestCase(unittest.TestCase):
         self.assertIsNone(planet.owner_id)
 
     def test_wrong_owner_type(self):
-        self.manager.register_entity(0, {'type': 'planet'})
-        self.manager.register_entity(1, {'type': 'planet'})
+        self.manager.register_entity({'pk': 0, 'type': 'planet'})
+        self.manager.register_entity({'pk': 1, 'type': 'planet'})
 
         planet0 = self.manager.get_entity('metadata', 0)
         planet1 = self.manager.get_entity('metadata', 1)
diff --git a/tests/test_engine.py b/tests/test_engine.py
index c935bea..869f417 100644
--- a/tests/test_engine.py
+++ b/tests/test_engine.py
@@ -8,8 +8,9 @@ class EntityTestCase(unittest.TestCase):
         state = {
             'turn': 2500,
             'width': 1000,
-            'entities': {
-                1: {
+            'entities': [
+                {
+                    'pk': 1,
                     'type': 'planet',
                     'x': 300,
                     'y': 600,
@@ -26,7 +27,7 @@ class EntityTestCase(unittest.TestCase):
                     'owner_id': 0,  # Not an entity in the pool.
                     'population': 1000,
                 }
-            }
+            ]
         }
 
         S = engine.GameState(state, {})
@@ -41,8 +42,9 @@ class PersistenceTestCase(unittest.TestCase):
         state = {
             'turn': 2500,
             'width': 1000,
-            'entities': {
-                0: {
+            'entities': [
+                {
+                    'pk': 0,
                     'type': 'species',
                     'name': 'Human',
                     'plural_name': 'Humans',
@@ -51,7 +53,8 @@ class PersistenceTestCase(unittest.TestCase):
                     'temperature_immune': True,
                     'radiation_immune': True,
                 },
-                1: {
+                {
+                    'pk': 1,
                     'type': 'planet',
                     'x': 300,
                     'y': 600,
@@ -68,36 +71,38 @@ class PersistenceTestCase(unittest.TestCase):
                     'owner_id': 0,
                     'population': 1000,
                 }
-            }
+            ]
         }
 
         S = engine.GameState(state, {})
         results = S.generate()
 
         self.assertEqual(results['turn'], 2501)
-        self.assertIn(1, results['entities'])
-        self.assertEqual(results['entities'][1]['type'], 'planet')
-        self.assertEqual(results['entities'][1]['x'], 300)
-        self.assertEqual(results['entities'][1]['y'], 600)
-        self.assertEqual(results['entities'][1]['gravity'], 27)
-        self.assertEqual(results['entities'][1]['temperature'], 36)
-        self.assertEqual(results['entities'][1]['radiation'], 45)
-        self.assertEqual(results['entities'][1]['ironium_conc'], 67)
-        self.assertEqual(results['entities'][1]['boranium_conc'], 78)
-        self.assertEqual(results['entities'][1]['germanium_conc'], 82)
-        self.assertEqual(results['entities'][1]['ironium'], 20)
-        self.assertEqual(results['entities'][1]['boranium'], 30)
-        self.assertEqual(results['entities'][1]['germanium'], 40)
-        self.assertEqual(results['entities'][1]['queue'], [])
-        self.assertEqual(results['entities'][1]['owner_id'], 0)
-        self.assertGreater(results['entities'][1]['population'], 0)
+        self.assertEqual(len(results['entities']), 2)
+        planet = next(E for E in results['entities'] if E['pk'] == 1)
+        self.assertEqual(planet['type'], 'planet')
+        self.assertEqual(planet['x'], 300)
+        self.assertEqual(planet['y'], 600)
+        self.assertEqual(planet['gravity'], 27)
+        self.assertEqual(planet['temperature'], 36)
+        self.assertEqual(planet['radiation'], 45)
+        self.assertEqual(planet['ironium_conc'], 67)
+        self.assertEqual(planet['boranium_conc'], 78)
+        self.assertEqual(planet['germanium_conc'], 82)
+        self.assertEqual(planet['ironium'], 20)
+        self.assertEqual(planet['boranium'], 30)
+        self.assertEqual(planet['germanium'], 40)
+        self.assertEqual(planet['queue'], [])
+        self.assertEqual(planet['owner_id'], 0)
+        self.assertGreater(planet['population'], 0)
 
     def test_ship(self):
         state = {
             'turn': 2500,
             'width': 1000,
-            'entities': {
-                0: {
+            'entities': [
+                {
+                    'pk': 0,
                     'type': 'species',
                     'name': 'Human',
                     'plural_name': 'Humans',
@@ -106,7 +111,8 @@ class PersistenceTestCase(unittest.TestCase):
                     'temperature_immune': True,
                     'radiation_immune': True,
                 },
-                1: {
+                {
+                    'pk': 1,
                     'type': 'ship',
                     'x': 300,
                     'y': 600,
@@ -117,30 +123,32 @@ class PersistenceTestCase(unittest.TestCase):
                     'owner_id': 0,
                     'population': 1000,
                 }
-            }
+            ]
         }
 
         S = engine.GameState(state, {})
         results = S.generate()
 
         self.assertEqual(results['turn'], 2501)
-        self.assertIn(1, results['entities'])
-        self.assertEqual(results['entities'][1]['type'], 'ship')
-        self.assertEqual(results['entities'][1]['x'], 300)
-        self.assertEqual(results['entities'][1]['y'], 600)
-        self.assertEqual(results['entities'][1]['ironium'], 20)
-        self.assertEqual(results['entities'][1]['boranium'], 30)
-        self.assertEqual(results['entities'][1]['germanium'], 40)
-        self.assertEqual(results['entities'][1]['queue'], [])
-        self.assertEqual(results['entities'][1]['owner_id'], 0)
-        self.assertGreater(results['entities'][1]['population'], 0)
+        self.assertEqual(len(results['entities']), 2)
+        ship = next(E for E in results['entities'] if E['pk'] == 1)
+        self.assertEqual(ship['type'], 'ship')
+        self.assertEqual(ship['x'], 300)
+        self.assertEqual(ship['y'], 600)
+        self.assertEqual(ship['ironium'], 20)
+        self.assertEqual(ship['boranium'], 30)
+        self.assertEqual(ship['germanium'], 40)
+        self.assertEqual(ship['queue'], [])
+        self.assertEqual(ship['owner_id'], 0)
+        self.assertGreater(ship['population'], 0)
 
     def test_species(self):
         state = {
             'turn': 2500,
             'width': 1000,
-            'entities': {
-                0: {
+            'entities': [
+                {
+                    'pk': 0,
                     'type': 'species',
                     'name': 'Human',
                     'plural_name': 'Humans',
@@ -149,37 +157,38 @@ class PersistenceTestCase(unittest.TestCase):
                     'temperature_immune': True,
                     'radiation_immune': True,
                 },
-            }
+            ]
         }
 
         S = engine.GameState(state, {})
         results = S.generate()
 
         self.assertEqual(results['turn'], 2501)
-        self.assertIn(0, results['entities'])
-        self.assertEqual(results['entities'][0]['type'], 'species')
-        self.assertEqual(results['entities'][0]['name'], 'Human')
-        self.assertEqual(results['entities'][0]['plural_name'], 'Humans')
-        self.assertEqual(results['entities'][0]['growth_rate'], 15)
-        self.assertEqual(results['entities'][0]['gravity_immune'], True)
-        self.assertEqual(results['entities'][0]['temperature_immune'], True)
-        self.assertEqual(results['entities'][0]['radiation_immune'], True)
+        self.assertEqual(len(results['entities']), 1)
+        species = next(E for E in results['entities'] if E['pk'] == 0)
+        self.assertEqual(species['type'], 'species')
+        self.assertEqual(species['name'], 'Human')
+        self.assertEqual(species['plural_name'], 'Humans')
+        self.assertEqual(species['growth_rate'], 15)
+        self.assertEqual(species['gravity_immune'], True)
+        self.assertEqual(species['temperature_immune'], True)
+        self.assertEqual(species['radiation_immune'], True)
 
 
 class MovementTestCase(unittest.TestCase):
     def test_empty_universe(self):
-        state = {'turn': 2500, 'width': 1000, 'entities': {}}
+        state = {'turn': 2500, 'width': 1000, 'entities': []}
         updates = {}
 
         S = engine.GameState(state, updates)
         results = S.generate()
 
         self.assertEqual(results,
-                         {'turn': 2501, 'width': 1000, 'entities': {}})
+                         {'turn': 2501, 'width': 1000, 'seq': 0, 'entities': []})
 
     def test_one_stationary_object(self):
         state = {'turn': 2500, 'width': 1000,
-                 'entities': {0: {'type': 'ship', 'x': 456, 'y': 337, 'queue': []}}}
+                 'entities': [{'pk': 0, 'type': 'ship', 'x': 456, 'y': 337, 'queue': []}]}
         updates = {}
 
         S = engine.GameState(state, updates)
@@ -189,12 +198,12 @@ class MovementTestCase(unittest.TestCase):
         self.assertEqual(results['width'], 1000)
         self.assertEqual(
             results['entities'],
-            {0: {'type': 'ship', 'x': 456, 'x_prev': 456, 'y': 337, 'y_prev': 337, 'queue': []}}
+            [{'pk': 0, 'type': 'ship', 'x': 456, 'x_prev': 456, 'y': 337, 'y_prev': 337, 'queue': []}]
         )
 
     def test_one_object_single_turn_move(self):
         state = {'turn': 2500, 'width': 1000,
-                 'entities': {0: {'type': 'ship', 'x': 480, 'y': 235, 'queue': []}}}
+                 'entities': [{'pk': 0, 'type': 'ship', 'x': 480, 'y': 235, 'queue': []}]}
         # actual speed is warp**2
         updates = {0: [{'seq': 0, 'x_t': 422, 'y_t': 210, 'warp': 10}]}
 
@@ -205,12 +214,12 @@ class MovementTestCase(unittest.TestCase):
         self.assertEqual(results['width'], 1000)
         self.assertEqual(
             results['entities'],
-            {0: {'type': 'ship', 'x': 422, 'x_prev': 480, 'y': 210, 'y_prev': 235, 'queue': []}}
+            [{'pk': 0, 'type': 'ship', 'x': 422, 'x_prev': 480, 'y': 210, 'y_prev': 235, 'queue': []}]
         )
 
     def test_multi_turn_single_move(self):
         state = {'turn': 2500, 'width': 1000,
-                 'entities': {0: {'type': 'ship', 'x': 480, 'y': 235, 'queue': []}}}
+                 'entities': [{'pk': 0, 'type': 'ship', 'x': 480, 'y': 235, 'queue': []}]}
         # actual speed is warp**2
         updates = {0: [{'seq': 0, 'x_t': 168, 'y_t': 870, 'warp': 10}]}
 
@@ -221,14 +230,17 @@ class MovementTestCase(unittest.TestCase):
         self.assertEqual(results['width'], 1000)
         self.assertEqual(
             results['entities'],
-            {0: {'type': 'ship', 'x': 436, 'x_prev': 480, 'y': 325, 'y_prev': 235,
-                 'queue': [{'x_t': 168, 'y_t': 870, 'warp': 10}]}}
+            [{'pk': 0, 'type': 'ship', 'x': 436, 'x_prev': 480, 'y': 325, 'y_prev': 235,
+              'queue': [{'x_t': 168, 'y_t': 870, 'warp': 10}]}]
         )
 
     def test_previously_queued_move(self):
-        state = {'turn': 2500, 'width': 1000,
-                 'entities': {
-                     0: {'type': 'ship', 'x': 480, 'y': 235, 'queue': [{'x_t': 422, 'y_t': 210, 'warp': 10}]}}}
+        state = {
+            'turn': 2500, 'width': 1000,
+            'entities': [
+                {'pk': 0, 'type': 'ship', 'x': 480, 'y': 235, 'queue': [{'x_t': 422, 'y_t': 210, 'warp': 10}]}
+            ]
+        }
         updates = {}
 
         S = engine.GameState(state, updates)
@@ -238,13 +250,16 @@ class MovementTestCase(unittest.TestCase):
         self.assertEqual(results['width'], 1000)
         self.assertEqual(
             results['entities'],
-            {0: {'type': 'ship', 'x': 422, 'x_prev': 480, 'y': 210, 'y_prev': 235, 'queue': []}}
+            [{'pk': 0, 'type': 'ship', 'x': 422, 'x_prev': 480, 'y': 210, 'y_prev': 235, 'queue': []}]
         )
 
     def test_replace_queued_move(self):
-        state = {'turn': 2500, 'width': 1000,
-                 'entities': {
-                     0: {'type': 'ship', 'x': 480, 'y': 235, 'queue': [{'x_t': 637, 'y_t': 786, 'warp': 8}]}}}
+        state = {
+            'turn': 2500, 'width': 1000,
+            'entities': [
+                {'pk': 0, 'type': 'ship', 'x': 480, 'y': 235, 'queue': [{'x_t': 637, 'y_t': 786, 'warp': 8}]}
+            ]
+        }
         updates = {0: [{'seq': 0, 'x_t': 422, 'y_t': 210, 'warp': 10}]}
 
         S = engine.GameState(state, updates)
@@ -254,13 +269,16 @@ class MovementTestCase(unittest.TestCase):
         self.assertEqual(results['width'], 1000)
         self.assertEqual(
             results['entities'],
-            {0: {'type': 'ship', 'x': 422, 'x_prev': 480, 'y': 210, 'y_prev': 235, 'queue': []}}
+            [{'pk': 0, 'type': 'ship', 'x': 422, 'x_prev': 480, 'y': 210, 'y_prev': 235, 'queue': []}]
         )
 
     def test_add_to_queued_moves(self):
-        state = {'turn': 2500, 'width': 1000,
-                 'entities': {
-                     0: {'type': 'ship', 'x': 480, 'y': 235, 'queue': [{'x_t': 422, 'y_t': 210, 'warp': 10}]}}}
+        state = {
+            'turn': 2500, 'width': 1000,
+            'entities': [
+                {'pk': 0, 'type': 'ship', 'x': 480, 'y': 235, 'queue': [{'x_t': 422, 'y_t': 210, 'warp': 10}]}
+            ]
+        }
         updates = {0: [{'seq': 1, 'x_t': 637, 'y_t': 786, 'warp': 8}]}
 
         S = engine.GameState(state, updates)
@@ -270,14 +288,16 @@ class MovementTestCase(unittest.TestCase):
         self.assertEqual(results['width'], 1000)
         self.assertEqual(
             results['entities'],
-            {0: {'type': 'ship', 'x': 422, 'x_prev': 480, 'y': 210, 'y_prev': 235,
-                 'queue': [{'x_t': 637, 'y_t': 786, 'warp': 8}]}}
+            [{'pk': 0, 'type': 'ship', 'x': 422, 'x_prev': 480, 'y': 210, 'y_prev': 235,
+              'queue': [{'x_t': 637, 'y_t': 786, 'warp': 8}]}]
         )
 
     def test_slow_motion(self):
         state = {
             'turn': 2500, 'width': 1000,
-            'entities': {0: {'type': 'ship', 'x': 500, 'y': 500, 'queue': [{'x_t': 501, 'y_t': 500, 'warp': 1}]}}
+            'entities': [
+                {'pk': 0, 'type': 'ship', 'x': 500, 'y': 500, 'queue': [{'x_t': 501, 'y_t': 500, 'warp': 1}]}
+            ]
         }
 
         S = engine.GameState(state, {})
@@ -287,13 +307,17 @@ class MovementTestCase(unittest.TestCase):
         self.assertEqual(results['width'], 1000)
         self.assertEqual(
             results['entities'],
-            {0: {'type': 'ship', 'x': 501, 'x_prev': 500, 'y': 500, 'y_prev': 500, 'queue': []}}
+            [{'pk': 0, 'type': 'ship', 'x': 501, 'x_prev': 500, 'y': 500, 'y_prev': 500, 'queue': []}]
         )
 
     def test_target_stationary_object(self):
-        state = {'turn': 2500, 'width': 1000,
-                 'entities': {0: {'type': 'ship', 'x': 480, 'y': 235, 'queue': [{'target_id': 1, 'warp': 10}]},
-                              1: {'type': 'ship', 'x': 460, 'y': 215, 'queue': []}}}
+        state = {
+            'turn': 2500, 'width': 1000,
+            'entities': [
+                {'pk': 0, 'type': 'ship', 'x': 480, 'y': 235, 'queue': [{'target_id': 1, 'warp': 10}]},
+                {'pk': 1, 'type': 'ship', 'x': 460, 'y': 215, 'queue': []}
+            ]
+        }
         updates = {}
 
         S = engine.GameState(state, updates)
@@ -303,15 +327,17 @@ class MovementTestCase(unittest.TestCase):
         self.assertEqual(results['width'], 1000)
         self.assertEqual(
             results['entities'],
-            {0: {'type': 'ship', 'x': 460, 'x_prev': 480, 'y': 215, 'y_prev': 235, 'queue': []},
-             1: {'type': 'ship', 'x': 460, 'x_prev': 460, 'y': 215, 'y_prev': 215, 'queue': []}}
+            [{'pk': 0, 'type': 'ship', 'x': 460, 'x_prev': 480, 'y': 215, 'y_prev': 235, 'queue': []},
+             {'pk': 1, 'type': 'ship', 'x': 460, 'x_prev': 460, 'y': 215, 'y_prev': 215, 'queue': []}]
         )
 
     def test_target_moving_object(self):
         state = {
             'turn': 2500, 'width': 1000,
-            'entities': {0: {'type': 'ship', 'x': 480, 'y': 235, 'queue': [{'target_id': 1, 'warp': 10}]},
-                         1: {'type': 'ship', 'x': 460, 'y': 215, 'queue': [{'x_t': 465, 'y_t': 220, 'warp': 10}]}}
+            'entities': [
+                {'pk': 0, 'type': 'ship', 'x': 480, 'y': 235, 'queue': [{'target_id': 1, 'warp': 10}]},
+                {'pk': 1, 'type': 'ship', 'x': 460, 'y': 215, 'queue': [{'x_t': 465, 'y_t': 220, 'warp': 10}]}
+            ]
         }
         updates = {}
 
@@ -322,15 +348,17 @@ class MovementTestCase(unittest.TestCase):
         self.assertEqual(results['width'], 1000)
         self.assertEqual(
             results['entities'],
-            {0: {'type': 'ship', 'x': 465, 'x_prev': 480, 'y': 220, 'y_prev': 235, 'queue': []},
-             1: {'type': 'ship', 'x': 465, 'x_prev': 460, 'y': 220, 'y_prev': 215, 'queue': []}}
+            [{'pk': 0, 'type': 'ship', 'x': 465, 'x_prev': 480, 'y': 220, 'y_prev': 235, 'queue': []},
+             {'pk': 1, 'type': 'ship', 'x': 465, 'x_prev': 460, 'y': 220, 'y_prev': 215, 'queue': []}]
         )
 
     def test_target_can_barely_reach(self):
         state = {
             'turn': 2500, 'width': 1000,
-            'entities': {0: {'type': 'ship', 'x': 560, 'y': 315, 'queue': [{'target_id': 1, 'warp': 10}]},
-                         1: {'type': 'ship', 'x': 460, 'y': 215, 'queue': [{'x_t': 660, 'y_t': 215, 'warp': 10}]}}
+            'entities': [
+                {'pk': 0, 'type': 'ship', 'x': 560, 'y': 315, 'queue': [{'target_id': 1, 'warp': 10}]},
+                {'pk': 1, 'type': 'ship', 'x': 460, 'y': 215, 'queue': [{'x_t': 660, 'y_t': 215, 'warp': 10}]}
+            ]
         }
         S = engine.GameState(state, {})
         results = S.generate()
@@ -339,16 +367,18 @@ class MovementTestCase(unittest.TestCase):
         self.assertEqual(results['width'], 1000)
         self.assertEqual(
             results['entities'],
-            {0: {'type': 'ship', 'x': 560, 'x_prev': 560, 'y': 215, 'y_prev': 315, 'queue': []},
-             1: {'type': 'ship', 'x': 560, 'x_prev': 460, 'y': 215, 'y_prev': 215,
-                 'queue': [{'x_t': 660, 'y_t': 215, 'warp': 10}]}}
+            [{'pk': 0, 'type': 'ship', 'x': 560, 'x_prev': 560, 'y': 215, 'y_prev': 315, 'queue': []},
+             {'pk': 1, 'type': 'ship', 'x': 560, 'x_prev': 460, 'y': 215, 'y_prev': 215,
+              'queue': [{'x_t': 660, 'y_t': 215, 'warp': 10}]}]
         )
 
     def test_target_stops_short(self):
         state = {
             'turn': 2500, 'width': 1000,
-            'entities': {0: {'type': 'ship', 'x': 560, 'y': 315, 'queue': [{'target_id': 1, 'warp': 10}]},
-                         1: {'type': 'ship', 'x': 460, 'y': 215, 'queue': [{'x_t': 510, 'y_t': 215, 'warp': 10}]}}
+            'entities': [
+                {'pk': 0, 'type': 'ship', 'x': 560, 'y': 315, 'queue': [{'target_id': 1, 'warp': 10}]},
+                {'pk': 1, 'type': 'ship', 'x': 460, 'y': 215, 'queue': [{'x_t': 510, 'y_t': 215, 'warp': 10}]}
+            ]
         }
         S = engine.GameState(state, {})
         results = S.generate()
@@ -359,15 +389,19 @@ class MovementTestCase(unittest.TestCase):
         # i.e. roughly 35 lightyears in each direction
         self.assertEqual(
             results['entities'],
-            {0: {'type': 'ship', 'x': 524, 'x_prev': 560, 'y': 230, 'y_prev': 315,
-                 'queue': [{'target_id': 1, 'warp': 10}]},
-             1: {'type': 'ship', 'x': 510, 'x_prev': 460, 'y': 215, 'y_prev': 215, 'queue': []}}
+            [{'pk': 0, 'type': 'ship', 'x': 524, 'x_prev': 560, 'y': 230, 'y_prev': 315,
+              'queue': [{'target_id': 1, 'warp': 10}]},
+             {'pk': 1, 'type': 'ship', 'x': 510, 'x_prev': 460, 'y': 215, 'y_prev': 215, 'queue': []}]
         )
 
     def test_mutual_intercept(self):
-        state = {'turn': 2500, 'width': 1000,
-                 'entities': {0: {'type': 'ship', 'x': 480, 'y': 235, 'queue': [{'target_id': 1, 'warp': 10}]},
-                              1: {'type': 'ship', 'x': 460, 'y': 215, 'queue': [{'target_id': 0, 'warp': 10}]}}}
+        state = {
+            'turn': 2500, 'width': 1000,
+            'entities': [
+                {'type': 'ship', 'x': 480, 'y': 235, 'queue': [{'target_id': 1, 'warp': 10}]},
+                {'type': 'ship', 'x': 460, 'y': 215, 'queue': [{'target_id': 0, 'warp': 10}]}
+            ]
+        }
         updates = {}
 
         S = engine.GameState(state, updates)
@@ -376,16 +410,20 @@ class MovementTestCase(unittest.TestCase):
         self.assertEqual(results['turn'], 2501)
         self.assertEqual(results['width'], 1000)
         self.assertEqual(len(results['entities']), 2)
-        coordinates = {(entity['x'], entity['y']) for entity in results['entities'].values()}
+        coordinates = {(entity['x'], entity['y']) for entity in results['entities']}
         self.assertEqual(coordinates, {(470, 225)})
         self.assertEqual(results['entities'][0]['queue'], [])
         self.assertEqual(results['entities'][1]['queue'], [])
 
     def test_three_way_cycle_intercept(self):
-        state = {'turn': 2500, 'width': 1000,
-                 'entities': {0: {'type': 'ship', 'x': 480, 'y': 235, 'queue': [{'target_id': 1, 'warp': 10}]},
-                              1: {'type': 'ship', 'x': 460, 'y': 215, 'queue': [{'target_id': 2, 'warp': 10}]},
-                              2: {'type': 'ship', 'x': 500, 'y': 205, 'queue': [{'target_id': 0, 'warp': 10}]}}}
+        state = {
+            'turn': 2500, 'width': 1000,
+            'entities': [
+                {'type': 'ship', 'x': 480, 'y': 235, 'queue': [{'target_id': 1, 'warp': 10}]},
+                {'type': 'ship', 'x': 460, 'y': 215, 'queue': [{'target_id': 2, 'warp': 10}]},
+                {'type': 'ship', 'x': 500, 'y': 205, 'queue': [{'target_id': 0, 'warp': 10}]}
+            ]
+        }
         updates = {}
 
         S = engine.GameState(state, updates)
@@ -394,16 +432,20 @@ class MovementTestCase(unittest.TestCase):
         self.assertEqual(results['turn'], 2501)
         self.assertEqual(results['width'], 1000)
         self.assertEqual(len(results['entities']), 3)
-        coordinates = {(entity['x'], entity['y']) for entity in results['entities'].values()}
+        coordinates = {(entity['x'], entity['y']) for entity in results['entities']}
         self.assertEqual(coordinates, {(480, 217)})
-        self.assertTrue(all(not entity['queue'] for entity in results['entities'].values()))
+        self.assertTrue(all(not entity['queue'] for entity in results['entities']))
 
     def test_four_way_cycle_intercept(self):
-        state = {'turn': 2500, 'width': 1000,
-                 'entities': {0: {'type': 'ship', 'x': 500, 'y': 500, 'queue': [{'target_id': 1, 'warp': 10}]},
-                              1: {'type': 'ship', 'x': 600, 'y': 500, 'queue': [{'target_id': 2, 'warp': 10}]},
-                              2: {'type': 'ship', 'x': 600, 'y': 600, 'queue': [{'target_id': 3, 'warp': 10}]},
-                              3: {'type': 'ship', 'x': 500, 'y': 600, 'queue': [{'target_id': 0, 'warp': 10}]}}}
+        state = {
+            'turn': 2500, 'width': 1000,
+            'entities': [
+                {'pk': 0, 'type': 'ship', 'x': 500, 'y': 500, 'queue': [{'target_id': 1, 'warp': 10}]},
+                {'pk': 1, 'type': 'ship', 'x': 600, 'y': 500, 'queue': [{'target_id': 2, 'warp': 10}]},
+                {'pk': 2, 'type': 'ship', 'x': 600, 'y': 600, 'queue': [{'target_id': 3, 'warp': 10}]},
+                {'pk': 3, 'type': 'ship', 'x': 500, 'y': 600, 'queue': [{'target_id': 0, 'warp': 10}]}
+            ]
+        }
         updates = {}
 
         S = engine.GameState(state, updates)
@@ -412,6 +454,6 @@ class MovementTestCase(unittest.TestCase):
         self.assertEqual(results['turn'], 2501)
         self.assertEqual(results['width'], 1000)
         self.assertEqual(len(results['entities']), 4)
-        coordinates = {(entity['x'], entity['y']) for entity in results['entities'].values()}
+        coordinates = {(entity['x'], entity['y']) for entity in results['entities']}
         self.assertEqual(coordinates, {(550, 550)})
-        self.assertTrue(all(not entity['queue'] for entity in results['entities'].values()))
+        self.assertTrue(all(not entity['queue'] for entity in results['entities']))
diff --git a/tests/test_systems.py b/tests/test_systems.py
index 22a774a..c5df266 100644
--- a/tests/test_systems.py
+++ b/tests/test_systems.py
@@ -9,8 +9,9 @@ class PopulationTestCase(unittest.TestCase):
         state = {
             'turn': 2500,
             'width': 1000,
-            'entities': {
-                0: {
+            'entities': [
+                {
+                    'pk': 0,
                     'type': 'species',
                     'name': 'Human',
                     'plural_name': 'Humans',
@@ -19,7 +20,8 @@ class PopulationTestCase(unittest.TestCase):
                     'temperature_immune': True,
                     'radiation_immune': True,
                 },
-                1: {
+                {
+                    'pk': 1,
                     'type': 'planet',
                     'population': 1000,
                     'x': 480,
@@ -33,7 +35,7 @@ class PopulationTestCase(unittest.TestCase):
                     'owner_id': 0,
                     'queue': [],
                 },
-            }
+            ]
         }
 
         S = engine.GameState(state, {})
@@ -47,8 +49,9 @@ class PopulationTestCase(unittest.TestCase):
         state = {
             'turn': 2500,
             'width': 1000,
-            'entities': {
-                0: {
+            'entities': [
+                {
+                    'pk': 0,
                     'type': 'species',
                     'name': 'Human',
                     'plural_name': 'Humans',
@@ -57,7 +60,8 @@ class PopulationTestCase(unittest.TestCase):
                     'temperature_immune': True,
                     'radiation_immune': True,
                 },
-                1: {
+                {
+                    'pk': 1,
                     'type': 'planet',
                     'population': 500_000,
                     'x': 480,
@@ -71,7 +75,7 @@ class PopulationTestCase(unittest.TestCase):
                     'owner_id': 0,
                     'queue': [],
                 },
-            }
+            ]
         }
 
         S = engine.GameState(state, {})
@@ -88,8 +92,9 @@ class PopulationTestCase(unittest.TestCase):
         state = {
             'turn': 2500,
             'width': 1000,
-            'entities': {
-                0: {
+            'entities': [
+                {
+                    'pk': 0,
                     'type': 'species',
                     'name': 'Human',
                     'plural_name': 'Humans',
@@ -104,7 +109,8 @@ class PopulationTestCase(unittest.TestCase):
                     'radiation_min': 38,
                     'radiation_max': 90,
                 },
-                1: {
+                {
+                    'pk': 1,
                     'type': 'planet',
                     'population': 1_000_000,
                     'x': 480,
@@ -118,7 +124,7 @@ class PopulationTestCase(unittest.TestCase):
                     'owner_id': 0,
                     'queue': [],
                 },
-            }
+            ]
         }
 
         S = engine.GameState(state, {})
@@ -132,8 +138,9 @@ class PopulationTestCase(unittest.TestCase):
         state = {
             'turn': 2500,
             'width': 1000,
-            'entities': {
-                0: {
+            'entities': [
+                {
+                    'pk': 0,
                     'type': 'species',
                     'name': 'Human',
                     'plural_name': 'Humans',
@@ -148,7 +155,8 @@ class PopulationTestCase(unittest.TestCase):
                     'radiation_min': 38,
                     'radiation_max': 90,
                 },
-                1: {
+                {
+                    'pk': 1,
                     'type': 'planet',
                     'population': 2_000_000,
                     'x': 480,
@@ -162,7 +170,7 @@ class PopulationTestCase(unittest.TestCase):
                     'owner_id': 0,
                     'queue': [],
                 },
-            }
+            ]
         }
 
         S = engine.GameState(state, {})
@@ -178,8 +186,9 @@ class PopulationTestCase(unittest.TestCase):
         state = {
             'turn': 2500,
             'width': 1000,
-            'entities': {
-                0: {
+            'entities': [
+                {
+                    'pk': 0,
                     'type': 'species',
                     'name': 'Human',
                     'plural_name': 'Humans',
@@ -194,7 +203,8 @@ class PopulationTestCase(unittest.TestCase):
                     'radiation_min': 38,
                     'radiation_max': 90,
                 },
-                1: {
+                {
+                    'pk': 1,
                     'type': 'planet',
                     'population': 100_000,
                     'x': 480,
@@ -208,7 +218,7 @@ class PopulationTestCase(unittest.TestCase):
                     'owner_id': 0,
                     'queue': [],
                 },
-            }
+            ]
         }
 
         S = engine.GameState(state, {})
@@ -224,8 +234,9 @@ class PopulationTestCase(unittest.TestCase):
         state = {
             'turn': 2500,
             'width': 1000,
-            'entities': {
-                0: {
+            'entities': [
+                {
+                    'pk': 0,
                     'type': 'species',
                     'name': 'Human',
                     'plural_name': 'Humans',
@@ -234,7 +245,8 @@ class PopulationTestCase(unittest.TestCase):
                     'temperature_immune': True,
                     'radiation_immune': True,
                 },
-                1: {
+                {
+                    'pk': 1,
                     'type': 'planet',
                     'population': 0,
                     'x': 480,
@@ -248,7 +260,7 @@ class PopulationTestCase(unittest.TestCase):
                     'owner_id': 0,
                     'queue': [],
                 },
-            }
+            ]
         }
 
         S = engine.GameState(state, {})

pip install -e .
parse_log_pytest
pytest
tox pytest
3.8
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
cachetools==5.5.2
chardet==5.2.0
colorama==0.4.6
distlib==0.3.9
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
filelock==3.16.1
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
packaging==24.2
platformdirs==4.3.6
pluggy==1.5.0
pyproject-api==1.8.0
pytest @ file:///croot/pytest_1717793244625/work
tomli==2.2.1
tox==4.25.0
typing_extensions==4.13.0
-e git+https://github.com/jbradberry/universe.git@2d66ec17708be23ed04f2fe4afe58ed1ba89b1f9#egg=universe
virtualenv==20.29.3

name: universe
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py38h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=24.2=py38h06a4308_0
  - pytest=7.4.4=py38h06a4308_0
  - python=3.8.20=he870216_0
  - readline=8.2=h5eee18b_0
  - setuptools=75.1.0=py38h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.44.0=py38h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - cachetools==5.5.2
      - chardet==5.2.0
      - colorama==0.4.6
      - distlib==0.3.9
      - filelock==3.16.1
      - packaging==24.2
      - platformdirs==4.3.6
      - pluggy==1.5.0
      - pyproject-api==1.8.0
      - tomli==2.2.1
      - tox==4.25.0
      - typing-extensions==4.13.0
      - virtualenv==20.29.3
prefix: /opt/conda/envs/universe

tests/test_components.py::MetadataComponentTestCase::test_simple tests/test_components.py::OwnershipComponentTestCase::test_entity_reference tests/test_components.py::OwnershipComponentTestCase::test_null_pointer tests/test_components.py::OwnershipComponentTestCase::test_set_reference tests/test_components.py::OwnershipComponentTestCase::test_wrong_owner_type tests/test_engine.py::EntityTestCase::test_invalid_ownership tests/test_engine.py::PersistenceTestCase::test_planet tests/test_engine.py::PersistenceTestCase::test_ship tests/test_engine.py::PersistenceTestCase::test_species tests/test_engine.py::MovementTestCase::test_add_to_queued_moves tests/test_engine.py::MovementTestCase::test_empty_universe tests/test_engine.py::MovementTestCase::test_four_way_cycle_intercept tests/test_engine.py::MovementTestCase::test_multi_turn_single_move tests/test_engine.py::MovementTestCase::test_mutual_intercept tests/test_engine.py::MovementTestCase::test_one_object_single_turn_move tests/test_engine.py::MovementTestCase::test_one_stationary_object tests/test_engine.py::MovementTestCase::test_previously_queued_move tests/test_engine.py::MovementTestCase::test_replace_queued_move tests/test_engine.py::MovementTestCase::test_slow_motion tests/test_engine.py::MovementTestCase::test_target_can_barely_reach tests/test_engine.py::MovementTestCase::test_target_moving_object tests/test_engine.py::MovementTestCase::test_target_stationary_object tests/test_engine.py::MovementTestCase::test_target_stops_short tests/test_engine.py::MovementTestCase::test_three_way_cycle_intercept tests/test_systems.py::PopulationTestCase::test_capacity_growth tests/test_systems.py::PopulationTestCase::test_crowding_growth tests/test_systems.py::PopulationTestCase::test_habitability_growth tests/test_systems.py::PopulationTestCase::test_lose_ownership tests/test_systems.py::PopulationTestCase::test_overcrowding_growth tests/test_systems.py::PopulationTestCase::test_uninhabitable_growth
tests/test_components.py::ComponentTestCase::test_custom_component_validation tests/test_components.py::ComponentTestCase::test_custom_field_validation tests/test_components.py::ComponentTestCase::test_invalid_type_bar tests/test_components.py::ComponentTestCase::test_invalid_type_foo tests/test_components.py::ComponentTestCase::test_valid_bar tests/test_components.py::ComponentTestCase::test_valid_foo tests/test_components.py::MetadataComponentTestCase::test_missing_required_field tests/test_components.py::EnvironmentComponentTestCase::test_component_display tests/test_components.py::EnvironmentComponentTestCase::test_display_fields tests/test_components.py::EnvironmentComponentTestCase::test_random tests/test_components.py::MineralConcentrationComponentTestCase::test_random tests/test_components.py::SpeciesComponentTestCase::test_immune tests/test_components.py::SpeciesComponentTestCase::test_immune_with_value tests/test_components.py::SpeciesComponentTestCase::test_not_immune tests/test_components.py::SpeciesComponentTestCase::test_not_immune_without_value
universe/fields.py universe/components.py universe/engine.py
universe/fields.py universe/components.py universe/engine.py