Repository: mlange-42/arche
Pull request #324 (2024-01-12 11:59:34)

Twelve (12) generic components
Extend generic API to up to 12 components

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


* **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/65038642/badge)](https://coveralls.io/builds/65038642) |
| :-- | --: |
| Change from base [Build 7496521915](https://coveralls.io/builds/65030717): |  0.0% |
| Covered Lines: | 4814 |
| Relevant Lines: | 4814 |

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


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 645b3e1..2f0f2da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,8 +4,9 @@
 
 ### Highlights
 
-* Arche now supports 256 instead of 128 component types as well as resource types and engine locks (#313)
 * Arche supports full world serialization and deserialization, in conjunction with [arche-serde](https://github.com/mlange-42/arche-serde) (#319)
+* Supports 256 instead of 128 component types as well as resource types and engine locks (#313)
+* Generic API supports up to 12 instead of 8 component types (#324)
 
 ### Breaking changes
 
diff --git a/generic/generate/main.go b/generic/generate/main.go
index bb32112..04e4f52 100644
--- a/generic/generate/main.go
+++ b/generic/generate/main.go
@@ -9,10 +9,10 @@ import (
 	"text/template"
 )
 
-var typeLetters = []string{"A", "B", "C", "D", "E", "F", "G", "H"}
-var variableLetters = []string{"a", "b", "c", "d", "e", "f", "g", "h"}
-var numbers = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8"}
-var numberStr = []string{"zero", "one", "two", "three", "four", "five", "six", "seven", "eight"}
+var typeLetters = []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"}
+var variableLetters = []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}
+var numbers = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"}
+var numberStr = []string{"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve"}
 
 type query struct {
 	Index         int
diff --git a/generic/map_generated.go b/generic/map_generated.go
index 7e583fb..943a566 100644
--- a/generic/map_generated.go
+++ b/generic/map_generated.go
@@ -1387,3 +1387,887 @@ func (m *Map8[A, B, C, D, E, F, G, H]) RemoveEntities(exclusive bool) int {
 	}
 	return m.world.Batch().RemoveEntities(m.mask)
 }
+
+//////////////////////////////////////////////////////////////////////////
+
+// Map9 is a helper for mapping nine components.
+//
+// # Example
+//
+//	world := ecs.NewWorld()
+//
+//	mapper := NewMap9[A, B, C, D, E, F, G, H, I](&world)
+//
+//	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
+}
+
+// NewMap9 creates a new Map9 object.
+//
+// The optional argument can be used to set an [ecs.Relation] component type.
+func NewMap9[A any, B any, C any, D any, E any, F any, G any, H any, I any](w *ecs.World, relation ...Comp) Map9[A, B, C, D, E, F, G, H, I] {
+	m := Map9[A, B, C, D, E, F, G, H, I]{
+		world: w,
+		id0:   ecs.ComponentID[A](w),
+		id1:   ecs.ComponentID[B](w),
+		id2:   ecs.ComponentID[C](w),
+		id3:   ecs.ComponentID[D](w),
+		id4:   ecs.ComponentID[E](w),
+		id5:   ecs.ComponentID[F](w),
+		id6:   ecs.ComponentID[G](w),
+		id7:   ecs.ComponentID[H](w),
+		id8:   ecs.ComponentID[I](w),
+	}
+	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
+	if len(relation) > 0 {
+		m.relation = int8(ecs.TypeID(w, relation[0]))
+	}
+	return m
+}
+
+// Get all the Map9's components for the given entity.
+//
+// See [Map9.GetUnchecked] for an optimized version for static entities.
+// See also [ecs.World.Get].
+func (m *Map9[A, B, C, D, E, F, G, H, I]) Get(entity ecs.Entity) (*A, *B, *C, *D, *E, *F, *G, *H, *I) {
+	return (*A)(m.world.Get(entity, m.id0)),
+		(*B)(m.world.GetUnchecked(entity, m.id1)),
+		(*C)(m.world.GetUnchecked(entity, m.id2)),
+		(*D)(m.world.GetUnchecked(entity, m.id3)),
+		(*E)(m.world.GetUnchecked(entity, m.id4)),
+		(*F)(m.world.GetUnchecked(entity, m.id5)),
+		(*G)(m.world.GetUnchecked(entity, m.id6)),
+		(*H)(m.world.GetUnchecked(entity, m.id7)),
+		(*I)(m.world.GetUnchecked(entity, m.id8))
+}
+
+// GetUnchecked all the Map9's components for the given entity.
+//
+// GetUnchecked is an optimized version of [Map9.Get],
+// for cases where entities are static or checked with [ecs.World.Alive] in user code.
+//
+// See also [ecs.World.GetUnchecked].
+func (m *Map9[A, B, C, D, E, F, G, H, I]) GetUnchecked(entity ecs.Entity) (*A, *B, *C, *D, *E, *F, *G, *H, *I) {
+	return (*A)(m.world.GetUnchecked(entity, m.id0)),
+		(*B)(m.world.GetUnchecked(entity, m.id1)),
+		(*C)(m.world.GetUnchecked(entity, m.id2)),
+		(*D)(m.world.GetUnchecked(entity, m.id3)),
+		(*E)(m.world.GetUnchecked(entity, m.id4)),
+		(*F)(m.world.GetUnchecked(entity, m.id5)),
+		(*G)(m.world.GetUnchecked(entity, m.id6)),
+		(*H)(m.world.GetUnchecked(entity, m.id7)),
+		(*I)(m.world.GetUnchecked(entity, m.id8))
+}
+
+// New creates a new [ecs.Entity] with the Map9's components.
+//
+// The optional argument can be used to set the target [ecs.Entity] for the Map9's [ecs.Relation].
+//
+// 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...)
+}
+
+// NewBatch creates entities with the Map9's components.
+//
+// The optional argument can be used to set the target [ecs.Entity] for the Map9's [ecs.Relation].
+//
+// 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...)
+}
+
+// NewBatchQ creates entities with the Map9's components.
+// It returns a [Query9] over the new entities.
+//
+// The optional argument can be used to set the target [ecs.Entity] for the Map9's [ecs.Relation].
+//
+// Listener notification is delayed until the query is closed of fully iterated.
+//
+// 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...)
+	return Query9[A, B, C, D, E, F, G, H, I]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+		id2:   m.id2,
+		id3:   m.id3,
+		id4:   m.id4,
+		id5:   m.id5,
+		id6:   m.id6,
+		id7:   m.id7,
+		id8:   m.id8,
+	}
+}
+
+// NewWith creates a new [ecs.Entity] with the Map9's components, using the supplied values.
+//
+// The optional argument can be used to set the target [ecs.Entity] for the Map9's [ecs.Relation].
+//
+// See also [ecs.NewBuilderWith].
+func (m *Map9[A, B, C, D, E, F, G, H, I]) NewWith(a *A, b *B, c *C, d *D, e *E, f *F, g *G, h *H, i *I, target ...ecs.Entity) ecs.Entity {
+	if len(target) == 0 {
+		return m.world.NewEntityWith(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},
+			ecs.Component{ID: m.id4, Comp: e},
+			ecs.Component{ID: m.id5, Comp: f},
+			ecs.Component{ID: m.id6, Comp: g},
+			ecs.Component{ID: m.id7, Comp: h},
+			ecs.Component{ID: m.id8, Comp: i},
+		)
+	}
+	if m.relation < 0 {
+		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},
+		ecs.Component{ID: m.id4, Comp: e},
+		ecs.Component{ID: m.id5, Comp: f},
+		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])
+}
+
+// Add the Map9's components to the given entity.
+//
+// See also [ecs.World.Add].
+func (m *Map9[A, B, C, D, E, F, G, H, I]) Add(entity ecs.Entity) {
+	m.world.Add(entity, m.ids...)
+}
+
+// Assign the Map9's components to the given entity, using the supplied values.
+//
+// See also [ecs.World.Assign].
+func (m *Map9[A, B, C, D, E, F, G, H, I]) Assign(entity ecs.Entity, a *A, b *B, c *C, d *D, e *E, f *F, g *G, h *H, i *I) {
+	m.world.Assign(entity,
+		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},
+		ecs.Component{ID: m.id4, Comp: e},
+		ecs.Component{ID: m.id5, Comp: f},
+		ecs.Component{ID: m.id6, Comp: g},
+		ecs.Component{ID: m.id7, Comp: h},
+		ecs.Component{ID: m.id8, Comp: i},
+	)
+}
+
+// Remove the Map9's components from the given entity.
+//
+// See also [ecs.World.Remove].
+func (m *Map9[A, B, C, D, E, F, G, H, I]) Remove(entity ecs.Entity) {
+	m.world.Remove(entity, m.ids...)
+}
+
+// RemoveEntities removes all entities from the world that match the Map9's components.
+//
+// The argument determines whether to match the components exactly (i.e. no other components are allowed),
+// or to use a simple filter that does not restrict further components.
+//
+// Returns the number of removed entities.
+//
+// See also [ecs.World.NewEntityWith].
+func (m *Map9[A, B, C, D, E, F, G, H, I]) RemoveEntities(exclusive bool) int {
+	if exclusive {
+		filter := m.mask.Exclusive()
+		return m.world.Batch().RemoveEntities(&filter)
+	}
+	return m.world.Batch().RemoveEntities(m.mask)
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+// Map10 is a helper for mapping ten components.
+//
+// # Example
+//
+//	world := ecs.NewWorld()
+//
+//	mapper := NewMap10[A, B, C, D, E, F, G, H, I, J](&world)
+//
+//	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
+}
+
+// NewMap10 creates a new Map10 object.
+//
+// The optional argument can be used to set an [ecs.Relation] component type.
+func NewMap10[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any](w *ecs.World, relation ...Comp) Map10[A, B, C, D, E, F, G, H, I, J] {
+	m := Map10[A, B, C, D, E, F, G, H, I, J]{
+		world: w,
+		id0:   ecs.ComponentID[A](w),
+		id1:   ecs.ComponentID[B](w),
+		id2:   ecs.ComponentID[C](w),
+		id3:   ecs.ComponentID[D](w),
+		id4:   ecs.ComponentID[E](w),
+		id5:   ecs.ComponentID[F](w),
+		id6:   ecs.ComponentID[G](w),
+		id7:   ecs.ComponentID[H](w),
+		id8:   ecs.ComponentID[I](w),
+		id9:   ecs.ComponentID[J](w),
+	}
+	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
+	if len(relation) > 0 {
+		m.relation = int8(ecs.TypeID(w, relation[0]))
+	}
+	return m
+}
+
+// Get all the Map10's components for the given entity.
+//
+// See [Map10.GetUnchecked] for an optimized version for static entities.
+// See also [ecs.World.Get].
+func (m *Map10[A, B, C, D, E, F, G, H, I, J]) Get(entity ecs.Entity) (*A, *B, *C, *D, *E, *F, *G, *H, *I, *J) {
+	return (*A)(m.world.Get(entity, m.id0)),
+		(*B)(m.world.GetUnchecked(entity, m.id1)),
+		(*C)(m.world.GetUnchecked(entity, m.id2)),
+		(*D)(m.world.GetUnchecked(entity, m.id3)),
+		(*E)(m.world.GetUnchecked(entity, m.id4)),
+		(*F)(m.world.GetUnchecked(entity, m.id5)),
+		(*G)(m.world.GetUnchecked(entity, m.id6)),
+		(*H)(m.world.GetUnchecked(entity, m.id7)),
+		(*I)(m.world.GetUnchecked(entity, m.id8)),
+		(*J)(m.world.GetUnchecked(entity, m.id9))
+}
+
+// GetUnchecked all the Map10's components for the given entity.
+//
+// GetUnchecked is an optimized version of [Map10.Get],
+// for cases where entities are static or checked with [ecs.World.Alive] in user code.
+//
+// See also [ecs.World.GetUnchecked].
+func (m *Map10[A, B, C, D, E, F, G, H, I, J]) GetUnchecked(entity ecs.Entity) (*A, *B, *C, *D, *E, *F, *G, *H, *I, *J) {
+	return (*A)(m.world.GetUnchecked(entity, m.id0)),
+		(*B)(m.world.GetUnchecked(entity, m.id1)),
+		(*C)(m.world.GetUnchecked(entity, m.id2)),
+		(*D)(m.world.GetUnchecked(entity, m.id3)),
+		(*E)(m.world.GetUnchecked(entity, m.id4)),
+		(*F)(m.world.GetUnchecked(entity, m.id5)),
+		(*G)(m.world.GetUnchecked(entity, m.id6)),
+		(*H)(m.world.GetUnchecked(entity, m.id7)),
+		(*I)(m.world.GetUnchecked(entity, m.id8)),
+		(*J)(m.world.GetUnchecked(entity, m.id9))
+}
+
+// New creates a new [ecs.Entity] with the Map10's components.
+//
+// The optional argument can be used to set the target [ecs.Entity] for the Map10's [ecs.Relation].
+//
+// 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...)
+}
+
+// NewBatch creates entities with the Map10's components.
+//
+// The optional argument can be used to set the target [ecs.Entity] for the Map10's [ecs.Relation].
+//
+// 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...)
+}
+
+// NewBatchQ creates entities with the Map10's components.
+// It returns a [Query10] over the new entities.
+//
+// The optional argument can be used to set the target [ecs.Entity] for the Map10's [ecs.Relation].
+//
+// Listener notification is delayed until the query is closed of fully iterated.
+//
+// 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...)
+	return Query10[A, B, C, D, E, F, G, H, I, J]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+		id2:   m.id2,
+		id3:   m.id3,
+		id4:   m.id4,
+		id5:   m.id5,
+		id6:   m.id6,
+		id7:   m.id7,
+		id8:   m.id8,
+		id9:   m.id9,
+	}
+}
+
+// NewWith creates a new [ecs.Entity] with the Map10's components, using the supplied values.
+//
+// The optional argument can be used to set the target [ecs.Entity] for the Map10's [ecs.Relation].
+//
+// See also [ecs.NewBuilderWith].
+func (m *Map10[A, B, C, D, E, F, G, H, I, J]) NewWith(a *A, b *B, c *C, d *D, e *E, f *F, g *G, h *H, i *I, j *J, target ...ecs.Entity) ecs.Entity {
+	if len(target) == 0 {
+		return m.world.NewEntityWith(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},
+			ecs.Component{ID: m.id4, Comp: e},
+			ecs.Component{ID: m.id5, Comp: f},
+			ecs.Component{ID: m.id6, Comp: g},
+			ecs.Component{ID: m.id7, Comp: h},
+			ecs.Component{ID: m.id8, Comp: i},
+			ecs.Component{ID: m.id9, Comp: j},
+		)
+	}
+	if m.relation < 0 {
+		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},
+		ecs.Component{ID: m.id4, Comp: e},
+		ecs.Component{ID: m.id5, Comp: f},
+		ecs.Component{ID: m.id6, Comp: g},
+		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])
+}
+
+// Add the Map10's components to the given entity.
+//
+// See also [ecs.World.Add].
+func (m *Map10[A, B, C, D, E, F, G, H, I, J]) Add(entity ecs.Entity) {
+	m.world.Add(entity, m.ids...)
+}
+
+// Assign the Map10's components to the given entity, using the supplied values.
+//
+// See also [ecs.World.Assign].
+func (m *Map10[A, B, C, D, E, F, G, H, I, J]) Assign(entity ecs.Entity, a *A, b *B, c *C, d *D, e *E, f *F, g *G, h *H, i *I, j *J) {
+	m.world.Assign(entity,
+		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},
+		ecs.Component{ID: m.id4, Comp: e},
+		ecs.Component{ID: m.id5, Comp: f},
+		ecs.Component{ID: m.id6, Comp: g},
+		ecs.Component{ID: m.id7, Comp: h},
+		ecs.Component{ID: m.id8, Comp: i},
+		ecs.Component{ID: m.id9, Comp: j},
+	)
+}
+
+// Remove the Map10's components from the given entity.
+//
+// See also [ecs.World.Remove].
+func (m *Map10[A, B, C, D, E, F, G, H, I, J]) Remove(entity ecs.Entity) {
+	m.world.Remove(entity, m.ids...)
+}
+
+// RemoveEntities removes all entities from the world that match the Map10's components.
+//
+// The argument determines whether to match the components exactly (i.e. no other components are allowed),
+// or to use a simple filter that does not restrict further components.
+//
+// Returns the number of removed entities.
+//
+// See also [ecs.World.NewEntityWith].
+func (m *Map10[A, B, C, D, E, F, G, H, I, J]) RemoveEntities(exclusive bool) int {
+	if exclusive {
+		filter := m.mask.Exclusive()
+		return m.world.Batch().RemoveEntities(&filter)
+	}
+	return m.world.Batch().RemoveEntities(m.mask)
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+// Map11 is a helper for mapping eleven components.
+//
+// # Example
+//
+//	world := ecs.NewWorld()
+//
+//	mapper := NewMap11[A, B, C, D, E, F, G, H, I, J, K](&world)
+//
+//	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
+}
+
+// NewMap11 creates a new Map11 object.
+//
+// The optional argument can be used to set an [ecs.Relation] component type.
+func NewMap11[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any, K any](w *ecs.World, relation ...Comp) Map11[A, B, C, D, E, F, G, H, I, J, K] {
+	m := Map11[A, B, C, D, E, F, G, H, I, J, K]{
+		world: w,
+		id0:   ecs.ComponentID[A](w),
+		id1:   ecs.ComponentID[B](w),
+		id2:   ecs.ComponentID[C](w),
+		id3:   ecs.ComponentID[D](w),
+		id4:   ecs.ComponentID[E](w),
+		id5:   ecs.ComponentID[F](w),
+		id6:   ecs.ComponentID[G](w),
+		id7:   ecs.ComponentID[H](w),
+		id8:   ecs.ComponentID[I](w),
+		id9:   ecs.ComponentID[J](w),
+		id10:  ecs.ComponentID[K](w),
+	}
+	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
+	if len(relation) > 0 {
+		m.relation = int8(ecs.TypeID(w, relation[0]))
+	}
+	return m
+}
+
+// Get all the Map11's components for the given entity.
+//
+// See [Map11.GetUnchecked] for an optimized version for static entities.
+// See also [ecs.World.Get].
+func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) Get(entity ecs.Entity) (*A, *B, *C, *D, *E, *F, *G, *H, *I, *J, *K) {
+	return (*A)(m.world.Get(entity, m.id0)),
+		(*B)(m.world.GetUnchecked(entity, m.id1)),
+		(*C)(m.world.GetUnchecked(entity, m.id2)),
+		(*D)(m.world.GetUnchecked(entity, m.id3)),
+		(*E)(m.world.GetUnchecked(entity, m.id4)),
+		(*F)(m.world.GetUnchecked(entity, m.id5)),
+		(*G)(m.world.GetUnchecked(entity, m.id6)),
+		(*H)(m.world.GetUnchecked(entity, m.id7)),
+		(*I)(m.world.GetUnchecked(entity, m.id8)),
+		(*J)(m.world.GetUnchecked(entity, m.id9)),
+		(*K)(m.world.GetUnchecked(entity, m.id10))
+}
+
+// GetUnchecked all the Map11's components for the given entity.
+//
+// GetUnchecked is an optimized version of [Map11.Get],
+// for cases where entities are static or checked with [ecs.World.Alive] in user code.
+//
+// See also [ecs.World.GetUnchecked].
+func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) GetUnchecked(entity ecs.Entity) (*A, *B, *C, *D, *E, *F, *G, *H, *I, *J, *K) {
+	return (*A)(m.world.GetUnchecked(entity, m.id0)),
+		(*B)(m.world.GetUnchecked(entity, m.id1)),
+		(*C)(m.world.GetUnchecked(entity, m.id2)),
+		(*D)(m.world.GetUnchecked(entity, m.id3)),
+		(*E)(m.world.GetUnchecked(entity, m.id4)),
+		(*F)(m.world.GetUnchecked(entity, m.id5)),
+		(*G)(m.world.GetUnchecked(entity, m.id6)),
+		(*H)(m.world.GetUnchecked(entity, m.id7)),
+		(*I)(m.world.GetUnchecked(entity, m.id8)),
+		(*J)(m.world.GetUnchecked(entity, m.id9)),
+		(*K)(m.world.GetUnchecked(entity, m.id10))
+}
+
+// New creates a new [ecs.Entity] with the Map11's components.
+//
+// The optional argument can be used to set the target [ecs.Entity] for the Map11's [ecs.Relation].
+//
+// 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...)
+}
+
+// NewBatch creates entities with the Map11's components.
+//
+// The optional argument can be used to set the target [ecs.Entity] for the Map11's [ecs.Relation].
+//
+// 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...)
+}
+
+// NewBatchQ creates entities with the Map11's components.
+// It returns a [Query11] over the new entities.
+//
+// The optional argument can be used to set the target [ecs.Entity] for the Map11's [ecs.Relation].
+//
+// Listener notification is delayed until the query is closed of fully iterated.
+//
+// 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...)
+	return Query11[A, B, C, D, E, F, G, H, I, J, K]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+		id2:   m.id2,
+		id3:   m.id3,
+		id4:   m.id4,
+		id5:   m.id5,
+		id6:   m.id6,
+		id7:   m.id7,
+		id8:   m.id8,
+		id9:   m.id9,
+		id10:  m.id10,
+	}
+}
+
+// NewWith creates a new [ecs.Entity] with the Map11's components, using the supplied values.
+//
+// The optional argument can be used to set the target [ecs.Entity] for the Map11's [ecs.Relation].
+//
+// See also [ecs.NewBuilderWith].
+func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) NewWith(a *A, b *B, c *C, d *D, e *E, f *F, g *G, h *H, i *I, j *J, k *K, target ...ecs.Entity) ecs.Entity {
+	if len(target) == 0 {
+		return m.world.NewEntityWith(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},
+			ecs.Component{ID: m.id4, Comp: e},
+			ecs.Component{ID: m.id5, Comp: f},
+			ecs.Component{ID: m.id6, Comp: g},
+			ecs.Component{ID: m.id7, Comp: h},
+			ecs.Component{ID: m.id8, Comp: i},
+			ecs.Component{ID: m.id9, Comp: j},
+			ecs.Component{ID: m.id10, Comp: k},
+		)
+	}
+	if m.relation < 0 {
+		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},
+		ecs.Component{ID: m.id4, Comp: e},
+		ecs.Component{ID: m.id5, Comp: f},
+		ecs.Component{ID: m.id6, Comp: g},
+		ecs.Component{ID: m.id7, Comp: h},
+		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])
+}
+
+// Add the Map11's components to the given entity.
+//
+// See also [ecs.World.Add].
+func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) Add(entity ecs.Entity) {
+	m.world.Add(entity, m.ids...)
+}
+
+// Assign the Map11's components to the given entity, using the supplied values.
+//
+// See also [ecs.World.Assign].
+func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) Assign(entity ecs.Entity, a *A, b *B, c *C, d *D, e *E, f *F, g *G, h *H, i *I, j *J, k *K) {
+	m.world.Assign(entity,
+		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},
+		ecs.Component{ID: m.id4, Comp: e},
+		ecs.Component{ID: m.id5, Comp: f},
+		ecs.Component{ID: m.id6, Comp: g},
+		ecs.Component{ID: m.id7, Comp: h},
+		ecs.Component{ID: m.id8, Comp: i},
+		ecs.Component{ID: m.id9, Comp: j},
+		ecs.Component{ID: m.id10, Comp: k},
+	)
+}
+
+// Remove the Map11's components from the given entity.
+//
+// See also [ecs.World.Remove].
+func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) Remove(entity ecs.Entity) {
+	m.world.Remove(entity, m.ids...)
+}
+
+// RemoveEntities removes all entities from the world that match the Map11's components.
+//
+// The argument determines whether to match the components exactly (i.e. no other components are allowed),
+// or to use a simple filter that does not restrict further components.
+//
+// Returns the number of removed entities.
+//
+// See also [ecs.World.NewEntityWith].
+func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) RemoveEntities(exclusive bool) int {
+	if exclusive {
+		filter := m.mask.Exclusive()
+		return m.world.Batch().RemoveEntities(&filter)
+	}
+	return m.world.Batch().RemoveEntities(m.mask)
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+// Map12 is a helper for mapping twelve components.
+//
+// # Example
+//
+//	world := ecs.NewWorld()
+//
+//	mapper := NewMap12[A, B, C, D, E, F, G, H, I, J, K, L](&world)
+//
+//	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
+}
+
+// NewMap12 creates a new Map12 object.
+//
+// The optional argument can be used to set an [ecs.Relation] component type.
+func NewMap12[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any, K any, L any](w *ecs.World, relation ...Comp) Map12[A, B, C, D, E, F, G, H, I, J, K, L] {
+	m := Map12[A, B, C, D, E, F, G, H, I, J, K, L]{
+		world: w,
+		id0:   ecs.ComponentID[A](w),
+		id1:   ecs.ComponentID[B](w),
+		id2:   ecs.ComponentID[C](w),
+		id3:   ecs.ComponentID[D](w),
+		id4:   ecs.ComponentID[E](w),
+		id5:   ecs.ComponentID[F](w),
+		id6:   ecs.ComponentID[G](w),
+		id7:   ecs.ComponentID[H](w),
+		id8:   ecs.ComponentID[I](w),
+		id9:   ecs.ComponentID[J](w),
+		id10:  ecs.ComponentID[K](w),
+		id11:  ecs.ComponentID[L](w),
+	}
+	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
+	if len(relation) > 0 {
+		m.relation = int8(ecs.TypeID(w, relation[0]))
+	}
+	return m
+}
+
+// Get all the Map12's components for the given entity.
+//
+// See [Map12.GetUnchecked] for an optimized version for static entities.
+// See also [ecs.World.Get].
+func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) Get(entity ecs.Entity) (*A, *B, *C, *D, *E, *F, *G, *H, *I, *J, *K, *L) {
+	return (*A)(m.world.Get(entity, m.id0)),
+		(*B)(m.world.GetUnchecked(entity, m.id1)),
+		(*C)(m.world.GetUnchecked(entity, m.id2)),
+		(*D)(m.world.GetUnchecked(entity, m.id3)),
+		(*E)(m.world.GetUnchecked(entity, m.id4)),
+		(*F)(m.world.GetUnchecked(entity, m.id5)),
+		(*G)(m.world.GetUnchecked(entity, m.id6)),
+		(*H)(m.world.GetUnchecked(entity, m.id7)),
+		(*I)(m.world.GetUnchecked(entity, m.id8)),
+		(*J)(m.world.GetUnchecked(entity, m.id9)),
+		(*K)(m.world.GetUnchecked(entity, m.id10)),
+		(*L)(m.world.GetUnchecked(entity, m.id11))
+}
+
+// GetUnchecked all the Map12's components for the given entity.
+//
+// GetUnchecked is an optimized version of [Map12.Get],
+// for cases where entities are static or checked with [ecs.World.Alive] in user code.
+//
+// See also [ecs.World.GetUnchecked].
+func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) GetUnchecked(entity ecs.Entity) (*A, *B, *C, *D, *E, *F, *G, *H, *I, *J, *K, *L) {
+	return (*A)(m.world.GetUnchecked(entity, m.id0)),
+		(*B)(m.world.GetUnchecked(entity, m.id1)),
+		(*C)(m.world.GetUnchecked(entity, m.id2)),
+		(*D)(m.world.GetUnchecked(entity, m.id3)),
+		(*E)(m.world.GetUnchecked(entity, m.id4)),
+		(*F)(m.world.GetUnchecked(entity, m.id5)),
+		(*G)(m.world.GetUnchecked(entity, m.id6)),
+		(*H)(m.world.GetUnchecked(entity, m.id7)),
+		(*I)(m.world.GetUnchecked(entity, m.id8)),
+		(*J)(m.world.GetUnchecked(entity, m.id9)),
+		(*K)(m.world.GetUnchecked(entity, m.id10)),
+		(*L)(m.world.GetUnchecked(entity, m.id11))
+}
+
+// New creates a new [ecs.Entity] with the Map12's components.
+//
+// The optional argument can be used to set the target [ecs.Entity] for the Map12's [ecs.Relation].
+//
+// 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...)
+}
+
+// NewBatch creates entities with the Map12's components.
+//
+// The optional argument can be used to set the target [ecs.Entity] for the Map12's [ecs.Relation].
+//
+// 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...)
+}
+
+// NewBatchQ creates entities with the Map12's components.
+// It returns a [Query12] over the new entities.
+//
+// The optional argument can be used to set the target [ecs.Entity] for the Map12's [ecs.Relation].
+//
+// Listener notification is delayed until the query is closed of fully iterated.
+//
+// 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...)
+	return Query12[A, B, C, D, E, F, G, H, I, J, K, L]{
+		Query: query,
+		id0:   m.id0,
+		id1:   m.id1,
+		id2:   m.id2,
+		id3:   m.id3,
+		id4:   m.id4,
+		id5:   m.id5,
+		id6:   m.id6,
+		id7:   m.id7,
+		id8:   m.id8,
+		id9:   m.id9,
+		id10:  m.id10,
+		id11:  m.id11,
+	}
+}
+
+// NewWith creates a new [ecs.Entity] with the Map12's components, using the supplied values.
+//
+// The optional argument can be used to set the target [ecs.Entity] for the Map12's [ecs.Relation].
+//
+// See also [ecs.NewBuilderWith].
+func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) NewWith(a *A, b *B, c *C, d *D, e *E, f *F, g *G, h *H, i *I, j *J, k *K, l *L, target ...ecs.Entity) ecs.Entity {
+	if len(target) == 0 {
+		return m.world.NewEntityWith(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},
+			ecs.Component{ID: m.id4, Comp: e},
+			ecs.Component{ID: m.id5, Comp: f},
+			ecs.Component{ID: m.id6, Comp: g},
+			ecs.Component{ID: m.id7, Comp: h},
+			ecs.Component{ID: m.id8, Comp: i},
+			ecs.Component{ID: m.id9, Comp: j},
+			ecs.Component{ID: m.id10, Comp: k},
+			ecs.Component{ID: m.id11, Comp: l},
+		)
+	}
+	if m.relation < 0 {
+		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},
+		ecs.Component{ID: m.id4, Comp: e},
+		ecs.Component{ID: m.id5, Comp: f},
+		ecs.Component{ID: m.id6, Comp: g},
+		ecs.Component{ID: m.id7, Comp: h},
+		ecs.Component{ID: m.id8, Comp: i},
+		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])
+}
+
+// Add the Map12's components to the given entity.
+//
+// See also [ecs.World.Add].
+func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) Add(entity ecs.Entity) {
+	m.world.Add(entity, m.ids...)
+}
+
+// Assign the Map12's components to the given entity, using the supplied values.
+//
+// See also [ecs.World.Assign].
+func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) Assign(entity ecs.Entity, a *A, b *B, c *C, d *D, e *E, f *F, g *G, h *H, i *I, j *J, k *K, l *L) {
+	m.world.Assign(entity,
+		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},
+		ecs.Component{ID: m.id4, Comp: e},
+		ecs.Component{ID: m.id5, Comp: f},
+		ecs.Component{ID: m.id6, Comp: g},
+		ecs.Component{ID: m.id7, Comp: h},
+		ecs.Component{ID: m.id8, Comp: i},
+		ecs.Component{ID: m.id9, Comp: j},
+		ecs.Component{ID: m.id10, Comp: k},
+		ecs.Component{ID: m.id11, Comp: l},
+	)
+}
+
+// Remove the Map12's components from the given entity.
+//
+// See also [ecs.World.Remove].
+func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) Remove(entity ecs.Entity) {
+	m.world.Remove(entity, m.ids...)
+}
+
+// RemoveEntities removes all entities from the world that match the Map12's components.
+//
+// The argument determines whether to match the components exactly (i.e. no other components are allowed),
+// or to use a simple filter that does not restrict further components.
+//
+// Returns the number of removed entities.
+//
+// See also [ecs.World.NewEntityWith].
+func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) RemoveEntities(exclusive bool) int {
+	if exclusive {
+		filter := m.mask.Exclusive()
+		return m.world.Batch().RemoveEntities(&filter)
+	}
+	return m.world.Batch().RemoveEntities(m.mask)
+}
diff --git a/generic/mask.go b/generic/mask.go
index 3ddc68b..644fc54 100644
--- a/generic/mask.go
+++ b/generic/mask.go
@@ -65,3 +65,39 @@ func T8[A any, B any, C any, D any, E any, F any, G any, H any]() []Comp {
 		typeOf[E](), typeOf[F](), typeOf[G](), typeOf[H](),
 	}
 }
+
+// T9 creates a component type list for nine component types.
+func T9[A any, B any, C any, D any, E any, F any, G any, H any, I any]() []Comp {
+	return []Comp{
+		typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
+		typeOf[E](), typeOf[F](), typeOf[G](), typeOf[H](),
+		typeOf[I](),
+	}
+}
+
+// T10 creates a component type list for ten component types.
+func T10[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any]() []Comp {
+	return []Comp{
+		typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
+		typeOf[E](), typeOf[F](), typeOf[G](), typeOf[H](),
+		typeOf[I](), typeOf[J](),
+	}
+}
+
+// T11 creates a component type list for eleven component types.
+func T11[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any, K any]() []Comp {
+	return []Comp{
+		typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
+		typeOf[E](), typeOf[F](), typeOf[G](), typeOf[H](),
+		typeOf[I](), typeOf[J](), typeOf[K](),
+	}
+}
+
+// T12 creates a component type list for twelve component types.
+func T12[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any, K any, L any]() []Comp {
+	return []Comp{
+		typeOf[A](), typeOf[B](), typeOf[C](), typeOf[D](),
+		typeOf[E](), typeOf[F](), typeOf[G](), typeOf[H](),
+		typeOf[I](), typeOf[J](), typeOf[K](), typeOf[L](),
+	}
+}
diff --git a/generic/query_generated.go b/generic/query_generated.go
index 28e7393..5e21eb5 100644
--- a/generic/query_generated.go
+++ b/generic/query_generated.go
@@ -1648,3 +1648,847 @@ func (q *Query8[A, B, C, D, E, F, G, H]) Relation() ecs.Entity {
 	}
 	return q.Query.Relation(ecs.ID(q.target))
 }
+
+//////////////////////////////////////////////////////////////////////////
+
+// Filter9 is a helper for building [Query9] query iterators.
+//
+// # Example
+//
+//	world := ecs.NewWorld()
+//
+//	filter := NewFilter9[A, B, C, D, E, F, G, H, I]()
+//	query := filter.Query(&world)
+//
+//	complexFilter :=
+//		NewFilter9[A, B, C, D, E, F, G, H, I]().
+//			Optional(T[A]()).
+//			With(T2[V, W]()...).
+//			Without(T3[X, Y, Z]()...).
+type Filter9[A any, B any, C any, D any, E any, F any, G any, H any, I any] filter
+
+// NewFilter9 creates a generic Filter9 for nine components.
+//
+// See also [ecs.World.Query].
+func NewFilter9[A any, B any, C any, D any, E any, F any, G any, H any, I any]() *Filter9[A, B, C, D, E, F, G, H, I] {
+	f := Filter9[A, B, C, D, E, F, G, H, I](newFilter(
+		typeOf[A](),
+		typeOf[B](),
+		typeOf[C](),
+		typeOf[D](),
+		typeOf[E](),
+		typeOf[F](),
+		typeOf[G](),
+		typeOf[H](),
+		typeOf[I](),
+	))
+	return &f
+}
+
+// Optional makes some of the query's components optional.
+//
+// Create the required mask items with [T].
+//
+// Only affects component types that were specified in the query.
+func (q *Filter9[A, B, C, D, E, F, G, H, I]) Optional(mask ...Comp) *Filter9[A, B, C, D, E, F, G, H, I] {
+	if q.compiled.locked {
+		panic("can't modify a registered filter")
+	}
+	q.optional = append(q.optional, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// With adds components that are required, but not accessible via [Query9.Get].
+//
+// Create the required mask items with [T].
+func (q *Filter9[A, B, C, D, E, F, G, H, I]) With(mask ...Comp) *Filter9[A, B, C, D, E, F, G, H, I] {
+	if q.compiled.locked {
+		panic("can't modify a registered filter")
+	}
+	q.include = append(q.include, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Without excludes entities with any of the given components from the query.
+//
+// Create the required mask items with [T].
+func (q *Filter9[A, B, C, D, E, F, G, H, I]) Without(mask ...Comp) *Filter9[A, B, C, D, E, F, G, H, I] {
+	if q.compiled.locked {
+		panic("can't modify a registered filter")
+	}
+	q.exclude = append(q.exclude, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// WithRelation sets the filter's [ecs.Relation] component and optionally
+// restricts the query to entities that have the given relation target.
+//
+// Use without the optional argument to specify the relation target in [Filter9.Query].
+// If the optional argument is provided, the filter's relation target is set permanently.
+//
+// Create the required component ID with [T].
+func (q *Filter9[A, B, C, D, E, F, G, H, I]) WithRelation(comp Comp, target ...ecs.Entity) *Filter9[A, B, C, D, E, F, G, H, I] {
+	if q.compiled.locked {
+		panic("can't modify a registered filter")
+	}
+	q.targetType = comp
+	if len(target) > 0 {
+		q.target = target[0]
+		q.hasTarget = true
+	}
+	q.compiled.Reset()
+	return q
+}
+
+// Query builds a [Query9] query for iteration, with an optional relation target.
+//
+// A relation target can't be used:
+//   - if [Filter9.WithRelation] was not called
+//   - if the target was already set via [Filter9.WithRelation]
+//   - if the filter is registered for caching
+//
+// Panics in these cases.
+func (q *Filter9[A, B, C, D, E, F, G, H, I]) Query(w *ecs.World, target ...ecs.Entity) Query9[A, B, C, D, E, F, G, H, I] {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+
+	filter := q.compiled.filter
+	if len(target) > 0 {
+		if q.compiled.locked {
+			panic("can't change relation target on a cached query")
+		}
+		if q.hasTarget {
+			panic("can't change relation target on a query with fixed target")
+		}
+		q.compiled.relationFilter.Filter = &q.compiled.maskFilter
+		q.compiled.relationFilter.Target = target[0]
+		filter = &q.compiled.relationFilter
+	}
+
+	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],
+	}
+}
+
+// Register the filter for caching.
+//
+// See [ecs.Cache] for details on filter caching.
+func (q *Filter9[A, B, C, D, E, F, G, H, I]) Register(w *ecs.World) {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+	q.compiled.Register(w)
+}
+
+// Unregister the filter from caching.
+//
+// See [ecs.Cache] for details on filter caching.
+func (q *Filter9[A, B, C, D, E, F, G, H, I]) Unregister(w *ecs.World) {
+	q.compiled.Unregister(w)
+}
+
+// Query9 is a generic query iterator for nine components.
+//
+// Create it with [NewFilter9] and [Filter9.Query].
+//
+// Also has all methods of [ecs.Query].
+//
+// # Example
+//
+//	world := ecs.NewWorld()
+//
+//	filter := NewFilter9[A, B, C, D, E, F, G, H, I]()
+//	query := filter.Query(&world)
+//	for query.Next() {
+//		entity = query.Entity()
+//		a, b, c, d, e, f, g, h, i := query.Get()
+//	}
+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
+}
+
+// Get returns all queried components for the current query iterator position.
+//
+// Use [ecs.Query.Entity] to get the current Entity.
+func (q *Query9[A, B, C, D, E, F, G, H, I]) Get() (*A, *B, *C, *D, *E, *F, *G, *H, *I) {
+	return (*A)(q.Query.Get(q.id0)),
+		(*B)(q.Query.Get(q.id1)),
+		(*C)(q.Query.Get(q.id2)),
+		(*D)(q.Query.Get(q.id3)),
+		(*E)(q.Query.Get(q.id4)),
+		(*F)(q.Query.Get(q.id5)),
+		(*G)(q.Query.Get(q.id6)),
+		(*H)(q.Query.Get(q.id7)),
+		(*I)(q.Query.Get(q.id8))
+}
+
+// Relation returns the target entity for the query's relation.
+//
+// Panics if the entity does not have the given component, or if the component is not an [ecs.Relation].
+// 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 {
+		panic("query has no relation")
+	}
+	return q.Query.Relation(ecs.ID(q.target))
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+// Filter10 is a helper for building [Query10] query iterators.
+//
+// # Example
+//
+//	world := ecs.NewWorld()
+//
+//	filter := NewFilter10[A, B, C, D, E, F, G, H, I, J]()
+//	query := filter.Query(&world)
+//
+//	complexFilter :=
+//		NewFilter10[A, B, C, D, E, F, G, H, I, J]().
+//			Optional(T[A]()).
+//			With(T2[V, W]()...).
+//			Without(T3[X, Y, Z]()...).
+type Filter10[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any] filter
+
+// NewFilter10 creates a generic Filter10 for ten components.
+//
+// See also [ecs.World.Query].
+func NewFilter10[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any]() *Filter10[A, B, C, D, E, F, G, H, I, J] {
+	f := Filter10[A, B, C, D, E, F, G, H, I, J](newFilter(
+		typeOf[A](),
+		typeOf[B](),
+		typeOf[C](),
+		typeOf[D](),
+		typeOf[E](),
+		typeOf[F](),
+		typeOf[G](),
+		typeOf[H](),
+		typeOf[I](),
+		typeOf[J](),
+	))
+	return &f
+}
+
+// Optional makes some of the query's components optional.
+//
+// Create the required mask items with [T].
+//
+// Only affects component types that were specified in the query.
+func (q *Filter10[A, B, C, D, E, F, G, H, I, J]) Optional(mask ...Comp) *Filter10[A, B, C, D, E, F, G, H, I, J] {
+	if q.compiled.locked {
+		panic("can't modify a registered filter")
+	}
+	q.optional = append(q.optional, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// With adds components that are required, but not accessible via [Query10.Get].
+//
+// Create the required mask items with [T].
+func (q *Filter10[A, B, C, D, E, F, G, H, I, J]) With(mask ...Comp) *Filter10[A, B, C, D, E, F, G, H, I, J] {
+	if q.compiled.locked {
+		panic("can't modify a registered filter")
+	}
+	q.include = append(q.include, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Without excludes entities with any of the given components from the query.
+//
+// Create the required mask items with [T].
+func (q *Filter10[A, B, C, D, E, F, G, H, I, J]) Without(mask ...Comp) *Filter10[A, B, C, D, E, F, G, H, I, J] {
+	if q.compiled.locked {
+		panic("can't modify a registered filter")
+	}
+	q.exclude = append(q.exclude, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// WithRelation sets the filter's [ecs.Relation] component and optionally
+// restricts the query to entities that have the given relation target.
+//
+// Use without the optional argument to specify the relation target in [Filter10.Query].
+// If the optional argument is provided, the filter's relation target is set permanently.
+//
+// Create the required component ID with [T].
+func (q *Filter10[A, B, C, D, E, F, G, H, I, J]) WithRelation(comp Comp, target ...ecs.Entity) *Filter10[A, B, C, D, E, F, G, H, I, J] {
+	if q.compiled.locked {
+		panic("can't modify a registered filter")
+	}
+	q.targetType = comp
+	if len(target) > 0 {
+		q.target = target[0]
+		q.hasTarget = true
+	}
+	q.compiled.Reset()
+	return q
+}
+
+// Query builds a [Query10] query for iteration, with an optional relation target.
+//
+// A relation target can't be used:
+//   - if [Filter10.WithRelation] was not called
+//   - if the target was already set via [Filter10.WithRelation]
+//   - if the filter is registered for caching
+//
+// Panics in these cases.
+func (q *Filter10[A, B, C, D, E, F, G, H, I, J]) Query(w *ecs.World, target ...ecs.Entity) Query10[A, B, C, D, E, F, G, H, I, J] {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+
+	filter := q.compiled.filter
+	if len(target) > 0 {
+		if q.compiled.locked {
+			panic("can't change relation target on a cached query")
+		}
+		if q.hasTarget {
+			panic("can't change relation target on a query with fixed target")
+		}
+		q.compiled.relationFilter.Filter = &q.compiled.maskFilter
+		q.compiled.relationFilter.Target = target[0]
+		filter = &q.compiled.relationFilter
+	}
+
+	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],
+	}
+}
+
+// Register the filter for caching.
+//
+// See [ecs.Cache] for details on filter caching.
+func (q *Filter10[A, B, C, D, E, F, G, H, I, J]) Register(w *ecs.World) {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+	q.compiled.Register(w)
+}
+
+// Unregister the filter from caching.
+//
+// See [ecs.Cache] for details on filter caching.
+func (q *Filter10[A, B, C, D, E, F, G, H, I, J]) Unregister(w *ecs.World) {
+	q.compiled.Unregister(w)
+}
+
+// Query10 is a generic query iterator for ten components.
+//
+// Create it with [NewFilter10] and [Filter10.Query].
+//
+// Also has all methods of [ecs.Query].
+//
+// # Example
+//
+//	world := ecs.NewWorld()
+//
+//	filter := NewFilter10[A, B, C, D, E, F, G, H, I, J]()
+//	query := filter.Query(&world)
+//	for query.Next() {
+//		entity = query.Entity()
+//		a, b, c, d, e, f, g, h, i, j := query.Get()
+//	}
+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
+}
+
+// Get returns all queried components for the current query iterator position.
+//
+// Use [ecs.Query.Entity] to get the current Entity.
+func (q *Query10[A, B, C, D, E, F, G, H, I, J]) Get() (*A, *B, *C, *D, *E, *F, *G, *H, *I, *J) {
+	return (*A)(q.Query.Get(q.id0)),
+		(*B)(q.Query.Get(q.id1)),
+		(*C)(q.Query.Get(q.id2)),
+		(*D)(q.Query.Get(q.id3)),
+		(*E)(q.Query.Get(q.id4)),
+		(*F)(q.Query.Get(q.id5)),
+		(*G)(q.Query.Get(q.id6)),
+		(*H)(q.Query.Get(q.id7)),
+		(*I)(q.Query.Get(q.id8)),
+		(*J)(q.Query.Get(q.id9))
+}
+
+// Relation returns the target entity for the query's relation.
+//
+// Panics if the entity does not have the given component, or if the component is not an [ecs.Relation].
+// 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 {
+		panic("query has no relation")
+	}
+	return q.Query.Relation(ecs.ID(q.target))
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+// Filter11 is a helper for building [Query11] query iterators.
+//
+// # Example
+//
+//	world := ecs.NewWorld()
+//
+//	filter := NewFilter11[A, B, C, D, E, F, G, H, I, J, K]()
+//	query := filter.Query(&world)
+//
+//	complexFilter :=
+//		NewFilter11[A, B, C, D, E, F, G, H, I, J, K]().
+//			Optional(T[A]()).
+//			With(T2[V, W]()...).
+//			Without(T3[X, Y, Z]()...).
+type Filter11[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any, K any] filter
+
+// NewFilter11 creates a generic Filter11 for eleven components.
+//
+// See also [ecs.World.Query].
+func NewFilter11[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any, K any]() *Filter11[A, B, C, D, E, F, G, H, I, J, K] {
+	f := Filter11[A, B, C, D, E, F, G, H, I, J, K](newFilter(
+		typeOf[A](),
+		typeOf[B](),
+		typeOf[C](),
+		typeOf[D](),
+		typeOf[E](),
+		typeOf[F](),
+		typeOf[G](),
+		typeOf[H](),
+		typeOf[I](),
+		typeOf[J](),
+		typeOf[K](),
+	))
+	return &f
+}
+
+// Optional makes some of the query's components optional.
+//
+// Create the required mask items with [T].
+//
+// Only affects component types that were specified in the query.
+func (q *Filter11[A, B, C, D, E, F, G, H, I, J, K]) Optional(mask ...Comp) *Filter11[A, B, C, D, E, F, G, H, I, J, K] {
+	if q.compiled.locked {
+		panic("can't modify a registered filter")
+	}
+	q.optional = append(q.optional, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// With adds components that are required, but not accessible via [Query11.Get].
+//
+// Create the required mask items with [T].
+func (q *Filter11[A, B, C, D, E, F, G, H, I, J, K]) With(mask ...Comp) *Filter11[A, B, C, D, E, F, G, H, I, J, K] {
+	if q.compiled.locked {
+		panic("can't modify a registered filter")
+	}
+	q.include = append(q.include, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Without excludes entities with any of the given components from the query.
+//
+// Create the required mask items with [T].
+func (q *Filter11[A, B, C, D, E, F, G, H, I, J, K]) Without(mask ...Comp) *Filter11[A, B, C, D, E, F, G, H, I, J, K] {
+	if q.compiled.locked {
+		panic("can't modify a registered filter")
+	}
+	q.exclude = append(q.exclude, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// WithRelation sets the filter's [ecs.Relation] component and optionally
+// restricts the query to entities that have the given relation target.
+//
+// Use without the optional argument to specify the relation target in [Filter11.Query].
+// If the optional argument is provided, the filter's relation target is set permanently.
+//
+// Create the required component ID with [T].
+func (q *Filter11[A, B, C, D, E, F, G, H, I, J, K]) WithRelation(comp Comp, target ...ecs.Entity) *Filter11[A, B, C, D, E, F, G, H, I, J, K] {
+	if q.compiled.locked {
+		panic("can't modify a registered filter")
+	}
+	q.targetType = comp
+	if len(target) > 0 {
+		q.target = target[0]
+		q.hasTarget = true
+	}
+	q.compiled.Reset()
+	return q
+}
+
+// Query builds a [Query11] query for iteration, with an optional relation target.
+//
+// A relation target can't be used:
+//   - if [Filter11.WithRelation] was not called
+//   - if the target was already set via [Filter11.WithRelation]
+//   - if the filter is registered for caching
+//
+// Panics in these cases.
+func (q *Filter11[A, B, C, D, E, F, G, H, I, J, K]) Query(w *ecs.World, target ...ecs.Entity) Query11[A, B, C, D, E, F, G, H, I, J, K] {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+
+	filter := q.compiled.filter
+	if len(target) > 0 {
+		if q.compiled.locked {
+			panic("can't change relation target on a cached query")
+		}
+		if q.hasTarget {
+			panic("can't change relation target on a query with fixed target")
+		}
+		q.compiled.relationFilter.Filter = &q.compiled.maskFilter
+		q.compiled.relationFilter.Target = target[0]
+		filter = &q.compiled.relationFilter
+	}
+
+	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],
+	}
+}
+
+// Register the filter for caching.
+//
+// See [ecs.Cache] for details on filter caching.
+func (q *Filter11[A, B, C, D, E, F, G, H, I, J, K]) Register(w *ecs.World) {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+	q.compiled.Register(w)
+}
+
+// Unregister the filter from caching.
+//
+// See [ecs.Cache] for details on filter caching.
+func (q *Filter11[A, B, C, D, E, F, G, H, I, J, K]) Unregister(w *ecs.World) {
+	q.compiled.Unregister(w)
+}
+
+// Query11 is a generic query iterator for eleven components.
+//
+// Create it with [NewFilter11] and [Filter11.Query].
+//
+// Also has all methods of [ecs.Query].
+//
+// # Example
+//
+//	world := ecs.NewWorld()
+//
+//	filter := NewFilter11[A, B, C, D, E, F, G, H, I, J, K]()
+//	query := filter.Query(&world)
+//	for query.Next() {
+//		entity = query.Entity()
+//		a, b, c, d, e, f, g, h, i, j, k := query.Get()
+//	}
+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
+}
+
+// Get returns all queried components for the current query iterator position.
+//
+// Use [ecs.Query.Entity] to get the current Entity.
+func (q *Query11[A, B, C, D, E, F, G, H, I, J, K]) Get() (*A, *B, *C, *D, *E, *F, *G, *H, *I, *J, *K) {
+	return (*A)(q.Query.Get(q.id0)),
+		(*B)(q.Query.Get(q.id1)),
+		(*C)(q.Query.Get(q.id2)),
+		(*D)(q.Query.Get(q.id3)),
+		(*E)(q.Query.Get(q.id4)),
+		(*F)(q.Query.Get(q.id5)),
+		(*G)(q.Query.Get(q.id6)),
+		(*H)(q.Query.Get(q.id7)),
+		(*I)(q.Query.Get(q.id8)),
+		(*J)(q.Query.Get(q.id9)),
+		(*K)(q.Query.Get(q.id10))
+}
+
+// Relation returns the target entity for the query's relation.
+//
+// Panics if the entity does not have the given component, or if the component is not an [ecs.Relation].
+// 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 {
+		panic("query has no relation")
+	}
+	return q.Query.Relation(ecs.ID(q.target))
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+// Filter12 is a helper for building [Query12] query iterators.
+//
+// # Example
+//
+//	world := ecs.NewWorld()
+//
+//	filter := NewFilter12[A, B, C, D, E, F, G, H, I, J, K, L]()
+//	query := filter.Query(&world)
+//
+//	complexFilter :=
+//		NewFilter12[A, B, C, D, E, F, G, H, I, J, K, L]().
+//			Optional(T[A]()).
+//			With(T2[V, W]()...).
+//			Without(T3[X, Y, Z]()...).
+type Filter12[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any, K any, L any] filter
+
+// NewFilter12 creates a generic Filter12 for twelve components.
+//
+// See also [ecs.World.Query].
+func NewFilter12[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any, K any, L any]() *Filter12[A, B, C, D, E, F, G, H, I, J, K, L] {
+	f := Filter12[A, B, C, D, E, F, G, H, I, J, K, L](newFilter(
+		typeOf[A](),
+		typeOf[B](),
+		typeOf[C](),
+		typeOf[D](),
+		typeOf[E](),
+		typeOf[F](),
+		typeOf[G](),
+		typeOf[H](),
+		typeOf[I](),
+		typeOf[J](),
+		typeOf[K](),
+		typeOf[L](),
+	))
+	return &f
+}
+
+// Optional makes some of the query's components optional.
+//
+// Create the required mask items with [T].
+//
+// Only affects component types that were specified in the query.
+func (q *Filter12[A, B, C, D, E, F, G, H, I, J, K, L]) Optional(mask ...Comp) *Filter12[A, B, C, D, E, F, G, H, I, J, K, L] {
+	if q.compiled.locked {
+		panic("can't modify a registered filter")
+	}
+	q.optional = append(q.optional, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// With adds components that are required, but not accessible via [Query12.Get].
+//
+// Create the required mask items with [T].
+func (q *Filter12[A, B, C, D, E, F, G, H, I, J, K, L]) With(mask ...Comp) *Filter12[A, B, C, D, E, F, G, H, I, J, K, L] {
+	if q.compiled.locked {
+		panic("can't modify a registered filter")
+	}
+	q.include = append(q.include, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// Without excludes entities with any of the given components from the query.
+//
+// Create the required mask items with [T].
+func (q *Filter12[A, B, C, D, E, F, G, H, I, J, K, L]) Without(mask ...Comp) *Filter12[A, B, C, D, E, F, G, H, I, J, K, L] {
+	if q.compiled.locked {
+		panic("can't modify a registered filter")
+	}
+	q.exclude = append(q.exclude, mask...)
+	q.compiled.Reset()
+	return q
+}
+
+// WithRelation sets the filter's [ecs.Relation] component and optionally
+// restricts the query to entities that have the given relation target.
+//
+// Use without the optional argument to specify the relation target in [Filter12.Query].
+// If the optional argument is provided, the filter's relation target is set permanently.
+//
+// Create the required component ID with [T].
+func (q *Filter12[A, B, C, D, E, F, G, H, I, J, K, L]) WithRelation(comp Comp, target ...ecs.Entity) *Filter12[A, B, C, D, E, F, G, H, I, J, K, L] {
+	if q.compiled.locked {
+		panic("can't modify a registered filter")
+	}
+	q.targetType = comp
+	if len(target) > 0 {
+		q.target = target[0]
+		q.hasTarget = true
+	}
+	q.compiled.Reset()
+	return q
+}
+
+// Query builds a [Query12] query for iteration, with an optional relation target.
+//
+// A relation target can't be used:
+//   - if [Filter12.WithRelation] was not called
+//   - if the target was already set via [Filter12.WithRelation]
+//   - if the filter is registered for caching
+//
+// Panics in these cases.
+func (q *Filter12[A, B, C, D, E, F, G, H, I, J, K, L]) Query(w *ecs.World, target ...ecs.Entity) Query12[A, B, C, D, E, F, G, H, I, J, K, L] {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+
+	filter := q.compiled.filter
+	if len(target) > 0 {
+		if q.compiled.locked {
+			panic("can't change relation target on a cached query")
+		}
+		if q.hasTarget {
+			panic("can't change relation target on a query with fixed target")
+		}
+		q.compiled.relationFilter.Filter = &q.compiled.maskFilter
+		q.compiled.relationFilter.Target = target[0]
+		filter = &q.compiled.relationFilter
+	}
+
+	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],
+	}
+}
+
+// Register the filter for caching.
+//
+// See [ecs.Cache] for details on filter caching.
+func (q *Filter12[A, B, C, D, E, F, G, H, I, J, K, L]) Register(w *ecs.World) {
+	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+	q.compiled.Register(w)
+}
+
+// Unregister the filter from caching.
+//
+// See [ecs.Cache] for details on filter caching.
+func (q *Filter12[A, B, C, D, E, F, G, H, I, J, K, L]) Unregister(w *ecs.World) {
+	q.compiled.Unregister(w)
+}
+
+// Query12 is a generic query iterator for twelve components.
+//
+// Create it with [NewFilter12] and [Filter12.Query].
+//
+// Also has all methods of [ecs.Query].
+//
+// # Example
+//
+//	world := ecs.NewWorld()
+//
+//	filter := NewFilter12[A, B, C, D, E, F, G, H, I, J, K, L]()
+//	query := filter.Query(&world)
+//	for query.Next() {
+//		entity = query.Entity()
+//		a, b, c, d, e, f, g, h, i, j, k, l := query.Get()
+//	}
+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
+}
+
+// Get returns all queried components for the current query iterator position.
+//
+// Use [ecs.Query.Entity] to get the current Entity.
+func (q *Query12[A, B, C, D, E, F, G, H, I, J, K, L]) Get() (*A, *B, *C, *D, *E, *F, *G, *H, *I, *J, *K, *L) {
+	return (*A)(q.Query.Get(q.id0)),
+		(*B)(q.Query.Get(q.id1)),
+		(*C)(q.Query.Get(q.id2)),
+		(*D)(q.Query.Get(q.id3)),
+		(*E)(q.Query.Get(q.id4)),
+		(*F)(q.Query.Get(q.id5)),
+		(*G)(q.Query.Get(q.id6)),
+		(*H)(q.Query.Get(q.id7)),
+		(*I)(q.Query.Get(q.id8)),
+		(*J)(q.Query.Get(q.id9)),
+		(*K)(q.Query.Get(q.id10)),
+		(*L)(q.Query.Get(q.id11))
+}
+
+// Relation returns the target entity for the query's relation.
+//
+// Panics if the entity does not have the given component, or if the component is not an [ecs.Relation].
+// 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 {
+		panic("query has no relation")
+	}
+	return q.Query.Relation(ecs.ID(q.target))
+}


Test patch:
diff --git a/generic/map_generated_test.go b/generic/map_generated_test.go
index f602618..1d129cc 100644
--- a/generic/map_generated_test.go
+++ b/generic/map_generated_test.go
@@ -708,3 +708,419 @@ func TestMap8Generated(t *testing.T) {
 	assert.Equal(t, target, mapper.GetRelation(e))
 	assert.Equal(t, target, mapper.GetRelationUnchecked(e))
 }
+
+func TestMap9Generated(t *testing.T) {
+	w := ecs.NewWorld()
+	registerAll(&w)
+
+	mut := NewMap9[
+		testStruct0, testStruct1, testStruct2, testStruct3,
+		testStruct4, testStruct5, testStruct6, testStruct7,
+		testStruct8,
+	](&w)
+	map0 := NewMap[testStruct0](&w)
+	map1 := NewMap[testStruct1](&w)
+
+	e := mut.New()
+	s0, s1, _, _, _, _, _, _, _ := mut.Get(e)
+	assert.NotNil(t, s0)
+	assert.NotNil(t, s1)
+
+	mut.Remove(e)
+	assert.False(t, map0.Has(e))
+	assert.False(t, map1.Has(e))
+
+	mut.Add(e)
+	s0, s1, _, _, _, _, _, _, _ = mut.Get(e)
+	assert.NotNil(t, s0)
+	assert.NotNil(t, s1)
+
+	e = w.NewEntity()
+	mut.Assign(e,
+		&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+		&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+		&testStruct8{},
+	)
+	assert.True(t, map0.Has(e))
+	assert.True(t, map1.Has(e))
+
+	e = mut.New()
+	assert.True(t, map0.Has(e))
+	assert.True(t, map1.Has(e))
+
+	e = mut.NewWith(
+		&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+		&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+		&testStruct8{},
+	)
+
+	assert.Panics(t, func() {
+		mut.NewWith(
+			&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+			&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+			&testStruct8{},
+			e,
+		)
+	})
+
+	s0, s1, _, _, _, _, _, _, _ = mut.Get(e)
+	assert.NotNil(t, s0)
+	assert.NotNil(t, s1)
+
+	mut.NewBatch(2)
+
+	q := mut.NewBatchQ(2)
+	q.Close()
+
+	mut.GetUnchecked(e)
+
+	mut.RemoveEntities(true)
+	mut.RemoveEntities(false)
+
+	assert.Panics(t, func() { mut.Get(e) })
+
+	target := w.NewEntity()
+	mut = NewMap9[
+		testStruct0, testStruct1, testStruct2, testStruct3,
+		testStruct4, testStruct5, testStruct6, testStruct7,
+		testStruct8,
+	](&w)
+
+	assert.Panics(t, func() { mut.New(target) })
+	assert.Panics(t, func() { mut.NewBatch(5, target) })
+	assert.Panics(t, func() { mut.NewBatchQ(5, target) })
+
+	mut2 := NewMap9[
+		testRelationA, testStruct1, testStruct2, testStruct3,
+		testStruct4, testStruct5, testStruct6, testStruct7,
+		testStruct8,
+	](&w, T[testRelationA]())
+
+	mut2.New(target)
+	mut2.NewBatch(5, target)
+	q2 := mut2.NewBatchQ(5, target)
+	assert.Equal(t, 5, q2.Count())
+	q2.Close()
+
+	e = mut2.NewWith(
+		&testRelationA{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+		&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+		&testStruct8{},
+		target,
+	)
+	mapper := NewMap[testRelationA](&w)
+	assert.Equal(t, target, mapper.GetRelation(e))
+	assert.Equal(t, target, mapper.GetRelationUnchecked(e))
+}
+
+func TestMap10Generated(t *testing.T) {
+	w := ecs.NewWorld()
+	registerAll(&w)
+
+	mut := NewMap10[
+		testStruct0, testStruct1, testStruct2, testStruct3,
+		testStruct4, testStruct5, testStruct6, testStruct7,
+		testStruct8, testStruct9,
+	](&w)
+	map0 := NewMap[testStruct0](&w)
+	map1 := NewMap[testStruct1](&w)
+
+	e := mut.New()
+	s0, s1, _, _, _, _, _, _, _, _ := mut.Get(e)
+	assert.NotNil(t, s0)
+	assert.NotNil(t, s1)
+
+	mut.Remove(e)
+	assert.False(t, map0.Has(e))
+	assert.False(t, map1.Has(e))
+
+	mut.Add(e)
+	s0, s1, _, _, _, _, _, _, _, _ = mut.Get(e)
+	assert.NotNil(t, s0)
+	assert.NotNil(t, s1)
+
+	e = w.NewEntity()
+	mut.Assign(e,
+		&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+		&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+		&testStruct8{}, &testStruct9{},
+	)
+	assert.True(t, map0.Has(e))
+	assert.True(t, map1.Has(e))
+
+	e = mut.New()
+	assert.True(t, map0.Has(e))
+	assert.True(t, map1.Has(e))
+
+	e = mut.NewWith(
+		&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+		&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+		&testStruct8{}, &testStruct9{},
+	)
+
+	assert.Panics(t, func() {
+		mut.NewWith(
+			&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+			&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+			&testStruct8{}, &testStruct9{},
+			e,
+		)
+	})
+
+	s0, s1, _, _, _, _, _, _, _, _ = mut.Get(e)
+	assert.NotNil(t, s0)
+	assert.NotNil(t, s1)
+
+	mut.NewBatch(2)
+
+	q := mut.NewBatchQ(2)
+	q.Close()
+
+	mut.GetUnchecked(e)
+
+	mut.RemoveEntities(true)
+	mut.RemoveEntities(false)
+
+	assert.Panics(t, func() { mut.Get(e) })
+
+	target := w.NewEntity()
+	mut = NewMap10[
+		testStruct0, testStruct1, testStruct2, testStruct3,
+		testStruct4, testStruct5, testStruct6, testStruct7,
+		testStruct8, testStruct9,
+	](&w)
+
+	assert.Panics(t, func() { mut.New(target) })
+	assert.Panics(t, func() { mut.NewBatch(5, target) })
+	assert.Panics(t, func() { mut.NewBatchQ(5, target) })
+
+	mut2 := NewMap10[
+		testRelationA, testStruct1, testStruct2, testStruct3,
+		testStruct4, testStruct5, testStruct6, testStruct7,
+		testStruct8, testStruct9,
+	](&w, T[testRelationA]())
+
+	mut2.New(target)
+	mut2.NewBatch(5, target)
+	q2 := mut2.NewBatchQ(5, target)
+	assert.Equal(t, 5, q2.Count())
+	q2.Close()
+
+	e = mut2.NewWith(
+		&testRelationA{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+		&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+		&testStruct8{}, &testStruct9{},
+		target,
+	)
+	mapper := NewMap[testRelationA](&w)
+	assert.Equal(t, target, mapper.GetRelation(e))
+	assert.Equal(t, target, mapper.GetRelationUnchecked(e))
+}
+
+func TestMap11Generated(t *testing.T) {
+	w := ecs.NewWorld()
+	registerAll(&w)
+
+	mut := NewMap11[
+		testStruct0, testStruct1, testStruct2, testStruct3,
+		testStruct4, testStruct5, testStruct6, testStruct7,
+		testStruct8, testStruct9, testStruct10,
+	](&w)
+	map0 := NewMap[testStruct0](&w)
+	map1 := NewMap[testStruct1](&w)
+
+	e := mut.New()
+	s0, s1, _, _, _, _, _, _, _, _, _ := mut.Get(e)
+	assert.NotNil(t, s0)
+	assert.NotNil(t, s1)
+
+	mut.Remove(e)
+	assert.False(t, map0.Has(e))
+	assert.False(t, map1.Has(e))
+
+	mut.Add(e)
+	s0, s1, _, _, _, _, _, _, _, _, _ = mut.Get(e)
+	assert.NotNil(t, s0)
+	assert.NotNil(t, s1)
+
+	e = w.NewEntity()
+	mut.Assign(e,
+		&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+		&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+		&testStruct8{}, &testStruct9{}, &testStruct10{},
+	)
+	assert.True(t, map0.Has(e))
+	assert.True(t, map1.Has(e))
+
+	e = mut.New()
+	assert.True(t, map0.Has(e))
+	assert.True(t, map1.Has(e))
+
+	e = mut.NewWith(
+		&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+		&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+		&testStruct8{}, &testStruct9{}, &testStruct10{},
+	)
+
+	assert.Panics(t, func() {
+		mut.NewWith(
+			&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+			&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+			&testStruct8{}, &testStruct9{}, &testStruct10{},
+			e,
+		)
+	})
+
+	s0, s1, _, _, _, _, _, _, _, _, _ = mut.Get(e)
+	assert.NotNil(t, s0)
+	assert.NotNil(t, s1)
+
+	mut.NewBatch(2)
+
+	q := mut.NewBatchQ(2)
+	q.Close()
+
+	mut.GetUnchecked(e)
+
+	mut.RemoveEntities(true)
+	mut.RemoveEntities(false)
+
+	assert.Panics(t, func() { mut.Get(e) })
+
+	target := w.NewEntity()
+	mut = NewMap11[
+		testStruct0, testStruct1, testStruct2, testStruct3,
+		testStruct4, testStruct5, testStruct6, testStruct7,
+		testStruct8, testStruct9, testStruct10,
+	](&w)
+
+	assert.Panics(t, func() { mut.New(target) })
+	assert.Panics(t, func() { mut.NewBatch(5, target) })
+	assert.Panics(t, func() { mut.NewBatchQ(5, target) })
+
+	mut2 := NewMap11[
+		testRelationA, testStruct1, testStruct2, testStruct3,
+		testStruct4, testStruct5, testStruct6, testStruct7,
+		testStruct8, testStruct9, testStruct10,
+	](&w, T[testRelationA]())
+
+	mut2.New(target)
+	mut2.NewBatch(5, target)
+	q2 := mut2.NewBatchQ(5, target)
+	assert.Equal(t, 5, q2.Count())
+	q2.Close()
+
+	e = mut2.NewWith(
+		&testRelationA{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+		&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+		&testStruct8{}, &testStruct9{}, &testStruct10{},
+		target,
+	)
+	mapper := NewMap[testRelationA](&w)
+	assert.Equal(t, target, mapper.GetRelation(e))
+	assert.Equal(t, target, mapper.GetRelationUnchecked(e))
+}
+
+func TestMap12Generated(t *testing.T) {
+	w := ecs.NewWorld()
+	registerAll(&w)
+
+	mut := NewMap12[
+		testStruct0, testStruct1, testStruct2, testStruct3,
+		testStruct4, testStruct5, testStruct6, testStruct7,
+		testStruct8, testStruct9, testStruct10, testStruct11,
+	](&w)
+	map0 := NewMap[testStruct0](&w)
+	map1 := NewMap[testStruct1](&w)
+
+	e := mut.New()
+	s0, s1, _, _, _, _, _, _, _, _, _, _ := mut.Get(e)
+	assert.NotNil(t, s0)
+	assert.NotNil(t, s1)
+
+	mut.Remove(e)
+	assert.False(t, map0.Has(e))
+	assert.False(t, map1.Has(e))
+
+	mut.Add(e)
+	s0, s1, _, _, _, _, _, _, _, _, _, _ = mut.Get(e)
+	assert.NotNil(t, s0)
+	assert.NotNil(t, s1)
+
+	e = w.NewEntity()
+	mut.Assign(e,
+		&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+		&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+		&testStruct8{}, &testStruct9{}, &testStruct10{}, &testStruct11{},
+	)
+	assert.True(t, map0.Has(e))
+	assert.True(t, map1.Has(e))
+
+	e = mut.New()
+	assert.True(t, map0.Has(e))
+	assert.True(t, map1.Has(e))
+
+	e = mut.NewWith(
+		&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+		&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+		&testStruct8{}, &testStruct9{}, &testStruct10{}, &testStruct11{},
+	)
+
+	assert.Panics(t, func() {
+		mut.NewWith(
+			&testStruct0{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+			&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+			&testStruct8{}, &testStruct9{}, &testStruct10{}, &testStruct11{},
+			e,
+		)
+	})
+
+	s0, s1, _, _, _, _, _, _, _, _, _, _ = mut.Get(e)
+	assert.NotNil(t, s0)
+	assert.NotNil(t, s1)
+
+	mut.NewBatch(2)
+
+	q := mut.NewBatchQ(2)
+	q.Close()
+
+	mut.GetUnchecked(e)
+
+	mut.RemoveEntities(true)
+	mut.RemoveEntities(false)
+
+	assert.Panics(t, func() { mut.Get(e) })
+
+	target := w.NewEntity()
+	mut = NewMap12[
+		testStruct0, testStruct1, testStruct2, testStruct3,
+		testStruct4, testStruct5, testStruct6, testStruct7,
+		testStruct8, testStruct9, testStruct10, testStruct11,
+	](&w)
+
+	assert.Panics(t, func() { mut.New(target) })
+	assert.Panics(t, func() { mut.NewBatch(5, target) })
+	assert.Panics(t, func() { mut.NewBatchQ(5, target) })
+
+	mut2 := NewMap12[
+		testRelationA, testStruct1, testStruct2, testStruct3,
+		testStruct4, testStruct5, testStruct6, testStruct7,
+		testStruct8, testStruct9, testStruct10, testStruct11,
+	](&w, T[testRelationA]())
+
+	mut2.New(target)
+	mut2.NewBatch(5, target)
+	q2 := mut2.NewBatchQ(5, target)
+	assert.Equal(t, 5, q2.Count())
+	q2.Close()
+
+	e = mut2.NewWith(
+		&testRelationA{}, &testStruct1{}, &testStruct2{}, &testStruct3{},
+		&testStruct4{}, &testStruct5{}, &testStruct6{}, &testStruct7{},
+		&testStruct8{}, &testStruct9{}, &testStruct10{}, &testStruct11{},
+		target,
+	)
+	mapper := NewMap[testRelationA](&w)
+	assert.Equal(t, target, mapper.GetRelation(e))
+	assert.Equal(t, target, mapper.GetRelationUnchecked(e))
+}
diff --git a/generic/mask_test.go b/generic/mask_test.go
index a66ddbd..beb6bcb 100644
--- a/generic/mask_test.go
+++ b/generic/mask_test.go
@@ -99,4 +99,78 @@ func TestGenericMask(t *testing.T) {
 			typeOf[testStruct7](),
 		},
 	)
+
+	assert.Equal(t,
+		T9[testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7,
+			testStruct8](),
+		[]Comp{
+			typeOf[testStruct0](),
+			typeOf[testStruct1](),
+			typeOf[testStruct2](),
+			typeOf[testStruct3](),
+			typeOf[testStruct4](),
+			typeOf[testStruct5](),
+			typeOf[testStruct6](),
+			typeOf[testStruct7](),
+			typeOf[testStruct8](),
+		},
+	)
+
+	assert.Equal(t,
+		T10[testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7,
+			testStruct8, testStruct9](),
+		[]Comp{
+			typeOf[testStruct0](),
+			typeOf[testStruct1](),
+			typeOf[testStruct2](),
+			typeOf[testStruct3](),
+			typeOf[testStruct4](),
+			typeOf[testStruct5](),
+			typeOf[testStruct6](),
+			typeOf[testStruct7](),
+			typeOf[testStruct8](),
+			typeOf[testStruct9](),
+		},
+	)
+
+	assert.Equal(t,
+		T11[testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7,
+			testStruct8, testStruct9, testStruct10](),
+		[]Comp{
+			typeOf[testStruct0](),
+			typeOf[testStruct1](),
+			typeOf[testStruct2](),
+			typeOf[testStruct3](),
+			typeOf[testStruct4](),
+			typeOf[testStruct5](),
+			typeOf[testStruct6](),
+			typeOf[testStruct7](),
+			typeOf[testStruct8](),
+			typeOf[testStruct9](),
+			typeOf[testStruct10](),
+		},
+	)
+
+	assert.Equal(t,
+		T12[testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7,
+			testStruct8, testStruct9, testStruct10, testStruct11](),
+		[]Comp{
+			typeOf[testStruct0](),
+			typeOf[testStruct1](),
+			typeOf[testStruct2](),
+			typeOf[testStruct3](),
+			typeOf[testStruct4](),
+			typeOf[testStruct5](),
+			typeOf[testStruct6](),
+			typeOf[testStruct7](),
+			typeOf[testStruct8](),
+			typeOf[testStruct9](),
+			typeOf[testStruct10](),
+			typeOf[testStruct11](),
+		},
+	)
 }
diff --git a/generic/query_test.go b/generic/query_test.go
index a6c2685..635c59f 100644
--- a/generic/query_test.go
+++ b/generic/query_test.go
@@ -961,6 +961,518 @@ func TestQuery8(t *testing.T) {
 	filter2.Unregister(&w)
 }
 
+func TestQuery9(t *testing.T) {
+	w := ecs.NewWorld()
+
+	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: 1, Comp: &testStruct1{2}})
+	w.Assign(e1, ecs.Component{ID: 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: 3, Comp: &testStruct3{4, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: 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: 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: 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: 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: 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: 9, Comp: &testStruct9{10, 0, 0, 0, 0, 0, 0, 0, 0}})
+
+	w.Assign(e2, ecs.Component{ID: 10, Comp: &testStruct10{}})
+
+	cnt := 0
+	filter :=
+		NewFilter9[
+			testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7,
+			testStruct8,
+		]().
+			Optional(T[testStruct1]()).
+			With(T[testStruct9]()).
+			Without(T[testStruct10]())
+
+	filter.Register(&w)
+
+	query := filter.Query(&w)
+	for query.Next() {
+		c1, c2, c3, c4, c5, c6, c7, c8, c9 := query.Get()
+		assert.Equal(t, cnt+1, int(c1.val))
+		assert.Equal(t, cnt+2, int(c2.val))
+		assert.Equal(t, cnt+3, int(c3.val))
+		assert.Equal(t, cnt+4, int(c4.val))
+		assert.Equal(t, cnt+5, int(c5.val))
+		assert.Equal(t, cnt+6, int(c6.val))
+		assert.Equal(t, cnt+7, int(c7.val))
+		assert.Equal(t, cnt+8, int(c8.val))
+		assert.Equal(t, cnt+9, int(c9.val))
+		assert.Panics(t, func() { query.Relation() })
+		cnt++
+	}
+	assert.Equal(t, 1, cnt)
+
+	filter.Unregister(&w)
+	assert.Panics(t, func() { filter.Unregister(&w) })
+
+	targ := w.NewEntity(0)
+
+	w.Add(e0, relID)
+	w.Add(e1, relID)
+	w.Add(e2, relID)
+
+	w.Relations().Set(e0, relID, targ)
+
+	filter2 :=
+		NewFilter9[
+			testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7,
+			testRelationA,
+		]().
+			WithRelation(T[testRelationA](), targ)
+
+	q := filter2.Query(&w)
+	assert.Equal(t, 1, q.Count())
+	for q.Next() {
+		trg := q.Relation()
+		assert.Equal(t, targ, trg)
+	}
+
+	assert.Panics(t, func() { filter2.Query(&w, targ) })
+
+	filter2 =
+		NewFilter9[
+			testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7,
+			testRelationA,
+		]().
+			WithRelation(T[testRelationA]())
+	q = filter2.Query(&w)
+	assert.Equal(t, 2, q.Count())
+	q.Close()
+	q = filter2.Query(&w, targ)
+	assert.Equal(t, 1, q.Count())
+	q.Close()
+
+	filter2.Register(&w)
+	assert.Panics(t, func() { filter2.Query(&w, targ) })
+	assert.Panics(t, func() { filter2.Optional(T[testStruct0]()) })
+	assert.Panics(t, func() { filter2.With(T[testStruct0]()) })
+	assert.Panics(t, func() { filter2.Without(T[testStruct0]()) })
+	assert.Panics(t, func() { filter2.WithRelation(T[testRelationA](), targ) })
+	filter2.Unregister(&w)
+}
+
+func TestQuery10(t *testing.T) {
+	w := ecs.NewWorld()
+
+	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: 1, Comp: &testStruct1{2}})
+	w.Assign(e1, ecs.Component{ID: 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: 3, Comp: &testStruct3{4, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: 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: 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: 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: 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: 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: 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: 10, Comp: &testStruct10{11, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
+
+	w.Assign(e2, ecs.Component{ID: 11, Comp: &testStruct11{}})
+
+	cnt := 0
+	filter :=
+		NewFilter10[
+			testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7,
+			testStruct8, testStruct9,
+		]().
+			Optional(T[testStruct1]()).
+			With(T[testStruct10]()).
+			Without(T[testStruct11]())
+
+	filter.Register(&w)
+
+	query := filter.Query(&w)
+	for query.Next() {
+		c1, c2, c3, c4, c5, c6, c7, c8, c9, c10 := query.Get()
+		assert.Equal(t, cnt+1, int(c1.val))
+		assert.Equal(t, cnt+2, int(c2.val))
+		assert.Equal(t, cnt+3, int(c3.val))
+		assert.Equal(t, cnt+4, int(c4.val))
+		assert.Equal(t, cnt+5, int(c5.val))
+		assert.Equal(t, cnt+6, int(c6.val))
+		assert.Equal(t, cnt+7, int(c7.val))
+		assert.Equal(t, cnt+8, int(c8.val))
+		assert.Equal(t, cnt+9, int(c9.val))
+		assert.Equal(t, cnt+10, int(c10.val))
+		assert.Panics(t, func() { query.Relation() })
+		cnt++
+	}
+	assert.Equal(t, 1, cnt)
+
+	filter.Unregister(&w)
+	assert.Panics(t, func() { filter.Unregister(&w) })
+
+	targ := w.NewEntity(0)
+
+	w.Add(e0, relID)
+	w.Add(e1, relID)
+	w.Add(e2, relID)
+
+	w.Relations().Set(e0, relID, targ)
+
+	filter2 :=
+		NewFilter10[
+			testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7,
+			testStruct8, testRelationA,
+		]().
+			WithRelation(T[testRelationA](), targ)
+
+	q := filter2.Query(&w)
+	assert.Equal(t, 1, q.Count())
+	for q.Next() {
+		trg := q.Relation()
+		assert.Equal(t, targ, trg)
+	}
+
+	assert.Panics(t, func() { filter2.Query(&w, targ) })
+
+	filter2 =
+		NewFilter10[
+			testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7,
+			testStruct8, testRelationA,
+		]().
+			WithRelation(T[testRelationA]())
+	q = filter2.Query(&w)
+	assert.Equal(t, 2, q.Count())
+	q.Close()
+	q = filter2.Query(&w, targ)
+	assert.Equal(t, 1, q.Count())
+	q.Close()
+
+	filter2.Register(&w)
+	assert.Panics(t, func() { filter2.Query(&w, targ) })
+	assert.Panics(t, func() { filter2.Optional(T[testStruct0]()) })
+	assert.Panics(t, func() { filter2.With(T[testStruct0]()) })
+	assert.Panics(t, func() { filter2.Without(T[testStruct0]()) })
+	assert.Panics(t, func() { filter2.WithRelation(T[testRelationA](), targ) })
+	filter2.Unregister(&w)
+}
+
+func TestQuery11(t *testing.T) {
+	w := ecs.NewWorld()
+
+	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: 1, Comp: &testStruct1{2}})
+	w.Assign(e1, ecs.Component{ID: 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: 3, Comp: &testStruct3{4, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: 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: 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: 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: 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: 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: 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: 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: 11, Comp: &testStruct11{12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
+
+	w.Assign(e2, ecs.Component{ID: 12, Comp: &testStruct12{}})
+
+	cnt := 0
+	filter :=
+		NewFilter11[
+			testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7,
+			testStruct8, testStruct9, testStruct10,
+		]().
+			Optional(T[testStruct1]()).
+			With(T[testStruct11]()).
+			Without(T[testStruct12]())
+
+	filter.Register(&w)
+
+	query := filter.Query(&w)
+	for query.Next() {
+		c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11 := query.Get()
+		assert.Equal(t, cnt+1, int(c1.val))
+		assert.Equal(t, cnt+2, int(c2.val))
+		assert.Equal(t, cnt+3, int(c3.val))
+		assert.Equal(t, cnt+4, int(c4.val))
+		assert.Equal(t, cnt+5, int(c5.val))
+		assert.Equal(t, cnt+6, int(c6.val))
+		assert.Equal(t, cnt+7, int(c7.val))
+		assert.Equal(t, cnt+8, int(c8.val))
+		assert.Equal(t, cnt+9, int(c9.val))
+		assert.Equal(t, cnt+10, int(c10.val))
+		assert.Equal(t, cnt+11, int(c11.val))
+		assert.Panics(t, func() { query.Relation() })
+		cnt++
+	}
+	assert.Equal(t, 1, cnt)
+
+	filter.Unregister(&w)
+	assert.Panics(t, func() { filter.Unregister(&w) })
+
+	targ := w.NewEntity(0)
+
+	w.Add(e0, relID)
+	w.Add(e1, relID)
+	w.Add(e2, relID)
+
+	w.Relations().Set(e0, relID, targ)
+
+	filter2 :=
+		NewFilter11[
+			testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7,
+			testStruct8, testStruct9, testRelationA,
+		]().
+			WithRelation(T[testRelationA](), targ)
+
+	q := filter2.Query(&w)
+	assert.Equal(t, 1, q.Count())
+	for q.Next() {
+		trg := q.Relation()
+		assert.Equal(t, targ, trg)
+	}
+
+	assert.Panics(t, func() { filter2.Query(&w, targ) })
+
+	filter2 =
+		NewFilter11[
+			testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7,
+			testStruct8, testStruct9, testRelationA,
+		]().
+			WithRelation(T[testRelationA]())
+	q = filter2.Query(&w)
+	assert.Equal(t, 2, q.Count())
+	q.Close()
+	q = filter2.Query(&w, targ)
+	assert.Equal(t, 1, q.Count())
+	q.Close()
+
+	filter2.Register(&w)
+	assert.Panics(t, func() { filter2.Query(&w, targ) })
+	assert.Panics(t, func() { filter2.Optional(T[testStruct0]()) })
+	assert.Panics(t, func() { filter2.With(T[testStruct0]()) })
+	assert.Panics(t, func() { filter2.Without(T[testStruct0]()) })
+	assert.Panics(t, func() { filter2.WithRelation(T[testRelationA](), targ) })
+	filter2.Unregister(&w)
+}
+
+func TestQuery12(t *testing.T) {
+	w := ecs.NewWorld()
+
+	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: 1, Comp: &testStruct1{2}})
+	w.Assign(e1, ecs.Component{ID: 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: 3, Comp: &testStruct3{4, 0, 0}})
+	w.Assign(e1, ecs.Component{ID: 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: 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: 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: 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: 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: 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: 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: 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: 12, Comp: &testStruct12{13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
+
+	w.Assign(e2, ecs.Component{ID: 13, Comp: &testStruct13{}})
+
+	cnt := 0
+	filter :=
+		NewFilter12[
+			testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7,
+			testStruct8, testStruct9, testStruct10, testStruct11,
+		]().
+			Optional(T[testStruct1]()).
+			With(T[testStruct12]()).
+			Without(T[testStruct13]())
+
+	filter.Register(&w)
+
+	query := filter.Query(&w)
+	for query.Next() {
+		c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12 := query.Get()
+		assert.Equal(t, cnt+1, int(c1.val))
+		assert.Equal(t, cnt+2, int(c2.val))
+		assert.Equal(t, cnt+3, int(c3.val))
+		assert.Equal(t, cnt+4, int(c4.val))
+		assert.Equal(t, cnt+5, int(c5.val))
+		assert.Equal(t, cnt+6, int(c6.val))
+		assert.Equal(t, cnt+7, int(c7.val))
+		assert.Equal(t, cnt+8, int(c8.val))
+		assert.Equal(t, cnt+9, int(c9.val))
+		assert.Equal(t, cnt+10, int(c10.val))
+		assert.Equal(t, cnt+11, int(c11.val))
+		assert.Equal(t, cnt+12, int(c12.val))
+		assert.Panics(t, func() { query.Relation() })
+		cnt++
+	}
+	assert.Equal(t, 1, cnt)
+
+	filter.Unregister(&w)
+	assert.Panics(t, func() { filter.Unregister(&w) })
+
+	targ := w.NewEntity(0)
+
+	w.Add(e0, relID)
+	w.Add(e1, relID)
+	w.Add(e2, relID)
+
+	w.Relations().Set(e0, relID, targ)
+
+	filter2 :=
+		NewFilter12[
+			testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7,
+			testStruct8, testStruct9, testStruct10, testRelationA,
+		]().
+			WithRelation(T[testRelationA](), targ)
+
+	q := filter2.Query(&w)
+	assert.Equal(t, 1, q.Count())
+	for q.Next() {
+		trg := q.Relation()
+		assert.Equal(t, targ, trg)
+	}
+
+	assert.Panics(t, func() { filter2.Query(&w, targ) })
+
+	filter2 =
+		NewFilter12[
+			testStruct0, testStruct1, testStruct2, testStruct3,
+			testStruct4, testStruct5, testStruct6, testStruct7,
+			testStruct8, testStruct9, testStruct10, testRelationA,
+		]().
+			WithRelation(T[testRelationA]())
+	q = filter2.Query(&w)
+	assert.Equal(t, 2, q.Count())
+	q.Close()
+	q = filter2.Query(&w, targ)
+	assert.Equal(t, 1, q.Count())
+	q.Close()
+
+	filter2.Register(&w)
+	assert.Panics(t, func() { filter2.Query(&w, targ) })
+	assert.Panics(t, func() { filter2.Optional(T[testStruct0]()) })
+	assert.Panics(t, func() { filter2.With(T[testStruct0]()) })
+	assert.Panics(t, func() { filter2.Without(T[testStruct0]()) })
+	assert.Panics(t, func() { filter2.WithRelation(T[testRelationA](), targ) })
+	filter2.Unregister(&w)
+}
+
 func TestQueryGeneric(t *testing.T) {
 	count := 1000
 	world := ecs.NewWorld()
@@ -1019,8 +1531,12 @@ func registerAll(w *ecs.World) []ecs.ID {
 	_ = testStruct7{}
 	_ = testStruct8{}
 	_ = testStruct9{}
+	_ = testStruct10{}
+	_ = testStruct11{}
+	_ = testStruct12{}
+	_ = testStruct13{}
 
-	ids := make([]ecs.ID, 10)
+	ids := make([]ecs.ID, 14)
 	ids[0] = ecs.ComponentID[testStruct0](w)
 	ids[1] = ecs.ComponentID[testStruct1](w)
 	ids[2] = ecs.ComponentID[testStruct2](w)
@@ -1031,6 +1547,10 @@ func registerAll(w *ecs.World) []ecs.ID {
 	ids[7] = ecs.ComponentID[testStruct7](w)
 	ids[8] = ecs.ComponentID[testStruct8](w)
 	ids[9] = ecs.ComponentID[testStruct9](w)
+	ids[10] = ecs.ComponentID[testStruct10](w)
+	ids[11] = ecs.ComponentID[testStruct11](w)
+	ids[12] = ecs.ComponentID[testStruct12](w)
+	ids[13] = ecs.ComponentID[testStruct13](w)
 
 	return ids
 }
@@ -1116,3 +1636,65 @@ type testStruct9 struct {
 	val8 int32
 	val9 int32
 }
+
+//lint:ignore U1000 test type
+type testStruct10 struct {
+	val   int32
+	val2  int32
+	val3  int32
+	val4  int32
+	val5  int32
+	val6  int32
+	val7  int32
+	val8  int32
+	val9  int32
+	val10 int32
+}
+
+//lint:ignore U1000 test type
+type testStruct11 struct {
+	val   int32
+	val2  int32
+	val3  int32
+	val4  int32
+	val5  int32
+	val6  int32
+	val7  int32
+	val8  int32
+	val9  int32
+	val10 int32
+	val11 int32
+}
+
+//lint:ignore U1000 test type
+type testStruct12 struct {
+	val   int32
+	val2  int32
+	val3  int32
+	val4  int32
+	val5  int32
+	val6  int32
+	val7  int32
+	val8  int32
+	val9  int32
+	val10 int32
+	val11 int32
+	val12 int32
+}
+
+//lint:ignore U1000 test type
+type testStruct13 struct {
+	val   int32
+	val2  int32
+	val3  int32
+	val4  int32
+	val5  int32
+	val6  int32
+	val7  int32
+	val8  int32
+	val9  int32
+	val10 int32
+	val11 int32
+	val12 int32
+	val13 int32
+}