Repository: mlange-42/arche
Pull request #330 (2024-01-14 20:57:14)

IDs are opaque types
Component and resource IDs are now opaque types instead of type aliases for `uint8`

This makes it (almost) impossible to create invalid IDs. The only way is to use IDs obtained from a different world.

Before, it was possible to create invalid IDs like this: `ID(42)`, `ResID(42)`. Now, they can only be obtained from a World with `ecs.ComponentID(*World)` and `ecs.ResourceID(*World)`.

coveralls: ## Pull Request Test Coverage Report for [Build 7521666665](https://coveralls.io/builds/65062413)


* **0** of **0**   changed or added relevant lines in **0** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage remained the same at **100.0%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/65062413/badge)](https://coveralls.io/builds/65062413) |
| :-- | --: |
| Change from base [Build 7515952855](https://coveralls.io/builds/65058320): |  0.0% |
| Covered Lines: | 4936 |
| Relevant Lines: | 4936 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e58f219..b557b40 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
 * Removes `Mask.Lo` and `Mask.Hi`, internal mask representation is now private (#313)
 * `Filters.Matches(Mask)` became `Filters.Matches(*Mask)`; same for all `Filter` implementations (#313)  
 This change was necessary to get the same performance as before, despite the more heavyweight implementation of the now 256 bits `Mask`.
+* Component and resource IDs are now opaque types instead of type aliases for `uint8` (#329)
 
 ### Features
 
@@ -22,6 +23,7 @@ This change was necessary to get the same performance as before, despite the mor
 * Entities support JSON marshalling and unmarshalling (#319)
 * Adds methods `Entity.ID()` and `Entity.Gen()` for serialization purposes (#319)
 * The world's entity state can be extracted and re-established via `World.DumpEntities()` and `World.LoadEntities()` (#319, #326)
+* Adds functions `ComponentIDs(*World)` and `ResourceIDs(*World)` to get all registered IDs (#329)
 
 ### Performance
 
diff --git a/benchmark/profile/many_arch/main.go b/benchmark/profile/many_arch/main.go
index 5b69385..f19aa71 100644
--- a/benchmark/profile/many_arch/main.go
+++ b/benchmark/profile/many_arch/main.go
@@ -30,7 +30,7 @@ func main() {
 func run(rounds, iters, entities int) {
 	for i := 0; i < rounds; i++ {
 		world := ecs.NewWorld()
-		c.RegisterAll(&world)
+		ids := c.RegisterAll(&world)
 
 		perArch := 2 * entities / 1000
 
@@ -38,7 +38,7 @@ func run(rounds, iters, entities int) {
 			mask := i
 			add := make([]ecs.ID, 0, 10)
 			for j := 0; j < 10; j++ {
-				id := ecs.ID(j)
+				id := ids[j]
 				m := 1 << j
 				if mask&m == m {
 					add = append(add, id)
@@ -50,11 +50,11 @@ func run(rounds, iters, entities int) {
 			}
 		}
 
-		var filter ecs.Filter = ecs.All(6)
+		var filter ecs.Filter = ecs.All(ids[6])
 		for j := 0; j < iters; j++ {
 			query := world.Query(filter)
 			for query.Next() {
-				pos := (*c.TestStruct6)(query.Get(6))
+				pos := (*c.TestStruct6)(query.Get(ids[6]))
 				pos.Val++
 			}
 		}
diff --git a/benchmark/profile/many_arch_build/main.go b/benchmark/profile/many_arch_build/main.go
index 879936d..8af284a 100644
--- a/benchmark/profile/many_arch_build/main.go
+++ b/benchmark/profile/many_arch_build/main.go
@@ -28,13 +28,13 @@ func main() {
 func run(rounds int) {
 	for i := 0; i < rounds; i++ {
 		world := ecs.NewWorld()
-		c.RegisterAll(&world)
+		ids := c.RegisterAll(&world)
 
 		for i := 0; i < 1024; i++ {
 			mask := i
 			add := make([]ecs.ID, 0, 10)
 			for j := 0; j < 10; j++ {
-				id := ecs.ID(j)
+				id := ids[j]
 				m := 1 << j
 				if mask&m == m {
 					add = append(add, id)
diff --git a/ecs/archetype.go b/ecs/archetype.go
index 0dded56..59907d8 100644
--- a/ecs/archetype.go
+++ b/ecs/archetype.go
@@ -15,11 +15,12 @@ var layoutSize uint32 = uint32(unsafe.Sizeof(layout{}))
 
 // Helper for accessing data from an archetype
 type archetypeAccess struct {
-	Mask              Mask           // Archetype's mask
-	basePointer       unsafe.Pointer // Pointer to the first component column layout.
-	entityPointer     unsafe.Pointer // Pointer to the entity storage
-	RelationTarget    Entity
-	RelationComponent int8
+	Mask                 Mask           // Archetype's mask
+	basePointer          unsafe.Pointer // Pointer to the first component column layout.
+	entityPointer        unsafe.Pointer // Pointer to the entity storage
+	RelationTarget       Entity
+	RelationComponent    ID
+	HasRelationComponent bool
 }
 
 // GetEntity returns the entity at the given index
@@ -39,12 +40,12 @@ func (a *archetypeAccess) HasComponent(id ID) bool {
 
 // HasRelation returns whether the archetype has a relation component.
 func (a *archetypeAccess) HasRelation() bool {
-	return a.RelationComponent >= 0
+	return a.HasRelationComponent
 }
 
 // GetLayout returns the column layout for a component.
 func (a *archetypeAccess) getLayout(id ID) *layout {
-	return (*layout)(unsafe.Add(a.basePointer, layoutSize*uint32(id)))
+	return (*layout)(unsafe.Add(a.basePointer, layoutSize*uint32(id.id)))
 }
 
 // layout specification of a component column.
@@ -101,20 +102,21 @@ func (a *archetype) Init(node *archNode, data *archetypeData, index int32, forSt
 		size = (size + (align - 1)) / align * align
 
 		a.buffers[i] = reflect.New(reflect.ArrayOf(cap, tp)).Elem()
-		a.layouts[id] = layout{
+		a.layouts[id.id] = layout{
 			a.buffers[i].Addr().UnsafePointer(),
 			uint32(size),
 		}
-		a.indices.Set(id, uint32(i))
+		a.indices.Set(id.id, uint32(i))
 	}
 	a.entityBuffer = reflect.New(reflect.ArrayOf(cap, entityType)).Elem()
 
 	a.archetypeAccess = archetypeAccess{
-		basePointer:       unsafe.Pointer(&a.layouts[0]),
-		entityPointer:     a.entityBuffer.Addr().UnsafePointer(),
-		Mask:              node.Mask,
-		RelationTarget:    relation,
-		RelationComponent: node.Relation,
+		basePointer:          unsafe.Pointer(&a.layouts[0]),
+		entityPointer:        a.entityBuffer.Addr().UnsafePointer(),
+		Mask:                 node.Mask,
+		RelationTarget:       relation,
+		RelationComponent:    node.Relation,
+		HasRelationComponent: node.HasRelation,
 	}
 
 	a.node = node
@@ -296,12 +298,12 @@ func (a *archetype) Cap() uint32 {
 }
 
 // Stats generates statistics for an archetype
-func (a *archetype) Stats(reg *componentRegistry[ID]) stats.ArchetypeStats {
+func (a *archetype) Stats(reg *componentRegistry) stats.ArchetypeStats {
 	ids := a.Components()
 	aCompCount := len(ids)
 	aTypes := make([]reflect.Type, aCompCount)
 	for j, id := range ids {
-		aTypes[j], _ = reg.ComponentType(id)
+		aTypes[j], _ = reg.ComponentType(id.id)
 	}
 
 	cap := int(a.Cap())
@@ -321,7 +323,7 @@ func (a *archetype) Stats(reg *componentRegistry[ID]) stats.ArchetypeStats {
 }
 
 // UpdateStats updates statistics for an archetype
-func (a *archetype) UpdateStats(node *stats.NodeStats, stats *stats.ArchetypeStats, reg *componentRegistry[ID]) {
+func (a *archetype) UpdateStats(node *stats.NodeStats, stats *stats.ArchetypeStats, reg *componentRegistry) {
 	cap := int(a.Cap())
 	memory := cap * (int(entitySize) + node.MemoryPerEntity)
 
@@ -356,7 +358,7 @@ func (a *archetype) extend(by uint32) {
 		if lay.itemSize == 0 {
 			continue
 		}
-		index, _ := a.indices.Get(id)
+		index, _ := a.indices.Get(id.id)
 		old := a.buffers[index]
 		a.buffers[index] = reflect.New(reflect.ArrayOf(int(a.cap), old.Type().Elem())).Elem()
 		lay.pointer = a.buffers[index].Addr().UnsafePointer()
diff --git a/ecs/archetype_node.go b/ecs/archetype_node.go
index 247a1c6..457753a 100644
--- a/ecs/archetype_node.go
+++ b/ecs/archetype_node.go
@@ -11,9 +11,9 @@ import (
 type archNode struct {
 	*nodeData
 	Mask        Mask // Mask of the archetype
-	Relation    int8 // The node's relation component ID. Negative value stands for no relation
-	IsActive    bool
+	Relation    ID
 	HasRelation bool
+	IsActive    bool
 }
 
 type nodeData struct {
@@ -32,9 +32,9 @@ type nodeData struct {
 }
 
 // Creates a new archNode
-func newArchNode(mask Mask, data *nodeData, relation int8, capacityIncrement int, components []componentType) archNode {
+func newArchNode(mask Mask, data *nodeData, relation ID, hasRelation bool, capacityIncrement int, components []componentType) archNode {
 	var arch map[Entity]*archetype
-	if relation >= 0 {
+	if hasRelation {
 		arch = map[Entity]*archetype{}
 	}
 	ids := make([]ID, len(components))
@@ -43,10 +43,10 @@ func newArchNode(mask Mask, data *nodeData, relation int8, capacityIncrement int
 	var maxSize uintptr = 0
 	prev := -1
 	for i, c := range components {
-		if int(c.ID) <= prev {
+		if int(c.ID.id) <= prev {
 			panic("component arguments must be sorted by ID")
 		}
-		prev = int(c.ID)
+		prev = int(c.ID.id)
 
 		ids[i] = c.ID
 		types[i] = c.Type
@@ -77,7 +77,7 @@ func newArchNode(mask Mask, data *nodeData, relation int8, capacityIncrement int
 		nodeData:    data,
 		Mask:        mask,
 		Relation:    relation,
-		HasRelation: relation >= 0,
+		HasRelation: hasRelation,
 	}
 }
 
@@ -101,7 +101,7 @@ func (a *archNode) Archetypes() archetypes {
 //
 // The target is ignored if the node has no relation component.
 func (a *archNode) GetArchetype(target Entity) *archetype {
-	if a.Relation >= 0 {
+	if a.HasRelation {
 		return a.archetypeMap[target]
 	}
 	return a.archetype
@@ -186,12 +186,12 @@ func (a *archNode) Reset(cache *Cache) {
 }
 
 // Stats generates statistics for an archetype node.
-func (a *archNode) Stats(reg *componentRegistry[ID]) stats.NodeStats {
+func (a *archNode) Stats(reg *componentRegistry) stats.NodeStats {
 	ids := a.Ids
 	aCompCount := len(ids)
 	aTypes := make([]reflect.Type, aCompCount)
 	for j, id := range ids {
-		aTypes[j], _ = reg.ComponentType(id)
+		aTypes[j], _ = reg.ComponentType(id.id)
 	}
 
 	arches := a.Archetypes()
@@ -214,7 +214,9 @@ func (a *archNode) Stats(reg *componentRegistry[ID]) stats.NodeStats {
 	}
 
 	memPerEntity := 0
-	for j := range ids {
+	intIDs := make([]uint8, len(ids))
+	for j, id := range ids {
+		intIDs[j] = id.id
 		memPerEntity += int(aTypes[j].Size())
 	}
 
@@ -224,7 +226,7 @@ func (a *archNode) Stats(reg *componentRegistry[ID]) stats.NodeStats {
 		IsActive:             a.IsActive,
 		HasRelation:          a.HasRelation,
 		Components:           aCompCount,
-		ComponentIDs:         ids,
+		ComponentIDs:         intIDs,
 		ComponentTypes:       aTypes,
 		Memory:               memory,
 		MemoryPerEntity:      memPerEntity,
@@ -235,7 +237,7 @@ func (a *archNode) Stats(reg *componentRegistry[ID]) stats.NodeStats {
 }
 
 // UpdateStats updates statistics for an archetype node.
-func (a *archNode) UpdateStats(stats *stats.NodeStats, reg *componentRegistry[ID]) {
+func (a *archNode) UpdateStats(stats *stats.NodeStats, reg *componentRegistry) {
 	if !a.IsActive {
 		return
 	}
diff --git a/ecs/bitmask.go b/ecs/bitmask.go
index 17fe980..bdd55ad 100644
--- a/ecs/bitmask.go
+++ b/ecs/bitmask.go
@@ -58,8 +58,8 @@ func (b Mask) Exclusive() MaskFilter {
 //
 // Returns false for bit >= [MaskTotalBits].
 func (b *Mask) Get(bit ID) bool {
-	idx := bit / 64
-	offset := bit - (64 * idx)
+	idx := bit.id / 64
+	offset := bit.id - (64 * idx)
 	mask := uint64(1 << offset)
 	return b.bits[idx]&mask == mask
 }
@@ -68,8 +68,8 @@ func (b *Mask) Get(bit ID) bool {
 //
 // Has no effect for bit >= [MaskTotalBits].
 func (b *Mask) Set(bit ID, value bool) {
-	idx := bit / 64
-	offset := bit - (64 * idx)
+	idx := bit.id / 64
+	offset := bit.id - (64 * idx)
 	if value {
 		b.bits[idx] |= (1 << offset)
 	} else {
diff --git a/ecs/builder.go b/ecs/builder.go
index 833949a..6bf844c 100644
--- a/ecs/builder.go
+++ b/ecs/builder.go
@@ -68,16 +68,16 @@ func (b *Builder) NewBatch(count int, target ...Entity) {
 			panic("can't set target entity: builder has no relation")
 		}
 		if b.comps == nil {
-			b.world.newEntities(count, int8(b.targetID), target[0], b.ids...)
+			b.world.newEntities(count, b.targetID, true, target[0], b.ids...)
 			return
 		}
-		b.world.newEntitiesWith(count, int8(b.targetID), target[0], b.comps...)
+		b.world.newEntitiesWith(count, b.targetID, true, target[0], b.comps...)
 		return
 	}
 	if b.comps == nil {
-		b.world.newEntities(count, -1, Entity{}, b.ids...)
+		b.world.newEntities(count, ID{}, false, Entity{}, b.ids...)
 	} else {
-		b.world.newEntitiesWith(count, -1, Entity{}, b.comps...)
+		b.world.newEntitiesWith(count, ID{}, false, Entity{}, b.comps...)
 	}
 }
 
@@ -91,14 +91,14 @@ func (b *Builder) NewBatchQ(count int, target ...Entity) Query {
 			panic("can't set target entity: builder has no relation")
 		}
 		if b.comps == nil {
-			return b.world.newEntitiesQuery(count, int8(b.targetID), target[0], b.ids...)
+			return b.world.newEntitiesQuery(count, b.targetID, true, target[0], b.ids...)
 		}
-		return b.world.newEntitiesWithQuery(count, int8(b.targetID), target[0], b.comps...)
+		return b.world.newEntitiesWithQuery(count, b.targetID, true, target[0], b.comps...)
 	}
 	if b.comps == nil {
-		return b.world.newEntitiesQuery(count, -1, Entity{}, b.ids...)
+		return b.world.newEntitiesQuery(count, ID{}, false, Entity{}, b.ids...)
 	}
-	return b.world.newEntitiesWithQuery(count, -1, Entity{}, b.comps...)
+	return b.world.newEntitiesWithQuery(count, ID{}, false, Entity{}, b.comps...)
 }
 
 // Add the builder's components to an entity.
@@ -111,10 +111,10 @@ func (b *Builder) Add(entity Entity, target ...Entity) {
 			panic("can't set target entity: builder has no relation")
 		}
 		if b.comps == nil {
-			b.world.exchange(entity, b.ids, nil, int8(b.targetID), target[0])
+			b.world.exchange(entity, b.ids, nil, b.targetID, b.hasTarget, target[0])
 			return
 		}
-		b.world.assign(entity, int8(b.targetID), target[0], b.comps...)
+		b.world.assign(entity, b.targetID, b.hasTarget, target[0], b.comps...)
 		return
 	}
 	if b.comps == nil {
diff --git a/ecs/id_map.go b/ecs/id_map.go
index d7d1e1a..967a99a 100644
--- a/ecs/id_map.go
+++ b/ecs/id_map.go
@@ -31,7 +31,7 @@ func newIDMap[T any]() idMap[T] {
 
 // Get returns the value at the given key and whether the key is present.
 func (m *idMap[T]) Get(index uint8) (T, bool) {
-	if !m.used.Get(index) {
+	if !m.used.Get(id(index)) {
 		return m.zeroValue, false
 	}
 	return m.chunks[index/idMapChunkSize][index%idMapChunkSize], true
@@ -39,7 +39,7 @@ func (m *idMap[T]) Get(index uint8) (T, bool) {
 
 // Get returns a pointer to the value at the given key and whether the key is present.
 func (m *idMap[T]) GetPointer(index uint8) (*T, bool) {
-	if !m.used.Get(index) {
+	if !m.used.Get(id(index)) {
 		return nil, false
 	}
 	return &m.chunks[index/idMapChunkSize][index%idMapChunkSize], true
@@ -52,7 +52,7 @@ func (m *idMap[T]) Set(index uint8, value T) {
 		m.chunks[chunk] = make([]T, idMapChunkSize)
 	}
 	m.chunks[chunk][index%idMapChunkSize] = value
-	m.used.Set(index, true)
+	m.used.Set(id(index), true)
 	m.chunkUsed[chunk]++
 }
 
@@ -60,7 +60,7 @@ func (m *idMap[T]) Set(index uint8, value T) {
 // It de-allocates empty chunks.
 func (m *idMap[T]) Remove(index uint8) {
 	chunk := index / idMapChunkSize
-	m.used.Set(index, false)
+	m.used.Set(id(index), false)
 	m.chunkUsed[chunk]--
 	if m.chunkUsed[chunk] == 0 {
 		m.chunks[chunk] = nil
diff --git a/ecs/query.go b/ecs/query.go
index af4205f..427fa11 100644
--- a/ecs/query.go
+++ b/ecs/query.go
@@ -105,8 +105,8 @@ func (q *Query) Entity() Entity {
 //
 // Panics if the entity does not have the given component, or if the component is not a [Relation].
 func (q *Query) Relation(comp ID) Entity {
-	if q.access.RelationComponent != int8(comp) {
-		panic(fmt.Sprintf("entity has no component %v, or it is not a relation component", q.world.registry.Types[comp]))
+	if q.access.RelationComponent.id != comp.id {
+		panic(fmt.Sprintf("entity has no component %v, or it is not a relation component", q.world.registry.Types[comp.id]))
 	}
 	return q.access.RelationTarget
 }
diff --git a/ecs/registry.go b/ecs/registry.go
index b8035dc..cc677b8 100644
--- a/ecs/registry.go
+++ b/ecs/registry.go
@@ -6,26 +6,28 @@ import (
 )
 
 // componentRegistry keeps track of component IDs.
-type componentRegistry[T uint8] struct {
-	Components map[reflect.Type]T
+type componentRegistry struct {
+	Components map[reflect.Type]uint8
 	Types      []reflect.Type
 	Used       Mask
 	IsRelation Mask
+	IDs        []uint8
 }
 
 // newComponentRegistry creates a new ComponentRegistry.
-func newComponentRegistry[T uint8]() componentRegistry[T] {
-	return componentRegistry[T]{
-		Components: map[reflect.Type]T{},
+func newComponentRegistry() componentRegistry {
+	return componentRegistry{
+		Components: map[reflect.Type]uint8{},
 		Types:      make([]reflect.Type, MaskTotalBits),
 		Used:       Mask{},
 		IsRelation: Mask{},
+		IDs:        []uint8{},
 	}
 }
 
 // ComponentID returns the ID for a component type, and registers it if not already registered.
 // The second return value indicates if it is a newly created ID.
-func (r *componentRegistry[T]) ComponentID(tp reflect.Type) (T, bool) {
+func (r *componentRegistry) ComponentID(tp reflect.Type) (uint8, bool) {
 	if id, ok := r.Components[tp]; ok {
 		return id, false
 	}
@@ -33,40 +35,44 @@ func (r *componentRegistry[T]) ComponentID(tp reflect.Type) (T, bool) {
 }
 
 // ComponentType returns the type of a component by ID.
-func (r *componentRegistry[T]) ComponentType(id T) (reflect.Type, bool) {
-	return r.Types[id], r.Used.Get(uint8(id))
+func (r *componentRegistry) ComponentType(id uint8) (reflect.Type, bool) {
+	return r.Types[id], r.Used.Get(ID{id: id})
 }
 
 // ComponentType returns the type of a component by ID.
-func (r *componentRegistry[T]) Count() int {
+func (r *componentRegistry) Count() int {
 	return len(r.Components)
 }
 
 // registerComponent registers a components and assigns an ID for it.
-func (r *componentRegistry[T]) registerComponent(tp reflect.Type, totalBits int) T {
+func (r *componentRegistry) registerComponent(tp reflect.Type, totalBits int) uint8 {
 	val := len(r.Components)
 	if val >= totalBits {
 		panic(fmt.Sprintf("maximum of %d component types exceeded", totalBits))
 	}
-	id := T(val)
-	r.Components[tp], r.Types[id] = id, tp
-	r.Used.Set(uint8(id), true)
+	newID := uint8(val)
+	id := id(newID)
+	r.Components[tp], r.Types[newID] = newID, tp
+	r.Used.Set(id, true)
 	if r.isRelation(tp) {
-		r.IsRelation.Set(uint8(id), true)
+		r.IsRelation.Set(id, true)
 	}
-	return id
+	r.IDs = append(r.IDs, newID)
+	return newID
 }
 
-func (r *componentRegistry[T]) unregisterLastComponent() {
-	id := T(len(r.Components) - 1)
-	tp, _ := r.ComponentType(id)
+func (r *componentRegistry) unregisterLastComponent() {
+	newID := uint8(len(r.Components) - 1)
+	id := id(newID)
+	tp, _ := r.ComponentType(newID)
 	delete(r.Components, tp)
-	r.Types[id] = nil
-	r.Used.Set(uint8(id), false)
-	r.IsRelation.Set(uint8(id), false)
+	r.Types[newID] = nil
+	r.Used.Set(id, false)
+	r.IsRelation.Set(id, false)
+	r.IDs = r.IDs[:len(r.IDs)-1]
 }
 
-func (r *componentRegistry[T]) isRelation(tp reflect.Type) bool {
+func (r *componentRegistry) isRelation(tp reflect.Type) bool {
 	if tp.NumField() == 0 {
 		return false
 	}
diff --git a/ecs/resources.go b/ecs/resources.go
index 1606024..bc74c95 100644
--- a/ecs/resources.go
+++ b/ecs/resources.go
@@ -9,7 +9,7 @@ import (
 //
 // Access it using [World.Resources].
 type Resources struct {
-	registry  componentRegistry[ResID]
+	registry  componentRegistry
 	resources []any
 }
 
@@ -28,10 +28,10 @@ func newResources() Resources {
 //
 // See also [github.com/mlange-42/arche/generic.Resource.Add] for a generic variant.
 func (r *Resources) Add(id ResID, res any) {
-	if r.resources[id] != nil {
-		panic(fmt.Sprintf("Resource of ID %d was already added (type %v)", id, reflect.TypeOf(res)))
+	if r.resources[id.id] != nil {
+		panic(fmt.Sprintf("Resource of ID %d was already added (type %v)", id.id, reflect.TypeOf(res)))
 	}
-	r.resources[id] = res
+	r.resources[id.id] = res
 }
 
 // Remove a resource from the world.
@@ -40,10 +40,10 @@ func (r *Resources) Add(id ResID, res any) {
 //
 // See also [github.com/mlange-42/arche/generic.Resource.Remove] for a generic variant.
 func (r *Resources) Remove(id ResID) {
-	if r.resources[id] == nil {
-		panic(fmt.Sprintf("Resource of ID %d is not present", id))
+	if r.resources[id.id] == nil {
+		panic(fmt.Sprintf("Resource of ID %d is not present", id.id))
 	}
-	r.resources[id] = nil
+	r.resources[id.id] = nil
 }
 
 // Get returns a pointer to the resource of the given type.
@@ -52,14 +52,14 @@ func (r *Resources) Remove(id ResID) {
 //
 // See also [github.com/mlange-42/arche/generic.Resource.Get] for a generic variant.
 func (r *Resources) Get(id ResID) interface{} {
-	return r.resources[id]
+	return r.resources[id.id]
 }
 
 // Has returns whether the world has the given resource.
 //
 // See also [github.com/mlange-42/arche/generic.Resource.Has] for a generic variant.
 func (r *Resources) Has(id ResID) bool {
-	return r.resources[id] != nil
+	return r.resources[id.id] != nil
 }
 
 // reset removes all resources.
diff --git a/ecs/types.go b/ecs/types.go
index 21d0b49..3529fcd 100644
--- a/ecs/types.go
+++ b/ecs/types.go
@@ -6,10 +6,18 @@ import "reflect"
 type eid uint32
 
 // ID is the component identifier type.
-type ID = uint8
+type ID struct {
+	id uint8
+}
+
+func id(id uint8) ID {
+	return ID{id: id}
+}
 
 // ResID is the resource identifier type.
-type ResID = uint8
+type ResID struct {
+	id uint8
+}
 
 // Component is a component ID/pointer pair.
 //
diff --git a/ecs/util.go b/ecs/util.go
index 8a39219..e8f6b99 100644
--- a/ecs/util.go
+++ b/ecs/util.go
@@ -39,7 +39,7 @@ func capacityU32(size, increment uint32) uint32 {
 	return cap
 }
 
-func maskToTypes(mask Mask, reg *componentRegistry[ID]) []componentType {
+func maskToTypes(mask Mask, reg *componentRegistry) []componentType {
 	count := int(mask.TotalBitsSet())
 	types := make([]componentType, count)
 
@@ -49,9 +49,9 @@ func maskToTypes(mask Mask, reg *componentRegistry[ID]) []componentType {
 			continue
 		}
 		for j := 0; j < wordSize; j++ {
-			id := ID(i*wordSize + j)
+			id := ID{id: uint8(i*wordSize + j)}
 			if mask.Get(id) {
-				types[idx] = componentType{ID: id, Type: reg.Types[id]}
+				types[idx] = componentType{ID: id, Type: reg.Types[id.id]}
 				idx++
 			}
 		}
@@ -70,16 +70,16 @@ type lockMask struct {
 // Lock the world and get the Lock bit for later unlocking.
 func (m *lockMask) Lock() uint8 {
 	lock := m.bitPool.Get()
-	m.locks.Set(ID(lock), true)
+	m.locks.Set(id(lock), true)
 	return lock
 }
 
 // Unlock unlocks the given lock bit.
 func (m *lockMask) Unlock(l uint8) {
-	if !m.locks.Get(ID(l)) {
+	if !m.locks.Get(id(l)) {
 		panic("unbalanced unlock")
 	}
-	m.locks.Set(ID(l), false)
+	m.locks.Set(id(l), false)
 	m.bitPool.Recycle(l)
 }
 
diff --git a/ecs/world.go b/ecs/world.go
index b872188..22670e5 100644
--- a/ecs/world.go
+++ b/ecs/world.go
@@ -21,6 +21,16 @@ func ComponentID[T any](w *World) ID {
 	return w.componentID(tp)
 }
 
+// ComponentIDs returns a list of all registered component IDs.
+func ComponentIDs(w *World) []ID {
+	intIds := w.registry.IDs
+	ids := make([]ID, len(intIds))
+	for i, iid := range intIds {
+		ids[i] = id(iid)
+	}
+	return ids
+}
+
 // TypeID returns the [ID] for a component type.
 // Registers the type if it is not already registered.
 //
@@ -31,7 +41,7 @@ func TypeID(w *World, tp reflect.Type) ID {
 
 // ComponentInfo returns the [CompInfo] for a component [ID], and whether the ID is assigned.
 func ComponentInfo(w *World, id ID) (CompInfo, bool) {
-	tp, ok := w.registry.ComponentType(id)
+	tp, ok := w.registry.ComponentType(id.id)
 	if !ok {
 		return CompInfo{}, false
 	}
@@ -52,9 +62,19 @@ func ResourceID[T any](w *World) ResID {
 	return w.resourceID(tp)
 }
 
+// ResourceIDs returns a list of all registered resource IDs.
+func ResourceIDs(w *World) []ResID {
+	intIds := w.resources.registry.IDs
+	ids := make([]ResID, len(intIds))
+	for i, iid := range intIds {
+		ids[i] = ResID{id: iid}
+	}
+	return ids
+}
+
 // ResourceType returns the reflect.Type for a resource [ResID], and whether the ID is assigned.
 func ResourceType(w *World, id ResID) (reflect.Type, bool) {
-	return w.resources.registry.ComponentType(id)
+	return w.resources.registry.ComponentType(id.id)
 }
 
 // GetResource returns a pointer to the given resource type in the world.
@@ -101,14 +121,14 @@ type World struct {
 	entityPool     entityPool            // Pool for entities.
 	archetypes     pagedSlice[archetype] // Archetypes that have no relations components.
 	archetypeData  pagedSlice[archetypeData]
-	nodes          pagedSlice[archNode]  // The archetype graph.
-	nodeData       pagedSlice[nodeData]  // The archetype graph's data.
-	nodePointers   []*archNode           // Helper list of all node pointers for queries.
-	relationNodes  []*archNode           // Archetype nodes that have an entity relation.
-	locks          lockMask              // World locks.
-	registry       componentRegistry[ID] // Component registry.
-	filterCache    Cache                 // Cache for registered filters.
-	stats          stats.WorldStats      // Cached world statistics
+	nodes          pagedSlice[archNode] // The archetype graph.
+	nodeData       pagedSlice[nodeData] // The archetype graph's data.
+	nodePointers   []*archNode          // Helper list of all node pointers for queries.
+	relationNodes  []*archNode          // Archetype nodes that have an entity relation.
+	locks          lockMask             // World locks.
+	registry       componentRegistry    // Component registry.
+	filterCache    Cache                // Cache for registered filters.
+	stats          stats.WorldStats     // Cached world statistics
 }
 
 // NewWorld creates a new [World] from an optional [Config].
@@ -153,7 +173,7 @@ func fromConfig(conf Config) World {
 		resources:      newResources(),
 		filterCache:    newCache(),
 	}
-	node := w.createArchetypeNode(Mask{}, -1)
+	node := w.createArchetypeNode(Mask{}, ID{}, false)
 	w.createArchetype(node, Entity{}, false)
 	return w
 }
@@ -296,8 +316,8 @@ func (w *World) newEntityTargetWith(targetID ID, target Entity, comps ...Compone
 
 // Creates new entities without returning a query over them.
 // Used via [World.Batch].
-func (w *World) newEntities(count int, targetID int8, target Entity, comps ...ID) (*archetype, uint32) {
-	arch, startIdx := w.newEntitiesNoNotify(count, targetID, target, comps...)
+func (w *World) newEntities(count int, targetID ID, hasTarget bool, target Entity, comps ...ID) (*archetype, uint32) {
+	arch, startIdx := w.newEntitiesNoNotify(count, targetID, hasTarget, target, comps...)
 
 	if w.listener != nil {
 		cnt := uint32(count)
@@ -314,8 +334,8 @@ func (w *World) newEntities(count int, targetID int8, target Entity, comps ...ID
 
 // Creates new entities and returns a query over them.
 // Used via [World.Batch].
-func (w *World) newEntitiesQuery(count int, targetID int8, target Entity, comps ...ID) Query {
-	arch, startIdx := w.newEntitiesNoNotify(count, targetID, target, comps...)
+func (w *World) newEntitiesQuery(count int, targetID ID, hasTarget bool, target Entity, comps ...ID) Query {
+	arch, startIdx := w.newEntitiesNoNotify(count, targetID, hasTarget, target, comps...)
 	lock := w.lock()
 
 	batches := batchArchetypes{
@@ -328,13 +348,13 @@ func (w *World) newEntitiesQuery(count int, targetID int8, target Entity, comps
 
 // Creates new entities with component values without returning a query over them.
 // Used via [World.Batch].
-func (w *World) newEntitiesWith(count int, targetID int8, target Entity, comps ...Component) (*archetype, uint32) {
+func (w *World) newEntitiesWith(count int, targetID ID, hasTarget bool, target Entity, comps ...Component) (*archetype, uint32) {
 	ids := make([]ID, len(comps))
 	for i, c := range comps {
 		ids[i] = c.ID
 	}
 
-	arch, startIdx := w.newEntitiesWithNoNotify(count, targetID, target, ids, comps...)
+	arch, startIdx := w.newEntitiesWithNoNotify(count, targetID, hasTarget, target, ids, comps...)
 
 	if w.listener != nil {
 		var i uint32
@@ -351,13 +371,13 @@ func (w *World) newEntitiesWith(count int, targetID int8, target Entity, comps .
 
 // Creates new entities with component values and returns a query over them.
 // Used via [World.Batch].
-func (w *World) newEntitiesWithQuery(count int, targetID int8, target Entity, comps ...Component) Query {
+func (w *World) newEntitiesWithQuery(count int, targetID ID, hasTarget bool, target Entity, comps ...Component) Query {
 	ids := make([]ID, len(comps))
 	for i, c := range comps {
 		ids[i] = c.ID
 	}
 
-	arch, startIdx := w.newEntitiesWithNoNotify(count, targetID, target, ids, comps...)
+	arch, startIdx := w.newEntitiesWithNoNotify(count, targetID, hasTarget, target, ids, comps...)
 	lock := w.lock()
 	batches := batchArchetypes{
 		Added:   arch.Components(),
@@ -547,18 +567,18 @@ func (w *World) Add(entity Entity, comps ...ID) {
 //
 // See also the generic variants under [github.com/mlange-42/arche/generic.Map1], etc.
 func (w *World) Assign(entity Entity, comps ...Component) {
-	w.assign(entity, -1, Entity{}, comps...)
+	w.assign(entity, ID{}, false, Entity{}, comps...)
 }
 
 // assign with relation target.
-func (w *World) assign(entity Entity, relation int8, target Entity, comps ...Component) {
+func (w *World) assign(entity Entity, relation ID, hasRelation bool, target Entity, comps ...Component) {
 	len := len(comps)
 	if len == 0 {
 		panic("no components given to assign")
 	}
 	if len == 1 {
 		c := comps[0]
-		w.exchange(entity, []ID{c.ID}, nil, relation, target)
+		w.exchange(entity, []ID{c.ID}, nil, relation, hasRelation, target)
 		w.copyTo(entity, c.ID, c.Comp)
 		return
 	}
@@ -566,7 +586,7 @@ func (w *World) assign(entity Entity, relation int8, target Entity, comps ...Com
 	for i, c := range comps {
 		ids[i] = c.ID
 	}
-	w.exchange(entity, ids, nil, relation, target)
+	w.exchange(entity, ids, nil, relation, hasRelation, target)
 	for _, c := range comps {
 		w.copyTo(entity, c.ID, c.Comp)
 	}
@@ -611,11 +631,11 @@ func (w *World) Remove(entity Entity, comps ...ID) {
 //
 // See also the generic variants under [github.com/mlange-42/arche/generic.Exchange].
 func (w *World) Exchange(entity Entity, add []ID, rem []ID) {
-	w.exchange(entity, add, rem, -1, Entity{})
+	w.exchange(entity, add, rem, ID{}, false, Entity{})
 }
 
 // exchange with relation target.
-func (w *World) exchange(entity Entity, add []ID, rem []ID, relation int8, target Entity) {
+func (w *World) exchange(entity Entity, add []ID, rem []ID, relation ID, hasRelation bool, target Entity) {
 	w.checkLocked()
 
 	if !w.entityPool.Alive(entity) {
@@ -631,11 +651,11 @@ func (w *World) exchange(entity Entity, add []ID, rem []ID, relation int8, targe
 	oldMask := oldArch.Mask
 	mask := w.getExchangeMask(oldMask, add, rem)
 
-	if relation >= 0 {
-		if !mask.Get(ID(relation)) {
+	if hasRelation {
+		if !mask.Get(relation) {
 			panic("can't add relation: resulting entity has no relation")
 		}
-		if !w.registry.IsRelation.Get(ID(relation)) {
+		if !w.registry.IsRelation.Get(relation) {
 			panic("can't add relation: this is not a relation component")
 		}
 	} else {
@@ -673,13 +693,13 @@ func (w *World) exchange(entity Entity, add []ID, rem []ID, relation int8, targe
 func (w *World) getExchangeMask(mask Mask, add []ID, rem []ID) Mask {
 	for _, comp := range add {
 		if mask.Get(comp) {
-			panic(fmt.Sprintf("entity already has component of type %v, can't add", w.registry.Types[comp]))
+			panic(fmt.Sprintf("entity already has component of type %v, can't add", w.registry.Types[comp.id]))
 		}
 		mask.Set(comp, true)
 	}
 	for _, comp := range rem {
 		if !mask.Get(comp) {
-			panic(fmt.Sprintf("entity does not have a component of type %v, can't remove", w.registry.Types[comp]))
+			panic(fmt.Sprintf("entity does not have a component of type %v, can't remove", w.registry.Types[comp.id]))
 		}
 		mask.Set(comp, false)
 	}
@@ -950,16 +970,16 @@ func (w *World) setRelationArch(oldArch *archetype, oldArchLen uint32, comp ID,
 }
 
 func (w *World) checkRelation(arch *archetype, comp ID) {
-	if arch.node.Relation != int8(comp) {
+	if arch.node.Relation.id != comp.id {
 		w.relationError(arch, comp)
 	}
 }
 
 func (w *World) relationError(arch *archetype, comp ID) {
 	if !arch.HasComponent(comp) {
-		panic(fmt.Sprintf("entity does not have relation component %v", w.registry.Types[comp]))
+		panic(fmt.Sprintf("entity does not have relation component %v", w.registry.Types[comp.id]))
 	}
-	panic(fmt.Sprintf("not a relation component: %v", w.registry.Types[comp]))
+	panic(fmt.Sprintf("not a relation component: %v", w.registry.Types[comp.id]))
 }
 
 // Reset removes all entities and resources from the world.
@@ -1062,7 +1082,7 @@ func (w *World) Ids(entity Entity) []ID {
 
 // ComponentType returns the reflect.Type for a given component ID, as well as whether the ID is in use.
 func (w *World) ComponentType(id ID) (reflect.Type, bool) {
-	return w.registry.ComponentType(id)
+	return w.registry.ComponentType(id.id)
 }
 
 // SetListener sets a listener callback func(e *EntityEvent) for the world.
@@ -1203,7 +1223,7 @@ func (w *World) checkLocked() {
 }
 
 // Internal method to create new entities.
-func (w *World) newEntitiesNoNotify(count int, targetID int8, target Entity, comps ...ID) (*archetype, uint32) {
+func (w *World) newEntitiesNoNotify(count int, targetID ID, hasTarget bool, target Entity, comps ...ID) (*archetype, uint32) {
 	w.checkLocked()
 
 	if count < 1 {
@@ -1218,8 +1238,8 @@ func (w *World) newEntitiesNoNotify(count int, targetID int8, target Entity, com
 	if len(comps) > 0 {
 		arch = w.findOrCreateArchetype(arch, comps, nil, target)
 	}
-	if targetID >= 0 {
-		w.checkRelation(arch, uint8(targetID))
+	if hasTarget {
+		w.checkRelation(arch, targetID)
 		if !target.IsZero() {
 			w.targetEntities.Set(target.id, true)
 		}
@@ -1232,7 +1252,7 @@ func (w *World) newEntitiesNoNotify(count int, targetID int8, target Entity, com
 }
 
 // Internal method to create new entities with component values.
-func (w *World) newEntitiesWithNoNotify(count int, targetID int8, target Entity, ids []ID, comps ...Component) (*archetype, uint32) {
+func (w *World) newEntitiesWithNoNotify(count int, targetID ID, hasTarget bool, target Entity, ids []ID, comps ...Component) (*archetype, uint32) {
 	w.checkLocked()
 
 	if count < 1 {
@@ -1244,7 +1264,7 @@ func (w *World) newEntitiesWithNoNotify(count int, targetID int8, target Entity,
 	}
 
 	if len(comps) == 0 {
-		return w.newEntitiesNoNotify(count, targetID, target)
+		return w.newEntitiesNoNotify(count, targetID, hasTarget, target)
 	}
 
 	cnt := uint32(count)
@@ -1253,8 +1273,8 @@ func (w *World) newEntitiesWithNoNotify(count int, targetID int8, target Entity,
 	if len(comps) > 0 {
 		arch = w.findOrCreateArchetype(arch, ids, nil, target)
 	}
-	if targetID >= 0 {
-		w.checkRelation(arch, uint8(targetID))
+	if hasTarget {
+		w.checkRelation(arch, targetID)
 		if !target.IsZero() {
 			w.targetEntities.Set(target.id, true)
 		}
@@ -1341,34 +1361,37 @@ func (w *World) findOrCreateArchetype(start *archetype, add []ID, rem []ID, targ
 	curr := start.node
 	mask := start.Mask
 	relation := start.RelationComponent
+	hasRelation := start.HasRelationComponent
 	for _, id := range rem {
 		mask.Set(id, false)
 		if w.registry.IsRelation.Get(id) {
-			relation = -1
+			relation = ID{}
+			hasRelation = false
 		}
-		if next, ok := curr.TransitionRemove.Get(id); ok {
+		if next, ok := curr.TransitionRemove.Get(id.id); ok {
 			curr = next
 		} else {
-			next, _ := w.findOrCreateArchetypeSlow(mask, relation)
-			next.TransitionAdd.Set(id, curr)
-			curr.TransitionRemove.Set(id, next)
+			next, _ := w.findOrCreateArchetypeSlow(mask, relation, hasRelation)
+			next.TransitionAdd.Set(id.id, curr)
+			curr.TransitionRemove.Set(id.id, next)
 			curr = next
 		}
 	}
 	for _, id := range add {
 		mask.Set(id, true)
 		if w.registry.IsRelation.Get(id) {
-			if relation >= 0 {
+			if hasRelation {
 				panic("entity already has a relation component")
 			}
-			relation = int8(id)
+			relation = id
+			hasRelation = true
 		}
-		if next, ok := curr.TransitionAdd.Get(id); ok {
+		if next, ok := curr.TransitionAdd.Get(id.id); ok {
 			curr = next
 		} else {
-			next, _ := w.findOrCreateArchetypeSlow(mask, relation)
-			next.TransitionRemove.Set(id, curr)
-			curr.TransitionAdd.Set(id, next)
+			next, _ := w.findOrCreateArchetypeSlow(mask, relation, hasRelation)
+			next.TransitionRemove.Set(id.id, curr)
+			curr.TransitionAdd.Set(id.id, next)
 			curr = next
 		}
 	}
@@ -1381,11 +1404,11 @@ func (w *World) findOrCreateArchetype(start *archetype, add []ID, rem []ID, targ
 
 // Tries to find an archetype for a mask, when it can't be reached through the archetype graph.
 // Creates an archetype graph node.
-func (w *World) findOrCreateArchetypeSlow(mask Mask, relation int8) (*archNode, bool) {
+func (w *World) findOrCreateArchetypeSlow(mask Mask, relation ID, hasRelation bool) (*archNode, bool) {
 	if arch, ok := w.findArchetypeSlow(mask); ok {
 		return arch, false
 	}
-	return w.createArchetypeNode(mask, relation), true
+	return w.createArchetypeNode(mask, relation, hasRelation), true
 }
 
 // Searches for an archetype by a mask.
@@ -1402,16 +1425,16 @@ func (w *World) findArchetypeSlow(mask Mask) (*archNode, bool) {
 }
 
 // Creates a node in the archetype graph.
-func (w *World) createArchetypeNode(mask Mask, relation int8) *archNode {
+func (w *World) createArchetypeNode(mask Mask, relation ID, hasRelation bool) *archNode {
 	capInc := w.config.CapacityIncrement
-	if relation >= 0 {
+	if hasRelation {
 		capInc = w.config.RelationCapacityIncrement
 	}
 
 	types := maskToTypes(mask, &w.registry)
 
 	w.nodeData.Add(nodeData{})
-	w.nodes.Add(newArchNode(mask, w.nodeData.Get(w.nodeData.Len()-1), relation, capInc, types))
+	w.nodes.Add(newArchNode(mask, w.nodeData.Get(w.nodeData.Len()-1), relation, hasRelation, capInc, types))
 	nd := w.nodes.Get(w.nodes.Len() - 1)
 	w.relationNodes = append(w.relationNodes, nd)
 	w.nodePointers = append(w.nodePointers, nd)
@@ -1525,13 +1548,13 @@ func (w *World) componentID(tp reflect.Type) ID {
 			w.extendArchetypeLayouts(id + layoutChunkSize)
 		}
 	}
-	return id
+	return ID{id: id}
 }
 
 // resourceID returns the ID for a resource type, and registers it if not already registered.
 func (w *World) resourceID(tp reflect.Type) ResID {
 	id, _ := w.resources.registry.ComponentID(tp)
-	return id
+	return ResID{id: id}
 }
 
 // closeQuery closes a query and unlocks the world.
diff --git a/generic/compiled.go b/generic/compiled.go
index 968050c..0a13275 100644
--- a/generic/compiled.go
+++ b/generic/compiled.go
@@ -16,7 +16,7 @@ type compiledQuery struct {
 	cachedFilter   ecs.CachedFilter
 	filter         ecs.Filter
 	Ids            []ecs.ID
-	TargetComp     int8
+	TargetComp     ecs.ID
 	Target         ecs.Entity
 	HasTarget      bool
 	compiled       bool
@@ -25,9 +25,7 @@ type compiledQuery struct {
 }
 
 func newCompiledQuery() compiledQuery {
-	return compiledQuery{
-		TargetComp: -1,
-	}
+	return compiledQuery{}
 }
 
 // Compile compiles a generic filter.
@@ -44,12 +42,14 @@ func (q *compiledQuery) Compile(w *ecs.World, include, optional, exclude []Comp,
 
 	if targetType == nil {
 		q.filter = &q.maskFilter
-		q.TargetComp = -1
+		q.TargetComp = ecs.ID{}
+		q.HasTarget = false
 	} else {
 
 		targetID := ecs.TypeID(w, targetType)
 
-		q.TargetComp = int8(targetID)
+		q.TargetComp = targetID
+		q.HasTarget = true
 
 		if !q.maskFilter.Include.Get(targetID) {
 			panic(fmt.Sprintf("relation component %v not in filter", targetType))
diff --git a/generic/generate/map.go.txt b/generic/generate/map.go.txt
index d7858fd..e1b5671 100644
--- a/generic/generate/map.go.txt
+++ b/generic/generate/map.go.txt
@@ -14,7 +14,8 @@
 type Map{{ .Index }}{{ .TypesFull }} struct {
 	world *ecs.World
 	mask ecs.Mask
-	relation int8
+	relation ecs.ID
+	hasRelation bool
 	ids []ecs.ID
 	{{ .IDTypes }}
 }
@@ -29,9 +30,11 @@ func NewMap{{ .Index }}{{ .TypesFull }}(w *ecs.World, relation ...Comp) Map{{ .I
 	}
 	m.ids = []ecs.ID{ {{ .IDList }} }
 	m.mask = ecs.All(m.ids...)
-	m.relation = -1
+	m.relation = ecs.ID{}
+	m.hasRelation = false
 	if len(relation) > 0 {
-		m.relation = int8(ecs.TypeID(w, relation[0]))
+		m.relation = ecs.TypeID(w, relation[0])
+		m.hasRelation = true
 	}
 	return m
 }
@@ -60,7 +63,7 @@ func (m *Map{{ .Index }}{{ .Types }}) GetUnchecked(entity ecs.Entity) ({{ .Types
 //
 // See also [ecs.World.NewEntity].
 func (m *Map{{ .Index }}{{ .Types }}) New(target ...ecs.Entity) ecs.Entity {
-	return newEntity(m.world, m.ids, m.relation, target...)
+	return newEntity(m.world, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatch creates entities with the Map{{ .Index }}'s components.
@@ -69,7 +72,7 @@ func (m *Map{{ .Index }}{{ .Types }}) New(target ...ecs.Entity) ecs.Entity {
 //
 // See also [Map{{ .Index }}.NewBatchQ] and [ecs.Batch.NewBatch].
 func (m *Map{{ .Index }}{{ .Types }}) NewBatch(count int, target ...ecs.Entity) {
-	newBatch(m.world, count, m.ids, m.relation, target...)
+	newBatch(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatchQ creates entities with the Map{{ .Index }}'s components.
@@ -81,7 +84,7 @@ func (m *Map{{ .Index }}{{ .Types }}) NewBatch(count int, target ...ecs.Entity)
 //
 // See also [Map{{ .Index }}.NewBatch] and [ecs.Builder.NewBatchQ].
 func (m *Map{{ .Index }}{{ .Types }}) NewBatchQ(count int, target ...ecs.Entity) Query{{ .Index }}{{ .Types }} {
-	query := newQuery(m.world, count, m.ids, m.relation, target...)
+	query := newQuery(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 	return Query{{ .Index }}{{ .Types }}{
 		Query: query,
 		{{ .IDAssign2 }}
@@ -98,10 +101,10 @@ func (m *Map{{ .Index }}{{ .Types }}) NewWith({{ .Arguments }}, target ...ecs.En
 	if len(target) == 0 {
 		return m.world.NewEntityWith({{ .Components }})
 	}
-	if m.relation < 0 {
+	if !m.hasRelation {
 		panic("map has no relation defined")
 	}
-	return ecs.NewBuilderWith(m.world, {{ .Components }}).WithRelation(uint8(m.relation)).New(target[0])
+	return ecs.NewBuilderWith(m.world, {{ .Components }}).WithRelation(m.relation).New(target[0])
 }
 
 // Add the Map{{ .Index }}'s components to the given entity.
diff --git a/generic/generate/query.go.txt b/generic/generate/query.go.txt
index df008c3..3610790 100644
--- a/generic/generate/query.go.txt
+++ b/generic/generate/query.go.txt
@@ -114,6 +114,7 @@ func (q *Filter{{ .Index }}{{ .Types }}) Query(w *ecs.World, target ...ecs.Entit
 	return Query{{ .Index }}{{ .Types }}{
 		Query: w.Query(filter),
 		target: q.compiled.TargetComp,
+		hasTarget: q.compiled.HasTarget,
 		{{ .IDAssign }}
 	}
 }
@@ -152,7 +153,8 @@ func (q *Filter{{ .Index }}{{ .Types }}) Unregister(w *ecs.World) {
 type Query{{ .Index }}{{ .TypesFull }} struct {
 	ecs.Query
 	{{ .IDTypes }}
-	target int8
+	target ecs.ID
+	hasTarget bool
 }
 
 {{if .ReturnAll}}
@@ -170,8 +172,8 @@ func (q *Query{{ .Index }}{{ .Types }}) Get() ({{ .TypesReturn }}) {
 // Panics if the underlying [Filter{{ .Index }}] was not prepared for relations
 // using [Filter{{ .Index }}.WithRelation].
 func (q *Query{{ .Index }}{{ .Types }}) Relation() ecs.Entity {
-	if q.target < 0 {
+	if !q.hasTarget {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(ecs.ID(q.target))
+	return q.Query.Relation(q.target)
 }
diff --git a/generic/map_generated.go b/generic/map_generated.go
index 943a566..feb4d24 100644
--- a/generic/map_generated.go
+++ b/generic/map_generated.go
@@ -19,11 +19,12 @@ import (
 //	entity := mapper.NewEntity()
 //	a := mapper.Get(entity)
 type Map1[A any] struct {
-	world    *ecs.World
-	mask     ecs.Mask
-	relation int8
-	ids      []ecs.ID
-	id0      ecs.ID
+	world       *ecs.World
+	mask        ecs.Mask
+	relation    ecs.ID
+	hasRelation bool
+	ids         []ecs.ID
+	id0         ecs.ID
 }
 
 // NewMap1 creates a new Map1 object.
@@ -36,9 +37,11 @@ func NewMap1[A any](w *ecs.World, relation ...Comp) Map1[A] {
 	}
 	m.ids = []ecs.ID{m.id0}
 	m.mask = ecs.All(m.ids...)
-	m.relation = -1
+	m.relation = ecs.ID{}
+	m.hasRelation = false
 	if len(relation) > 0 {
-		m.relation = int8(ecs.TypeID(w, relation[0]))
+		m.relation = ecs.TypeID(w, relation[0])
+		m.hasRelation = true
 	}
 	return m
 }
@@ -67,7 +70,7 @@ func (m *Map1[A]) GetUnchecked(entity ecs.Entity) *A {
 //
 // See also [ecs.World.NewEntity].
 func (m *Map1[A]) New(target ...ecs.Entity) ecs.Entity {
-	return newEntity(m.world, m.ids, m.relation, target...)
+	return newEntity(m.world, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatch creates entities with the Map1's components.
@@ -76,7 +79,7 @@ func (m *Map1[A]) New(target ...ecs.Entity) ecs.Entity {
 //
 // See also [Map1.NewBatchQ] and [ecs.Batch.NewBatch].
 func (m *Map1[A]) NewBatch(count int, target ...ecs.Entity) {
-	newBatch(m.world, count, m.ids, m.relation, target...)
+	newBatch(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatchQ creates entities with the Map1's components.
@@ -88,7 +91,7 @@ func (m *Map1[A]) NewBatch(count int, target ...ecs.Entity) {
 //
 // See also [Map1.NewBatch] and [ecs.Builder.NewBatchQ].
 func (m *Map1[A]) NewBatchQ(count int, target ...ecs.Entity) Query1[A] {
-	query := newQuery(m.world, count, m.ids, m.relation, target...)
+	query := newQuery(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 	return Query1[A]{
 		Query: query,
 		id0:   m.id0,
@@ -104,10 +107,10 @@ func (m *Map1[A]) NewWith(a *A, target ...ecs.Entity) ecs.Entity {
 	if len(target) == 0 {
 		return m.world.NewEntityWith(ecs.Component{ID: m.id0, Comp: a})
 	}
-	if m.relation < 0 {
+	if !m.hasRelation {
 		panic("map has no relation defined")
 	}
-	return ecs.NewBuilderWith(m.world, ecs.Component{ID: m.id0, Comp: a}).WithRelation(uint8(m.relation)).New(target[0])
+	return ecs.NewBuilderWith(m.world, ecs.Component{ID: m.id0, Comp: a}).WithRelation(m.relation).New(target[0])
 }
 
 // Add the Map1's components to the given entity.
@@ -162,12 +165,13 @@ func (m *Map1[A]) RemoveEntities(exclusive bool) int {
 //	entity := mapper.NewEntity()
 //	a, b := mapper.Get(entity)
 type Map2[A any, B any] struct {
-	world    *ecs.World
-	mask     ecs.Mask
-	relation int8
-	ids      []ecs.ID
-	id0      ecs.ID
-	id1      ecs.ID
+	world       *ecs.World
+	mask        ecs.Mask
+	relation    ecs.ID
+	hasRelation bool
+	ids         []ecs.ID
+	id0         ecs.ID
+	id1         ecs.ID
 }
 
 // NewMap2 creates a new Map2 object.
@@ -181,9 +185,11 @@ func NewMap2[A any, B any](w *ecs.World, relation ...Comp) Map2[A, B] {
 	}
 	m.ids = []ecs.ID{m.id0, m.id1}
 	m.mask = ecs.All(m.ids...)
-	m.relation = -1
+	m.relation = ecs.ID{}
+	m.hasRelation = false
 	if len(relation) > 0 {
-		m.relation = int8(ecs.TypeID(w, relation[0]))
+		m.relation = ecs.TypeID(w, relation[0])
+		m.hasRelation = true
 	}
 	return m
 }
@@ -214,7 +220,7 @@ func (m *Map2[A, B]) GetUnchecked(entity ecs.Entity) (*A, *B) {
 //
 // See also [ecs.World.NewEntity].
 func (m *Map2[A, B]) New(target ...ecs.Entity) ecs.Entity {
-	return newEntity(m.world, m.ids, m.relation, target...)
+	return newEntity(m.world, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatch creates entities with the Map2's components.
@@ -223,7 +229,7 @@ func (m *Map2[A, B]) New(target ...ecs.Entity) ecs.Entity {
 //
 // See also [Map2.NewBatchQ] and [ecs.Batch.NewBatch].
 func (m *Map2[A, B]) NewBatch(count int, target ...ecs.Entity) {
-	newBatch(m.world, count, m.ids, m.relation, target...)
+	newBatch(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatchQ creates entities with the Map2's components.
@@ -235,7 +241,7 @@ func (m *Map2[A, B]) NewBatch(count int, target ...ecs.Entity) {
 //
 // See also [Map2.NewBatch] and [ecs.Builder.NewBatchQ].
 func (m *Map2[A, B]) NewBatchQ(count int, target ...ecs.Entity) Query2[A, B] {
-	query := newQuery(m.world, count, m.ids, m.relation, target...)
+	query := newQuery(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 	return Query2[A, B]{
 		Query: query,
 		id0:   m.id0,
@@ -254,12 +260,12 @@ func (m *Map2[A, B]) NewWith(a *A, b *B, target ...ecs.Entity) ecs.Entity {
 			ecs.Component{ID: m.id1, Comp: b},
 		)
 	}
-	if m.relation < 0 {
+	if !m.hasRelation {
 		panic("map has no relation defined")
 	}
 	return ecs.NewBuilderWith(m.world, ecs.Component{ID: m.id0, Comp: a},
 		ecs.Component{ID: m.id1, Comp: b},
-	).WithRelation(uint8(m.relation)).New(target[0])
+	).WithRelation(m.relation).New(target[0])
 }
 
 // Add the Map2's components to the given entity.
@@ -315,13 +321,14 @@ func (m *Map2[A, B]) RemoveEntities(exclusive bool) int {
 //	entity := mapper.NewEntity()
 //	a, b, c := mapper.Get(entity)
 type Map3[A any, B any, C any] struct {
-	world    *ecs.World
-	mask     ecs.Mask
-	relation int8
-	ids      []ecs.ID
-	id0      ecs.ID
-	id1      ecs.ID
-	id2      ecs.ID
+	world       *ecs.World
+	mask        ecs.Mask
+	relation    ecs.ID
+	hasRelation bool
+	ids         []ecs.ID
+	id0         ecs.ID
+	id1         ecs.ID
+	id2         ecs.ID
 }
 
 // NewMap3 creates a new Map3 object.
@@ -336,9 +343,11 @@ func NewMap3[A any, B any, C any](w *ecs.World, relation ...Comp) Map3[A, B, C]
 	}
 	m.ids = []ecs.ID{m.id0, m.id1, m.id2}
 	m.mask = ecs.All(m.ids...)
-	m.relation = -1
+	m.relation = ecs.ID{}
+	m.hasRelation = false
 	if len(relation) > 0 {
-		m.relation = int8(ecs.TypeID(w, relation[0]))
+		m.relation = ecs.TypeID(w, relation[0])
+		m.hasRelation = true
 	}
 	return m
 }
@@ -371,7 +380,7 @@ func (m *Map3[A, B, C]) GetUnchecked(entity ecs.Entity) (*A, *B, *C) {
 //
 // See also [ecs.World.NewEntity].
 func (m *Map3[A, B, C]) New(target ...ecs.Entity) ecs.Entity {
-	return newEntity(m.world, m.ids, m.relation, target...)
+	return newEntity(m.world, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatch creates entities with the Map3's components.
@@ -380,7 +389,7 @@ func (m *Map3[A, B, C]) New(target ...ecs.Entity) ecs.Entity {
 //
 // See also [Map3.NewBatchQ] and [ecs.Batch.NewBatch].
 func (m *Map3[A, B, C]) NewBatch(count int, target ...ecs.Entity) {
-	newBatch(m.world, count, m.ids, m.relation, target...)
+	newBatch(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatchQ creates entities with the Map3's components.
@@ -392,7 +401,7 @@ func (m *Map3[A, B, C]) NewBatch(count int, target ...ecs.Entity) {
 //
 // See also [Map3.NewBatch] and [ecs.Builder.NewBatchQ].
 func (m *Map3[A, B, C]) NewBatchQ(count int, target ...ecs.Entity) Query3[A, B, C] {
-	query := newQuery(m.world, count, m.ids, m.relation, target...)
+	query := newQuery(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 	return Query3[A, B, C]{
 		Query: query,
 		id0:   m.id0,
@@ -413,13 +422,13 @@ func (m *Map3[A, B, C]) NewWith(a *A, b *B, c *C, target ...ecs.Entity) ecs.Enti
 			ecs.Component{ID: m.id2, Comp: c},
 		)
 	}
-	if m.relation < 0 {
+	if !m.hasRelation {
 		panic("map has no relation defined")
 	}
 	return ecs.NewBuilderWith(m.world, ecs.Component{ID: m.id0, Comp: a},
 		ecs.Component{ID: m.id1, Comp: b},
 		ecs.Component{ID: m.id2, Comp: c},
-	).WithRelation(uint8(m.relation)).New(target[0])
+	).WithRelation(m.relation).New(target[0])
 }
 
 // Add the Map3's components to the given entity.
@@ -476,14 +485,15 @@ func (m *Map3[A, B, C]) RemoveEntities(exclusive bool) int {
 //	entity := mapper.NewEntity()
 //	a, b, c, d := mapper.Get(entity)
 type Map4[A any, B any, C any, D any] struct {
-	world    *ecs.World
-	mask     ecs.Mask
-	relation int8
-	ids      []ecs.ID
-	id0      ecs.ID
-	id1      ecs.ID
-	id2      ecs.ID
-	id3      ecs.ID
+	world       *ecs.World
+	mask        ecs.Mask
+	relation    ecs.ID
+	hasRelation bool
+	ids         []ecs.ID
+	id0         ecs.ID
+	id1         ecs.ID
+	id2         ecs.ID
+	id3         ecs.ID
 }
 
 // NewMap4 creates a new Map4 object.
@@ -499,9 +509,11 @@ func NewMap4[A any, B any, C any, D any](w *ecs.World, relation ...Comp) Map4[A,
 	}
 	m.ids = []ecs.ID{m.id0, m.id1, m.id2, m.id3}
 	m.mask = ecs.All(m.ids...)
-	m.relation = -1
+	m.relation = ecs.ID{}
+	m.hasRelation = false
 	if len(relation) > 0 {
-		m.relation = int8(ecs.TypeID(w, relation[0]))
+		m.relation = ecs.TypeID(w, relation[0])
+		m.hasRelation = true
 	}
 	return m
 }
@@ -536,7 +548,7 @@ func (m *Map4[A, B, C, D]) GetUnchecked(entity ecs.Entity) (*A, *B, *C, *D) {
 //
 // See also [ecs.World.NewEntity].
 func (m *Map4[A, B, C, D]) New(target ...ecs.Entity) ecs.Entity {
-	return newEntity(m.world, m.ids, m.relation, target...)
+	return newEntity(m.world, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatch creates entities with the Map4's components.
@@ -545,7 +557,7 @@ func (m *Map4[A, B, C, D]) New(target ...ecs.Entity) ecs.Entity {
 //
 // See also [Map4.NewBatchQ] and [ecs.Batch.NewBatch].
 func (m *Map4[A, B, C, D]) NewBatch(count int, target ...ecs.Entity) {
-	newBatch(m.world, count, m.ids, m.relation, target...)
+	newBatch(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatchQ creates entities with the Map4's components.
@@ -557,7 +569,7 @@ func (m *Map4[A, B, C, D]) NewBatch(count int, target ...ecs.Entity) {
 //
 // See also [Map4.NewBatch] and [ecs.Builder.NewBatchQ].
 func (m *Map4[A, B, C, D]) NewBatchQ(count int, target ...ecs.Entity) Query4[A, B, C, D] {
-	query := newQuery(m.world, count, m.ids, m.relation, target...)
+	query := newQuery(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 	return Query4[A, B, C, D]{
 		Query: query,
 		id0:   m.id0,
@@ -580,14 +592,14 @@ func (m *Map4[A, B, C, D]) NewWith(a *A, b *B, c *C, d *D, target ...ecs.Entity)
 			ecs.Component{ID: m.id3, Comp: d},
 		)
 	}
-	if m.relation < 0 {
+	if !m.hasRelation {
 		panic("map has no relation defined")
 	}
 	return ecs.NewBuilderWith(m.world, ecs.Component{ID: m.id0, Comp: a},
 		ecs.Component{ID: m.id1, Comp: b},
 		ecs.Component{ID: m.id2, Comp: c},
 		ecs.Component{ID: m.id3, Comp: d},
-	).WithRelation(uint8(m.relation)).New(target[0])
+	).WithRelation(m.relation).New(target[0])
 }
 
 // Add the Map4's components to the given entity.
@@ -645,15 +657,16 @@ func (m *Map4[A, B, C, D]) RemoveEntities(exclusive bool) int {
 //	entity := mapper.NewEntity()
 //	a, b, c, d, e := mapper.Get(entity)
 type Map5[A any, B any, C any, D any, E any] struct {
-	world    *ecs.World
-	mask     ecs.Mask
-	relation int8
-	ids      []ecs.ID
-	id0      ecs.ID
-	id1      ecs.ID
-	id2      ecs.ID
-	id3      ecs.ID
-	id4      ecs.ID
+	world       *ecs.World
+	mask        ecs.Mask
+	relation    ecs.ID
+	hasRelation bool
+	ids         []ecs.ID
+	id0         ecs.ID
+	id1         ecs.ID
+	id2         ecs.ID
+	id3         ecs.ID
+	id4         ecs.ID
 }
 
 // NewMap5 creates a new Map5 object.
@@ -670,9 +683,11 @@ func NewMap5[A any, B any, C any, D any, E any](w *ecs.World, relation ...Comp)
 	}
 	m.ids = []ecs.ID{m.id0, m.id1, m.id2, m.id3, m.id4}
 	m.mask = ecs.All(m.ids...)
-	m.relation = -1
+	m.relation = ecs.ID{}
+	m.hasRelation = false
 	if len(relation) > 0 {
-		m.relation = int8(ecs.TypeID(w, relation[0]))
+		m.relation = ecs.TypeID(w, relation[0])
+		m.hasRelation = true
 	}
 	return m
 }
@@ -709,7 +724,7 @@ func (m *Map5[A, B, C, D, E]) GetUnchecked(entity ecs.Entity) (*A, *B, *C, *D, *
 //
 // See also [ecs.World.NewEntity].
 func (m *Map5[A, B, C, D, E]) New(target ...ecs.Entity) ecs.Entity {
-	return newEntity(m.world, m.ids, m.relation, target...)
+	return newEntity(m.world, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatch creates entities with the Map5's components.
@@ -718,7 +733,7 @@ func (m *Map5[A, B, C, D, E]) New(target ...ecs.Entity) ecs.Entity {
 //
 // See also [Map5.NewBatchQ] and [ecs.Batch.NewBatch].
 func (m *Map5[A, B, C, D, E]) NewBatch(count int, target ...ecs.Entity) {
-	newBatch(m.world, count, m.ids, m.relation, target...)
+	newBatch(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatchQ creates entities with the Map5's components.
@@ -730,7 +745,7 @@ func (m *Map5[A, B, C, D, E]) NewBatch(count int, target ...ecs.Entity) {
 //
 // See also [Map5.NewBatch] and [ecs.Builder.NewBatchQ].
 func (m *Map5[A, B, C, D, E]) NewBatchQ(count int, target ...ecs.Entity) Query5[A, B, C, D, E] {
-	query := newQuery(m.world, count, m.ids, m.relation, target...)
+	query := newQuery(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 	return Query5[A, B, C, D, E]{
 		Query: query,
 		id0:   m.id0,
@@ -755,7 +770,7 @@ func (m *Map5[A, B, C, D, E]) NewWith(a *A, b *B, c *C, d *D, e *E, target ...ec
 			ecs.Component{ID: m.id4, Comp: e},
 		)
 	}
-	if m.relation < 0 {
+	if !m.hasRelation {
 		panic("map has no relation defined")
 	}
 	return ecs.NewBuilderWith(m.world, ecs.Component{ID: m.id0, Comp: a},
@@ -763,7 +778,7 @@ func (m *Map5[A, B, C, D, E]) NewWith(a *A, b *B, c *C, d *D, e *E, target ...ec
 		ecs.Component{ID: m.id2, Comp: c},
 		ecs.Component{ID: m.id3, Comp: d},
 		ecs.Component{ID: m.id4, Comp: e},
-	).WithRelation(uint8(m.relation)).New(target[0])
+	).WithRelation(m.relation).New(target[0])
 }
 
 // Add the Map5's components to the given entity.
@@ -822,16 +837,17 @@ func (m *Map5[A, B, C, D, E]) RemoveEntities(exclusive bool) int {
 //	entity := mapper.NewEntity()
 //	a, b, c, d, e, f := mapper.Get(entity)
 type Map6[A any, B any, C any, D any, E any, F any] struct {
-	world    *ecs.World
-	mask     ecs.Mask
-	relation int8
-	ids      []ecs.ID
-	id0      ecs.ID
-	id1      ecs.ID
-	id2      ecs.ID
-	id3      ecs.ID
-	id4      ecs.ID
-	id5      ecs.ID
+	world       *ecs.World
+	mask        ecs.Mask
+	relation    ecs.ID
+	hasRelation bool
+	ids         []ecs.ID
+	id0         ecs.ID
+	id1         ecs.ID
+	id2         ecs.ID
+	id3         ecs.ID
+	id4         ecs.ID
+	id5         ecs.ID
 }
 
 // NewMap6 creates a new Map6 object.
@@ -849,9 +865,11 @@ func NewMap6[A any, B any, C any, D any, E any, F any](w *ecs.World, relation ..
 	}
 	m.ids = []ecs.ID{m.id0, m.id1, m.id2, m.id3, m.id4, m.id5}
 	m.mask = ecs.All(m.ids...)
-	m.relation = -1
+	m.relation = ecs.ID{}
+	m.hasRelation = false
 	if len(relation) > 0 {
-		m.relation = int8(ecs.TypeID(w, relation[0]))
+		m.relation = ecs.TypeID(w, relation[0])
+		m.hasRelation = true
 	}
 	return m
 }
@@ -890,7 +908,7 @@ func (m *Map6[A, B, C, D, E, F]) GetUnchecked(entity ecs.Entity) (*A, *B, *C, *D
 //
 // See also [ecs.World.NewEntity].
 func (m *Map6[A, B, C, D, E, F]) New(target ...ecs.Entity) ecs.Entity {
-	return newEntity(m.world, m.ids, m.relation, target...)
+	return newEntity(m.world, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatch creates entities with the Map6's components.
@@ -899,7 +917,7 @@ func (m *Map6[A, B, C, D, E, F]) New(target ...ecs.Entity) ecs.Entity {
 //
 // See also [Map6.NewBatchQ] and [ecs.Batch.NewBatch].
 func (m *Map6[A, B, C, D, E, F]) NewBatch(count int, target ...ecs.Entity) {
-	newBatch(m.world, count, m.ids, m.relation, target...)
+	newBatch(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatchQ creates entities with the Map6's components.
@@ -911,7 +929,7 @@ func (m *Map6[A, B, C, D, E, F]) NewBatch(count int, target ...ecs.Entity) {
 //
 // See also [Map6.NewBatch] and [ecs.Builder.NewBatchQ].
 func (m *Map6[A, B, C, D, E, F]) NewBatchQ(count int, target ...ecs.Entity) Query6[A, B, C, D, E, F] {
-	query := newQuery(m.world, count, m.ids, m.relation, target...)
+	query := newQuery(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 	return Query6[A, B, C, D, E, F]{
 		Query: query,
 		id0:   m.id0,
@@ -938,7 +956,7 @@ func (m *Map6[A, B, C, D, E, F]) NewWith(a *A, b *B, c *C, d *D, e *E, f *F, tar
 			ecs.Component{ID: m.id5, Comp: f},
 		)
 	}
-	if m.relation < 0 {
+	if !m.hasRelation {
 		panic("map has no relation defined")
 	}
 	return ecs.NewBuilderWith(m.world, ecs.Component{ID: m.id0, Comp: a},
@@ -947,7 +965,7 @@ func (m *Map6[A, B, C, D, E, F]) NewWith(a *A, b *B, c *C, d *D, e *E, f *F, tar
 		ecs.Component{ID: m.id3, Comp: d},
 		ecs.Component{ID: m.id4, Comp: e},
 		ecs.Component{ID: m.id5, Comp: f},
-	).WithRelation(uint8(m.relation)).New(target[0])
+	).WithRelation(m.relation).New(target[0])
 }
 
 // Add the Map6's components to the given entity.
@@ -1007,17 +1025,18 @@ func (m *Map6[A, B, C, D, E, F]) RemoveEntities(exclusive bool) int {
 //	entity := mapper.NewEntity()
 //	a, b, c, d, e, f, g := mapper.Get(entity)
 type Map7[A any, B any, C any, D any, E any, F any, G any] struct {
-	world    *ecs.World
-	mask     ecs.Mask
-	relation int8
-	ids      []ecs.ID
-	id0      ecs.ID
-	id1      ecs.ID
-	id2      ecs.ID
-	id3      ecs.ID
-	id4      ecs.ID
-	id5      ecs.ID
-	id6      ecs.ID
+	world       *ecs.World
+	mask        ecs.Mask
+	relation    ecs.ID
+	hasRelation bool
+	ids         []ecs.ID
+	id0         ecs.ID
+	id1         ecs.ID
+	id2         ecs.ID
+	id3         ecs.ID
+	id4         ecs.ID
+	id5         ecs.ID
+	id6         ecs.ID
 }
 
 // NewMap7 creates a new Map7 object.
@@ -1036,9 +1055,11 @@ func NewMap7[A any, B any, C any, D any, E any, F any, G any](w *ecs.World, rela
 	}
 	m.ids = []ecs.ID{m.id0, m.id1, m.id2, m.id3, m.id4, m.id5, m.id6}
 	m.mask = ecs.All(m.ids...)
-	m.relation = -1
+	m.relation = ecs.ID{}
+	m.hasRelation = false
 	if len(relation) > 0 {
-		m.relation = int8(ecs.TypeID(w, relation[0]))
+		m.relation = ecs.TypeID(w, relation[0])
+		m.hasRelation = true
 	}
 	return m
 }
@@ -1079,7 +1100,7 @@ func (m *Map7[A, B, C, D, E, F, G]) GetUnchecked(entity ecs.Entity) (*A, *B, *C,
 //
 // See also [ecs.World.NewEntity].
 func (m *Map7[A, B, C, D, E, F, G]) New(target ...ecs.Entity) ecs.Entity {
-	return newEntity(m.world, m.ids, m.relation, target...)
+	return newEntity(m.world, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatch creates entities with the Map7's components.
@@ -1088,7 +1109,7 @@ func (m *Map7[A, B, C, D, E, F, G]) New(target ...ecs.Entity) ecs.Entity {
 //
 // See also [Map7.NewBatchQ] and [ecs.Batch.NewBatch].
 func (m *Map7[A, B, C, D, E, F, G]) NewBatch(count int, target ...ecs.Entity) {
-	newBatch(m.world, count, m.ids, m.relation, target...)
+	newBatch(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatchQ creates entities with the Map7's components.
@@ -1100,7 +1121,7 @@ func (m *Map7[A, B, C, D, E, F, G]) NewBatch(count int, target ...ecs.Entity) {
 //
 // See also [Map7.NewBatch] and [ecs.Builder.NewBatchQ].
 func (m *Map7[A, B, C, D, E, F, G]) NewBatchQ(count int, target ...ecs.Entity) Query7[A, B, C, D, E, F, G] {
-	query := newQuery(m.world, count, m.ids, m.relation, target...)
+	query := newQuery(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 	return Query7[A, B, C, D, E, F, G]{
 		Query: query,
 		id0:   m.id0,
@@ -1129,7 +1150,7 @@ func (m *Map7[A, B, C, D, E, F, G]) NewWith(a *A, b *B, c *C, d *D, e *E, f *F,
 			ecs.Component{ID: m.id6, Comp: g},
 		)
 	}
-	if m.relation < 0 {
+	if !m.hasRelation {
 		panic("map has no relation defined")
 	}
 	return ecs.NewBuilderWith(m.world, ecs.Component{ID: m.id0, Comp: a},
@@ -1139,7 +1160,7 @@ func (m *Map7[A, B, C, D, E, F, G]) NewWith(a *A, b *B, c *C, d *D, e *E, f *F,
 		ecs.Component{ID: m.id4, Comp: e},
 		ecs.Component{ID: m.id5, Comp: f},
 		ecs.Component{ID: m.id6, Comp: g},
-	).WithRelation(uint8(m.relation)).New(target[0])
+	).WithRelation(m.relation).New(target[0])
 }
 
 // Add the Map7's components to the given entity.
@@ -1200,18 +1221,19 @@ func (m *Map7[A, B, C, D, E, F, G]) RemoveEntities(exclusive bool) int {
 //	entity := mapper.NewEntity()
 //	a, b, c, d, e, f, g, h := mapper.Get(entity)
 type Map8[A any, B any, C any, D any, E any, F any, G any, H any] struct {
-	world    *ecs.World
-	mask     ecs.Mask
-	relation int8
-	ids      []ecs.ID
-	id0      ecs.ID
-	id1      ecs.ID
-	id2      ecs.ID
-	id3      ecs.ID
-	id4      ecs.ID
-	id5      ecs.ID
-	id6      ecs.ID
-	id7      ecs.ID
+	world       *ecs.World
+	mask        ecs.Mask
+	relation    ecs.ID
+	hasRelation bool
+	ids         []ecs.ID
+	id0         ecs.ID
+	id1         ecs.ID
+	id2         ecs.ID
+	id3         ecs.ID
+	id4         ecs.ID
+	id5         ecs.ID
+	id6         ecs.ID
+	id7         ecs.ID
 }
 
 // NewMap8 creates a new Map8 object.
@@ -1231,9 +1253,11 @@ func NewMap8[A any, B any, C any, D any, E any, F any, G any, H any](w *ecs.Worl
 	}
 	m.ids = []ecs.ID{m.id0, m.id1, m.id2, m.id3, m.id4, m.id5, m.id6, m.id7}
 	m.mask = ecs.All(m.ids...)
-	m.relation = -1
+	m.relation = ecs.ID{}
+	m.hasRelation = false
 	if len(relation) > 0 {
-		m.relation = int8(ecs.TypeID(w, relation[0]))
+		m.relation = ecs.TypeID(w, relation[0])
+		m.hasRelation = true
 	}
 	return m
 }
@@ -1276,7 +1300,7 @@ func (m *Map8[A, B, C, D, E, F, G, H]) GetUnchecked(entity ecs.Entity) (*A, *B,
 //
 // See also [ecs.World.NewEntity].
 func (m *Map8[A, B, C, D, E, F, G, H]) New(target ...ecs.Entity) ecs.Entity {
-	return newEntity(m.world, m.ids, m.relation, target...)
+	return newEntity(m.world, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatch creates entities with the Map8's components.
@@ -1285,7 +1309,7 @@ func (m *Map8[A, B, C, D, E, F, G, H]) New(target ...ecs.Entity) ecs.Entity {
 //
 // See also [Map8.NewBatchQ] and [ecs.Batch.NewBatch].
 func (m *Map8[A, B, C, D, E, F, G, H]) NewBatch(count int, target ...ecs.Entity) {
-	newBatch(m.world, count, m.ids, m.relation, target...)
+	newBatch(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatchQ creates entities with the Map8's components.
@@ -1297,7 +1321,7 @@ func (m *Map8[A, B, C, D, E, F, G, H]) NewBatch(count int, target ...ecs.Entity)
 //
 // See also [Map8.NewBatch] and [ecs.Builder.NewBatchQ].
 func (m *Map8[A, B, C, D, E, F, G, H]) NewBatchQ(count int, target ...ecs.Entity) Query8[A, B, C, D, E, F, G, H] {
-	query := newQuery(m.world, count, m.ids, m.relation, target...)
+	query := newQuery(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 	return Query8[A, B, C, D, E, F, G, H]{
 		Query: query,
 		id0:   m.id0,
@@ -1328,7 +1352,7 @@ func (m *Map8[A, B, C, D, E, F, G, H]) NewWith(a *A, b *B, c *C, d *D, e *E, f *
 			ecs.Component{ID: m.id7, Comp: h},
 		)
 	}
-	if m.relation < 0 {
+	if !m.hasRelation {
 		panic("map has no relation defined")
 	}
 	return ecs.NewBuilderWith(m.world, ecs.Component{ID: m.id0, Comp: a},
@@ -1339,7 +1363,7 @@ func (m *Map8[A, B, C, D, E, F, G, H]) NewWith(a *A, b *B, c *C, d *D, e *E, f *
 		ecs.Component{ID: m.id5, Comp: f},
 		ecs.Component{ID: m.id6, Comp: g},
 		ecs.Component{ID: m.id7, Comp: h},
-	).WithRelation(uint8(m.relation)).New(target[0])
+	).WithRelation(m.relation).New(target[0])
 }
 
 // Add the Map8's components to the given entity.
@@ -1401,19 +1425,20 @@ func (m *Map8[A, B, C, D, E, F, G, H]) RemoveEntities(exclusive bool) int {
 //	entity := mapper.NewEntity()
 //	a, b, c, d, e, f, g, h, i := mapper.Get(entity)
 type Map9[A any, B any, C any, D any, E any, F any, G any, H any, I any] struct {
-	world    *ecs.World
-	mask     ecs.Mask
-	relation int8
-	ids      []ecs.ID
-	id0      ecs.ID
-	id1      ecs.ID
-	id2      ecs.ID
-	id3      ecs.ID
-	id4      ecs.ID
-	id5      ecs.ID
-	id6      ecs.ID
-	id7      ecs.ID
-	id8      ecs.ID
+	world       *ecs.World
+	mask        ecs.Mask
+	relation    ecs.ID
+	hasRelation bool
+	ids         []ecs.ID
+	id0         ecs.ID
+	id1         ecs.ID
+	id2         ecs.ID
+	id3         ecs.ID
+	id4         ecs.ID
+	id5         ecs.ID
+	id6         ecs.ID
+	id7         ecs.ID
+	id8         ecs.ID
 }
 
 // NewMap9 creates a new Map9 object.
@@ -1434,9 +1459,11 @@ func NewMap9[A any, B any, C any, D any, E any, F any, G any, H any, I any](w *e
 	}
 	m.ids = []ecs.ID{m.id0, m.id1, m.id2, m.id3, m.id4, m.id5, m.id6, m.id7, m.id8}
 	m.mask = ecs.All(m.ids...)
-	m.relation = -1
+	m.relation = ecs.ID{}
+	m.hasRelation = false
 	if len(relation) > 0 {
-		m.relation = int8(ecs.TypeID(w, relation[0]))
+		m.relation = ecs.TypeID(w, relation[0])
+		m.hasRelation = true
 	}
 	return m
 }
@@ -1481,7 +1508,7 @@ func (m *Map9[A, B, C, D, E, F, G, H, I]) GetUnchecked(entity ecs.Entity) (*A, *
 //
 // See also [ecs.World.NewEntity].
 func (m *Map9[A, B, C, D, E, F, G, H, I]) New(target ...ecs.Entity) ecs.Entity {
-	return newEntity(m.world, m.ids, m.relation, target...)
+	return newEntity(m.world, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatch creates entities with the Map9's components.
@@ -1490,7 +1517,7 @@ func (m *Map9[A, B, C, D, E, F, G, H, I]) New(target ...ecs.Entity) ecs.Entity {
 //
 // See also [Map9.NewBatchQ] and [ecs.Batch.NewBatch].
 func (m *Map9[A, B, C, D, E, F, G, H, I]) NewBatch(count int, target ...ecs.Entity) {
-	newBatch(m.world, count, m.ids, m.relation, target...)
+	newBatch(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatchQ creates entities with the Map9's components.
@@ -1502,7 +1529,7 @@ func (m *Map9[A, B, C, D, E, F, G, H, I]) NewBatch(count int, target ...ecs.Enti
 //
 // See also [Map9.NewBatch] and [ecs.Builder.NewBatchQ].
 func (m *Map9[A, B, C, D, E, F, G, H, I]) NewBatchQ(count int, target ...ecs.Entity) Query9[A, B, C, D, E, F, G, H, I] {
-	query := newQuery(m.world, count, m.ids, m.relation, target...)
+	query := newQuery(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 	return Query9[A, B, C, D, E, F, G, H, I]{
 		Query: query,
 		id0:   m.id0,
@@ -1535,7 +1562,7 @@ func (m *Map9[A, B, C, D, E, F, G, H, I]) NewWith(a *A, b *B, c *C, d *D, e *E,
 			ecs.Component{ID: m.id8, Comp: i},
 		)
 	}
-	if m.relation < 0 {
+	if !m.hasRelation {
 		panic("map has no relation defined")
 	}
 	return ecs.NewBuilderWith(m.world, ecs.Component{ID: m.id0, Comp: a},
@@ -1547,7 +1574,7 @@ func (m *Map9[A, B, C, D, E, F, G, H, I]) NewWith(a *A, b *B, c *C, d *D, e *E,
 		ecs.Component{ID: m.id6, Comp: g},
 		ecs.Component{ID: m.id7, Comp: h},
 		ecs.Component{ID: m.id8, Comp: i},
-	).WithRelation(uint8(m.relation)).New(target[0])
+	).WithRelation(m.relation).New(target[0])
 }
 
 // Add the Map9's components to the given entity.
@@ -1610,20 +1637,21 @@ func (m *Map9[A, B, C, D, E, F, G, H, I]) RemoveEntities(exclusive bool) int {
 //	entity := mapper.NewEntity()
 //	a, b, c, d, e, f, g, h, i, j := mapper.Get(entity)
 type Map10[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any] struct {
-	world    *ecs.World
-	mask     ecs.Mask
-	relation int8
-	ids      []ecs.ID
-	id0      ecs.ID
-	id1      ecs.ID
-	id2      ecs.ID
-	id3      ecs.ID
-	id4      ecs.ID
-	id5      ecs.ID
-	id6      ecs.ID
-	id7      ecs.ID
-	id8      ecs.ID
-	id9      ecs.ID
+	world       *ecs.World
+	mask        ecs.Mask
+	relation    ecs.ID
+	hasRelation bool
+	ids         []ecs.ID
+	id0         ecs.ID
+	id1         ecs.ID
+	id2         ecs.ID
+	id3         ecs.ID
+	id4         ecs.ID
+	id5         ecs.ID
+	id6         ecs.ID
+	id7         ecs.ID
+	id8         ecs.ID
+	id9         ecs.ID
 }
 
 // NewMap10 creates a new Map10 object.
@@ -1645,9 +1673,11 @@ func NewMap10[A any, B any, C any, D any, E any, F any, G any, H any, I any, J a
 	}
 	m.ids = []ecs.ID{m.id0, m.id1, m.id2, m.id3, m.id4, m.id5, m.id6, m.id7, m.id8, m.id9}
 	m.mask = ecs.All(m.ids...)
-	m.relation = -1
+	m.relation = ecs.ID{}
+	m.hasRelation = false
 	if len(relation) > 0 {
-		m.relation = int8(ecs.TypeID(w, relation[0]))
+		m.relation = ecs.TypeID(w, relation[0])
+		m.hasRelation = true
 	}
 	return m
 }
@@ -1694,7 +1724,7 @@ func (m *Map10[A, B, C, D, E, F, G, H, I, J]) GetUnchecked(entity ecs.Entity) (*
 //
 // See also [ecs.World.NewEntity].
 func (m *Map10[A, B, C, D, E, F, G, H, I, J]) New(target ...ecs.Entity) ecs.Entity {
-	return newEntity(m.world, m.ids, m.relation, target...)
+	return newEntity(m.world, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatch creates entities with the Map10's components.
@@ -1703,7 +1733,7 @@ func (m *Map10[A, B, C, D, E, F, G, H, I, J]) New(target ...ecs.Entity) ecs.Enti
 //
 // See also [Map10.NewBatchQ] and [ecs.Batch.NewBatch].
 func (m *Map10[A, B, C, D, E, F, G, H, I, J]) NewBatch(count int, target ...ecs.Entity) {
-	newBatch(m.world, count, m.ids, m.relation, target...)
+	newBatch(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatchQ creates entities with the Map10's components.
@@ -1715,7 +1745,7 @@ func (m *Map10[A, B, C, D, E, F, G, H, I, J]) NewBatch(count int, target ...ecs.
 //
 // See also [Map10.NewBatch] and [ecs.Builder.NewBatchQ].
 func (m *Map10[A, B, C, D, E, F, G, H, I, J]) NewBatchQ(count int, target ...ecs.Entity) Query10[A, B, C, D, E, F, G, H, I, J] {
-	query := newQuery(m.world, count, m.ids, m.relation, target...)
+	query := newQuery(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 	return Query10[A, B, C, D, E, F, G, H, I, J]{
 		Query: query,
 		id0:   m.id0,
@@ -1750,7 +1780,7 @@ func (m *Map10[A, B, C, D, E, F, G, H, I, J]) NewWith(a *A, b *B, c *C, d *D, e
 			ecs.Component{ID: m.id9, Comp: j},
 		)
 	}
-	if m.relation < 0 {
+	if !m.hasRelation {
 		panic("map has no relation defined")
 	}
 	return ecs.NewBuilderWith(m.world, ecs.Component{ID: m.id0, Comp: a},
@@ -1763,7 +1793,7 @@ func (m *Map10[A, B, C, D, E, F, G, H, I, J]) NewWith(a *A, b *B, c *C, d *D, e
 		ecs.Component{ID: m.id7, Comp: h},
 		ecs.Component{ID: m.id8, Comp: i},
 		ecs.Component{ID: m.id9, Comp: j},
-	).WithRelation(uint8(m.relation)).New(target[0])
+	).WithRelation(m.relation).New(target[0])
 }
 
 // Add the Map10's components to the given entity.
@@ -1827,21 +1857,22 @@ func (m *Map10[A, B, C, D, E, F, G, H, I, J]) RemoveEntities(exclusive bool) int
 //	entity := mapper.NewEntity()
 //	a, b, c, d, e, f, g, h, i, j, k := mapper.Get(entity)
 type Map11[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any, K any] struct {
-	world    *ecs.World
-	mask     ecs.Mask
-	relation int8
-	ids      []ecs.ID
-	id0      ecs.ID
-	id1      ecs.ID
-	id2      ecs.ID
-	id3      ecs.ID
-	id4      ecs.ID
-	id5      ecs.ID
-	id6      ecs.ID
-	id7      ecs.ID
-	id8      ecs.ID
-	id9      ecs.ID
-	id10     ecs.ID
+	world       *ecs.World
+	mask        ecs.Mask
+	relation    ecs.ID
+	hasRelation bool
+	ids         []ecs.ID
+	id0         ecs.ID
+	id1         ecs.ID
+	id2         ecs.ID
+	id3         ecs.ID
+	id4         ecs.ID
+	id5         ecs.ID
+	id6         ecs.ID
+	id7         ecs.ID
+	id8         ecs.ID
+	id9         ecs.ID
+	id10        ecs.ID
 }
 
 // NewMap11 creates a new Map11 object.
@@ -1864,9 +1895,11 @@ func NewMap11[A any, B any, C any, D any, E any, F any, G any, H any, I any, J a
 	}
 	m.ids = []ecs.ID{m.id0, m.id1, m.id2, m.id3, m.id4, m.id5, m.id6, m.id7, m.id8, m.id9, m.id10}
 	m.mask = ecs.All(m.ids...)
-	m.relation = -1
+	m.relation = ecs.ID{}
+	m.hasRelation = false
 	if len(relation) > 0 {
-		m.relation = int8(ecs.TypeID(w, relation[0]))
+		m.relation = ecs.TypeID(w, relation[0])
+		m.hasRelation = true
 	}
 	return m
 }
@@ -1915,7 +1948,7 @@ func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) GetUnchecked(entity ecs.Entity)
 //
 // See also [ecs.World.NewEntity].
 func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) New(target ...ecs.Entity) ecs.Entity {
-	return newEntity(m.world, m.ids, m.relation, target...)
+	return newEntity(m.world, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatch creates entities with the Map11's components.
@@ -1924,7 +1957,7 @@ func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) New(target ...ecs.Entity) ecs.E
 //
 // See also [Map11.NewBatchQ] and [ecs.Batch.NewBatch].
 func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) NewBatch(count int, target ...ecs.Entity) {
-	newBatch(m.world, count, m.ids, m.relation, target...)
+	newBatch(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatchQ creates entities with the Map11's components.
@@ -1936,7 +1969,7 @@ func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) NewBatch(count int, target ...e
 //
 // See also [Map11.NewBatch] and [ecs.Builder.NewBatchQ].
 func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) NewBatchQ(count int, target ...ecs.Entity) Query11[A, B, C, D, E, F, G, H, I, J, K] {
-	query := newQuery(m.world, count, m.ids, m.relation, target...)
+	query := newQuery(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 	return Query11[A, B, C, D, E, F, G, H, I, J, K]{
 		Query: query,
 		id0:   m.id0,
@@ -1973,7 +2006,7 @@ func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) NewWith(a *A, b *B, c *C, d *D,
 			ecs.Component{ID: m.id10, Comp: k},
 		)
 	}
-	if m.relation < 0 {
+	if !m.hasRelation {
 		panic("map has no relation defined")
 	}
 	return ecs.NewBuilderWith(m.world, ecs.Component{ID: m.id0, Comp: a},
@@ -1987,7 +2020,7 @@ func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) NewWith(a *A, b *B, c *C, d *D,
 		ecs.Component{ID: m.id8, Comp: i},
 		ecs.Component{ID: m.id9, Comp: j},
 		ecs.Component{ID: m.id10, Comp: k},
-	).WithRelation(uint8(m.relation)).New(target[0])
+	).WithRelation(m.relation).New(target[0])
 }
 
 // Add the Map11's components to the given entity.
@@ -2052,22 +2085,23 @@ func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) RemoveEntities(exclusive bool)
 //	entity := mapper.NewEntity()
 //	a, b, c, d, e, f, g, h, i, j, k, l := mapper.Get(entity)
 type Map12[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any, K any, L any] struct {
-	world    *ecs.World
-	mask     ecs.Mask
-	relation int8
-	ids      []ecs.ID
-	id0      ecs.ID
-	id1      ecs.ID
-	id2      ecs.ID
-	id3      ecs.ID
-	id4      ecs.ID
-	id5      ecs.ID
-	id6      ecs.ID
-	id7      ecs.ID
-	id8      ecs.ID
-	id9      ecs.ID
-	id10     ecs.ID
-	id11     ecs.ID
+	world       *ecs.World
+	mask        ecs.Mask
+	relation    ecs.ID
+	hasRelation bool
+	ids         []ecs.ID
+	id0         ecs.ID
+	id1         ecs.ID
+	id2         ecs.ID
+	id3         ecs.ID
+	id4         ecs.ID
+	id5         ecs.ID
+	id6         ecs.ID
+	id7         ecs.ID
+	id8         ecs.ID
+	id9         ecs.ID
+	id10        ecs.ID
+	id11        ecs.ID
 }
 
 // NewMap12 creates a new Map12 object.
@@ -2091,9 +2125,11 @@ func NewMap12[A any, B any, C any, D any, E any, F any, G any, H any, I any, J a
 	}
 	m.ids = []ecs.ID{m.id0, m.id1, m.id2, m.id3, m.id4, m.id5, m.id6, m.id7, m.id8, m.id9, m.id10, m.id11}
 	m.mask = ecs.All(m.ids...)
-	m.relation = -1
+	m.relation = ecs.ID{}
+	m.hasRelation = false
 	if len(relation) > 0 {
-		m.relation = int8(ecs.TypeID(w, relation[0]))
+		m.relation = ecs.TypeID(w, relation[0])
+		m.hasRelation = true
 	}
 	return m
 }
@@ -2144,7 +2180,7 @@ func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) GetUnchecked(entity ecs.Enti
 //
 // See also [ecs.World.NewEntity].
 func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) New(target ...ecs.Entity) ecs.Entity {
-	return newEntity(m.world, m.ids, m.relation, target...)
+	return newEntity(m.world, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatch creates entities with the Map12's components.
@@ -2153,7 +2189,7 @@ func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) New(target ...ecs.Entity) ec
 //
 // See also [Map12.NewBatchQ] and [ecs.Batch.NewBatch].
 func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) NewBatch(count int, target ...ecs.Entity) {
-	newBatch(m.world, count, m.ids, m.relation, target...)
+	newBatch(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 }
 
 // NewBatchQ creates entities with the Map12's components.
@@ -2165,7 +2201,7 @@ func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) NewBatch(count int, target .
 //
 // See also [Map12.NewBatch] and [ecs.Builder.NewBatchQ].
 func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) NewBatchQ(count int, target ...ecs.Entity) Query12[A, B, C, D, E, F, G, H, I, J, K, L] {
-	query := newQuery(m.world, count, m.ids, m.relation, target...)
+	query := newQuery(m.world, count, m.ids, m.relation, m.hasRelation, target...)
 	return Query12[A, B, C, D, E, F, G, H, I, J, K, L]{
 		Query: query,
 		id0:   m.id0,
@@ -2204,7 +2240,7 @@ func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) NewWith(a *A, b *B, c *C, d
 			ecs.Component{ID: m.id11, Comp: l},
 		)
 	}
-	if m.relation < 0 {
+	if !m.hasRelation {
 		panic("map has no relation defined")
 	}
 	return ecs.NewBuilderWith(m.world, ecs.Component{ID: m.id0, Comp: a},
@@ -2219,7 +2255,7 @@ func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) NewWith(a *A, b *B, c *C, d
 		ecs.Component{ID: m.id9, Comp: j},
 		ecs.Component{ID: m.id10, Comp: k},
 		ecs.Component{ID: m.id11, Comp: l},
-	).WithRelation(uint8(m.relation)).New(target[0])
+	).WithRelation(m.relation).New(target[0])
 }
 
 // Add the Map12's components to the given entity.
diff --git a/generic/query_generated.go b/generic/query_generated.go
index 5e21eb5..7e39a9d 100644
--- a/generic/query_generated.go
+++ b/generic/query_generated.go
@@ -100,8 +100,9 @@ func (q *Filter0) Query(w *ecs.World, target ...ecs.Entity) Query0 {
 	}
 
 	return Query0{
-		Query:  w.Query(filter),
-		target: q.compiled.TargetComp,
+		Query:     w.Query(filter),
+		target:    q.compiled.TargetComp,
+		hasTarget: q.compiled.HasTarget,
 	}
 }
 
@@ -138,7 +139,8 @@ func (q *Filter0) Unregister(w *ecs.World) {
 type Query0 struct {
 	ecs.Query
 
-	target int8
+	target    ecs.ID
+	hasTarget bool
 }
 
 // Relation returns the target entity for the query's relation.
@@ -147,10 +149,10 @@ type Query0 struct {
 // Panics if the underlying [Filter0] was not prepared for relations
 // using [Filter0.WithRelation].
 func (q *Query0) Relation() ecs.Entity {
-	if q.target < 0 {
+	if !q.hasTarget {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(ecs.ID(q.target))
+	return q.Query.Relation(q.target)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -264,9 +266,10 @@ func (q *Filter1[A]) Query(w *ecs.World, target ...ecs.Entity) Query1[A] {
 	}
 
 	return Query1[A]{
-		Query:  w.Query(filter),
-		target: q.compiled.TargetComp,
-		id0:    q.compiled.Ids[0],
+		Query:     w.Query(filter),
+		target:    q.compiled.TargetComp,
+		hasTarget: q.compiled.HasTarget,
+		id0:       q.compiled.Ids[0],
 	}
 }
 
@@ -303,8 +306,9 @@ func (q *Filter1[A]) Unregister(w *ecs.World) {
 //	}
 type Query1[A any] struct {
 	ecs.Query
-	id0    ecs.ID
-	target int8
+	id0       ecs.ID
+	target    ecs.ID
+	hasTarget bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -320,10 +324,10 @@ func (q *Query1[A]) Get() *A {
 // Panics if the underlying [Filter1] was not prepared for relations
 // using [Filter1.WithRelation].
 func (q *Query1[A]) Relation() ecs.Entity {
-	if q.target < 0 {
+	if !q.hasTarget {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(ecs.ID(q.target))
+	return q.Query.Relation(q.target)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -438,10 +442,11 @@ func (q *Filter2[A, B]) Query(w *ecs.World, target ...ecs.Entity) Query2[A, B] {
 	}
 
 	return Query2[A, B]{
-		Query:  w.Query(filter),
-		target: q.compiled.TargetComp,
-		id0:    q.compiled.Ids[0],
-		id1:    q.compiled.Ids[1],
+		Query:     w.Query(filter),
+		target:    q.compiled.TargetComp,
+		hasTarget: q.compiled.HasTarget,
+		id0:       q.compiled.Ids[0],
+		id1:       q.compiled.Ids[1],
 	}
 }
 
@@ -478,9 +483,10 @@ func (q *Filter2[A, B]) Unregister(w *ecs.World) {
 //	}
 type Query2[A any, B any] struct {
 	ecs.Query
-	id0    ecs.ID
-	id1    ecs.ID
-	target int8
+	id0       ecs.ID
+	id1       ecs.ID
+	target    ecs.ID
+	hasTarget bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -497,10 +503,10 @@ func (q *Query2[A, B]) Get() (*A, *B) {
 // Panics if the underlying [Filter2] was not prepared for relations
 // using [Filter2.WithRelation].
 func (q *Query2[A, B]) Relation() ecs.Entity {
-	if q.target < 0 {
+	if !q.hasTarget {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(ecs.ID(q.target))
+	return q.Query.Relation(q.target)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -616,11 +622,12 @@ func (q *Filter3[A, B, C]) Query(w *ecs.World, target ...ecs.Entity) Query3[A, B
 	}
 
 	return Query3[A, B, C]{
-		Query:  w.Query(filter),
-		target: q.compiled.TargetComp,
-		id0:    q.compiled.Ids[0],
-		id1:    q.compiled.Ids[1],
-		id2:    q.compiled.Ids[2],
+		Query:     w.Query(filter),
+		target:    q.compiled.TargetComp,
+		hasTarget: q.compiled.HasTarget,
+		id0:       q.compiled.Ids[0],
+		id1:       q.compiled.Ids[1],
+		id2:       q.compiled.Ids[2],
 	}
 }
 
@@ -657,10 +664,11 @@ func (q *Filter3[A, B, C]) Unregister(w *ecs.World) {
 //	}
 type Query3[A any, B any, C any] struct {
 	ecs.Query
-	id0    ecs.ID
-	id1    ecs.ID
-	id2    ecs.ID
-	target int8
+	id0       ecs.ID
+	id1       ecs.ID
+	id2       ecs.ID
+	target    ecs.ID
+	hasTarget bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -678,10 +686,10 @@ func (q *Query3[A, B, C]) Get() (*A, *B, *C) {
 // Panics if the underlying [Filter3] was not prepared for relations
 // using [Filter3.WithRelation].
 func (q *Query3[A, B, C]) Relation() ecs.Entity {
-	if q.target < 0 {
+	if !q.hasTarget {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(ecs.ID(q.target))
+	return q.Query.Relation(q.target)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -798,12 +806,13 @@ func (q *Filter4[A, B, C, D]) Query(w *ecs.World, target ...ecs.Entity) Query4[A
 	}
 
 	return Query4[A, B, C, D]{
-		Query:  w.Query(filter),
-		target: q.compiled.TargetComp,
-		id0:    q.compiled.Ids[0],
-		id1:    q.compiled.Ids[1],
-		id2:    q.compiled.Ids[2],
-		id3:    q.compiled.Ids[3],
+		Query:     w.Query(filter),
+		target:    q.compiled.TargetComp,
+		hasTarget: q.compiled.HasTarget,
+		id0:       q.compiled.Ids[0],
+		id1:       q.compiled.Ids[1],
+		id2:       q.compiled.Ids[2],
+		id3:       q.compiled.Ids[3],
 	}
 }
 
@@ -840,11 +849,12 @@ func (q *Filter4[A, B, C, D]) Unregister(w *ecs.World) {
 //	}
 type Query4[A any, B any, C any, D any] struct {
 	ecs.Query
-	id0    ecs.ID
-	id1    ecs.ID
-	id2    ecs.ID
-	id3    ecs.ID
-	target int8
+	id0       ecs.ID
+	id1       ecs.ID
+	id2       ecs.ID
+	id3       ecs.ID
+	target    ecs.ID
+	hasTarget bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -863,10 +873,10 @@ func (q *Query4[A, B, C, D]) Get() (*A, *B, *C, *D) {
 // Panics if the underlying [Filter4] was not prepared for relations
 // using [Filter4.WithRelation].
 func (q *Query4[A, B, C, D]) Relation() ecs.Entity {
-	if q.target < 0 {
+	if !q.hasTarget {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(ecs.ID(q.target))
+	return q.Query.Relation(q.target)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -984,13 +994,14 @@ func (q *Filter5[A, B, C, D, E]) Query(w *ecs.World, target ...ecs.Entity) Query
 	}
 
 	return Query5[A, B, C, D, E]{
-		Query:  w.Query(filter),
-		target: q.compiled.TargetComp,
-		id0:    q.compiled.Ids[0],
-		id1:    q.compiled.Ids[1],
-		id2:    q.compiled.Ids[2],
-		id3:    q.compiled.Ids[3],
-		id4:    q.compiled.Ids[4],
+		Query:     w.Query(filter),
+		target:    q.compiled.TargetComp,
+		hasTarget: q.compiled.HasTarget,
+		id0:       q.compiled.Ids[0],
+		id1:       q.compiled.Ids[1],
+		id2:       q.compiled.Ids[2],
+		id3:       q.compiled.Ids[3],
+		id4:       q.compiled.Ids[4],
 	}
 }
 
@@ -1027,12 +1038,13 @@ func (q *Filter5[A, B, C, D, E]) Unregister(w *ecs.World) {
 //	}
 type Query5[A any, B any, C any, D any, E any] struct {
 	ecs.Query
-	id0    ecs.ID
-	id1    ecs.ID
-	id2    ecs.ID
-	id3    ecs.ID
-	id4    ecs.ID
-	target int8
+	id0       ecs.ID
+	id1       ecs.ID
+	id2       ecs.ID
+	id3       ecs.ID
+	id4       ecs.ID
+	target    ecs.ID
+	hasTarget bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -1052,10 +1064,10 @@ func (q *Query5[A, B, C, D, E]) Get() (*A, *B, *C, *D, *E) {
 // Panics if the underlying [Filter5] was not prepared for relations
 // using [Filter5.WithRelation].
 func (q *Query5[A, B, C, D, E]) Relation() ecs.Entity {
-	if q.target < 0 {
+	if !q.hasTarget {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(ecs.ID(q.target))
+	return q.Query.Relation(q.target)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -1174,14 +1186,15 @@ func (q *Filter6[A, B, C, D, E, F]) Query(w *ecs.World, target ...ecs.Entity) Qu
 	}
 
 	return Query6[A, B, C, D, E, F]{
-		Query:  w.Query(filter),
-		target: q.compiled.TargetComp,
-		id0:    q.compiled.Ids[0],
-		id1:    q.compiled.Ids[1],
-		id2:    q.compiled.Ids[2],
-		id3:    q.compiled.Ids[3],
-		id4:    q.compiled.Ids[4],
-		id5:    q.compiled.Ids[5],
+		Query:     w.Query(filter),
+		target:    q.compiled.TargetComp,
+		hasTarget: q.compiled.HasTarget,
+		id0:       q.compiled.Ids[0],
+		id1:       q.compiled.Ids[1],
+		id2:       q.compiled.Ids[2],
+		id3:       q.compiled.Ids[3],
+		id4:       q.compiled.Ids[4],
+		id5:       q.compiled.Ids[5],
 	}
 }
 
@@ -1218,13 +1231,14 @@ func (q *Filter6[A, B, C, D, E, F]) Unregister(w *ecs.World) {
 //	}
 type Query6[A any, B any, C any, D any, E any, F any] struct {
 	ecs.Query
-	id0    ecs.ID
-	id1    ecs.ID
-	id2    ecs.ID
-	id3    ecs.ID
-	id4    ecs.ID
-	id5    ecs.ID
-	target int8
+	id0       ecs.ID
+	id1       ecs.ID
+	id2       ecs.ID
+	id3       ecs.ID
+	id4       ecs.ID
+	id5       ecs.ID
+	target    ecs.ID
+	hasTarget bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -1245,10 +1259,10 @@ func (q *Query6[A, B, C, D, E, F]) Get() (*A, *B, *C, *D, *E, *F) {
 // Panics if the underlying [Filter6] was not prepared for relations
 // using [Filter6.WithRelation].
 func (q *Query6[A, B, C, D, E, F]) Relation() ecs.Entity {
-	if q.target < 0 {
+	if !q.hasTarget {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(ecs.ID(q.target))
+	return q.Query.Relation(q.target)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -1368,15 +1382,16 @@ func (q *Filter7[A, B, C, D, E, F, G]) Query(w *ecs.World, target ...ecs.Entity)
 	}
 
 	return Query7[A, B, C, D, E, F, G]{
-		Query:  w.Query(filter),
-		target: q.compiled.TargetComp,
-		id0:    q.compiled.Ids[0],
-		id1:    q.compiled.Ids[1],
-		id2:    q.compiled.Ids[2],
-		id3:    q.compiled.Ids[3],
-		id4:    q.compiled.Ids[4],
-		id5:    q.compiled.Ids[5],
-		id6:    q.compiled.Ids[6],
+		Query:     w.Query(filter),
+		target:    q.compiled.TargetComp,
+		hasTarget: q.compiled.HasTarget,
+		id0:       q.compiled.Ids[0],
+		id1:       q.compiled.Ids[1],
+		id2:       q.compiled.Ids[2],
+		id3:       q.compiled.Ids[3],
+		id4:       q.compiled.Ids[4],
+		id5:       q.compiled.Ids[5],
+		id6:       q.compiled.Ids[6],
 	}
 }
 
@@ -1413,14 +1428,15 @@ func (q *Filter7[A, B, C, D, E, F, G]) Unregister(w *ecs.World) {
 //	}
 type Query7[A any, B any, C any, D any, E any, F any, G any] struct {
 	ecs.Query
-	id0    ecs.ID
-	id1    ecs.ID
-	id2    ecs.ID
-	id3    ecs.ID
-	id4    ecs.ID
-	id5    ecs.ID
-	id6    ecs.ID
-	target int8
+	id0       ecs.ID
+	id1       ecs.ID
+	id2       ecs.ID
+	id3       ecs.ID
+	id4       ecs.ID
+	id5       ecs.ID
+	id6       ecs.ID
+	target    ecs.ID
+	hasTarget bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -1442,10 +1458,10 @@ func (q *Query7[A, B, C, D, E, F, G]) Get() (*A, *B, *C, *D, *E, *F, *G) {
 // Panics if the underlying [Filter7] was not prepared for relations
 // using [Filter7.WithRelation].
 func (q *Query7[A, B, C, D, E, F, G]) Relation() ecs.Entity {
-	if q.target < 0 {
+	if !q.hasTarget {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(ecs.ID(q.target))
+	return q.Query.Relation(q.target)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -1566,16 +1582,17 @@ func (q *Filter8[A, B, C, D, E, F, G, H]) Query(w *ecs.World, target ...ecs.Enti
 	}
 
 	return Query8[A, B, C, D, E, F, G, H]{
-		Query:  w.Query(filter),
-		target: q.compiled.TargetComp,
-		id0:    q.compiled.Ids[0],
-		id1:    q.compiled.Ids[1],
-		id2:    q.compiled.Ids[2],
-		id3:    q.compiled.Ids[3],
-		id4:    q.compiled.Ids[4],
-		id5:    q.compiled.Ids[5],
-		id6:    q.compiled.Ids[6],
-		id7:    q.compiled.Ids[7],
+		Query:     w.Query(filter),
+		target:    q.compiled.TargetComp,
+		hasTarget: q.compiled.HasTarget,
+		id0:       q.compiled.Ids[0],
+		id1:       q.compiled.Ids[1],
+		id2:       q.compiled.Ids[2],
+		id3:       q.compiled.Ids[3],
+		id4:       q.compiled.Ids[4],
+		id5:       q.compiled.Ids[5],
+		id6:       q.compiled.Ids[6],
+		id7:       q.compiled.Ids[7],
 	}
 }
 
@@ -1612,15 +1629,16 @@ func (q *Filter8[A, B, C, D, E, F, G, H]) Unregister(w *ecs.World) {
 //	}
 type Query8[A any, B any, C any, D any, E any, F any, G any, H any] struct {
 	ecs.Query
-	id0    ecs.ID
-	id1    ecs.ID
-	id2    ecs.ID
-	id3    ecs.ID
-	id4    ecs.ID
-	id5    ecs.ID
-	id6    ecs.ID
-	id7    ecs.ID
-	target int8
+	id0       ecs.ID
+	id1       ecs.ID
+	id2       ecs.ID
+	id3       ecs.ID
+	id4       ecs.ID
+	id5       ecs.ID
+	id6       ecs.ID
+	id7       ecs.ID
+	target    ecs.ID
+	hasTarget bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -1643,10 +1661,10 @@ func (q *Query8[A, B, C, D, E, F, G, H]) Get() (*A, *B, *C, *D, *E, *F, *G, *H)
 // Panics if the underlying [Filter8] was not prepared for relations
 // using [Filter8.WithRelation].
 func (q *Query8[A, B, C, D, E, F, G, H]) Relation() ecs.Entity {
-	if q.target < 0 {
+	if !q.hasTarget {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(ecs.ID(q.target))
+	return q.Query.Relation(q.target)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -1768,17 +1786,18 @@ func (q *Filter9[A, B, C, D, E, F, G, H, I]) Query(w *ecs.World, target ...ecs.E
 	}
 
 	return Query9[A, B, C, D, E, F, G, H, I]{
-		Query:  w.Query(filter),
-		target: q.compiled.TargetComp,
-		id0:    q.compiled.Ids[0],
-		id1:    q.compiled.Ids[1],
-		id2:    q.compiled.Ids[2],
-		id3:    q.compiled.Ids[3],
-		id4:    q.compiled.Ids[4],
-		id5:    q.compiled.Ids[5],
-		id6:    q.compiled.Ids[6],
-		id7:    q.compiled.Ids[7],
-		id8:    q.compiled.Ids[8],
+		Query:     w.Query(filter),
+		target:    q.compiled.TargetComp,
+		hasTarget: q.compiled.HasTarget,
+		id0:       q.compiled.Ids[0],
+		id1:       q.compiled.Ids[1],
+		id2:       q.compiled.Ids[2],
+		id3:       q.compiled.Ids[3],
+		id4:       q.compiled.Ids[4],
+		id5:       q.compiled.Ids[5],
+		id6:       q.compiled.Ids[6],
+		id7:       q.compiled.Ids[7],
+		id8:       q.compiled.Ids[8],
 	}
 }
 
@@ -1815,16 +1834,17 @@ func (q *Filter9[A, B, C, D, E, F, G, H, I]) Unregister(w *ecs.World) {
 //	}
 type Query9[A any, B any, C any, D any, E any, F any, G any, H any, I any] struct {
 	ecs.Query
-	id0    ecs.ID
-	id1    ecs.ID
-	id2    ecs.ID
-	id3    ecs.ID
-	id4    ecs.ID
-	id5    ecs.ID
-	id6    ecs.ID
-	id7    ecs.ID
-	id8    ecs.ID
-	target int8
+	id0       ecs.ID
+	id1       ecs.ID
+	id2       ecs.ID
+	id3       ecs.ID
+	id4       ecs.ID
+	id5       ecs.ID
+	id6       ecs.ID
+	id7       ecs.ID
+	id8       ecs.ID
+	target    ecs.ID
+	hasTarget bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -1848,10 +1868,10 @@ func (q *Query9[A, B, C, D, E, F, G, H, I]) Get() (*A, *B, *C, *D, *E, *F, *G, *
 // Panics if the underlying [Filter9] was not prepared for relations
 // using [Filter9.WithRelation].
 func (q *Query9[A, B, C, D, E, F, G, H, I]) Relation() ecs.Entity {
-	if q.target < 0 {
+	if !q.hasTarget {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(ecs.ID(q.target))
+	return q.Query.Relation(q.target)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -1974,18 +1994,19 @@ func (q *Filter10[A, B, C, D, E, F, G, H, I, J]) Query(w *ecs.World, target ...e
 	}
 
 	return Query10[A, B, C, D, E, F, G, H, I, J]{
-		Query:  w.Query(filter),
-		target: q.compiled.TargetComp,
-		id0:    q.compiled.Ids[0],
-		id1:    q.compiled.Ids[1],
-		id2:    q.compiled.Ids[2],
-		id3:    q.compiled.Ids[3],
-		id4:    q.compiled.Ids[4],
-		id5:    q.compiled.Ids[5],
-		id6:    q.compiled.Ids[6],
-		id7:    q.compiled.Ids[7],
-		id8:    q.compiled.Ids[8],
-		id9:    q.compiled.Ids[9],
+		Query:     w.Query(filter),
+		target:    q.compiled.TargetComp,
+		hasTarget: q.compiled.HasTarget,
+		id0:       q.compiled.Ids[0],
+		id1:       q.compiled.Ids[1],
+		id2:       q.compiled.Ids[2],
+		id3:       q.compiled.Ids[3],
+		id4:       q.compiled.Ids[4],
+		id5:       q.compiled.Ids[5],
+		id6:       q.compiled.Ids[6],
+		id7:       q.compiled.Ids[7],
+		id8:       q.compiled.Ids[8],
+		id9:       q.compiled.Ids[9],
 	}
 }
 
@@ -2022,17 +2043,18 @@ func (q *Filter10[A, B, C, D, E, F, G, H, I, J]) Unregister(w *ecs.World) {
 //	}
 type Query10[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any] struct {
 	ecs.Query
-	id0    ecs.ID
-	id1    ecs.ID
-	id2    ecs.ID
-	id3    ecs.ID
-	id4    ecs.ID
-	id5    ecs.ID
-	id6    ecs.ID
-	id7    ecs.ID
-	id8    ecs.ID
-	id9    ecs.ID
-	target int8
+	id0       ecs.ID
+	id1       ecs.ID
+	id2       ecs.ID
+	id3       ecs.ID
+	id4       ecs.ID
+	id5       ecs.ID
+	id6       ecs.ID
+	id7       ecs.ID
+	id8       ecs.ID
+	id9       ecs.ID
+	target    ecs.ID
+	hasTarget bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -2057,10 +2079,10 @@ func (q *Query10[A, B, C, D, E, F, G, H, I, J]) Get() (*A, *B, *C, *D, *E, *F, *
 // Panics if the underlying [Filter10] was not prepared for relations
 // using [Filter10.WithRelation].
 func (q *Query10[A, B, C, D, E, F, G, H, I, J]) Relation() ecs.Entity {
-	if q.target < 0 {
+	if !q.hasTarget {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(ecs.ID(q.target))
+	return q.Query.Relation(q.target)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -2184,19 +2206,20 @@ func (q *Filter11[A, B, C, D, E, F, G, H, I, J, K]) Query(w *ecs.World, target .
 	}
 
 	return Query11[A, B, C, D, E, F, G, H, I, J, K]{
-		Query:  w.Query(filter),
-		target: q.compiled.TargetComp,
-		id0:    q.compiled.Ids[0],
-		id1:    q.compiled.Ids[1],
-		id2:    q.compiled.Ids[2],
-		id3:    q.compiled.Ids[3],
-		id4:    q.compiled.Ids[4],
-		id5:    q.compiled.Ids[5],
-		id6:    q.compiled.Ids[6],
-		id7:    q.compiled.Ids[7],
-		id8:    q.compiled.Ids[8],
-		id9:    q.compiled.Ids[9],
-		id10:   q.compiled.Ids[10],
+		Query:     w.Query(filter),
+		target:    q.compiled.TargetComp,
+		hasTarget: q.compiled.HasTarget,
+		id0:       q.compiled.Ids[0],
+		id1:       q.compiled.Ids[1],
+		id2:       q.compiled.Ids[2],
+		id3:       q.compiled.Ids[3],
+		id4:       q.compiled.Ids[4],
+		id5:       q.compiled.Ids[5],
+		id6:       q.compiled.Ids[6],
+		id7:       q.compiled.Ids[7],
+		id8:       q.compiled.Ids[8],
+		id9:       q.compiled.Ids[9],
+		id10:      q.compiled.Ids[10],
 	}
 }
 
@@ -2233,18 +2256,19 @@ func (q *Filter11[A, B, C, D, E, F, G, H, I, J, K]) Unregister(w *ecs.World) {
 //	}
 type Query11[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any, K any] struct {
 	ecs.Query
-	id0    ecs.ID
-	id1    ecs.ID
-	id2    ecs.ID
-	id3    ecs.ID
-	id4    ecs.ID
-	id5    ecs.ID
-	id6    ecs.ID
-	id7    ecs.ID
-	id8    ecs.ID
-	id9    ecs.ID
-	id10   ecs.ID
-	target int8
+	id0       ecs.ID
+	id1       ecs.ID
+	id2       ecs.ID
+	id3       ecs.ID
+	id4       ecs.ID
+	id5       ecs.ID
+	id6       ecs.ID
+	id7       ecs.ID
+	id8       ecs.ID
+	id9       ecs.ID
+	id10      ecs.ID
+	target    ecs.ID
+	hasTarget bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -2270,10 +2294,10 @@ func (q *Query11[A, B, C, D, E, F, G, H, I, J, K]) Get() (*A, *B, *C, *D, *E, *F
 // Panics if the underlying [Filter11] was not prepared for relations
 // using [Filter11.WithRelation].
 func (q *Query11[A, B, C, D, E, F, G, H, I, J, K]) Relation() ecs.Entity {
-	if q.target < 0 {
+	if !q.hasTarget {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(ecs.ID(q.target))
+	return q.Query.Relation(q.target)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -2398,20 +2422,21 @@ func (q *Filter12[A, B, C, D, E, F, G, H, I, J, K, L]) Query(w *ecs.World, targe
 	}
 
 	return Query12[A, B, C, D, E, F, G, H, I, J, K, L]{
-		Query:  w.Query(filter),
-		target: q.compiled.TargetComp,
-		id0:    q.compiled.Ids[0],
-		id1:    q.compiled.Ids[1],
-		id2:    q.compiled.Ids[2],
-		id3:    q.compiled.Ids[3],
-		id4:    q.compiled.Ids[4],
-		id5:    q.compiled.Ids[5],
-		id6:    q.compiled.Ids[6],
-		id7:    q.compiled.Ids[7],
-		id8:    q.compiled.Ids[8],
-		id9:    q.compiled.Ids[9],
-		id10:   q.compiled.Ids[10],
-		id11:   q.compiled.Ids[11],
+		Query:     w.Query(filter),
+		target:    q.compiled.TargetComp,
+		hasTarget: q.compiled.HasTarget,
+		id0:       q.compiled.Ids[0],
+		id1:       q.compiled.Ids[1],
+		id2:       q.compiled.Ids[2],
+		id3:       q.compiled.Ids[3],
+		id4:       q.compiled.Ids[4],
+		id5:       q.compiled.Ids[5],
+		id6:       q.compiled.Ids[6],
+		id7:       q.compiled.Ids[7],
+		id8:       q.compiled.Ids[8],
+		id9:       q.compiled.Ids[9],
+		id10:      q.compiled.Ids[10],
+		id11:      q.compiled.Ids[11],
 	}
 }
 
@@ -2448,19 +2473,20 @@ func (q *Filter12[A, B, C, D, E, F, G, H, I, J, K, L]) Unregister(w *ecs.World)
 //	}
 type Query12[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any, K any, L any] struct {
 	ecs.Query
-	id0    ecs.ID
-	id1    ecs.ID
-	id2    ecs.ID
-	id3    ecs.ID
-	id4    ecs.ID
-	id5    ecs.ID
-	id6    ecs.ID
-	id7    ecs.ID
-	id8    ecs.ID
-	id9    ecs.ID
-	id10   ecs.ID
-	id11   ecs.ID
-	target int8
+	id0       ecs.ID
+	id1       ecs.ID
+	id2       ecs.ID
+	id3       ecs.ID
+	id4       ecs.ID
+	id5       ecs.ID
+	id6       ecs.ID
+	id7       ecs.ID
+	id8       ecs.ID
+	id9       ecs.ID
+	id10      ecs.ID
+	id11      ecs.ID
+	target    ecs.ID
+	hasTarget bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -2487,8 +2513,8 @@ func (q *Query12[A, B, C, D, E, F, G, H, I, J, K, L]) Get() (*A, *B, *C, *D, *E,
 // Panics if the underlying [Filter12] was not prepared for relations
 // using [Filter12.WithRelation].
 func (q *Query12[A, B, C, D, E, F, G, H, I, J, K, L]) Relation() ecs.Entity {
-	if q.target < 0 {
+	if !q.hasTarget {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(ecs.ID(q.target))
+	return q.Query.Relation(q.target)
 }
diff --git a/generic/util.go b/generic/util.go
index 0285fb3..2545240 100644
--- a/generic/util.go
+++ b/generic/util.go
@@ -56,37 +56,37 @@ func toMaskOptional(w *ecs.World, include []ecs.ID, optional []Comp) ecs.Mask {
 	return mask
 }
 
-func newEntity(w *ecs.World, ids []ecs.ID, relation int8, target ...ecs.Entity) ecs.Entity {
+func newEntity(w *ecs.World, ids []ecs.ID, relation ecs.ID, hasRelation bool, target ...ecs.Entity) ecs.Entity {
 	if len(target) == 0 {
 		return w.NewEntity(ids...)
 	}
-	if relation < 0 {
+	if !hasRelation {
 		panic("map has no relation defined")
 	}
-	return ecs.NewBuilder(w, ids...).WithRelation(uint8(relation)).New(target[0])
+	return ecs.NewBuilder(w, ids...).WithRelation(relation).New(target[0])
 }
 
-func newBatch(w *ecs.World, count int, ids []ecs.ID, relation int8, target ...ecs.Entity) {
+func newBatch(w *ecs.World, count int, ids []ecs.ID, relation ecs.ID, hasRelation bool, target ...ecs.Entity) {
 	if len(target) == 0 {
 		ecs.NewBuilder(w, ids...).NewBatch(count)
 		return
 	}
-	if relation < 0 {
+	if !hasRelation {
 		panic("map has no relation defined")
 	}
-	ecs.NewBuilder(w, ids...).WithRelation(uint8(relation)).NewBatch(count, target[0])
+	ecs.NewBuilder(w, ids...).WithRelation(relation).NewBatch(count, target[0])
 }
 
-func newQuery(w *ecs.World, count int, ids []ecs.ID, relation int8, target ...ecs.Entity) ecs.Query {
+func newQuery(w *ecs.World, count int, ids []ecs.ID, relation ecs.ID, hasRelation bool, target ...ecs.Entity) ecs.Query {
 	var query ecs.Query
 
 	if len(target) == 0 {
 		query = ecs.NewBuilder(w, ids...).NewBatchQ(count)
 	} else {
-		if relation < 0 {
+		if !hasRelation {
 			panic("map has no relation defined")
 		}
-		query = ecs.NewBuilder(w, ids...).WithRelation(uint8(relation)).NewBatchQ(count, target[0])
+		query = ecs.NewBuilder(w, ids...).WithRelation(relation).NewBatchQ(count, target[0])
 	}
 
 	return query


Test patch:
diff --git a/benchmark/arche/build/arche_test.go b/benchmark/arche/build/arche_test.go
index dc443ec..4499f81 100644
--- a/benchmark/arche/build/arche_test.go
+++ b/benchmark/arche/build/arche_test.go
@@ -11,14 +11,14 @@ func BenchmarkBuild1kArch(b *testing.B) {
 	for i := 0; i < b.N; i++ {
 		b.StopTimer()
 		world := ecs.NewWorld()
-		c.RegisterAll(&world)
+		ids := c.RegisterAll(&world)
 		b.StartTimer()
 
 		for i := 0; i < 1024; i++ {
 			mask := i
 			add := make([]ecs.ID, 0, 10)
 			for j := 0; j < 10; j++ {
-				id := ecs.ID(j)
+				id := ids[j]
 				m := 1 << j
 				if mask&m == m {
 					add = append(add, id)
diff --git a/benchmark/arche/fragmentation/arche_test.go b/benchmark/arche/fragmentation/arche_test.go
index 30f3428..85d10d4 100644
--- a/benchmark/arche/fragmentation/arche_test.go
+++ b/benchmark/arche/fragmentation/arche_test.go
@@ -11,16 +11,16 @@ import (
 func runQuery1kArch(b *testing.B, count int) {
 	b.StopTimer()
 	world := ecs.NewWorld()
-	c.RegisterAll(&world)
+	ids := c.RegisterAll(&world)
 
 	perArch := count / 1000
 
 	for i := 0; i < 1024; i++ {
 		mask := i
 		add := make([]ecs.ID, 0, 11)
-		add = append(add, 10)
+		add = append(add, ids[10])
 		for j := 0; j < 10; j++ {
-			id := ecs.ID(j)
+			id := ids[j]
 			m := 1 << j
 			if mask&m == m {
 				add = append(add, id)
@@ -30,13 +30,13 @@ func runQuery1kArch(b *testing.B, count int) {
 			world.NewEntity(add...)
 		}
 	}
-	filter := ecs.All(10)
+	filter := ecs.All(ids[10])
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
 		query := world.Query(filter)
 		for query.Next() {
-			pos := (*c.TestStruct10)(query.Get(10))
+			pos := (*c.TestStruct10)(query.Get(ids[10]))
 			pos.Val = 1
 		}
 	}
@@ -45,16 +45,16 @@ func runQuery1kArch(b *testing.B, count int) {
 func runQuery1kArchCached(b *testing.B, count int) {
 	b.StopTimer()
 	world := ecs.NewWorld()
-	c.RegisterAll(&world)
+	ids := c.RegisterAll(&world)
 
 	perArch := count / 1000
 
 	for i := 0; i < 1024; i++ {
 		mask := i
 		add := make([]ecs.ID, 0, 11)
-		add = append(add, 10)
+		add = append(add, ids[10])
 		for j := 0; j < 10; j++ {
-			id := ecs.ID(j)
+			id := ids[j]
 			m := 1 << j
 			if mask&m == m {
 				add = append(add, id)
@@ -65,14 +65,14 @@ func runQuery1kArchCached(b *testing.B, count int) {
 		}
 	}
 
-	cf := world.Cache().Register(ecs.All(10))
+	cf := world.Cache().Register(ecs.All(ids[10]))
 	var filter ecs.Filter = &cf
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
 		query := world.Query(filter)
 		for query.Next() {
-			pos := (*c.TestStruct10)(query.Get(10))
+			pos := (*c.TestStruct10)(query.Get(ids[10]))
 			pos.Val = 1
 		}
 	}
@@ -81,16 +81,16 @@ func runQuery1kArchCached(b *testing.B, count int) {
 func runFilter1kArch(b *testing.B, count int) {
 	b.StopTimer()
 	world := ecs.NewWorld()
-	c.RegisterAll(&world)
+	ids := c.RegisterAll(&world)
 
 	perArch := count / 1000
 
 	for i := 0; i < 1024; i++ {
 		mask := i
 		add := make([]ecs.ID, 0, 11)
-		add = append(add, 10)
+		add = append(add, ids[10])
 		for j := 0; j < 10; j++ {
-			id := ecs.ID(j)
+			id := ids[j]
 			m := 1 << j
 			if mask&m == m {
 				add = append(add, id)
@@ -101,13 +101,13 @@ func runFilter1kArch(b *testing.B, count int) {
 			world.Add(entity, add...)
 		}
 	}
-	filter := filter.All(10)
+	filter := filter.All(ids[10])
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
 		query := world.Query(filter)
 		for query.Next() {
-			pos := (*c.TestStruct10)(query.Get(10))
+			pos := (*c.TestStruct10)(query.Get(ids[10]))
 			pos.Val = 1
 		}
 	}
@@ -116,7 +116,7 @@ func runFilter1kArch(b *testing.B, count int) {
 func runQuery1Of1kArch(b *testing.B, count int) {
 	b.StopTimer()
 	world := ecs.NewWorld()
-	c.RegisterAll(&world)
+	ids := c.RegisterAll(&world)
 
 	perArch := 2 * count / 1000
 
@@ -124,7 +124,7 @@ func runQuery1Of1kArch(b *testing.B, count int) {
 		mask := i
 		add := make([]ecs.ID, 0, 10)
 		for j := 0; j < 10; j++ {
-			id := ecs.ID(j)
+			id := ids[j]
 			m := 1 << j
 			if mask&m == m {
 				add = append(add, id)
@@ -136,14 +136,14 @@ func runQuery1Of1kArch(b *testing.B, count int) {
 		}
 	}
 
-	ecs.NewBuilder(&world, 10).NewBatch(count)
-	filter := ecs.All(10)
+	ecs.NewBuilder(&world, ids[10]).NewBatch(count)
+	filter := ecs.All(ids[10])
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
 		query := world.Query(filter)
 		for query.Next() {
-			pos := (*c.TestStruct6)(query.Get(10))
+			pos := (*c.TestStruct6)(query.Get(ids[10]))
 			pos.Val = 1
 		}
 	}
@@ -152,7 +152,7 @@ func runQuery1Of1kArch(b *testing.B, count int) {
 func runQuery1Of1kArchCached(b *testing.B, count int) {
 	b.StopTimer()
 	world := ecs.NewWorld()
-	c.RegisterAll(&world)
+	ids := c.RegisterAll(&world)
 
 	perArch := 2 * count / 1000
 
@@ -160,7 +160,7 @@ func runQuery1Of1kArchCached(b *testing.B, count int) {
 		mask := i
 		add := make([]ecs.ID, 0, 10)
 		for j := 0; j < 10; j++ {
-			id := ecs.ID(j)
+			id := ids[j]
 			m := 1 << j
 			if mask&m == m {
 				add = append(add, id)
@@ -172,16 +172,16 @@ func runQuery1Of1kArchCached(b *testing.B, count int) {
 		}
 	}
 
-	ecs.NewBuilder(&world, 10).NewBatch(count)
+	ecs.NewBuilder(&world, ids[10]).NewBatch(count)
 
-	cf := world.Cache().Register(ecs.All(10))
+	cf := world.Cache().Register(ecs.All(ids[10]))
 	var filter ecs.Filter = &cf
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
 		query := world.Query(filter)
 		for query.Next() {
-			pos := (*c.TestStruct10)(query.Get(10))
+			pos := (*c.TestStruct10)(query.Get(ids[10]))
 			pos.Val = 1
 		}
 	}
diff --git a/ecs/archetype_test.go b/ecs/archetype_test.go
index e3bc2ad..10c74c7 100644
--- a/ecs/archetype_test.go
+++ b/ecs/archetype_test.go
@@ -9,25 +9,25 @@ import (
 
 func TestArchetype(t *testing.T) {
 	comps := []componentType{
-		{ID: 0, Type: reflect.TypeOf(Position{})},
-		{ID: 1, Type: reflect.TypeOf(rotation{})},
+		{ID: id(0), Type: reflect.TypeOf(Position{})},
+		{ID: id(1), Type: reflect.TypeOf(rotation{})},
 	}
 
-	node := newArchNode(All(0, 1), &nodeData{}, -1, 32, comps)
+	node := newArchNode(All(id(0), id(1)), &nodeData{}, ID{}, false, 32, comps)
 	arch := archetype{}
 	data := archetypeData{}
 	arch.Init(&node, &data, 0, false, 16, Entity{})
 
 	arch.Add(
 		newEntity(0),
-		Component{ID: 0, Comp: &Position{1, 2}},
-		Component{ID: 1, Comp: &rotation{3}},
+		Component{ID: id(0), Comp: &Position{1, 2}},
+		Component{ID: id(1), Comp: &rotation{3}},
 	)
 
 	arch.Add(
 		newEntity(1),
-		Component{ID: 0, Comp: &Position{4, 5}},
-		Component{ID: 1, Comp: &rotation{6}},
+		Component{ID: id(0), Comp: &Position{4, 5}},
+		Component{ID: id(1), Comp: &rotation{6}},
 	)
 
 	assert.Equal(t, 2, int(arch.Len()))
@@ -37,10 +37,10 @@ func TestArchetype(t *testing.T) {
 	assert.Equal(t, Entity{0, 0}, e0)
 	assert.Equal(t, Entity{1, 0}, e1)
 
-	pos0 := (*Position)(arch.Get(0, ID(0)))
-	rot0 := (*rotation)(arch.Get(0, ID(1)))
-	pos1 := (*Position)(arch.Get(1, ID(0)))
-	rot1 := (*rotation)(arch.Get(1, ID(1)))
+	pos0 := (*Position)(arch.Get(0, id(0)))
+	rot0 := (*rotation)(arch.Get(0, id(1)))
+	pos1 := (*Position)(arch.Get(1, id(0)))
+	rot1 := (*rotation)(arch.Get(1, id(1)))
 
 	assert.Equal(t, 1, pos0.X)
 	assert.Equal(t, 2, pos0.Y)
@@ -52,8 +52,8 @@ func TestArchetype(t *testing.T) {
 	arch.Remove(0)
 	assert.Equal(t, 1, int(arch.Len()))
 
-	pos0 = (*Position)(arch.Get(0, ID(0)))
-	rot0 = (*rotation)(arch.Get(0, ID(1)))
+	pos0 = (*Position)(arch.Get(0, id(0)))
+	rot0 = (*rotation)(arch.Get(0, id(1)))
 	assert.Equal(t, 4, pos0.X)
 	assert.Equal(t, 5, pos0.Y)
 	assert.Equal(t, 6, rot0.Angle)
@@ -61,18 +61,18 @@ func TestArchetype(t *testing.T) {
 	assert.Panics(t, func() {
 		arch.Add(
 			newEntity(1),
-			Component{ID: 0, Comp: &Position{4, 5}},
+			Component{ID: id(0), Comp: &Position{4, 5}},
 		)
 	})
 }
 
 func TestNewArchetype(t *testing.T) {
 	comps := []componentType{
-		{ID: 0, Type: reflect.TypeOf(Position{})},
-		{ID: 1, Type: reflect.TypeOf(rotation{})},
+		{ID: id(0), Type: reflect.TypeOf(Position{})},
+		{ID: id(1), Type: reflect.TypeOf(rotation{})},
 	}
 
-	node := newArchNode(All(0, 1), &nodeData{}, -1, 32, comps)
+	node := newArchNode(All(id(0), id(1)), &nodeData{}, ID{}, false, 32, comps)
 	arch := archetype{}
 	data := archetypeData{}
 	arch.Init(&node, &data, 0, true, 16, Entity{})
@@ -84,11 +84,11 @@ func TestNewArchetype(t *testing.T) {
 	assert.Equal(t, 1, int(arch.Cap()))
 
 	comps = []componentType{
-		{ID: 1, Type: reflect.TypeOf(rotation{})},
-		{ID: 0, Type: reflect.TypeOf(Position{})},
+		{ID: id(1), Type: reflect.TypeOf(rotation{})},
+		{ID: id(0), Type: reflect.TypeOf(Position{})},
 	}
 	assert.Panics(t, func() {
-		node := newArchNode(All(0, 1), &nodeData{}, -1, 32, comps)
+		node := newArchNode(All(id(0), id(1)), &nodeData{}, ID{}, false, 32, comps)
 		arch := archetype{}
 		data := archetypeData{}
 		arch.Init(&node, &data, 0, true, 16, Entity{})
@@ -97,11 +97,11 @@ func TestNewArchetype(t *testing.T) {
 
 func TestArchetypeExtend(t *testing.T) {
 	comps := []componentType{
-		{ID: 0, Type: reflect.TypeOf(Position{})},
-		{ID: 1, Type: reflect.TypeOf(rotation{})},
+		{ID: id(0), Type: reflect.TypeOf(Position{})},
+		{ID: id(1), Type: reflect.TypeOf(rotation{})},
 	}
 
-	node := newArchNode(All(0, 1), &nodeData{}, -1, 8, comps)
+	node := newArchNode(All(id(0), id(1)), &nodeData{}, ID{}, false, 8, comps)
 	arch := archetype{}
 	data := archetypeData{}
 	arch.Init(&node, &data, 0, true, 16, Entity{})
@@ -121,13 +121,13 @@ func TestArchetypeExtend(t *testing.T) {
 
 func TestArchetypeExtendLayouts(t *testing.T) {
 	comps := []componentType{
-		{ID: 0, Type: reflect.TypeOf(Position{})},
-		{ID: 1, Type: reflect.TypeOf(rotation{})},
-		{ID: 2, Type: reflect.TypeOf(relationComp{})},
+		{ID: id(0), Type: reflect.TypeOf(Position{})},
+		{ID: id(1), Type: reflect.TypeOf(rotation{})},
+		{ID: id(2), Type: reflect.TypeOf(relationComp{})},
 	}
 	entity := newEntity(1)
 
-	node := newArchNode(All(0, 1), &nodeData{}, -1, 8, comps[:2])
+	node := newArchNode(All(id(0), id(1)), &nodeData{}, ID{}, false, 8, comps[:2])
 	arch := archetype{}
 	data := archetypeData{}
 	arch.Init(&node, &data, 0, true, 16, Entity{})
@@ -139,7 +139,7 @@ func TestArchetypeExtendLayouts(t *testing.T) {
 	node.ExtendArchetypeLayouts(32)
 	assert.Equal(t, len(arch.layouts), 32)
 
-	node = newArchNode(All(0, 1), &nodeData{}, 2, 8, comps)
+	node = newArchNode(All(id(0), id(1)), &nodeData{}, id(2), true, 8, comps)
 	node.CreateArchetype(16, entity)
 	arch2 := node.GetArchetype(entity)
 
@@ -152,10 +152,10 @@ func TestArchetypeExtendLayouts(t *testing.T) {
 
 func TestArchetypeAlloc(t *testing.T) {
 	comps := []componentType{
-		{ID: 0, Type: reflect.TypeOf(Position{})},
-		{ID: 1, Type: reflect.TypeOf(rotation{})},
+		{ID: id(0), Type: reflect.TypeOf(Position{})},
+		{ID: id(1), Type: reflect.TypeOf(rotation{})},
 	}
-	node := newArchNode(All(0, 1), &nodeData{}, -1, 8, comps)
+	node := newArchNode(All(id(0), id(1)), &nodeData{}, ID{}, false, 8, comps)
 	arch := archetype{}
 	data := archetypeData{}
 	arch.Init(&node, &data, 0, true, 16, Entity{})
@@ -177,11 +177,11 @@ func TestArchetypeAlloc(t *testing.T) {
 
 func TestArchetypeAddGetSet(t *testing.T) {
 	comps := []componentType{
-		{ID: 0, Type: reflect.TypeOf(testStruct0{})},
-		{ID: 1, Type: reflect.TypeOf(label{})},
+		{ID: id(0), Type: reflect.TypeOf(testStruct0{})},
+		{ID: id(1), Type: reflect.TypeOf(label{})},
 	}
 
-	node := newArchNode(All(0, 1), &nodeData{}, -1, 1, comps)
+	node := newArchNode(All(id(0), id(1)), &nodeData{}, ID{}, false, 1, comps)
 	a := archetype{}
 	data := archetypeData{}
 	a.Init(&node, &data, 0, true, 16, Entity{})
@@ -189,17 +189,17 @@ func TestArchetypeAddGetSet(t *testing.T) {
 	assert.Equal(t, 1, int(a.Cap()))
 	assert.Equal(t, 0, int(a.Len()))
 
-	a.Add(Entity{1, 0}, Component{ID: 0, Comp: &testStruct0{100}}, Component{ID: 1, Comp: &label{}})
-	a.Add(Entity{2, 0}, Component{ID: 0, Comp: &testStruct0{200}}, Component{ID: 1, Comp: &label{}})
+	a.Add(Entity{1, 0}, Component{ID: id(0), Comp: &testStruct0{100}}, Component{ID: id(1), Comp: &label{}})
+	a.Add(Entity{2, 0}, Component{ID: id(0), Comp: &testStruct0{200}}, Component{ID: id(1), Comp: &label{}})
 
-	ts := (*testStruct0)(a.Get(0, 0))
+	ts := (*testStruct0)(a.Get(0, id(0)))
 	assert.Equal(t, 100, int(ts.Val))
 
-	a.Set(1, 0, &testStruct0{200})
-	a.Set(1, 1, &label{})
+	a.Set(1, id(0), &testStruct0{200})
+	a.Set(1, id(1), &label{})
 
-	_ = (*testStruct0)(a.Get(1, 0))
-	_ = (*label)(a.Get(1, 1))
+	_ = (*testStruct0)(a.Get(1, id(0)))
+	_ = (*label)(a.Get(1, id(1)))
 
 	a.SetEntity(1, Entity{100, 200})
 	e := a.GetEntity(1)
@@ -213,29 +213,29 @@ func TestArchetypeAddGetSet(t *testing.T) {
 
 func TestArchetypeReset(t *testing.T) {
 	comps := []componentType{
-		{ID: 0, Type: reflect.TypeOf(Position{})},
-		{ID: 1, Type: reflect.TypeOf(rotation{})},
+		{ID: id(0), Type: reflect.TypeOf(Position{})},
+		{ID: id(1), Type: reflect.TypeOf(rotation{})},
 	}
 
-	node := newArchNode(All(0, 1), &nodeData{}, -1, 32, comps)
+	node := newArchNode(All(id(0), id(1)), &nodeData{}, ID{}, false, 32, comps)
 	arch := archetype{}
 	data := archetypeData{}
 	arch.Init(&node, &data, 0, false, 16, Entity{})
 
 	arch.Add(
 		newEntity(0),
-		Component{ID: 0, Comp: &Position{1, 2}},
-		Component{ID: 1, Comp: &rotation{3}},
+		Component{ID: id(0), Comp: &Position{1, 2}},
+		Component{ID: id(1), Comp: &rotation{3}},
 	)
 
 	arch.Add(
 		newEntity(1),
-		Component{ID: 0, Comp: &Position{4, 5}},
-		Component{ID: 1, Comp: &rotation{6}},
+		Component{ID: id(0), Comp: &Position{4, 5}},
+		Component{ID: id(1), Comp: &rotation{6}},
 	)
 
-	assert.Equal(t, Position{1, 2}, *(*Position)(arch.Get(0, 0)))
-	assert.Equal(t, Position{4, 5}, *(*Position)(arch.Get(1, 0)))
+	assert.Equal(t, Position{1, 2}, *(*Position)(arch.Get(0, id(0))))
+	assert.Equal(t, Position{4, 5}, *(*Position)(arch.Get(1, id(0))))
 	assert.Equal(t, 2, int(arch.Len()))
 
 	arch.Reset()
@@ -243,28 +243,28 @@ func TestArchetypeReset(t *testing.T) {
 
 	arch.Add(
 		newEntity(0),
-		Component{ID: 0, Comp: &Position{10, 20}},
-		Component{ID: 1, Comp: &rotation{3}},
+		Component{ID: id(0), Comp: &Position{10, 20}},
+		Component{ID: id(1), Comp: &rotation{3}},
 	)
 
 	arch.Add(
 		newEntity(1),
-		Component{ID: 0, Comp: &Position{40, 50}},
-		Component{ID: 1, Comp: &rotation{6}},
+		Component{ID: id(0), Comp: &Position{40, 50}},
+		Component{ID: id(1), Comp: &rotation{6}},
 	)
 
-	assert.Equal(t, Position{10, 20}, *(*Position)(arch.Get(0, 0)))
-	assert.Equal(t, Position{40, 50}, *(*Position)(arch.Get(1, 0)))
+	assert.Equal(t, Position{10, 20}, *(*Position)(arch.Get(0, id(0))))
+	assert.Equal(t, Position{40, 50}, *(*Position)(arch.Get(1, id(0))))
 	assert.Equal(t, 2, int(arch.Len()))
 }
 
 func TestArchetypeZero(t *testing.T) {
 	comps := []componentType{
-		{ID: 0, Type: reflect.TypeOf(Position{})},
-		{ID: 1, Type: reflect.TypeOf(rotation{})},
+		{ID: id(0), Type: reflect.TypeOf(Position{})},
+		{ID: id(1), Type: reflect.TypeOf(rotation{})},
 	}
 
-	node := newArchNode(All(0, 1), &nodeData{}, -1, 32, comps)
+	node := newArchNode(All(id(0), id(1)), &nodeData{}, ID{}, false, 32, comps)
 	arch := archetype{}
 	data := archetypeData{}
 	arch.Init(&node, &data, 0, false, 16, Entity{})
@@ -272,32 +272,32 @@ func TestArchetypeZero(t *testing.T) {
 	arch.Alloc(newEntity(0))
 	arch.Alloc(newEntity(1))
 
-	assert.Equal(t, Position{0, 0}, *(*Position)(arch.Get(0, 0)))
-	assert.Equal(t, Position{0, 0}, *(*Position)(arch.Get(1, 0)))
+	assert.Equal(t, Position{0, 0}, *(*Position)(arch.Get(0, id(0))))
+	assert.Equal(t, Position{0, 0}, *(*Position)(arch.Get(1, id(0))))
 
-	pos := (*Position)(arch.Get(0, 0))
+	pos := (*Position)(arch.Get(0, id(0)))
 	pos.X = 100
-	pos = (*Position)(arch.Get(1, 0))
+	pos = (*Position)(arch.Get(1, id(0)))
 	pos.X = 100
 
-	assert.Equal(t, Position{100, 0}, *(*Position)(arch.Get(0, 0)))
-	assert.Equal(t, Position{100, 0}, *(*Position)(arch.Get(1, 0)))
+	assert.Equal(t, Position{100, 0}, *(*Position)(arch.Get(0, id(0))))
+	assert.Equal(t, Position{100, 0}, *(*Position)(arch.Get(1, id(0))))
 
 	arch.Remove(0)
 	arch.Remove(0)
 	arch.Alloc(newEntity(0))
 	arch.Alloc(newEntity(1))
-	assert.Equal(t, Position{0, 0}, *(*Position)(arch.Get(0, 0)))
-	assert.Equal(t, Position{0, 0}, *(*Position)(arch.Get(1, 0)))
+	assert.Equal(t, Position{0, 0}, *(*Position)(arch.Get(0, id(0))))
+	assert.Equal(t, Position{0, 0}, *(*Position)(arch.Get(1, id(0))))
 }
 
 func BenchmarkIterArchetype_1000(b *testing.B) {
 	b.StopTimer()
 	comps := []componentType{
-		{ID: 0, Type: reflect.TypeOf(testStruct0{})},
+		{ID: id(0), Type: reflect.TypeOf(testStruct0{})},
 	}
 
-	node := newArchNode(All(0), &nodeData{}, -1, 32, comps)
+	node := newArchNode(All(id(0)), &nodeData{}, ID{}, false, 32, comps)
 	arch := archetype{}
 	data := archetypeData{}
 	arch.Init(&node, &data, 0, true, 16, Entity{})
@@ -309,7 +309,7 @@ func BenchmarkIterArchetype_1000(b *testing.B) {
 
 	for i := 0; i < b.N; i++ {
 		len := arch.Len()
-		id := ID(0)
+		id := id(0)
 		var j uint32
 		for j = 0; j < len; j++ {
 			pos := (*testStruct0)(arch.Get(j, id))
diff --git a/ecs/bitmask_test.go b/ecs/bitmask_test.go
index 3b78ef3..6be7a89 100644
--- a/ecs/bitmask_test.go
+++ b/ecs/bitmask_test.go
@@ -1,42 +1,41 @@
-package ecs_test
+package ecs
 
 import (
 	"math/rand"
 	"testing"
 
-	"github.com/mlange-42/arche/ecs"
 	"github.com/stretchr/testify/assert"
 )
 
-func all(ids ...ecs.ID) *ecs.Mask {
-	mask := ecs.All(ids...)
+func all(ids ...ID) *Mask {
+	mask := All(ids...)
 	return &mask
 }
 
 func TestBitMask(t *testing.T) {
-	mask := ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27), ecs.ID(200))
+	mask := All(id(1), id(2), id(13), id(27), id(200))
 
 	assert.Equal(t, 5, mask.TotalBitsSet())
 
-	assert.True(t, mask.Get(1))
-	assert.True(t, mask.Get(2))
-	assert.True(t, mask.Get(13))
-	assert.True(t, mask.Get(27))
-	assert.True(t, mask.Get(200))
+	assert.True(t, mask.Get(id(1)))
+	assert.True(t, mask.Get(id(2)))
+	assert.True(t, mask.Get(id(13)))
+	assert.True(t, mask.Get(id(27)))
+	assert.True(t, mask.Get(id(200)))
 
-	assert.False(t, mask.Get(0))
-	assert.False(t, mask.Get(3))
-	assert.False(t, mask.Get(199))
-	assert.False(t, mask.Get(201))
+	assert.False(t, mask.Get(id(0)))
+	assert.False(t, mask.Get(id(3)))
+	assert.False(t, mask.Get(id(199)))
+	assert.False(t, mask.Get(id(201)))
 
-	mask.Set(ecs.ID(0), true)
-	mask.Set(ecs.ID(1), false)
+	mask.Set(id(0), true)
+	mask.Set(id(1), false)
 
-	assert.True(t, mask.Get(0))
-	assert.False(t, mask.Get(1))
+	assert.True(t, mask.Get(id(0)))
+	assert.False(t, mask.Get(id(1)))
 
-	other1 := ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(32))
-	other2 := ecs.All(ecs.ID(0), ecs.ID(2))
+	other1 := All(id(1), id(2), id(32))
+	other2 := All(id(0), id(2))
 
 	assert.False(t, mask.Contains(&other1))
 	assert.True(t, mask.Contains(&other2))
@@ -44,86 +43,86 @@ func TestBitMask(t *testing.T) {
 	mask.Reset()
 	assert.Equal(t, 0, mask.TotalBitsSet())
 
-	mask = ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))
-	other1 = ecs.All(ecs.ID(1), ecs.ID(32))
-	other2 = ecs.All(ecs.ID(0), ecs.ID(32))
+	mask = All(id(1), id(2), id(13), id(27))
+	other1 = All(id(1), id(32))
+	other2 = All(id(0), id(32))
 
 	assert.True(t, mask.ContainsAny(&other1))
 	assert.False(t, mask.ContainsAny(&other2))
 }
 
 func TestBitMaskWithoutExclusive(t *testing.T) {
-	mask := ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13))
+	mask := All(id(1), id(2), id(13))
 
-	assert.True(t, mask.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13))))
-	assert.True(t, mask.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))))
+	assert.True(t, mask.Matches(all(id(1), id(2), id(13))))
+	assert.True(t, mask.Matches(all(id(1), id(2), id(13), id(27))))
 
-	assert.False(t, mask.Matches(all(ecs.ID(1), ecs.ID(2))))
+	assert.False(t, mask.Matches(all(id(1), id(2))))
 
-	without := mask.Without(ecs.ID(3))
+	without := mask.Without(id(3))
 
-	assert.True(t, without.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13))))
-	assert.True(t, without.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))))
+	assert.True(t, without.Matches(all(id(1), id(2), id(13))))
+	assert.True(t, without.Matches(all(id(1), id(2), id(13), id(27))))
 
-	assert.False(t, without.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(3), ecs.ID(13))))
-	assert.False(t, without.Matches(all(ecs.ID(1), ecs.ID(2))))
+	assert.False(t, without.Matches(all(id(1), id(2), id(3), id(13))))
+	assert.False(t, without.Matches(all(id(1), id(2))))
 
 	excl := mask.Exclusive()
 
-	assert.True(t, excl.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13))))
-	assert.False(t, excl.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))))
-	assert.False(t, excl.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(3), ecs.ID(13))))
+	assert.True(t, excl.Matches(all(id(1), id(2), id(13))))
+	assert.False(t, excl.Matches(all(id(1), id(2), id(13), id(27))))
+	assert.False(t, excl.Matches(all(id(1), id(2), id(3), id(13))))
 }
 
 func TestBitMask256(t *testing.T) {
-	for i := 0; i < ecs.MaskTotalBits; i++ {
-		mask := ecs.All(ecs.ID(i))
+	for i := 0; i < MaskTotalBits; i++ {
+		mask := All(id(uint8(i)))
 		assert.Equal(t, 1, mask.TotalBitsSet())
-		assert.True(t, mask.Get(ecs.ID(i)))
+		assert.True(t, mask.Get(id(uint8(i))))
 	}
-	mask := ecs.Mask{}
+	mask := Mask{}
 	assert.Equal(t, 0, mask.TotalBitsSet())
 
-	for i := 0; i < ecs.MaskTotalBits; i++ {
-		mask.Set(ecs.ID(i), true)
+	for i := 0; i < MaskTotalBits; i++ {
+		mask.Set(id(uint8(i)), true)
 		assert.Equal(t, i+1, mask.TotalBitsSet())
-		assert.True(t, mask.Get(ecs.ID(i)))
+		assert.True(t, mask.Get(id(uint8(i))))
 	}
 
-	mask = ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27), ecs.ID(63), ecs.ID(64), ecs.ID(65))
+	mask = All(id(1), id(2), id(13), id(27), id(63), id(64), id(65))
 
-	assert.True(t, mask.Contains(all(ecs.ID(1), ecs.ID(2), ecs.ID(63), ecs.ID(64))))
-	assert.False(t, mask.Contains(all(ecs.ID(1), ecs.ID(2), ecs.ID(63), ecs.ID(90))))
+	assert.True(t, mask.Contains(all(id(1), id(2), id(63), id(64))))
+	assert.False(t, mask.Contains(all(id(1), id(2), id(63), id(90))))
 
-	assert.True(t, mask.ContainsAny(all(ecs.ID(6), ecs.ID(65), ecs.ID(111))))
-	assert.False(t, mask.ContainsAny(all(ecs.ID(6), ecs.ID(66), ecs.ID(90))))
+	assert.True(t, mask.ContainsAny(all(id(6), id(65), id(111))))
+	assert.False(t, mask.ContainsAny(all(id(6), id(66), id(90))))
 }
 
 func TestBitMask64(t *testing.T) {
-	mask := newBitMask64(ecs.ID(1))
-	assert.True(t, mask.Get(ecs.ID(1)))
+	mask := newBitMask64(id(1))
+	assert.True(t, mask.Get(1))
 	for i := 0; i < 64; i++ {
-		mask.Set(ecs.ID(i), true)
-		assert.True(t, mask.Get(ecs.ID(i)))
-		mask.Set(ecs.ID(i), false)
-		assert.False(t, mask.Get(ecs.ID(i)))
+		mask.Set(uint8(i), true)
+		assert.True(t, mask.Get(uint8(i)))
+		mask.Set(uint8(i), false)
+		assert.False(t, mask.Get(uint8(i)))
 	}
 }
 
 func BenchmarkBitmask64Get(b *testing.B) {
 	b.StopTimer()
 	mask := newBitMask64()
-	for i := 0; i < ecs.MaskTotalBits; i++ {
+	for i := 0; i < MaskTotalBits; i++ {
 		if rand.Float64() < 0.5 {
-			mask.Set(ecs.ID(i), true)
+			mask.Set(uint8(i), true)
 		}
 	}
-	idx := ecs.ID(rand.Intn(ecs.MaskTotalBits / 2))
+	idx := id(uint8(rand.Intn(MaskTotalBits / 4)))
 	b.StartTimer()
 
 	var v bool
 	for i := 0; i < b.N; i++ {
-		v = mask.Get(idx)
+		v = mask.Get(idx.id)
 	}
 	b.StopTimer()
 	v = !v
@@ -132,13 +131,13 @@ func BenchmarkBitmask64Get(b *testing.B) {
 
 func BenchmarkBitmask256Get(b *testing.B) {
 	b.StopTimer()
-	mask := ecs.All()
-	for i := 0; i < ecs.MaskTotalBits; i++ {
+	mask := All()
+	for i := 0; i < MaskTotalBits; i++ {
 		if rand.Float64() < 0.5 {
-			mask.Set(ecs.ID(i), true)
+			mask.Set(id(uint8(i)), true)
 		}
 	}
-	idx := ecs.ID(rand.Intn(ecs.MaskTotalBits))
+	idx := id(uint8(rand.Intn(MaskTotalBits)))
 	b.StartTimer()
 
 	var v bool
@@ -153,13 +152,13 @@ func BenchmarkBitmask256Get(b *testing.B) {
 
 func BenchmarkBitmaskContains(b *testing.B) {
 	b.StopTimer()
-	mask := ecs.All()
-	for i := 0; i < ecs.MaskTotalBits; i++ {
+	mask := All()
+	for i := 0; i < MaskTotalBits; i++ {
 		if rand.Float64() < 0.5 {
-			mask.Set(ecs.ID(i), true)
+			mask.Set(id(uint8(i)), true)
 		}
 	}
-	filter := ecs.All(ecs.ID(rand.Intn(ecs.MaskTotalBits)))
+	filter := All(id(uint8(rand.Intn(MaskTotalBits))))
 	b.StartTimer()
 
 	var v bool
@@ -174,13 +173,13 @@ func BenchmarkBitmaskContains(b *testing.B) {
 
 func BenchmarkBitmaskContainsAny(b *testing.B) {
 	b.StopTimer()
-	mask := ecs.All()
-	for i := 0; i < ecs.MaskTotalBits; i++ {
+	mask := All()
+	for i := 0; i < MaskTotalBits; i++ {
 		if rand.Float64() < 0.5 {
-			mask.Set(ecs.ID(i), true)
+			mask.Set(id(uint8(i)), true)
 		}
 	}
-	filter := ecs.All(ecs.ID(rand.Intn(ecs.MaskTotalBits)))
+	filter := All(id(uint8(rand.Intn(MaskTotalBits))))
 	b.StartTimer()
 
 	var v bool
@@ -195,8 +194,8 @@ func BenchmarkBitmaskContainsAny(b *testing.B) {
 
 func BenchmarkMaskFilter(b *testing.B) {
 	b.StopTimer()
-	mask := ecs.All(0, 1, 2).Without(3)
-	bits := ecs.All(0, 1, 2)
+	mask := All(id(0), id(1), id(2)).Without(id(3))
+	bits := All(id(0), id(1), id(2))
 	b.StartTimer()
 	var v bool
 	for i := 0; i < b.N; i++ {
@@ -209,8 +208,8 @@ func BenchmarkMaskFilter(b *testing.B) {
 
 func BenchmarkMaskFilterNoPointer(b *testing.B) {
 	b.StopTimer()
-	mask := maskFilterPointer{ecs.All(0, 1, 2), ecs.All(3)}
-	bits := ecs.All(0, 1, 2)
+	mask := maskFilterPointer{All(id(0), id(1), id(2)), All(id(3))}
+	bits := All(id(0), id(1), id(2))
 	b.StartTimer()
 	var v bool
 	for i := 0; i < b.N; i++ {
@@ -223,8 +222,8 @@ func BenchmarkMaskFilterNoPointer(b *testing.B) {
 
 func BenchmarkMaskPointer(b *testing.B) {
 	b.StopTimer()
-	mask := maskPointer(ecs.All(0, 1, 2))
-	bits := ecs.All(0, 1, 2)
+	mask := maskPointer(All(id(0), id(1), id(2)))
+	bits := All(id(0), id(1), id(2))
 	b.StartTimer()
 	var v bool
 	for i := 0; i < b.N; i++ {
@@ -237,8 +236,8 @@ func BenchmarkMaskPointer(b *testing.B) {
 
 func BenchmarkMask(b *testing.B) {
 	b.StopTimer()
-	mask := ecs.All(0, 1, 2)
-	bits := ecs.All(0, 1, 2)
+	mask := All(id(0), id(1), id(2))
+	bits := All(id(0), id(1), id(2))
 	b.StartTimer()
 	var v bool
 	for i := 0; i < b.N; i++ {
@@ -252,19 +251,19 @@ func BenchmarkMask(b *testing.B) {
 // bitMask64 is there just for performance comparison with the new 256 bit Mask.
 type bitMask64 uint64
 
-func newBitMask64(ids ...ecs.ID) bitMask64 {
+func newBitMask64(ids ...ID) bitMask64 {
 	var mask bitMask64
 	for _, id := range ids {
-		mask.Set(id, true)
+		mask.Set(id.id, true)
 	}
 	return mask
 }
-func (e bitMask64) Get(bit ecs.ID) bool {
+func (e bitMask64) Get(bit uint8) bool {
 	mask := bitMask64(1 << bit)
 	return e&mask == mask
 }
 
-func (e *bitMask64) Set(bit ecs.ID, value bool) {
+func (e *bitMask64) Set(bit uint8, value bool) {
 	if value {
 		*e |= bitMask64(1 << bit)
 	} else {
@@ -273,30 +272,30 @@ func (e *bitMask64) Set(bit ecs.ID, value bool) {
 }
 
 type maskFilterPointer struct {
-	Mask    ecs.Mask
-	Exclude ecs.Mask
+	Mask    Mask
+	Exclude Mask
 }
 
 // Matches a filter against a mask.
-func (f maskFilterPointer) Matches(bits ecs.Mask) bool {
+func (f maskFilterPointer) Matches(bits Mask) bool {
 	return bits.Contains(&f.Mask) &&
 		(f.Exclude.IsZero() || !bits.ContainsAny(&f.Exclude))
 }
 
-type maskPointer ecs.Mask
+type maskPointer Mask
 
 // Matches a filter against a mask.
-func (f *maskPointer) Matches(bits ecs.Mask) bool {
-	m := ecs.Mask(*f)
+func (f *maskPointer) Matches(bits Mask) bool {
+	m := Mask(*f)
 	return bits.Contains(&m)
 }
 
 func ExampleMask() {
-	world := ecs.NewWorld()
-	posID := ecs.ComponentID[Position](&world)
-	velID := ecs.ComponentID[Velocity](&world)
+	world := NewWorld()
+	posID := ComponentID[Position](&world)
+	velID := ComponentID[Velocity](&world)
 
-	filter := ecs.All(posID, velID)
+	filter := All(posID, velID)
 	query := world.Query(filter)
 
 	for query.Next() {
@@ -306,11 +305,11 @@ func ExampleMask() {
 }
 
 func ExampleMask_Without() {
-	world := ecs.NewWorld()
-	posID := ecs.ComponentID[Position](&world)
-	velID := ecs.ComponentID[Velocity](&world)
+	world := NewWorld()
+	posID := ComponentID[Position](&world)
+	velID := ComponentID[Velocity](&world)
 
-	filter := ecs.All(posID).Without(velID)
+	filter := All(posID).Without(velID)
 	query := world.Query(&filter)
 
 	for query.Next() {
@@ -320,11 +319,11 @@ func ExampleMask_Without() {
 }
 
 func ExampleMask_Exclusive() {
-	world := ecs.NewWorld()
-	posID := ecs.ComponentID[Position](&world)
-	velID := ecs.ComponentID[Velocity](&world)
+	world := NewWorld()
+	posID := ComponentID[Position](&world)
+	velID := ComponentID[Velocity](&world)
 
-	filter := ecs.All(posID, velID).Exclusive()
+	filter := All(posID, velID).Exclusive()
 	query := world.Query(&filter)
 
 	for query.Next() {
diff --git a/ecs/filter_test.go b/ecs/filter_test.go
index 930d13a..7a930f2 100644
--- a/ecs/filter_test.go
+++ b/ecs/filter_test.go
@@ -1,40 +1,39 @@
-package ecs_test
+package ecs
 
 import (
 	"testing"
 
-	"github.com/mlange-42/arche/ecs"
 	"github.com/stretchr/testify/assert"
 )
 
 func TestCachedMaskFilter(t *testing.T) {
-	f := ecs.All(1, 2, 3).Without(4)
+	f := All(id(1), id(2), id(3)).Without(id(4))
 
-	assert.True(t, f.Matches(all(1, 2, 3)))
-	assert.True(t, f.Matches(all(1, 2, 3, 5)))
+	assert.True(t, f.Matches(all(id(1), id(2), id(3))))
+	assert.True(t, f.Matches(all(id(1), id(2), id(3), id(5))))
 
-	assert.False(t, f.Matches(all(1, 2)))
-	assert.False(t, f.Matches(all(1, 2, 3, 4)))
+	assert.False(t, f.Matches(all(id(1), id(2))))
+	assert.False(t, f.Matches(all(id(1), id(2), id(3), id(4))))
 }
 
 func TestCachedFilter(t *testing.T) {
-	w := ecs.NewWorld()
+	w := NewWorld()
 
-	f := ecs.All(1, 2, 3)
+	f := All(id(1), id(2), id(3))
 	fc := w.Cache().Register(f)
 
-	assert.Equal(t, f.Matches(all(1, 2, 3)), fc.Matches(all(1, 2, 3)))
-	assert.Equal(t, f.Matches(all(1, 2)), fc.Matches(all(1, 2)))
+	assert.Equal(t, f.Matches(all(id(1), id(2), id(3))), fc.Matches(all(id(1), id(2), id(3))))
+	assert.Equal(t, f.Matches(all(id(1), id(2))), fc.Matches(all(id(1), id(2))))
 
 	w.Cache().Unregister(&fc)
 }
 
 func ExampleMaskFilter() {
-	world := ecs.NewWorld()
-	posID := ecs.ComponentID[Position](&world)
-	velID := ecs.ComponentID[Velocity](&world)
+	world := NewWorld()
+	posID := ComponentID[Position](&world)
+	velID := ComponentID[Velocity](&world)
 
-	filter := ecs.All(posID).Without(velID)
+	filter := All(posID).Without(velID)
 	query := world.Query(&filter)
 
 	for query.Next() {
@@ -44,10 +43,10 @@ func ExampleMaskFilter() {
 }
 
 func ExampleCachedFilter() {
-	world := ecs.NewWorld()
-	posID := ecs.ComponentID[Position](&world)
+	world := NewWorld()
+	posID := ComponentID[Position](&world)
 
-	filter := ecs.All(posID)
+	filter := All(posID)
 	cached := world.Cache().Register(filter)
 
 	query := world.Query(&cached)
@@ -59,15 +58,15 @@ func ExampleCachedFilter() {
 }
 
 func ExampleRelationFilter() {
-	world := ecs.NewWorld()
-	childID := ecs.ComponentID[ChildOf](&world)
+	world := NewWorld()
+	childID := ComponentID[ChildOf](&world)
 
 	target := world.NewEntity()
 
-	builder := ecs.NewBuilder(&world, childID).WithRelation(childID)
+	builder := NewBuilder(&world, childID).WithRelation(childID)
 	builder.NewBatch(100, target)
 
-	filter := ecs.NewRelationFilter(ecs.All(childID), target)
+	filter := NewRelationFilter(All(childID), target)
 
 	query := world.Query(&filter)
 	for query.Next() {
diff --git a/ecs/id_map_test.go b/ecs/id_map_test.go
index 0c154f2..fb4b9cc 100644
--- a/ecs/id_map_test.go
+++ b/ecs/id_map_test.go
@@ -100,14 +100,14 @@ func BenchmarkIdMapping_IDMap(b *testing.B) {
 
 	for i := 0; i < MaskTotalBits; i++ {
 		entities[i] = Entity{eid(i), 0}
-		m.Set(ID(i), &entities[i])
+		m.Set(uint8(i), &entities[i])
 	}
 
 	b.StartTimer()
 
 	var ptr *Entity = nil
 	for i := 0; i < b.N; i++ {
-		ptr, _ = m.Get(ID(i % MaskTotalBits))
+		ptr, _ = m.Get(uint8(i % MaskTotalBits))
 	}
 	_ = ptr
 }
@@ -140,14 +140,14 @@ func BenchmarkIdMapping_HashMap(b *testing.B) {
 
 	for i := 0; i < MaskTotalBits; i++ {
 		entities[i] = Entity{eid(i), 0}
-		m[ID(i)] = &entities[i]
+		m[uint8(i)] = &entities[i]
 	}
 
 	b.StartTimer()
 
 	var ptr *Entity = nil
 	for i := 0; i < b.N; i++ {
-		ptr = m[ID(i%MaskTotalBits)]
+		ptr = m[uint8(i%MaskTotalBits)]
 	}
 	_ = ptr
 }
diff --git a/ecs/query_test.go b/ecs/query_test.go
index 069731a..b26ba88 100644
--- a/ecs/query_test.go
+++ b/ecs/query_test.go
@@ -7,12 +7,12 @@ import (
 )
 
 func TestMask(t *testing.T) {
-	filter := All(0, 2, 4)
-	other := All(0, 1, 2)
+	filter := All(id(0), id(2), id(4))
+	other := All(id(0), id(1), id(2))
 
 	assert.False(t, filter.Matches(&other))
 
-	other = All(0, 1, 2, 3, 4)
+	other = All(id(0), id(1), id(2), id(3), id(4))
 	assert.True(t, filter.Matches(&other))
 }
 
@@ -455,7 +455,7 @@ func TestQueryIds(t *testing.T) {
 	query := world.Query(filter)
 
 	query.Next()
-	assert.Equal(t, query.Ids(), []ID{1})
+	assert.Equal(t, query.Ids(), []ID{id(1)})
 	query.Next()
-	assert.Equal(t, query.Ids(), []ID{0, 1})
+	assert.Equal(t, query.Ids(), []ID{id(0), id(1)})
 }
diff --git a/ecs/registry_test.go b/ecs/registry_test.go
index f025614..b32ba4d 100644
--- a/ecs/registry_test.go
+++ b/ecs/registry_test.go
@@ -14,14 +14,21 @@ func TestComponentRegistry(t *testing.T) {
 	rotType := reflect.TypeOf((*rotation)(nil)).Elem()
 
 	reg.registerComponent(posType, MaskTotalBits)
+	assert.Equal(t, []uint8{uint8(0)}, reg.IDs)
+
+	reg.registerComponent(rotType, MaskTotalBits)
+	reg.unregisterLastComponent()
+	assert.Equal(t, []uint8{uint8(0)}, reg.IDs)
 
 	id0, _ := reg.ComponentID(posType)
 	id1, _ := reg.ComponentID(rotType)
-	assert.Equal(t, ID(0), id0)
-	assert.Equal(t, ID(1), id1)
+	assert.Equal(t, uint8(0), id0)
+	assert.Equal(t, uint8(1), id1)
+
+	assert.Equal(t, []uint8{uint8(0), uint8(1)}, reg.IDs)
 
-	t1, _ := reg.ComponentType(ID(0))
-	t2, _ := reg.ComponentType(ID(1))
+	t1, _ := reg.ComponentType(uint8(0))
+	t2, _ := reg.ComponentType(uint8(1))
 
 	assert.Equal(t, posType, t1)
 	assert.Equal(t, rotType, t2)
@@ -69,8 +76,8 @@ func TestRegistryRelations(t *testing.T) {
 	id3, _ := registry.ComponentID(noRelCompTp2)
 	id4, _ := registry.ComponentID(noRelCompTp3)
 
-	assert.True(t, registry.IsRelation.Get(id1))
-	assert.False(t, registry.IsRelation.Get(id2))
-	assert.False(t, registry.IsRelation.Get(id3))
-	assert.False(t, registry.IsRelation.Get(id4))
+	assert.True(t, registry.IsRelation.Get(id(id1)))
+	assert.False(t, registry.IsRelation.Get(id(id2)))
+	assert.False(t, registry.IsRelation.Get(id(id3)))
+	assert.False(t, registry.IsRelation.Get(id(id4)))
 }
diff --git a/ecs/resources_test.go b/ecs/resources_test.go
index fd0741d..56765c9 100644
--- a/ecs/resources_test.go
+++ b/ecs/resources_test.go
@@ -11,8 +11,10 @@ import (
 func TestResources(t *testing.T) {
 	res := newResources()
 
-	posID, _ := res.registry.ComponentID(reflect.TypeOf(Position{}))
-	rotID, _ := res.registry.ComponentID(reflect.TypeOf(rotation{}))
+	posIDint, _ := res.registry.ComponentID(reflect.TypeOf(Position{}))
+	rotIDint, _ := res.registry.ComponentID(reflect.TypeOf(rotation{}))
+	posID := ResID{id: posIDint}
+	rotID := ResID{id: rotIDint}
 
 	assert.False(t, res.Has(posID))
 	assert.Nil(t, res.Get(posID))
@@ -40,8 +42,10 @@ func TestResources(t *testing.T) {
 func TestResourcesReset(t *testing.T) {
 	res := newResources()
 
-	posID, _ := res.registry.ComponentID(reflect.TypeOf(Position{}))
-	rotID, _ := res.registry.ComponentID(reflect.TypeOf(rotation{}))
+	posIDint, _ := res.registry.ComponentID(reflect.TypeOf(Position{}))
+	rotIDint, _ := res.registry.ComponentID(reflect.TypeOf(rotation{}))
+	posID := ResID{id: posIDint}
+	rotID := ResID{id: rotIDint}
 
 	res.Add(posID, &Position{1, 2})
 	res.Add(rotID, &rotation{5})
diff --git a/ecs/types_test_test.go b/ecs/types_test_test.go
index f101248..5466f1e 100644
--- a/ecs/types_test_test.go
+++ b/ecs/types_test_test.go
@@ -24,6 +24,10 @@ type testRelationB struct {
 	Relation
 }
 
+type ChildOf struct {
+	Relation
+}
+
 type testStruct0 struct{ Val int32 }
 type testStruct1 struct{ val int32 }
 type testStruct2 struct{ val int32 }
diff --git a/ecs/world_benchmark_test.go b/ecs/world_benchmark_test.go
index 1ad59a2..918ace0 100644
--- a/ecs/world_benchmark_test.go
+++ b/ecs/world_benchmark_test.go
@@ -13,7 +13,7 @@ func BenchmarkEntityAlive_1000(b *testing.B) {
 	posID := ComponentID[Position](&world)
 
 	entities := make([]Entity, 0, 1000)
-	q := world.newEntitiesQuery(1000, -1, Entity{}, posID)
+	q := world.newEntitiesQuery(1000, ID{}, false, Entity{}, posID)
 	for q.Next() {
 		entities = append(entities, q.Entity())
 	}
@@ -83,7 +83,7 @@ func BenchmarkNewEntitiesBatch_10_000_New(b *testing.B) {
 		posID := ComponentID[Position](&world)
 		velID := ComponentID[Velocity](&world)
 
-		world.newEntities(10000, -1, Entity{}, posID, velID)
+		world.newEntities(10000, ID{}, false, Entity{}, posID, velID)
 	}
 }
 
@@ -121,7 +121,7 @@ func BenchmarkNewEntitiesBatch_10_000_Reset(b *testing.B) {
 
 	for i := 0; i < b.N; i++ {
 		world.Reset()
-		world.newEntities(10000, -1, Entity{}, posID, velID)
+		world.newEntities(10000, ID{}, false, Entity{}, posID, velID)
 	}
 }
 
@@ -134,7 +134,7 @@ func BenchmarkRemoveEntities_10_000(b *testing.B) {
 		velID := ComponentID[Velocity](&world)
 
 		entities := make([]Entity, 10000)
-		q := world.newEntitiesQuery(10000, -1, Entity{}, posID, velID)
+		q := world.newEntitiesQuery(10000, ID{}, false, Entity{}, posID, velID)
 
 		cnt := 0
 		for q.Next() {
@@ -232,7 +232,7 @@ func BenchmarkRemoveEntitiesBatch_10_000(b *testing.B) {
 		posID := ComponentID[Position](&world)
 		velID := ComponentID[Velocity](&world)
 
-		q := world.newEntitiesQuery(10000, -1, Entity{}, posID, velID)
+		q := world.newEntitiesQuery(10000, ID{}, false, Entity{}, posID, velID)
 		q.Close()
 		b.StartTimer()
 		world.Batch().RemoveEntities(All(posID, velID))
diff --git a/ecs/world_test.go b/ecs/world_test.go
index 48f60ab..e43bf4b 100644
--- a/ecs/world_test.go
+++ b/ecs/world_test.go
@@ -112,6 +112,25 @@ func TestWorldNewEntites(t *testing.T) {
 	}
 }
 
+func TestWorldIDs(t *testing.T) {
+	w := NewWorld()
+
+	posID := ComponentID[Position](&w)
+	rotID := ComponentID[rotation](&w)
+
+	res1ID := ResourceID[Position](&w)
+	res2ID := ResourceID[Velocity](&w)
+
+	assert.Equal(t, uint8(0), posID.id)
+	assert.Equal(t, uint8(1), rotID.id)
+
+	assert.Equal(t, uint8(0), res1ID.id)
+	assert.Equal(t, uint8(1), res2ID.id)
+
+	assert.Equal(t, []ID{id(0), id(1)}, ComponentIDs(&w))
+	assert.Equal(t, []ResID{{id: 0}, {id: 1}}, ResourceIDs(&w))
+}
+
 func TestWorldComponents(t *testing.T) {
 	w := NewWorld()
 
@@ -191,7 +210,7 @@ func TestWorldComponentInfo(t *testing.T) {
 	assert.True(t, ok)
 	assert.Equal(t, info.Type, reflect.TypeOf(Position{}))
 
-	info, ok = ComponentInfo(&w, 3)
+	info, ok = ComponentInfo(&w, ID{id: 3})
 	assert.False(t, ok)
 	assert.Equal(t, info, CompInfo{})
 
@@ -201,7 +220,7 @@ func TestWorldComponentInfo(t *testing.T) {
 	assert.True(t, ok)
 	assert.Equal(t, tp, reflect.TypeOf(Velocity{}))
 
-	tp, ok = ResourceType(&w, 3)
+	tp, ok = ResourceType(&w, ResID{id: 3})
 	assert.False(t, ok)
 	assert.Equal(t, tp, nil)
 }
@@ -215,9 +234,9 @@ func TestWorldIds(t *testing.T) {
 	e2 := w.NewEntity(posID, velID)
 	e3 := w.NewEntity(velID)
 
-	assert.Equal(t, w.Ids(e1), []ID{1})
-	assert.Equal(t, w.Ids(e2), []ID{0, 1})
-	assert.Equal(t, w.Ids(e3), []ID{0})
+	assert.Equal(t, w.Ids(e1), []ID{id(1)})
+	assert.Equal(t, w.Ids(e2), []ID{id(0), id(1)})
+	assert.Equal(t, w.Ids(e3), []ID{id(0)})
 
 	w.RemoveEntity(e1)
 
@@ -300,11 +319,11 @@ func TestWorldExchange(t *testing.T) {
 	target := w.NewEntity()
 	e0 = w.NewEntity(rel1ID)
 
-	assert.Panics(t, func() { w.exchange(e0, []ID{rel2ID}, nil, int8(rel2ID), target) })
-	assert.Panics(t, func() { w.exchange(e0, []ID{posID}, nil, int8(posID), target) })
+	assert.Panics(t, func() { w.exchange(e0, []ID{rel2ID}, nil, rel2ID, true, target) })
+	assert.Panics(t, func() { w.exchange(e0, []ID{posID}, nil, posID, true, target) })
 
 	w.Remove(e0, rel1ID)
-	assert.Panics(t, func() { w.exchange(e0, []ID{posID}, nil, int8(rel1ID), target) })
+	assert.Panics(t, func() { w.exchange(e0, []ID{posID}, nil, rel1ID, true, target) })
 }
 
 func TestWorldExchangeBatch(t *testing.T) {
@@ -558,9 +577,9 @@ func TestWorldNewEntities(t *testing.T) {
 	world.NewEntity(posID, rotID)
 	assert.Equal(t, 2, len(world.entities))
 
-	assert.Panics(t, func() { world.newEntitiesQuery(0, -1, Entity{}, posID, rotID) })
+	assert.Panics(t, func() { world.newEntitiesQuery(0, ID{}, false, Entity{}, posID, rotID) })
 
-	query := world.newEntitiesQuery(100, -1, Entity{}, posID, rotID)
+	query := world.newEntitiesQuery(100, ID{}, false, Entity{}, posID, rotID)
 	assert.Equal(t, 100, query.Count())
 	assert.Equal(t, 102, len(world.entities))
 	assert.Equal(t, 1, len(events))
@@ -588,7 +607,7 @@ func TestWorldNewEntities(t *testing.T) {
 	world.Reset()
 	assert.Equal(t, 1, len(world.entities))
 
-	query = world.newEntitiesQuery(100, -1, Entity{}, posID, rotID)
+	query = world.newEntitiesQuery(100, ID{}, false, Entity{}, posID, rotID)
 	assert.Equal(t, 100, query.Count())
 	assert.Equal(t, 101, len(events))
 
@@ -607,13 +626,13 @@ func TestWorldNewEntities(t *testing.T) {
 	assert.Equal(t, 301, len(events))
 	assert.Equal(t, 101, len(world.entities))
 
-	query = world.newEntitiesQuery(100, -1, Entity{}, posID, rotID)
+	query = world.newEntitiesQuery(100, ID{}, false, Entity{}, posID, rotID)
 	assert.Equal(t, 301, len(events))
 	query.Close()
 	assert.Equal(t, 401, len(events))
 	assert.Equal(t, 101, len(world.entities))
 
-	world.newEntities(100, -1, Entity{}, posID, rotID)
+	world.newEntities(100, ID{}, false, Entity{}, posID, rotID)
 	assert.Equal(t, 501, len(events))
 	assert.Equal(t, 201, len(world.entities))
 }
@@ -638,15 +657,15 @@ func TestWorldNewEntitiesWith(t *testing.T) {
 	world.NewEntity(posID, rotID)
 	assert.Equal(t, 1, len(events))
 
-	assert.Panics(t, func() { world.newEntitiesWithQuery(0, -1, Entity{}, comps...) })
+	assert.Panics(t, func() { world.newEntitiesWithQuery(0, ID{}, false, Entity{}, comps...) })
 	assert.Equal(t, 1, len(events))
 
-	query := world.newEntitiesWithQuery(1, -1, Entity{})
+	query := world.newEntitiesWithQuery(1, ID{}, false, Entity{})
 	assert.Equal(t, 1, len(events))
 	query.Close()
 	assert.Equal(t, 2, len(events))
 
-	query = world.newEntitiesWithQuery(100, -1, Entity{}, comps...)
+	query = world.newEntitiesWithQuery(100, ID{}, false, Entity{}, comps...)
 	assert.Equal(t, 100, query.Count())
 	assert.Equal(t, 2, len(events))
 
@@ -674,7 +693,7 @@ func TestWorldNewEntitiesWith(t *testing.T) {
 
 	world.Reset()
 
-	query = world.newEntitiesWithQuery(100, -1, Entity{},
+	query = world.newEntitiesWithQuery(100, ID{}, false, Entity{},
 		Component{ID: posID, Comp: &Position{100, 200}},
 		Component{ID: rotID, Comp: &rotation{300}},
 	)
@@ -688,7 +707,7 @@ func TestWorldNewEntitiesWith(t *testing.T) {
 	assert.Equal(t, 100, cnt)
 	assert.Equal(t, 202, len(events))
 
-	world.newEntitiesWith(100, -1, Entity{}, comps...)
+	world.newEntitiesWith(100, ID{}, false, Entity{}, comps...)
 	assert.Equal(t, 302, len(events))
 }
 
@@ -704,12 +723,12 @@ func TestWorldRemoveEntities(t *testing.T) {
 	posID := ComponentID[Position](&world)
 	rotID := ComponentID[rotation](&world)
 
-	query := world.newEntitiesQuery(100, -1, Entity{}, posID)
+	query := world.newEntitiesQuery(100, ID{}, false, Entity{}, posID)
 	assert.Equal(t, 100, query.Count())
 	query.Close()
 	assert.Equal(t, 100, len(events))
 
-	query = world.newEntitiesQuery(100, -1, Entity{}, posID, rotID)
+	query = world.newEntitiesQuery(100, ID{}, false, Entity{}, posID, rotID)
 	assert.Equal(t, 100, query.Count())
 	query.Close()
 	assert.Equal(t, 200, len(events))
@@ -1099,18 +1118,18 @@ func TestWorldRelationCreate(t *testing.T) {
 	dead := world.NewEntity()
 	world.RemoveEntity(dead)
 
-	world.newEntities(5, int8(relID), alive, posID, relID)
-	assert.Panics(t, func() { world.newEntitiesNoNotify(5, int8(relID), dead, posID, relID) })
+	world.newEntities(5, relID, true, alive, posID, relID)
+	assert.Panics(t, func() { world.newEntitiesNoNotify(5, relID, true, dead, posID, relID) })
 
 	world.newEntityTarget(relID, alive, posID, relID)
 	assert.Panics(t, func() { world.newEntityTarget(relID, dead, posID, relID) })
 
-	world.newEntitiesWith(5, int8(relID), alive,
+	world.newEntitiesWith(5, relID, true, alive,
 		Component{ID: posID, Comp: &Position{}},
 		Component{ID: relID, Comp: &testRelationA{}},
 	)
 	assert.Panics(t, func() {
-		world.newEntitiesWith(5, int8(relID), dead,
+		world.newEntitiesWith(5, relID, true, dead,
 			Component{ID: posID, Comp: &Position{}},
 			Component{ID: relID, Comp: &testRelationA{}},
 		)
@@ -1310,7 +1329,7 @@ func TestWorldComponentType(t *testing.T) {
 	assert.True(t, ok)
 	assert.Equal(t, reflect.TypeOf(rotation{}), tp)
 
-	_, ok = w.ComponentType(2)
+	_, ok = w.ComponentType(id(2))
 	assert.False(t, ok)
 }
 
@@ -1319,8 +1338,8 @@ func TestRegisterComponents(t *testing.T) {
 
 	ComponentID[Position](&world)
 
-	assert.Equal(t, ID(0), ComponentID[Position](&world))
-	assert.Equal(t, ID(1), ComponentID[rotation](&world))
+	assert.Equal(t, id(0), ComponentID[Position](&world))
+	assert.Equal(t, id(1), ComponentID[rotation](&world))
 }
 
 func TestWorldBatchRemove(t *testing.T) {
@@ -1432,7 +1451,7 @@ func TestArchetypeGraph(t *testing.T) {
 	arch01 := world.findOrCreateArchetype(arch0, []ID{velID}, []ID{}, Entity{})
 	arch012 := world.findOrCreateArchetype(arch01, []ID{rotID}, []ID{}, Entity{})
 
-	assert.Equal(t, []ID{0, 1, 2}, arch012.node.Ids)
+	assert.Equal(t, []ID{id(0), id(1), id(2)}, arch012.node.Ids)
 
 	archEmpty4 := world.findOrCreateArchetype(arch012, []ID{}, []ID{posID, rotID, velID}, Entity{})
 	assert.Equal(t, archEmpty, archEmpty4)
@@ -1637,7 +1656,7 @@ func Test1000Archetypes(t *testing.T) {
 		mask := Mask{[4]uint64{uint64(i), 0, 0, 0}}
 		add := make([]ID, 0, 10)
 		for j := 0; j < 10; j++ {
-			id := ID(j)
+			id := id(uint8(j))
 			if mask.Get(id) {
 				add = append(add, id)
 			}
@@ -1648,7 +1667,7 @@ func Test1000Archetypes(t *testing.T) {
 	assert.Equal(t, int32(1024), w.archetypes.Len())
 
 	cnt := 0
-	query := w.Query(All(0, 7))
+	query := w.Query(All(id(0), id(7)))
 	for query.Next() {
 		cnt++
 	}
@@ -1788,7 +1807,7 @@ func TestTypeSizes(t *testing.T) {
 	printTypeSize[nodeData]()
 	printTypeSize[layout]()
 	printTypeSize[entityPool]()
-	printTypeSizeName[componentRegistry[ID]]("componentRegistry")
+	printTypeSize[componentRegistry]()
 	printTypeSize[bitPool]()
 	printTypeSize[Query]()
 	printTypeSize[Resources]()
diff --git a/filter/filter_test.go b/filter/filter_test.go
index 8795e01..5cd2269 100644
--- a/filter/filter_test.go
+++ b/filter/filter_test.go
@@ -1,9 +1,10 @@
-package filter
+package filter_test
 
 import (
 	"testing"
 
 	"github.com/mlange-42/arche/ecs"
+	f "github.com/mlange-42/arche/filter"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -16,11 +17,54 @@ type rotation struct {
 	Angle int
 }
 
+type TestStruct0 struct{ Val int32 }
+type TestStruct1 struct{ Val int32 }
+type TestStruct2 struct{ Val int32 }
+type TestStruct3 struct{ Val int32 }
+type TestStruct4 struct{ Val int32 }
+type TestStruct5 struct{ Val int32 }
+type TestStruct6 struct{ Val int32 }
+type TestStruct7 struct{ Val int32 }
+type TestStruct8 struct{ Val int32 }
+type TestStruct9 struct{ Val int32 }
+type TestStruct10 struct{ Val int32 }
+
+func RegisterAll(w *ecs.World) []ecs.ID {
+	_ = TestStruct0{1}
+	_ = TestStruct1{1}
+	_ = TestStruct2{1}
+	_ = TestStruct3{1}
+	_ = TestStruct4{1}
+	_ = TestStruct5{1}
+	_ = TestStruct6{1}
+	_ = TestStruct7{1}
+	_ = TestStruct8{1}
+	_ = TestStruct9{1}
+	_ = TestStruct10{1}
+
+	ids := make([]ecs.ID, 11)
+	ids[0] = ecs.ComponentID[TestStruct0](w)
+	ids[1] = ecs.ComponentID[TestStruct1](w)
+	ids[2] = ecs.ComponentID[TestStruct2](w)
+	ids[3] = ecs.ComponentID[TestStruct3](w)
+	ids[4] = ecs.ComponentID[TestStruct4](w)
+	ids[5] = ecs.ComponentID[TestStruct5](w)
+	ids[6] = ecs.ComponentID[TestStruct6](w)
+	ids[7] = ecs.ComponentID[TestStruct7](w)
+	ids[8] = ecs.ComponentID[TestStruct8](w)
+	ids[9] = ecs.ComponentID[TestStruct9](w)
+	ids[10] = ecs.ComponentID[TestStruct10](w)
+
+	return ids
+}
+
 func TestLogicFilters(t *testing.T) {
+	w := ecs.NewWorld()
+	ids := RegisterAll(&w)
 
-	hasA := ecs.All(0)
-	hasB := ecs.All(1)
-	hasAll := ecs.All(0, 1)
+	hasA := ecs.All(ids[0])
+	hasB := ecs.All(ids[1])
+	hasAll := ecs.All(ids[0], ids[1])
 	hasNone := ecs.All()
 
 	var filter ecs.Filter
@@ -30,31 +74,31 @@ func TestLogicFilters(t *testing.T) {
 	assert.False(t, match(filter, hasB))
 	assert.False(t, match(filter, hasNone))
 
-	filter = All(0, 1)
+	filter = f.All(ids[0], ids[1])
 	assert.True(t, match(filter, hasAll))
 	assert.False(t, match(filter, hasA))
 	assert.False(t, match(filter, hasB))
 	assert.False(t, match(filter, hasNone))
 
-	filter = Not(All(0, 1))
+	filter = f.Not(f.All(ids[0], ids[1]))
 	assert.False(t, match(filter, hasAll))
 	assert.True(t, match(filter, hasA))
 	assert.True(t, match(filter, hasB))
 	assert.True(t, match(filter, hasNone))
 
-	filter = Any(0, 1)
+	filter = f.Any(ids[0], ids[1])
 	assert.True(t, match(filter, hasAll))
 	assert.True(t, match(filter, hasA))
 	assert.True(t, match(filter, hasB))
 	assert.False(t, match(filter, hasNone))
 
-	filter = Or(hasA, hasB)
+	filter = f.Or(hasA, hasB)
 	assert.True(t, match(filter, hasAll))
 	assert.True(t, match(filter, hasA))
 	assert.True(t, match(filter, hasB))
 	assert.False(t, match(filter, hasNone))
 
-	filter = XOr(hasA, hasB)
+	filter = f.XOr(hasA, hasB)
 	assert.False(t, match(filter, hasAll))
 	assert.True(t, match(filter, hasA))
 	assert.True(t, match(filter, hasB))
@@ -66,49 +110,49 @@ func TestLogicFilters(t *testing.T) {
 	assert.False(t, match(filter, hasB))
 	assert.False(t, match(filter, hasNone))
 
-	filter = And(hasA, hasB)
+	filter = f.And(hasA, hasB)
 	assert.True(t, match(filter, hasAll))
 	assert.False(t, match(filter, hasA))
 	assert.False(t, match(filter, hasB))
 	assert.False(t, match(filter, hasNone))
 
-	filter = AnyNot(1)
+	filter = f.AnyNot(ids[1])
 	assert.False(t, match(filter, hasAll))
 	assert.True(t, match(filter, hasA))
 	assert.False(t, match(filter, hasB))
 	assert.True(t, match(filter, hasNone))
 
-	filter = AnyNot(0, 1)
+	filter = f.AnyNot(ids[0], ids[1])
 	assert.False(t, match(filter, hasAll))
 	assert.True(t, match(filter, hasA))
 	assert.True(t, match(filter, hasB))
 	assert.True(t, match(filter, hasNone))
 
-	filter = NoneOf(0)
+	filter = f.NoneOf(ids[0])
 	assert.False(t, match(filter, hasAll))
 	assert.False(t, match(filter, hasA))
 	assert.True(t, match(filter, hasB))
 	assert.True(t, match(filter, hasNone))
 
-	filter = NoneOf(0, 1)
+	filter = f.NoneOf(ids[0], ids[1])
 	assert.False(t, match(filter, hasAll))
 	assert.False(t, match(filter, hasA))
 	assert.False(t, match(filter, hasB))
 	assert.True(t, match(filter, hasNone))
 
-	filter = And(hasA, AnyNOT(hasB))
+	filter = f.And(hasA, f.AnyNOT(hasB))
 	assert.False(t, match(filter, hasAll))
 	assert.True(t, match(filter, hasA))
 	assert.False(t, match(filter, hasB))
 	assert.False(t, match(filter, hasNone))
 
-	filter = Or(hasAll, AnyNot(1))
+	filter = f.Or(hasAll, f.AnyNot(ids[1]))
 	assert.True(t, match(filter, hasAll))
 	assert.True(t, match(filter, hasA))
 	assert.False(t, match(filter, hasB))
 	assert.True(t, match(filter, hasNone))
 
-	filter = Or(Any(0, 1), AnyNOT(hasB))
+	filter = f.Or(f.Any(ids[0], ids[1]), f.AnyNOT(hasB))
 	assert.True(t, match(filter, hasAll))
 	assert.True(t, match(filter, hasA))
 	assert.True(t, match(filter, hasB))
@@ -133,7 +177,7 @@ func TestFilter(t *testing.T) {
 	w.Add(e3, rotID)
 	w.Add(e4, rotID)
 
-	q := w.Query(All(posID, rotID))
+	q := w.Query(f.All(posID, rotID))
 	cnt := 0
 	for q.Next() {
 		ent := q.Entity()
@@ -146,7 +190,7 @@ func TestFilter(t *testing.T) {
 	}
 	assert.Equal(t, 2, cnt)
 
-	q = w.Query(All(posID))
+	q = w.Query(f.All(posID))
 	cnt = 0
 	for q.Next() {
 		ent := q.Entity()
@@ -157,7 +201,7 @@ func TestFilter(t *testing.T) {
 	}
 	assert.Equal(t, 3, cnt)
 
-	q = w.Query(All(rotID))
+	q = w.Query(f.All(rotID))
 	cnt = 0
 	for q.Next() {
 		ent := q.Entity()
@@ -172,7 +216,7 @@ func TestFilter(t *testing.T) {
 
 	assert.Panics(t, func() { q.Next() })
 
-	q = w.Query(&AND{L: All(rotID), R: AnyNOT(All(posID))})
+	q = w.Query(&f.AND{L: f.All(rotID), R: f.AnyNOT(f.All(posID))})
 
 	cnt = 0
 	for q.Next() {
@@ -191,16 +235,18 @@ func TestInterface(t *testing.T) {
 
 	posID := ecs.ComponentID[position](&w)
 
-	f := w.Query(All(posID))
+	f := w.Query(f.All(posID))
 	var f2 *ecs.Query = &f
 	_ = f2
 }
 
 func BenchmarkFilterStackOr(b *testing.B) {
 	b.StopTimer()
-	mask := All(1, 2, 3, 4, 5)
+	w := ecs.NewWorld()
+	ids := RegisterAll(&w)
+	mask := f.All(ids[1], ids[2], ids[3], ids[4], ids[5])
 
-	filter := OR{All(1), All(2)}
+	filter := f.OR{f.All(ids[1]), f.All(ids[2])}
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
@@ -210,9 +256,11 @@ func BenchmarkFilterStackOr(b *testing.B) {
 
 func BenchmarkFilterHeapOr(b *testing.B) {
 	b.StopTimer()
-	mask := All(1, 2, 3, 4, 5)
+	w := ecs.NewWorld()
+	ids := RegisterAll(&w)
+	mask := f.All(ids[1], ids[2], ids[3], ids[4], ids[5])
 
-	filter := Or(All(1), All(2))
+	filter := f.Or(f.All(ids[1]), f.All(ids[2]))
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
@@ -222,12 +270,14 @@ func BenchmarkFilterHeapOr(b *testing.B) {
 
 func BenchmarkFilterStack5And(b *testing.B) {
 	b.StopTimer()
-	mask := All(1, 2, 3, 4, 5)
+	w := ecs.NewWorld()
+	ids := RegisterAll(&w)
+	mask := f.All(ids[1], ids[2], ids[3], ids[4], ids[5])
 
-	a1 := AND{All(1), All(2)}
-	a2 := AND{&a1, All(3)}
-	a3 := AND{&a2, All(4)}
-	filter := AND{&a3, All(5)}
+	a1 := f.AND{f.All(ids[1]), f.All(ids[2])}
+	a2 := f.AND{&a1, f.All(ids[3])}
+	a3 := f.AND{&a2, f.All(ids[4])}
+	filter := f.AND{&a3, f.All(ids[5])}
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
@@ -237,9 +287,11 @@ func BenchmarkFilterStack5And(b *testing.B) {
 
 func BenchmarkFilterHeap5And(b *testing.B) {
 	b.StopTimer()
-	mask := All(1, 2, 3, 4, 5)
+	w := ecs.NewWorld()
+	ids := RegisterAll(&w)
+	mask := f.All(ids[1], ids[2], ids[3], ids[4], ids[5])
 
-	filter := And(All(1), And(All(2), And(All(3), And(All(4), All(5)))))
+	filter := f.And(f.All(ids[1]), f.And(f.All(ids[2]), f.And(f.All(ids[3]), f.And(f.All(ids[4]), f.All(ids[5])))))
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
diff --git a/generic/query_test.go b/generic/query_test.go
index 635c59f..685b8e9 100644
--- a/generic/query_test.go
+++ b/generic/query_test.go
@@ -10,24 +10,24 @@ import (
 func TestQueryOptionalNot(t *testing.T) {
 	w := ecs.NewWorld()
 
-	registerAll(&w)
+	ids := registerAll(&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
 	e2 := w.NewEntity()
 
-	w.Assign(e0, ecs.Component{ID: 0, Comp: &testStruct0{1}})
-	w.Assign(e0, ecs.Component{ID: 1, Comp: &testStruct1{1}})
+	w.Assign(e0, ecs.Component{ID: ids[0], Comp: &testStruct0{1}})
+	w.Assign(e0, ecs.Component{ID: ids[1], Comp: &testStruct1{1}})
 
-	w.Assign(e1, ecs.Component{ID: 0, Comp: &testStruct0{2}})
-	w.Assign(e1, ecs.Component{ID: 1, Comp: &testStruct1{2}})
-	w.Assign(e1, ecs.Component{ID: 2, Comp: &testStruct2{1, 1}})
-	w.Assign(e1, ecs.Component{ID: 8, Comp: &testStruct8{}})
+	w.Assign(e1, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
+	w.Assign(e1, ecs.Component{ID: ids[1], Comp: &testStruct1{2}})
+	w.Assign(e1, ecs.Component{ID: ids[2], Comp: &testStruct2{1, 1}})
+	w.Assign(e1, ecs.Component{ID: ids[8], Comp: &testStruct8{}})
 
-	w.Assign(e2, ecs.Component{ID: 0, Comp: &testStruct0{3}})
-	w.Assign(e2, ecs.Component{ID: 1, Comp: &testStruct1{3}})
-	w.Assign(e2, ecs.Component{ID: 2, Comp: &testStruct2{1, 1}})
-	w.Assign(e2, ecs.Component{ID: 9, Comp: &testStruct9{}})
+	w.Assign(e2, ecs.Component{ID: ids[0], Comp: &testStruct0{3}})
+	w.Assign(e2, ecs.Component{ID: ids[1], Comp: &testStruct1{3}})
+	w.Assign(e2, ecs.Component{ID: ids[2], Comp: &testStruct2{1, 1}})
+	w.Assign(e2, ecs.Component{ID: ids[9], Comp: &testStruct9{}})
 
 	query2 := NewFilter2[testStruct0, testStruct1]().Query(&w)
 	cnt := 0
@@ -81,20 +81,20 @@ func TestQueryOptionalNot(t *testing.T) {
 func TestQuery0(t *testing.T) {
 	w := ecs.NewWorld()
 
-	registerAll(&w)
+	ids := registerAll(&w)
 	relID := ecs.ComponentID[testRelationA](&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
 	e2 := w.NewEntity()
 
-	w.Assign(e0, ecs.Component{ID: 0, Comp: &testStruct0{1}})
-	w.Assign(e0, ecs.Component{ID: 8, Comp: &testStruct8{}})
+	w.Assign(e0, ecs.Component{ID: ids[0], Comp: &testStruct0{1}})
+	w.Assign(e0, ecs.Component{ID: ids[8], Comp: &testStruct8{}})
 
-	w.Assign(e1, ecs.Component{ID: 0, Comp: &testStruct0{2}})
+	w.Assign(e1, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
 
-	w.Assign(e2, ecs.Component{ID: 0, Comp: &testStruct0{2}})
-	w.Assign(e2, ecs.Component{ID: 9, Comp: &testStruct9{}})
+	w.Assign(e2, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
+	w.Assign(e2, ecs.Component{ID: ids[9], Comp: &testStruct9{}})
 
 	cnt := 0
 	filter :=
@@ -115,7 +115,7 @@ func TestQuery0(t *testing.T) {
 	filter.Unregister(&w)
 	assert.Panics(t, func() { filter.Unregister(&w) })
 
-	targ := w.NewEntity(0)
+	targ := w.NewEntity(ids[0])
 
 	w.Add(e0, relID)
 	w.Add(e1, relID)
@@ -159,21 +159,21 @@ func TestQuery0(t *testing.T) {
 func TestQuery1(t *testing.T) {
 	w := ecs.NewWorld()
 
-	registerAll(&w)
+	ids := registerAll(&w)
 	relID := ecs.ComponentID[testRelationA](&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
 	e2 := w.NewEntity()
 
-	w.Assign(e0, ecs.Component{ID: 0, Comp: &testStruct0{1}})
+	w.Assign(e0, ecs.Component{ID: ids[0], Comp: &testStruct0{1}})
 
-	w.Assign(e1, ecs.Component{ID: 0, Comp: &testStruct0{2}})
+	w.Assign(e1, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
 
-	w.Assign(e0, ecs.Component{ID: 8, Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[8], Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e2, ecs.Component{ID: 0, Comp: &testStruct0{0}})
-	w.Assign(e2, ecs.Component{ID: 9, Comp: &testStruct9{}})
+	w.Assign(e2, ecs.Component{ID: ids[0], Comp: &testStruct0{0}})
+	w.Assign(e2, ecs.Component{ID: ids[9], Comp: &testStruct9{}})
 
 	cnt := 0
 	filter :=
@@ -196,7 +196,7 @@ func TestQuery1(t *testing.T) {
 	filter.Unregister(&w)
 	assert.Panics(t, func() { filter.Unregister(&w) })
 
-	targ := w.NewEntity(0)
+	targ := w.NewEntity(ids[0])
 
 	w.Add(e0, relID)
 	w.Add(e1, relID)
@@ -239,24 +239,24 @@ func TestQuery1(t *testing.T) {
 func TestQuery2(t *testing.T) {
 	w := ecs.NewWorld()
 
-	registerAll(&w)
+	ids := registerAll(&w)
 	relID := ecs.ComponentID[testRelationA](&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
 	e2 := w.NewEntity()
 
-	w.Assign(e0, ecs.Component{ID: 0, Comp: &testStruct0{1}})
-	w.Assign(e1, ecs.Component{ID: 0, Comp: &testStruct0{2}})
-	w.Assign(e2, ecs.Component{ID: 0, Comp: &testStruct0{3}})
+	w.Assign(e0, ecs.Component{ID: ids[0], Comp: &testStruct0{1}})
+	w.Assign(e1, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
+	w.Assign(e2, ecs.Component{ID: ids[0], Comp: &testStruct0{3}})
 
-	w.Assign(e0, ecs.Component{ID: 1, Comp: &testStruct1{2}})
-	w.Assign(e1, ecs.Component{ID: 1, Comp: &testStruct1{3}})
-	w.Assign(e2, ecs.Component{ID: 1, Comp: &testStruct1{4}})
+	w.Assign(e0, ecs.Component{ID: ids[1], Comp: &testStruct1{2}})
+	w.Assign(e1, ecs.Component{ID: ids[1], Comp: &testStruct1{3}})
+	w.Assign(e2, ecs.Component{ID: ids[1], Comp: &testStruct1{4}})
 
-	w.Assign(e0, ecs.Component{ID: 8, Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[8], Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e2, ecs.Component{ID: 9, Comp: &testStruct9{}})
+	w.Assign(e2, ecs.Component{ID: ids[9], Comp: &testStruct9{}})
 
 	filter :=
 		NewFilter2[testStruct0, testStruct1]().
@@ -282,7 +282,7 @@ func TestQuery2(t *testing.T) {
 	filter.Unregister(&w)
 	assert.Panics(t, func() { filter.Unregister(&w) })
 
-	targ := w.NewEntity(0)
+	targ := w.NewEntity(ids[0])
 
 	w.Add(e0, relID)
 	w.Add(e1, relID)
@@ -327,28 +327,28 @@ func TestQuery2(t *testing.T) {
 func TestQuery3(t *testing.T) {
 	w := ecs.NewWorld()
 
-	registerAll(&w)
+	ids := registerAll(&w)
 	relID := ecs.ComponentID[testRelationA](&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
 	e2 := w.NewEntity()
 
-	w.Assign(e0, ecs.Component{ID: 0, Comp: &testStruct0{1}})
-	w.Assign(e1, ecs.Component{ID: 0, Comp: &testStruct0{2}})
-	w.Assign(e2, ecs.Component{ID: 0, Comp: &testStruct0{3}})
+	w.Assign(e0, ecs.Component{ID: ids[0], Comp: &testStruct0{1}})
+	w.Assign(e1, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
+	w.Assign(e2, ecs.Component{ID: ids[0], Comp: &testStruct0{3}})
 
-	w.Assign(e0, ecs.Component{ID: 1, Comp: &testStruct1{2}})
-	w.Assign(e1, ecs.Component{ID: 1, Comp: &testStruct1{3}})
-	w.Assign(e2, ecs.Component{ID: 1, Comp: &testStruct1{4}})
+	w.Assign(e0, ecs.Component{ID: ids[1], Comp: &testStruct1{2}})
+	w.Assign(e1, ecs.Component{ID: ids[1], Comp: &testStruct1{3}})
+	w.Assign(e2, ecs.Component{ID: ids[1], Comp: &testStruct1{4}})
 
-	w.Assign(e0, ecs.Component{ID: 2, Comp: &testStruct2{3, 0}})
-	w.Assign(e1, ecs.Component{ID: 2, Comp: &testStruct2{4, 0}})
-	w.Assign(e2, ecs.Component{ID: 2, Comp: &testStruct2{5, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[2], Comp: &testStruct2{3, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[2], Comp: &testStruct2{4, 0}})
+	w.Assign(e2, ecs.Component{ID: ids[2], Comp: &testStruct2{5, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 8, Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[8], Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e2, ecs.Component{ID: 9, Comp: &testStruct9{}})
+	w.Assign(e2, ecs.Component{ID: ids[9], Comp: &testStruct9{}})
 
 	cnt := 0
 	filter :=
@@ -373,7 +373,7 @@ func TestQuery3(t *testing.T) {
 	filter.Unregister(&w)
 	assert.Panics(t, func() { filter.Unregister(&w) })
 
-	targ := w.NewEntity(0)
+	targ := w.NewEntity(ids[0])
 
 	w.Add(e0, relID)
 	w.Add(e1, relID)
@@ -418,32 +418,32 @@ func TestQuery3(t *testing.T) {
 func TestQuery4(t *testing.T) {
 	w := ecs.NewWorld()
 
-	registerAll(&w)
+	ids := registerAll(&w)
 	relID := ecs.ComponentID[testRelationA](&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
 	e2 := w.NewEntity()
 
-	w.Assign(e0, ecs.Component{ID: 0, Comp: &testStruct0{1}})
-	w.Assign(e1, ecs.Component{ID: 0, Comp: &testStruct0{2}})
-	w.Assign(e2, ecs.Component{ID: 0, Comp: &testStruct0{2}})
+	w.Assign(e0, ecs.Component{ID: ids[0], Comp: &testStruct0{1}})
+	w.Assign(e1, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
+	w.Assign(e2, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
 
-	w.Assign(e0, ecs.Component{ID: 1, Comp: &testStruct1{2}})
-	w.Assign(e1, ecs.Component{ID: 1, Comp: &testStruct1{3}})
-	w.Assign(e2, ecs.Component{ID: 1, Comp: &testStruct1{3}})
+	w.Assign(e0, ecs.Component{ID: ids[1], Comp: &testStruct1{2}})
+	w.Assign(e1, ecs.Component{ID: ids[1], Comp: &testStruct1{3}})
+	w.Assign(e2, ecs.Component{ID: ids[1], Comp: &testStruct1{3}})
 
-	w.Assign(e0, ecs.Component{ID: 2, Comp: &testStruct2{3, 0}})
-	w.Assign(e1, ecs.Component{ID: 2, Comp: &testStruct2{4, 0}})
-	w.Assign(e2, ecs.Component{ID: 2, Comp: &testStruct2{4, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[2], Comp: &testStruct2{3, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[2], Comp: &testStruct2{4, 0}})
+	w.Assign(e2, ecs.Component{ID: ids[2], Comp: &testStruct2{4, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 3, Comp: &testStruct3{4, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 3, Comp: &testStruct3{5, 0, 0}})
-	w.Assign(e2, ecs.Component{ID: 3, Comp: &testStruct3{5, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[3], Comp: &testStruct3{4, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[3], Comp: &testStruct3{5, 0, 0}})
+	w.Assign(e2, ecs.Component{ID: ids[3], Comp: &testStruct3{5, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 8, Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[8], Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e2, ecs.Component{ID: 9, Comp: &testStruct9{}})
+	w.Assign(e2, ecs.Component{ID: ids[9], Comp: &testStruct9{}})
 
 	cnt := 0
 	filter :=
@@ -469,7 +469,7 @@ func TestQuery4(t *testing.T) {
 	filter.Unregister(&w)
 	assert.Panics(t, func() { filter.Unregister(&w) })
 
-	targ := w.NewEntity(0)
+	targ := w.NewEntity(ids[0])
 
 	w.Add(e0, relID)
 	w.Add(e1, relID)
@@ -516,36 +516,36 @@ func TestQuery4(t *testing.T) {
 func TestQuery5(t *testing.T) {
 	w := ecs.NewWorld()
 
-	registerAll(&w)
+	ids := registerAll(&w)
 	relID := ecs.ComponentID[testRelationA](&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
 	e2 := w.NewEntity()
 
-	w.Assign(e0, ecs.Component{ID: 0, Comp: &testStruct0{1}})
-	w.Assign(e1, ecs.Component{ID: 0, Comp: &testStruct0{2}})
-	w.Assign(e2, ecs.Component{ID: 0, Comp: &testStruct0{2}})
+	w.Assign(e0, ecs.Component{ID: ids[0], Comp: &testStruct0{1}})
+	w.Assign(e1, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
+	w.Assign(e2, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
 
-	w.Assign(e0, ecs.Component{ID: 1, Comp: &testStruct1{2}})
-	w.Assign(e1, ecs.Component{ID: 1, Comp: &testStruct1{3}})
-	w.Assign(e2, ecs.Component{ID: 1, Comp: &testStruct1{3}})
+	w.Assign(e0, ecs.Component{ID: ids[1], Comp: &testStruct1{2}})
+	w.Assign(e1, ecs.Component{ID: ids[1], Comp: &testStruct1{3}})
+	w.Assign(e2, ecs.Component{ID: ids[1], Comp: &testStruct1{3}})
 
-	w.Assign(e0, ecs.Component{ID: 2, Comp: &testStruct2{3, 0}})
-	w.Assign(e1, ecs.Component{ID: 2, Comp: &testStruct2{4, 0}})
-	w.Assign(e2, ecs.Component{ID: 2, Comp: &testStruct2{4, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[2], Comp: &testStruct2{3, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[2], Comp: &testStruct2{4, 0}})
+	w.Assign(e2, ecs.Component{ID: ids[2], Comp: &testStruct2{4, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 3, Comp: &testStruct3{4, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 3, Comp: &testStruct3{5, 0, 0}})
-	w.Assign(e2, ecs.Component{ID: 3, Comp: &testStruct3{5, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[3], Comp: &testStruct3{4, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[3], Comp: &testStruct3{5, 0, 0}})
+	w.Assign(e2, ecs.Component{ID: ids[3], Comp: &testStruct3{5, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 4, Comp: &testStruct4{5, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 4, Comp: &testStruct4{6, 0, 0, 0}})
-	w.Assign(e2, ecs.Component{ID: 4, Comp: &testStruct4{6, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[4], Comp: &testStruct4{5, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[4], Comp: &testStruct4{6, 0, 0, 0}})
+	w.Assign(e2, ecs.Component{ID: ids[4], Comp: &testStruct4{6, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 8, Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[8], Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e2, ecs.Component{ID: 9, Comp: &testStruct9{}})
+	w.Assign(e2, ecs.Component{ID: ids[9], Comp: &testStruct9{}})
 
 	cnt := 0
 	filter :=
@@ -572,7 +572,7 @@ func TestQuery5(t *testing.T) {
 	filter.Unregister(&w)
 	assert.Panics(t, func() { filter.Unregister(&w) })
 
-	targ := w.NewEntity(0)
+	targ := w.NewEntity(ids[0])
 
 	w.Add(e0, relID)
 	w.Add(e1, relID)
@@ -621,40 +621,40 @@ func TestQuery5(t *testing.T) {
 func TestQuery6(t *testing.T) {
 	w := ecs.NewWorld()
 
-	registerAll(&w)
+	ids := registerAll(&w)
 	relID := ecs.ComponentID[testRelationA](&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
 	e2 := w.NewEntity()
 
-	w.Assign(e0, ecs.Component{ID: 0, Comp: &testStruct0{1}})
-	w.Assign(e1, ecs.Component{ID: 0, Comp: &testStruct0{2}})
-	w.Assign(e2, ecs.Component{ID: 0, Comp: &testStruct0{2}})
+	w.Assign(e0, ecs.Component{ID: ids[0], Comp: &testStruct0{1}})
+	w.Assign(e1, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
+	w.Assign(e2, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
 
-	w.Assign(e0, ecs.Component{ID: 1, Comp: &testStruct1{2}})
-	w.Assign(e1, ecs.Component{ID: 1, Comp: &testStruct1{3}})
-	w.Assign(e2, ecs.Component{ID: 1, Comp: &testStruct1{3}})
+	w.Assign(e0, ecs.Component{ID: ids[1], Comp: &testStruct1{2}})
+	w.Assign(e1, ecs.Component{ID: ids[1], Comp: &testStruct1{3}})
+	w.Assign(e2, ecs.Component{ID: ids[1], Comp: &testStruct1{3}})
 
-	w.Assign(e0, ecs.Component{ID: 2, Comp: &testStruct2{3, 0}})
-	w.Assign(e1, ecs.Component{ID: 2, Comp: &testStruct2{4, 0}})
-	w.Assign(e2, ecs.Component{ID: 2, Comp: &testStruct2{4, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[2], Comp: &testStruct2{3, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[2], Comp: &testStruct2{4, 0}})
+	w.Assign(e2, ecs.Component{ID: ids[2], Comp: &testStruct2{4, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 3, Comp: &testStruct3{4, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 3, Comp: &testStruct3{5, 0, 0}})
-	w.Assign(e2, ecs.Component{ID: 3, Comp: &testStruct3{5, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[3], Comp: &testStruct3{4, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[3], Comp: &testStruct3{5, 0, 0}})
+	w.Assign(e2, ecs.Component{ID: ids[3], Comp: &testStruct3{5, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 4, Comp: &testStruct4{5, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 4, Comp: &testStruct4{6, 0, 0, 0}})
-	w.Assign(e2, ecs.Component{ID: 4, Comp: &testStruct4{6, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[4], Comp: &testStruct4{5, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[4], Comp: &testStruct4{6, 0, 0, 0}})
+	w.Assign(e2, ecs.Component{ID: ids[4], Comp: &testStruct4{6, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 5, Comp: &testStruct5{6, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 5, Comp: &testStruct5{7, 0, 0, 0, 0}})
-	w.Assign(e2, ecs.Component{ID: 5, Comp: &testStruct5{7, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[5], Comp: &testStruct5{6, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[5], Comp: &testStruct5{7, 0, 0, 0, 0}})
+	w.Assign(e2, ecs.Component{ID: ids[5], Comp: &testStruct5{7, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 8, Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[8], Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e2, ecs.Component{ID: 9, Comp: &testStruct9{}})
+	w.Assign(e2, ecs.Component{ID: ids[9], Comp: &testStruct9{}})
 
 	cnt := 0
 	filter :=
@@ -682,7 +682,7 @@ func TestQuery6(t *testing.T) {
 	filter.Unregister(&w)
 	assert.Panics(t, func() { filter.Unregister(&w) })
 
-	targ := w.NewEntity(0)
+	targ := w.NewEntity(ids[0])
 
 	w.Add(e0, relID)
 	w.Add(e1, relID)
@@ -731,44 +731,44 @@ func TestQuery6(t *testing.T) {
 func TestQuery7(t *testing.T) {
 	w := ecs.NewWorld()
 
-	registerAll(&w)
+	ids := registerAll(&w)
 	relID := ecs.ComponentID[testRelationA](&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
 	e2 := w.NewEntity()
 
-	w.Assign(e0, ecs.Component{ID: 0, Comp: &testStruct0{1}})
-	w.Assign(e1, ecs.Component{ID: 0, Comp: &testStruct0{2}})
-	w.Assign(e2, ecs.Component{ID: 0, Comp: &testStruct0{2}})
+	w.Assign(e0, ecs.Component{ID: ids[0], Comp: &testStruct0{1}})
+	w.Assign(e1, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
+	w.Assign(e2, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
 
-	w.Assign(e0, ecs.Component{ID: 1, Comp: &testStruct1{2}})
-	w.Assign(e1, ecs.Component{ID: 1, Comp: &testStruct1{3}})
-	w.Assign(e2, ecs.Component{ID: 1, Comp: &testStruct1{3}})
+	w.Assign(e0, ecs.Component{ID: ids[1], Comp: &testStruct1{2}})
+	w.Assign(e1, ecs.Component{ID: ids[1], Comp: &testStruct1{3}})
+	w.Assign(e2, ecs.Component{ID: ids[1], Comp: &testStruct1{3}})
 
-	w.Assign(e0, ecs.Component{ID: 2, Comp: &testStruct2{3, 0}})
-	w.Assign(e1, ecs.Component{ID: 2, Comp: &testStruct2{4, 0}})
-	w.Assign(e2, ecs.Component{ID: 2, Comp: &testStruct2{4, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[2], Comp: &testStruct2{3, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[2], Comp: &testStruct2{4, 0}})
+	w.Assign(e2, ecs.Component{ID: ids[2], Comp: &testStruct2{4, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 3, Comp: &testStruct3{4, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 3, Comp: &testStruct3{5, 0, 0}})
-	w.Assign(e2, ecs.Component{ID: 3, Comp: &testStruct3{5, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[3], Comp: &testStruct3{4, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[3], Comp: &testStruct3{5, 0, 0}})
+	w.Assign(e2, ecs.Component{ID: ids[3], Comp: &testStruct3{5, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 4, Comp: &testStruct4{5, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 4, Comp: &testStruct4{6, 0, 0, 0}})
-	w.Assign(e2, ecs.Component{ID: 4, Comp: &testStruct4{6, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[4], Comp: &testStruct4{5, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[4], Comp: &testStruct4{6, 0, 0, 0}})
+	w.Assign(e2, ecs.Component{ID: ids[4], Comp: &testStruct4{6, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 5, Comp: &testStruct5{6, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 5, Comp: &testStruct5{7, 0, 0, 0, 0}})
-	w.Assign(e2, ecs.Component{ID: 5, Comp: &testStruct5{7, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[5], Comp: &testStruct5{6, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[5], Comp: &testStruct5{7, 0, 0, 0, 0}})
+	w.Assign(e2, ecs.Component{ID: ids[5], Comp: &testStruct5{7, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 6, Comp: &testStruct6{7, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 6, Comp: &testStruct6{8, 0, 0, 0, 0, 0}})
-	w.Assign(e2, ecs.Component{ID: 6, Comp: &testStruct6{8, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[6], Comp: &testStruct6{7, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[6], Comp: &testStruct6{8, 0, 0, 0, 0, 0}})
+	w.Assign(e2, ecs.Component{ID: ids[6], Comp: &testStruct6{8, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 8, Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[8], Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e2, ecs.Component{ID: 9, Comp: &testStruct9{}})
+	w.Assign(e2, ecs.Component{ID: ids[9], Comp: &testStruct9{}})
 
 	cnt := 0
 	filter :=
@@ -800,7 +800,7 @@ func TestQuery7(t *testing.T) {
 	filter.Unregister(&w)
 	assert.Panics(t, func() { filter.Unregister(&w) })
 
-	targ := w.NewEntity(0)
+	targ := w.NewEntity(ids[0])
 
 	w.Add(e0, relID)
 	w.Add(e1, relID)
@@ -849,40 +849,40 @@ func TestQuery7(t *testing.T) {
 func TestQuery8(t *testing.T) {
 	w := ecs.NewWorld()
 
-	registerAll(&w)
+	ids := registerAll(&w)
 	relID := ecs.ComponentID[testRelationA](&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
 	e2 := w.NewEntity()
 
-	w.Assign(e0, ecs.Component{ID: 0, Comp: &testStruct0{1}})
-	w.Assign(e1, ecs.Component{ID: 0, Comp: &testStruct0{2}})
+	w.Assign(e0, ecs.Component{ID: ids[0], Comp: &testStruct0{1}})
+	w.Assign(e1, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
 
-	w.Assign(e0, ecs.Component{ID: 1, Comp: &testStruct1{2}})
-	w.Assign(e1, ecs.Component{ID: 1, Comp: &testStruct1{3}})
+	w.Assign(e0, ecs.Component{ID: ids[1], Comp: &testStruct1{2}})
+	w.Assign(e1, ecs.Component{ID: ids[1], Comp: &testStruct1{3}})
 
-	w.Assign(e0, ecs.Component{ID: 2, Comp: &testStruct2{3, 0}})
-	w.Assign(e1, ecs.Component{ID: 2, Comp: &testStruct2{4, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[2], Comp: &testStruct2{3, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[2], Comp: &testStruct2{4, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 3, Comp: &testStruct3{4, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 3, Comp: &testStruct3{5, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[3], Comp: &testStruct3{4, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[3], Comp: &testStruct3{5, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 4, Comp: &testStruct4{5, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 4, Comp: &testStruct4{6, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[4], Comp: &testStruct4{5, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[4], Comp: &testStruct4{6, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 5, Comp: &testStruct5{6, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 5, Comp: &testStruct5{7, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[5], Comp: &testStruct5{6, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[5], Comp: &testStruct5{7, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 6, Comp: &testStruct6{7, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 6, Comp: &testStruct6{8, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[6], Comp: &testStruct6{7, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[6], Comp: &testStruct6{8, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 7, Comp: &testStruct7{8, 0, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 7, Comp: &testStruct7{9, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[7], Comp: &testStruct7{8, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[7], Comp: &testStruct7{9, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 8, Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[8], Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e2, ecs.Component{ID: 9, Comp: &testStruct9{}})
+	w.Assign(e2, ecs.Component{ID: ids[9], Comp: &testStruct9{}})
 
 	cnt := 0
 	filter :=
@@ -915,7 +915,7 @@ func TestQuery8(t *testing.T) {
 	filter.Unregister(&w)
 	assert.Panics(t, func() { filter.Unregister(&w) })
 
-	targ := w.NewEntity(0)
+	targ := w.NewEntity(ids[0])
 
 	w.Add(e0, relID)
 	w.Add(e1, relID)
@@ -964,43 +964,43 @@ func TestQuery8(t *testing.T) {
 func TestQuery9(t *testing.T) {
 	w := ecs.NewWorld()
 
-	registerAll(&w)
+	ids := registerAll(&w)
 	relID := ecs.ComponentID[testRelationA](&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
 	e2 := w.NewEntity()
 
-	w.Assign(e0, ecs.Component{ID: 0, Comp: &testStruct0{1}})
-	w.Assign(e1, ecs.Component{ID: 0, Comp: &testStruct0{2}})
+	w.Assign(e0, ecs.Component{ID: ids[0], Comp: &testStruct0{1}})
+	w.Assign(e1, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
 
-	w.Assign(e0, ecs.Component{ID: 1, Comp: &testStruct1{2}})
-	w.Assign(e1, ecs.Component{ID: 1, Comp: &testStruct1{3}})
+	w.Assign(e0, ecs.Component{ID: ids[1], Comp: &testStruct1{2}})
+	w.Assign(e1, ecs.Component{ID: ids[1], Comp: &testStruct1{3}})
 
-	w.Assign(e0, ecs.Component{ID: 2, Comp: &testStruct2{3, 0}})
-	w.Assign(e1, ecs.Component{ID: 2, Comp: &testStruct2{4, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[2], Comp: &testStruct2{3, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[2], Comp: &testStruct2{4, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 3, Comp: &testStruct3{4, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 3, Comp: &testStruct3{5, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[3], Comp: &testStruct3{4, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[3], Comp: &testStruct3{5, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 4, Comp: &testStruct4{5, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 4, Comp: &testStruct4{6, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[4], Comp: &testStruct4{5, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[4], Comp: &testStruct4{6, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 5, Comp: &testStruct5{6, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 5, Comp: &testStruct5{7, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[5], Comp: &testStruct5{6, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[5], Comp: &testStruct5{7, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 6, Comp: &testStruct6{7, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 6, Comp: &testStruct6{8, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[6], Comp: &testStruct6{7, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[6], Comp: &testStruct6{8, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 7, Comp: &testStruct7{8, 0, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 7, Comp: &testStruct7{9, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[7], Comp: &testStruct7{8, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[7], Comp: &testStruct7{9, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 8, Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 8, Comp: &testStruct8{10, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[8], Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[8], Comp: &testStruct8{10, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 9, Comp: &testStruct9{10, 0, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[9], Comp: &testStruct9{10, 0, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e2, ecs.Component{ID: 10, Comp: &testStruct10{}})
+	w.Assign(e2, ecs.Component{ID: ids[10], Comp: &testStruct10{}})
 
 	cnt := 0
 	filter :=
@@ -1035,7 +1035,7 @@ func TestQuery9(t *testing.T) {
 	filter.Unregister(&w)
 	assert.Panics(t, func() { filter.Unregister(&w) })
 
-	targ := w.NewEntity(0)
+	targ := w.NewEntity(ids[0])
 
 	w.Add(e0, relID)
 	w.Add(e1, relID)
@@ -1086,46 +1086,46 @@ func TestQuery9(t *testing.T) {
 func TestQuery10(t *testing.T) {
 	w := ecs.NewWorld()
 
-	registerAll(&w)
+	ids := registerAll(&w)
 	relID := ecs.ComponentID[testRelationA](&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
 	e2 := w.NewEntity()
 
-	w.Assign(e0, ecs.Component{ID: 0, Comp: &testStruct0{1}})
-	w.Assign(e1, ecs.Component{ID: 0, Comp: &testStruct0{2}})
+	w.Assign(e0, ecs.Component{ID: ids[0], Comp: &testStruct0{1}})
+	w.Assign(e1, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
 
-	w.Assign(e0, ecs.Component{ID: 1, Comp: &testStruct1{2}})
-	w.Assign(e1, ecs.Component{ID: 1, Comp: &testStruct1{3}})
+	w.Assign(e0, ecs.Component{ID: ids[1], Comp: &testStruct1{2}})
+	w.Assign(e1, ecs.Component{ID: ids[1], Comp: &testStruct1{3}})
 
-	w.Assign(e0, ecs.Component{ID: 2, Comp: &testStruct2{3, 0}})
-	w.Assign(e1, ecs.Component{ID: 2, Comp: &testStruct2{4, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[2], Comp: &testStruct2{3, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[2], Comp: &testStruct2{4, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 3, Comp: &testStruct3{4, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 3, Comp: &testStruct3{5, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[3], Comp: &testStruct3{4, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[3], Comp: &testStruct3{5, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 4, Comp: &testStruct4{5, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 4, Comp: &testStruct4{6, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[4], Comp: &testStruct4{5, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[4], Comp: &testStruct4{6, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 5, Comp: &testStruct5{6, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 5, Comp: &testStruct5{7, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[5], Comp: &testStruct5{6, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[5], Comp: &testStruct5{7, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 6, Comp: &testStruct6{7, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 6, Comp: &testStruct6{8, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[6], Comp: &testStruct6{7, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[6], Comp: &testStruct6{8, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 7, Comp: &testStruct7{8, 0, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 7, Comp: &testStruct7{9, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[7], Comp: &testStruct7{8, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[7], Comp: &testStruct7{9, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 8, Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 8, Comp: &testStruct8{10, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[8], Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[8], Comp: &testStruct8{10, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 9, Comp: &testStruct9{10, 0, 0, 0, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 9, Comp: &testStruct9{11, 0, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[9], Comp: &testStruct9{10, 0, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[9], Comp: &testStruct9{11, 0, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 10, Comp: &testStruct10{11, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[10], Comp: &testStruct10{11, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e2, ecs.Component{ID: 11, Comp: &testStruct11{}})
+	w.Assign(e2, ecs.Component{ID: ids[11], Comp: &testStruct11{}})
 
 	cnt := 0
 	filter :=
@@ -1161,7 +1161,7 @@ func TestQuery10(t *testing.T) {
 	filter.Unregister(&w)
 	assert.Panics(t, func() { filter.Unregister(&w) })
 
-	targ := w.NewEntity(0)
+	targ := w.NewEntity(ids[0])
 
 	w.Add(e0, relID)
 	w.Add(e1, relID)
@@ -1212,49 +1212,49 @@ func TestQuery10(t *testing.T) {
 func TestQuery11(t *testing.T) {
 	w := ecs.NewWorld()
 
-	registerAll(&w)
+	ids := registerAll(&w)
 	relID := ecs.ComponentID[testRelationA](&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
 	e2 := w.NewEntity()
 
-	w.Assign(e0, ecs.Component{ID: 0, Comp: &testStruct0{1}})
-	w.Assign(e1, ecs.Component{ID: 0, Comp: &testStruct0{2}})
+	w.Assign(e0, ecs.Component{ID: ids[0], Comp: &testStruct0{1}})
+	w.Assign(e1, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
 
-	w.Assign(e0, ecs.Component{ID: 1, Comp: &testStruct1{2}})
-	w.Assign(e1, ecs.Component{ID: 1, Comp: &testStruct1{3}})
+	w.Assign(e0, ecs.Component{ID: ids[1], Comp: &testStruct1{2}})
+	w.Assign(e1, ecs.Component{ID: ids[1], Comp: &testStruct1{3}})
 
-	w.Assign(e0, ecs.Component{ID: 2, Comp: &testStruct2{3, 0}})
-	w.Assign(e1, ecs.Component{ID: 2, Comp: &testStruct2{4, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[2], Comp: &testStruct2{3, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[2], Comp: &testStruct2{4, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 3, Comp: &testStruct3{4, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 3, Comp: &testStruct3{5, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[3], Comp: &testStruct3{4, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[3], Comp: &testStruct3{5, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 4, Comp: &testStruct4{5, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 4, Comp: &testStruct4{6, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[4], Comp: &testStruct4{5, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[4], Comp: &testStruct4{6, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 5, Comp: &testStruct5{6, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 5, Comp: &testStruct5{7, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[5], Comp: &testStruct5{6, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[5], Comp: &testStruct5{7, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 6, Comp: &testStruct6{7, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 6, Comp: &testStruct6{8, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[6], Comp: &testStruct6{7, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[6], Comp: &testStruct6{8, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 7, Comp: &testStruct7{8, 0, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 7, Comp: &testStruct7{9, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[7], Comp: &testStruct7{8, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[7], Comp: &testStruct7{9, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 8, Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 8, Comp: &testStruct8{10, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[8], Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[8], Comp: &testStruct8{10, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 9, Comp: &testStruct9{10, 0, 0, 0, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 9, Comp: &testStruct9{11, 0, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[9], Comp: &testStruct9{10, 0, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[9], Comp: &testStruct9{11, 0, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 10, Comp: &testStruct10{11, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 10, Comp: &testStruct10{12, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[10], Comp: &testStruct10{11, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[10], Comp: &testStruct10{12, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 11, Comp: &testStruct11{12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[11], Comp: &testStruct11{12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e2, ecs.Component{ID: 12, Comp: &testStruct12{}})
+	w.Assign(e2, ecs.Component{ID: ids[12], Comp: &testStruct12{}})
 
 	cnt := 0
 	filter :=
@@ -1291,7 +1291,7 @@ func TestQuery11(t *testing.T) {
 	filter.Unregister(&w)
 	assert.Panics(t, func() { filter.Unregister(&w) })
 
-	targ := w.NewEntity(0)
+	targ := w.NewEntity(ids[0])
 
 	w.Add(e0, relID)
 	w.Add(e1, relID)
@@ -1342,52 +1342,52 @@ func TestQuery11(t *testing.T) {
 func TestQuery12(t *testing.T) {
 	w := ecs.NewWorld()
 
-	registerAll(&w)
+	ids := registerAll(&w)
 	relID := ecs.ComponentID[testRelationA](&w)
 
 	e0 := w.NewEntity()
 	e1 := w.NewEntity()
 	e2 := w.NewEntity()
 
-	w.Assign(e0, ecs.Component{ID: 0, Comp: &testStruct0{1}})
-	w.Assign(e1, ecs.Component{ID: 0, Comp: &testStruct0{2}})
+	w.Assign(e0, ecs.Component{ID: ids[0], Comp: &testStruct0{1}})
+	w.Assign(e1, ecs.Component{ID: ids[0], Comp: &testStruct0{2}})
 
-	w.Assign(e0, ecs.Component{ID: 1, Comp: &testStruct1{2}})
-	w.Assign(e1, ecs.Component{ID: 1, Comp: &testStruct1{3}})
+	w.Assign(e0, ecs.Component{ID: ids[1], Comp: &testStruct1{2}})
+	w.Assign(e1, ecs.Component{ID: ids[1], Comp: &testStruct1{3}})
 
-	w.Assign(e0, ecs.Component{ID: 2, Comp: &testStruct2{3, 0}})
-	w.Assign(e1, ecs.Component{ID: 2, Comp: &testStruct2{4, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[2], Comp: &testStruct2{3, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[2], Comp: &testStruct2{4, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 3, Comp: &testStruct3{4, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 3, Comp: &testStruct3{5, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[3], Comp: &testStruct3{4, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[3], Comp: &testStruct3{5, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 4, Comp: &testStruct4{5, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 4, Comp: &testStruct4{6, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[4], Comp: &testStruct4{5, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[4], Comp: &testStruct4{6, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 5, Comp: &testStruct5{6, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 5, Comp: &testStruct5{7, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[5], Comp: &testStruct5{6, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[5], Comp: &testStruct5{7, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 6, Comp: &testStruct6{7, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 6, Comp: &testStruct6{8, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[6], Comp: &testStruct6{7, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[6], Comp: &testStruct6{8, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 7, Comp: &testStruct7{8, 0, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 7, Comp: &testStruct7{9, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[7], Comp: &testStruct7{8, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[7], Comp: &testStruct7{9, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 8, Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 8, Comp: &testStruct8{10, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[8], Comp: &testStruct8{9, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[8], Comp: &testStruct8{10, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 9, Comp: &testStruct9{10, 0, 0, 0, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 9, Comp: &testStruct9{11, 0, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[9], Comp: &testStruct9{10, 0, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[9], Comp: &testStruct9{11, 0, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 10, Comp: &testStruct10{11, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 10, Comp: &testStruct10{12, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[10], Comp: &testStruct10{11, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[10], Comp: &testStruct10{12, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 11, Comp: &testStruct11{12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
-	w.Assign(e1, ecs.Component{ID: 11, Comp: &testStruct11{13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[11], Comp: &testStruct11{12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: ids[11], Comp: &testStruct11{13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e0, ecs.Component{ID: 12, Comp: &testStruct12{13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
+	w.Assign(e0, ecs.Component{ID: ids[12], Comp: &testStruct12{13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
 
-	w.Assign(e2, ecs.Component{ID: 13, Comp: &testStruct13{}})
+	w.Assign(e2, ecs.Component{ID: ids[13], Comp: &testStruct13{}})
 
 	cnt := 0
 	filter :=
@@ -1425,7 +1425,7 @@ func TestQuery12(t *testing.T) {
 	filter.Unregister(&w)
 	assert.Panics(t, func() { filter.Unregister(&w) })
 
-	targ := w.NewEntity(0)
+	targ := w.NewEntity(ids[0])
 
 	w.Add(e0, relID)
 	w.Add(e1, relID)