diff --git a/src/Rev2/Archetype.cs b/src/Rev2/Archetype.cs
index c0442f0..31ae816 100644
--- a/src/Rev2/Archetype.cs
+++ b/src/Rev2/Archetype.cs
@@ -7,11 +7,11 @@ internal class Archetype
 	public World World;
 	public ArchetypeSignature Signature;
 	public List<Column> ComponentColumns = new List<Column>();
-	public List<EntityId> RowToEntity = new List<EntityId>();
+	public List<Id> RowToEntity = new List<Id>();
 
-	public Dictionary<ComponentId, int> ComponentToColumnIndex =
-		new Dictionary<ComponentId, int>();
-	public SortedDictionary<ComponentId, ArchetypeEdge> Edges = new SortedDictionary<ComponentId, ArchetypeEdge>();
+	public Dictionary<Id, int> ComponentToColumnIndex =
+		new Dictionary<Id, int>();
+	public SortedDictionary<Id, ArchetypeEdge> Edges = new SortedDictionary<Id, ArchetypeEdge>();
 
 	public int Count => RowToEntity.Count;
 
diff --git a/src/Rev2/ArchetypeSignature.cs b/src/Rev2/ArchetypeSignature.cs
index 728d5e2..e0cf620 100644
--- a/src/Rev2/ArchetypeSignature.cs
+++ b/src/Rev2/ArchetypeSignature.cs
@@ -7,36 +7,36 @@ namespace MoonTools.ECS.Rev2
 	{
 		public static ArchetypeSignature Empty = new ArchetypeSignature(0);
 
-		List<int> Ids;
+		List<ulong> Ids;
 
 		public int Count => Ids.Count;
 
-		public ComponentId this[int i] => new ComponentId(Ids[i]);
+		public Id this[int i] => new Id(Ids[i]);
 
 		public ArchetypeSignature()
 		{
-			Ids = new List<int>();
+			Ids = new List<ulong>();
 		}
 
 		public ArchetypeSignature(int capacity)
 		{
-			Ids = new List<int>(capacity);
+			Ids = new List<ulong>(capacity);
 		}
 
 		// Maintains sorted order
-		public void Insert(ComponentId componentId)
+		public void Insert(Id componentId)
 		{
-			var index = Ids.BinarySearch(componentId.Id);
+			var index = Ids.BinarySearch(componentId.Value);
 
 			if (index < 0)
 			{
-				Ids.Insert(~index, componentId.Id);
+				Ids.Insert(~index, componentId.Value);
 			}
 		}
 
-		public void Remove(ComponentId componentId)
+		public void Remove(Id componentId)
 		{
-			var index = Ids.BinarySearch(componentId.Id);
+			var index = Ids.BinarySearch(componentId.Value);
 
 			if (index >= 0)
 			{
diff --git a/src/Rev2/ComponentId.cs b/src/Rev2/ComponentId.cs
deleted file mode 100644
index 8d59b3e..0000000
--- a/src/Rev2/ComponentId.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-
-namespace MoonTools.ECS.Rev2
-{
-	internal readonly record struct ComponentId(int Id) : IHasId, IComparable<ComponentId>
-	{
-		public int CompareTo(ComponentId other)
-		{
-			return Id.CompareTo(other.Id);
-		}
-	}
-}
diff --git a/src/Rev2/Entity.cs b/src/Rev2/Entity.cs
new file mode 100644
index 0000000..2a9b435
--- /dev/null
+++ b/src/Rev2/Entity.cs
@@ -0,0 +1,3 @@
+namespace MoonTools.ECS.Rev2;
+
+public readonly record struct Entity(uint Id);
diff --git a/src/Rev2/EntityId.cs b/src/Rev2/EntityId.cs
deleted file mode 100644
index ee0c5d7..0000000
--- a/src/Rev2/EntityId.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace MoonTools.ECS.Rev2
-{
-	public readonly record struct EntityId(int Id) : IHasId;
-}
diff --git a/src/Rev2/Filter.cs b/src/Rev2/Filter.cs
index aea4010..62683c2 100644
--- a/src/Rev2/Filter.cs
+++ b/src/Rev2/Filter.cs
@@ -7,8 +7,8 @@ namespace MoonTools.ECS.Rev2
 	public class Filter
 	{
 		private Archetype EmptyArchetype;
-		private HashSet<ComponentId> Included;
-		private HashSet<ComponentId> Excluded;
+		private HashSet<Id> Included;
+		private HashSet<Id> Excluded;
 
 		public EntityEnumerator Entities => new EntityEnumerator(this);
 		internal ArchetypeEnumerator Archetypes => new ArchetypeEnumerator(this);
@@ -47,7 +47,7 @@ namespace MoonTools.ECS.Rev2
 			}
 		}
 
-		public EntityId RandomEntity
+		public Id RandomEntity
 		{
 			get
 			{
@@ -57,7 +57,7 @@ namespace MoonTools.ECS.Rev2
 		}
 
 		// WARNING: this WILL crash if the index is out of range!
-		public EntityId NthEntity(int index)
+		public Id NthEntity(int index)
 		{
 			var count = 0;
 
@@ -93,7 +93,7 @@ namespace MoonTools.ECS.Rev2
 			}
 		}
 
-		internal Filter(Archetype emptyArchetype, HashSet<ComponentId> included, HashSet<ComponentId> excluded)
+		internal Filter(Archetype emptyArchetype, HashSet<Id> included, HashSet<Id> excluded)
 		{
 			EmptyArchetype = emptyArchetype;
 			Included = included;
@@ -165,12 +165,12 @@ namespace MoonTools.ECS.Rev2
 
 		public ref struct EntityEnumerator
 		{
-			private EntityId CurrentEntity;
+			private Id CurrentEntity;
 
 			public EntityEnumerator GetEnumerator() => this;
 
 			// TODO: pool this
-			Queue<EntityId> EntityQueue = new Queue<EntityId>();
+			Queue<Id> EntityQueue = new Queue<Id>();
 
 			internal EntityEnumerator(Filter filter)
 			{
@@ -190,7 +190,7 @@ namespace MoonTools.ECS.Rev2
 				return EntityQueue.TryDequeue(out CurrentEntity);
 			}
 
-			public EntityId Current => CurrentEntity;
+			public Id Current => CurrentEntity;
 		}
 
 		public ref struct RandomEntityEnumerator
@@ -208,7 +208,7 @@ namespace MoonTools.ECS.Rev2
 			}
 
 			public bool MoveNext() => LinearCongruentialEnumerator.MoveNext();
-			public EntityId Current => Filter.NthEntity(LinearCongruentialEnumerator.Current);
+			public Id Current => Filter.NthEntity(LinearCongruentialEnumerator.Current);
 		}
 	}
 }
diff --git a/src/Rev2/FilterBuilder.cs b/src/Rev2/FilterBuilder.cs
index 5edc8b8..b4be164 100644
--- a/src/Rev2/FilterBuilder.cs
+++ b/src/Rev2/FilterBuilder.cs
@@ -5,17 +5,17 @@ namespace MoonTools.ECS.Rev2
 	public ref struct FilterBuilder
 	{
 		World World;
-		HashSet<ComponentId> Included;
-		HashSet<ComponentId> Excluded;
+		HashSet<Id> Included;
+		HashSet<Id> Excluded;
 
 		internal FilterBuilder(World world)
 		{
 			World = world;
-			Included = new HashSet<ComponentId>();
-			Excluded = new HashSet<ComponentId>();
+			Included = new HashSet<Id>();
+			Excluded = new HashSet<Id>();
 		}
 
-		private FilterBuilder(World world, HashSet<ComponentId> included, HashSet<ComponentId> excluded)
+		private FilterBuilder(World world, HashSet<Id> included, HashSet<Id> excluded)
 		{
 			World = world;
 			Included = included;
diff --git a/src/Rev2/Id.cs b/src/Rev2/Id.cs
new file mode 100644
index 0000000..5094ad1
--- /dev/null
+++ b/src/Rev2/Id.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace MoonTools.ECS.Rev2;
+
+public readonly record struct Id : IComparable<Id>
+{
+	public readonly ulong Value;
+
+	private const ulong HI = 0xFFFFFFFF00000000;
+	private const ulong LO = 0xFFFFFFFF;
+
+	public bool IsPair => (HI & Value) != 0;
+	public uint Low => (uint)(LO & Value);
+	public uint High => (uint)((HI & Value) >>> 32);
+
+	public Id(ulong value)
+	{
+		Value = value;
+	}
+
+	public Id(uint relation, uint target)
+	{
+		Value = (relation << 31) | target;
+	}
+
+	public int CompareTo(Id other)
+	{
+		return Value.CompareTo(other.Value);
+	}
+}
diff --git a/src/Rev2/IdAssigner.cs b/src/Rev2/IdAssigner.cs
index c797b43..f326a62 100644
--- a/src/Rev2/IdAssigner.cs
+++ b/src/Rev2/IdAssigner.cs
@@ -2,12 +2,12 @@ using System.Collections.Generic;
 
 namespace MoonTools.ECS.Rev2
 {
-	internal class IdAssigner<T> where T : struct, IHasId
+	internal class IdAssigner
 	{
-		int Next;
-		Queue<int> AvailableIds = new Queue<int>();
+		ulong Next;
+		Queue<ulong> AvailableIds = new Queue<ulong>();
 
-		public T Assign()
+		public Id Assign()
 		{
 			if (!AvailableIds.TryDequeue(out var id))
 			{
@@ -15,12 +15,12 @@ namespace MoonTools.ECS.Rev2
 				Next += 1;
 			}
 
-			return new T { Id = id };
+			return new Id(id);
 		}
 
-		public void Unassign(T idHaver)
+		public void Unassign(Id id)
 		{
-			AvailableIds.Enqueue(idHaver.Id);
+			AvailableIds.Enqueue(id.Value);
 		}
 	}
 }
diff --git a/src/Rev2/World.cs b/src/Rev2/World.cs
index 8e8858b..8974ace 100644
--- a/src/Rev2/World.cs
+++ b/src/Rev2/World.cs
@@ -8,22 +8,21 @@ namespace MoonTools.ECS.Rev2
 	public class World : IDisposable
 	{
 		// Get ComponentId from a Type
-		internal static Dictionary<Type, ComponentId> TypeToComponentId = new Dictionary<Type, ComponentId>();
+		internal static Dictionary<Type, Id> TypeToComponentId = new Dictionary<Type, Id>();
 		// Get element size from a ComponentId
-		internal static Dictionary<ComponentId, int> ElementSizes = new Dictionary<ComponentId, int>();
+		internal static Dictionary<Id, int> ElementSizes = new Dictionary<Id, int>();
 
 		// Lookup from ArchetypeSignature to Archetype
 		internal Dictionary<ArchetypeSignature, Archetype> ArchetypeIndex = new Dictionary<ArchetypeSignature, Archetype>();
 
 		// Going from EntityId to Archetype and storage row
-		Dictionary<EntityId, Record> EntityIndex = new Dictionary<EntityId, Record>();
+		Dictionary<Id, Record> EntityIndex = new Dictionary<Id, Record>();
 
 		// Going from ComponentId to Archetype list
-		Dictionary<ComponentId, List<Archetype>> ComponentIndex = new Dictionary<ComponentId, List<Archetype>>();
+		Dictionary<Id, List<Archetype>> ComponentIndex = new Dictionary<Id, List<Archetype>>();
 
 		// ID Management
-		IdAssigner<EntityId> EntityIdAssigner = new IdAssigner<EntityId>();
-		IdAssigner<ComponentId> ComponentIdAssigner = new IdAssigner<ComponentId>();
+		IdAssigner IdAssigner = new IdAssigner();
 
 		internal readonly Archetype EmptyArchetype;
 
@@ -59,9 +58,9 @@ namespace MoonTools.ECS.Rev2
 			return archetype;
 		}
 
-		public EntityId CreateEntity()
+		public Id CreateEntity()
 		{
-			var entityId = EntityIdAssigner.Assign();
+			var entityId = IdAssigner.Assign();
 			EntityIndex.Add(entityId, new Record(EmptyArchetype, EmptyArchetype.Count));
 			EmptyArchetype.RowToEntity.Add(entityId);
 			return entityId;
@@ -70,27 +69,41 @@ namespace MoonTools.ECS.Rev2
 		// used as a fast path by snapshot restore
 		internal void CreateEntityOnArchetype(Archetype archetype)
 		{
-			var entityId = EntityIdAssigner.Assign();
+			var entityId = IdAssigner.Assign();
 			EntityIndex.Add(entityId, new Record(archetype, archetype.Count));
 			archetype.RowToEntity.Add(entityId);
 		}
 
 		// used as a fast path by Archetype.ClearAll and snapshot restore
-		internal void FreeEntity(EntityId entityId)
+		internal void FreeEntity(Id entityId)
 		{
 			EntityIndex.Remove(entityId);
-			EntityIdAssigner.Unassign(entityId);
+			IdAssigner.Unassign(entityId);
+		}
+
+		private void RegisterTypeId(Id typeId, int elementSize)
+		{
+			ComponentIndex.Add(typeId, new List<Archetype>());
+			ElementSizes.Add(typeId, elementSize);
 		}
 
 		// FIXME: would be much more efficient to do all this at load time somehow
 		private void RegisterComponent<T>() where T : unmanaged
 		{
-			var componentId = ComponentIdAssigner.Assign();
+			var componentId = IdAssigner.Assign();
 			TypeToComponentId.Add(typeof(T), componentId);
 			ComponentIndex.Add(componentId, new List<Archetype>());
 			ElementSizes.Add(componentId, Unsafe.SizeOf<T>());
 		}
 
+		private void TryRegisterTypeId(Id typeId, int elementSize)
+		{
+			if (!ComponentIndex.ContainsKey(typeId))
+			{
+				RegisterTypeId(typeId, elementSize);
+			}
+		}
+
 		private void TryRegisterComponentId<T>() where T : unmanaged
 		{
 			if (!TypeToComponentId.ContainsKey(typeof(T)))
@@ -99,22 +112,13 @@ namespace MoonTools.ECS.Rev2
 			}
 		}
 
-		// non-generic variant for use with Transfer
-		internal void AddComponentIndexEntry(ComponentId componentId)
-		{
-			if (!ComponentIndex.ContainsKey(componentId))
-			{
-				ComponentIndex.Add(componentId, new List<Archetype>());
-			}
-		}
-
 		[MethodImpl(MethodImplOptions.AggressiveInlining)]
-		private ComponentId GetComponentId<T>() where T : unmanaged
+		private Id GetComponentId<T>() where T : unmanaged
 		{
 			return TypeToComponentId[typeof(T)];
 		}
 
-		public bool Has<T>(EntityId entityId) where T : unmanaged
+		public bool Has<T>(Id entityId) where T : unmanaged
 		{
 			var componentId = GetComponentId<T>();
 			var record = EntityIndex[entityId];
@@ -122,7 +126,7 @@ namespace MoonTools.ECS.Rev2
 		}
 
 		// will throw if non-existent
-		public unsafe ref T Get<T>(EntityId entityId) where T : unmanaged
+		public unsafe ref T Get<T>(Id entityId) where T : unmanaged
 		{
 			var componentId = GetComponentId<T>();
 
@@ -133,7 +137,7 @@ namespace MoonTools.ECS.Rev2
 			return ref ((T*) column.Elements)[record.Row];
 		}
 
-		public unsafe void Set<T>(in EntityId entityId, in T component) where T : unmanaged
+		public unsafe void Set<T>(in Id entityId, in T component) where T : unmanaged
 		{
 			TryRegisterComponentId<T>();
 			var componentId = GetComponentId<T>();
@@ -152,7 +156,7 @@ namespace MoonTools.ECS.Rev2
 			}
 		}
 
-		private void Add<T>(EntityId entityId, in T component) where T : unmanaged
+		private void Add<T>(Id entityId, in T component) where T : unmanaged
 		{
 			Archetype? nextArchetype;
 
@@ -191,7 +195,7 @@ namespace MoonTools.ECS.Rev2
 			column.Append(component);
 		}
 
-		public void Remove<T>(EntityId entityId) where T : unmanaged
+		public void Remove<T>(Id entityId) where T : unmanaged
 		{
 			Archetype? nextArchetype;
 
@@ -223,7 +227,105 @@ namespace MoonTools.ECS.Rev2
 			MoveEntityToLowerArchetype(entityId, row, archetype, nextArchetype, componentId);
 		}
 
-		public void Destroy(EntityId entityId)
+		private Id Pair(in Id relation, in Id target)
+		{
+			return new Id(relation.Low, target.Low);
+		}
+
+		public void Relate<T>(in Id entityA, in Id entityB, in T relation) where T : unmanaged
+		{
+			TryRegisterComponentId<T>();
+			var relationDataTypeId = GetComponentId<T>();
+
+			var typeId = Pair(relationDataTypeId, entityB);
+
+			TryRegisterTypeId(typeId, Unsafe.SizeOf<T>());
+			SetRelationData(entityA, typeId, relation);
+		}
+
+		public bool Related<T>(in Id entityA, in Id entityB) where T : unmanaged
+		{
+			var relationDataTypeId = GetComponentId<T>();
+			var typeId = Pair(relationDataTypeId, entityB);
+			return Has(entityA, typeId);
+		}
+
+		public T GetRelationData<T>(in Id entityA, in Id entityB) where T : unmanaged
+		{
+			var relationDataTypeId = GetComponentId<T>();
+			var typeId = Pair(relationDataTypeId, entityB);
+			return Get<T>(entityA, typeId);
+		}
+
+		private unsafe ref T Get<T>(Id entityId, Id typeId) where T : unmanaged
+		{
+			var record = EntityIndex[entityId];
+			var columnIndex = record.Archetype.ComponentToColumnIndex[typeId];
+			var column = record.Archetype.ComponentColumns[columnIndex];
+
+			return ref ((T*) column.Elements)[record.Row];
+		}
+
+		private bool Has(Id entityId, Id typeId)
+		{
+			var record = EntityIndex[entityId];
+			return record.Archetype.ComponentToColumnIndex.ContainsKey(typeId);
+		}
+
+		private void Add<T>(Id entityId, Id typeId, in T component) where T : unmanaged
+		{
+			Archetype? nextArchetype;
+
+			// move the entity to the new archetype
+			var record = EntityIndex[entityId];
+			var archetype = record.Archetype;
+
+			if (archetype.Edges.TryGetValue(typeId, out var edge))
+			{
+				nextArchetype = edge.Add;
+			}
+			else
+			{
+				// FIXME: pool the signatures
+				var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1);
+				archetype.Signature.CopyTo(nextSignature);
+				nextSignature.Insert(typeId);
+
+				if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype))
+				{
+					nextArchetype = CreateArchetype(nextSignature);
+				}
+
+				var newEdge = new ArchetypeEdge(nextArchetype, archetype);
+				archetype.Edges.Add(typeId, newEdge);
+				nextArchetype.Edges.Add(typeId, newEdge);
+			}
+
+			MoveEntityToHigherArchetype(entityId, record.Row, archetype, nextArchetype);
+
+			// add the new component to the new archetype
+			var columnIndex = nextArchetype.ComponentToColumnIndex[typeId];
+			var column = nextArchetype.ComponentColumns[columnIndex];
+			column.Append(component);
+		}
+
+		private unsafe void SetRelationData<T>(in Id entityId, in Id typeId, T data) where T : unmanaged
+		{
+			if (Has(entityId, typeId))
+			{
+				var record = EntityIndex[entityId];
+				var columnIndex = record.Archetype.ComponentToColumnIndex[typeId];
+				var column = record.Archetype.ComponentColumns[columnIndex];
+
+				((T*) column.Elements)[record.Row] = data;
+			}
+			else
+			{
+				Add(entityId, typeId, data);
+			}
+		}
+
+		public void Destroy(Id entityId)
 		{
 			var record = EntityIndex[entityId];
 			var archetype = record.Archetype;
@@ -244,10 +346,10 @@ namespace MoonTools.ECS.Rev2
 
 			archetype.RowToEntity.RemoveAt(archetype.Count - 1);
 			EntityIndex.Remove(entityId);
-			EntityIdAssigner.Unassign(entityId);
+			IdAssigner.Unassign(entityId);
 		}
 
-		private void MoveEntityToHigherArchetype(EntityId entityId, int row, Archetype from, Archetype to)
+		private void MoveEntityToHigherArchetype(Id entityId, int row, Archetype from, Archetype to)
 		{
 			for (int i = 0; i < from.ComponentColumns.Count; i += 1)
 			{
@@ -276,7 +378,7 @@ namespace MoonTools.ECS.Rev2
 			to.RowToEntity.Add(entityId);
 		}
 
-		private void MoveEntityToLowerArchetype(EntityId entityId, int row, Archetype from, Archetype to, ComponentId removed)
+		private void MoveEntityToLowerArchetype(Id entityId, int row, Archetype from, Archetype to, Id removed)
 		{
 			for (int i = 0; i < from.ComponentColumns.Count; i += 1)
 			{