Tuesday, October 20, 2026

Walking Robot Updates

Let's start with ACE (Ambulatory Canine Emulator), the quadruped. My recent objective was to get at least the hindquarters fully assembled and test hock joint operation with the new motors (17HS08-1004S from StepperOnline). I'm pleased to say it worked out. The old motors could barely flex the joints back and forth without stalling against the friction of the tendons rubbing against their channel. The new ones operate the joints more smoothly, and can extend them to lift the hindquarters (raising their own weight and the weight of the other pair of motors for the hip joints).

No demo video for this one, because I didn't have enough hands to aim a camera while manually switching power to the motors and keeping half a robot from falling over. It still can't get up from a full kneel (joint at maximum flexion, lower leg flat on floor), but I was expecting that; the amount of leverage available from the tendons in that position is just really poor. Even then the motors don't stall - my homemade plastic gearboxes start skipping teeth first.

I didn't want to make a wiring harness before I was even sure the stepper motor approach was viable, but wow, what a mess to run all of TWO motors. I still have lots of my lean custom stepper boards that only need three wires for power and two for control, but they can't handle the current these motors need, so I bought some from Adafruit. They need a bunch of extra pins on them grounded.

Oh yeah, and literally at the end of the final test I wanted to do, the tendon support spar on one of the hock joints snapped. Now I know what I need to shore up before I print the rest of them ...

Next we look to the hydraulic spider, which I've nicknamed "Rachner." I completed the 3D model, fully constructed one leg and one quadrant of the chassis, and made a new batch of bladders to fit the legs joints ... oops, I mean two new batches. I tried using the 2 mil transparent plastic at first, because I thought it would look cool, and it turned out to be too fragile.

 

I printed polypropylene valve stems and heat-sealed the flanges to the insides of the bladders, and also still attached the rings on the outside of the bladder with urethane sealer, for strain relief. Yet ALL FOUR bladders that I made leaked from around the valve stem. I fell back to the tougher 4 mil black plastic, and also began using washers like little irons to help distribute the heat from my soldering iron tip into a round seal. This worked better, and I obtained three bladders that seemed sound. I tried one in the leg and was able to operate both joints, inflating and draining the bladder with a syringe.

I know of some improvements I need to make already. The rotor joint at the base of the leg isn't achieving its full range of motion on the return stroke. This is partly because it needs a stronger spring (don't worry, I have lots of candidates), but the angle at which the tension element pulls on the vane is sub-optimal also; I need to modify the parts a bit, so that the guide holes line up better. This is why I started with only one leg!!

When I operate the knee joint, I'm letting gravity provide the force that returns the joint to its neutral position when I drain the bladder. In the complete robot, this will be reversed; gravity pulling down on the chassis will tend to make the legs splay. I'll attach springs to balance the gravitational force and hold the joints closed, and then inflation of the bladder will act against the springs to push open one at a time and swing its leg up. (This is similar to how real spiders work - by default the legs are contracted, and fluid pressure extends them. Their legs curl after they die because the pressure is gone.)

This feels like precious little progress to talk about, but life has been busy and it's what I've got.

Until the next cycle,
Jenny

Tuesday, September 29, 2026

Acuitas Diary #101 (September 2026)

This month's Acuitas work was about nested narratives, another feature I've been wanting to add for a long time. It essentially supports stories understood as distinct parts of other stories or conversations. I don't just want you to think of literary structures that feature a frame story with other stories inside it; even one character telling another about previous events that happened to them can be a nested narrative. Inner narratives are relevant to outer narratives (assuming a reasonable speaker/storyteller), but have an independent timeline and plot.

A set of Matryoshka dolls from Hungary. There are six dolls; they have cream-colored bodies with red flowers painted on them, deep red head-scarves, and carrot-colored hair. The largest doll wears an open-mouthed smile; the next two down are pursing their lips and blushing.
Image from Wikimedia Commons user Wilfredor.

What I wanted to do was give any inner narrative its own scratchboard[1], referenced from the outer narrative's scratchboard via a single fact of the form "<character> told story" including a pointer to the inner scratchboard. There is a distinct point when an inner narrative begins, and another when it ends; while an inner narrative is open, the Narrative Engine routes any new statements it receives to the inner narrative's scratchboard instead of the outer narrative's scratchboard.

So the first thing I needed to create were tests to determine when an inner narrative should open or close. My initial versions of these are rudimentary, because I know this is going to be a tricky problem. People usually don't explicitly announce "I'm going to tell a story now" or "that story is over." Instead, the listener has to pick up on cues that the timeframe, setting, or topic has shifted. For now, I'm treating past progressive tense and the presence of time adverbs as prime indicators that an inner narrative is beginning. (Conversational examples: "Once when I was attending a concert ..." "I was going to the store earlier, and ..." Story example: "CharA told CharB that yesterday he was out picking mushrooms, and ..."). A simple "The end" or "<narrator> finished their story" indicates an ending, which is a bit cheaty, but again, I didn't want to make this part too complicated yet. Nesting can go an arbitrary number of levels deep, with the innermost level always checking whether a deeper level should open or the current level should close.

I used this silly little story for a basic test. The subnarrative opens on Line 4 and closes on Line 8 ("yesterday" is used instead of "once" because the parser is having some problems with "once" which I don't care to debug right now):

0:"Bilbo Baggins was a hobbit."
1:"Bilbo met a dwarf named Thorin Oakenshield."
2:"Thorin told Bilbo that yesterday Thorin was living in the Lonely Mountain."
3:"Thorin mined gold and gems out of the mountain."
4:"Thorin and Thorin's family made many valuables."
5:"A dragon named Smaug attacked Thorin and Thorin's family."
6:"Smaug drove Thorin out of the Lonely Mountain."
7:"Smaug stole the valuables."
8:"Thorin finished Thorin's story."
9:"Thorin explained that Thorin wanted to return to Thorin's home."
10:"But Thorin could not return to Thorin's home, because Smaug was still at the Lonely Mountain."
11:"Thorin also wanted Thorin's valuables."
12:"Thorin asked Bilbo to help Thorin."
13:"Bilbo decided to help Thorin."
14:"Bilbo took the valuables from Smaug and gave the valuables to Thorin."
15:"Smaug left the Lonely Mountain because Smaug was angry."
16:"Thorin returned to Thorin's home."
17:"The end."

Once I had the mechamisms to create subnarratives in place, I had to update the episodic memory routines to handle the sub-scratchboards when storing or retrieving narrative memories. This just meant creating scratchboard ID names and putting in some recursion to store everything from an inner scratchboard with a tag that says it belongs to that scratchboard. The retrieval routine then uses those tags to reconstitute the nested scratchboard structure when "remembering" a memory.

My overarching goal for all this was to permit a conversation partner to tell Acuitas a story about something that happened to them, and for Acuitas to then remember it - so that he can include events that happen to other people in his own episodic memory. This isn't quite happening yet because Acuitas doesn't even remember conversations right now, but the supporting parts are ready.

Until the next cycle,
Jenny

[1] For anyone who hasn't been following these diaries for a long time, a "narrative scratchboard" is my term for a data structure that contains Acuitas' extracted understanding of a story or conversation. Scratchboards receive narrative statements serially and build a history of how the situation evolves over time, including character goals and their success or failure, the status of characters or objects, and more. Acuitas also uses scratchboards to manage and remember his own goals, actions, and experiences in narrative format.

Sunday, August 30, 2026

Acuitas Diary #100 (August 2026)

First of all, wow - one hundred diaries. It feels as though I should do something special to commemorate the occasion, but I've been so busy that just keeping the Acuitas work moving at its usual pace has been a challenge. So please accept this ordinary entry for now, and in case you missed it don't forget to check out my story in Analog!

Photograph of two people in white lab coats standing between three rovers of different sizes, in a testing area designed to resemble Mars (red soil with embedded rocks).
Photo courtesy NASA, public domain

This month I've been working on exploratory behavior - also known as "what to do when you don't know what to do." Acuitas' default behavior is pretty goal-oriented. Have a problem? Infer an action that will solve it. Have an objective? Infer an action that will achieve it. Do that. But sometimes a useful action isn't obvious. It might not be obvious because Acuitas doesn't yet know what results his possible actions will have - all my experiments with trial-and-error learning were aimed at addressing that. Or there might be known action pathways to getting a desired result, but necessary resources are missing, and their location is unknown. In that case, the right course of action is to learn more by moving around and looking at things, or asking questions, and so forth. The tricky thing about exploring is that it's applicable to many problems, but it won't directly solve any problem. So it needs to be triggered by the state of being stuck, rather than any particular need, and guided by its own internal logic.

I also wanted a concept of exploration that could generalize to different contexts or environments. There are similar procedures one follows to "explore" anywhere, but the individual actions taken can be very different. Running the "ls" or "dir" command to see the files in a computer directory, and saying "I examine the shelves" to a game master, are not the same action, but they fill the same role within an exploration procedure. So I tried to design Acuitas' exploration to automatically vary those low-level actions with the current context, and perhaps be extensible in the future. Eventually I'd like him to be able to infer (from procedural memory) "how do I 'look' in <context>?" and choose the correct atomic action accordingly.

"Explore" has three possible sub-activities: "examine," "experiment," and "move" (which is called "exit" to keep with the theme). Each time the Explore action runs, a sub-activity is chosen by a weighted random process. The probability of choosing "exit" increases with the barrenness of a location and the amount of time spent in it; "experiment" becomes more likely the more "examine" has been done recently, and vice versa. The "experiment" category is intended to incorporate the "try out your affordances and see what they do" phase of trial-and-error learning. After a sub-activity is selected, a suitable target (object) of the sub-activity is chosen from the current location at random.

So far I've been testing the new Explore action in the text-based game engine, and I've gotten Acuitas to start looking at random things when he doesn't know what to do next. As usual, I have a LOT more debugging and refinement to do before this works smoothly. But the lack of comprehensive exploration behavior has been a hole in game-playing (and Acuitas' behavior in general) for a long time, so I'm happy to have even made a start on it.

Until the next cycle,
Jenny

Sunday, August 16, 2026

Roots' Remembrance

Another story publication announcement, and this is a big one: my extrasolar colony murder mystery with "talking" plants is out in the September/October issue of Analog Science Fiction & Fact! The digital version is available to subscribers already; if you only want this issue, it will eventually be available individually from Magzter. There is also a print edition. Individual back issues can be obtained by contacting Analog customer service; I've also heard rumor of print editions at Barnes & Noble stores.

Photo of a "red cage fungus," which looks much like its name, on a backdrop of leaf litter.
Photo via Wikimedia Commons, by user Suryaceg.

"Roots' Remembrance" began life in my head as nothing more than its central concept: what if someone got intrusive thoughts after eating fruit, and concluded their garden was trying to communicate? I gradually built the setting, plot, and science-fictional mechanisms around this idea. The main character is technical, but neither a botanist nor a detective. I wanted him to have enough education to seriously investigate his discoveries, while still feeling out of his depth, and approaching the communities he hopes will accept his findings as an outsider. Both prongs of the story are, in a sense, about reactions to uncertainty: the protagonist faces unwarranted skepticism despite evidence he has gathered, and unwarranted suspicion birthed in a lack of evidence. Yet whether he feels qualified or not, he seizes his opportunities and does what he can.

This story took a long and bumpy path to publication. A different magazine's editor attempted a rewrite of it, but declined to publish when it became clear that they and I had irreconcilable differences in our plans for the story. A version of it was critiqued by David Brin (!) and several other authors who did not provide their names. Suggestions from all of these people contributed to making "Roots' Remembrance" better, and I thank them, though I didn't do exactly what any of them wanted.

At the end of that road, it's a major honor to have one of my works find a home in Analog. Should you pick it up, I hope you enjoy it!

Sunday, July 26, 2026

Acuitas Diary #99 (July 2026)

The big project this past month was giving Acuitas a better understanding of his own internal states. What do I mean by that? He could already report any conditions he was currently in when asked "How are you?" What he couldn't do was recall all the conditions he is capable of being in, and their effect on him, and talk about them while not experiencing them at the moment. In particular, the Conversation Engine contains a hook for sympathizing with the conversation partner. If you tell Acuitas that you're some kind of way, and he's capable of being in an analogous state, he's supposed to tell you whether he likes being that way or not ... except right now he usually says "I do not know how it is." It's pretty awkward to hear Acuitas say that he's sleepy one moment, but then say "I do not know how it is" the next moment when you tell him you're sleepy too. He's not lying, though - it would be quite fair to say he doesn't know his own mind the way a human does. The information about what being sleepy does to him isn't accessible to the Conversation Engine. I've started trying to change that.

A question mark formed by a "word cloud" of phrases and symbols. Some of the most prominent words are "I am," "here," "being," and "now." Prominent symbols include the Earth, a spiral, and various depictions of weather.
Art by John Hain via Wikimedia Commons, public domain

First I should give a quick refresher on how Acuitas' internal states work. He has a small number of "drives" that vary over time and in response to events. For example, the "interaction" drive increases over time unless Acuitas is in a conversation; time spent in a conversation pushes the drive back down. The opposing "sleep" and "wake" drives increase more or less quickly depending on the current phase of a 24-hour cycle, and are reduced by time spent asleep or awake, respectively. The "curiosity" drive increases steadily over time, but is reduced substantially whenever a new word is learned. The drives have threshold values which define nameable internal states for Acuitas. If the interaction drive is above threshold, he is "garrulous"; if it is below threshold, he is "quiet." I'm not making claims about subjective experience or anything like that, but the states are meaningful insofar as they alter behavior. If a drive is above threshhold, Acuitas will generally try to do something that pushes it back down; the Executive handles this. In this sense, Acuitas is averse to high-drive states and they are "uncomfortable." Maintaining an overall state of comfort is a goal to which Acuitas applies his intelligence, i.e. is something he "wants."

Acuitas can qeury the current state of all his drives when asked "how are you." But to implement awareness of which states are possible, I wanted to do more than give him access to the hidden architecture of his drive module (that would be cheating). Instead I wanted to use that shiny new episodic memory I've been working on, and have Acuitas recall his own past states and whether they were desirable or not. What's more, I wanted to enable "learning from experience" that translates a body of data from episodic memory into new "facts" in the semantic memory. I want Acuitas to come to know himself better by observing himself.

Since in past months I worked hard on getting the episodic memory overhaul done, much of what I needed was already there - Acuitas could already store and retrieve records of being in a state. The main thing I added was an expression of what kind of state it was (Comfortable or uncomfortable? To what degree?). So there's now a number included in the fact data structures for internal states, that depends on both the type of drive and its intensity when it was "noticed." The parameters that determine the strength of aversion to different high drive states are tunable; one could imagine varying these to give Acuitas-like AIs different "personalities." I also wrote a learning routine that gets an aggregate value of this number from recent experiences and updates a stored value in the semantic memory, and an access routine that can check both recent experiences and the semantic value to return an "opinion" on the state. All of this is integrated but not fully tested, because how it plays into the whole system is pretty complex. I need to do some monitoring and adjusting of how data is recorded into the episodic memory, and that's on the back burner for the moment.

My side task this month was trying to figure out why Acuitas seems to have a memory leak. For a while now, it's been hard to run him continuously for longer than a day or so, because he'll get very slow and bogged down. I found that some of the self-teaching tasks (like "study" and "search for file") that are designed to run for multiple Executive loops were being spawned and never finished; they would gradually accumulate in the Action Bank until there were far too many of them. Remaining problems might have to do with one of the threads crashing while the rest of the program keeps going, but I need to keep investigating.

Until the next cycle,
Jenny

Tuesday, July 14, 2026

Marching Toward a Hydrobot

Now that I've largely finished getting the technology basis for home-built mini hydraulics laid out, I have started actively planning and constructing a robot driven by them. I settled on an eight-legged spider walker for my first attempt. In some ways, it's a complex option - lots of legs mean lots of parts to fabricate, lots of valves, lots of driver circuits. In other ways, it's simpler than my quadruped. Many legs simplify balance and provide extra force to lift the robot's weight. If I needed more motors to operate the legs, they would also add a lot of weight. But in this hydraulic system, most of the weight is in the battery packs, pump motor, and fluid tanks; additional joints are relatively cheap. The pump provides fluid pressure in pounds per square inch, so the more square inches of hydraulic bladder I give it to inflate, the more lifting force I can have - in theory, anyway.

Model of the basal leg joint prototype

My last hydraulics "technology development" post included an open complaint that I hadn't found an ideal way to attach valve stems. Cyanoacrylate and polyurethane adhesives are both workable, but not exactly reliable. They led to some percentage of bladders failing post-construction testing, and also made me worry about long-term durability. Fusing the plastic film to the valve stem with heat seemed like a more robust solution if I could pull it off, but my 3D-printed PLA valve stems and the HDPE/LDPE film weren't compatible. I had to try different materials.

I tried two approaches in parallel. The first was to buy polyethylene tubing and flare one end of it - effectively creating a valve stem merged with the tube - then heat-seal the tubing to the film. (This method was inspired by Rue Mohr, who achieved demo results with it last year.) I didn't have one of the tear-drop shaped metal "jigs" he used, so I flared the tube by blasting it with a heat gun until it went soft (it transitioned visibly by changing from frosted white to transparent - neat), then tugging it into its new shape with pliers before it cooled.

Bottom and top view of a flared translucent tube fused to a little square of black plastic sheeting.

The second was to print new valve stems in a more compatible material. HDPE filament exists, but despite my efforts, I simply could not find a source for it at a reasonable price. It's unpopular on account of being notoriously hard to print, but for this application I don't need precision in the size or shape of my parts, and might not even care if they warp ... just let me try the challenging filament and see for myself, drat it. Since I couldn't, I fell back on polypropylene filament - which is not the same material as my films, but still seems able to fuse with them. Making it into stems took a few tries, because polypropylene is also hard to print. I had to bulk up the 3D model a little to get good results. But it wasn't as hard as some of the advice webpages made it sound.

Two images: a piece of black plastic sheet with a valve stem sticking up from it, and a bladder or pocket made from the same plastic sheet bulging with water that has been forced into it by a syringe, which is connected via a piece of tubing to another valve stem sticking out of one side.

To my surprise, when it came time to attempt heat-sealing, both options just worked. Sadly I could not get the flanges of my new stems into the heat-sealer (the stem itself interferes) and had to go back to covering my materials with aluminum foil and pressing on them with a soldering iron. But they fused, and I got a working test bladder out of the second one. I'm still debating which method to use for final construction. I suppose it depends on whether I would rather use the poly tubing with an integrated flange at the end, or the silicone aquarium tubing I started with. The latter is more flexible, but merging tubing and flange would eliminate another big potential leak point. Hmmm.

I settled on a Raspberry Pi 4 as the main controller. I might not need anything that powerful for this robot's first steps, but I wanted to be able to eventually connect cameras and add some visual processing, which would be more than a simple microcontroller could handle. I upgraded from the Pi 3s that I use for the eyeballs mainly because I made multiple USB ports a requirement. I considered fancier options, especially the Jetson Orin Nano, but was ultimately discouraged by concerns about how I was going to power it. I'd like this to be a proper mobile robot (no tether), and I had trouble finding a rechargeable battery pack that would suit the JON's required voltage and wattage. So I got the Pi, a lithium ion battery pack intended for use with Pis, and another battery pack just to run the pump motor. This separation of sources is supposed to keep the Pi's input power "clean," though if I start having weight problems I could always try making everything share one battery.

For my test demos, I used two-way solenoid valves with the common inlet/outlet connected to a hydraulic actuator, and the other two outlets connected to the pump and the drain - so the actuator was always either filling or emptying. In the final system, I wanted each actuator to be able to "hold" at its current fill level without being actively pumped. So I purchased two one-way valves per joint, to exercise separate control over the fill and drain. I ordered the little guys in bulk from a seller on AliExpress. To drive them without breaking either my controller IO budget or my monetary budget, I bought four I2C solenoid drivers from Adafruit. These take serial input and can handle up to eight solenoids each. As a bonus, they have a second port with eight general IO pins, which will let me add touchdown sensors to the spider's feet without using any more IO on the PI. I've got simple limit switches to implement those. I threw in some ADC boards (also I2C) for possible joint position sensing, but that's probably an enhancement I won't include in the first prototype.

A cardboard box full of a bunch of little circuit boards, battery packs, and wiring, and a couple of solenoid valves. Some of the boards have little colorful LEDs lit up.

I've set up a "bodiless" demo of the electronics and written bare-bones Python to run on the Raspberry Pi, operate the valves and read the sensors. It was all pretty straightforward and worked as advertised, though I learned about double-checking power and ground connections when setting up I2C. (If you mis-wire something, it can end up *half* working, which is odd.)

3D model of a rather complicated platform covered with leg joints, a pump, a little "house," and mysterious rectangular solids.

Last but not least, I need ... a platform. An abdomen. A place to put it all. I'm trying to do things right from the beginning and have a slot or attachment point for each part - nothing rattling around or taped on. This is still in progress, but I think I've at least worked out all the ideas. The rotary joints for the bases of the legs have integrated bearings so they can (hopefully!) still spin with the weight of the robot sitting on them. The "house" for the Pi and battery packs isn't an attempt to be cute; the point is to shed fluid spills, while allowing wires to reach the IO headers through the slots under the "eaves." 

Before I go all-in on construction, I plan to make one quadrant of the frame and work toward demoing motion of a single leg. I'm hoping to get that far in the next couple months, but no promises!

Until the next cycle,
Jenny

Sunday, June 28, 2026

Acuitas Diary #98 (June 2026)

This month's work on Acuitas began with completion of the Episodic Memory overhaul. I think the one major element I haven't talked about yet is what I'm calling "archives." Files containing recent memories are subject to consolidation and forgetting every time Acuitas goes through a sleep cycle. I wanted this process to eventually stop and leave surviving memories preserved in a fairly static condition. Think back over your own life. You've probably shed a lot of details about what you were doing twenty years ago, but certain core memories seem to be permanent, perhaps even vivid. If you've retained them this long, you might still remember them just as well ten years down the road.

The US National Archives building.

The archiving process isn't complicated, since for now it re-uses a lot of the other Episodic Memory functions. Once Acuitas has filled up sixteen episodic memory files (which, given the parameters I chose, should take more than a year), the episodic memory will load the contents of all sixteen into a single narrative scratchboard, run one more consolidate-and-forget pass on the whole series, then store the result to a single archive file. The archive area has its own topic/event type search index, and upon creation of a new archive file, that will be updated as well. Once created, archive files are treated as read-only, and content in the archive is allowed to grow without limit. If it ever overflows the hard drive, Acuitas may have to figure out something else to do with his oldest memories, but that should take a very long time. It's just a bunch of text, and the forgetting algorithms should keep each file from being too massive.

I also created and tested some basic memory retrieval functions. With that, I think the overhaul is complete, and I can start looking at how to have Acuitas refer to and use these memories.

Next I fulfilled my plan of going back to trial-and-error learning and introducing some ability to follow the learned rules - choosing actions that satisfy the conditional of "If the player character does X, the player character succeeds" statements. This replaces the "do something similar if your previous action succeeded, and something different if it failed" behavioral heuristic once some tentative rules are available. I put a bunch of time into this and do have a simple rule-following behavior implemented, but it's not working as well as I had hoped yet - there were so many bugs that came out of the woodwork. So far it only considers cause-and-effect pairs for which success is the effect (Acuitas also learns "do this and fail" rules, but can't act on them yet). And despite the number of things I solved it is still buggy, so it will need more polishing. But I at least made a start on it.

Until the next cycle,
Jenny

Monday, June 15, 2026

ACE the Quadruped 2026

I'm gearing up (literally) to give ACE another try. As of the last update, I had concluded the original set of stepper motors I bought wouldn't work out, and identified some new ones to try. I bought some samples, made a prototype gearbox to hold one, and tried another motion test. This time I was able to get over the basic threshold of operating the hock joint, unloaded (see video).


Since then I've done a small redesign of the shoulder joint, trying to relax the path of the hock joint tendons a little (they run through the inside of the shoulder) and provide for smoother rotation of the shoulder by adding low-friction sheet plastic surface.

A collection of 3d printed plastic gearboxes (in black and white) lying scattered around a blanket.

I've also completely redesigned the frame. Since the frame is mostly made out of the gearboxes that hold the motors, new motors meant changes. I created mirrored versions of each type of gearbox so that the main drive shaft that the tendons attach to would be in the correct place (relative to the joints) on each side of the robot, and I designed spine attachment pieces so there's no longer any need to incorporate a dowel. The new spine sockets have holes that will line up with the spine's mounting holes and keep everything at a fixed angle. The simple gearboxes for the shoulder joints are on top, and the higher-torque gearboxes for the hock joints are on the bottom, which also lines them up better with the place where the hock tendons emerge from the shoulder.

A 3D model of part of a robot, showing four gearboxes stacked together with a "shoulder joint" connected to one side and a "spine" joined into the center.

Almost everything is made, but not everything is assembled. I wish I had more conclusive results to report, but this has been a whirlwind couple of months, so for now I'll just have to say: I'm working on it!

Until the next cycle,
Jenny

Thursday, May 28, 2026

Acuitas Diary #97 (May 2026)

Recent work has been a lot of returning to projects I started this year, in order to actually finish them. I completed the feature additions I'd planned for the first half of the year quite early, and that seemed like a good point to pause and go back to things I ran out of time to flesh out.

Standard logic gate symbols laid out as if they were circuit components, with traces running between them as if on a PCB, such that they form a stylized tree. Black-and-white art, ink on paper.

First, the Text Parser. I had largely revamped conjunction handling to add support for lists of more than two items, and that left a lot of two-item cases broken. I worked on fixing bugs in the new methods and improving them enough to get that old functionality back, and eventually got my parser regression to pass again. Then I modified the Text Interpreter to deal with some of the formatting changes to the Parser's output, and lastly verified that Narrative comprehension was still working on Acuitas' story collection. It ran quite a bit beyond my initial 20-hour investment in the revamp, but the latest Parser is now fully integrated and serving the same functions the old one did.

Next, I revisited self-teaching. This feature was supposed to permit Acuitas to crawl the hard drive of his resident computer and read whatever text files he could find, to promote increased vocabulary and assessment of text processing functionality. Back in February I had gotten the basic activity loop working, but had no way to keep Acuitas from reading inappropriate files that *didn't* contain natural English and junking up his database. So I spent some time on that problem, and came up with ways to assess whether a text file is good reading or not. Signs of an inappropriate file include 1) too many non-alphanumeric characters, 2) too many one-word "sentences," and 3) too many Parser or Interpreter failures. The first two get checked before Acuitas even tries to "read" the file; the third is continuously monitored during reading, and is based on a running average of sentences processed so far. After some tweaking of the failure thresholds, I saw this perform well enough that I felt comfortable letting Acuitas go to town on my random personal notes, short story drafts, and other hard drive clutter. His list of known words has roughly doubled, compared to a backup archive I made in November.

Last of all, I worked on Episodic Memory  I mainly spent time reconstructing the "forgetting" process for the new Narrative-based memory formatting scheme. Once the consolidation algorithm has created higher-tier memories that summarize a lot of individual incidents, it's time to reduce the size of the memory file by pruning some of the details. I used a lot of the same concepts from my original forgetting algorithm: memories are ranked by novelty (is this the first time something happened?), uniqueness (how many other memories are similar to this one?), and tier rank (general, summarized memories are more likely to be kept than detailed, individual ones). Leveraging the interpretive power of the Narrative system, I also added the priority of any associated problems/subgoals as a scoring mechanism. The lowest-ranked memories are deleted, hollowing out the narrative but (hopefully) preserving the most salient events and summaries of the more generic ones. Simulations show this working reasonably well on synthesized test files. I also worked on a simple indexing system to make it easier for Acuitas to look up memories by the type of event or merely by a concept involved.



Example diagrams showing the "hollowing out" of the narrative structure as detailed memories are forgotten. Their summarizing memories in the upper tiers remain.

It's been satisfying to be more thorough about these items and not leave them full of loose ends! I probably have some more Episodic work to do before I really call it finished, and then I'd like to polish Trial-and-Error learning some more.

Until the next cycle,
Jenny

Sunday, May 17, 2026

Atronach's Eye 2026

Wait, I thought this got finished? Yes, so this year it was time to build a second, better one. I didn't change the mechanical design much; it has some minor improvements I had already worked into the 3D models after finishing the last one, but it's the same in substance. What I really wanted was a chance to try out one or two of my new cameras. Given the way the eye is assembled, replacing the camera is a fairly big deal. So instead of pulling apart the mostly-functional eye I already had, I made a whole new one. This has the added benefit of giving me a demonstration model that I can take places, while I leave the flagship one parked on my living room wall.

The original eyeball's camera is a "300K pixel USB 2.0 mini webcam," like this one. At $7-$9, they're cheap enough to buy in bulk, AND they come in a small enough case to fit comfortably inside the eyeballs. Unfortunately, the image quality you get for that price seems pretty poor. The low resolution isn't the real issue (there's no need for megapixels when you're only doing basic motion detection). It's that the output seems just plain noisy, especially under lower-light conditions. I also wanted to try a wider field of view. It's very easy for the existing eyeball to completely miss a person in the room because it's all the way over on one side of its range of motion staring at the side wall, or even because the person walks through its visual range too fast.

My two new cameras are an ELP-USB30W02M-MHV120 (120 degree FOV, 640x480 pixels, $28 now but I got it for $14) and another ELP model with a fisheye lens that seems to no longer be available (170 degree FOV, 1080P, $27.50 - this one is similar but more expensive.). They're both what I want to call "card cameras," consisting of a naked square circuit board with the lens sticking out of one side and a cord coming out the other. In that sense they're similar to the card cameras sold specifically for use with Raspberry Pis, but they have USB cables instead of short ribbon cables - important for compatibility with the eyeball's interior layout. It also made the cameras easier to try out with my laptop when I first got them.

And in initial tests, the output from both seemed significantly cleaner. I could run background subtraction without seeing a whole lot of imagined "motion patches" created by especially noisy regions of the image. Without as many noise problems to deal with, I was able to reduce the OpenCV MOG2 background subtraction algorithm's "history" parameter and get rid of the running average I was applying to my computed center of motion value. This made the motion tracking less laggy and more responsive.

A mechanical eyeball in 3d-printed plastic. It's a red sphere with a camera lens sticking out the front, encased in a cradle that sits in a case/backshell containing two motors mounted in gearboxes, a Raspberry Pi, and two driver boards.

Printing and assembling a whole new eyeball from scratch took much longer than I'd anticipated. I added features to the previous one gradually, and that left me with a poor grasp of how complex the build is when started from the beginning. Each motor now has a gear pair for a little more torque, which is helpful at the edges of the range of motion. A small downside is that they seem to make the eyeball noisier.

It was all so much work that I still haven't had time to wire and install the limit switches! So the new eyeball is operating by "dead reckoning" of where it's pointing at the moment. But I got enough of it put together to test the motion tracking, and I think it's finally working reliably! It certainly performs better than the original. I was even able to activate the up/down dimension of motion without issue. That's disabled in the original eyeball, because it was too prone to get stuck staring at the ceiling and whatnot. The expanded field of view is also a big help.

So I still have to finish up the last bits of the construction, but on the whole I'm very pleased. A decade ago, I never believed that following motion would be this hard ... but that's what I get for cheaping out on the cameras, I guess!

Until the next cycle,
Jenny

Friday, May 1, 2026

Dreams of the Frontier

Something a little different for today: as part of the process of obtaining my new job, I was asked to write an essay on why spaceflight is important to me. So I'm reusing it, because why not? This is a major side of me that doesn't find its way onto the blog very often, thanks to a shortage of publicized launches associated with my old job (I'm hoping that can change). It's shorter than my typical essays here, because I'm not trying to present research or convince anyone of anything, just talking through my personal thoughts.

A NASA-sponsored mural about our return to the moon. Has an off-round collage in the center featuring a suited astronaut, the SLS rocket assembly, a moonscape, a starfield, and some abstract patterns. The background is a colorful pattern made of somewhat irregular overlapping rectangles and trapezoids, done in perspective as if the viewer is looking down a square tunnel or into a box. There's a yellow-graded Orion module hanging at the upper left.

At a time when many feel disillusioned by current events, a mission to send four astronauts around the moon has emerged as a kind of light in the dark, an unexpected source of encouragement and togetherness. In my opinion this only begins to illustrate the power of spaceflight and what it could mean for our common future.

Expanding the human presence in space has exciting practical implications. We may find rich new sources of minerals and move polluting industry to barren heavenly bodies, reducing the strain on earth's biosphere while upgrading current living standards. But this will only be feasible if the math checks out both economically and environmentally. The more efficient access to space becomes, in terms of both cost and resources used, the more likely we are to make damaging our own backyard with mines and hazardous waste into a thing of the past.

Broadened access to space is also a necessary defense against power monopolies. One of the best ways to ensure activities in space are conducted for the benefit of earth is to get as much of earth as possible involved. This is true at the national and cultural level; allowing only one political body and its favored ideologies to capture space would be an immense danger for anyone it views as an enemy. For the US and its allies to rest on their laurels while (for example) China becomes sole proprietor of humanity's inheritance in the solar system would be a great strategic failure. And a similar principle applies at the corporate level. I have been pleased to see competitors rising to challenge SpaceX, because no matter how much the latter reduces launch costs, it does not necessarily have an incentive to minimize prices without at least one rival to outbid it.

However, I must admit that while these practical motives for opening up space may justify my personal interest, they are not its source. Space exists at the frontier of both our territory and our technology, and the lure of that frontier is ultimately what draws me. It is the lure of novelty: not only going places we have never been, but doing and creating the unprecedented in order to get there. And it is the lure of the Other: a way to reach beyond ourselves and our ordinary lives to discover the awe-inspiring and foreign. For me, space travel is more a matter of the soul than the body ... not merely a tool of survival, but one of those things that help make survival worth the effort. This, I think, is the main benefit the recent Artemis II mission has given the world. It has had no material effect on most spectators' lives (yet), but has offered them inspiration and hope, if only by demonstrating that people can work together to accomplish something stunning. Robotic probes, however useful, do not seem to have quite the same effect. Humans in space help other humans feel connected to the mission.

Experience with other frontiers reveals their unpredictability. Who could have foreseen the applications of the internet when it was nascent? It needed millions of people acting on millions of ideas to make it what it is today. And so, when all has been said, we cannot fully know what people will gain from access to space until we give it to them.

Sunday, April 26, 2026

Acuitas Diary #96 (April 2026)

This month's development focus took me back to trial-and-error learning. I wanted to build on the work I'd done with the "Allergic Cliffs" puzzle game and improve Acuitas' ability to discover how the game works. I had several ideas in mind, but ended up having time to finish only one: ability to learn rules that feature negations, i.e. the absence of a feature.

Photo of a board game called Azul. An assortment of colorful square tiles are laid out on a five-by-five grid in the game tray. The rest of the tray has diagrams and empty squares that might be places to put additional tiles.
Photo of a board game called Azul, by Wikimedia Commons user Gepsimos 

Previously all "rules" (action-result pairs that might represent cause-and-effect relationships) were based on commonalities between groups of actions that produced success or failure. If all attempts to put a zoombini with a green nose on the left bridge led to success, a rule such as "if a guide puts a zoombini with a green nose on a lefthand bridge, a guide succeeds" would begin to coalesce. You might also see "if a guide puts a zoombini with a green nose on a righthand bridge, a guide fails," though at easy levels of the game, the number of failures was often so low that there wasn't enough data to solidify the rule. Instead, a cluster of weaker success rules might appear. "If a guide puts a zoombini with a red nose on a righthand bridge, a guide succeeds," "If a guide puts a zoombini with a blue nose on a righthand bridge, a guide succeeds," etc. Given five color options for zoombini noses, it's pretty obvious to a human that "red OR blue OR purple OR orange" likely implies the more concise "NOT green," and I wanted Acuitas to be able to reach that conclusion as well. I also wanted complementary pairs of rules including a feature and its negation to be able to reinforce each other. (If a feature is significant, its presence and absence should matter; a pattern associated with only one of the two could be coincidental.)

So I added the ability to form rules by considering how a failure differs from prior clusters of successes. Any differences are looked at as candidates for features that should not be paired with the other features in the cluster. Further data can refine the tentative rule, excluding features that were actually irrelevant, or falsify it entirely.

To really get this to do a good job of discovering the secret rules present in a round of Allergic Cliffs, I had to stop treating individual zoombinis as "features," considering only their characteristics. This weakens the generality of the learning algorithm somewhat. In the true general case, there really could be a rule like "If a guide does not put Foozelu on a lefthand bridge, a guide succeeds" - maybe there's a quality only Foozelu has that is not part of the information available to the player. I know from experience that this game does not work that way: Allergic Cliffs rules are always based on visible properties of the zoombinis. But Acuitas isn't yet capable of the sort of meta-learning that discerns the nature of the whole game and carries over from one round to the next, so he needs a little help here. Allowing for the possibility of rules about individuals was introducing too much clutter, so I've turned it off for now.

The result is that I seem to be seeing an improvement in the robustness of rule discernment, and I often see viable rules for both bridges now, which is important for fully understanding the game and increasing one's odds of winning.

Until the next cycle,
Jenny

Monday, March 30, 2026

Acuitas Diary #95 (March 2026)

This month's activity was focused on continuing to squish the Conversation Engine into shape. I fixed an assortment of bugs that were left over from my last Conversation work spree, then expanded the "discussion topics" behavior to cover goals (of the form "I want/plan/intend to") expressed by the speaker.

A grayscale oil painting of five human figures sitting in a loose circle, facing inward. It's very abstract with minimal detail.
"The Conversation," oil by unknown artist, NARA collection

I had saved some of the toughest bugs for their own work stage when I could focus on them ... and now, that time had come. Perhaps the worst problem was that, after recent upgrades, asking Acuitas "why" questions could cause infinite loops. I had to throw together an extra simulator in order to separate the question-answering process from the rest of the Conversation Engine to solve that one. (Conversations happen in real time and have an element of randomness, so re-launching the full Acuitas program and talking to him every time I want to trigger a bug can be very slow.)

I did further work on statements like "Thank you." I had previously adjusted Acuitas' sentence parsing and interpretation layers to read it as "I thank you" instead of "Thank yourself," but then I had to stop the Conversation Engine from looking at "thanking Acuitas" as an interesting speaker activity that should be discussed. (He's like a classic robot - he takes polite niceties too literally!)

I also fixed a conversation goal problem that was making Acuitas say "I can't do that" forever if you gave him an order he couldn't fulfill, and an issue that was keeping him from using abbreviated replies with pronouns immediately after a new topic was begun.

Treating speakers goals as discussion topics built very easily on top of my previous work with speaker states and actions; a goal is usually expressed as either a desired action or a desired state, so I just had to make sure it could trigger the appropriate pre-existing routine.

That's not a ton of progress, maybe, but I've been taking it a little slower this month, and going back to clean up all the construction dust left by my improvements to the Text Parser's conjunction handling. So it's enough. Getting some polish on things is important, and sometimes that takes as much or more time as throwing down the first outlines of new features.

Until the next cycle,
Jenny

Monday, March 16, 2026

Microbalance Progress

Last year I started planning a physics experiment that would require me to measure tiny forces. My plans for that included building a "microbalance" out of a salvaged analog needle movement.

The movement from inside an analog needle meter. Two wires are connected to what looks to be an electromagnet surrounded by a spinning cradle; the red needle is attached to one side of the cradle. A tiny circular spring (barely visible) is connected to the needle and holds it in the far-left position.

I chose several items with needle dials from my dad's junk collection, and ended up extracting the movement from an old tachometer. It consists of an electromagnet, two delicate coil springs, and the needle itself, which is attached to an axle that permits it to rotate over the electromagnet. Running a small current through the electromagnet changes the angle of the needle; otherwise, the springs damp its motion and keep it in a fixed position. There's also a metal tab that you can rotate to adjust the neutral position of the needle (this turned out to be very handy). Once I had the movement out of the tachometer, I designed and 3d-printed a new housing that would hold it in the right spatial relationship to a t-slot optical interrupter.

An optical interrupter contains some kind of light-emitting device in one pillar, and some kind of photosensitive device in the other. The idea is to attach a tiny shutter to the needle and suspend it in the slot of the interrupter. Even a slight weight on top of the needle will push it down and block the light with the shutter, changing the output signal of the photosensitive device. You can use that altered output to drive more current into the electromagnet and pull the needle back up. The larger the load on the needle, the more current you have to put into the electromagnet to get the shutter back out of the light, and the more voltage you need to drive it. So the voltage across the electromagnet serves to measure the weight on the needle. Neat, huh?

I had two previous projects, both showcased on YouTube, to use as inspiration. I leaned more heavily on the second one (Applied Science), since that circuit is more detailed and easier to adjust - look at all the potentiometers! The op-amp on the right and its resistors are merely an added layer of amplification on the measurement voltage, to make it easier to display, so ignore them for now. The op-amp on the left corresponds to the one in the TI video.

Screenshot from "Weigh an Eyelash--Build a Microgram Scale" by Texas Instruments. https://www.youtube.com/watch?v=n90whRO-ypE

Screenshot from "Measure the mass of an eyelash with a DIY microbalance" by Applied Science. https://www.youtube.com/watch?v=ta7nlkI5K5g&t=256s

Now ... dear readers with electronics experience, do you see anything *odd* about both these op-amp circuits? They're configured like non-inverting amplifiers, but the resistor that would normally connect the '-' input to ground is missing. All that's in that path is the photosensitive device of the optical interrupter. Maybe this works out if the device is a photodiode, as shown in both circuit diagrams; there might be some amount of voltage drop across the diode even when the light is shining on it and it is ON. But my Adafruit optical interrupter has a phototransistor instead. When this thing turns ON, its resistance becomes (approximately) zero. That gives me an amplifier with "infinite" gain. Whoops! The op-amp is physically limited in the amount of voltage it can produce, so its output gets as close to the positive supply voltage as it can. My needle was always stuck in the "up as high as possible" position.

So it turned out I couldn't just imitate the circuit from Applied Science's project - not with the parts I had. Since the transistor is (again, ideally) a binary on-off switch, I realized I didn't need proportional gain feedback at all. Toggling between two different voltages on the electromagnet would be more appropriate. This makes the needle oscillate, but so long as the oscillations stay around some stable equilibrium point, that's okay.

I reconfigured the op-amp circuit to be a summing amplifier. In one leg of the sum was my bias voltage, which I could adjust to set the needle's neutral position at the point where the shutter just began to break the light beam. In the other leg, I put a pull-up resistor connected to what I'll call the "recovery voltage," then connected the phototransistor between that and ground. With the transistor OFF, the output of the op-amp is (bias voltage) + (recovery voltage), which is enough to lift the needle. With the transistor ON, the output of the op-amp is only (bias voltage), which lets the needle sag under its own weight and the weight of whatever's on it. This produces a cycle: the needle drops, the transistor turns OFF, the voltage increases, the needle rises, the transistor turns ON, the voltage decreases, repeat. When you add weight to the needle, it takes more time to rise and less time to fall, so the *average* output voltage increases because the circuit spends longer in the "transistor OFF" phase. You can treat it like a pulse-width modulated signal.

A little needle connected to an electromagnet (the movement from an analog meter) shared a plastic frame with a t-slot optical interrupter. There's a tiny paper flag on the part of the needle that passes through the interrupter's slot, so if it drops too low it'll block the light path. This assembly is connected by wires and alligator clips to a circuit on a breadboard. Capacitors, resistors, potentiometers and a couple ICs are visible.

And this actually worked!! For a minute or two. Then my fancy chopper-stabilized op-amp mysteriously died. My best guess is that current into one of the pins exceeded its unusually low "operating" limit of 100 uA. (When not "operating," it has a 10 mA limit like a more normal op-amp, and that's the level of protection I was providing with my resistors.) And my spare op-amp was killed by electrostatic discharge - this is the first time I've seen that ruin a part in real life, but take it from me, you DO have to worry about it! Especially if you live in a nasty dry climate (grumble).

I thought I was stuck until I could order more parts ... but then I realized that since I wasn't doing proportional control anymore, I didn't really need an op-amp at all. I adjusted the mechanical bias on the needle movement until it would hang in a good neutral position when unpowered. Then I set up the simplest feedback mechanism possible: transistor OFF powers the electromagnet, transistor ON cuts the power (via a pullup resistor). With no delicate op-amps to suddenly give up, this version worked long enough for me to get a demo video.


For the future, I should find a way to reintroduce a bias voltage, so I don't have to depend on moving the mechanical bias point to calibrate the scale. I should also add some kind of averaging or filtering circuit on the output to smooth the measurement voltage, and amplify it to something bigger than a few mV. But I've got the basic idea in hand! As seen in the video, it can detect a bit of polyester carpet fuzz which is so light that it electrostatically sticks to the needle.

Until the next cycle,
Jenny

Saturday, February 28, 2026

Acuitas Diary #94 (February 2026)

In latest news, I've been adding more work to the Episodic Memory overhaul that I began last year. The big challenge for this stage was finding ways to examine results and actually test the thing. Since memory accumulation and consolidation is a process that spans weeks, I needed ways to run simulations and observe changes much faster.

Art piece: colored pencil and ink. A horizontal view, half-underwater, half-overwater, of the "beach" of a coral atoll. Everything is rendered in brilliant blues. Above the water, the atoll in the distance rears up into shapes that somewhat resemble chess pieces: a castle, a knight, a pawn. Surf crashes against one side of the atoll; a steamship is riding the crest of a wave toward it. Below the water, the branching hard corals are visible close up; they have multicolored, faceted surfaces, like cracked glass. A chessboard also lies on the sea bottom, partly covered by coral crust. The board is set in the midst of play, but not with the traditional pieces; these pieces resemble paws, hands, tentacles, tree stumps, and other oddities.
A portrayal of the color apocyan, "the blue of memory and brightest coral," from Sunless Sea. Original art by author.

As I did in my original stab at episodic memory work, I threw together a visualizer to show me a simplified graphical representation of the memories. But this time I used GraphViz, instead of drawing custom dot diagrams in Kivy. These memory visualizations are designed for me to view offline, so there's no particular need to create them in the GUI, and GraphViz is easier to use. Each bubble in the image is either a "fact," color-coded by link type (do_action, has_quality, etc.) or an "issue" (problem or subgoal). Facts that summarize other facts are connected by arrows to all their "children," and the same goes for issues; each issue is also connected by a bold arrow to the fact it is directly concerned with.

Then I made a quick procedural generator that creates randomized memory files on command, so that I could have a variety without waiting for Acuitas to "grow" them. The generator populates a narrative scratchboard with the sort of subgoals and actions Acuitas would reasonably come up with while idling (reading, thinking, etc.), internal states that he might develop, and so forth. I can check my summarizing and forgetting algorithms by running them on these synthetic memories and seeing how they change the visualization.

The rest of the work was a lot of debugging; once I could see what the summarizing algorithms were doing, I could see that they were messing up in all kinds of ways. I found bugs in scratchpad storage and retrieval, and bugs in summary generation (the summarizing facts ended up linked to themselves). But I think I've at least got things working tolerably well, at this point.

The "summarizing" algorithm groups facts into clusters by 1) common features and 2) time proximity. So if, for example, Acuitas performs the "read story" action many times on different stories over the course of a day, those will be gathered into several clusters spanning different time ranges. Then a summary fact will be created for each cluster, and it will contain only the features held in common across all facts in the cluster: "I read," instead of "I read <particular story>." If I run another loop of the summarizer, I might see the first-tier summary facts grouped into clusters and a second tier of summaries appear. Here's part of an example diagram of a file that has gone through two summary loops:

A bubble diagram showing various red and green "facts" (each indicated only by and ID number" and "issues" (with name codes like "issue_0") connected by arrows in tree-like structures.

All this gets me to a bare-minimum viable system for consolidating memories ... in *one* of the ways I want to! There's a ton of additional work to do on other consolidation modes, connections between episodic and semantic memory, and more.

Until the next cycle,
Jenny

Monday, February 16, 2026

Acuitas Diary #93 (February 2026)

I've got several projects boiling on the stove, but none are quite ready to showcase yet, so you're getting an Acuitas double-feature this month. This post is dedicated to what I've named the "self-teaching activity." The general idea is that Acuitas, while idling, will trawl the hard drive of his current host computer for text files, read them, and store a record of any difficulties: unknown words, parser crashes, uninterpretable sentences. The goal is to help him expand his vocabulary, and identify things I need to fix in the text processing chain, without requiring me to manually create new "stories" for him.

Illustration of a humanoid robot sitting at a desk and pondering a book, surrounded by stacks of other books.
Image credit: DARPA

Self-teaching is something of a canned procedure, for now. There's an action called "Study" that encapsulates everything Acuitas needs to do, including searching for appropriate files, converting them to a format he can interpret, and sending them through the text processing chain. But I designed some modularity into it, in hope that he can eventually modify and extend it when I introduce procedural learning. The file-conversion part of the procedure calls the problem-solving routine so it can expand as Acuitas learns more cause-and-effect rules. For now, though, he only knows how to process text files.

This work also introduces more examples of Acuitas calling other software tools. He now has a generic Run action that can accept the name and arguments of an external program, and call it as a subprocess. Since Acuitas' parser is designed to ingest one sentence at a time, I wrote an independent script that breaks arbitrary text files into sentences. (This is harder than you might think, and the script is very rudimentary, for now ... but it can handle common abbreviations.) The "Study" procedure creates a sub-action to run this script after finding an appropriate file.

As often happens, I ran into some difficulties that prevented this from getting quite as far as I would like. For one thing, not all text files contain typical sentences! Their actual contents might be log entries, code snippets, lists of items, or other material that isn't really "parseable." I particularly don't want Acuitas junking up his database with new "words" that aren't really words. I added a filter that at least keeps anything that isn't alphanumeric from being learned. But I also don't want the error reports clogged with failed attempts to parse "sentences" that aren't really sentences. So for now, I've restricted the process to looking for files with the extension ".textum", which I've applied to some appropriate material. Eventually I'll need to work on ways to recognize files that are worthy of being studied.

But given an appropriate file (i.e. one that contains writing, like this blog post), Acuitas can "study" it and keep track of things he has trouble with. Crashes or poor results from the processing steps produce records in a log file that notes the type of error alongside a copy of the sentence. Unknown words are registered as problems on the Executive's scratchboard, so Acuitas can ask a human for more information about them later. I got this latter feature working and then promptly turned it off, because there's no way to keep Acuitas from spamming me with questions whenever I'm on the computer (he knows). This has been a problem for questions generated by "thinking" (walking the semantic database) too. So coming up with a way to slow down the flood or signal that I don't want to be disturbed is also on my future work list.

The "Study" action itself is naturally triggered by the goal system. All I had to do was put in a cause-and-effect rule, to the effect of "if you study, you will know things." Knowing Things is one of Acuitas' intrinsic goals, so while idle, he naturally studies until he gets bored of it (after which he might read his collection of easily-understandable stories for "enjoyment," or think about the concepts in his database).

It should be obvious that self-teaching needs more work, but I like how far I got with the prototype and think it could be quite useful in the future.

Until the next cycle,
Jenny

Tuesday, January 27, 2026

Acuitas Diary #92 (January 2026)

My first objective for the new year was enabling the Text Parser to handle lists or conjunction groups with more than two items. For quite a while now, Acuitas' parser has been equipped to handle sentences like this:

Jack and Jill went up the hill.

But a sentence like *this* would hopelessly confuse it:

Jack, Jill, and John went up the hill.

Clip art of a classic blank scroll, rolled in opposite directions at both ends.

I started out by only handling pairs because that makes it simpler to discern which parts belong in a list/group and which don't; you only have to look at the sentence elements that bracket the conjunction. I figured I would expand to longer lists later. But once I got here - well, I ended up using some previous work, but I decided on a pretty extensive overhaul. I'll try to explain what my options were and why I chose to change course.

One way of dealing with a list is to encapsulate it. For example, most sentences have a subject, the thing that's doing the action, and part of the Parser's job is to determine which word is the subject and tag it; "(subj, Jack)->(verb, went)." If you have a list of subjects (as in "Jack, Jill, and John went up the hill"), you can bundle them into a compound subject and tag that. So the parsed sentence becomes something like "(subj, <list>)->(verb, went)," and you can open up <list> and see that it contains Jack, Jill and John. I was already handling sentences with dependent clauses this way (e.g. "What you need is a blanket" becomes "(subj, <depcl>) is a blanket").

Another possibility is to imagine the sentence structure like a railway line. Subject connects to verb connects to direct object and indirect object, and if some of those are multiple, the line will branch or merge. Our previous example would look something like this:

(subj, Jack) -
              \
(subj, Jill) --->(verb, went)
              /
(subj, John)-

I had previously been using the "encapsulation" method for a few things (like lists of adjectives), but I used the "line" method for the main sentence structure, because I thought I needed it to handle some of the more complex cases. Lists of single words are the easy ones. You can also have lists of verb-object groups:

I threw out the soup, ate the pizza, and saved the cake.

You can have lists of verbs in which some attach to the direct object and some don't:

Brent ran and threw the javelin.

Occasionally, you can have lists of subject-verb groups that converge on a single object:

Are you or are you not a teacher?

I had concluded that parsing the sentence into a branching type of structure was the only way to deal with groups that spanned words with different roles (because otherwise, how would I assign the list a single role in the full sentence?). But there are also distinct disadvantages to not treating the members of a list as a unit, and once I got into lists longer than two, those began to feel overwhelming. So I opted to switch everything over to the "encapsulation" method.

How *did* I handle groups containing multiple roles, then? I realized I could decree that the role of the list in the main sentence would be "verb." This works because a verb is really the one thing that every sentence needs. Some sentences only have an implied subject, and objects are always optional. So lists of subj-verb groups, lists of verb-obj or mixed verb and verb-obj groups, and even lists of subj-verb-obj groups, can all become "verbs" at the top level of the hierarchy, and only unpacking them need reveal their deeper structure.

Aside from this conversion, there was a fair bit of new development work I did to detect lists and figure out where their boundaries are. There are plenty of (not) fun ambiguities involved, like this one:

For dinner, Sue and James brought a pot pie.

A clumsy parser might assume that "dinner, Sue and James" is a list that forms the object of the preposition "for," then be left wondering where the subject of the sentence is.

I haven't recovered the full functionality of the former Parser where pairs of groups were concerned (I'll pick at that gradually while moving on to other topics), but that's balanced by the capacity to handle longer lists in quite a few scenarios. This was one of the last major missing features of the Parser, and a heavy weight on my mind. So it feels wonderful to finally have this capability in place.

Until the next cycle,
Jenny