agent update

This commit is contained in:
Jonas H
2026-03-28 13:23:36 +01:00
parent e558b682e2
commit 5c94bb34d5
3 changed files with 145 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
# Intent-Based Architecture Skill
Read the architecture document at `docs/self-gating-systems.md` (resolve relative to the project root `/home/jonas/projects/snow_trail_sdl/`) and follow its patterns when:
- Adding cross-system communication (use intents, not direct function calls)
- Adding new systems to the main loop
- Changing how game modes work (editor, dialog, gameplay)
- Working with camera, input, or mode-switching logic
Key rule: **systems communicate through typed intent data in shared queues** — producers insert intents, consumers process and remove them. No system calls another system's functions directly.

135
docs/self-gating-systems.md Normal file
View File

@@ -0,0 +1,135 @@
# Intent-Based Architecture
Systems communicate through **intents** — one-frame typed data in shared queues — not through direct function calls. This decouples producers from consumers: the system that wants something to happen does not know or care which system processes it.
## The Pattern
```
Producer World (shared state) Consumer
──────── ──────────────────── ────────
detects dialog change
insert(camera, CameraTransitionIntent)
camera_transition_intents
camera_intent_system reads it
sets up CameraTransition component
removes intent
```
An intent is ephemeral (one frame). The state change it triggers (a component mutation) persists.
## Intent Types
Intents are plain structs stored in `Storage<T>` on `World`, keyed by target entity:
```rust
// components/intent.rs
pub struct FollowPlayerIntent;
pub struct StopFollowingIntent;
pub struct CameraTransitionIntent { pub duration: f32 }
```
```rust
// world.rs
pub follow_player_intents: Storage<FollowPlayerIntent>,
pub stop_following_intents: Storage<StopFollowingIntent>,
pub camera_transition_intents: Storage<CameraTransitionIntent>,
```
Adding a new intent = one struct + one storage field. Nothing else changes.
## Producing Intents
Any code with access to the storage can submit. The entity is the target:
```rust
// Event handler wants camera to follow player — doesn't call camera functions
world.follow_player_intents.insert(camera_entity, FollowPlayerIntent);
// Dialog system detects state change — doesn't know how transitions work
world.camera_transition_intents.insert(camera_entity, CameraTransitionIntent { duration: 0.8 });
```
Producers don't import consumer modules. They only know about the intent type and the storage.
## Consuming Intents
A consumer system reads, acts, and removes:
```rust
pub fn camera_intent_system(world: &mut World) {
// 1. Read
let follow_entities = world.follow_player_intents.all();
for entity in follow_entities {
// 2. Act — all the follow setup logic lives here
start_camera_following(world, entity);
// 3. Remove (consume)
world.follow_player_intents.remove(entity);
}
// ... same for stop_following_intents, camera_transition_intents
}
```
The consumer owns the implementation. `start_camera_following` is a private function inside the camera module — no other module can call it directly.
## Why This Matters
**Decoupling.** The dialog system doesn't import camera functions. It inserts a `CameraTransitionIntent`. If the camera system changes how transitions work, the dialog system is unaffected.
**Multiple producers, same path.** Editor toggle, dialog state changes, and future cutscene systems all produce the same `CameraTransitionIntent`. They all go through the same processing. No special cases.
**Testability.** To test camera transitions: insert a `CameraTransitionIntent`, call `camera_intent_system`, check the result. No need to simulate dialog state or editor toggles.
**Additive behavior.** To add a new reaction to `StopFollowingIntent` (e.g., play a sound), write a new system that reads the intent before the camera system consumes it. No existing code changes.
## Execution Order
The main loop is a flat pipeline. Order encodes causality:
```
Input + intent generation (camera_input, player_input, dialog_transition_detect)
Intent processing (camera_intent_system)
Camera behavior (noclip, dialog_camera, follow, transition, ground_clamp)
Editor overlay (UI only — not a game system)
Fixed-step physics (state_machine, physics, triggers, dialog)
Per-frame systems (state_machine, rotate, trees, spotlights, snow)
Render
```
Moving a system changes the data flow. The order is the contract.
## Systems Also Self-Gate
Because intents express what should happen, systems naturally have nothing to do when their data is absent:
- `camera_follow_system` — no `FollowComponent` = no work
- `dialog_camera_system` — no active bubbles = no work
- `player_input_system` — camera not following = no player input
- `camera_noclip_system` — camera has `FollowComponent` = skip
The main loop doesn't branch on mode flags. Systems check their own data.
## When to Use Intents vs. Direct Mutation
| Situation | Approach |
|---|---|
| One system wants another to do something | Intent |
| A system updating its own components | Direct mutation |
| Per-frame continuous computation | Components + tick system |
| Persistent state (is the camera following?) | Component (`FollowComponent`) |
| One-shot request (start following) | Intent (`FollowPlayerIntent`) |
## Adding New Intents
1. Define the struct in `components/intent.rs`
2. Add a `Storage<T>` field to `World` (+ `new()` + `despawn()`)
3. Producers insert into the storage
4. A consumer system reads, acts, and removes
5. Done — no other code changes