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:
	mineral-component
	organic-component
	fluid-component
	liquid-component
	gas-component
	metal-component
	edible-component
	drinkable-component

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.

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-machine

        (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)
                (break)))
    )))

(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: