Refactor ship control layers and update docs
This commit is contained in:
543
STATES.md
Normal file
543
STATES.md
Normal file
@@ -0,0 +1,543 @@
|
||||
# States, Orders, Behaviours, Assignments
|
||||
|
||||
This document defines the intended gameplay model for the project: a multiplayer X4-inspired RTS with strong automation and strong direct control.
|
||||
|
||||
It is no longer purely aspirational. The current runtime now uses the same top-level control layers described here:
|
||||
|
||||
1. `order`
|
||||
2. `defaultBehavior`
|
||||
3. `assignment`
|
||||
4. `controllerTask`
|
||||
5. `state`
|
||||
|
||||
What is still incomplete is scope, not structure.
|
||||
|
||||
## Design Goals
|
||||
|
||||
The game should support both:
|
||||
|
||||
- RTS-style direct control: `move-to`, `mine-this`, `hold-here`, `dock-there`, `attack-this`
|
||||
- X4-style automation: `auto-mine`, `patrol`, `escort`, `trade`, `defend-area`
|
||||
|
||||
The intended player experience is:
|
||||
|
||||
- you can micro units at any time
|
||||
- automation resumes when direct intervention is over
|
||||
- fleets, stations, carriers, and sectors can provide reusable command structure
|
||||
|
||||
## Control Layers
|
||||
|
||||
Each ship should be modeled with five layers:
|
||||
|
||||
1. `order`
|
||||
2. `defaultBehavior`
|
||||
3. `assignment`
|
||||
4. `controllerTask`
|
||||
5. `state`
|
||||
|
||||
### `order`
|
||||
|
||||
A direct player-issued instruction with highest priority.
|
||||
|
||||
Properties:
|
||||
|
||||
- one-shot unless explicitly marked persistent
|
||||
- can preempt automation
|
||||
- may complete, fail, cancel, or timeout
|
||||
- after resolution, ship falls back to `defaultBehavior`
|
||||
|
||||
Current active examples:
|
||||
|
||||
- `move-to(destination)`
|
||||
- `mine-this(node, refinery)`
|
||||
- `dock-at(target)`
|
||||
|
||||
### `defaultBehavior`
|
||||
|
||||
Persistent automation the ship should run when no direct order is active.
|
||||
|
||||
Properties:
|
||||
|
||||
- long-lived
|
||||
- self-replanning
|
||||
- normally loops or maintains a standing objective
|
||||
- resumes automatically after one-shot order resolution
|
||||
|
||||
Current active examples:
|
||||
|
||||
- `idle`
|
||||
- `auto-mine(area, refinery)`
|
||||
- `patrol(route)`
|
||||
- `escort-assigned`
|
||||
|
||||
### `assignment`
|
||||
|
||||
The command relationship the ship belongs to.
|
||||
|
||||
Examples:
|
||||
|
||||
- assigned to fleet
|
||||
- assigned to carrier
|
||||
- assigned to station
|
||||
- assigned to sector command
|
||||
- assigned to mining group
|
||||
|
||||
Assignment does not necessarily tell the ship what exact task to do every second. It provides authority, context, operating area, logistics ownership, and fallback doctrine.
|
||||
|
||||
Current active examples:
|
||||
|
||||
- `unassigned`
|
||||
- `commander-subordinate(commanderId, role)`
|
||||
- `station-based(stationId, role)`
|
||||
- `mining-group(controllerId)`
|
||||
|
||||
### `controllerTask`
|
||||
|
||||
The immediate executable action.
|
||||
|
||||
Examples:
|
||||
|
||||
- travel
|
||||
- dock
|
||||
- undock
|
||||
- mine/extract
|
||||
- unload
|
||||
- follow
|
||||
- hold-position
|
||||
|
||||
Current active examples:
|
||||
|
||||
- `idle`
|
||||
- `travel(destination, threshold)`
|
||||
- `dock(host, bay)`
|
||||
- `undock(host)`
|
||||
- `extract(node)`
|
||||
- `unload(target)`
|
||||
- `follow(target, offset)`
|
||||
|
||||
### `state`
|
||||
|
||||
The physical/runtime state of the unit.
|
||||
|
||||
Examples:
|
||||
|
||||
- idle
|
||||
- spooling-warp
|
||||
- warping
|
||||
- docking
|
||||
- docked
|
||||
- mining
|
||||
- attacking
|
||||
- holding
|
||||
|
||||
## Precedence Rules
|
||||
|
||||
Control precedence should be:
|
||||
|
||||
1. `order`
|
||||
2. `defaultBehavior`
|
||||
3. `assignment doctrine`
|
||||
4. safety/failsafe logic
|
||||
|
||||
Interpretation:
|
||||
|
||||
- if a one-shot order exists, it drives planning
|
||||
- if no order exists, the ship executes its default behavior
|
||||
- if no explicit default behavior exists, assignment may supply one
|
||||
- if none of the above apply, ship uses idle/safe fallback
|
||||
|
||||
This is the active planner model in the codebase.
|
||||
|
||||
## Order Lifecycle
|
||||
|
||||
One-shot orders are the RTS-facing layer.
|
||||
|
||||
Each order should move through:
|
||||
|
||||
1. `queued`
|
||||
2. `accepted`
|
||||
3. `planning`
|
||||
4. `executing`
|
||||
5. one of `completed`, `failed`, `cancelled`, `blocked`
|
||||
|
||||
Recommended behavior:
|
||||
|
||||
- `completed`: clear order and resume `defaultBehavior`
|
||||
- `cancelled`: clear order immediately and resume `defaultBehavior`
|
||||
- `failed`: clear order or retry depending on order policy
|
||||
- `blocked`: wait, reroute, or escalate depending on order policy
|
||||
|
||||
Current implementation notes:
|
||||
|
||||
- direct orders already move through `queued`, `accepted`, `planning`, `executing`
|
||||
- `completed`, `failed`, and `blocked` are already represented
|
||||
- full queued-order chains do not exist yet
|
||||
|
||||
## Command Categories
|
||||
|
||||
### One-shot direct orders
|
||||
|
||||
These are the core RTS commands.
|
||||
|
||||
| Order | Intent | Status |
|
||||
| --- | --- | --- |
|
||||
| `move-to(destination)` | go to a position or object | active |
|
||||
| `mine-this(node)` | mine this specific node | active |
|
||||
| `dock-at(target)` | dock at target | active |
|
||||
| `undock` | leave current host | not first-class yet |
|
||||
| `hold-here(position)` | stay at current or assigned point | not implemented |
|
||||
| `attack(target)` | attack a specific unit/object | not implemented as direct order |
|
||||
| `intercept(target)` | chase and engage moving target | not implemented |
|
||||
| `escort(target)` | follow and defend a specific target | not implemented as direct one-shot order |
|
||||
| `transfer-cargo(target, item, amount)` | move cargo between entities | not implemented |
|
||||
| `build-here(site)` | construct at location | not implemented |
|
||||
| `salvage-this(target)` | recover specific wreck/resource | not implemented |
|
||||
| `retreat-to(destination)` | disengage and move to safety | not implemented |
|
||||
|
||||
### Persistent default behaviours
|
||||
|
||||
These are the automation layer.
|
||||
|
||||
| Behavior | Intent | Status |
|
||||
| --- | --- | --- |
|
||||
| `idle` | do nothing beyond safety drift / local maintenance | active |
|
||||
| `auto-mine(area, refinery)` | find nodes, mine, deliver, repeat | active |
|
||||
| `patrol(route or area)` | move across checkpoints and react to threats | active |
|
||||
| `escort-assigned` | stay with assigned commander or protected ship | active |
|
||||
| `defend-area(area)` | hold area and engage threats inside rules | not implemented |
|
||||
| `trade(route or policy)` | acquire, move, and deliver goods automatically | not implemented |
|
||||
| `resupply(host or fleet)` | fetch and deliver fuel/ammo/cargo | not implemented |
|
||||
| `ferry-units(host)` | shuttle units between hosts or waypoints | not implemented |
|
||||
| `harvest(area)` | collect local resources automatically | not implemented |
|
||||
| `hold-area(center, radius)` | remain in zone with low autonomy | not implemented |
|
||||
|
||||
### Assignments
|
||||
|
||||
Assignments define command context.
|
||||
|
||||
| Assignment | Meaning | Status |
|
||||
| --- | --- | --- |
|
||||
| `unassigned` | independent unit | active |
|
||||
| `fleet-member(fleetId, role)` | unit belongs to a fleet structure | not implemented |
|
||||
| `commander-subordinate(commanderId, role)` | subordinate follows commander doctrine | active |
|
||||
| `carrier-wing(carrierId, role)` | launch/recover/escort under carrier | not implemented as distinct type |
|
||||
| `station-based(stationId, role)` | station-owned logistics or defense unit | active |
|
||||
| `sector-command(sectorId, role)` | unit operates inside a sector mandate | not implemented |
|
||||
| `mining-group(controllerId)` | industrial unit tied to a mining controller | active |
|
||||
|
||||
Assignments can supply default behavior if the unit has none explicitly set.
|
||||
|
||||
## Planner Model
|
||||
|
||||
Planning works like this:
|
||||
|
||||
1. if `order` exists, derive `controllerTask` from it
|
||||
2. else if `defaultBehavior` exists, derive `controllerTask` from it
|
||||
3. else if `assignment` implies doctrine, derive `defaultBehavior`
|
||||
4. else run idle fallback
|
||||
|
||||
This preserves RTS responsiveness while keeping X4-style automation.
|
||||
|
||||
Current implementation detail:
|
||||
|
||||
- mining and patrol progress are stored directly on `order` or `defaultBehavior`
|
||||
- there is no separate high-level “captain” intent layer anymore
|
||||
|
||||
## Physical States
|
||||
|
||||
These are the intended physical states for ships.
|
||||
|
||||
| State | Meaning | Status |
|
||||
| --- | --- | --- |
|
||||
| `idle` | no active movement or operation | active |
|
||||
| `holding` | maintaining current position/formation/anchor | reserved |
|
||||
| `spooling-warp` | charging local fast-travel | active |
|
||||
| `warping` | high-speed in-system travel | active |
|
||||
| `spooling-ftl` | charging inter-system travel | active |
|
||||
| `ftl` | inter-system travel | active |
|
||||
| `arriving` | final approach after warp/ftl | active |
|
||||
| `approaching` | standard approach to target | reserved |
|
||||
| `docking-approach` | approach to docking lane or bay | active |
|
||||
| `docking` | docking procedure in progress | active |
|
||||
| `docked` | physically docked | active |
|
||||
| `undocking` | undocking procedure in progress | active |
|
||||
| `mining-approach` | aligning to resource node | active |
|
||||
| `mining` | active extraction | active |
|
||||
| `transferring` | moving cargo, fuel, or units | active |
|
||||
| `building` | performing construction | reserved |
|
||||
| `repairing` | performing repair actions | reserved |
|
||||
| `patrolling` | movement as part of patrol | active |
|
||||
| `escorting` | movement as part of escort | active |
|
||||
| `attacking` | active weapons engagement | reserved |
|
||||
| `retreating` | disengaging toward safety | reserved |
|
||||
| `disabled` | cannot act | reserved |
|
||||
| `destroyed` | removed from play | active outcome |
|
||||
|
||||
## Controller Tasks
|
||||
|
||||
These are the low-level tasks the planner can issue.
|
||||
|
||||
| Task | Purpose | Status |
|
||||
| --- | --- | --- |
|
||||
| `idle` | no task | active |
|
||||
| `travel(destination, threshold)` | move to destination | active |
|
||||
| `hold(position, radius)` | maintain a point | not implemented |
|
||||
| `dock(host, bay)` | dock at host | active |
|
||||
| `undock(host)` | undock from host | active |
|
||||
| `extract(node)` | mine/extract resource | active |
|
||||
| `unload(target, item, amount?)` | move cargo out | active |
|
||||
| `load(target, item, amount?)` | move cargo in | not implemented |
|
||||
| `follow(target, offset)` | maintain formation | active |
|
||||
| `attack(target)` | engage target | not implemented as controller task |
|
||||
| `intercept(target)` | chase moving target | not implemented |
|
||||
| `orbit(target, radius)` | remain in orbit around target | not implemented |
|
||||
| `build(site)` | perform construction | not implemented |
|
||||
| `repair(target)` | perform repair | not implemented |
|
||||
| `scan(target)` | gather intel | not implemented |
|
||||
|
||||
## Events
|
||||
|
||||
Events should be explicit and usable by both gameplay and debug tools.
|
||||
|
||||
### Order events
|
||||
|
||||
| Event | Meaning | Status |
|
||||
| --- | --- | --- |
|
||||
| `order-issued` | player or AI created an order | partially implicit |
|
||||
| `order-accepted` | unit accepted order | represented in order status |
|
||||
| `order-rejected` | unit cannot accept order | not explicit yet |
|
||||
| `order-started` | execution began | represented in order status |
|
||||
| `order-blocked` | execution cannot proceed for now | represented in order status |
|
||||
| `order-completed` | order finished successfully | represented and logged |
|
||||
| `order-failed` | order failed | represented and logged |
|
||||
| `order-cancelled` | order was manually cleared or overridden | reserved |
|
||||
|
||||
### Behavior events
|
||||
|
||||
| Event | Meaning | Status |
|
||||
| --- | --- | --- |
|
||||
| `behavior-set` | default behavior assigned | represented in debug history |
|
||||
| `behavior-cleared` | default behavior removed | represented in debug history |
|
||||
| `behavior-resumed` | resumed after order completion | implicit |
|
||||
| `behavior-paused` | temporarily suspended | implicit |
|
||||
| `behavior-phase-changed` | automation phase updated | represented in debug history |
|
||||
|
||||
### Assignment events
|
||||
|
||||
| Event | Meaning | Status |
|
||||
| --- | --- | --- |
|
||||
| `assignment-set` | unit assigned | represented in debug history |
|
||||
| `assignment-cleared` | assignment removed | represented in debug history |
|
||||
| `assignment-role-changed` | subordinate role changed | partially represented |
|
||||
| `commander-lost` | assigned leader unavailable | implicit |
|
||||
|
||||
### Controller/runtime events
|
||||
|
||||
| Event | Meaning | Status |
|
||||
| --- | --- | --- |
|
||||
| `arrived` | destination reached | active |
|
||||
| `docking-clearance-granted` | docking permission accepted | active via history |
|
||||
| `docking-clearance-denied` | docking permission denied | active via history |
|
||||
| `docking-begin` | docking procedure started | active |
|
||||
| `docked` | docking complete | active |
|
||||
| `undocked` | undocking complete | active |
|
||||
| `cargo-full` | cargo reached capacity | active via history |
|
||||
| `cargo-empty` | cargo emptied | active via history |
|
||||
| `target-lost` | attack/escort target unavailable | not explicit yet |
|
||||
| `hostile-detected` | threat found | not explicit yet |
|
||||
| `resource-depleted` | node exhausted | not explicit yet |
|
||||
| `damaged` | ship took damage | not explicit yet |
|
||||
| `destroyed` | ship destroyed | active outcome |
|
||||
|
||||
## Transition Rules
|
||||
|
||||
### Core fallback rule
|
||||
|
||||
When a direct order resolves:
|
||||
|
||||
- clear `order`
|
||||
- emit terminal order event
|
||||
- resume `defaultBehavior`
|
||||
- if no explicit `defaultBehavior`, ask `assignment` for fallback
|
||||
- if none exists, become `idle`
|
||||
|
||||
This is the main rule that keeps automation and RTS control compatible.
|
||||
|
||||
### Example: direct move order
|
||||
|
||||
1. player issues `move-to`
|
||||
2. ship order becomes `move-to(destination)`
|
||||
3. planner emits `travel(destination)`
|
||||
4. ship moves through travel states
|
||||
5. on `arrived`, order completes
|
||||
6. ship resumes previous default behavior, such as `patrol` or `auto-mine`
|
||||
|
||||
### Example: direct mine-this order
|
||||
|
||||
1. player issues `mine-this(node)`
|
||||
2. order overrides `auto-mine`
|
||||
3. planner travels to node
|
||||
4. planner extracts until cargo is full
|
||||
5. order completes
|
||||
6. unit returns to `auto-mine`
|
||||
|
||||
Current limitation:
|
||||
|
||||
- the one-shot `mine-this` order does not yet auto-deliver once before completing
|
||||
|
||||
### Example: assignment-driven escort
|
||||
|
||||
1. fighter assigned to `commander-subordinate(commanderId, escort)`
|
||||
2. assignment implies default behavior `escort-assigned`
|
||||
3. on no direct order, fighter follows commander
|
||||
4. when direct order ends, fighter resumes escort automatically
|
||||
|
||||
## Blocking and Waiting
|
||||
|
||||
Blocked execution should be explicit, not hidden inside `idle`.
|
||||
|
||||
Common blocked reasons:
|
||||
|
||||
- no docking bay free
|
||||
- no path to destination
|
||||
- no cargo space
|
||||
- no valid mining target
|
||||
- no ammo/fuel/energy
|
||||
- target out of command scope
|
||||
- waiting for commander or carrier
|
||||
|
||||
Recommended blocked substate or status:
|
||||
|
||||
- `blocked(waiting-for-bay)`
|
||||
- `blocked(waiting-for-target)`
|
||||
- `blocked(waiting-for-resource)`
|
||||
- `blocked(waiting-for-order-scope)`
|
||||
|
||||
Current implementation:
|
||||
|
||||
- blocked status exists on `order`
|
||||
- blocked reason metadata does not yet exist
|
||||
|
||||
## Queue and Override Rules
|
||||
|
||||
For RTS feel, orders should support:
|
||||
|
||||
- immediate replace
|
||||
- optional shift-queue
|
||||
- cancel current order
|
||||
- clear all queued orders
|
||||
|
||||
Recommended semantics:
|
||||
|
||||
- normal click command replaces current direct order
|
||||
- shift-command appends to order queue
|
||||
- automation only runs when order queue is empty
|
||||
|
||||
Current implementation:
|
||||
|
||||
- immediate replace behavior exists
|
||||
- shift queues do not exist yet
|
||||
|
||||
## Suggested Data Shape
|
||||
|
||||
Current runtime shape is effectively:
|
||||
|
||||
```ts
|
||||
type ShipMind = {
|
||||
order?: ShipOrder;
|
||||
defaultBehavior: ShipBehavior;
|
||||
assignment: ShipAssignment;
|
||||
controllerTask: ControllerTask;
|
||||
state: UnitState;
|
||||
};
|
||||
```
|
||||
|
||||
Future expansion can add:
|
||||
|
||||
```ts
|
||||
type ShipMindExtensions = {
|
||||
queuedOrders?: ShipOrder[];
|
||||
blockedReason?: BlockedReason;
|
||||
stance?: CombatStance;
|
||||
};
|
||||
```
|
||||
|
||||
## Mapping To Current Project
|
||||
|
||||
Current runtime concepts map like this:
|
||||
|
||||
| Runtime concept | Current implementation |
|
||||
| --- | --- |
|
||||
| direct move | one-shot `order = move-to` |
|
||||
| direct mining | one-shot `order = mine-this` |
|
||||
| direct docking | one-shot `order = dock-at` |
|
||||
| automation mining | `defaultBehavior = auto-mine` |
|
||||
| automation patrol | `defaultBehavior = patrol` |
|
||||
| assignment escort | `assignment = commander-subordinate` plus `defaultBehavior = escort-assigned` |
|
||||
| execution | `controllerTask` |
|
||||
| physical movement / interaction | `state` |
|
||||
|
||||
## Recommended Next Scope
|
||||
|
||||
To move the game forward without overbuilding, the highest-value next steps are:
|
||||
|
||||
### Direct orders
|
||||
|
||||
- `hold-here`
|
||||
- `attack`
|
||||
- `escort` as a first-class direct order
|
||||
|
||||
### Default behaviors
|
||||
|
||||
- `defend-area`
|
||||
- `trade`
|
||||
- `resupply`
|
||||
|
||||
### Assignments
|
||||
|
||||
- `fleet-member`
|
||||
- `carrier-wing`
|
||||
- `sector-command`
|
||||
|
||||
### Runtime support
|
||||
|
||||
- blocked reason handling
|
||||
- optional order queue
|
||||
- better event emission
|
||||
- more explicit threat / target events
|
||||
|
||||
### Faction strategy
|
||||
|
||||
- reactive shipbuilding priorities
|
||||
- better pirate harassment of industrial targets
|
||||
- clearer economic pressure and throughput visibility
|
||||
|
||||
## Non-Goals
|
||||
|
||||
This model should not force:
|
||||
|
||||
- full economic AI before direct control feels good
|
||||
- full hierarchy simulation before ship orders work
|
||||
- perfect X4 parity
|
||||
|
||||
The target remains a practical hybrid:
|
||||
|
||||
- faster and clearer than a pure sim
|
||||
- deeper and more autonomous than a pure RTS
|
||||
|
||||
## Summary
|
||||
|
||||
The active design is now:
|
||||
|
||||
- one-shot orders for direct RTS control
|
||||
- default behaviors for persistent automation
|
||||
- assignments for organizational structure
|
||||
- controller tasks for immediate execution
|
||||
- physical states for movement and interaction
|
||||
|
||||
That structure supports the gameplay goal cleanly:
|
||||
|
||||
- players can micro ships directly
|
||||
- ships remain useful when not microed
|
||||
- factions can scale through automation
|
||||
- the game can feel like an RTS without losing simulation depth
|
||||
Reference in New Issue
Block a user