Category: Uncategorized

  • Ember’s Sonata: XControls and how to use them

    I’ve provided a useful script called XControls to some developer friends. I’ve decided to use a Github repository to start uploading useful utility scripts like it in case anybody ever needs them. The script was originally created to handle complex states in Ember’s Sonata but can apply to any UnityEngine game (it can also be modified easily to handle any C#, separate from Unity).

    https://github.com/Sophia-64/Sonata/tree/main/C%23%20Utility%20Scripts

    Here’s the guide (Also on GitHub):


    Xontrols adds a few useful classes designed for dealing with state machines. They are as follows:


    BControl

    A powerful and dirt simple boolean map that returns preset value (true or false) only if all of the booleans in the map match. It’s a bit like the boolean equivalent of a jury.

    Syntax:

    BControl blockPlayerCtrl = false; //Declares that the player should only have control if all conditions agree
    
    blockPlayerCtrl["bear trap"]       = true  ; //Sets a bool to the key "bear trap", or creats it if it does not exist
    blockPlayerCtrl[this.transform]    = false ; //component / monobehavior control, makes a component key. 
    blockPlayerCtrl[InventoryItemUI]   = false ; //object controls, for any non-data type
    blockPlayerCtrl[666]               = true  ; //Integer controls, too, and they work as identifier, not indices.
    
    blockPlayerCtrl.OnBoolAssigned += SomeFunction; //Adds a listener to an event called any time a boolean member is changed
    
    if (blockPlayerCtrl.stringControls.ContainsKey("yo")) ... //Checks for a specific key without creating one


    The BControl is useful anywhere a lot of different flags need to decide a single branch (state machines, e.g).


    Example

    public static class Courtroom
    {
      //If I set 'isGuilty' = true, it will only be true if all bools are in agreement. 
      public static BControl isGuilty = true; 
      public Meatbag defendant;
    
      void CheckVerdict()
      {
          if (isGuilty) defendant.ExileToAustralia(); //BControl is read like a normal boolean.
      }
    
      void StartTrial()
      {
          isGuilty.OnBoolAssigned += CheckVerdict(); //Bcontrols offer the option to run a delegate if any member bool is added or modified
      }
    }
    
    //If one juror says you're innocent, you're innocent. If there are no jurors, only the judge can save you.
    public class Juror : Meatbag
    {
      void JurorVerdict(int ID)
      {
          if (this_juror_thinks_you_are_innocent)
          {
              RaiseHand();
              Courtroom.isGuilty[ID] = false; //Activating a key creates it if its' new, sets it if it isn't. That's it
                                              //The BControl runs on dictionaries, so pick any int you want.
          }
          else Courtroom.isGuilty[ID] = true; 
    
          if (jurorIsInfestedByAlienParasite) Courtroom.isGuilty["alien infestation"] = true;
      }
    }
    
    //Meanwhile, on some object, somewhere, on its own time
    public class Judge : Meatbag
    {
        void CalledMenInBlack()
        {
          Courtroom.isGuilty["alien infestation"] = false;  //aliens think you are innocent and override the jury
        }
    
        void ActivateNepotism()
        {
          Courtroom.isGuilty[this] = false;  //judge thinks you're nice and overrides the jury
        }
    }

    Let’s consider what this would look like without a BControl.

      //racing conditions, or complex flag checks are necessary
      //a smart programmer will make a dictionary of bools instead
      //BControl does all that for you.
    
      static bool isGuilty = true ; 
      public Meatbag defendant    ;
      static Judge judge          ; //need to track a reference when a new judge takes the shift
      static List<Juror>  Jurors  ; //need add jurors to this list when they are created and loop through
      static alienInfestation     ; //need the aliens to let us know not to convict you 
      static nepotism             ; //need the judge to tell us not to convict you if they're your buddy
    
    void CheckVerdict()
    { 
      //need to loop through jurors, or smarter handling of flags
      //fine if there are 12, but what if you're being judged by a panel of 9 septillion mosquitos? 
      foreach (var juror in Jurors)                                     
        if (juror.this_juror_thinks_you_are_innocent) isGuilty = false;
    
      if (alienInfestation || nepotism) isGuilty = false;
    
      if (isGuilty) defendant.ExileToAustralia();
    }
    
    //...and so on
    --


    This leads to a lot of racing conditions and a lack of independence for various mechanics. The BControl’s performance is not significantly different than this scenario. But in my opinion, it is lot more intuitive.


    IControl

    The indexing / lookup system is identical to BControl. The difference is that instead of a boolean, it operates on an integer, and it returns the minimum or maximum value depending on the mode set.
    It is useful for having many different, independent objects cleanly operate on the same integer.

    Unlike BControl, IControl does not have an event upon changing an integer. Feel free to add one.

    Syntax

    IControl ControlLevel = IControlMode.Maximum;  //It will return a maximum of the values. 
    
    ControlLevel["menu open"]       = 3; //The menu being open suggests the control should be 3
    ControlLevel[enemy]             = 1; //an enemy wants control level to be 1
    ControlLevel[34]                = 0; //some weird OS condition says it should be 0
    ControlLevel[player.rigidbody]  = 4; //According to the player's rigidbody, we're going fast, let's make it 4
    
    Debug.Log($"CTRL Level = {ControlLevel}); // Spits out "CTRL Level = 4", because the highest number set is 4


    XControl


    This is the most advanced of the set. It is a generic, XControl, designed for handling superposed complex states.

    Like a BControl, it is keyhed with strings, ints, components, and objects. But the value returned is not one value, but a tuple of three values — a condition, an integer, and an object of type T. The returning system works the same as a BControl.

    It is most useful for handling complicated conditions, e.g knowing what description to set on a highlighting box when there may be multiple overlapping objects. It is powerful and flexible, and enables scripts to be more self-contained.

    Syntax:

    XControl<string> HighlightMap = false; //HighlightMap as a boolean returns 'false' unless any member has a boolean component of 'true'. 
    
    HighlightMap[SomeUIElement] = (true, 1, "label 1")  ; //According to SomeUIElement, it should be highlighted, it's priority 1, and the label should say "label 1". 
    HighlightMap["Bad Wolf"]    = (false, 99, "doctor") ; //According to "Bad Wolf", it shouldn't be highlighted, but it'd be top priority if it were
    HighlightMap[666]           = (true, 0, "oh my")    ; //According to something identified as the number 666, it should be highlighted with the label "oh my"
    HighlightMap[someRefType]   = (true, 4, "rosebud")  ; //According to this object, it should be highlighted with the label "rosebud". 

    //Now, let’s say that you have a highlighting utility. Should the following be true…

    ...{
          (bool showHL, string label) HLData = HighlightMap;
    
          if (showHL) 
          {
            HighlightBox.SetActive(true); //show the UI
            HighlightBox.SetText(label); //show the label with the highest priority
          }
        }

    In this case, the label would be “rosebud”, because it’s the highest order option with the lowest condition.

  • Build: Dreamcatcher Alpha, Part 2 – Software

    Having a nice display and a Pi is all well and good. But I’m not here to sit there on Raspberry Pi OS. Naturally, the display works without issue on the latest Pi OS. A little change to boot/firmware/config.txt, enable the driver, and you’re done. The display works.

    But I’m not here for Raspbian. I am here to learn, and I have more to learn from Kali.

    First, some ingredients:

    This is the GitHub page for the base driver for HyperPixel4. Let’s call it the ‘manual driver’.
    https://github.com/pimoroni/hyperpixel4

    This is the GitHub page for a fork of the driver that supposedly takes the pain out of setting it up. It was written for Kali 2024, which, as I would come to find out, is a different use case than Kali 2025.3. Let’s call this one ‘the easy driver’.
    https://github.com/cons0le7/hyperpixel4-kali

    Should be simple. Just use the ‘easy driver’, right? Well… no. It’s not that simple. Because at some point, Raspberry Pi added vc4-KMS support for HyperPixel4 natively, which leads us to driver number 3. Let’s call this one ‘the kernel driver’.
    https://github.com/raspberrypi/firmware/blob/master/boot/overlays/vc4-kms-dpi-hyperpixel4.dtbo

    This one ships with Kali, and Raspberry Pi. It is the one that worked for Raspbian.

    Before I could get far, I had to fix something. Until I could get the display working, the only way to run commands on Kali is through SSH or a display server. Opting for the simpler option, I chose SSH. But it was excruciatingly slow. I would type, and then occasionally wait several seconds before it was registered.

    I edited the sshd_config by typing sudo nano /etc/ssh/sshd_config, then added a comment before the X11 forwarding line:
    X11Forwarding yes
    to:
    #X11Forwarding yes

    This dramatically sped up my SSH session and enabled me to debug properly.

    Attempt 1
    . Try the kernel driver.

    I followed these steps:
    -> Edit /boot/firmware/config.txt

    dtoverlay=vc4-kms-v3d


    dtoverlay=vc4-kms-dpi-hyperpixel4

    Reboot.

    It works in Raspbian, so surely it will work here, right? No. Using this combination led to an interesting state:
    >The display manager, LightDM, was running, and by all accounts should be working.
    >Xorg appeared to be running, too. Logs show nothing useful.
    >Yet this yields nothing: dtoverlay -l
    >Checked /sys/class/drm/ folders. All the right stuff. So what gives?

    Upon further investigation… and by ‘further investigation’, I mean several hours spanning multiple days of dmesg this, cat that, reflashing, rebooting, I couldn’t get it to run.

    Attempt 2. Try easy driver.

    Cons0le7 has put together a wonderful little python utility, shown above, designed to take the headache out of installing. It is definitely designed to be the easy driver.

    The process is simple, and described better on the page. But to give it briefly:
    ->Clone the github link.
    ->cd into the folder.
    ->run ‘python3 easy.py‘.

    This is where it gets a little odd. The script downloads the ‘manual driver’ from Pimoroni’s github, makes sure Python3 and the GPIO libraries are properly installed, and does some housekeeping. Great. But upon entering the installer, I am met with a few options:

    Pi 4+
    [0] Rectangular
    [1] Weirdly Square

    Pi 3B
    [3] Rectangular
    [4] Weirdly Square


    But the device I’m running is a Pi Zero 2 W. So what do I pick?
    I picked the closest option — 0, hoping the Pi 4 is close enough.

    No dice. The terminal shows, no display manager, no desktop.

    I tried every iteration and every option again and again, and the result was always the same. No GUI. I even tried re-flashing.

    Eventually, I realized a possible cause — since the fork was created, /boot/config.txt was changed to /boot/firmware/config.txt. I looked into the install script for both Cons0le7 and Pimoroni, and all assume the folder must end in /boot/.

    I tried rewriting the scripts to reflect the new folder to no avail. The result was clear — I wasn’t going to get either of these working on Kali 2025.3.

    Attempt 3. Try the framebuffer driver.

    I decided to try the classic fallback of just running the frame buffer directly. No hardware acceleration, but at least I’d know the screen works. After a little bit of configuration, I rebooted to find a screen. Great! Job’s done, right?

    Well, no. Not unless I want to try to use a portrait screen run across the length of my forearm. I needed landscape, and there’s no good way to rotate a framebuffer (at least that I am aware of). But it did pass a crucial test — the display would work with Kali, just not in the way I had it set up.

    I won’t go into the setup for this, mostly because I don’t recall exactly what it was.

    Attempt 4. The easy driver, revisited

    I do not know why the KMS driver will not work in Kali 2025. I have not figured it out. But I had a sinking suspicion that it had to do with the major jump in kernel that moved /boot/config.txt to /boot/firmware/config.txt. So I decided to try something new — I acquired the same exact version of Kali 2024 used in Cons0le7’s driver. Crucially, this particular version retains /boot/overlays/ and /boot/config.txt.

    I booted up Kali 2024. I had to do some tricks to get sudo apt update to work, but once I did, I was able to run the ‘easy driver’ once again. I once again picked [0] for Raspberry Pi 4 (despite being on a Raspberry Pi Zero 2 W), and let it install. I rebooted, got ready to SSH back in and debug once again, but I stopped.

    In front of me was a beautiful sight. A kali logo. But I wasn’t done yet, because the kali logo didn’t go away. I was not logged in, and I had no way to log in!

    Next up, I configured LightDM via sudo nano /etc/lightdm/lightdm.conf, then I added my user as follows:

    [Seat:*]
    autologin-user=username
    autologin-session=xfce


    When I rebooted, I was met with a desktop, once again in portrait mode. I needed to rotate the display. Thanks to Cons0le7, this is easy — running their rotate.py script successfully rotated the display. I was almost done, except for one problem.

    The touchscreen was off by a lot. Pressing on the left would give it a correct position, but on the right, it would go way offscreen.

    It took me longer than I am willing to admit to realize that is because I didn’t disable composite, and so I was running two displays. I simply went to the now-accessible display settings, disabled the composite screen, and there it was.

    A lot of pain, a lot of learning, and a whole new appreciation for the people who contribute to Linux later, I was met with a beautiful sight.



    It finally works. It is not flawless — I will one day figure out how to get this working on the latest Kali. But for now, I have a more important problem to solve, one that is equal parts engineering and equal parts fashion:

    I need a battery system I can look good in.

    That will be part 3.

  • Build: Dreamcatcher Alpha, Part 1 – Hardware

    I’ve always loved the idea of a hacker girl with a computer strapped to her wrist. This is a theme of Ember’s Sonata (working title), where the protagonist, Ember, wears a wristbound device called a Dreamcatcher [sic]. When I created that device and the character model, I wanted to get the proportions right, so I measured my own wrist and used it to create the model.

    Fast forward several years to summer 2025, where I was working multiple simultaneous technically involved contracts and doing IT house calls for the townsfolk as a side hustle. Smartphones are nice and all, but what I really needed was something I could have on my wrist and write commands into on the fly.

    Luckily for me, I already had a model that I knew would fit me. All I needed to do was modify it for real world hardware. I call the result a Dreamcatcher Alpha, because it’s a real world version of a fictional design (the Dreamcatcher).


    The first build:

    Mainboard: Raspberry Pi Zero 2 W

    The Zero 2 W holds a stunning 1GHZ Quad-core, 512MB Ram, and a VideoCoreIV GPU. It is not as powerful as a smartphone, but it is a lot smaller than a smartphone. It also has a premade Kali 2025 OS ready to go for it. It should be powerful enough for my purposes. I’m not gaming, after all.

    Display: HyperPixel 4 Rectangular

    The HyperPixel4 is a 480×800 display that has two special properties that make it perfect for this build — it fits directly on to the pi’s breakout pins, and it is crucially only 2.3″ wide. I might be tall, but my wrists do not seem to have gotten the memo, so under 2.5″ is critical for the device to be wearable.

    Frame: 3D Printed PLA

    It took me a few iterations to get a frame that fit everything in and lined up with the fittings on the HyperPixel. The frame needed to not only protect the display, it also needed strap holes. I placed two on one side (the one with the USB out), and one on the other.

    Strap: Scavenged
    I needed a strap capable of handling a lot of stress, so I chose some high density lea– kidding. I cut the strap off a damaged life jacket and sewed a wrist strap from it. It works great.

    Iterations:
    For the initial hardware build, I used Raspberry Pi OS instead of Kali. I knew that getting drivers working well in Kali would not be trivial, so I chose to start with a compatible OS. Once I had the hardware working, I would get the software working.

    Step 1. Get the display and the PI working together.

    This part was easy. Square shape in the square hole. Plug the HyperPixel into the Pi (physically, it’s more the other way around, considering the screen is much larger!).

    Step 2. Get the Pi to show the display.

    The good news is that Pi (and Kali) technically has a driver builtin to use the display. This part is easy in theory – flash the OS, then follow the instructions over at the Hyperpixel4 driver’s github page.

    https://github.com/pimoroni/hyperpixel4

    Done. Right? Well… no. As I found out the hard way, those are the old instructions. After several attempts and a broken SD card, I eventually found out that the correct instructions are now here. Joke’s on me for not looking at the issues page before installing.

    https://github.com/pimoroni/hyperpixel4/issues/177

    After doing it the correct (and considerably easier) way, I had a display working. Now I had to get it into the frame.


    Step 3. The frame

    I went through a few rejects before I had a frame that fit properly. My first frame was too short because I had accidentally switched the calipers from mm to inches in a measurement (making it too tall).

    The second iteration fit the screen fine, but I made the mistake of designing it around the idea of inserting the pi and screen separately, and ended up busting another SD card. Those things are delicate!

    The third iteration had too small of a slot for the micro-usb slot, making it impossible to power.

    The fourth time was a charm. IT is imperfect and by far not final, but the fourth iteration was good enough to keep the screen safe and stay snug. The first test was with zip ties.


    Once I had the screen working with zip ties, I needed one final ingredient. A strap. Thankfully, living on Little Cranberry, finding a strap is pretty easy thanks to discarded life jackets. I cut a section of the strap off and removed both sides of the plastic clasp, then re-sewed the female clasp into a smaller strap. I attached the male end so that it could slide, and it was good enough.


    It’s ugly, dorky, and I love it. Plus, when combined with an opera glove, it does look cyberpunk in a primitive kind of way!

    The hardware was the easy part. The hard part comes next — getting the display to play nicely with Kali. But that is a story for another day.