We’ve been kind of neglecting this blog, haven’t we? Oh well. Best way to change that is to start posting again. In any case, hi! I’m Querral and this is my first solo post on here, though I may have… heavily influenced the reverse engineering series.
Who’s reverse-engineering game data files for fun again? That’s right, it’s me.
Content warning: AoC 2021 day 8 spoilers
Day 8 looks to be a fun bit of deductive reasoning. Should be interesting!
Content warning: AoC 2021 day 7 spoilers
What trickery is this? I’m doing the day 7 puzzle on day 7?
Content warning: AoC 2021 day 6 spoilers
Day 6 promises to be an interesting one. While it may be day 7 today, I’m still playing catchup. Solution in the usual place.
Content warning: AoC 2021 day 5 spoilers
Day 5! I was really hoping that our friend the giant squid would show up again, perhaps with dolphin racing tips, or even have brought friends for a few rounds of blackjack, but alas, it was not to be.
Content warning: AoC 2021 day 4 spoilers
Following on from my previous unpost, I took a bit of a break and did day 4 today. Solution on my git repo as always.
I’m still doing Advent of Code, but as sort of expected, I’ve been a bit short of energy, so I haven’t been writing up my solutions like I did for day 1.
Advent of Code is here again, and I’ve been looking forward to this! I’ve been doing AoC since 2019, so this will technically be my third year, though I’ve done some older puzzles in the meantime. My work from past years is available on my public git repository, and this year’s will be too.
You ever get struck by a bit of inspiration that won’t go away until you write it down? I got one of those today, and somehow wrung nine thousand and change words out of it.
Content warning: physical health, discussion of pain
So. I have Ehlers-Danlos syndrome. As you might imagine, this came as something of a surprise to me.
I think enough has changed in the last couple of weeks to warrant another post. You can see all of my writing on this subject so far in the plurality tag.
I’ve had a story idea rattling around for quite a while. I’m not sure if I want to develop it into anything, but I did want to get this intro scene written down.
Over the last month, I’ve been exploring exactly what being plural means for me. I felt that I wanted a specific way of referring to the unusual arrangement I find myself in, and settled on the word Composite to refer to it. The word was carefully chosen: a composite material is one made up of multiple constituents, and gains new strengths that none of them would have alone. I think that’s a good way of describing what I became post-fusion.
Following on from part 1, in which I talked about some of the world background, I’ve been thinking about how demons work and what life is like for them.
Been thinking about worlds I might like to use for a potential TTRPG, and here’s one idea.
I’ve been doing some introspection over the last week and it’s taken a while for me to work out how to put all this down, but here we finally are.
Next thing I’m trying to make sense of is the mesh format. This one is going to be difficult.
So you know all those things I said I was going to do next? I didn’t. I decided to take a look at texture files instead. I know others have already reverse-engineered this format, so this was purely for my own enjoyment.
In the community that surrounds modding any game, there are generally three kinds of people: players, modders, and toolmakers. It is possible to be more than one of these.
As of this evening, I now have a working PAR file compiler as well as a decompiler. Source files in the usual place.
This is what it’s all been building towards. I now have a script that reads off the contents of a PAR file and dumps all the data to a series of CSV files. You can find it over on my git repo if you’re interested, as I know a few people have been. Special thanks to Alexander O., CYDI-TST, Guardian, __jmp32, Noctis, and Skeltek, variously from the InsideEARTH and United Lunar Dynasty discords, for their help in figuring out the last few details, especially to __jmp32 and Skeltek for showing me where to find the source file so I could get the field names.
After talking to the Earth 2150 community a bit more, I hit the proverbial jackpot. I was able to get hold of the source file the PAR file was compiled from. It has the names of all of the fields, and they match the order of those in the PAR file. That means I have a lot more information than I did last time.
Since writing my last entry on reverse engineering Earth 2150, I’ve been in touch with the small community that still plays the game online. They don’t have any deep insights into the PAR file format, but they were able to point me at the SDK documentation. That doesn’t describe the format, but it does list various attributes that objects have for use in AI scripts, so I’ve got a bit more information to go on.
I’ve been playing an old game called Earth 2150 recently. It’s an RTS from around 2000 or so, and was one of the first full-3D entries in the genre. Over the weekend, I decided that instead of playing it, I’d try to reverse-engineer its data files. I made a surprising amount of progress. This post is extremely technical, particularly the later sections where I’m working out what each thing does, and probably isn’t of much interest to anyone but me, but writing all this down helps me think.
The setting and ideas of the Starship Game are system-independent, and can likely be adapted to most TTRPG systems. As I’m personally a fan of Fate Accelerated, my thoughts on how the game might be played in that system are presented here.
So you've read a little about the Commonwealth, and now you're wondering: what does being a Starship actually mean?
As noted previously, the centre of your being is your Intelligence Core, but what this actually is depends on which culture gave you form.
Intelligence Cores built from human-derived technology are AIs in the truest sense. Terran and Querral Starships are driven by a powerful quantum computer, granted the gift of sapience by the power of mathematics.
Like a biological brain, your mind is fundamentally tied to the hardware it runs on and can't be transferred like a normal computer program without losing the spark of life that makes you more than a simple automaton. That spark could be restored to a copy of you by the same process that brought you into being, but that copy would become a new and separate person, not another you.
Not every computer mind can call itself a Starship. In fact, you are in the minority. Most simply inhabit android bodies and live among their biological human and querral fellows, considered their equals in every sense, both moral and legal.
Even with a Starship hull for a body, you don't think of yourself as fundamentally separate from the rest of your people. Starship is a job you do; what you are and how you think of yourself is as a Terran or Querral.
Iomi and Corack Intelligence Cores are mostly biological, more an artificial species than a synthetic intelligence. You grew through a childhood spent within the hulls of adult Starships, learning how to be who and what you are. Your kind were made for this and nothing else, and now that you have your own hull and hyperdrive, you are ready to fulfil the life you were born to.
As an Iomi Starship, you were raised to carefully consider your choices and their impact before acting. Every Starship wields enormous personal power, and this is given in the expectation that you will use it responsibly.
Nonetheless, the choice was ultimately yours. There are many roles for a Starship within the Commonwealth, from passenger liners, to freighters, to Navy warships, to gigantic mobile shipyards that grow and nurture young Starship minds as their future hulls are prepared for them.
Now you have made your decision, you will not be easily swayed from it, for it is not the way of your people to change quickly, but it was, is, and always will be your own choice.
As a Corack Starship, there was never any question about what you would become. You are the first among equals, the voice of the people you carry within your hull. By living within you, they have conferred a great trust upon you to act with the consensus. Each Corack who joins you is trusted in turn to build that consensus in a direction that pleases everyone, or to leave and find another home that better suits them.
As the sole member of the consensus who cannot simply leave, however, your voice is given weight. While rare in the extreme, it has happened in the past that an entire population has left their Starship for another, and a new group has formed more aligned with the Starship's views.
None of this is something you ever really have to think about. You are in constant subconscious contact with your people through numerous tiny cues. They can tell how you feel by the tone of the lighting, the scents on the air supply, the subtle rhythms of the machinery, and they understand all this on an instinctive level, just as you read their postures, scents, and subtle rhythms of movement. Verbal debate is rare and reserved for major, complex issues.
This is a system that has allowed the Corack to endure all that a hostile universe has thrown at them, and it works well.
The distinction for Kuto is nonexistent. You are simply a Kuto, and your current body is a Starship. This is how your people have worked since you transcended mere biology in a time and place so distant that it is all but forgotten. The Intelligence Core is nothing more than the life support system for your brain, the same as every other Kuto has.
While uncrewed Starships exist, they are the exception, particularly when working with the Commonwealth Navy. There are countless benefits to having sapient minds with you to help oversee support systems, offer viewpoints, and to go places you cannot.
On non-Corack Starships, it is usual to have one person designated the Crew Captain, or simply Captain for short. Your Captain has no formal authority over you – quite the opposite, it is their job to function as your first mate or executive officer, overseeing the crew as they fulfil your directives. They do have formal authority over the rest of the crew, which only you can overrule.
Corack Starships have no Captain. Their consensus-driven society does not require one. This causes occasional confusion when Corack and non-Corack crews interact.
Within the Commonwealth Navy, all crew members are themselves members of the Navy. Most Navy Starships will also carry a contingent of Marines, whose job it is to expand "go places you cannot" to include "go places you are very much not wanted".
Again with the exception of the Corack, as their Starship you are also the crew's commanding officer, binding them to follow your orders and making you responsible for their actions. You are also responsible for their wellbeing in the same way you would be a civilian crew, with the caveat that you may be expected to order them to their deaths if necessary.
In practice, you will delegate most of this to your Captain, whose authority within your hull as second only to you is enshrined in the Navy's Articles. Your Captain may be a good deal more experienced than you, depending on how long you have been in the Navy, and listening to them is usually a good idea.
All that said, not all Navy Starships have crews. They are undeniably useful to have, but can limit the sorts of situations you can put yourself in. As a consequence, uncrewed Navy Starships have something of a reputation as daredevils, and while this is not entirely undeserved, it is more often the case that an uncrewed Starship will simply be the first choice to be ordered into a dangerous situation.
A Starship's Hull
If the Intelligence Core is your mind, your hull is your skin, and anything fitted to or in it can be considered, both practically and legally, a part of your body.
Starships are built in all shapes and sizes to fit their chosen or assigned role, and as a rule all are given the means for extensive self-modification should the nature of that role change. Given time, materials, and blueprints, you could refit yourself from a hyperspace tug to a naval battleship entirely unaided, though things will obviously go faster with an actual shipyard on hand.
Of course, there's nothing stopping you refitting yourself as a shipyard to provide this help to other Starships. It's considered good practice to bring extra engineering specialists on your crew if you think you may need to do this.
The actual nature of your hull is, itself, mutable. Iomi starships tend to prefer semi-organic hulls that are grown rather than built, whereas Terrans are fond of metal alloys, but these are not hard rules. Querral Starships, in particular, tend to resemble a hodgepodge of parts of other ships haphazardly welded together, though this is as much an aesthetic choice as a practical one.
Your hyperdrive is what makes you a Starship. For an enormous cost in energy, it allows you to transfer your entire hull into hyperspace. Hyperspace is a distorted reflection of the real universe, a quirk of physics with some useful properties, one of which is that the spaces between massive objects are greatly compressed. This allows for travel effectively faster than the speed of light without actually needing to go that fast.
Hyperdrives do not work everywhere. Hyperspace is a fragile, tenuous thing, and the mistakes of the past have created vast hyperspace "dead zones". Within a dead zone, as far as anyone can tell, hyperspace simply does not exist. Attempting to use your hyperdrive within one will simply fail, and trying to enter one from hyperspace will see your course curve around it, as though there were some invisible force pushing you away.
Besides accessing hyperspace for faster-than-light travel, your hyperdrive has a second function. It is also your primary slower-than-light drive. By projecting only part of itself into hyperspace, it can take advantage of the distorted nature of hyperspace to cheat physics and allow great acceleration with very little use of reaction mass.
Without the hyperdrive, the Commonwealth would not be possible. Preserving and safeguarding what remains of hyperspace is therefore the prime responsibility of every Starship and is the Navy's first and most important mission.
Every independent Starship needs fabrication facilities to make spare parts and to create task-specific equipment. These are standard pieces of equipment that can make use of a variety of techniques ranging from additive and subtractive manufacturing to vats of tailored microbes.
Given time, raw materials, and suitable blueprints, a standard Machine Shop can make any piece of Commonwealth technology small enough to fit inside it, and these are room-sized pieces of equipment.
As a rule, a Starship will have a minimum of two of these so that either can repair any failure in the other, though having more is not uncommon except in the smallest of hulls.
In addition to mechanical equipment, the Machine Shops can also supply food, clothing, and replacement body parts for organic crew members, though complaints about the subjective quality of said food are ubiquitous.
In addition to your living crew, you are also equipped with remotely-operated drones. These have many functions, ranging from maintaining and modifying your hull to shifting cargo. While not even close to truly sapient, they can perform simple tasks with minimal supervision thanks to their onboard computers. They do not handle unexpected situations well and complex operations will require more attention from their operator, whether that is you or a member of your crew.
Drones are expendable, and designed to be cheap and easy to replace. Nonetheless, some Starships and their crews become attached to long-serving drones, giving them names and customising their appearances. Hats made in the Machine Shop are particularly popular additions, though no one is entirely sure why. This practice is considered to be good for morale and is encouraged by the Navy.
In cases where a standard maintenance drone is not sufficient to a task, any decent engineer knows how to program the Machine Shop to wrap drone systems around almost any piece of specialised equipment, up to and including hyperdrives to create a hyperprobe.
These specialised drones require additional supervision compared to a standard one, and will often need a crew member skilled in the use of its extra equipment directing its every move.
Nonetheless, with skilled operators, these drones can allow a Starship to use their Machine Shops to assemble objects of arbitrary size and complexity, and this is in fact how new Starships are built.
Weapons and Defences
It is a truism that there is no such thing as an unarmed Starship. A hyperdrive can be used to easily accelerate objects to relativistic speeds, which will cause anything those objects hit to have a Very Bad Day.
Nonetheless, the Navy likes to think it is a little more sophisticated than just throwing rocks at things it doesn't like.
As any Starship has the ability to use its hyperdrive to instantly escape any trouble it may have, the majority of military unpleasantness takes place in hyperspace itself.
Here, the weapons of choice are missiles, which is the name of choice for semi-autonomous drones adapted for military purposes. The simplest of these simply go very, very fast and run into things, but the Navy likes to think it is a little more sophisticated than just throwing fast robots at things it doesn't like.
Hyperspace combat is a tangled interplay of simple kinetic missiles, missiles that intercept and deflect those, missiles that intercept the interceptors, missiles that make lots of distracting sensor noise, missiles that look like things they aren't, missiles that do anything and everything that their operators can think of to gain an advantage.
Then there are the ultimate weapons: hyperspace implosion devices, better known as Hyperbombs. A perversion of hyperdrive technology, the simplest implosion devices will collapse a volume of hyperspace, dumping everything within it back into real space. Other, nastier variants exist that will tear matter apart and dump it back into real space in the form of a supernova-like blast of radiation.
Using any such device creates a permanent hyperspace dead zone. If a hyperspace weapon is deployed, no one will ever be able to use a hyperdrive there again.
The use of Hyperbombs is enshrined in the Commonwealth Charter as the only capital crime, a crime against reality to which no other can compare. It is a punishment the Navy enforces as part of its primary mission and no exceptions are made. Even the Earth Empire know better than to play with these particular toys but unfortunately the same cannot be said for everyone.
The galaxy is littered with the debris from the Hyperspace War. No matter where you are in the galaxy, if you know where and when to look, every so often you can see a tiny flash in space as the light from the deployment of one of these weapons reaches you.
As far as anyone knows, none of the participants of the Hyperspace War survived to tell about it.
A Note From the Admiral
Welcome to the 3rd Fleet. You are reading this either because you have been assigned here for your current tour with the Commonwealth Navy, or have been seconded to us as an auxiliary. In either case, we are proud to have you with us.
The 3rd Fleet is the youngest and the smallest division of the Navy. Some think that places us in a lesser position, but those people are mistaken. We are light, nimble, and unladen by weight of procedure. We can go where we are needed and get the job done before the 1st and 2nd even finish waking up.
That flexibility is because of you. You will find no other Starship exactly like you here, no crew with exactly the skills yours has. Through diversity of form and unity of purpose, we protect the Commonwealth from any danger it may face.
And believe me, we face many dangers. The Empire continues to grow its borders. The proliferation of hyperspace weapons threatens to cut us off from faster-than-light travel and return us to the dark ages. And every one of the unaligned powers wants a piece of what we have.
But we do not face these dangers alone. The Commonwealth Stands Together. And together, the Commonwealth Navy, the 3rd Fleet, stand between it and harm. It is an honour to stand with you.
– Ql. Shining Ember, Admiral, Commonwealth Third Fleet
The Commonwealth is a formal alliance of five starfaring peoples: the Iomi, the Kuto, the Terran Diaspora, the Corack, and the Querral. Its primary purpose is the preservation and proliferation of technology necessary for faster-than-light travel, though it also functions as a trade bloc and a mutual defence organization. FTL travel has been completely lost to the galaxy twice in recorded history. The Commonwealth exists to ensure that this never happens again.
It began as an informal cooperation between the Iomi and the Corack, following the invention of modern hyperdrive, with others joining first by mutual agreement and later by formal treaty. Each shares a commitment to the preservation of hyperspace for the use of all, though they have differing views as to how other aspects of their societies should be run.
While there are planets and orbital habitats holding billions within the Commonwealth, all look to their Starships as the lifeblood of their people.
A Starship is, ultimately, two things together: an Intelligence Core and a hyperdrive. Without either of these things, a Starship is not a Starship. A hyperdrive without a Core is a hyperprobe, a Core without a hyperdrive is just a person.
The Intelligence Core is, in a very real sense, you. Your hull and peripheral systems can be reconfigured endlessly, but the Intelligence Core houses your mind and is held inviolate.
The hyperdrive is, as far as anyone knows, the only remaining way of reaching hyperspace, and thus the only means of faster-than-light travel. It is one of the few pieces of technology that varies little between the peoples of the Commonwealth, and any improvements are shared freely and widely.
The Iomi were one of the Commonwealth's founding members. Actually three closely related plant species, they are slow, deliberate, and above all patient. These traits ensure they are held in generally high regard by the other peoples of the Commonwealth.
They are highly unusual in that they had been using slower-than-light nuclear stardrives for quite some time before the discovery of hyperspace, and had managed to settle seven worlds across three star systems by that time.
They are seen as the diplomats and mediators of the Commonwealth, and all new members of the Commonwealth so far have been sponsored by them. In particular, it was the Iomi who argued that the Terran Diaspora be shown leniency in spite of the Earth Empire's continued aggression.
No one but the Kuto know what they originally looked like. All living Kuto are cyborgs, a mixture of bioengineered organs and mechanical parts. As a people, they are incredibly ancient, are widely believed to have invented the first jump gates, ushering in the first interstellar age for most of the galaxy.
Kuto philosophy is one of adaptability. The average Kuto, if such a thing can truly be said to exist, believes that if your body or mind is insufficient for a task, you should build a better one that is.
Despite their important role in galactic history, they only joined the Commonwealth relatively recently. They were believed destroyed in the Gate Wars, and every living Kuto today descends from a malfunctioning colony ship found drifting in deep space by the Commonwealth Navy.
The humans of the Commonwealth refer to themselves as Terrans, after their homeworld New Terra, to distinguish themselves from the militantly anti-synthetic Earth Empire. Humans are among the oldest starfaring peoples still living, being one of the first contacted by early Kuto explorers in the days of the jump gates.
Both the Terrans and the Empire have a rich military history, and within the Commonwealth have a reputation as fierce warriors and sharp tacticians. New Terra joined the Commonwealth out of self-interest, seeking protection from the growing Empire, but they are now the backbone of the Navy's crews and are as much defenders of the Commonwealth as they are defended.
The Corack are insectoid in appearance, highly social, and are one of the founding members of the Commonwealth. After the destruction of the jump gate system at the conclusion of the Gate Wars, they were the first to develop a working hyperdrive based on reverse-engineered jump gate technology.
Upon regaining FTL capability, they sought out others to share the knowledge, so that faster than light travel should never again be lost to the galaxy. After being nearly wiped out for their trouble, they forged an alliance with the Iomi, creating the institutions that would later become the Commonwealth and its Navy.
Each Corack Starship is a settlement in its own right, with populations ranging from the low hundreds to tens of thousands, and few Corack live anywhere else. Decisions are made by consensus, and happen remarkably quickly despite that.
The Querral are an uplifted species, the product of a remarkably successful Empire bioengineering programme. Originally based on a small Earth predator called the ferret, they are small, quick, and far more clever than their creators gave them credit for.
No one would ever have known they existed, except for an unlikely series of events that led to their taking control of the lab ship they were held on and making their escape from Empire space.
After being granted their own planet by the Iomi, they put their own spin on Commonwealth technology and petitioned for membership as a starfaring people in their own right.
The Querral, in general, have an uncanny knack for finding items of value almost anywhere, a trait which was a major factor in the Commonwealth accepting them as full members, following their sponsorship by the Iomi and Terrans.
The Earth Empire
Most humans who did not escape Earth-controlled space to count themselves among the Diaspora live in what is now known as the Earth Empire. Following a successful rebellion against their AI overlords, they live by a hard rule: no one and nothing but a living being shall make a decision for another living being.
They see the very presence of the Commonwealth on their borders, based as it is on Starships run by synthetic intelligences of various types, as an existential threat. A threat they will not hesitate to remove before it has a chance to remove them.
The Empire is more loosely organised than its name might suggest, with the Empress on Earth exercising only loose control over the Empire's other worlds, except in military matters – the Imperial Fleet's command structure answers to her and only to her, the ultimate human-in-the-loop to guard against their machine servants taking too much control.
If you’ve known me for more than a couple of years, you know that I keep coming back to a sci-fi TTRPG idea in which you play as a starship AI. The setting and rules have gone through many iterations and never been really finished. Today I decided to update them to use the Fate Accelerated system, because I’ve had some fun GMing that since my last pass over this.
I liked day 8. It made me feel clever when I worked it out. Spoilers follow as usual, and my solution is here.
There are a few classes of puzzle that show up time and time again in Advent of Code. One of these is the virtual machine puzzle. That's where it gives you some computer code, and the task is to make something that can run it.
Remember I mentioned Intcode from 2019? That was a virtual machine puzzle writ large. It was built up over multiple puzzle-days and ended up as a fully functional computer, capable of performing any computation and even able to talk to external hardware. It was a masterpiece of puzzle design.
This is not that complicated. Today, we've got a simple instruction set. There are three things our hypothetical computer can do: add a number to another number, jump forward or backward in its instruction list, or just do nothing and move on to the next step. It's not really a proper computer, just a slightly convoluted adding machine.
(Maybe I should go into exactly what a universal computer and Turing-completeness mean some time. Be a fun writing exercise.)
Something this simple wouldn't take me long at all in a language I already know, but as previously established, I've been learning Haskell this year. Haskell is a pure functional programming language. What this means is it doesn't follow step-by-step instructions like another language like C or Python would. It feeds the results of various functions into each other in a long pipeline. This makes having it pretend to be a step-by-step computer... challenging.
A challenge I greatly enjoyed.
The thing with a pure functional programming language is that it doesn't really have state. If you run the same function with the same inputs twice, you will get the same output both times, always. There are two ways around this.
The first of these you've already seen me use. It's recursion - the function calls itself for the next step with slightly different input. That would have worked here, because the pretend adding machine only keeps track of two numbers internally - the results of its adding, and the instruction number it's on. It'd be pretty easy to keep passing those to the next step.
That is insufficiently needlessly elaborate.
The second way, which is what I actually did, is to use something called a monad. It's a way for a functional language like Haskell to pretend to have state. Behind the scenes, it's still doing recursion for you. It's just easier to write. My friend Emi did an excellent introduction to the topic on their own site, and I heartily encourage you to have a read.
To summarise: when working with monads, you have actions instead of functions. Those take the existing state, do something with it, and give back the resulting changed state. You step through various actions not unlike how you would in an imperative programming language.
Definitely hit a few pitfalls on the way, though! This is a new way of working in Haskell, with its own syntax and rules that are slightly different from non-monadic stuff.
I fell into the trap of thinking the
returnfunction does the same thing a return does in an imperative language - drops you out of the function with the value you give it. Nope, nope, nope, it's just badly named. What it actually does is give you an action that will always produce the value you give it, regardless of the current state.
Day 7 was a bit more challenging than the last couple. As usual, spoilers for day 7 follow.
This time around, the subject of our airline-based adventures was the baggage restrictions. Each bag is given a certain colour coding, and must contain a selection of bags of other colour codings based on a simple set of rules. The rules are a mere six hundred lines long, so not that different from real baggage restrictions.
Two parts to it. For the first part, we have to work out which other bags can contain our bag (coded "shiny gold") either directly or indirectly. For the second, follow the rules to their "topologically impractical" conclusion and work out how many bags our bag must contain to comply with the rules.
My solution is in the usual place.
Rather than copy-paste the common bits or do it all in one file this time, I decided to try writing my own Haskell module that the solution files could import. That's Bag.hs in the repository.
The Bag module contains the functions for reading the ruleset from the text file it comes in and turning it into a form I can work with programmatically. It also includes the first time I've used a custom Haskell type. It's defined as follows:
type Bag = (String, [(String, Int)])
If you're not familiar with Haskell - and I'm assuming most folks reading this aren't - what this means is that whenever I write the word Bag in a function, what I actually mean is a string paired with a list of values, each of which is a pair of a string and an integer.
This encodes rules like "drab indigo bags contain 4 pale bronze bags, 2 mirrored lavender bags." In my Bag type, that'd look like
("drab indigo", [ ("pale bronze", 4), ("mirrored lavender", 2) ]). Much respect for anyone carrying around a pair of mirrored lavender bags, by the way.
There's a reason I structured things this way. My solution hinges on a function called
lookupwhich is a basic part of the Haskell language. It lets you treat a list of pairs like a dictionary - lookup "drab indigo" in the list of rules, and it'll give you back that it has to contain pale bronze and mirrored lavender bags - the list that's the second part of that pair.
In other languages you'd write something like
rules["drab indigo"]. It's exactly the same thing.
A bit of parsing later, with my list of rules in hand, I'm ready to actually do something with them. Weirdly, part 1 was actually harder than part 2.
Part 1 involved finding any bag that can hold "shiny gold" bags by filtering the list of rules based on whether there was a "shiny gold" part in the bag contents list. Then recursively doing the same for any bags that hold one of those and so on.
Part 2 was the same thing in reverse, more or less. Check what a shiny gold bag has to hold. Then check what those bags have to hold, and so on, until we get all the way down to bags that are empty. As it turns out, with the particular set of rules I was given, I need to bring 158730 other bags to put in my shiny gold one.
Could've been worse, I guess. If I had decided to be cool and bring a pair of mirrored lavender bags, I'd have needed over 22 million bags total. Ever feel like you might have overpacked a bit?
Not much to write about for day 6, though the premise is again very relatable. It's nice to help people out when you're the only one that can make sense of a system, though I believe more in teaching them how to understand the system than doing the work for them. I'd far rather have someone say to me, "let me show you how to do it," than, "let me do it for you."
The puzzle was pretty simple, though, as you can see from my solution. For part 1, take several groups of answers and count how many answers in each group were given by at least one person.
So I just borrowed my
dnlsplitfunction from day 4 to break up the groups, and took the union of all the lists in each group. From there, the answer is just the length of each list. Easy.
Part 2, it turns out that, oh no, we misread the instructions! It's answers that everyone in the group gave, not anyone. Well, it's a one-word change to the code, too! Instead of the union of the lists, we just take their intersection.
I should probably have done something clever to just spit out both answers in one go, but I'm sleepy today so I just copy-pasted the part 1 code to part 2. It's not like I've got code quality metrics to meet, just having a bit of fun.
EDIT: Okay, so you know I said I wasn't going to make it do both answers in one go? I made it do both answers in one go.
I changed my
groupsfunction from just finding the union or intersection as it was before to finding both and spitting them out as a two-element list. Easy enough, but now I can't just do
sum . map lengthto get the total number of answers any more.
What to do about that? Well, the
Data.Listmodule I was already using for union and intersection also has a function called
transpose. That takes a table in the form of a two-dimensional list, like the one I just made, and flips its rows and columns.
So now, instead of a long list of sets of two answers, I've got two long lists of answers. That is something I can sum, just doing
map (sum . map length)to apply the same calculation I was doing before to both lists of answers. That gives me the answers to both parts at once!
EDIT 2: Despite how simple today's puzzle was, I just can't seem to leave this one alone. After talking to Emi, I realised I could do away with that transpose by using a recursive function to just construct the two lists in one go rather than building pairs and then switching them around.
The result actually isn't any more complicated. Have a look.
Curiously, day 5 was actually simpler than day 4. My solution is over on my git repo, and as always, this post contains spoilers for day 5 and earlier.
First of all, I'm continuing to love the writing introducing the problems. Let me just quote the first paragraph verbatim:
You board your plane only to discover a new problem: you dropped your boarding pass! You aren't sure which seat is yours, and all of the flight attendants are busy with the flood of people that suddenly made it through passport control.
This is just brilliant, I think. Yep, the current issue is the obvious solution to the avoidable problem is unavailable because of the solution to the last avoidable problem. Our protagonist is actually a lot like me: forgetful, accident-prone, and tends towards the most complicated possible solution to a given problem. It's like Eric Wastl knows his audience.
The puzzle itself wasn't actually anything special, its introduction and framing aside. It's a simple binary space partition, which is once again the sort of problem Haskell excels at. Read the encoded instructions character by character, and move the upper or lower bound of the search space depending on which character it is until you get one answer. Trivially easy with a recursive function.
Part 2 didn't add much. Find the seat number that's missing from the list. My solution to that was just to sort the list (it's only got 800 elements or so) and look for two elements that aren't numerically adjacent. Since I figured out how to deal with comparing two adjacent list elements yesterday, also pretty easy.
My seat's in row 69, column 5, by the way. Come over and say hi if you're on the flight once the seat belt lights go off.
I'm a little disappointed that there hasn't been some sort of ongoing thread like there was with 2019's Intcode interpreter. Throughout the month, you were slowly building up a more and more capable interpreter for some made-up machine code, each day's solution building on the last one. There hasn't been anything like that here so far, and the fact that we've got to day 5 without seeing any sign of one isn't promising.
I'm also starting to feel like using Haskell for Advent of Code might be cheating a bit. It seems perfectly geared towards exactly the sorts of puzzles that have been coming up... though just watch when day 19 comes along, the really hard puzzles kick in, and I have to eat those words.
I've been doing Advent of Code again this year, and this time I'm using it to learn Haskell. You can see my adventures with the first three days over in this thread on fedi, but my day 4 writeup ran a bit long, so I'm posting here instead.
If you're doing Advent of Code yourself this year, then be aware that this post contains a detailed discussion of my solution to day 4, so consider this your spoiler warning.
First of all, I just want to say I love the premise behind this one, the puzzle aside. "This line is taking ages. I'm going to hack their system to still work properly but do it better to speed it up." To borrow a popular fediverse idiom, that's strong white hat energy.
After that, we get into some heavier string processing than has shown up so far this year. The input is a set of sets of key:value pairs. Individual sets of pairs are separated with either spaces or new lines, while the groups are separated by a blank line.
All my string processing in Haskell so far has been character-by-character, which is easy enough because Haskell treats strings as lists of characters. Looking for a specific pair of characters that appear together, however, is a bit harder. There's a library that can do this for you easily enough, but I wanted to roll my own, because... well, that's just what I'm like. Learn more this way, have more fun.
To do that, I had to learn about guards. This is a second level of pattern matching in Haskell's function definitions that let you specify a predicate for which branch to go down. In a procedural language, this would be a set of if-else blocks. Here, it's baked into the definition of the function itself.
Isn't that neat? That set of definitions handles four distinct cases, which combine to, as the comment says, split a string across the first double newline it finds. For an empty string, it just spits out two empty strings, easy enough.
For a string at least 2 characters long, if the first two are newlines, it spits out an empty string and the rest of the original string after those two newlines. That sounds like it'd only work if the newlines are at the very start, but bear with me.
If those first two characters aren't newlines, then it gets recursive. It chops the first character off the original string, feeds the rest back into itself, then sticks the first character on the front of the result. Sticking something on the front of a list is very cheap and efficient in Haskell, so what it does here is effectively jump ahead to where it finds its pattern, then rebuild the first part of the string character by character until it has the two chunks again.
That last case? Just a special handler for when it reaches the end of the string with no match. Then it just spits out the entire original string in the first part, and nothing in the second.
Wow. That was actually harder to explain than it was to write in the first place.
From there, we have to check that seven required fields are present in a given set (my solution would break if there were duplicate keys, but there aren't any in the test data), and that each of those has a valid value according to a set of rules.
Most of those were easy enough. Check if a number is in a given range, check if the value is all hex digits, and so on. Only one really gave me much trouble, and that was a height field, which could be specified in inches or cm, with the unit as a suffix, and the number having a different allowable range for each unit.
That might have been really hard... except I already taught myself how to look for a specific two-character sequence in a string and split on that earlier. So that actually paid off!
I was getting excited over on Fedi about finally having native IPv6 support on my home Internet connection, no tunnels necessary, and after someone asked, that led into an explanation of the basics of IP addressing, NAT, port forwarding, and routers generally. It was a bit long to dump onto people's timelines, but that sort of long-form infodump is exactly what I put this site up for, so here we go.
I'm going to try to keep the assumed level of knowledge low so as not to exclude anyone, so if you already know some of the basics, feel free to skip ahead a bit. Seriously, I won't mind. Or even notice. I'm not even tracking how many people load these pages up, never mind actually read them.
IP Addresses: v4 and v6
Oh, wow, did you see what happened to my voice there? That was cool. I bet it had a dramatic echo and everything. I sound Authoritative and Well-Read with all these fancy Section Headings.
So why is any of this IP address stuff and all those other acronyms even important? Well, it all comes down to knowing where to send things.
The Internet runs on the Internet Protocol, appropriately enough. Everything on the Internet has an IP address, and data tagged with an IP address will go to that thing, wherever it is. Hence, address.
Most of the Internet you know runs on IPv4 – version 4 of the Internet Protocol. No one talks about versions 1, 2, or 3, and even mentioning them can draw the ire of the Elders of the Internet, and you do not want that kind of attention. Version 5 is likewise verboten.
(More seriously, versions 1-3 and 5 were experimental and never made it into the real world. I don't know much about them.)
An IPv4 address is a 32-bit number, and that means there are about 4 billion of them in total. That sounds like a lot, right? Nope. Not even close to enough. The way addresses were allocated in the early days was massively wasteful, and even if they were distributed fairly, there are 7.8 billion people in the world. It's hard enough sharing a postal address with someone else, and at least letters have your name on them.
No, I haven't been reading your mail. I'm offended that you'd even ask.
Anyway, not enough IPv4 addresses. Clearly what we need is a bigger number so we can have more of them! And that's all IPv6 is: 128-bit addresses so we can have more of them. So many that each one of those 7.8 billion people could have a few zillion IPv4 Internets of their own and not even use a fraction of a percent of what's available.
Overkill? Maybe. But better than running out again. And we did run out of IPv4 addresses. Luckily, IPv6 was finalized in 1998, and following that was swiftly adopted worldwide and effectively solved the problem— ahahaha, no. About three quarters of all Internet traffic is still IPv4.
NAT: Network Address Translation
So how did the world actually deal with there not being enough IP addresses? Well, they learned to share. That's called NAT.
This is something that almost all IPv4 routers do, and that's (mostly) a good thing.
In the early days, everyone on the Internet had their own block of addresses that they could assign as they liked, but that hasn't been true since 1990 or so, due to the address shortage I spent the entire previous section going on about.
What this means is that your router gets one and only one IP address that can be reached from the Internet. Any time you connect to something else out there through the router, you appear to be connecting from that one address, and when replies come back to you, the router keeps track of which connections belong to whom and send them to the right place. Hence, "router".
So how do you connect in, to a specific device, when there isn't already a connection from that device out? Like running a server? Well, that's the problem with NAT. You more or less can't. The router doesn't know what to do with an incoming request with no context behind it.
Unless you tell it. And that's where port forwarding comes in.
Port Forwarding (you want me to do what to my router?)
Almost all traffic on the Internet is either TCP or UDP, and while the specifics are a bit beyond the scope of what I'm talking about here, those protocols have a port number associated with them. Think of it like an extra number on the end of the IP address.
It's like having your name on the letter. Because computers can do more than one thing, that port number is intended for the receiving computer to know which program should handle the incoming data.
But we can also make use of this when working with a NAT router. When a connection comes in on a specific port, a router can be configured to always send that on to one specific address on the internal network.
For example, you can have the router send everything on TCP port 80 (HTTP) to whichever computer is running your web server. Others on your home network can still browse the web, but unprompted external connections can be sent to that one server box.
So, if you know the port number the program you're working with listens to, and the internal IP address of the computer running it, you can configure the router to send all the traffic for that program to that computer.
Notably, you cannot have two servers for the same thing running on the same network without doing some complicated shenanigans with proxies that I won't go into. As much as I love a good shenanigan, this post is way too long as it is.
DHCP: Why It's Not That Simple
Now, if it's just a temporary thing for a game, you can find out your machine's local IP address, plug it in, and off you go. Unfortunately, the nature of private Local-Area Networks means there's a bit of extra work if you want a permanent server, and that's because of DHCP.
Dynamic Host Configuration Protocol is how computers on a network get their IP addresses. When they connect to the network, or their current address expires, they send out a DHCP request to anyone who'll listen.
Your router is also a DHCP server, which means it is listening for such broadcasts. When it gets one, it'll assign the computer an IP address, usually in the 192.168.x.x range (though there are others) which is reserved for LANs like this. Such assignments are temporary – after a set period, usually 24 hours, the computer has to ask for its assignment to be renewed.
Most DHCP servers will try to keep a computer's address consistent, but it's best-effort only, and there is no guarantee the address will remain stable over time.
So if you're running a permanent server on a private network, you need to assign it a static IP address in the router. That *does* guarantee that that computer will always receive the same address, and that no one else will ever have that address.
It makes sense on small home networks, but for bigger setups it can be a problem to permanently take an address out of service, as there are only 250 or so to go around on a given network due to the way IPv4 was designed.
Big company networks with more computers than that? Actually composed of several sub-networks that talk to each other through routers, which all just works because that's what the Internet Protocol was designed to do without NAT getting in the way.
(Yes, I know CIDR with variable subnets is a thing, but that's way above the level I'm going for in this post. Don't worry about it.)
Wrapping It All Up
IPv6 was designed to make all of this unnecessary. There is no NAT in IPv6. There is no port forwarding in IPv6. There is a greatly reduced need for proxy shenanigans and subnetworks can be bigger than the entire IPv4 Internet.
So why aren't we all using IPv6? Money.
IPv6 isn't backwards-compatible with IPv4. What that means is that you need to run the two separate systems side by side until everyone's on IPv6 and you can turn IPv4 off for good. You can run them on the same physical wires, but you need two completely different sets of software and, in some cases, switching hardware.
That's more expensive than having a single IP system you can use for both, and NAT really doesn't hurt most home users that much. I care because I'm running things on my home network that I want to be reachable from the Internet, but non-technical users don't have any such need.
What that means is that people aren't willing to pay for IPv6, so penny-pinching companies naturally don't want to invest in setting them up.
What about companies that actually need lots of IP addresses to make things available on the Internet and would pay for IPv6? Well, aside from the fact that most of them already do support IPv6, they can also afford to buy blocks of IPv4 addresses at a premium.
Should You Care?
I can't answer that. I do and I think I'm right to do so, but I'm not exactly your typical Internet user. I literally write blog posts about computer networking, as it turns out.
But if you've read this and decided that you should care, or at least want more information to decide if you do, the World IPv6 Launch site is a good place to start.
If not, don't worry about it. There are more important things in this world to worry about than IP addresses. Look after yourself first.
I've been playing Heroes of Might and Magic 2 recently. It's one of the games I grew up with, and it holds many fond memories for me. It is also, as anyone following me on the Fediverse will know, how I discovered my lifelong fascination with griffins.
After a bit of that, today I decided to try and get Heroes of Might and Magic 3 running. While I strongly prefer Heroes 2's aesthetic in most ways, Heroes 3 is mechanically the better game. If I could make Heroes 3 look like Heroes 2, I would be very happy indeed.
Now, getting Heroes 2 running was pretty easy, since it's from 1996. Stick it into DOSBox and go. Heroes 3 was an entirely different matter. It came out in 1999, so it was Windows 9x native, and games of that era are often troublesome to get running on a modern system.
(As an aside, all these things are available on GOG and those folks already did all this work, but I'm not paying for these games again for the sake of a weekend's nostalgia when I have my original CDs right here.)
First hurdle, the installer. It just didn't work. Try running it and... nothing. Nothing at all. No window, no error message, not so much as a stray griffin feather. So out comes my first troubleshooting tool, Process Explorer, to see how far it gets.
Well. That's not good. It's just sitting there, not doing anything.
It turns out it's an InstallShield installer, and those had a few quirks. For reasons I may go into another time, they use a 16-bit starter executable to bootstrap the main installer, and 16-bit support was dropped from 64-bit versions of Windows, which is all of them nowadays. The 32-bit launcher you can see there is trying to run the 16-bit one that'll run the main 32-bit installer. But it failed, because my system can't run 16-bit executables and it's apparently just... sitting there, scratching its head.
After fruitlessly trying to unwrap and run the 32-bit installer I knew was buried in there, I eventually stumbled across a program called i5comp that some kind soul (thank you, fOSSiL, whoever you are!) wrote to access the InstallShield cab data format.
So, armed with this, I decompressed the game files and tried running it. To my great surprise, it actually started up first time, ran through the infuriatingly unskippable intro cinematic...
...and dropped me to the main menu. Well, that was easier than I expected. That said, I like to play my games windowed these days, so I hit F4 (which was the standard key for this back then for some reason, except when it was alt-enter, ctrl-shift-f, or F11) and...
Oh. Well, that's a problem. Does my current setup even support 16-bit colour mode on the desktop? It's doing fine in full screen exclusive mode, but the other stuff I'm running might not take kindly to having the majority of its colour space taken away.
That was my immediate thought, anyway. Then I remembered that DxWnd exists. I haven't had a lot of luck with it in the past, but it turns out that Heroes 3 is just undemanding enough that it wraps it up in a nice little window, no problem.
Then I scale the window up to 1200x900, disable the Windows cursor and off it goes!
That should have been it, but there was one more wrinkle. I load up a scenario, play my first turn, all goes well, but then it complains that it "can't create save AUTOSAVE.CGM". That's not good. These games can run kind of long and I'm going to have problems if I can't save.
This turned out to be because I hadn't run the installer. Who could ever have thought that'd come back to bite me? Normally an installer does all sorts of nice things like creating the directory the save files will live in, and it's unhappy because the place it wants to put its saves doesn't exist.
So I go digging around to find out where saves are supposed to live. Fifteen minutes with DDG later, I find my way to a thread on the GOG forums in which someone else asked the same thing for a different reason. Seems it's just a subdirectory inside the game's main install directory (pretty common in 1999) called... "games"? I can sort of see it, but why games rather than saves?
Doesn't matter. I create that and off we go, saves work just fine! Everything now works and I can get a game of Heroes 3 going!
...well, that was exhausting. I think I'm going to unwind with Minecraft a bit and come back to this later.
Wow, I have a blog? When did that happen? Who is responsible for this travesty?
subscribe via RSS