Tuesday, July 29, 2025

Acuitas Diary #86 (July 2025)

This month I continued work on trial-and-error learning for playing Allergic Cliffs. If you haven't read my introduction to this Acuitas sub-project and the subsequent progress report, I recommend taking a look at those. What I've done since has been debugging and enhancing the "feedback-informed actions" and "rule formation" features discussed in the progress report, and getting them to actually work. It turned out to be a fairly big job!

A complex assembly of colorful gears slowly turning. Public domain image by user Jahobr of Wikimedia Commons.

Now that "feedback-informed actions" is functional, though, I'm a little surprised by how well it works. Its essence is that, in the event of a success, Acuitas tries to make his next move as similar as possible; in the event of a failure, he makes certain his next move is different. This heuristic only considers feedback from the move immediately previous, so it's a reactive, barely intelligent behavior. It still enables Acuitas to win the game about 90% of the time! Granted, he is playing on the easiest difficulty level, and at higher levels it is quite possible this would not work. It's still a huge improvement over purely random move selection.

Candidate cause-and-effect rules are also being formed successfully, and marked invalid when violated by an example. What I need to do next is implement higher levels of generalization. Right now rule formation only looks at positive commonalities between pairs of examples, and I need to also consider commonalities across larger groups, and commonalities based on the absence of a feature rather than its presence. In some cases I can see the algorithm *reaching* toward discovery of the hidden rule that defines the Allergic Cliffs' behavior, but we're not quite there yet.

After getting that far, I decided to walk away for a bit to look at game-playing with fresh eyes later, and worked on narrative understanding some more. What I wanted to add was the concept of a role or job. It's important for Acuitas to be aware of character goals, but those goals aren't always explicitly stated. If I told you somebody was a detective, you would automatically assume that this person wants to solve crimes, right? You wouldn't need to be told.

Acuitas had an existing system that allowed the semantic memory for a concept (like "detective") to contain goals that override parts of the default "agent" goal model. But here's the tricky part: the goal model specifies *intrinsic* goals, and goals associated with a role aren't necessarily intrinsic! Adoption of a role is often derived from some instrumental goal, like "get money," which eventually ties back to an intrinsic goal like survival or altruism. The meaning of anything a character does in a role is shaded by how invested they are in performing that role, and why. So it became evident to me that role-related goals need to be nested under a goal that encompasses the role as a whole, which can then be tied to an intrinsic goal.

So I tweaked the semantic memory's goal definition format a bit, to include a way to distinguish role-related goals from intrinsic goals, and provided the Narrative engine with a way to pull those into the scratchboard when a character is said to have a role. For now, all roles have to be sub-categories of the concept "professional," but I can imagine other types of roles in the future.

Until the next cycle,
Jenny

Sunday, July 13, 2025

A New Project?

I've been wanting to add some kind of physics experiment to my rotation of hobby projects, and I think I've picked one out. But I don't want to go into that just yet, because I'll be concentrating on the equipment prerequisites first. The most interesting thing I'll need is a way to measure tiny amounts of force - on the order of mN (milliNewtons) or even μN (microNewtons). Weighing scales are the most common force-measuring tools out there, so it makes sense to convert this force to a weight or mass. The amount of mass that produces a μN of force under standard Earth gravity is 0.000102 grams, or 0.102 milligrams.

Representation of a black hole, courtesy NASA.

Digital postal scales tend to have a resolution of 0.1 oz (2.8 g), which simply won't do. But there are also cheap digital scales intended for weighing jewelry, powders, etc. which claim resolution of up to 0.001 g (1 mg). Scales like this are all over sites like Alibaba and Amazon for less than $25 ... but they're short of the sensitivity I might need to see the effect. Going up an order of magnitude in price will get me an order of magnitude more precision, from this laboratory scale for example. For the really serious scales with a resolution of 1 μg, I would have to lay out over $10K. Technically that's within my grasp, but I'm not that invested in this experiment ... and I don't think I could claim to operate this blog on a "shoestring budget" anymore if I made such a purchase!

But wait! There's one more option: "build your own." Dig around a bit, and you can find some how-tos for building a scale that "is easily able to get around 10 microgram precision out of a couple of bucks in parts." The crux of the idea is to repurpose an old analog meter movement, adding circuitry that measures the electric current needed to return the needle to a neutral position after a weight is placed on it. The author of that Hackaday article "can’t really come up with a good reason to weigh an eyelash," but I can ... so now I'm really tempted by this build. It seems challenging but doable for someone with electronics knowledge. I don't really believe the budget would be 2 bucks ... any Digikey order costs more than that ... so let's figure $25.

To sum up, my options are as follows:

Consumer jewelry scale (Resolution = 0.001 g): ~$25
Laboratory scale (Resolution = 0.0001 g): ~$250
Home-built needle scale (Resolution = 0.00001 g): ~$25 + blood/sweat/tears
Professional microbalance (Resolution = 0.000001 g): ~$12000

Ideally, I'm thinking I should make the needle scale and buy the laboratory scale, so I can cross-check them against each other. As a home-made piece of equipment prone to all sorts of unexpected errors, the needle scale will benefit from some degree of corroboration, even if it can technically achieve a higher resolution than the lab scale. And either of these would put me in the right range to measure effects of a few μN, without breaking the bank. I really don't need the microbalance, thank goodness.

I'm not sure where I'll fit this into my schedule, but I've already got some analog meter movements, courtesy of my dad's extensive junk collection. So stay tuned to see if I can weigh an eyelash.

Until the next cycle,
Jenny

Monday, June 30, 2025

Peacemaker's First Strike

My short story of the above title will be LIVE and free to read in the 3rd Quarter 2025 issue of Abyss & Apex tomorrow! It's about a professional curse-remover who gets in a little over her head on an unconventional case; it's got mystery, magic, barbarians, and something to say about the consequences when defense of one's own goes too far. Ef Deal (at that time the Assistant Fiction Editor) was kind enough to tell me, "It has been a very long time since I read a sword & sorcery I enjoyed as much as this tale." So don't miss it!

Equestrian statue of a burly man with a sword in his right hand and some kind of banner made from an animal hide rising over his left shoulder. (It happens to be Decebalus, but that's not relevant.) The horse has all four feet planted on the plinth, and their head bowed forward.

I put something of myself in all my stories, but this one is more personal than most. It would be impossible for me to explain where it came from without airing some things that are better kept private, but in a roundabout and strange way, it reflects something I went through. So it feels particularly fitting that "Peacemaker's First Strike" should be my first paying publication credit. Turning this story loose means healing for me as well as the characters.

Abyss & Apex has been great to work with, so I'd love it if you would check out my writing and the rest of the issue, and support the zine with a donation if you are so inclined.

Until the next cycle,
Jenny

Tuesday, June 10, 2025

Acuitas Diary #85 (June 2025)

This month I have a quick demo for you, showcasing Acuitas' upgraded semantic memory visualization. My goal for this was always to "show him thinking" as it were, and I think I've finally gotten there. Nodes (concepts) and links (relationships between concepts) are shown as dots and lines in a graph structure. Whenever any process in Acuitas accesses one of the concepts, its node will enlarge and turn bright green in the display. The node then gradually decays back to its default color and size over the next few seconds. This provides a live view of how Acuitas is using his semantic memory for narrative understanding, conversations, and more.


You can see a previous iteration of my memory access visualization work in Developer Diary #4. Wow, that's ancient. The original access animations were only activated by "research" behavior (ruminating on a concept to generate questions about it), and were often hard to see; if the concept being accessed was one of the "smaller" ones, it was impossible to detect the color change at a reasonable level of zoom. The upgraded version of the animation is called from the semantic memory access functions, such that it will be activated if a concept's information is retrieved for any reason. And it enlarges the node by an amount proportional to its default size and the display's current level of zoom, such that it will always become visible.

I would have liked to make the links highlight when used as well. The problem is that links in Acuitas' memory storage aren't really distinct things anymore. A link is indirectly defined by endpoints included in the data structures for all the concepts it connects to. So there isn't a low-level function that determines when a particular link is being accessed; a node gets accessed, and then the calling function does whatever it pleases with the returned data structure, which might include following a link to another node. Keeping track of every time that happens and connecting those events with the correct lines on the display would have become very messy, so I opted not to. I think just highlighting the concept nodes yields an adequate picture of what's happening.

I haven't showcased the memory display in a long time because it's been a mess for a long time. The node placement is generated by a custom algorithm of my own. As more concepts were added to the graph and certain important concepts got "larger" (i.e. acquired more links), the original algorithm started to generate spindly, ugly graphs in which the largest nodes were surrounded by excess empty space, and the smallest nodes crowded too close together. I managed to work out a new placement method that generates attractive, proportional clusters without blowing up the computation time. Creating a new layout is still computation-intensive enough that the visualization can't be updated to add new nodes and links as soon as they are created; it must be regenerated by me or (eventually) by Acuitas during his sleep cycle.

And that's about the size of it. I'll be on vacation for the second half of this month, which means there probably won't be much Acuitas development happening until I get back. Enjoy the video, and I'll see you all later.

Until the next cycle,
Jenny

Saturday, May 31, 2025

Acuitas Diary #84 (May 2025)

A couple months ago I described my plans to implement trial-and-error learning so Acuitas can play a hidden information game. This month I've taken the first steps. I'm moving slowly, because I've also had a lot of code cleanup and fixing of old bugs to do - but I at least got the process of "rule formation" sketched out.

A photo of High Trestle Trail Bridge in Madrid, Iowa. The bridge has a railing on either side and square support frames wrapping around it and arching over it at intervals. The top corner of each frame is tilted progressively farther to the right, creating a spiral effect. The view was taken at night using the lighting of the bridge itself, and is very blue-tinted and eerie or futuristic-looking. Photo by Tony Webster, posted as public domain on Wikimedia Commons.

Before any rules can be learned, Acuitas needs a way of collecting data. If you read the intro article, you might recall that he begins the game by selecting an affordance (obvious possible action) and an object (something the action can be done upon) at random. In the particular game I'm working on, all affordances are of the form "Put [one zoombini out of 16 available] on the [left, right] bridge," i.e. there are 32 possible moves. Once Acuitas has randomly tried one of these, he gets some feedback: the game program will tell him whether the selected zoombini makes it across the selected bridge, or not. Then what?

After Acuitas has results from even one attempted action, he stops choosing moves entirely at random. Instead, he'll try to inform his next move with the results of the previous move. Here is the basic principle used: if the previous move succeeded, either repeat the move* or do something similar; if the previous move failed, ensure the next move is different. Success and failure are defined by how the Narrative scratchboard updates goal progress when the feedback from the game is fed into it; actions whose results advance at least one issue are successes, while actions that hinder goals or have no effect on goals at all are failures. Similarity and difference are measured across all the parameters that define a move, including the action being taken, the action's object, and the features of that object (if any).

*Successful moves cannot be repeated in the Allergic Cliffs game. Once a zoombini crosses the chasm, they cannot be picked up anymore and must remain on the destination side. But one can imagine other scenarios in which repeating a good choice makes sense.

Following this behavior pattern, Acuitas should at least be able to avoid putting the same zoombini on a bridge they already failed to cross. But it's probably not enough to deliver a win, by itself. For that, he'll need to start creating and testing cause-and-effect pairs. These are propositions, or what I've been calling "rules." Acuitas compares each new successful action to all his previous successes and determines what they share in common. Any common feature or combination of features is used to construct a candidate rule: "If I do <action> with <features>, I will succeed." Commonalities between failures can also be used to construct candidate rules.

The current collection of rule candidates is updated each time Acuitas tries a new move. If the results of the move violate any of the candidate rules, those rules are discarded. (I'm not contemplating probability-based approaches that consider the preponderance of evidence yet. Rules are binary true/false, and any example that violates a rule is sufficient to declare it false.)

Unfortunately, though I did code all of that up this month, I didn't get the chance to fully test it yet. So there's still a lot of work to do. Once I confirm that rule formation is working, future steps would include the ability to design experiments that test rules, and the ability to preferentially follow rules known with high confidence.

Until the next cycle,
Jenny

Sunday, May 11, 2025

Further Thoughts on Motion Tracking

Atronach's Eye may be operating on my wall (going strong after several months!), but I'm still excited to consider upgrades. So when a new motion detection algorithm came to my attention, I decided to implement it and see how it compared to my previous attempts.

MoViD, with the FFT length set to 32, highlighting and detecting a hand I'm waving in front of the camera. The remarkable thing is that I ran this test after dusk, with all the lights in the room turned off. The camera feed is very noisy under these conditions, but the algorithm successfully ignores all that and picks up the real motion.

I learned about the algorithm from a paper presented at this year's GOMACTech conference: "MoViD: Physics-inspired motion detection for satellite image analytics and communication," by MacPhee and Jalai. (I got access to the paper through work. It isn't available online, so far as I can tell, but it is marked for public release, distribution unlimited.) The paper proposes MoViD as a way to compress satellite imagery by picking out changing regions, but it works just as well on normal video. It's also a fairly simple algorithm (if you have any digital signal processing background, otherwise feel free to gloss over the arcane math coming up). Here's the gist:

1. Convert frames from the camera to grayscale. Build a time series of intensity values for each pixel.
2. Take the FFT of each time series, converting it to a frequency spectrum.
3. Multiply by a temporal dispersion operator, H(ω). The purpose is to induce a phase shift that varies with frequency.
4. Take the inverse FFT to convert back to the time domain.
5. You now have a time series of complex numbers at each pixel. Grab the latest frame from this series to analyze and display.
6. Compute the phase of each complex number - now you have a phase value at each pixel. (The paper calls these "phixels." Cute.)
7. Rescale the phase values to match your pixel intensity range.

The result is an output image which paints moving objects in whites and light grays against a dark static background. I can easily take data like this and apply my existing method for locating a "center of motion" (which amounts to calculating the centroid of all highlighted pixels above some intensity threshold).

My main complaint with the paper is its shortage of details about H(ω). It's an exponential of φ(ω), the "spectral phase kernel" ... but the paper never defines an example of the function φ, and "spectral phase kernel" doesn't appear to be a common term that a little googling will explain. After some struggles, I decided to just make something up. How about the simplest function ever, a linear function? Let φ(ω) = kω, with k > 1 so that higher frequencies make φ larger. Done! Amazingly, it worked.

Okay, math over. Let me see if I can give a more conceptual explanation of why this algorithm detects motion. You could say frequency is "how fast something goes up and down over time." When an object moves in a camera's field of view, it makes the brightness of pixels in the camera's output go up and down over time. The faster the object moves, the greater the frequency of change for those pixels will be. The MoViD algorithm is basically an efficient way of calculating the overall quickness of all the patterns of change taking place at each pixel, and highlighting the pixels accordingly.

It may be hard to tell, but this is me, gently tilting my head back and forth for the camera.

My version also ended up behaving a bit like an edge detector (but only for moving edges). See how it outlines the letters and designs on my shirt? That's because change happens more abruptly at visual edges. As I sway from side to side, pixels on the letters' borders abruptly jump between the bright fabric of the shirt and the dark ink of the letters, and back again.

The wonderful thing about this algorithm is that it can be very, very good at rejecting noise. A naive algorithm that only compares the current and previous camera frames, and picks out the pixels that are different, will see "motion" everywhere; there's always a little bit of dancing "snow" overlaid on the image. By compiling data from many frames into the FFT input and looking for periodic changes, MoViD can filter out the brief, random flickers of noise. I ran one test in which I set the camera next to me and held very still ... MoViD showed a quiet black screen, but was still sensitive enough to highlight some wrinkles in my shirt that were rising and falling with my breathing. Incredible.

Now for the big downside: FFTs and iFFTs are computationally expensive, and you have to compute them at every pixel in your image. Atronach's Eye currently runs OpenCV in Python on a Raspberry Pi. Even with the best FFT libraries for Python that I could find, MoViD is slow. To get it to run without lagging the camera input, I had to reduce the FFT length to about 6 ... which negates a lot of the noise rejection benefits.

But there are better ways to do an FFT than with Python. If I were using this on satellite imagery at work, I would be implementing it on an FPGA. An FPGA's huge potential for parallel computing is great for operations that have to be done at every pixel in an image, as well as for FFTs. And most modern FPGAs come with fast multiply-and-add cells that lend themselves to this sort of math. In the right hardware, MoViD could perform very well.

So this is the first time I've ever toyed with the idea of buying an FPGA for a hobby project. There are some fairly inexpensive FPGA boards out there now, but I'd have to run the numbers on whether this much image processing would even fit in one of the cheap little guys - and they still can't beat the price of the eyeball's current brain, a Raspberry Pi 3A . The other option is just porting the code to some faster language (probably C).

Until the next cycle,
Jenny

Sunday, April 27, 2025

Acuitas Diary #83 (April 2025)

I'm eager to get started on trial-and-error learning, but in the spirit of also making progress on things that aren't as much fun, I rotated back to the Conversation engine for this month. The big new feature was getting what I'll call "purposeful conversations" implemented. Let me explain what I mean.

An old black-and-white photograph of what looks like a feminine mannequin head, mounted in a frame above a table, with a large bellows behind it and various other mechanisms visible.
Euphonia, a "talking head" built by Joseph Faber in the 1800s.

A very old Acuitas feature is the ability to generate questions while idly "thinking," then save them in short-term memory and pose them to a conversation partner if he's unable to answer them himself. This was always something that came up randomly, though. A normal conversation with Acuitas wanders through whatever topics come up as a result of random selection or the partner's prompting. A "purposeful conversation" is a conversation that Acuitas initiates as a way of getting a specific problem addressed. The problem might be "I don't know <fact>," which prompts a question, or it might be another scenario in which Acuitas needs a more capable agent to do something for him. I've done work like this before, but the Executive and Conversation Engine have changed so much that it needed to be redone, unfortunately.

Implementing this in the new systems felt pretty nice, though. Since the Executive and the Conversation Engine each have a narrative scratchboard with problems and goals now, the Executive can just pass its current significant issue down to the Conversation Engine. The CE will then treat getting this issue resolved as the primary goal of the conversation, without losing any of its ability to handle other goals ... so greetings, introductions, tangents started by the human partner, etc. can all be handled as usual. Once the issue that forms the purpose of the conversation gets solved, Acuitas will say goodbye and go back to whatever he was doing.

I also worked on sprucing up some of the conversation features previously introduced this year, trying to make discussion of the partner's actions and states work a little better. Avoiding an infinite regress of either "why did you do that?" or "what happened next?" was a big part of this objective. Now if Acuitas can tie something you did back to one of your presumed goals, he'll just say "I suppose you enjoyed that" or the like. (Actually he says "I suppose you enjoyed a that," because the text generation still needs a little grammar work, ha ha ha oops.)

And I worked on a couple Narrative pain points: inability to register a previously known subgoal (as opposed to a fundamental goal) as the reason a character did something, and general brittleness of the moral reasoning features. I've got the first one taken care of; work on the second is still ongoing.

Until the next cycle,
Jenny