Unlike the mice, each pet has one state machine and a GoalManager which takes cares of goals and plans.
The hierarchy is Goal -> Plan -> State -> SCP action.
Goals describe top-level aims. They may have a Filter method which describes when the goal can be considered; these often check things like the pet's personality and age. A GoalToken carries stateful info about the sprites involved, current goal state, current plan etc. The GoalManager keeps track of a queue of possible Goals and is responsible for searching for relevant goals.
Plans can be multi-stage. The current Goal sets the plan. A PlanToken carries stateful info for the Plan. The Plan pushes states by calling NewState. (States can also be pushed directly by interactions like petting.)
By P4 the goals and plans are almost 1-1, e.g. for GoalLookAtLover there exists PlanLookAtLover.
States are the lowest/most detailed level. These describe immediate state, e.g. PlanInBasket (the pet sitting in the basket) may use StateDroppedInBasket, StatePeekFromBasket, and StateEndPeekFromBasket. States push SCP actions by calling PushUAction (often via some util PetSprite function). A UAction is a 'universal action' which is mapped to a dog or cat SCP action via MapFromUAction.
Goals, Plans and States are all registered on startup into a BehaviourRegistry. (Breeds may register additional Goals/Plans/States, as pigs and bunnies do.)
PetSprite::RunUpdate is responsible for calling GoalManager::UpdateGoals and StateMachine::UpdateState, but goals/plans themselves can also trigger updates.
Goals may choose to let PlanMetascript handle the action, in which case there is no logic - the SCP just handles running animations. In this case the Goal sets up the UAction to call and the Plan handles executing it. e.g. GoalSleep sets the uaction id which PlanMetascript accesses later.
The list of Goals/Plans can be seen here.
GoalTokens have a BehaviorToken which has an InteractionToken. I haven't fully scoped out what info goes in these, but they allow goals to pass info forward to plans which can pass info forward to states. Some of the fields might be arbitrary data fields which goals/plans/states can decide how to make use of. Others hold specific goal/plan/state status info.
Cats and dogs have completely different SCP action mappings. A run action for a dog might be a sleeping action for a cat. To deal with this, states push universal actions which are mapped differently to SCP actions depending on species. There are two mapping arrays in the base game to cover cats and dogs. The mapping is done in
PetSprite::MapFromUAction. Since this is a virtual function of PetSprite, inheriting species-specific classes can theoretically override this to work with their own SCPs.
Cat uaction mapping: uactions_cat.csv
Dog uaction mapping: uactions_dog.csv
Very incomplete list of what states push what uactions: uactions.csv
Here are in-progress Gephi graphs of the dog and cat SCPs.
Cat graph: catgraph.gephi
Dog graph: doggraph.gephi
Cat CSV source: CAT.csv
Dog CSV source: DOG.csv
Note that nodes are SCP states and edges are actions. SCP states themselves have no behaviour. State machine states push action IDs, not state IDs. They will often push actions which represent a self-transition, e.g. cat potato bug pushes uaction 484, which maps to SCP action 1451, which is a 207-207 transition and plays the potatobug animation frames.