150 lines
6.7 KiB
Markdown
150 lines
6.7 KiB
Markdown
# Feature slice: commands, targets, and economy
|
|
|
|
This slice makes player and NPC interactions converge on one authority path.
|
|
The main pieces are `ActionCommand`, `ActionOffer`, `ActionResult`,
|
|
`ActionCommandService`, `CatalogInteractionService`,
|
|
`ActivityActionCommandService`, `WorldTargetRegistry`, `ActiveWorldAdapter`,
|
|
`StorageRoutingPolicy`, and `VillageEconomy`.
|
|
|
|
## Offer versus command
|
|
|
|
An offer is a read-only, copied description of what an actor could attempt.
|
|
It may contain stable action/target IDs, capability data, copy text, a reason
|
|
trace, expected state revision, target generation, context identity, and
|
|
registry-instance identity. It must not contain a `Node`, provider reference,
|
|
callable, or authoritative result.
|
|
|
|
The actor submits an `ActionCommand` with stable actor/action/target IDs,
|
|
optional item and amount, and an expected state revision. The service rechecks
|
|
everything at execution time. A valid offer is not permission to execute after
|
|
the world changes.
|
|
|
|
```text
|
|
query -> copied ActionOffer
|
|
|
|
|
v
|
|
player/NPC command
|
|
|
|
|
v
|
|
revalidate revision + registry instance + generation + provider
|
|
|
|
|
v
|
|
revalidate actor life + capability + range + target + reservation + cost
|
|
|
|
|
v
|
|
atomic mutation -> exact world fact -> copied ActionResult
|
|
```
|
|
|
|
## Context-scoped targets
|
|
|
|
`ActiveWorldAdapter` has explicit `world_id`, `location_id`, and `context_id`
|
|
identity. It owns one `WorldTargetRegistry` for that context. Resource,
|
|
storage, activity, and animal providers register a `WorldTargetDescriptor`
|
|
containing a canonical target ID, target kind, capability descriptors, and a
|
|
transient provider binding.
|
|
|
|
The registry provides:
|
|
|
|
- O(1) canonical ID lookup;
|
|
- cross-kind duplicate rejection within a context;
|
|
- deterministic sorted IDs and capability filtering;
|
|
- generation-checked transient `WorldTargetHandle` values;
|
|
- provider registration/unregistration and stale-provider pruning.
|
|
|
|
The target ID and contextual scope are the authority. A handle is only a
|
|
short-lived geometry/provider reference. Rebuilding a context restarts local
|
|
generations, so player results also carry a registry-instance token to prevent
|
|
ABA execution against a replacement registry.
|
|
|
|
`LoadedResourceSpatialIndex` accelerates finite resource queries by action and
|
|
horizontal cell. It is disposable presentation state; amounts, reservations,
|
|
yield, risk, and discovery priority remain in simulation records.
|
|
|
|
## The current shared handler
|
|
|
|
`ActivityActionCommandService` is the first production shared command handler.
|
|
Patrol and study are authored `activity_metric_delta` actions. The handler:
|
|
|
|
1. validates the actor and expected revision;
|
|
2. validates the action's typed handler/effect contract;
|
|
3. validates activity target kind, live provider, registry scope/instance,
|
|
capability, range, capacity, and task authorization;
|
|
4. preflights and withdraws any authored completion cost through
|
|
`VillageEconomy` and `StorageRoutingPolicy`;
|
|
5. applies the bounded safety or knowledge metric delta;
|
|
6. appends one scoped `activity_completed` fact;
|
|
7. returns exact event IDs, final revision, and reason trace.
|
|
|
|
If the cost is unavailable, no effect is applied and one structured
|
|
`task_blocked` fact records the action, source storage, item, and required
|
|
amount. A capped metric does not consume a cost or emit a false delta. The
|
|
primary event keeps the command revision; synchronous situation/commitment
|
|
consequences may append a causal suffix, and the result reports the final
|
|
revision.
|
|
|
|
NPCs must use their actual current action, target, task state, completion flag,
|
|
alive state, and context scope. An idle, dead, downed, wrong-target, or
|
|
wrong-world direct command fails without mutation. Players use the dedicated
|
|
player boundary, which requires all stale-offer tokens rather than accepting
|
|
sentinel defaults. Both paths share definition validation, economy, event, and
|
|
state checks.
|
|
|
|
## Typed effects and transactional planning
|
|
|
|
`ActionEffectPlanner` turns authored `ActionEffect` resources into an explicit
|
|
`ActionEffectPlan` with stable operation IDs, authored order, parameters, and a
|
|
reason trace. It supports the bounded typed effects currently registered:
|
|
metric/need changes, exact inventory/storage/cargo transfers, damage/healing,
|
|
relationship changes, scheduling ordinary work, and site-condition changes.
|
|
|
|
`TransactionalActionEffectCommitter` is the extension seam for a mechanic that
|
|
needs more than the built-in activity handler. A committer must apply one whole
|
|
plan atomically, return unique event IDs on success, or return a rollback-safe
|
|
failure with no event IDs. Plans reject unknown kinds, duplicate/reordered
|
|
operations, non-primitive values, non-finite numbers, same-role transfers, and
|
|
empty effect sets.
|
|
|
|
## Economy and routing
|
|
|
|
`VillageEconomy` owns exact storage/NPC/player inventory mutations. The
|
|
pantry, woodpile, apothecary, resource yields, animal feeding, player carry
|
|
loop, and action costs use the same transaction boundary. `village.food` and
|
|
`village.wood` are synchronized aggregate views, not second mutation paths.
|
|
|
|
`StorageRoutingPolicy` selects compatible storage by accepted item ID/tag,
|
|
priority, capacity, settlement scope, and stable target ID. The herb →
|
|
apothecary route proves that routing is no longer a food/wood switch. The
|
|
production Jajce economy still has one village settlement; regional stockpiles
|
|
and markets are future work.
|
|
|
|
## Adding a new interaction
|
|
|
|
1. Add or reuse an `ActionDefinition` with target/capability/handler/effect
|
|
fields.
|
|
2. Add target capability data to the provider definition or scene provider.
|
|
3. Register the provider through `ActiveWorldAdapter`.
|
|
4. Add an offer query and a command service/handler only if the existing typed
|
|
handler cannot express the mechanic.
|
|
5. Revalidate all authority at command time.
|
|
6. Mutate records, append the exact event, and test both player and NPC paths.
|
|
7. Add loaded/abstract and stale-offer regressions where applicable.
|
|
|
|
Do not put persistent amounts on `StorageNode`, use abstract Vector3 fallback
|
|
targets, or let the HUD call `SimulationManager` mutation methods directly.
|
|
|
|
## Reference tests and contracts
|
|
|
|
- `tests/world_target_registry_test.gd`
|
|
- `tests/context_scoped_world_targets_test.gd`
|
|
- `tests/unit/test_catalog_interaction_service.gd`
|
|
- `tests/unit/test_activity_action_command_service.gd`
|
|
- `tests/unit/test_activity_content_catalog_contract.gd`
|
|
- `tests/unit/test_action_effect_planning.gd`
|
|
- `tests/resource_node_player_parity_test.gd`
|
|
- `tests/food_storage_loop_test.gd`
|
|
- `tests/animal_food_delivery_test.gd`
|
|
|
|
For the complete action lifecycle and active-position rules, read
|
|
[Action System Architecture](ACTION_SYSTEM_ARCHITECTURE.md). For definition
|
|
fields, read [Simulation Definitions](SIMULATION_DEFINITIONS.md).
|