Building Domains
This guide walks through building a new simulation domain for Quale. For the Domain interface reference and existing domain tables, see Domain Adapters.
Building a New Domain
Section titled “Building a New Domain”To run a Quale experiment in a new domain:
1. Create the Domain Package
Section titled “1. Create the Domain Package”Create domains/newdomain/ with a struct implementing evolution.Domain:
package newdomain
type MyDomain struct { // domain-specific config fields}
func New() *MyDomain { return &MyDomain{}}
func (d *MyDomain) Configure(cp *parser.CompiledProject) error { // Validate and adapt categories, metrics, world config return nil}
func (d *MyDomain) Evaluate( brain *core.Brain, connectionCount int, scenarios int, rng *rand.Rand,) evolution.EvalResult { // Run brain through simulation, return fitness + metrics return evolution.EvalResult{ Fitness: score, Metrics: map[string]float64{ ... }, }}
func (d *MyDomain) PrintStats(s evolution.GenerationStats) { // Format and print generation statistics}2. Create the Registration File
Section titled “2. Create the Registration File”Create domains/newdomain/register.go with an init() function:
package newdomain
import "quale/evolution"
func init() { evolution.RegisterDomain("newdomain", func() evolution.Domain { return New() })}3. Add the Blank Import
Section titled “3. Add the Blank Import”Add a blank import to main.go so the domain’s init() runs at startup:
import ( _ "quale/domains/newdomain" // registers "newdomain" domain)4. Run With the Domain Flag
Section titled “4. Run With the Domain Flag”quale evolve --domain newdomain experiment.qualeWhat the Domain Must Implement
Section titled “What the Domain Must Implement”- Category validation - reject categories the simulation does not support
- Metric validation - reject fitness metrics the simulation cannot compute
- Sensor provider - given the world state, produce
map[string]float64sensor values for the brain - Actuator interpreter - given brain output, apply actions to the world
- State dynamics - how internal states change each tick (or use
DynamicsEnginefrom adynamicsblock) - Fitness computation - at the end of a scenario, compute metric values from the results
- Scenario runner - the main simulation loop (spawn items, tick agents, track metrics)
The evolution engine is generic - it creates brains, evaluates them via Domain.Evaluate(), and evolves. Your domain provides the sensor values and interprets the actuator outputs.
What the Parser Checks vs What the Domain Checks
Section titled “What the Parser Checks vs What the Domain Checks”| Check | Parser (compile time) | Domain.Configure() (runtime) |
|---|---|---|
| Sensor/actuator syntax | Yes | No |
| Effect keys match body sensors | Yes | No |
| Item properties referenced by body | Yes | No |
| Spawn categories match item categories | Yes | No |
| World capacity overflow | Yes | No |
| Category is supported by domain | No | Yes |
| Fitness metric is computable | No | Yes |
| Sensor names match domain simulation | No | Implicitly (via scenario runner) |
The parser validates the .quale file against itself (internal consistency). Domain.Configure() validates the .quale file against the domain layer (external compatibility).
Error Messages
Section titled “Error Messages”When a .quale file is valid Quale syntax but incompatible with the domain layer:
unsupported item category "signal" for item "GreenSignal" (rail domain supports: signal, crossing, station)This tells the user: your .quale file is syntactically and semantically valid, but the domain you are running it against does not support that category. You need either a different domain or different categories.
Compare with a parser error:
experiment.quale:14:23: unknown sensor type "directonal"
14 | sensor food_nearby: directonal(range: 20, directions: 4) | ^^^^^^^^^ = did you mean "directional"?Parser errors are about the language. Domain errors are about the simulation.