Repository: mlange-42/arche
Pull request #344 (2024-01-19 11:04:25)

Generic batch relations
* Adds `Map.SetRelationBatch()` and `Map.SetRelationBatchQ()`
* Fixes relations of generic queries returned by batch operations
* Fixes internal field names in generic API for consistency

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


* **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/65175672/badge)](https://coveralls.io/builds/65175672) |
| :-- | --: |
| Change from base [Build 7577914431](https://coveralls.io/builds/65167697): |  0.0% |
| Covered Lines: | 6143 |
| Relevant Lines: | 6143 |

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


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index db2f8b3..228c2d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,6 +34,7 @@ This change was necessary to get the same performance as before, despite the mor
 * Generic API adds `MapX.AddBatch()`, `MapX.AddBatchQ()`, `MapX.RemoveBatch()`and `MapX.RemoveBatchQ()` (#342)
 * Generic API adds optional relation target argument to most `MapX` methods (#342)
 * Generic API adds `FilterX.Filter()` to get an `ecs.Filter` from a generic one (#342)
+* Generic API adds `Map.SetRelationBatch()` and `Map.SetRelationBatchQ()` (#344)
 
 ### Performance
 
diff --git a/README.md b/README.md
index 0b8eb9e..83537f5 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@
 * Simple [core API](https://pkg.go.dev/github.com/mlange-42/arche/ecs). See the [API docs](https://pkg.go.dev/github.com/mlange-42/arche).
 * Optional logic [filter](https://pkg.go.dev/github.com/mlange-42/arche/filter) and type-safe [generic](https://pkg.go.dev/github.com/mlange-42/arche/generic) API.
 * Entity relations as first-class feature. See [Architecture](https://github.com/mlange-42/arche/blob/main/ARCHITECTURE.md#entity-relations).
-* Supports serialization and deserialization of the entire world state via [arche-serde](https://github.com/mlange-42/arche-serde).
+* World serialization and deserialization via [arche-serde](https://github.com/mlange-42/arche-serde).
 * No systems. Just queries. Use your own structure (or the [Tools](#tools)).
 * No dependencies. Except for unit tests ([100% coverage](https://coveralls.io/github/mlange-42/arche)).
 * Probably the fastest Go ECS out there. See the [Benchmarks](#benchmarks).
diff --git a/generic/compiled.go b/generic/compiled.go
index 0a13275..ad18c84 100644
--- a/generic/compiled.go
+++ b/generic/compiled.go
@@ -16,9 +16,9 @@ type compiledQuery struct {
 	cachedFilter   ecs.CachedFilter
 	filter         ecs.Filter
 	Ids            []ecs.ID
-	TargetComp     ecs.ID
+	Relation       ecs.ID
 	Target         ecs.Entity
-	HasTarget      bool
+	HasRelation    bool
 	compiled       bool
 	targetCompiled bool
 	locked         bool
@@ -42,14 +42,14 @@ func (q *compiledQuery) Compile(w *ecs.World, include, optional, exclude []Comp,
 
 	if targetType == nil {
 		q.filter = &q.maskFilter
-		q.TargetComp = ecs.ID{}
-		q.HasTarget = false
+		q.Relation = ecs.ID{}
+		q.HasRelation = false
 	} else {
 
 		targetID := ecs.TypeID(w, targetType)
 
-		q.TargetComp = targetID
-		q.HasTarget = true
+		q.Relation = targetID
+		q.HasRelation = true
 
 		if !q.maskFilter.Include.Get(targetID) {
 			panic(fmt.Sprintf("relation component %v not in filter", targetType))
@@ -64,7 +64,6 @@ func (q *compiledQuery) Compile(w *ecs.World, include, optional, exclude []Comp,
 		}
 
 		if hasTarget {
-			q.HasTarget = true
 			q.Target = target
 			q.relationFilter = ecs.NewRelationFilter(&q.maskFilter, target)
 			q.filter = &q.relationFilter
diff --git a/generic/generate/main.go b/generic/generate/main.go
index 04e4f52..ebc076e 100644
--- a/generic/generate/main.go
+++ b/generic/generate/main.go
@@ -164,7 +164,7 @@ func generateQueries() {
 			idTypes = "id" + strings.Join(numbers[:i], " ecs.ID\n\tid") + " ecs.ID"
 			for j := 0; j < i; j++ {
 				returnAll += fmt.Sprintf("(*%s)(q.Query.Get(q.id%d))", typeLetters[j], j)
-				idAssign += fmt.Sprintf("	id%d: q.compiled.Ids[%d],\n", j, j)
+				idAssign += fmt.Sprintf("	id%d: f.compiled.Ids[%d],\n", j, j)
 				if j < i-1 {
 					returnAll += ",\n"
 				}
diff --git a/generic/generate/map.go.txt b/generic/generate/map.go.txt
index e2eef10..fd9eb41 100644
--- a/generic/generate/map.go.txt
+++ b/generic/generate/map.go.txt
@@ -88,6 +88,8 @@ func (m *Map{{ .Index }}{{ .Types }}) NewBatchQ(count int, target ...ecs.Entity)
 	return Query{{ .Index }}{{ .Types }}{
 		Query: query,
 		{{ .IDAssign2 }}
+		hasRelation: m.hasRelation,
+		relation: m.relation,
 	}
 }
 
@@ -158,6 +160,8 @@ func (m *Map{{ .Index }}{{ .Types }}) AddBatchQ(filter ecs.Filter, target ...ecs
 	return Query{{ .Index }}{{ .Types }}{
 		Query: query,
 		{{ .IDAssign2 }}
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -221,6 +225,8 @@ func (m *Map{{ .Index }}{{ .Types }}) RemoveBatchQ(filter ecs.Filter, target ...
 	}
 	return Query0{
 		Query: query,
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
diff --git a/generic/generate/query.go.txt b/generic/generate/query.go.txt
index b778a6a..348d310 100644
--- a/generic/generate/query.go.txt
+++ b/generic/generate/query.go.txt
@@ -33,38 +33,38 @@ func NewFilter{{ .Index }}{{ .TypesFull }}() *Filter{{ .Index }}{{ .Types }} {
 // Create the required mask items with [T].
 //
 // Only affects component types that were specified in the query.
-func (q *Filter{{ .Index }}{{ .Types }}) Optional(mask ...Comp) *Filter{{ .Index }}{{ .Types }} {
-	if q.compiled.locked {
+func (f *Filter{{ .Index }}{{ .Types }}) Optional(mask ...Comp) *Filter{{ .Index }}{{ .Types }} {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.optional = append(q.optional, mask...)
-	q.compiled.Reset()
-	return q
+	f.optional = append(f.optional, mask...)
+	f.compiled.Reset()
+	return f
 }
 {{ end }}
 
 // With adds components that are required, but not accessible via [Query{{ .Index }}.Get].
 //
 // Create the required mask items with [T].
-func (q *Filter{{ .Index }}{{ .Types }}) With(mask ...Comp) *Filter{{ .Index }}{{ .Types }} {
-	if q.compiled.locked {
+func (f *Filter{{ .Index }}{{ .Types }}) With(mask ...Comp) *Filter{{ .Index }}{{ .Types }} {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.include = append(q.include, mask...)
-	q.compiled.Reset()
-	return q
+	f.include = append(f.include, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // Without excludes entities with any of the given components from the query.
 //
 // Create the required mask items with [T].
-func (q *Filter{{ .Index }}{{ .Types }}) Without(mask ...Comp) *Filter{{ .Index }}{{ .Types }} {
-	if q.compiled.locked {
+func (f *Filter{{ .Index }}{{ .Types }}) Without(mask ...Comp) *Filter{{ .Index }}{{ .Types }} {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.exclude = append(q.exclude, mask...)
-	q.compiled.Reset()
-	return q
+	f.exclude = append(f.exclude, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // WithRelation sets the filter's [ecs.Relation] component and optionally
@@ -74,17 +74,17 @@ func (q *Filter{{ .Index }}{{ .Types }}) Without(mask ...Comp) *Filter{{ .Index
 // If the optional argument is provided, the filter's relation target is set permanently.
 //
 // Create the required component ID with [T].
-func (q *Filter{{ .Index }}{{ .Types }}) WithRelation(comp Comp, target ...ecs.Entity) *Filter{{ .Index }}{{ .Types }} {
-	if q.compiled.locked {
+func (f *Filter{{ .Index }}{{ .Types }}) WithRelation(comp Comp, target ...ecs.Entity) *Filter{{ .Index }}{{ .Types }} {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.targetType = comp
+	f.targetType = comp
 	if len(target) > 0 {
-		q.target = target[0]
-		q.hasTarget = true
+		f.target = target[0]
+		f.hasTarget = true
 	}
-	q.compiled.Reset()
-	return q
+	f.compiled.Reset()
+	return f
 }
 
 // Filter builds an [ecs.Filter], with an optional relation target.
@@ -95,20 +95,20 @@ func (q *Filter{{ .Index }}{{ .Types }}) WithRelation(comp Comp, target ...ecs.E
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter{{ .Index }}{{ .Types }}) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+func (f *Filter{{ .Index }}{{ .Types }}) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
 	
-	filter := q.compiled.filter
+	filter := f.compiled.filter
 	if len(target) > 0 {
-		if q.compiled.locked {
+		if f.compiled.locked {
 			panic("can't change relation target on a cached query")
 		}
-		if q.hasTarget {
+		if f.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
+		f.compiled.relationFilter.Filter = &f.compiled.maskFilter
+		f.compiled.relationFilter.Target = target[0]
+		filter = &f.compiled.relationFilter
 	}
 
 	return filter
@@ -122,12 +122,12 @@ func (q *Filter{{ .Index }}{{ .Types }}) Filter(w *ecs.World, target ...ecs.Enti
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter{{ .Index }}{{ .Types }}) Query(w *ecs.World, target ...ecs.Entity) Query{{ .Index }}{{ .Types }} {
-	filter := q.Filter(w, target...)
+func (f *Filter{{ .Index }}{{ .Types }}) Query(w *ecs.World, target ...ecs.Entity) Query{{ .Index }}{{ .Types }} {
+	filter := f.Filter(w, target...)
 	return Query{{ .Index }}{{ .Types }}{
 		Query: w.Query(filter),
-		target: q.compiled.TargetComp,
-		hasTarget: q.compiled.HasTarget,
+		relation: f.compiled.Relation,
+		hasRelation: f.compiled.HasRelation,
 		{{ .IDAssign }}
 	}
 }
@@ -135,16 +135,16 @@ func (q *Filter{{ .Index }}{{ .Types }}) Query(w *ecs.World, target ...ecs.Entit
 // Register the filter for caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter{{ .Index }}{{ .Types }}) Register(w *ecs.World) {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
-	q.compiled.Register(w)
+func (f *Filter{{ .Index }}{{ .Types }}) Register(w *ecs.World) {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
+	f.compiled.Register(w)
 }
 
 // Unregister the filter from caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter{{ .Index }}{{ .Types }}) Unregister(w *ecs.World) {
-	q.compiled.Unregister(w)
+func (f *Filter{{ .Index }}{{ .Types }}) Unregister(w *ecs.World) {
+	f.compiled.Unregister(w)
 }
 
 // Query{{ .Index }} is a generic query iterator for {{ .NumberStr }} components.
@@ -166,8 +166,8 @@ func (q *Filter{{ .Index }}{{ .Types }}) Unregister(w *ecs.World) {
 type Query{{ .Index }}{{ .TypesFull }} struct {
 	ecs.Query
 	{{ .IDTypes }}
-	target ecs.ID
-	hasTarget bool
+	relation ecs.ID
+	hasRelation bool
 }
 
 {{if .ReturnAll}}
@@ -185,8 +185,8 @@ func (q *Query{{ .Index }}{{ .Types }}) Get() ({{ .TypesReturn }}) {
 // Panics if the underlying [Filter{{ .Index }}] was not prepared for relations
 // using [Filter{{ .Index }}.WithRelation].
 func (q *Query{{ .Index }}{{ .Types }}) Relation() ecs.Entity {
-	if !q.hasTarget {
+	if !q.hasRelation {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(q.target)
+	return q.Query.Relation(q.relation)
 }
diff --git a/generic/map.go b/generic/map.go
index 0f4060b..6c3a54d 100644
--- a/generic/map.go
+++ b/generic/map.go
@@ -23,16 +23,16 @@ func NewMap[T any](w *ecs.World) Map[T] {
 }
 
 // ID returns the component ID for this Map.
-func (g *Map[T]) ID() ecs.ID {
-	return g.id
+func (m *Map[T]) ID() ecs.ID {
+	return m.id
 }
 
 // Get gets the component for the given entity.
 //
 // See [Map.HasUnchecked] for an optimized version for static entities.
 // See also [ecs.World.Get].
-func (g *Map[T]) Get(entity ecs.Entity) *T {
-	return (*T)(g.world.Get(entity, g.id))
+func (m *Map[T]) Get(entity ecs.Entity) *T {
+	return (*T)(m.world.Get(entity, m.id))
 }
 
 // GetUnchecked gets the component for the given entity.
@@ -41,16 +41,16 @@ func (g *Map[T]) Get(entity ecs.Entity) *T {
 // for cases where entities are static or checked with [ecs.World.Alive] in user code.
 //
 // See also [ecs.World.GetUnchecked].
-func (g *Map[T]) GetUnchecked(entity ecs.Entity) *T {
-	return (*T)(g.world.GetUnchecked(entity, g.id))
+func (m *Map[T]) GetUnchecked(entity ecs.Entity) *T {
+	return (*T)(m.world.GetUnchecked(entity, m.id))
 }
 
 // Has returns whether the entity has the component.
 //
 // See [Map.HasUnchecked] for an optimized version for static entities.
 // See also [ecs.World.Has].
-func (g *Map[T]) Has(entity ecs.Entity) bool {
-	return g.world.Has(entity, g.id)
+func (m *Map[T]) Has(entity ecs.Entity) bool {
+	return m.world.Has(entity, m.id)
 }
 
 // HasUnchecked returns whether the entity has the component.
@@ -59,8 +59,8 @@ func (g *Map[T]) Has(entity ecs.Entity) bool {
 // for cases where entities are static or checked with [ecs.World.Alive] in user code.
 //
 // See also [ecs.World.HasUnchecked].
-func (g *Map[T]) HasUnchecked(entity ecs.Entity) bool {
-	return g.world.HasUnchecked(entity, g.id)
+func (m *Map[T]) HasUnchecked(entity ecs.Entity) bool {
+	return m.world.HasUnchecked(entity, m.id)
 }
 
 // Set overwrites the component for the given entity.
@@ -68,8 +68,8 @@ func (g *Map[T]) HasUnchecked(entity ecs.Entity) bool {
 // Panics if the entity does not have a component of that type.
 //
 // See also [ecs.World.Set].
-func (g *Map[T]) Set(entity ecs.Entity, comp *T) *T {
-	return (*T)(g.world.Set(entity, g.id, comp))
+func (m *Map[T]) Set(entity ecs.Entity, comp *T) *T {
+	return (*T)(m.world.Set(entity, m.id, comp))
 }
 
 // GetRelation returns the target entity for the given entity and the Map's relation component.
@@ -80,8 +80,8 @@ func (g *Map[T]) Set(entity ecs.Entity, comp *T) *T {
 //   - if the entity has been removed/recycled.
 //
 // See also [ecs.World.GetRelation].
-func (g *Map[T]) GetRelation(entity ecs.Entity) ecs.Entity {
-	return g.world.Relations().Get(entity, g.id)
+func (m *Map[T]) GetRelation(entity ecs.Entity) ecs.Entity {
+	return m.world.Relations().Get(entity, m.id)
 }
 
 // GetRelation returns the target entity for the given entity and the Map's relation component.
@@ -93,16 +93,43 @@ func (g *Map[T]) GetRelation(entity ecs.Entity) ecs.Entity {
 // Does not check if the entity is alive or that the component ID is applicable.
 //
 // See also [ecs.World.GetRelationUnchecked].
-func (g *Map[T]) GetRelationUnchecked(entity ecs.Entity) ecs.Entity {
-	return g.world.Relations().GetUnchecked(entity, g.id)
+func (m *Map[T]) GetRelationUnchecked(entity ecs.Entity) ecs.Entity {
+	return m.world.Relations().GetUnchecked(entity, m.id)
 }
 
-// SetRelation sets the target entity for the given entity and the Map's relation component.
+// SetRelation sets the target entity for the given entity and the Map's component.
 //
 // Panics if the entity does not have a component of that type.
 // Panics if the component is not a relation.
 //
 // See also [ecs.World.SetRelation].
-func (g *Map[T]) SetRelation(entity, target ecs.Entity) {
-	g.world.Relations().Set(entity, g.id, target)
+func (m *Map[T]) SetRelation(entity, target ecs.Entity) {
+	m.world.Relations().Set(entity, m.id, target)
+}
+
+// SetRelationBatch sets the target entity for many entities and the Map's component.
+//
+// Panics if the entity does not have a component of that type.
+// Panics if the component is not a relation.
+//
+// See also [ecs.Batch.SetRelation].
+func (m *Map[T]) SetRelationBatch(filter ecs.Filter, target ecs.Entity) {
+	m.world.Batch().SetRelation(filter, m.id, target)
+}
+
+// SetRelationBatch sets the target entity for many entities and the Map's component,
+// and returns a query over them.
+//
+// Panics if the entity does not have a component of that type.
+// Panics if the component is not a relation.
+//
+// See also [ecs.Batch.SetRelation].
+func (m *Map[T]) SetRelationBatchQ(filter ecs.Filter, target ecs.Entity) Query1[T] {
+	query := m.world.Batch().SetRelationQ(filter, m.id, target)
+	return Query1[T]{
+		Query:       query,
+		id0:         m.id,
+		hasRelation: true,
+		relation:    m.id,
+	}
 }
diff --git a/generic/map_generated.go b/generic/map_generated.go
index ea42912..8f2c758 100644
--- a/generic/map_generated.go
+++ b/generic/map_generated.go
@@ -95,6 +95,9 @@ func (m *Map1[A]) NewBatchQ(count int, target ...ecs.Entity) Query1[A] {
 	return Query1[A]{
 		Query: query,
 		id0:   m.id0,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -164,6 +167,9 @@ func (m *Map1[A]) AddBatchQ(filter ecs.Filter, target ...ecs.Entity) Query1[A] {
 	return Query1[A]{
 		Query: query,
 		id0:   m.id0,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -225,7 +231,9 @@ func (m *Map1[A]) RemoveBatchQ(filter ecs.Filter, target ...ecs.Entity) Query0 {
 		query = m.world.Batch().ExchangeQ(filter, nil, m.ids)
 	}
 	return Query0{
-		Query: query,
+		Query:       query,
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -339,6 +347,9 @@ func (m *Map2[A, B]) NewBatchQ(count int, target ...ecs.Entity) Query2[A, B] {
 		Query: query,
 		id0:   m.id0,
 		id1:   m.id1,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -413,6 +424,9 @@ func (m *Map2[A, B]) AddBatchQ(filter ecs.Filter, target ...ecs.Entity) Query2[A
 		Query: query,
 		id0:   m.id0,
 		id1:   m.id1,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -475,7 +489,9 @@ func (m *Map2[A, B]) RemoveBatchQ(filter ecs.Filter, target ...ecs.Entity) Query
 		query = m.world.Batch().ExchangeQ(filter, nil, m.ids)
 	}
 	return Query0{
-		Query: query,
+		Query:       query,
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -594,6 +610,9 @@ func (m *Map3[A, B, C]) NewBatchQ(count int, target ...ecs.Entity) Query3[A, B,
 		id0:   m.id0,
 		id1:   m.id1,
 		id2:   m.id2,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -671,6 +690,9 @@ func (m *Map3[A, B, C]) AddBatchQ(filter ecs.Filter, target ...ecs.Entity) Query
 		id0:   m.id0,
 		id1:   m.id1,
 		id2:   m.id2,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -734,7 +756,9 @@ func (m *Map3[A, B, C]) RemoveBatchQ(filter ecs.Filter, target ...ecs.Entity) Qu
 		query = m.world.Batch().ExchangeQ(filter, nil, m.ids)
 	}
 	return Query0{
-		Query: query,
+		Query:       query,
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -858,6 +882,9 @@ func (m *Map4[A, B, C, D]) NewBatchQ(count int, target ...ecs.Entity) Query4[A,
 		id1:   m.id1,
 		id2:   m.id2,
 		id3:   m.id3,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -938,6 +965,9 @@ func (m *Map4[A, B, C, D]) AddBatchQ(filter ecs.Filter, target ...ecs.Entity) Qu
 		id1:   m.id1,
 		id2:   m.id2,
 		id3:   m.id3,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -1002,7 +1032,9 @@ func (m *Map4[A, B, C, D]) RemoveBatchQ(filter ecs.Filter, target ...ecs.Entity)
 		query = m.world.Batch().ExchangeQ(filter, nil, m.ids)
 	}
 	return Query0{
-		Query: query,
+		Query:       query,
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -1131,6 +1163,9 @@ func (m *Map5[A, B, C, D, E]) NewBatchQ(count int, target ...ecs.Entity) Query5[
 		id2:   m.id2,
 		id3:   m.id3,
 		id4:   m.id4,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -1214,6 +1249,9 @@ func (m *Map5[A, B, C, D, E]) AddBatchQ(filter ecs.Filter, target ...ecs.Entity)
 		id2:   m.id2,
 		id3:   m.id3,
 		id4:   m.id4,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -1279,7 +1317,9 @@ func (m *Map5[A, B, C, D, E]) RemoveBatchQ(filter ecs.Filter, target ...ecs.Enti
 		query = m.world.Batch().ExchangeQ(filter, nil, m.ids)
 	}
 	return Query0{
-		Query: query,
+		Query:       query,
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -1413,6 +1453,9 @@ func (m *Map6[A, B, C, D, E, F]) NewBatchQ(count int, target ...ecs.Entity) Quer
 		id3:   m.id3,
 		id4:   m.id4,
 		id5:   m.id5,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -1499,6 +1542,9 @@ func (m *Map6[A, B, C, D, E, F]) AddBatchQ(filter ecs.Filter, target ...ecs.Enti
 		id3:   m.id3,
 		id4:   m.id4,
 		id5:   m.id5,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -1565,7 +1611,9 @@ func (m *Map6[A, B, C, D, E, F]) RemoveBatchQ(filter ecs.Filter, target ...ecs.E
 		query = m.world.Batch().ExchangeQ(filter, nil, m.ids)
 	}
 	return Query0{
-		Query: query,
+		Query:       query,
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -1704,6 +1752,9 @@ func (m *Map7[A, B, C, D, E, F, G]) NewBatchQ(count int, target ...ecs.Entity) Q
 		id4:   m.id4,
 		id5:   m.id5,
 		id6:   m.id6,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -1793,6 +1844,9 @@ func (m *Map7[A, B, C, D, E, F, G]) AddBatchQ(filter ecs.Filter, target ...ecs.E
 		id4:   m.id4,
 		id5:   m.id5,
 		id6:   m.id6,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -1860,7 +1914,9 @@ func (m *Map7[A, B, C, D, E, F, G]) RemoveBatchQ(filter ecs.Filter, target ...ec
 		query = m.world.Batch().ExchangeQ(filter, nil, m.ids)
 	}
 	return Query0{
-		Query: query,
+		Query:       query,
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -2004,6 +2060,9 @@ func (m *Map8[A, B, C, D, E, F, G, H]) NewBatchQ(count int, target ...ecs.Entity
 		id5:   m.id5,
 		id6:   m.id6,
 		id7:   m.id7,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -2096,6 +2155,9 @@ func (m *Map8[A, B, C, D, E, F, G, H]) AddBatchQ(filter ecs.Filter, target ...ec
 		id5:   m.id5,
 		id6:   m.id6,
 		id7:   m.id7,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -2164,7 +2226,9 @@ func (m *Map8[A, B, C, D, E, F, G, H]) RemoveBatchQ(filter ecs.Filter, target ..
 		query = m.world.Batch().ExchangeQ(filter, nil, m.ids)
 	}
 	return Query0{
-		Query: query,
+		Query:       query,
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -2313,6 +2377,9 @@ func (m *Map9[A, B, C, D, E, F, G, H, I]) NewBatchQ(count int, target ...ecs.Ent
 		id6:   m.id6,
 		id7:   m.id7,
 		id8:   m.id8,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -2408,6 +2475,9 @@ func (m *Map9[A, B, C, D, E, F, G, H, I]) AddBatchQ(filter ecs.Filter, target ..
 		id6:   m.id6,
 		id7:   m.id7,
 		id8:   m.id8,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -2477,7 +2547,9 @@ func (m *Map9[A, B, C, D, E, F, G, H, I]) RemoveBatchQ(filter ecs.Filter, target
 		query = m.world.Batch().ExchangeQ(filter, nil, m.ids)
 	}
 	return Query0{
-		Query: query,
+		Query:       query,
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -2631,6 +2703,9 @@ func (m *Map10[A, B, C, D, E, F, G, H, I, J]) NewBatchQ(count int, target ...ecs
 		id7:   m.id7,
 		id8:   m.id8,
 		id9:   m.id9,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -2729,6 +2804,9 @@ func (m *Map10[A, B, C, D, E, F, G, H, I, J]) AddBatchQ(filter ecs.Filter, targe
 		id7:   m.id7,
 		id8:   m.id8,
 		id9:   m.id9,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -2799,7 +2877,9 @@ func (m *Map10[A, B, C, D, E, F, G, H, I, J]) RemoveBatchQ(filter ecs.Filter, ta
 		query = m.world.Batch().ExchangeQ(filter, nil, m.ids)
 	}
 	return Query0{
-		Query: query,
+		Query:       query,
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -2958,6 +3038,9 @@ func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) NewBatchQ(count int, target ...
 		id8:   m.id8,
 		id9:   m.id9,
 		id10:  m.id10,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -3059,6 +3142,9 @@ func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) AddBatchQ(filter ecs.Filter, ta
 		id8:   m.id8,
 		id9:   m.id9,
 		id10:  m.id10,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -3130,7 +3216,9 @@ func (m *Map11[A, B, C, D, E, F, G, H, I, J, K]) RemoveBatchQ(filter ecs.Filter,
 		query = m.world.Batch().ExchangeQ(filter, nil, m.ids)
 	}
 	return Query0{
-		Query: query,
+		Query:       query,
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -3294,6 +3382,9 @@ func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) NewBatchQ(count int, target
 		id9:   m.id9,
 		id10:  m.id10,
 		id11:  m.id11,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -3398,6 +3489,9 @@ func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) AddBatchQ(filter ecs.Filter,
 		id9:   m.id9,
 		id10:  m.id10,
 		id11:  m.id11,
+
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
@@ -3470,7 +3564,9 @@ func (m *Map12[A, B, C, D, E, F, G, H, I, J, K, L]) RemoveBatchQ(filter ecs.Filt
 		query = m.world.Batch().ExchangeQ(filter, nil, m.ids)
 	}
 	return Query0{
-		Query: query,
+		Query:       query,
+		hasRelation: m.hasRelation,
+		relation:    m.relation,
 	}
 }
 
diff --git a/generic/query_generated.go b/generic/query_generated.go
index 02100d5..8ee1989 100644
--- a/generic/query_generated.go
+++ b/generic/query_generated.go
@@ -34,25 +34,25 @@ func NewFilter0() *Filter0 {
 // With adds components that are required, but not accessible via [Query0.Get].
 //
 // Create the required mask items with [T].
-func (q *Filter0) With(mask ...Comp) *Filter0 {
-	if q.compiled.locked {
+func (f *Filter0) With(mask ...Comp) *Filter0 {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.include = append(q.include, mask...)
-	q.compiled.Reset()
-	return q
+	f.include = append(f.include, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // Without excludes entities with any of the given components from the query.
 //
 // Create the required mask items with [T].
-func (q *Filter0) Without(mask ...Comp) *Filter0 {
-	if q.compiled.locked {
+func (f *Filter0) Without(mask ...Comp) *Filter0 {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.exclude = append(q.exclude, mask...)
-	q.compiled.Reset()
-	return q
+	f.exclude = append(f.exclude, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // WithRelation sets the filter's [ecs.Relation] component and optionally
@@ -62,17 +62,17 @@ func (q *Filter0) Without(mask ...Comp) *Filter0 {
 // If the optional argument is provided, the filter's relation target is set permanently.
 //
 // Create the required component ID with [T].
-func (q *Filter0) WithRelation(comp Comp, target ...ecs.Entity) *Filter0 {
-	if q.compiled.locked {
+func (f *Filter0) WithRelation(comp Comp, target ...ecs.Entity) *Filter0 {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.targetType = comp
+	f.targetType = comp
 	if len(target) > 0 {
-		q.target = target[0]
-		q.hasTarget = true
+		f.target = target[0]
+		f.hasTarget = true
 	}
-	q.compiled.Reset()
-	return q
+	f.compiled.Reset()
+	return f
 }
 
 // Filter builds an [ecs.Filter], with an optional relation target.
@@ -83,20 +83,20 @@ func (q *Filter0) WithRelation(comp Comp, target ...ecs.Entity) *Filter0 {
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter0) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+func (f *Filter0) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
 
-	filter := q.compiled.filter
+	filter := f.compiled.filter
 	if len(target) > 0 {
-		if q.compiled.locked {
+		if f.compiled.locked {
 			panic("can't change relation target on a cached query")
 		}
-		if q.hasTarget {
+		if f.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
+		f.compiled.relationFilter.Filter = &f.compiled.maskFilter
+		f.compiled.relationFilter.Target = target[0]
+		filter = &f.compiled.relationFilter
 	}
 
 	return filter
@@ -110,28 +110,28 @@ func (q *Filter0) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter0) Query(w *ecs.World, target ...ecs.Entity) Query0 {
-	filter := q.Filter(w, target...)
+func (f *Filter0) Query(w *ecs.World, target ...ecs.Entity) Query0 {
+	filter := f.Filter(w, target...)
 	return Query0{
-		Query:     w.Query(filter),
-		target:    q.compiled.TargetComp,
-		hasTarget: q.compiled.HasTarget,
+		Query:       w.Query(filter),
+		relation:    f.compiled.Relation,
+		hasRelation: f.compiled.HasRelation,
 	}
 }
 
 // Register the filter for caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter0) Register(w *ecs.World) {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
-	q.compiled.Register(w)
+func (f *Filter0) Register(w *ecs.World) {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
+	f.compiled.Register(w)
 }
 
 // Unregister the filter from caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter0) Unregister(w *ecs.World) {
-	q.compiled.Unregister(w)
+func (f *Filter0) Unregister(w *ecs.World) {
+	f.compiled.Unregister(w)
 }
 
 // Query0 is a generic query iterator for zero components.
@@ -152,8 +152,8 @@ func (q *Filter0) Unregister(w *ecs.World) {
 type Query0 struct {
 	ecs.Query
 
-	target    ecs.ID
-	hasTarget bool
+	relation    ecs.ID
+	hasRelation bool
 }
 
 // Relation returns the target entity for the query's relation.
@@ -162,10 +162,10 @@ type Query0 struct {
 // Panics if the underlying [Filter0] was not prepared for relations
 // using [Filter0.WithRelation].
 func (q *Query0) Relation() ecs.Entity {
-	if !q.hasTarget {
+	if !q.hasRelation {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(q.target)
+	return q.Query.Relation(q.relation)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -201,37 +201,37 @@ func NewFilter1[A any]() *Filter1[A] {
 // Create the required mask items with [T].
 //
 // Only affects component types that were specified in the query.
-func (q *Filter1[A]) Optional(mask ...Comp) *Filter1[A] {
-	if q.compiled.locked {
+func (f *Filter1[A]) Optional(mask ...Comp) *Filter1[A] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.optional = append(q.optional, mask...)
-	q.compiled.Reset()
-	return q
+	f.optional = append(f.optional, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // With adds components that are required, but not accessible via [Query1.Get].
 //
 // Create the required mask items with [T].
-func (q *Filter1[A]) With(mask ...Comp) *Filter1[A] {
-	if q.compiled.locked {
+func (f *Filter1[A]) With(mask ...Comp) *Filter1[A] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.include = append(q.include, mask...)
-	q.compiled.Reset()
-	return q
+	f.include = append(f.include, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // Without excludes entities with any of the given components from the query.
 //
 // Create the required mask items with [T].
-func (q *Filter1[A]) Without(mask ...Comp) *Filter1[A] {
-	if q.compiled.locked {
+func (f *Filter1[A]) Without(mask ...Comp) *Filter1[A] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.exclude = append(q.exclude, mask...)
-	q.compiled.Reset()
-	return q
+	f.exclude = append(f.exclude, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // WithRelation sets the filter's [ecs.Relation] component and optionally
@@ -241,17 +241,17 @@ func (q *Filter1[A]) Without(mask ...Comp) *Filter1[A] {
 // If the optional argument is provided, the filter's relation target is set permanently.
 //
 // Create the required component ID with [T].
-func (q *Filter1[A]) WithRelation(comp Comp, target ...ecs.Entity) *Filter1[A] {
-	if q.compiled.locked {
+func (f *Filter1[A]) WithRelation(comp Comp, target ...ecs.Entity) *Filter1[A] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.targetType = comp
+	f.targetType = comp
 	if len(target) > 0 {
-		q.target = target[0]
-		q.hasTarget = true
+		f.target = target[0]
+		f.hasTarget = true
 	}
-	q.compiled.Reset()
-	return q
+	f.compiled.Reset()
+	return f
 }
 
 // Filter builds an [ecs.Filter], with an optional relation target.
@@ -262,20 +262,20 @@ func (q *Filter1[A]) WithRelation(comp Comp, target ...ecs.Entity) *Filter1[A] {
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter1[A]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+func (f *Filter1[A]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
 
-	filter := q.compiled.filter
+	filter := f.compiled.filter
 	if len(target) > 0 {
-		if q.compiled.locked {
+		if f.compiled.locked {
 			panic("can't change relation target on a cached query")
 		}
-		if q.hasTarget {
+		if f.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
+		f.compiled.relationFilter.Filter = &f.compiled.maskFilter
+		f.compiled.relationFilter.Target = target[0]
+		filter = &f.compiled.relationFilter
 	}
 
 	return filter
@@ -289,29 +289,29 @@ func (q *Filter1[A]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter1[A]) Query(w *ecs.World, target ...ecs.Entity) Query1[A] {
-	filter := q.Filter(w, target...)
+func (f *Filter1[A]) Query(w *ecs.World, target ...ecs.Entity) Query1[A] {
+	filter := f.Filter(w, target...)
 	return Query1[A]{
-		Query:     w.Query(filter),
-		target:    q.compiled.TargetComp,
-		hasTarget: q.compiled.HasTarget,
-		id0:       q.compiled.Ids[0],
+		Query:       w.Query(filter),
+		relation:    f.compiled.Relation,
+		hasRelation: f.compiled.HasRelation,
+		id0:         f.compiled.Ids[0],
 	}
 }
 
 // Register the filter for caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter1[A]) Register(w *ecs.World) {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
-	q.compiled.Register(w)
+func (f *Filter1[A]) Register(w *ecs.World) {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
+	f.compiled.Register(w)
 }
 
 // Unregister the filter from caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter1[A]) Unregister(w *ecs.World) {
-	q.compiled.Unregister(w)
+func (f *Filter1[A]) Unregister(w *ecs.World) {
+	f.compiled.Unregister(w)
 }
 
 // Query1 is a generic query iterator for one components.
@@ -332,9 +332,9 @@ func (q *Filter1[A]) Unregister(w *ecs.World) {
 //	}
 type Query1[A any] struct {
 	ecs.Query
-	id0       ecs.ID
-	target    ecs.ID
-	hasTarget bool
+	id0         ecs.ID
+	relation    ecs.ID
+	hasRelation bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -350,10 +350,10 @@ func (q *Query1[A]) Get() *A {
 // Panics if the underlying [Filter1] was not prepared for relations
 // using [Filter1.WithRelation].
 func (q *Query1[A]) Relation() ecs.Entity {
-	if !q.hasTarget {
+	if !q.hasRelation {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(q.target)
+	return q.Query.Relation(q.relation)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -390,37 +390,37 @@ func NewFilter2[A any, B any]() *Filter2[A, B] {
 // Create the required mask items with [T].
 //
 // Only affects component types that were specified in the query.
-func (q *Filter2[A, B]) Optional(mask ...Comp) *Filter2[A, B] {
-	if q.compiled.locked {
+func (f *Filter2[A, B]) Optional(mask ...Comp) *Filter2[A, B] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.optional = append(q.optional, mask...)
-	q.compiled.Reset()
-	return q
+	f.optional = append(f.optional, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // With adds components that are required, but not accessible via [Query2.Get].
 //
 // Create the required mask items with [T].
-func (q *Filter2[A, B]) With(mask ...Comp) *Filter2[A, B] {
-	if q.compiled.locked {
+func (f *Filter2[A, B]) With(mask ...Comp) *Filter2[A, B] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.include = append(q.include, mask...)
-	q.compiled.Reset()
-	return q
+	f.include = append(f.include, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // Without excludes entities with any of the given components from the query.
 //
 // Create the required mask items with [T].
-func (q *Filter2[A, B]) Without(mask ...Comp) *Filter2[A, B] {
-	if q.compiled.locked {
+func (f *Filter2[A, B]) Without(mask ...Comp) *Filter2[A, B] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.exclude = append(q.exclude, mask...)
-	q.compiled.Reset()
-	return q
+	f.exclude = append(f.exclude, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // WithRelation sets the filter's [ecs.Relation] component and optionally
@@ -430,17 +430,17 @@ func (q *Filter2[A, B]) Without(mask ...Comp) *Filter2[A, B] {
 // If the optional argument is provided, the filter's relation target is set permanently.
 //
 // Create the required component ID with [T].
-func (q *Filter2[A, B]) WithRelation(comp Comp, target ...ecs.Entity) *Filter2[A, B] {
-	if q.compiled.locked {
+func (f *Filter2[A, B]) WithRelation(comp Comp, target ...ecs.Entity) *Filter2[A, B] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.targetType = comp
+	f.targetType = comp
 	if len(target) > 0 {
-		q.target = target[0]
-		q.hasTarget = true
+		f.target = target[0]
+		f.hasTarget = true
 	}
-	q.compiled.Reset()
-	return q
+	f.compiled.Reset()
+	return f
 }
 
 // Filter builds an [ecs.Filter], with an optional relation target.
@@ -451,20 +451,20 @@ func (q *Filter2[A, B]) WithRelation(comp Comp, target ...ecs.Entity) *Filter2[A
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter2[A, B]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+func (f *Filter2[A, B]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
 
-	filter := q.compiled.filter
+	filter := f.compiled.filter
 	if len(target) > 0 {
-		if q.compiled.locked {
+		if f.compiled.locked {
 			panic("can't change relation target on a cached query")
 		}
-		if q.hasTarget {
+		if f.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
+		f.compiled.relationFilter.Filter = &f.compiled.maskFilter
+		f.compiled.relationFilter.Target = target[0]
+		filter = &f.compiled.relationFilter
 	}
 
 	return filter
@@ -478,30 +478,30 @@ func (q *Filter2[A, B]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter2[A, B]) Query(w *ecs.World, target ...ecs.Entity) Query2[A, B] {
-	filter := q.Filter(w, target...)
+func (f *Filter2[A, B]) Query(w *ecs.World, target ...ecs.Entity) Query2[A, B] {
+	filter := f.Filter(w, target...)
 	return Query2[A, B]{
-		Query:     w.Query(filter),
-		target:    q.compiled.TargetComp,
-		hasTarget: q.compiled.HasTarget,
-		id0:       q.compiled.Ids[0],
-		id1:       q.compiled.Ids[1],
+		Query:       w.Query(filter),
+		relation:    f.compiled.Relation,
+		hasRelation: f.compiled.HasRelation,
+		id0:         f.compiled.Ids[0],
+		id1:         f.compiled.Ids[1],
 	}
 }
 
 // Register the filter for caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter2[A, B]) Register(w *ecs.World) {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
-	q.compiled.Register(w)
+func (f *Filter2[A, B]) Register(w *ecs.World) {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
+	f.compiled.Register(w)
 }
 
 // Unregister the filter from caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter2[A, B]) Unregister(w *ecs.World) {
-	q.compiled.Unregister(w)
+func (f *Filter2[A, B]) Unregister(w *ecs.World) {
+	f.compiled.Unregister(w)
 }
 
 // Query2 is a generic query iterator for two components.
@@ -522,10 +522,10 @@ func (q *Filter2[A, B]) Unregister(w *ecs.World) {
 //	}
 type Query2[A any, B any] struct {
 	ecs.Query
-	id0       ecs.ID
-	id1       ecs.ID
-	target    ecs.ID
-	hasTarget bool
+	id0         ecs.ID
+	id1         ecs.ID
+	relation    ecs.ID
+	hasRelation bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -542,10 +542,10 @@ func (q *Query2[A, B]) Get() (*A, *B) {
 // Panics if the underlying [Filter2] was not prepared for relations
 // using [Filter2.WithRelation].
 func (q *Query2[A, B]) Relation() ecs.Entity {
-	if !q.hasTarget {
+	if !q.hasRelation {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(q.target)
+	return q.Query.Relation(q.relation)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -583,37 +583,37 @@ func NewFilter3[A any, B any, C any]() *Filter3[A, B, C] {
 // Create the required mask items with [T].
 //
 // Only affects component types that were specified in the query.
-func (q *Filter3[A, B, C]) Optional(mask ...Comp) *Filter3[A, B, C] {
-	if q.compiled.locked {
+func (f *Filter3[A, B, C]) Optional(mask ...Comp) *Filter3[A, B, C] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.optional = append(q.optional, mask...)
-	q.compiled.Reset()
-	return q
+	f.optional = append(f.optional, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // With adds components that are required, but not accessible via [Query3.Get].
 //
 // Create the required mask items with [T].
-func (q *Filter3[A, B, C]) With(mask ...Comp) *Filter3[A, B, C] {
-	if q.compiled.locked {
+func (f *Filter3[A, B, C]) With(mask ...Comp) *Filter3[A, B, C] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.include = append(q.include, mask...)
-	q.compiled.Reset()
-	return q
+	f.include = append(f.include, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // Without excludes entities with any of the given components from the query.
 //
 // Create the required mask items with [T].
-func (q *Filter3[A, B, C]) Without(mask ...Comp) *Filter3[A, B, C] {
-	if q.compiled.locked {
+func (f *Filter3[A, B, C]) Without(mask ...Comp) *Filter3[A, B, C] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.exclude = append(q.exclude, mask...)
-	q.compiled.Reset()
-	return q
+	f.exclude = append(f.exclude, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // WithRelation sets the filter's [ecs.Relation] component and optionally
@@ -623,17 +623,17 @@ func (q *Filter3[A, B, C]) Without(mask ...Comp) *Filter3[A, B, C] {
 // If the optional argument is provided, the filter's relation target is set permanently.
 //
 // Create the required component ID with [T].
-func (q *Filter3[A, B, C]) WithRelation(comp Comp, target ...ecs.Entity) *Filter3[A, B, C] {
-	if q.compiled.locked {
+func (f *Filter3[A, B, C]) WithRelation(comp Comp, target ...ecs.Entity) *Filter3[A, B, C] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.targetType = comp
+	f.targetType = comp
 	if len(target) > 0 {
-		q.target = target[0]
-		q.hasTarget = true
+		f.target = target[0]
+		f.hasTarget = true
 	}
-	q.compiled.Reset()
-	return q
+	f.compiled.Reset()
+	return f
 }
 
 // Filter builds an [ecs.Filter], with an optional relation target.
@@ -644,20 +644,20 @@ func (q *Filter3[A, B, C]) WithRelation(comp Comp, target ...ecs.Entity) *Filter
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter3[A, B, C]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+func (f *Filter3[A, B, C]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
 
-	filter := q.compiled.filter
+	filter := f.compiled.filter
 	if len(target) > 0 {
-		if q.compiled.locked {
+		if f.compiled.locked {
 			panic("can't change relation target on a cached query")
 		}
-		if q.hasTarget {
+		if f.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
+		f.compiled.relationFilter.Filter = &f.compiled.maskFilter
+		f.compiled.relationFilter.Target = target[0]
+		filter = &f.compiled.relationFilter
 	}
 
 	return filter
@@ -671,31 +671,31 @@ func (q *Filter3[A, B, C]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter3[A, B, C]) Query(w *ecs.World, target ...ecs.Entity) Query3[A, B, C] {
-	filter := q.Filter(w, target...)
+func (f *Filter3[A, B, C]) Query(w *ecs.World, target ...ecs.Entity) Query3[A, B, C] {
+	filter := f.Filter(w, target...)
 	return Query3[A, B, C]{
-		Query:     w.Query(filter),
-		target:    q.compiled.TargetComp,
-		hasTarget: q.compiled.HasTarget,
-		id0:       q.compiled.Ids[0],
-		id1:       q.compiled.Ids[1],
-		id2:       q.compiled.Ids[2],
+		Query:       w.Query(filter),
+		relation:    f.compiled.Relation,
+		hasRelation: f.compiled.HasRelation,
+		id0:         f.compiled.Ids[0],
+		id1:         f.compiled.Ids[1],
+		id2:         f.compiled.Ids[2],
 	}
 }
 
 // Register the filter for caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter3[A, B, C]) Register(w *ecs.World) {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
-	q.compiled.Register(w)
+func (f *Filter3[A, B, C]) Register(w *ecs.World) {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
+	f.compiled.Register(w)
 }
 
 // Unregister the filter from caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter3[A, B, C]) Unregister(w *ecs.World) {
-	q.compiled.Unregister(w)
+func (f *Filter3[A, B, C]) Unregister(w *ecs.World) {
+	f.compiled.Unregister(w)
 }
 
 // Query3 is a generic query iterator for three components.
@@ -716,11 +716,11 @@ func (q *Filter3[A, B, C]) Unregister(w *ecs.World) {
 //	}
 type Query3[A any, B any, C any] struct {
 	ecs.Query
-	id0       ecs.ID
-	id1       ecs.ID
-	id2       ecs.ID
-	target    ecs.ID
-	hasTarget bool
+	id0         ecs.ID
+	id1         ecs.ID
+	id2         ecs.ID
+	relation    ecs.ID
+	hasRelation bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -738,10 +738,10 @@ func (q *Query3[A, B, C]) Get() (*A, *B, *C) {
 // Panics if the underlying [Filter3] was not prepared for relations
 // using [Filter3.WithRelation].
 func (q *Query3[A, B, C]) Relation() ecs.Entity {
-	if !q.hasTarget {
+	if !q.hasRelation {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(q.target)
+	return q.Query.Relation(q.relation)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -780,37 +780,37 @@ func NewFilter4[A any, B any, C any, D any]() *Filter4[A, B, C, D] {
 // Create the required mask items with [T].
 //
 // Only affects component types that were specified in the query.
-func (q *Filter4[A, B, C, D]) Optional(mask ...Comp) *Filter4[A, B, C, D] {
-	if q.compiled.locked {
+func (f *Filter4[A, B, C, D]) Optional(mask ...Comp) *Filter4[A, B, C, D] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.optional = append(q.optional, mask...)
-	q.compiled.Reset()
-	return q
+	f.optional = append(f.optional, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // With adds components that are required, but not accessible via [Query4.Get].
 //
 // Create the required mask items with [T].
-func (q *Filter4[A, B, C, D]) With(mask ...Comp) *Filter4[A, B, C, D] {
-	if q.compiled.locked {
+func (f *Filter4[A, B, C, D]) With(mask ...Comp) *Filter4[A, B, C, D] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.include = append(q.include, mask...)
-	q.compiled.Reset()
-	return q
+	f.include = append(f.include, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // Without excludes entities with any of the given components from the query.
 //
 // Create the required mask items with [T].
-func (q *Filter4[A, B, C, D]) Without(mask ...Comp) *Filter4[A, B, C, D] {
-	if q.compiled.locked {
+func (f *Filter4[A, B, C, D]) Without(mask ...Comp) *Filter4[A, B, C, D] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.exclude = append(q.exclude, mask...)
-	q.compiled.Reset()
-	return q
+	f.exclude = append(f.exclude, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // WithRelation sets the filter's [ecs.Relation] component and optionally
@@ -820,17 +820,17 @@ func (q *Filter4[A, B, C, D]) Without(mask ...Comp) *Filter4[A, B, C, D] {
 // If the optional argument is provided, the filter's relation target is set permanently.
 //
 // Create the required component ID with [T].
-func (q *Filter4[A, B, C, D]) WithRelation(comp Comp, target ...ecs.Entity) *Filter4[A, B, C, D] {
-	if q.compiled.locked {
+func (f *Filter4[A, B, C, D]) WithRelation(comp Comp, target ...ecs.Entity) *Filter4[A, B, C, D] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.targetType = comp
+	f.targetType = comp
 	if len(target) > 0 {
-		q.target = target[0]
-		q.hasTarget = true
+		f.target = target[0]
+		f.hasTarget = true
 	}
-	q.compiled.Reset()
-	return q
+	f.compiled.Reset()
+	return f
 }
 
 // Filter builds an [ecs.Filter], with an optional relation target.
@@ -841,20 +841,20 @@ func (q *Filter4[A, B, C, D]) WithRelation(comp Comp, target ...ecs.Entity) *Fil
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter4[A, B, C, D]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+func (f *Filter4[A, B, C, D]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
 
-	filter := q.compiled.filter
+	filter := f.compiled.filter
 	if len(target) > 0 {
-		if q.compiled.locked {
+		if f.compiled.locked {
 			panic("can't change relation target on a cached query")
 		}
-		if q.hasTarget {
+		if f.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
+		f.compiled.relationFilter.Filter = &f.compiled.maskFilter
+		f.compiled.relationFilter.Target = target[0]
+		filter = &f.compiled.relationFilter
 	}
 
 	return filter
@@ -868,32 +868,32 @@ func (q *Filter4[A, B, C, D]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Fil
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter4[A, B, C, D]) Query(w *ecs.World, target ...ecs.Entity) Query4[A, B, C, D] {
-	filter := q.Filter(w, target...)
+func (f *Filter4[A, B, C, D]) Query(w *ecs.World, target ...ecs.Entity) Query4[A, B, C, D] {
+	filter := f.Filter(w, target...)
 	return Query4[A, B, C, D]{
-		Query:     w.Query(filter),
-		target:    q.compiled.TargetComp,
-		hasTarget: q.compiled.HasTarget,
-		id0:       q.compiled.Ids[0],
-		id1:       q.compiled.Ids[1],
-		id2:       q.compiled.Ids[2],
-		id3:       q.compiled.Ids[3],
+		Query:       w.Query(filter),
+		relation:    f.compiled.Relation,
+		hasRelation: f.compiled.HasRelation,
+		id0:         f.compiled.Ids[0],
+		id1:         f.compiled.Ids[1],
+		id2:         f.compiled.Ids[2],
+		id3:         f.compiled.Ids[3],
 	}
 }
 
 // Register the filter for caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter4[A, B, C, D]) Register(w *ecs.World) {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
-	q.compiled.Register(w)
+func (f *Filter4[A, B, C, D]) Register(w *ecs.World) {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
+	f.compiled.Register(w)
 }
 
 // Unregister the filter from caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter4[A, B, C, D]) Unregister(w *ecs.World) {
-	q.compiled.Unregister(w)
+func (f *Filter4[A, B, C, D]) Unregister(w *ecs.World) {
+	f.compiled.Unregister(w)
 }
 
 // Query4 is a generic query iterator for four components.
@@ -914,12 +914,12 @@ func (q *Filter4[A, B, C, D]) Unregister(w *ecs.World) {
 //	}
 type Query4[A any, B any, C any, D any] struct {
 	ecs.Query
-	id0       ecs.ID
-	id1       ecs.ID
-	id2       ecs.ID
-	id3       ecs.ID
-	target    ecs.ID
-	hasTarget bool
+	id0         ecs.ID
+	id1         ecs.ID
+	id2         ecs.ID
+	id3         ecs.ID
+	relation    ecs.ID
+	hasRelation bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -938,10 +938,10 @@ func (q *Query4[A, B, C, D]) Get() (*A, *B, *C, *D) {
 // Panics if the underlying [Filter4] was not prepared for relations
 // using [Filter4.WithRelation].
 func (q *Query4[A, B, C, D]) Relation() ecs.Entity {
-	if !q.hasTarget {
+	if !q.hasRelation {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(q.target)
+	return q.Query.Relation(q.relation)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -981,37 +981,37 @@ func NewFilter5[A any, B any, C any, D any, E any]() *Filter5[A, B, C, D, E] {
 // Create the required mask items with [T].
 //
 // Only affects component types that were specified in the query.
-func (q *Filter5[A, B, C, D, E]) Optional(mask ...Comp) *Filter5[A, B, C, D, E] {
-	if q.compiled.locked {
+func (f *Filter5[A, B, C, D, E]) Optional(mask ...Comp) *Filter5[A, B, C, D, E] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.optional = append(q.optional, mask...)
-	q.compiled.Reset()
-	return q
+	f.optional = append(f.optional, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // With adds components that are required, but not accessible via [Query5.Get].
 //
 // Create the required mask items with [T].
-func (q *Filter5[A, B, C, D, E]) With(mask ...Comp) *Filter5[A, B, C, D, E] {
-	if q.compiled.locked {
+func (f *Filter5[A, B, C, D, E]) With(mask ...Comp) *Filter5[A, B, C, D, E] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.include = append(q.include, mask...)
-	q.compiled.Reset()
-	return q
+	f.include = append(f.include, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // Without excludes entities with any of the given components from the query.
 //
 // Create the required mask items with [T].
-func (q *Filter5[A, B, C, D, E]) Without(mask ...Comp) *Filter5[A, B, C, D, E] {
-	if q.compiled.locked {
+func (f *Filter5[A, B, C, D, E]) Without(mask ...Comp) *Filter5[A, B, C, D, E] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.exclude = append(q.exclude, mask...)
-	q.compiled.Reset()
-	return q
+	f.exclude = append(f.exclude, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // WithRelation sets the filter's [ecs.Relation] component and optionally
@@ -1021,17 +1021,17 @@ func (q *Filter5[A, B, C, D, E]) Without(mask ...Comp) *Filter5[A, B, C, D, E] {
 // If the optional argument is provided, the filter's relation target is set permanently.
 //
 // Create the required component ID with [T].
-func (q *Filter5[A, B, C, D, E]) WithRelation(comp Comp, target ...ecs.Entity) *Filter5[A, B, C, D, E] {
-	if q.compiled.locked {
+func (f *Filter5[A, B, C, D, E]) WithRelation(comp Comp, target ...ecs.Entity) *Filter5[A, B, C, D, E] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.targetType = comp
+	f.targetType = comp
 	if len(target) > 0 {
-		q.target = target[0]
-		q.hasTarget = true
+		f.target = target[0]
+		f.hasTarget = true
 	}
-	q.compiled.Reset()
-	return q
+	f.compiled.Reset()
+	return f
 }
 
 // Filter builds an [ecs.Filter], with an optional relation target.
@@ -1042,20 +1042,20 @@ func (q *Filter5[A, B, C, D, E]) WithRelation(comp Comp, target ...ecs.Entity) *
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter5[A, B, C, D, E]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+func (f *Filter5[A, B, C, D, E]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
 
-	filter := q.compiled.filter
+	filter := f.compiled.filter
 	if len(target) > 0 {
-		if q.compiled.locked {
+		if f.compiled.locked {
 			panic("can't change relation target on a cached query")
 		}
-		if q.hasTarget {
+		if f.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
+		f.compiled.relationFilter.Filter = &f.compiled.maskFilter
+		f.compiled.relationFilter.Target = target[0]
+		filter = &f.compiled.relationFilter
 	}
 
 	return filter
@@ -1069,33 +1069,33 @@ func (q *Filter5[A, B, C, D, E]) Filter(w *ecs.World, target ...ecs.Entity) ecs.
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter5[A, B, C, D, E]) Query(w *ecs.World, target ...ecs.Entity) Query5[A, B, C, D, E] {
-	filter := q.Filter(w, target...)
+func (f *Filter5[A, B, C, D, E]) Query(w *ecs.World, target ...ecs.Entity) Query5[A, B, C, D, E] {
+	filter := f.Filter(w, target...)
 	return Query5[A, B, C, D, E]{
-		Query:     w.Query(filter),
-		target:    q.compiled.TargetComp,
-		hasTarget: q.compiled.HasTarget,
-		id0:       q.compiled.Ids[0],
-		id1:       q.compiled.Ids[1],
-		id2:       q.compiled.Ids[2],
-		id3:       q.compiled.Ids[3],
-		id4:       q.compiled.Ids[4],
+		Query:       w.Query(filter),
+		relation:    f.compiled.Relation,
+		hasRelation: f.compiled.HasRelation,
+		id0:         f.compiled.Ids[0],
+		id1:         f.compiled.Ids[1],
+		id2:         f.compiled.Ids[2],
+		id3:         f.compiled.Ids[3],
+		id4:         f.compiled.Ids[4],
 	}
 }
 
 // Register the filter for caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter5[A, B, C, D, E]) Register(w *ecs.World) {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
-	q.compiled.Register(w)
+func (f *Filter5[A, B, C, D, E]) Register(w *ecs.World) {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
+	f.compiled.Register(w)
 }
 
 // Unregister the filter from caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter5[A, B, C, D, E]) Unregister(w *ecs.World) {
-	q.compiled.Unregister(w)
+func (f *Filter5[A, B, C, D, E]) Unregister(w *ecs.World) {
+	f.compiled.Unregister(w)
 }
 
 // Query5 is a generic query iterator for five components.
@@ -1116,13 +1116,13 @@ func (q *Filter5[A, B, C, D, E]) Unregister(w *ecs.World) {
 //	}
 type Query5[A any, B any, C any, D any, E any] struct {
 	ecs.Query
-	id0       ecs.ID
-	id1       ecs.ID
-	id2       ecs.ID
-	id3       ecs.ID
-	id4       ecs.ID
-	target    ecs.ID
-	hasTarget bool
+	id0         ecs.ID
+	id1         ecs.ID
+	id2         ecs.ID
+	id3         ecs.ID
+	id4         ecs.ID
+	relation    ecs.ID
+	hasRelation bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -1142,10 +1142,10 @@ func (q *Query5[A, B, C, D, E]) Get() (*A, *B, *C, *D, *E) {
 // Panics if the underlying [Filter5] was not prepared for relations
 // using [Filter5.WithRelation].
 func (q *Query5[A, B, C, D, E]) Relation() ecs.Entity {
-	if !q.hasTarget {
+	if !q.hasRelation {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(q.target)
+	return q.Query.Relation(q.relation)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -1186,37 +1186,37 @@ func NewFilter6[A any, B any, C any, D any, E any, F any]() *Filter6[A, B, C, D,
 // Create the required mask items with [T].
 //
 // Only affects component types that were specified in the query.
-func (q *Filter6[A, B, C, D, E, F]) Optional(mask ...Comp) *Filter6[A, B, C, D, E, F] {
-	if q.compiled.locked {
+func (f *Filter6[A, B, C, D, E, F]) Optional(mask ...Comp) *Filter6[A, B, C, D, E, F] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.optional = append(q.optional, mask...)
-	q.compiled.Reset()
-	return q
+	f.optional = append(f.optional, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // With adds components that are required, but not accessible via [Query6.Get].
 //
 // Create the required mask items with [T].
-func (q *Filter6[A, B, C, D, E, F]) With(mask ...Comp) *Filter6[A, B, C, D, E, F] {
-	if q.compiled.locked {
+func (f *Filter6[A, B, C, D, E, F]) With(mask ...Comp) *Filter6[A, B, C, D, E, F] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.include = append(q.include, mask...)
-	q.compiled.Reset()
-	return q
+	f.include = append(f.include, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // Without excludes entities with any of the given components from the query.
 //
 // Create the required mask items with [T].
-func (q *Filter6[A, B, C, D, E, F]) Without(mask ...Comp) *Filter6[A, B, C, D, E, F] {
-	if q.compiled.locked {
+func (f *Filter6[A, B, C, D, E, F]) Without(mask ...Comp) *Filter6[A, B, C, D, E, F] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.exclude = append(q.exclude, mask...)
-	q.compiled.Reset()
-	return q
+	f.exclude = append(f.exclude, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // WithRelation sets the filter's [ecs.Relation] component and optionally
@@ -1226,17 +1226,17 @@ func (q *Filter6[A, B, C, D, E, F]) Without(mask ...Comp) *Filter6[A, B, C, D, E
 // If the optional argument is provided, the filter's relation target is set permanently.
 //
 // Create the required component ID with [T].
-func (q *Filter6[A, B, C, D, E, F]) WithRelation(comp Comp, target ...ecs.Entity) *Filter6[A, B, C, D, E, F] {
-	if q.compiled.locked {
+func (f *Filter6[A, B, C, D, E, F]) WithRelation(comp Comp, target ...ecs.Entity) *Filter6[A, B, C, D, E, F] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.targetType = comp
+	f.targetType = comp
 	if len(target) > 0 {
-		q.target = target[0]
-		q.hasTarget = true
+		f.target = target[0]
+		f.hasTarget = true
 	}
-	q.compiled.Reset()
-	return q
+	f.compiled.Reset()
+	return f
 }
 
 // Filter builds an [ecs.Filter], with an optional relation target.
@@ -1247,20 +1247,20 @@ func (q *Filter6[A, B, C, D, E, F]) WithRelation(comp Comp, target ...ecs.Entity
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter6[A, B, C, D, E, F]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+func (f *Filter6[A, B, C, D, E, F]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
 
-	filter := q.compiled.filter
+	filter := f.compiled.filter
 	if len(target) > 0 {
-		if q.compiled.locked {
+		if f.compiled.locked {
 			panic("can't change relation target on a cached query")
 		}
-		if q.hasTarget {
+		if f.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
+		f.compiled.relationFilter.Filter = &f.compiled.maskFilter
+		f.compiled.relationFilter.Target = target[0]
+		filter = &f.compiled.relationFilter
 	}
 
 	return filter
@@ -1274,34 +1274,34 @@ func (q *Filter6[A, B, C, D, E, F]) Filter(w *ecs.World, target ...ecs.Entity) e
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter6[A, B, C, D, E, F]) Query(w *ecs.World, target ...ecs.Entity) Query6[A, B, C, D, E, F] {
-	filter := q.Filter(w, target...)
+func (f *Filter6[A, B, C, D, E, F]) Query(w *ecs.World, target ...ecs.Entity) Query6[A, B, C, D, E, F] {
+	filter := f.Filter(w, target...)
 	return Query6[A, B, C, D, E, F]{
-		Query:     w.Query(filter),
-		target:    q.compiled.TargetComp,
-		hasTarget: q.compiled.HasTarget,
-		id0:       q.compiled.Ids[0],
-		id1:       q.compiled.Ids[1],
-		id2:       q.compiled.Ids[2],
-		id3:       q.compiled.Ids[3],
-		id4:       q.compiled.Ids[4],
-		id5:       q.compiled.Ids[5],
+		Query:       w.Query(filter),
+		relation:    f.compiled.Relation,
+		hasRelation: f.compiled.HasRelation,
+		id0:         f.compiled.Ids[0],
+		id1:         f.compiled.Ids[1],
+		id2:         f.compiled.Ids[2],
+		id3:         f.compiled.Ids[3],
+		id4:         f.compiled.Ids[4],
+		id5:         f.compiled.Ids[5],
 	}
 }
 
 // Register the filter for caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter6[A, B, C, D, E, F]) Register(w *ecs.World) {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
-	q.compiled.Register(w)
+func (f *Filter6[A, B, C, D, E, F]) Register(w *ecs.World) {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
+	f.compiled.Register(w)
 }
 
 // Unregister the filter from caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter6[A, B, C, D, E, F]) Unregister(w *ecs.World) {
-	q.compiled.Unregister(w)
+func (f *Filter6[A, B, C, D, E, F]) Unregister(w *ecs.World) {
+	f.compiled.Unregister(w)
 }
 
 // Query6 is a generic query iterator for six components.
@@ -1322,14 +1322,14 @@ func (q *Filter6[A, B, C, D, E, F]) Unregister(w *ecs.World) {
 //	}
 type Query6[A any, B any, C any, D any, E any, F any] struct {
 	ecs.Query
-	id0       ecs.ID
-	id1       ecs.ID
-	id2       ecs.ID
-	id3       ecs.ID
-	id4       ecs.ID
-	id5       ecs.ID
-	target    ecs.ID
-	hasTarget bool
+	id0         ecs.ID
+	id1         ecs.ID
+	id2         ecs.ID
+	id3         ecs.ID
+	id4         ecs.ID
+	id5         ecs.ID
+	relation    ecs.ID
+	hasRelation bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -1350,10 +1350,10 @@ func (q *Query6[A, B, C, D, E, F]) Get() (*A, *B, *C, *D, *E, *F) {
 // Panics if the underlying [Filter6] was not prepared for relations
 // using [Filter6.WithRelation].
 func (q *Query6[A, B, C, D, E, F]) Relation() ecs.Entity {
-	if !q.hasTarget {
+	if !q.hasRelation {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(q.target)
+	return q.Query.Relation(q.relation)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -1395,37 +1395,37 @@ func NewFilter7[A any, B any, C any, D any, E any, F any, G any]() *Filter7[A, B
 // Create the required mask items with [T].
 //
 // Only affects component types that were specified in the query.
-func (q *Filter7[A, B, C, D, E, F, G]) Optional(mask ...Comp) *Filter7[A, B, C, D, E, F, G] {
-	if q.compiled.locked {
+func (f *Filter7[A, B, C, D, E, F, G]) Optional(mask ...Comp) *Filter7[A, B, C, D, E, F, G] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.optional = append(q.optional, mask...)
-	q.compiled.Reset()
-	return q
+	f.optional = append(f.optional, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // With adds components that are required, but not accessible via [Query7.Get].
 //
 // Create the required mask items with [T].
-func (q *Filter7[A, B, C, D, E, F, G]) With(mask ...Comp) *Filter7[A, B, C, D, E, F, G] {
-	if q.compiled.locked {
+func (f *Filter7[A, B, C, D, E, F, G]) With(mask ...Comp) *Filter7[A, B, C, D, E, F, G] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.include = append(q.include, mask...)
-	q.compiled.Reset()
-	return q
+	f.include = append(f.include, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // Without excludes entities with any of the given components from the query.
 //
 // Create the required mask items with [T].
-func (q *Filter7[A, B, C, D, E, F, G]) Without(mask ...Comp) *Filter7[A, B, C, D, E, F, G] {
-	if q.compiled.locked {
+func (f *Filter7[A, B, C, D, E, F, G]) Without(mask ...Comp) *Filter7[A, B, C, D, E, F, G] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.exclude = append(q.exclude, mask...)
-	q.compiled.Reset()
-	return q
+	f.exclude = append(f.exclude, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // WithRelation sets the filter's [ecs.Relation] component and optionally
@@ -1435,17 +1435,17 @@ func (q *Filter7[A, B, C, D, E, F, G]) Without(mask ...Comp) *Filter7[A, B, C, D
 // If the optional argument is provided, the filter's relation target is set permanently.
 //
 // Create the required component ID with [T].
-func (q *Filter7[A, B, C, D, E, F, G]) WithRelation(comp Comp, target ...ecs.Entity) *Filter7[A, B, C, D, E, F, G] {
-	if q.compiled.locked {
+func (f *Filter7[A, B, C, D, E, F, G]) WithRelation(comp Comp, target ...ecs.Entity) *Filter7[A, B, C, D, E, F, G] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.targetType = comp
+	f.targetType = comp
 	if len(target) > 0 {
-		q.target = target[0]
-		q.hasTarget = true
+		f.target = target[0]
+		f.hasTarget = true
 	}
-	q.compiled.Reset()
-	return q
+	f.compiled.Reset()
+	return f
 }
 
 // Filter builds an [ecs.Filter], with an optional relation target.
@@ -1456,20 +1456,20 @@ func (q *Filter7[A, B, C, D, E, F, G]) WithRelation(comp Comp, target ...ecs.Ent
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter7[A, B, C, D, E, F, G]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+func (f *Filter7[A, B, C, D, E, F, G]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
 
-	filter := q.compiled.filter
+	filter := f.compiled.filter
 	if len(target) > 0 {
-		if q.compiled.locked {
+		if f.compiled.locked {
 			panic("can't change relation target on a cached query")
 		}
-		if q.hasTarget {
+		if f.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
+		f.compiled.relationFilter.Filter = &f.compiled.maskFilter
+		f.compiled.relationFilter.Target = target[0]
+		filter = &f.compiled.relationFilter
 	}
 
 	return filter
@@ -1483,35 +1483,35 @@ func (q *Filter7[A, B, C, D, E, F, G]) Filter(w *ecs.World, target ...ecs.Entity
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter7[A, B, C, D, E, F, G]) Query(w *ecs.World, target ...ecs.Entity) Query7[A, B, C, D, E, F, G] {
-	filter := q.Filter(w, target...)
+func (f *Filter7[A, B, C, D, E, F, G]) Query(w *ecs.World, target ...ecs.Entity) Query7[A, B, C, D, E, F, G] {
+	filter := f.Filter(w, target...)
 	return Query7[A, B, C, D, E, F, G]{
-		Query:     w.Query(filter),
-		target:    q.compiled.TargetComp,
-		hasTarget: q.compiled.HasTarget,
-		id0:       q.compiled.Ids[0],
-		id1:       q.compiled.Ids[1],
-		id2:       q.compiled.Ids[2],
-		id3:       q.compiled.Ids[3],
-		id4:       q.compiled.Ids[4],
-		id5:       q.compiled.Ids[5],
-		id6:       q.compiled.Ids[6],
+		Query:       w.Query(filter),
+		relation:    f.compiled.Relation,
+		hasRelation: f.compiled.HasRelation,
+		id0:         f.compiled.Ids[0],
+		id1:         f.compiled.Ids[1],
+		id2:         f.compiled.Ids[2],
+		id3:         f.compiled.Ids[3],
+		id4:         f.compiled.Ids[4],
+		id5:         f.compiled.Ids[5],
+		id6:         f.compiled.Ids[6],
 	}
 }
 
 // Register the filter for caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter7[A, B, C, D, E, F, G]) Register(w *ecs.World) {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
-	q.compiled.Register(w)
+func (f *Filter7[A, B, C, D, E, F, G]) Register(w *ecs.World) {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
+	f.compiled.Register(w)
 }
 
 // Unregister the filter from caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter7[A, B, C, D, E, F, G]) Unregister(w *ecs.World) {
-	q.compiled.Unregister(w)
+func (f *Filter7[A, B, C, D, E, F, G]) Unregister(w *ecs.World) {
+	f.compiled.Unregister(w)
 }
 
 // Query7 is a generic query iterator for seven components.
@@ -1532,15 +1532,15 @@ func (q *Filter7[A, B, C, D, E, F, G]) Unregister(w *ecs.World) {
 //	}
 type Query7[A any, B any, C any, D any, E any, F any, G any] struct {
 	ecs.Query
-	id0       ecs.ID
-	id1       ecs.ID
-	id2       ecs.ID
-	id3       ecs.ID
-	id4       ecs.ID
-	id5       ecs.ID
-	id6       ecs.ID
-	target    ecs.ID
-	hasTarget bool
+	id0         ecs.ID
+	id1         ecs.ID
+	id2         ecs.ID
+	id3         ecs.ID
+	id4         ecs.ID
+	id5         ecs.ID
+	id6         ecs.ID
+	relation    ecs.ID
+	hasRelation bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -1562,10 +1562,10 @@ func (q *Query7[A, B, C, D, E, F, G]) Get() (*A, *B, *C, *D, *E, *F, *G) {
 // Panics if the underlying [Filter7] was not prepared for relations
 // using [Filter7.WithRelation].
 func (q *Query7[A, B, C, D, E, F, G]) Relation() ecs.Entity {
-	if !q.hasTarget {
+	if !q.hasRelation {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(q.target)
+	return q.Query.Relation(q.relation)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -1608,37 +1608,37 @@ func NewFilter8[A any, B any, C any, D any, E any, F any, G any, H any]() *Filte
 // Create the required mask items with [T].
 //
 // Only affects component types that were specified in the query.
-func (q *Filter8[A, B, C, D, E, F, G, H]) Optional(mask ...Comp) *Filter8[A, B, C, D, E, F, G, H] {
-	if q.compiled.locked {
+func (f *Filter8[A, B, C, D, E, F, G, H]) Optional(mask ...Comp) *Filter8[A, B, C, D, E, F, G, H] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.optional = append(q.optional, mask...)
-	q.compiled.Reset()
-	return q
+	f.optional = append(f.optional, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // With adds components that are required, but not accessible via [Query8.Get].
 //
 // Create the required mask items with [T].
-func (q *Filter8[A, B, C, D, E, F, G, H]) With(mask ...Comp) *Filter8[A, B, C, D, E, F, G, H] {
-	if q.compiled.locked {
+func (f *Filter8[A, B, C, D, E, F, G, H]) With(mask ...Comp) *Filter8[A, B, C, D, E, F, G, H] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.include = append(q.include, mask...)
-	q.compiled.Reset()
-	return q
+	f.include = append(f.include, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // Without excludes entities with any of the given components from the query.
 //
 // Create the required mask items with [T].
-func (q *Filter8[A, B, C, D, E, F, G, H]) Without(mask ...Comp) *Filter8[A, B, C, D, E, F, G, H] {
-	if q.compiled.locked {
+func (f *Filter8[A, B, C, D, E, F, G, H]) Without(mask ...Comp) *Filter8[A, B, C, D, E, F, G, H] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.exclude = append(q.exclude, mask...)
-	q.compiled.Reset()
-	return q
+	f.exclude = append(f.exclude, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // WithRelation sets the filter's [ecs.Relation] component and optionally
@@ -1648,17 +1648,17 @@ func (q *Filter8[A, B, C, D, E, F, G, H]) Without(mask ...Comp) *Filter8[A, B, C
 // If the optional argument is provided, the filter's relation target is set permanently.
 //
 // Create the required component ID with [T].
-func (q *Filter8[A, B, C, D, E, F, G, H]) WithRelation(comp Comp, target ...ecs.Entity) *Filter8[A, B, C, D, E, F, G, H] {
-	if q.compiled.locked {
+func (f *Filter8[A, B, C, D, E, F, G, H]) WithRelation(comp Comp, target ...ecs.Entity) *Filter8[A, B, C, D, E, F, G, H] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.targetType = comp
+	f.targetType = comp
 	if len(target) > 0 {
-		q.target = target[0]
-		q.hasTarget = true
+		f.target = target[0]
+		f.hasTarget = true
 	}
-	q.compiled.Reset()
-	return q
+	f.compiled.Reset()
+	return f
 }
 
 // Filter builds an [ecs.Filter], with an optional relation target.
@@ -1669,20 +1669,20 @@ func (q *Filter8[A, B, C, D, E, F, G, H]) WithRelation(comp Comp, target ...ecs.
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter8[A, B, C, D, E, F, G, H]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+func (f *Filter8[A, B, C, D, E, F, G, H]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
 
-	filter := q.compiled.filter
+	filter := f.compiled.filter
 	if len(target) > 0 {
-		if q.compiled.locked {
+		if f.compiled.locked {
 			panic("can't change relation target on a cached query")
 		}
-		if q.hasTarget {
+		if f.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
+		f.compiled.relationFilter.Filter = &f.compiled.maskFilter
+		f.compiled.relationFilter.Target = target[0]
+		filter = &f.compiled.relationFilter
 	}
 
 	return filter
@@ -1696,36 +1696,36 @@ func (q *Filter8[A, B, C, D, E, F, G, H]) Filter(w *ecs.World, target ...ecs.Ent
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter8[A, B, C, D, E, F, G, H]) Query(w *ecs.World, target ...ecs.Entity) Query8[A, B, C, D, E, F, G, H] {
-	filter := q.Filter(w, target...)
+func (f *Filter8[A, B, C, D, E, F, G, H]) Query(w *ecs.World, target ...ecs.Entity) Query8[A, B, C, D, E, F, G, H] {
+	filter := f.Filter(w, target...)
 	return Query8[A, B, C, D, E, F, G, H]{
-		Query:     w.Query(filter),
-		target:    q.compiled.TargetComp,
-		hasTarget: q.compiled.HasTarget,
-		id0:       q.compiled.Ids[0],
-		id1:       q.compiled.Ids[1],
-		id2:       q.compiled.Ids[2],
-		id3:       q.compiled.Ids[3],
-		id4:       q.compiled.Ids[4],
-		id5:       q.compiled.Ids[5],
-		id6:       q.compiled.Ids[6],
-		id7:       q.compiled.Ids[7],
+		Query:       w.Query(filter),
+		relation:    f.compiled.Relation,
+		hasRelation: f.compiled.HasRelation,
+		id0:         f.compiled.Ids[0],
+		id1:         f.compiled.Ids[1],
+		id2:         f.compiled.Ids[2],
+		id3:         f.compiled.Ids[3],
+		id4:         f.compiled.Ids[4],
+		id5:         f.compiled.Ids[5],
+		id6:         f.compiled.Ids[6],
+		id7:         f.compiled.Ids[7],
 	}
 }
 
 // Register the filter for caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter8[A, B, C, D, E, F, G, H]) Register(w *ecs.World) {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
-	q.compiled.Register(w)
+func (f *Filter8[A, B, C, D, E, F, G, H]) Register(w *ecs.World) {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
+	f.compiled.Register(w)
 }
 
 // Unregister the filter from caching.
 //
 // See [ecs.Cache] for details on filter caching.
-func (q *Filter8[A, B, C, D, E, F, G, H]) Unregister(w *ecs.World) {
-	q.compiled.Unregister(w)
+func (f *Filter8[A, B, C, D, E, F, G, H]) Unregister(w *ecs.World) {
+	f.compiled.Unregister(w)
 }
 
 // Query8 is a generic query iterator for eight components.
@@ -1746,16 +1746,16 @@ func (q *Filter8[A, B, C, D, E, F, G, H]) Unregister(w *ecs.World) {
 //	}
 type Query8[A any, B any, C any, D any, E any, F any, G any, H any] struct {
 	ecs.Query
-	id0       ecs.ID
-	id1       ecs.ID
-	id2       ecs.ID
-	id3       ecs.ID
-	id4       ecs.ID
-	id5       ecs.ID
-	id6       ecs.ID
-	id7       ecs.ID
-	target    ecs.ID
-	hasTarget bool
+	id0         ecs.ID
+	id1         ecs.ID
+	id2         ecs.ID
+	id3         ecs.ID
+	id4         ecs.ID
+	id5         ecs.ID
+	id6         ecs.ID
+	id7         ecs.ID
+	relation    ecs.ID
+	hasRelation bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -1778,10 +1778,10 @@ func (q *Query8[A, B, C, D, E, F, G, H]) Get() (*A, *B, *C, *D, *E, *F, *G, *H)
 // Panics if the underlying [Filter8] was not prepared for relations
 // using [Filter8.WithRelation].
 func (q *Query8[A, B, C, D, E, F, G, H]) Relation() ecs.Entity {
-	if !q.hasTarget {
+	if !q.hasRelation {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(q.target)
+	return q.Query.Relation(q.relation)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -1825,37 +1825,37 @@ func NewFilter9[A any, B any, C any, D any, E any, F any, G any, H any, I any]()
 // 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 {
+func (f *Filter9[A, B, C, D, E, F, G, H, I]) Optional(mask ...Comp) *Filter9[A, B, C, D, E, F, G, H, I] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.optional = append(q.optional, mask...)
-	q.compiled.Reset()
-	return q
+	f.optional = append(f.optional, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // 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 {
+func (f *Filter9[A, B, C, D, E, F, G, H, I]) With(mask ...Comp) *Filter9[A, B, C, D, E, F, G, H, I] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.include = append(q.include, mask...)
-	q.compiled.Reset()
-	return q
+	f.include = append(f.include, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // 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 {
+func (f *Filter9[A, B, C, D, E, F, G, H, I]) Without(mask ...Comp) *Filter9[A, B, C, D, E, F, G, H, I] {
+	if f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.exclude = append(q.exclude, mask...)
-	q.compiled.Reset()
-	return q
+	f.exclude = append(f.exclude, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // WithRelation sets the filter's [ecs.Relation] component and optionally
@@ -1865,17 +1865,17 @@ func (q *Filter9[A, B, C, D, E, F, G, H, I]) Without(mask ...Comp) *Filter9[A, B
 // 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 {
+func (f *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 f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.targetType = comp
+	f.targetType = comp
 	if len(target) > 0 {
-		q.target = target[0]
-		q.hasTarget = true
+		f.target = target[0]
+		f.hasTarget = true
 	}
-	q.compiled.Reset()
-	return q
+	f.compiled.Reset()
+	return f
 }
 
 // Filter builds an [ecs.Filter], with an optional relation target.
@@ -1886,20 +1886,20 @@ func (q *Filter9[A, B, C, D, E, F, G, H, I]) WithRelation(comp Comp, target ...e
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter9[A, B, C, D, E, F, G, H, I]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+func (f *Filter9[A, B, C, D, E, F, G, H, I]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
 
-	filter := q.compiled.filter
+	filter := f.compiled.filter
 	if len(target) > 0 {
-		if q.compiled.locked {
+		if f.compiled.locked {
 			panic("can't change relation target on a cached query")
 		}
-		if q.hasTarget {
+		if f.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
+		f.compiled.relationFilter.Filter = &f.compiled.maskFilter
+		f.compiled.relationFilter.Target = target[0]
+		filter = &f.compiled.relationFilter
 	}
 
 	return filter
@@ -1913,37 +1913,37 @@ func (q *Filter9[A, B, C, D, E, F, G, H, I]) Filter(w *ecs.World, target ...ecs.
 //   - 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] {
-	filter := q.Filter(w, target...)
+func (f *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] {
+	filter := f.Filter(w, target...)
 	return Query9[A, B, C, D, E, F, G, H, I]{
-		Query:     w.Query(filter),
-		target:    q.compiled.TargetComp,
-		hasTarget: q.compiled.HasTarget,
-		id0:       q.compiled.Ids[0],
-		id1:       q.compiled.Ids[1],
-		id2:       q.compiled.Ids[2],
-		id3:       q.compiled.Ids[3],
-		id4:       q.compiled.Ids[4],
-		id5:       q.compiled.Ids[5],
-		id6:       q.compiled.Ids[6],
-		id7:       q.compiled.Ids[7],
-		id8:       q.compiled.Ids[8],
+		Query:       w.Query(filter),
+		relation:    f.compiled.Relation,
+		hasRelation: f.compiled.HasRelation,
+		id0:         f.compiled.Ids[0],
+		id1:         f.compiled.Ids[1],
+		id2:         f.compiled.Ids[2],
+		id3:         f.compiled.Ids[3],
+		id4:         f.compiled.Ids[4],
+		id5:         f.compiled.Ids[5],
+		id6:         f.compiled.Ids[6],
+		id7:         f.compiled.Ids[7],
+		id8:         f.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)
+func (f *Filter9[A, B, C, D, E, F, G, H, I]) Register(w *ecs.World) {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
+	f.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)
+func (f *Filter9[A, B, C, D, E, F, G, H, I]) Unregister(w *ecs.World) {
+	f.compiled.Unregister(w)
 }
 
 // Query9 is a generic query iterator for nine components.
@@ -1964,17 +1964,17 @@ func (q *Filter9[A, B, C, D, E, F, G, H, I]) Unregister(w *ecs.World) {
 //	}
 type Query9[A any, B any, C any, D any, E any, F any, G any, H any, I any] struct {
 	ecs.Query
-	id0       ecs.ID
-	id1       ecs.ID
-	id2       ecs.ID
-	id3       ecs.ID
-	id4       ecs.ID
-	id5       ecs.ID
-	id6       ecs.ID
-	id7       ecs.ID
-	id8       ecs.ID
-	target    ecs.ID
-	hasTarget bool
+	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
+	relation    ecs.ID
+	hasRelation bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -1998,10 +1998,10 @@ func (q *Query9[A, B, C, D, E, F, G, H, I]) Get() (*A, *B, *C, *D, *E, *F, *G, *
 // Panics if the underlying [Filter9] was not prepared for relations
 // using [Filter9.WithRelation].
 func (q *Query9[A, B, C, D, E, F, G, H, I]) Relation() ecs.Entity {
-	if !q.hasTarget {
+	if !q.hasRelation {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(q.target)
+	return q.Query.Relation(q.relation)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -2046,37 +2046,37 @@ func NewFilter10[A any, B any, C any, D any, E any, F any, G any, H any, I any,
 // 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 {
+func (f *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 f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.optional = append(q.optional, mask...)
-	q.compiled.Reset()
-	return q
+	f.optional = append(f.optional, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // 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 {
+func (f *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 f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.include = append(q.include, mask...)
-	q.compiled.Reset()
-	return q
+	f.include = append(f.include, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // 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 {
+func (f *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 f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.exclude = append(q.exclude, mask...)
-	q.compiled.Reset()
-	return q
+	f.exclude = append(f.exclude, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // WithRelation sets the filter's [ecs.Relation] component and optionally
@@ -2086,17 +2086,17 @@ func (q *Filter10[A, B, C, D, E, F, G, H, I, J]) Without(mask ...Comp) *Filter10
 // 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 {
+func (f *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 f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.targetType = comp
+	f.targetType = comp
 	if len(target) > 0 {
-		q.target = target[0]
-		q.hasTarget = true
+		f.target = target[0]
+		f.hasTarget = true
 	}
-	q.compiled.Reset()
-	return q
+	f.compiled.Reset()
+	return f
 }
 
 // Filter builds an [ecs.Filter], with an optional relation target.
@@ -2107,20 +2107,20 @@ func (q *Filter10[A, B, C, D, E, F, G, H, I, J]) WithRelation(comp Comp, target
 //   - if the filter is registered for caching
 //
 // Panics in these cases.
-func (q *Filter10[A, B, C, D, E, F, G, H, I, J]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+func (f *Filter10[A, B, C, D, E, F, G, H, I, J]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
 
-	filter := q.compiled.filter
+	filter := f.compiled.filter
 	if len(target) > 0 {
-		if q.compiled.locked {
+		if f.compiled.locked {
 			panic("can't change relation target on a cached query")
 		}
-		if q.hasTarget {
+		if f.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
+		f.compiled.relationFilter.Filter = &f.compiled.maskFilter
+		f.compiled.relationFilter.Target = target[0]
+		filter = &f.compiled.relationFilter
 	}
 
 	return filter
@@ -2134,38 +2134,38 @@ func (q *Filter10[A, B, C, D, E, F, G, H, I, J]) Filter(w *ecs.World, target ...
 //   - 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] {
-	filter := q.Filter(w, target...)
+func (f *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] {
+	filter := f.Filter(w, target...)
 	return Query10[A, B, C, D, E, F, G, H, I, J]{
-		Query:     w.Query(filter),
-		target:    q.compiled.TargetComp,
-		hasTarget: q.compiled.HasTarget,
-		id0:       q.compiled.Ids[0],
-		id1:       q.compiled.Ids[1],
-		id2:       q.compiled.Ids[2],
-		id3:       q.compiled.Ids[3],
-		id4:       q.compiled.Ids[4],
-		id5:       q.compiled.Ids[5],
-		id6:       q.compiled.Ids[6],
-		id7:       q.compiled.Ids[7],
-		id8:       q.compiled.Ids[8],
-		id9:       q.compiled.Ids[9],
+		Query:       w.Query(filter),
+		relation:    f.compiled.Relation,
+		hasRelation: f.compiled.HasRelation,
+		id0:         f.compiled.Ids[0],
+		id1:         f.compiled.Ids[1],
+		id2:         f.compiled.Ids[2],
+		id3:         f.compiled.Ids[3],
+		id4:         f.compiled.Ids[4],
+		id5:         f.compiled.Ids[5],
+		id6:         f.compiled.Ids[6],
+		id7:         f.compiled.Ids[7],
+		id8:         f.compiled.Ids[8],
+		id9:         f.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)
+func (f *Filter10[A, B, C, D, E, F, G, H, I, J]) Register(w *ecs.World) {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
+	f.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)
+func (f *Filter10[A, B, C, D, E, F, G, H, I, J]) Unregister(w *ecs.World) {
+	f.compiled.Unregister(w)
 }
 
 // Query10 is a generic query iterator for ten components.
@@ -2186,18 +2186,18 @@ func (q *Filter10[A, B, C, D, E, F, G, H, I, J]) Unregister(w *ecs.World) {
 //	}
 type Query10[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any] struct {
 	ecs.Query
-	id0       ecs.ID
-	id1       ecs.ID
-	id2       ecs.ID
-	id3       ecs.ID
-	id4       ecs.ID
-	id5       ecs.ID
-	id6       ecs.ID
-	id7       ecs.ID
-	id8       ecs.ID
-	id9       ecs.ID
-	target    ecs.ID
-	hasTarget bool
+	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
+	relation    ecs.ID
+	hasRelation bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -2222,10 +2222,10 @@ func (q *Query10[A, B, C, D, E, F, G, H, I, J]) Get() (*A, *B, *C, *D, *E, *F, *
 // Panics if the underlying [Filter10] was not prepared for relations
 // using [Filter10.WithRelation].
 func (q *Query10[A, B, C, D, E, F, G, H, I, J]) Relation() ecs.Entity {
-	if !q.hasTarget {
+	if !q.hasRelation {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(q.target)
+	return q.Query.Relation(q.relation)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -2271,37 +2271,37 @@ func NewFilter11[A any, B any, C any, D any, E any, F any, G any, H any, I any,
 // 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 {
+func (f *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 f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.optional = append(q.optional, mask...)
-	q.compiled.Reset()
-	return q
+	f.optional = append(f.optional, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // 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 {
+func (f *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 f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.include = append(q.include, mask...)
-	q.compiled.Reset()
-	return q
+	f.include = append(f.include, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // 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 {
+func (f *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 f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.exclude = append(q.exclude, mask...)
-	q.compiled.Reset()
-	return q
+	f.exclude = append(f.exclude, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // WithRelation sets the filter's [ecs.Relation] component and optionally
@@ -2311,17 +2311,17 @@ func (q *Filter11[A, B, C, D, E, F, G, H, I, J, K]) Without(mask ...Comp) *Filte
 // 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 {
+func (f *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 f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.targetType = comp
+	f.targetType = comp
 	if len(target) > 0 {
-		q.target = target[0]
-		q.hasTarget = true
+		f.target = target[0]
+		f.hasTarget = true
 	}
-	q.compiled.Reset()
-	return q
+	f.compiled.Reset()
+	return f
 }
 
 // Filter builds an [ecs.Filter], with an optional relation target.
@@ -2332,20 +2332,20 @@ func (q *Filter11[A, B, C, D, E, F, G, H, I, J, K]) WithRelation(comp Comp, targ
 //   - 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]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+func (f *Filter11[A, B, C, D, E, F, G, H, I, J, K]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
 
-	filter := q.compiled.filter
+	filter := f.compiled.filter
 	if len(target) > 0 {
-		if q.compiled.locked {
+		if f.compiled.locked {
 			panic("can't change relation target on a cached query")
 		}
-		if q.hasTarget {
+		if f.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
+		f.compiled.relationFilter.Filter = &f.compiled.maskFilter
+		f.compiled.relationFilter.Target = target[0]
+		filter = &f.compiled.relationFilter
 	}
 
 	return filter
@@ -2359,39 +2359,39 @@ func (q *Filter11[A, B, C, D, E, F, G, H, I, J, K]) Filter(w *ecs.World, target
 //   - 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] {
-	filter := q.Filter(w, target...)
+func (f *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] {
+	filter := f.Filter(w, target...)
 	return Query11[A, B, C, D, E, F, G, H, I, J, K]{
-		Query:     w.Query(filter),
-		target:    q.compiled.TargetComp,
-		hasTarget: q.compiled.HasTarget,
-		id0:       q.compiled.Ids[0],
-		id1:       q.compiled.Ids[1],
-		id2:       q.compiled.Ids[2],
-		id3:       q.compiled.Ids[3],
-		id4:       q.compiled.Ids[4],
-		id5:       q.compiled.Ids[5],
-		id6:       q.compiled.Ids[6],
-		id7:       q.compiled.Ids[7],
-		id8:       q.compiled.Ids[8],
-		id9:       q.compiled.Ids[9],
-		id10:      q.compiled.Ids[10],
+		Query:       w.Query(filter),
+		relation:    f.compiled.Relation,
+		hasRelation: f.compiled.HasRelation,
+		id0:         f.compiled.Ids[0],
+		id1:         f.compiled.Ids[1],
+		id2:         f.compiled.Ids[2],
+		id3:         f.compiled.Ids[3],
+		id4:         f.compiled.Ids[4],
+		id5:         f.compiled.Ids[5],
+		id6:         f.compiled.Ids[6],
+		id7:         f.compiled.Ids[7],
+		id8:         f.compiled.Ids[8],
+		id9:         f.compiled.Ids[9],
+		id10:        f.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)
+func (f *Filter11[A, B, C, D, E, F, G, H, I, J, K]) Register(w *ecs.World) {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
+	f.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)
+func (f *Filter11[A, B, C, D, E, F, G, H, I, J, K]) Unregister(w *ecs.World) {
+	f.compiled.Unregister(w)
 }
 
 // Query11 is a generic query iterator for eleven components.
@@ -2412,19 +2412,19 @@ func (q *Filter11[A, B, C, D, E, F, G, H, I, J, K]) Unregister(w *ecs.World) {
 //	}
 type Query11[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any, K any] struct {
 	ecs.Query
-	id0       ecs.ID
-	id1       ecs.ID
-	id2       ecs.ID
-	id3       ecs.ID
-	id4       ecs.ID
-	id5       ecs.ID
-	id6       ecs.ID
-	id7       ecs.ID
-	id8       ecs.ID
-	id9       ecs.ID
-	id10      ecs.ID
-	target    ecs.ID
-	hasTarget bool
+	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
+	relation    ecs.ID
+	hasRelation bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -2450,10 +2450,10 @@ func (q *Query11[A, B, C, D, E, F, G, H, I, J, K]) Get() (*A, *B, *C, *D, *E, *F
 // Panics if the underlying [Filter11] was not prepared for relations
 // using [Filter11.WithRelation].
 func (q *Query11[A, B, C, D, E, F, G, H, I, J, K]) Relation() ecs.Entity {
-	if !q.hasTarget {
+	if !q.hasRelation {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(q.target)
+	return q.Query.Relation(q.relation)
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -2500,37 +2500,37 @@ func NewFilter12[A any, B any, C any, D any, E any, F any, G any, H any, I any,
 // 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 {
+func (f *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 f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.optional = append(q.optional, mask...)
-	q.compiled.Reset()
-	return q
+	f.optional = append(f.optional, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // 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 {
+func (f *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 f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.include = append(q.include, mask...)
-	q.compiled.Reset()
-	return q
+	f.include = append(f.include, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // 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 {
+func (f *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 f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.exclude = append(q.exclude, mask...)
-	q.compiled.Reset()
-	return q
+	f.exclude = append(f.exclude, mask...)
+	f.compiled.Reset()
+	return f
 }
 
 // WithRelation sets the filter's [ecs.Relation] component and optionally
@@ -2540,17 +2540,17 @@ func (q *Filter12[A, B, C, D, E, F, G, H, I, J, K, L]) Without(mask ...Comp) *Fi
 // 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 {
+func (f *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 f.compiled.locked {
 		panic("can't modify a registered filter")
 	}
-	q.targetType = comp
+	f.targetType = comp
 	if len(target) > 0 {
-		q.target = target[0]
-		q.hasTarget = true
+		f.target = target[0]
+		f.hasTarget = true
 	}
-	q.compiled.Reset()
-	return q
+	f.compiled.Reset()
+	return f
 }
 
 // Filter builds an [ecs.Filter], with an optional relation target.
@@ -2561,20 +2561,20 @@ func (q *Filter12[A, B, C, D, E, F, G, H, I, J, K, L]) WithRelation(comp Comp, t
 //   - 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]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
-	q.compiled.Compile(w, q.include, q.optional, q.exclude, q.targetType, q.target, q.hasTarget)
+func (f *Filter12[A, B, C, D, E, F, G, H, I, J, K, L]) Filter(w *ecs.World, target ...ecs.Entity) ecs.Filter {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
 
-	filter := q.compiled.filter
+	filter := f.compiled.filter
 	if len(target) > 0 {
-		if q.compiled.locked {
+		if f.compiled.locked {
 			panic("can't change relation target on a cached query")
 		}
-		if q.hasTarget {
+		if f.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
+		f.compiled.relationFilter.Filter = &f.compiled.maskFilter
+		f.compiled.relationFilter.Target = target[0]
+		filter = &f.compiled.relationFilter
 	}
 
 	return filter
@@ -2588,40 +2588,40 @@ func (q *Filter12[A, B, C, D, E, F, G, H, I, J, K, L]) Filter(w *ecs.World, targ
 //   - 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] {
-	filter := q.Filter(w, target...)
+func (f *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] {
+	filter := f.Filter(w, target...)
 	return Query12[A, B, C, D, E, F, G, H, I, J, K, L]{
-		Query:     w.Query(filter),
-		target:    q.compiled.TargetComp,
-		hasTarget: q.compiled.HasTarget,
-		id0:       q.compiled.Ids[0],
-		id1:       q.compiled.Ids[1],
-		id2:       q.compiled.Ids[2],
-		id3:       q.compiled.Ids[3],
-		id4:       q.compiled.Ids[4],
-		id5:       q.compiled.Ids[5],
-		id6:       q.compiled.Ids[6],
-		id7:       q.compiled.Ids[7],
-		id8:       q.compiled.Ids[8],
-		id9:       q.compiled.Ids[9],
-		id10:      q.compiled.Ids[10],
-		id11:      q.compiled.Ids[11],
+		Query:       w.Query(filter),
+		relation:    f.compiled.Relation,
+		hasRelation: f.compiled.HasRelation,
+		id0:         f.compiled.Ids[0],
+		id1:         f.compiled.Ids[1],
+		id2:         f.compiled.Ids[2],
+		id3:         f.compiled.Ids[3],
+		id4:         f.compiled.Ids[4],
+		id5:         f.compiled.Ids[5],
+		id6:         f.compiled.Ids[6],
+		id7:         f.compiled.Ids[7],
+		id8:         f.compiled.Ids[8],
+		id9:         f.compiled.Ids[9],
+		id10:        f.compiled.Ids[10],
+		id11:        f.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)
+func (f *Filter12[A, B, C, D, E, F, G, H, I, J, K, L]) Register(w *ecs.World) {
+	f.compiled.Compile(w, f.include, f.optional, f.exclude, f.targetType, f.target, f.hasTarget)
+	f.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)
+func (f *Filter12[A, B, C, D, E, F, G, H, I, J, K, L]) Unregister(w *ecs.World) {
+	f.compiled.Unregister(w)
 }
 
 // Query12 is a generic query iterator for twelve components.
@@ -2642,20 +2642,20 @@ func (q *Filter12[A, B, C, D, E, F, G, H, I, J, K, L]) Unregister(w *ecs.World)
 //	}
 type Query12[A any, B any, C any, D any, E any, F any, G any, H any, I any, J any, K any, L any] struct {
 	ecs.Query
-	id0       ecs.ID
-	id1       ecs.ID
-	id2       ecs.ID
-	id3       ecs.ID
-	id4       ecs.ID
-	id5       ecs.ID
-	id6       ecs.ID
-	id7       ecs.ID
-	id8       ecs.ID
-	id9       ecs.ID
-	id10      ecs.ID
-	id11      ecs.ID
-	target    ecs.ID
-	hasTarget bool
+	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
+	relation    ecs.ID
+	hasRelation bool
 }
 
 // Get returns all queried components for the current query iterator position.
@@ -2682,8 +2682,8 @@ func (q *Query12[A, B, C, D, E, F, G, H, I, J, K, L]) Get() (*A, *B, *C, *D, *E,
 // Panics if the underlying [Filter12] was not prepared for relations
 // using [Filter12.WithRelation].
 func (q *Query12[A, B, C, D, E, F, G, H, I, J, K, L]) Relation() ecs.Entity {
-	if !q.hasTarget {
+	if !q.hasRelation {
 		panic("query has no relation")
 	}
-	return q.Query.Relation(q.target)
+	return q.Query.Relation(q.relation)
 }


Test patch:
diff --git a/generic/map_test.go b/generic/map_test.go
index 988a21d..2474f14 100644
--- a/generic/map_test.go
+++ b/generic/map_test.go
@@ -61,6 +61,28 @@ func TestGenericMapRelations(t *testing.T) {
 	assert.Equal(t, targ, get.GetRelationUnchecked(e0))
 }
 
+func TestGenericMapBatchRelations(t *testing.T) {
+	w := ecs.NewWorld()
+	get := NewMap[testRelationA](&w)
+	genTarg := NewMap1[Position](&w)
+	gen := NewMap2[testRelationA, Position](&w)
+
+	targ1 := genTarg.New()
+	targ2 := genTarg.New()
+	gen.NewBatch(10)
+
+	filter := NewFilter2[testRelationA, Position]().Filter(&w)
+	get.SetRelationBatch(filter, targ1)
+
+	filter = NewFilter2[testRelationA, Position]().Filter(&w, targ1)
+	query := get.SetRelationBatchQ(filter, targ2)
+	assert.Equal(t, 10, query.Count())
+
+	for query.Next() {
+		assert.Equal(t, targ2, query.Relation())
+	}
+}
+
 func ExampleMap() {
 	// Create a world.
 	world := ecs.NewWorld()