The color wheel model comes from a 1980 paper by Robert Plutchik, “A General Psychoevolutionary Theory of Emotion”. It is based on, in part, Russell and Mehrabian’s PAD model, the common citation being the 1977 paper “Evidence For a Three-Factor Theory of Emotions”. The M&R paper, and Russell’s later circumplex model are very much sources for the ideas I used in writing the code.
One of the results M&R gives is of a study in which a bunch of undergrads were given a list of emotional terms and were asked to rate them in terms of pleasure (that is, when you are experiencing this emotion, does it feel good or bad; this is sometimes also called “valence” in the literature, as in “positive valence” versus “negative valence”), arousal (sometimes “activation”, the distinction being made here is whether the emotion is associated with a high or low energy level: getting to sleeping late and winning the big game are both positive valence but at opposite ends of the arousal scale), and dominance (whether the emotion is associated with feeling in control or feeling out of control). The result was that the values are in aggregate fairly consistent, and more or less map out a continuous space. The weakest correlations were found for the “dominance” axis, and the later circumplex model is basically just a refinement of just the P and A elements from the original M&R model.
Anyway, the advantage, from my perspective, is that they give their results numerically, basically as population mean and standard deviations, which makes them very amenable to incorporation in a computational model like the one I’m building. I will add a substantial caveat that I basically just treat mental state vectors as just defining a point in a problem space, and evaluate similarity between different emotional states as Euclidean distances and this is not what the circumplex model presents: the metric it offers is radial separation between points on a circle.
That said, what I use it for (apart from assigning initial SWAG values to some things) is affective evaluation of NPC mental state. That is, if you want to script some NPC behavior and want to do different scripted things (like displaying different flavor text for a fidget, or forcing different choices in big narratively-important plot inflection points) by keying off whether the NPC is currently bored, exuberant, tranquil, anxious, or whatever, just seeing which indexed mental state the NPC is closest to is pretty straightforward and seems to be “intuitively correct” most of the time, even if it’s extrapolating outside of the supportable claims made in the paper. And while I’ve been generally relying on “nearest indexed affect” just because I think it’s more authorable, under the hood the model is always computing a mathematical value for the PAD evaluation, and it can be used directly instead.
Example of a random run of a debugging test case, to give a feel for the knobs on the programming interface:
disposition eval:
best disposition match:
Calm (910)
disposition matches by type:
social: Calm (910)
pathological: Stoic (680)
positive: Confident (852)
neutral: Nostalgic (659)
negative: Sad (674)
top three disposition matches:
Calm (910)
Confident (852)
Contempt (836)
affect eval:
valence: 410
arousal: -321
dominance: 405
affect: relaxed
The lattermost bit is just exactly a PAD-ish evaluation based on the current mental state, considered as a point in the PAD space (values in [ -1000, 1000 ], where 1000 is the general integer scale factor throughout).
The upper bits are comparing the current mental state to items in a library of pre-defined disposition vectors, grouped by type. This is used both for evaluating the NPC mental state, and for scripting that shifts the NPC’s mental state in the direction of a named disposition type. Basically just a method that takes a type ID and an integer gain and lerps between the current mental state and the target disposition.
In theory all of the actual decision logic is absolutely portable. In fact it would have been enormously easier to implement all of this in python or javascript or literally anything with a even moderately performant floating point implementation.
That said, several years ago when I started working on this project I looked specifically at Inform first. At the time my design test cases were a poker hand evaluator and a very simple form of a decision engine that just handled fold, raise, and call decisions for NPC poker-playing AI. And I couldn’t figure out how to do it in Inform, and it was comparatively straightfoward in TADS3, so TADS3 is what I went with.
That could just be due to a lack of experience with Inform, though.
Beyond the bare decision logic there’s a bunch of other code I wrote for TADS3, like the targetEngine module and the memoryEngine module, and the code I currently have in the unreleased decisionEngine module leans on them to actually produce behaviors. So that stuff isn’t really portable, but it’s a fairly small proportion of the overall code.
I also relay heavily on a dataTypes module that contains most of the linear algebra stuff (matrix and vector math) that I use in the decision logic. The decision engine and the procgen map creation stuff are probably about 75% of the code in the dataTypes module.