Files
gamedev-the-steward/docs/FEATURE_COMMANDS_TARGETS_AND_ECONOMY.md
2026-08-13 00:26:55 +02:00

6.7 KiB

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.

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. For definition fields, read Simulation Definitions.