Making inferences

Earlier on in the development of Argus, I made the decision to not bother checking if a character in the story already knew a piece of information before they ‘learned’ it.

My thinking at the time was that the act of looking up the information was essentially the same cost in time and resources as learning it. So, we may as well just learn it. If it altered what they knew, fine. If they already knew it, still fine. Nothing changed.

Then the knowledge inferences/assumptions system got more advanced. Story characters learn about each-other as the narrative progresses. Knowledge about the gender and approximate age-group of speakers is acquired by listeners, when the character speaks, and information is combined with other pieces of knowledge to determine, for example, just how characters should refer to each-other during the narrative.

Eventually the process of looking up lots of disparate pieces of knowledge, filling in with inferences and assumptions, and then producing a reference-model for referring to other characters (in a variety of levels of formality) started to get a bit expensive.

So, I finally bit the bullet, and added a method to the knowledge graph to test if something was already known before we tried to get a character to learn it. That way we can assess if knowledge the character consists of any new knowledge that might require updating their assumptions/inferences.

That means double the workload for any learning a character does (which happens a lot), but the reduction in reference-model updates actually pays back that cost almost fifty times over. The story can be processed even faster than before, with fewer spikes in CPU usage.

Overall, that’s a win.

It did lead me to code this peculiar little construct, though:

KnStore* KnStore::find(statement_t &statement, const std::wstring &p)
 if (statement.size() == 0 && this == nullptr)
   return (KnStore*)1;


Yes, I’m intentionally creating a bad pointer. I already had a find() method that is used to locate knowledge insertion points. Producing a contains() method (does the knowledge already exist) could use exactly the same logic (that is, call find() and test the returned pointer), but always ran off the end of the tree to a null KnStore instance, if and only┬áif it was successful.

That was easy to subvert, and I can return a dummy pointer that no code path ever dereferences (I could also have thrown an exception, but at an additional cost).

Honestly, if I ever saw code like this in someone else’s software, that would immediately raise questions and not a few red flags. Surprisingly, in this case, it’s the perfect solution for a very specific problem.