The Spatials V3 devblog 2016-03-07

Internal development notes, very slightly cleaned up and commented a week later.

TL;DR: Vitals state machine, sleep and hunger, better coordination between systems

This week finally saw the first working implementation of V3 vitals. Vitals are meant to be implemented as any other entity system, and there's also a base state machine for vitals that require it. Most of the work was centered on this new state machine, starting from the existing job-logistic-machine but ending up in a quite different system, down to the execution level.

- proto vitals fullfilling system
    a system increases/decreases the vital depending on stats and the current state of the entity
        sleep-system musts on sleep-component entities
        regularily decreases them
    each vital has system, and maybe a SM if required
        much better if job-like stuff needs to be done
        can be extended from a base SM
    uses the busy priority system to override as required
        higher prio than jobs, for now same prio for all vitals
    test sm for sleep, use job sm as base
        0) busy takeover at the right sleep level
        1) find available bed
        2) if found, act as job on it
        3) award large +delta sleep while pefroming job

The previous update mentioned the basic model for vitals. It was fleshed out during this week, first with the introduction of a preliminary execution system and a test state machine for sleep based in in the job machine.

- "hide while action" flag for beds
    add accessors to pivot helpers
    add new "hide" pose with empty sprite, use in sleep sm

Some basic features, like hiding entities, are still surfacing as new requirements. For many of these I prefer to wait until I have a clear usage of the feature, then decide on the design. In this case hiding an entity is just adding a new pose to its PoseComponent that has empty sprites. This works perfectly and elegantly and does not require any specific HideComponent or visibility flags, which are an obvious path when you only have engine code, but would have ended up being less elegant.

- replace RESOURCE_KIND_* with components
    add Resource::additionalComponents, simple flag-like comp list that get added to any item of a given Resource
    the various Item helpers must append the new flags
    fix planet resource node/extractors to use new system
- new components:

The kind flags for resources have been replaced by components meant to be added to any item entity, which will make them much easier to search for. The new hunger vital system, described later, just reports a requirement for item entities that have the edible-component, for example.

- new vital-logistic sm, stop using job-logistic sm for vital tasks
    single-run but resumable when needed
        can return failure to allow skipping to another vital
        add helpers to encapsulate start/resume
    optinonal single item logistics but allows matching by aspect
    optinonal action object matched by aspect
    vital-sm-step: single-stop helper for owning an entity and trying to run steps of the vital sm on it
        allows to not have to run sm steps all the time
        handles sm failures gracefully (removing busy states), like missing beds when trying to sleep
- throw away sleep sm based on job sm and base on new vital sm
- vital-sm-step is being too aggressive with attempting to grab busy status
    it's removing lower prio busyC that could stay
        for example trying to sleep while working and no beds are available
    rework vital sm to do a single step without ownership, reporting if it succeded or not, under a can-busy? check
    then vital-sm-step sets the busy status
    -> note this is going to instantiate and destroy a sm just for the check, will hammer when in needy status
        alternative could be always instantiate and keep state around
        ---> it's a mem vs cpu tradeoff
        the sm state is not that mem heavy...
- idea: use more local SM vars: item-ref object-ref
    weak refs plus mandatory deref on every node transition, so no fear of staleness
- rework vital-sm to first look up action reqs (object and item), then locking them with BusyC, then returning
    also add a much better ownership check node, based on the relevant internal refs, much more robust
- explicit (break) on the vital-sm find-reqs state in order to allow for delayed ownership of the entity by vital-sm-step
- (sidenote: this style of SM is more robust than the original job-logistic-sm. combined with proc entities this could be the final winner design pattern for the game AI)
- hunger vital
    reqs on anything edible plus a eating table

The big item for this week is the new vital-logistic-sm, a state machine for fullfilling vitals. It's meant to be run only when a vital reaches a thresold that must make its owner perform some action. For example, the base system for a sleep slowly decreases the sleep vital over time, and calls vital-sm-step on every tick in order to give a chance to either do nothing, spawn a new machine, or tick forward an existing machine.

Vital state machine

The machine itself differs from the previous job design in that it looks ups in the same step both the interaction object and the required resource, locking them both at the same time. This improves the robustness of the action. A new pattern that surfaced here is using weak references stored in the internal machine state, instead of using fields inside the entities components. This is much cleaner and has no problems with staleness, since weak references must always be checked before usage, and the node inheritance system is perfect for enforcing this (check-ownerships is the base node for most of the actual working nodes in the machine).

The simple sleep system was ported from a test implementation using the job machine to the new vital machine in very few lines of code:

(define sleep-sm (state-machine-extend vital-logistic-sm

        (state pre-perform-action
            (to perform-action #t
                (ecs-pose actor "hide")))

        (state perform-action-step
            (to perform-action #t
                (vital-delta actor sleep-component 10)

(define-mod-export (sleep-system-post-process s e)

    (vital-delta e sleep-component -0.5)

    (vital-sm-step e
        state-component: sleep-component
        state-machine: sleep-sm
        busy-priority: sleep-busy-priority
        busy-owner-hint: sleep-busy-owner-hint
        object-aspect: (ecs-aspect must: (list bed-component ReadyComponent_Kind))
        item-aspect: #f
        duration: 60
        phase: (s #@phase)
        need: (< (vital-factor e sleep-component) 0.2))

The result of combining the new hunger and sleep vitals with the existing cooking test job is this: