Perception
The perception block transforms world state and agent state into brain sensor values. It runs at step 2 of the tick loop, after world machines and before the brain fires. Every sensor declared in the body block must have a corresponding sensor X = expr assignment in the perception block (except directional sensors on grid worlds, which are filled automatically).
Perception is where you control what the brain can “see.” Raw simulation state (positions in km, speeds in m/s, entity distances) is normalized and filtered into the 0-1 range that neural networks work with. You can also introduce noise, attention degradation, or information delay to model realistic sensing limitations.
perception OperatorSenses { let max_speed_ms = world.max_speed / 3.6
-- Normalise speed to 0..1 range sensor current_speed = agent.speed / max_speed_ms
-- Distance to next waypoint let chk = nearest_ahead(waypoint, agent.position) sensor waypoint_near = 1.0 - min(chk.distance / 2.0, 1.0)
-- Speed limit in current zone let zone = speed_zone_at(agent.position) sensor speed_limit = zone / world.max_speed
-- Warning level sensor warning_level = agent.warning_active ? 1.0 : 0.0
-- Human factors (set by dynamics, forwarded to brain) sensor fatigue = agent.fatigue sensor stress = agent.stress sensor boredom = agent.boredom sensor cognitive_load = agent.cognitive_load
-- Distance to next warning marker let wm = nearest_ahead(warning_marker, agent.position) sensor distance_ahead = 1.0 - min(wm.distance / 5.0, 1.0)}perception TrafficAnalysis { -- Feed current connection metrics to the brain sensor rate = agent.packet_rate sensor entropy = agent.payload_entropy sensor syn = agent.syn_ratio sensor age = agent.connection_age
-- Alert level from state machine sensor alert_level = agent.threat_score}perception ForagerSenses { -- Map internal state to brain inputs sensor hunger = agent.hunger sensor thirst = agent.thirst sensor energy = agent.energy sensor health = agent.health sensor nausea = agent.nausea
-- Directional sensing fills food_nearby and water_nearby -- automatically from entity positions on the grid}Sensor Assignments
Section titled “Sensor Assignments”Each sensor X = expr line writes a value to the brain input node named X. The name must match a sensor X declaration in the body block.
-- Normalized computation (Human Factors)sensor current_speed = agent.speed / max_speed_ms
-- Direct passthrough from agent state (Network Security)sensor rate = agent.packet_rate
-- Ternary conditional (Human Factors)sensor warning_level = agent.warning_active ? 1.0 : 0.0
-- Inverse distance mapping (Human Factors)sensor waypoint_near = 1.0 - min(chk.distance / 2.0, 1.0)Sensor values are typically normalized to the 0-1 range, matching the internal(0..1) sensor type. The perception block is responsible for this normalization - the body only declares the sensor’s existence and type.
Directional Sensors
Section titled “Directional Sensors”For directional sensors (directional(range: N, directions: 4)) on grid worlds, the engine fills them automatically from entity positions. This is why the Survival perception block does not assign food_nearby or water_nearby explicitly:
perception ForagerSenses { sensor hunger = agent.hunger sensor thirst = agent.thirst sensor energy = agent.energy sensor health = agent.health sensor nausea = agent.nausea
-- Directional sensing fills food_nearby and water_nearby -- automatically from entity positions on the grid}For explicit control over directional sensor values, use a for direction loop:
for direction in sensor.food_nearby.directions { sensor food_nearby[direction] = nearest(food, agent.position, direction).distance}The engine expands this at compile time into 4 (or 8) separate sensor assignments. For route topology, directional sensors are not typically used - there is only one direction (ahead). Use internal() sensors with scalar computations instead.
Let Bindings
Section titled “Let Bindings”Local variables reduce repetition and improve readability. They are stack-allocated temporaries scoped to the perception block. See the let keyword reference at the end of this page for full documentation.
Basic Scalar Binding
Section titled “Basic Scalar Binding”Bind a scalar expression to a name for reuse.
-- Human Factors: convert world max speed from km/h to m/slet max_speed_ms = world.max_speed / 3.6Query Expansion
Section titled “Query Expansion”Bind a spatial query result. Entity properties are promoted to top-level fields on the result.
-- Human Factors: query the nearest waypoint aheadlet chk = nearest_ahead(waypoint, agent.position)-- chk.distance - distance to the entity (built-in)-- chk.index - ordinal index (built-in)-- chk.position - promoted from waypoint properties
-- Human Factors: query the nearest warning markerlet wm = nearest_ahead(warning_marker, agent.position)-- wm.distance - distance to the marker-- wm.severity - promoted from warning_marker properties
-- Human Factors: query the current speed zonelet zone = speed_zone_at(agent.position)-- zone is the speed limit value (scalar result)Let bindings are local to their enclosing block. A let in perception is not visible in action, and vice versa. Each block has its own independent scope.
Match Expressions
Section titled “Match Expressions”Piecewise functions for computing values based on conditions:
let approach_limit = match { when stn.distance > 0.16: zone_limit when stn.distance > 0.08: 60.0 when stn.distance > 0.03: 45.0 else: 15.0}Match evaluates conditions top-to-bottom and returns the value of the first matching branch. The else branch is the fallback.
Side Effects
Section titled “Side Effects”Perception can modify agent state as a side effect, though this should be limited to bookkeeping tasks. Side effects use when guards.
All agent.* writes must match state declarations in the body block.
Expression Reference
Section titled “Expression Reference”The perception block has access to the full expression language. See the expression reference at the end of this page.
Validation Rules
Section titled “Validation Rules”- Every
sensor X = exprmust match asensor Xdeclaration in the body block - Spatial queries (
nearest_ahead,speed_zone_at) must matchquerydeclarations in the world block - All
agent.*references must matchstatedeclarations in the body block - World state references (
world.X) are read-only in perception
Let Keyword Reference
Section titled “Let Keyword Reference”The let keyword creates a local variable binding. It is available in perception, action, machine bodies, and entity handlers.
Basic Scalar Binding
Section titled “Basic Scalar Binding”let dt = world.ticklet max_speed_ms = world.max_speed / 3.6Binds the right-hand expression to the name on the left. The binding is evaluated once when the line executes.
Query Expansion
Section titled “Query Expansion”When a spatial query is bound to a let, the result is a struct with built-in fields plus promoted entity properties.
let chk = nearest_ahead(waypoint, agent.position)-- chk.distance - distance to entity (built-in)-- chk.index - ordinal index in entity list (built-in)-- chk.position - promoted from entity properties-- chk.scheduled_time - promoted from entity propertiesLet bindings are local to their enclosing block:
- A
letin aperceptionblock is not visible inaction - A
letin anon_crosshandler is not visible outside it - A
letat machine level is visible to all states within that machine
Where let Can Be Used
Section titled “Where let Can Be Used”| Context | Example |
|---|---|
| Perception block | let max_speed_ms = world.max_speed / 3.6 |
| Action block | let dt = world.tick |
| Machine body | let sig = nearest_ahead(signal, agent.position) |
| Entity handler | let limit_ms = zone / 3.6 |
Expression Reference
The full expression language available in perception, action, machine, and entity handler blocks.
Arithmetic
Section titled “Arithmetic”| Operator | Description | Example |
|---|---|---|
+ | Addition | agent.speed + 1.0 |
- | Subtraction | 1.0 - min(chk.distance / 2.0, 1.0) |
* | Multiplication | agent.speed * dt / 1000.0 |
/ | Division | agent.speed / max_speed_ms |
Comparison
Section titled “Comparison”| Operator | Description | Example |
|---|---|---|
> | Greater than | actuator.block > 0.5 |
< | Less than | agent.speed < zone_limit_ms |
>= | Greater than or equal | agent.position >= world.length |
<= | Less than or equal | actuator.block <= 0.5 |
== | Equal | dir == none |
!= | Not equal | dir != none |
Boolean
Section titled “Boolean”| Operator | Description | Example |
|---|---|---|
and | Logical AND | actuator.block > 0.5 and elapsed_in_state > 1.0 |
or | Logical OR | actuator.move_n > 0.5 or actuator.move_e > 0.5 |
not | Logical NOT | not agent.warning_acknowledged |
Ternary
Section titled “Ternary”-- Human Factorssensor warning_level = agent.warning_active ? 1.0 : 0.0
-- Network Securitycorrect: malicious ? 1.0 : 0.0
-- Survivalmetric idle_rate = agent.ticks_alive > 0 ? agent.idle_ticks / agent.ticks_alive : 1.0Dot Access
Section titled “Dot Access”| Pattern | Description | Example |
|---|---|---|
agent.X | Agent state | agent.speed, agent.fatigue |
world.X | World constants and state | world.max_speed, world.tick, world.length |
actuator.X | Brain output values | actuator.brake, actuator.block |
sensor.X | Sensor metadata (directions) | sensor.food_nearby.directions |
result.X | Query result fields | chk.distance, chk.index |
Unary Operators
Section titled “Unary Operators”| Operator | Description | Example |
|---|---|---|
-x | Negation | -5.0, -agent.accel |
!x | Logical NOT (symbol) | !agent.alive |
not x | Logical NOT (keyword) | not agent.warning_acknowledged |
Ternary
Section titled “Ternary”Right-associative conditional expression.
sensor warning_level = agent.warning_active ? 1.0 : 0.0Nesting is supported: a ? b ? 1 : 2 : 3.
Match Expression
Section titled “Match Expression”Condition-based (returns the value of the first matching branch):
match { when stn.distance > 0.16: zone_limit when stn.distance > 0.08: 60.0 else: 15.0}Value-based (compares a target against patterns; _ is the wildcard default):
match direction { 0 -> "north" 1 -> "east" _ -> "unknown"}Built-in Functions
Section titled “Built-in Functions”| Function | Signature | Description | Example |
|---|---|---|---|
min(a, b) | (float, float) -> float | Minimum of two values | min(chk.distance / 2.0, 1.0) |
max(a, b) | (float, float) -> float | Maximum of two values | max(0, agent.speed + agent.accel * dt) |
abs(x) | (float) -> float | Absolute value | abs(agent.accel) |
clamp(val, lo, hi) | (float, float, float) -> float | Clamp to range | clamp((value - 0.5) / 1.5, 0, 1) |
sqrt(x) | (float) -> float | Square root | sqrt(agent.distance) |
Assignment Operators
Section titled “Assignment Operators”Used in statements (not inside expressions). Available in action, machine, and entity handler contexts.
| Operator | Description | Example |
|---|---|---|
= | Direct assignment | agent.accel = -5.0 |
+= | Add and assign | agent.warnings_acknowledged += 1 |
-= | Subtract and assign | agent.hunger -= 0.3 |
*= | Multiply and assign | agent.confidence *= 0.99 |
/= | Divide and assign | agent.score /= 2.0 |
Operator Precedence
Section titled “Operator Precedence”Listed from lowest to highest. Higher-precedence operators bind tighter.
| Level | Category | Operators | Associativity |
|---|---|---|---|
| 1 | Ternary | ? : | Right |
| 2 | Logical OR | or | Left |
| 3 | Logical AND | and | Left |
| 4 | Comparison | > < >= <= == != | Non-associative |
| 5 | Addition | + - | Left |
| 6 | Multiplication | * / | Left |
| 7 | Unary | -x !x not x | Right (prefix) |
| 8 | Primary | Literals, identifiers, ., (), [], match | Left (postfix) |
Statement Types
Section titled “Statement Types”All statement types available in perception, action, machine, and entity handler blocks.
| Statement | Syntax | Description |
|---|---|---|
let | let x = expr | Local variable binding |
sensor (perception only) | sensor x = expr | Write to brain input node |
when (block) | when cond { stmts } | Independent conditional guard |
when (single-line) | when cond: stmt | Single-line conditional |
when/else when/else | when cond { } else when cond { } else { } | Mutually exclusive branches |
| Assignment | target op expr | Modify state (=, +=, -=, *=, /=) |
record | record type { fields } | Emit a typed event record |
consume | consume() | Remove current entity (in entity handlers) |
| Transition | -> state_name | State transition (in machines) |