Blog

Dialogtree Musing

October 17th, 2019 (permalink)

I've had this eternal project of thinking of making games, realizing I need some kind of subsystem and then concentrate on that. That has lead to things like CFL (the virtual filesystem I made ages ago) or, in a way, SoLoud (audio engine for games).

One such subsystem are dialogues in games. One attempt at that is DialogTree which goes some ways into what I think is needed, but doesn't go far enough in some ways and too far in others.

While on a walk today I started pondering about it again. Another implementation of more or less the same idea is my MuCho engine for zx spectrum (for Multiple Choice). It goes farther than DialogTree in some ways, but that is partially because I wanted to integrate images and such in the speccy engine which are not, strictly speaking, part of the dialog proper.

To reiterate the basic idea of the DialogTree engine, all game dialogue scenes can be described with a mental image of index cards. Each card has some descriptive text, and then list of options where to go next, pointing at other index cards.

If that sounds somewhat familiar, think of "choose your own adventure" books, or "gamebooks", where you have a page or few of prose followed by options for where you want to take your adventure next.

DialogTree was to do some bookkeeping on what options you've seen so far and which options you picked, so you could have dialogue options pop up depending on what the player has seen so far, and other options disappear based on other options taken. This data could in future be compiled into compact state as bit fields to be saved along with other game data. The game engine itself could also query and modify the flags so the dialogue can affect the game world and the game world can affect dialogue. This also meant that it would be possible to make a sequel to your game later on which imports earlier game state to know which choices the player has done.

Where DialogTree really stumbled was the tools. I started writing a "dialogtree studio" which doesn't really work out. If I reimplement DialogTree at some point, I would rather write a simple scripting language and command-line tools to compile the data to game-usable formats. Along with the compiler some kind of visualizer (maybe just a .dot file generator?) might be useful.

MuCho is a more complete solution, since it also contains the game engine itself, and has support for changing colors and showing images and such.

Some of the "gamebooks" also include other game mechanics such as fights and collecting items; in such cases the reader is expected to keep notes on what's going on, hit points and such. I implemented a simple scripting language (of sorts) into MuCho that covers most of those with randomness, integer and boolean operations.

If I rewrite DialogTree, I'd have to take a good look at what I did with MuCho, what bits make sense to copy over, and what doesn't. There's gosubs and global pages, for instance, which let some logic be run without having to copy it over to every "index card".

As a middleware solution the API itself should be dead simple. DialogTree's C implementation requires a bit too much work from the user's side, iterating through "cards", querying numbers of questions and so on. It should really be something like:

d3      state(&gamestate);
d3deck  deck;
int     done = 0;
deck.load("deck.d3");
state.usedeck(deck);
while (!done) 
{
    display_message(state.getText());
    for (int i = 0; i < state.getAnswerCount(); i++)
        display_answer(i, state.getAnswer(i));
    done = state.choose(get_answer(state.getAnswerCount()));
}

Where display_message, display_answer and get_answer would be what the game engine would do to display things, and gamestate would be the store of dialogtree game state.

Where things get a bit hairy is localization and voice acting support. The game engine may also want the actors to emote while speaking, which potentially means a lot of metadata. The DialogTree script might just contain tags that are used to fetch the text displayed from completely somewhere else. Or maybe the script contains text for a different scripting engine completely, that is used to drive the game engine. In simplest form, the localization might just be contained within the DialogTree script under sub-headers.

Plenty of things to think about.

The Idea of OpenGL XS

September 10th, 2019 (permalink)

This was originally a Twitter thread, but since some people have asked for a blog post version, I'm posting it here.

(XS for extra small, or 'access')

I feel that OpenGL has way too much baggage, and the more modern approaches (Vulkan, and yes, even ES) have a rather high barrier to entry. We should have an easy, fun and small API that is easy to prototype with.

  1. I feel the API should be an user-space library that is linked to your application. Open source, non-attribution liberal license, with minimal dependencies. The actual functionality should be implemented on Vulkan behind the scenes.

  2. The API should be a fairly minimal subset of OpenGL and ES, including some form of glBegin/glEnd to ease building of vertex buffers, but also more direct buffer creation access. Built-in pipelines for gouraud, texture and gouraud texture with alpha and blending.

Most of the fixed function pipeline features should be axed. That means, for instance, lighting. If lighting is desired, the user can dive down to Vulkan level and replace the shaders. If a feature is not essential and can't be easily included, axe it. (see point 3, below)

What to do with the matrix handling? I'd say ditch them, and let people use whatever matrix library (like glm) they want. Forcing a dependency might lead to tacking other things in there like image loading, and this needs to be small. So: a single glLoadMatrix() call.

For the goals of this API to work, it can't be 1:1 with old OpenGL. It should still feel familiar, even if it's not exactly the same as, say, OpenGL 1.0. Porting simple OpenGL 1.2 applications to the "XS" API should be possible with a little effort.

  1. There should be an upgrade path to Vulkan where needed, so the pipelines and shaders should be easy to replace, handles to buffers and textures should be easily accessible. This would make it easy to prototype, but not stop the user from doing what they want to do.

  2. This needs to be an official @thekhronosgroup project. If it's something a joe random coder makes, all of three people in the world will embrace it. If Khronos doesn't want this, it won't fly.

Why would @thekhronosgroup want it? Hopefully for the same reasons I would - as a way to combine what was good in early OpenGL (prototyping, low barrier to entry) with upgrade path to modern Vulkan (high performance, modern hardware access).

ZX Spectrum Shrinky Challenge

September 1st, 2019 (permalink)

Sometime early August Janne Helsten posted a coding challenge on twitter, where the challenge to was to draw a certain image on the C64 using as few instructions as possible.

Inspired by this, I threw a similar, but more relaxed challenge on the ZX spectrum. Where the C64 challenge was very strictly defined (exact pixels, length of assembly code being the measure, etc), I defined it by saying that you should draw an 'X' across the full (or mostly full) screen after clearing the screen, and the resulting .TAP file was the measure. Since the speccy ROM has a line drawing routine, this is the obvious way to approach it.

The baseline code was in BASIC, as follows:

10 CLS : DRAW 255,175 : PLOT 255,0 : DRAW -255,175

This resulted in a 95 byte .TAP image. And "empty" .TAP file is 25 bytes, so the BASIC code here took 70 bytes. I estimated that it should be easy to shave around 11 bytes by abusing the way the constants are encoded in spectrum BASIC, so the best I expected was around 84 bytes.

A 60 byte version was promptly posted.

Making an assembly version on speccy has the disadvantage that you pretty much need to have a basic stub which executes the assembly, so someone who went that route commented that their version was 91 bytes.

And then Hikaru posted a 37 byte version. There haven't been any new versions for a while, so let's look at what the heck is going on there.

  • Bootstrap assembly code (the 12 bytes over the 25 byte "empty" tap) loaded into address where operating system returns when finished loading (instead of back to BASIC)
    • No need for BASIC bootstrap!
    • The bootstrap code sets up the first DRAW command parameters, and then jumps to..
  • Payload in filename
    • The system loads the filename in a known location
    • The code in filename calls ROM line draw routine, sets up the next draw coordinates and calls it again

I'm assuming the system just hangs after this, which is totally fine. This completely exceeded my expectations, even though we didn't go in the same direction as the C64 assembly trickery =)

Thanks to evilpaul, luny, Dunny, dbolli, Serzh and Hikaru on World of Spectrum forums for participating!

Time-Looped Games, or: It's Groundhog Day, Again

August 4th, 2019 (permalink)

I've been thinking about time-looped games recently. I wrote about this idea in 2008 and again a bit last year regarding "The Sexy Brutale".

What sparked me to think about this idea again now is Elsinore which is closest to my original idea that I've seen so far. In the game you're stuck in time as Ophelia along with a bunch of Shakespearean characters, and there's a mystery (or bunch) to solve in order to escape the time loop. And plenty of murders, to boot.

What makes Elsinore different from, say, The Sexy Brutale is that you interact mainly by talking with people, and telling people things changes their behaviour. Telling Hamlet that you have proof that his father was murdered by a certain person distracts him from killing someone else, and leads to a different outcome, as an example. Giving certain people certain information triggers future events and cancels others. It works out surprisingly well.

Unlike The Sexy Brutale, you can affect several things on one time loop at a time, and sometimes these changes interact (with either positive or negative result).

Since the game is so text heavy, I really would have loved it to have Wadjet Eye class voice acting, but you can't have everything, I guess.

That out of the way, what makes a time loop game?

One of the best examples of almost-a-time-loop-game is the Hitman series of games. You have a bunch of agents (where 'agent' is a term for non-player character) following a routine, and you can change their behaviour by changing some elements, which may lead to a chain reaction with other agents. What makes Hitman games not to be an example of time loop game is that the player character is not stuck in a time loop, even if the player might be.

If we accept Hitman as a time loop game, then any deterministic (i.e., non-random) single player game is a time loop game, and speedrunners are stuck in time loops.

Having the player character be stuck in a time loop means that while the world (largely) stays the same, the character doesn't; in Elsinore, once your player character learns who killed Hamlet's father, you can use that information right off the start of the time loop. Physical items (which there are fairly few in Elsinore) do need to be fetched from the same place every time, though.

Elsinore has a few tools to make the time loop more manageable; you can look at a time chart to see if you've unlocked some events in the future you may be interested in, and thus make sure you're in the right place when that occurs. You can also fast-forward time, so there's no need to wait doing nothing. There's also a list of mysteries and hints you've collected regarding them.

Another aspect of time-looped games is that the time loop needs to be relatively short. Running a time loop for 10 hours in real time would be too much. One hour of real time seems to be about the maximum comfortable time.

A game which I'd put in the box with other time-looped games, with more of a Zelda approach is Minit where the time loop is 60 seconds of real time.

Minit is nothing like the above description though: Some physical bits are kept from one loop to another (like if you change your spawn point, and some items are kept), and there are no agents to speak of. Still, the game world (largely) resets, you and your player character learn things and acquire skills as you go on.

It also uses that 60 second time limit in downright punishing ways, such as having one NPC who will talk for almost 60 seconds and will reveal a secret just before the time runs out. Many of the things you acquire in the game will lead to being able to push those 60 seconds a little bit further, like faster running speeds. Don't get me wrong: I think it's a great thing that they explored what the time limit can be used for, and since the time loop is so short, the "punishments" are not all that severe.

Then then there's of course Zelda: Majora's Mask, which I haven't played a lot.

When I did have the chance, I did not have enough free time in a single day (due to kids, etc) to get through the first time loop (~60 minutes), and thus couldn't save the game, and thus my motivation to delve deeper into it was rather low. Then my original Wii console then got fried in a lightning storm, and there we go, 100% data loss. The replacement Wii doesn't play GameCube games and doesn't connect to the internet. So my knowledge of this game is largely based on reading articles and watching YouTube videos.

Anyway: when the time loops, Link will retain maps, masks, music, weapons, and money stored in the bank, but then, this is a Zelda game first and foremost. There's 20 agents or so, following their scripted behaviours. NPCs will act differently based on what mask you might be wearing, for instance, and you need to be at the right place at the right time to do some things.

Stanley Parable is... borderline. There are no agents, and you don't retain anything, and sometimes the world changes when you loop.

Then there's a bunch of games where time loops are part of the game story, but not really a game mechanic. Braid and Bioshock Infinite are often quoted. Alan Wake's American Nightmare is an excellent example of using time loop as an excuse for reusing content.

Going back in time to the 8-bit home computer era you'll find a lot of games where if you beat it, you get to play it again, only harder this time. Those (and any game which unlocks harder difficulty when you solve them) could be said to be time looped, but... not really.

Then there's a bunch of games with time rewinding or time travel, like Prince of Persia: Sands of Time or Life is Strange which, again, I wouldn't count. Some of these could be seen as borderline, but they're not really committed to the time loop and are, at the heart of things, linear experiences.

There are a few inherit problems in game looped game designs. Understanding what is going on (and going wrong), understanding the results of your actions, potentially having to wait (in real time) for something to happen, and tedium of repeating the beginning of the loop.

What's going on needs to be telegraphed to the player strongly. As an example, in the Sexy Brutale you might witness a murder, then on the next loop you follow the murderer and see him (or her) pick up the murder weapon, and on the next loop you know to steal the murder weapon before the murder can occur. Something will happen because the murder did not, and that (usually) leads to the game opening more. If the player character has a journal or something similar (Elsinore, Majora's Mask), it can help the player understand what's going on.

Having the player wait doing nothing is rather annoying. This can be solved in (at least) three ways: first, keep the time loop short (like in Minit), second, let the player reset the time loop manually (like in Elsinore and if I remember correctly, in Sexy Brutale too). Third, you can have ways of the time to go faster, either by having explicit game UI for it (like in Elsinore's fast forward), or having some in-game way (like certain songs in Majora's Mask that let you skip forward, or by going to sleep in Elsinore)

The problem with re-setting is that you're bound to do certain things at the start over and over. Apart from time loop humour ("didn't we do this already?"), this can be helped by having a mechanic to change spawn locations (Minit, Sexy Brutale), opening up new pathways (Minit), changing the way the time loop starts in more or less subtle ways (Elsinore), or just simply keeping the locale small enough that it doesn't matter much (Elsinore), or simply moving the start point forward after some things have happened.

While there has been clear progress since 2008, what I'm still missing is a more systemic time loop game, where even the authors can't predict all of the outcomes. This will undoubtedly lead to a lot of issues with all of the potential problems I've listed as well as being one downright nightmare to debug, but it can also be really cool, as a game based on systems is more bound to have cascading events than one that relies on heavy scripting.

Since several time looped games have popped up in the past couple of years, it seems other people are thinking about them as well. We'll see what the future holds.

The Pedal Machine, Part 2

June 22nd, 2019 (permalink)

So I've been lazy. The parts arrived quite a while ago, but I haven't been bothered to work on this project. I was on vacation for three weeks, and today is the first workday, so I felt like soldering a bit. Unless I've messed something up and/or my solder connections get cut, the racing wheel's soldering should be done.

Here's some wire documentation..

Cable A
-------
Purple          vcc
Yellow-black    Dpad right
Red-black       Dpad down
Blue            Dpad up
Pink            Dpad left
Pink-black      Button:R2
Green-white     Button:Mode
Brown           Button:Start
Red-white       Button:Select
Brown-white     Button:L2

Cable B
-------
Yellow          POV hat down
Green           POV hat right
Brown           POV hat up
Orange          POV hat left
Blue            vcc

Cable C
-------
Blue            Led 1
Yellow          Led 2
Green           Led 3
Red             Big rumble 1
Black           Big rumble 2
Brown           Small rumble 1
Orange          Small rumble 2

Loose wires
-----------
Red-short       Steering pot 1
Blue-short      Steering pot 2
Yellow          Shift down 1
Orange          Pot1 1
Red             Pot2 1
Green           Shift up 1
Purple-black    ?!    

Cable A was the one that originally went from the logic board on the wheel to the base. I repurposed it, connecting the dpad, most of the buttons and what I assume to be the vcc plane.

The racing wheel also had a PlayStation cable which I cut into pieces to form cables B and C. Cable B I just wired to the POV hat (and vcc plane). Cable C is connected to the bits I'm most unsure of; the LED in front has three connections, maybe it's bicolor? The rumbles do not connect to the same vcc plane as the buttons, so they may require a different voltage. In any case, I connected those wires too, so I can experiment.

Then there's a group I call loose wires. These connect to the three potentiometers and two switches on the base of the wheel. The steering potentiometer is pretty clear (the 'short' wires), but as for the rest, well.. it's a tangled mess inside the casing. One of the wires that comes out is purple-black, which I don't see connecting to anything, but on the other hand I see a purple-white cable that is, as is a grey cable. More experimentation is needed, I guess.

Another thing that arrived was the teensy (or possibly a teensy clone) for which I tried uploading the test blinky program and it seems to work. I soldered a bunch of wires to it so I could test things out, so it looks a bit like a freaky techno-spider now.

To connect the wires from the wheel to teensy I could use a bread board (there's plenty of space inside the racing wheel's base), or screw-in "sugar cube" connectors. The sugar cubes are probably more stable, but breadboard is easier to experiment with.

I also have a few Reed sensors now, but haven't played with them yet. One of the questions is that how far apart the sensors should be so that they don't all trigger at the same time. So... a lot of things to experiment.

The Pedal Machine, Part 1

June 7th, 2019 (permalink)

I know I haven't been able to get much done hobby-wise lately (there's a bunch of pull requests for SoLoud I should go through, for instance) but that apparently doesn't stop me from starting new projects.

So I figured I'll start a new hardware project. The likelihood that it goes anywhere is pretty much zero, considering that my toy-piano-with-raspberry-pi is in limbo (but might get resurrected if I get my hands on a suitable toy piano to mod), and the gametoy thing... welll.. that was pretty much dead in the water to begin with.

In our basement gym (which sounds grander than it is) I've set up a PC as an entertainment center which can be used to watch YouTube or play music or DVDs or whatever. The monitor hooked to it is on the smaller side, as the one I had there died (probably old caps) and I had replaced it with another old monitor I had. I opened the broken monitor up to check on the caps but couldn't see anything obviously wrong, so I didn't attempt a repair. I figured someone might have an old TV panel they want to get rid of, so I browsed local online auctions.

I did not find the panel I was looking for, but I stumbled on something totally weird: a pedalling machine. Think exercise bike, but small enough to put under your desk. The person had been trying to sell it for quite some time with no success, so I underbid but the seller accepted anyway.

Based on the stores where these kinds of machines are sold, I presume they are meant for elders and people trying to get their feet mobile again after some accident.

The pedal machine came with a "computer" that shows number of rotations, RPM, estimated calorie burn, etc. I popped it open and yup, it's a reed sensor.

So I figure I'll turn it into a game controller. After doing some research I figure the easiest way to go about it is to use a Teensy, which is an Arduino variant that is easy to turn into a USB HID joystick. Hook that up to a reed switch, add some low pass filtering and that should work. Having only one pulse per rotation is a bit too inaccurate, but that's easily solved by just adding more magnets; 4 pulses per rotation would be much better.

But what about reverse?

Easiest solution would be to use a button for reverse, but that would be boring. The first idea I had would be to have two reed switches (one each side of the pedalling machine) and interleaved magnets. If we know which way we're going and which side had a magnet last, we can figure out if we changed direction.

Except, if the pedals stop ON a magnet. Then we simply won't know. A solution: use Grey code. Instead of interleaving the magnets, align them, on 10 11 01 pattern (can't use 00 because, well, we don't sense any magnets then). If last signal was 01 and the next is 10, we go one way, if the next signal is 11, we go another. The pattern could be repeated for 6 pulses per rotation.

Except, again, there's a hazard: the 11 signal won't definitely be clean; it'll start with 01 or 10, probably randomly. So let's misalign it on purpose. As long as the error signal matches the last correct signal, it'll be OK. So the expected pattern would be 00 00 01 00 01 11 10 00 00 10.. which would work, but... it feels error prone. Too many things to align, and analogue parts might not keep their alignment.

So my next idea is to use three reed switches. On one side. One after another. Let's call these A, B and C. Whenever B is hot, we wait until only A or C are hot, and that'll tell us which way we're going. This should be less error prone and easier to rig together, even though it requires one more reed switch. That's the current plan, unless I come up with something new.

That covers the acceleration and reverse, but I needed something for the rest of the controls. I considered getting a child's toy steering wheel and hooking an accelerometer in it along with a bunch of buttons, and while looking for one in local auctions I found that someone was selling a broken old racing wheel controller, which sounded perfect so I bought it.

Trying to get information about said wheel is rather difficult. It's been built by Guillemot (aka Thrustmaster) but is so old that it's not even listed on their legacy product page. I found a couple of expired eBay auctions with the same picture, but that was about it. It's got a PlayStation plug so I presume it's an original PlayStation or PlayStation 2 controller. That doesn't matter much, as I was planning on gutting it in any case.

The controller seems to have about 15 digital inputs, 3 analogue inputs, one LED and two motors for force feedback. I get the 15 inputs from two dpads (or directional hats?), gear up/down finger switches and five face panel buttons (including select, start..). The three analogue controls are the wheel itself, plus two analogue finger switches.

Add three wires for the reed switches and we're up to 24 needed pins. Teensy 2.0 has 25 i/o pins (12 of which can be analogue), which sounds quite lucky.

All those controls are hooked to a small circuit board which hosts a mystery chip under a blob, which probably is doing what I'm planning to use Teensy for: read all the inputs, turn them into digital information to push to the host using some kind of digital protocol. Originally the PlayStation one, USB HID in my case.

I've circled the button inputs here. The wires going off the sides go to the force feedback motors. The two black circular thingies are the dpad/direction hat things. Other incoming wires either go to the analogue control potentiometers or out to the PlayStation connector.

I haven't yet figured out how I'll be hooking the Teensy in. I considered removing the circuit board completely, but some bits (like the dpads) are bigger mechanic parts that are pretty much married to the board, so if I wish to use them, the board stays.

I also considered just drilling the blob out, but on second thought it shouldn't matter that it's there. It might eat some electricity, but as long as I'm not touching its outputs, it shouldn't matter.

I'm currently waiting on parts, they should come in a month or two. We'll see if this project goes anywhere..

On Amplifiers and Speakers

March 28th, 2019 (permalink)

Back when we were moving to our first apartment that we weren't renting, we bought an amplifier in order to switch between audio sources in the living room. The one we got was Sony STR-LV500. It did a decent job, even though we weren't exactly using it for its intended purpose. I can't even remember which speakers we were originally using it with, but those were probably just stereo.

When we moved to our current home I hooked my old Logitech Z-540 4.1 speaker system to it so we could have a better movie experience. So yes, we were using a PC speaker set hooked to an amplifier. Not exactly ideal and definitely not what the pieces were designed for, but serviceable. The amplifier did not even "know" that the speakers included a subwoofer.

Fast forward to this year, and our poor STR-LV500 died, probably of heat death, because it was inside a cabinet and someone had stored some magazines on top of it. Oops.

Being able to watch movies (and stuff) in the living room being somewhat critical for a family with small children in this day and age, a replacement was needed. After looking at various reviews and prices for a while we decided to go for Sony STR-DN1080. Brought it in, hooked it into everything and it was fine.

One of the new capabilities of a modern amplifier, which seems to be a more or less standard feature these days, is that it can calibrate itself. You hook a calibration mic into it and run the calibration, and if everything goes well, it can then optimize the outputs so the sound in your room is great.

Turns out, the right surround speaker sounded really tinny and quiet, something we had not even noticed before. No worries, the amplifier can compensate and blast that speaker at higher volume than others. The overall sound sounded much crappier than before calibration though, and then I realized that the calibration option I had picked equalizes all speakers - meaning that it made all speakers sound like the worst one. Oops. Switching to prefer the front speakers fixed that.

The sound still wasn't exactly stellar, and there seemed to be a really small sweet spot in the room where the sound was nice, so we figured we'd get a proper speakers.

Looking at the price of proper speakers, we opted to go for an affordable 5.1 speaker set instead. The ones I looked at first were sold out in Finland. Then I found that practically all speaker sets were sold out in Finland, unless you were willing to pay thousands. So if I had to order from abroad, I might as well do more research on which set to buy.

Back to reviews and downloading various speaker set manuals, I decided to go with Denon SYS-2020. There were 3 or 4 sets I considered, all of them at approximately the same price range, all of them more or less identical in their advertised specs and all came with speaker cables even. One of them had a manual that kept warning about the order of powering things up or things might catch fire, so while I expect that to be overly cautious, I decided to skip it.

As a result now the amplifier can use the speakers as intended, can drive the subwoofer directly - earlier it just "thought" it had four unpowered speakers - and so on. And if we want to upgrade the speakers, we have something to upgrade FROM. The logical thing would be to swap the front speakers with something more expensive, but it's unlikely we'll ever do that. But the option is there.

After re-wiring the whole living room, including well over 30 meters of speaker cables, the resulting sound is clearly better. Sound literally fills the room now, instead of having just a small sweet spot. Wife kept playing her Queen records for a whole day at a pretty high volume.

A few more words about the new Sony amp.

First, it's a damn computer. The previous one probably was, too, but this is even more clearly so. It can play Spotify on its own, for instance. It downloads updates using wifi. It also comes with a remote control that is surprisingly nice to use - typing wifi passwords and such with a virtual keyboard using the 4-way control in the remote wasn't infuriating, which is a first.

It still lives inside the cabinet, but this time I added some PC fans to keep it cooler.

In addition to handling audio input source switching, it also has HDMI inputs and outputs, which is pretty convenient. I even ordered a "mini" AV2HDMI adapter to get the Nintendo Wii output to HDMI, but unfortunately the image quality of that converter was so bad it was unusable, so I still need to switch inputs in the video projector to play with Wii. Not that I often do..

The connections in the amp are pretty funny, because you have a bunch of HDMI inputs and outputs next to bare-wire speaker connectors. I wonder why speakers never standardized to RCA cables.

Like the rug in Big Lebowski, the amp really ties the room together.

Kiku

March 19th, 2019 (permalink)

This is not something I wanted to write, not this soon.

After we had had our first dog, Jana, for a few years, we figured that we'd like to have two dogs as they'd keep each other company. We went with mixed breed again, as they tend to be healthier. We felt that Jana was a bit too small of a dog at 4kg, we felt that a dog at around 10kg might be better. More robust, so to say.

We found an ad selling Sheltie mix pups, which concentrated more about finding good homes than asking for a lot of money. Sheltie is basically a miniature Lassie, and even at their heaviest they tend to be around 12kg, so it was a safe bet.

Kiku was born on a farm in the middle of nowhere, in a town where everybody seemed to have the same surname, and there was just one taxi in service. Luckily said taxi was around when we hopped off the bus, because otherwise we'd have been tad bit lost.

The taxi driver took us there and came in with us to look at the pups. The litter was mostly females, with a single male pup who looked extremely tired. That wasn't Kiku though. Kiku was the pup who managed to always escape the box in which the pups lived.

Apparently nobody knew what breed the father was, as nobody knows for sure who he was. The suspected dog - a stray - was slightly bigger than Kiku's mother, but not by a lot. Kiku's mom was running free in the farm and at some point became hungrier than usual...

We went back home and fetched her when the time was due. We set up a box for her to sleep in, but she didn't care for it, wanting to explore the world. We got her a doggy bed soon after.

She was basically housebroken from day one, scratching the front door when she had to go, sometimes in the middle of the night. I remember standing outside at 3am hoping the dog would just do its business so I could get back to bed.

She ended up being a bit bigger than expected. Her weight was around 20kg - not especially huge for a dog, but definitely not what we were expecting. That didn't really change anything for us, it was just a bit of a surprise.

Partially because of her size, she never begged for food. She didn't have to - she could just lean on a table and take food instead. We tried to train her out of this forever using lots of different tricks, but when she gets an instant gratification from doing the bad thing, what can you do?

And like all dogs she was repentant when she got caught, but that didn't really stress her. She didn't mind being yelled at, for, say, eating a whole duck breast off the table. But if humans yelled at each other... that stressed her to no end.

So we soon learned not to leave food on the table, as it might magically disappear.

There had been cats at the farm where she was born, and she had nothing against cats, to begin with. Then one summer she suddenly got deadly scared of cats. We suspect that while we were at the summer cottage, she had become curious about the cat some of our relatives had, and said cat hit her with a claw. After that every time she saw a cat she went into total panic mode, hairs sticking out of her neck and all.

On walks she was totally different from Jana. Whereas Jana just wanted to sniff around, Kiku wanted long and fast walks. Sometimes we would take the dogs on separate walks due to this.

Oh, did she ever love to run. While she was a young dog we lived in a neighbourhood with a couple of dog parks in a walking distance. She would get other dogs to run with her, and with the exception of a greyhound, she was always faster than any other dogs in the park. When she got to run freely in a forest, she was a furry rocket that you'd hear approach and then zoom away again.

She would run inside our apartment, jumping on a sofa so hard it toppled over. Our carpets didn't stay in one place for long.

On the sea shore, she was suspicious about water, but eventually learned to enjoy swimming in the summer, fetching floating toys that we'd throw in the water.

On one hand her fur was convenient so even in a muddy weather you could just wipe the mud away, she also shed fur a lot. We tried various brushes to help with this, but nothing worked until we found Furminator which worked like magic. That's not a sponsored link. The local birds have made their nests for years from the fur Kiku shed.

Apart from the eating-off-table thing Kiku was very obedient and kind dog, patient with children. One of the commands she followed was me snapping fingers twice - she would come right away. I never actively taught her that; it was just something I did naturally when calling for her, and it stuck.

She was our living doorbell. Hardly ever anyone needed to actually ring our doorbell, as Kiku would be barking at the door well in advance.

If we'd lay down in bed, she'd love to curl into a ball behind our legs. She stopped doing that a couple years ago, and seemed to prefer sleeping on her pillow in a corner.

Of all our dogs, she was the most stereotypically dog-like. Guarding the home, suspicious of strangers that would come to our home, tracking animals like rabbits or even people, etc. She didn't like it when someone left home without her, and was filled with joy when a familiar face came home.

She was a tracker, she was a shepard, she was a hunter, she was a companion. She was a good dog.

Her size turned out to be a problem when travelling. Taxis that didn't have a trouble with lap dogs wouldn't take us. In one particular case we got off bus and went to take a taxi, and all five taxi drivers at the bus station started looking in different directions avoiding eye contact. After we confronted them they said that there's one taxi that takes dogs, and luckily she was on duty at that time, so we got where we needed to go.

While it was never a problem finding a dog sitter for Jana, nobody really wanted to take Kiku, again because of her size. And when we did find someone who could dogsit Kiku, they'd end up getting dogs of their own, and oddly enough in every single case said dog was aggressive towards other dogs. Bad luck, I guess.

She was crazy about pizza. If we left pizza leftovers in their cardboard box on the counter, she'd eat the box. We learned to put the pizza boxes well out of reach.

After Jana died, we got a new puppy, Neko (yes, we know what it means, that's the joke). Kiku really did not like the newcomer, and it took several weeks for them to get along. Eventually they did play together, both of them initiating play at one time or another.

We did know that Kiku was a rather old dog, and being a larger dog than Jana had been, she probably wouldn't last much longer. In fact, before Jana died we wondered which one would go first. Statistically, larger breeds tend to live shorter lives.

We noticed a month ago that Kiku had lost weight. The vet said it's probably just due to old age, but that the weight loss is not good. So we increased her diet, and she regained weight, albeit slowly.

Then one evening we noticed that one of her hind legs was noticeably wider than the other.

The vet took x-rays and said she had never seen anything like it. It was an aggressive tumour, and it was inoperable. Removing it would basically require completely removing the whole leg, and even if she survived that massive operation she was such an old dog that she probably wouldn't learn to live on three legs. And it was likely that wasn't the only tumour.

Additionally, the vet told us on the phone that the as the tumour grew it was bound to cause her constant pain, which dogs are notoriously good at hiding.

Jana's passing was sudden and tragic. Jana had had a lot of health problems before that, though. Kiku had been a healthy dog most of her life, rarely needing any medical attention, and even then it was about small things like cleaning of her teeth or some antibiotics for infections.

We wanted to do right by her. Should we wait? Wait to see her condition rapidly grow worse until she can't even stand up by herself, or when she has trouble pooping? Or should we let her go now, before her condition got unberable?

We reserved a time for her to be put down about a week later. Today.

We bought pain medication for her and continued with life. During that week we noticed that she was getting up more rigidly. We noticed that she didn't want to go down stairs - a pitiful obstacle that had probably never even crossed her mind before. And this with the heavy pain medication she was under. Like the vet had said, it was clearly time.

She loved to tear used napkins to pieces.

If you knew you'd be dying next Tuesday, would you want the days to be special, or ordinary? We opted for as ordinary as we can make it, except she got more treats. No need for her to steal food off the table, we simply gave her all the leftovers.

She loved to eat fish.

During that week, it was strange to find ourselves already missing someone who was still there.

Why does it hurt so much?

She loved..

Last night we ordered pizza, because she had loved it.

She was stressed about our crying for her.

Now she's no longer in pain.

And I know my pain will fade.

Eventually.

Magic Streams for PhysFS

February 23th, 2019 (permalink)

Update 5th April: The patch was not accepted upstream, which I understand and respect, so I've posted fork on github if someone wants this.

Once upon a time, I made a virtual filesystem called CFL (for "compressed file library"). The idea behind the library was to have a bunch of files in a single file, each filtered and compressed separately for efficiency, and then have applications access said file as if the contained files were separate files.

Additionally, several of those files could be mounted at the same time, and if duplicate filenames existed, the mounting order would dictate which file was opened. So, if you have your main game data as one file, and another one for "patch" data to override the main game data, you have a game mod system.

This isn't exactly a new idea. For example, DOOM .WAD files work this way. Nor was I the last one to do something like this. Possibly the most popular incarnation is the oddly named PhysFS library.

The last release of CFL is called CFL 3r3, being the third release of the third rewrite of CFL. That release is from 2009. I had a lot of crazy ideas for the next rewrite, including even more complicated filter system which would basically have required a scripting language to perform the compression. One of the ideas I had for that rewrite are magic streams.

Now that it's 10 years since the last CFL release, and I'm not seeing much of a chance that I'll spend time on doing another rewrite (even though I've started several times in the past 10 years), I got the idea of implementing the magic streams on PhysFS instead. I've sent a patch to Icculus, and we'll see if he feels isn't a good idea to include it or not.

So, what are magic streams?

Back when the original Xbox was a thing (to be confused with Xbox one), all games were released on physical, spinning discs. Seek times on such a media is huge. One of the release requirements from Microsoft was that games should start under certain time limit, and I remember reading from post-mortems how some teams had trouble hitting those time limits.

Magic streams help with this by completely eliminating seeks from file i/o.

Using magic streams is a three-step process. First, implement your application. Second, record magic streams. Third, use magic streams.

When implementing your application, you will open and close various files, seek around, read bits here and there. Maybe one file tells your application to load other files. Level files may read mesh files which may read texture files, for example.

Recording magic streams is done by first identifying deterministic file i/o segments, such as initial game load or level loads, and adding code that writes all file i/o into a linear file.

Using magic streams simply replaces those recording commands with reading ones. Your application still thinks it's opening various files, seeking around, etc, but in actuality everything is read from a single, linear file. File opens and closes become no-ops. File seeks become no-ops. Ftells actually read values from the magic stream.

The magic stream itself can also be compressed to reduce disk i/o further.

The obvious downside is the potential additional disk space usage. If a file is used several times by an application, it will probably be included in one or more magic streams as well as potentially being included as is. Also, patching applications becomes more of a hurdle if a resource that's included in a magic stream is changed.

Automating magic stream creation may also be tricky.

Modern technologies like flash media, SSDs and huge disk caches reduce the benefits of the magic stream, but does not eliminate them completely. I do not (as of yet) have any real data from existing games, as setting such up takes time, but my synthetic benchmark shows a case where using magic streams speeds up file operations so much that the test only uses 25% of the base time. This is, however, a synthetic benchmark and actual results will depend on the use case.

Synthetic benchmark results
Without magic streams: ======================================== 416ms
With magic streams:    ========== 98ms

On False Vacuums

January 18th, 2019 (permalink)

One of the doomsday scenarios going around is the false vacuum, a theory where our universe exists as a bubble that may collapse at any point.

On a walk today I started pondering about what it would look like if this was the case, and considered sending a mail to xkcd "what if", but it would probably be ignored. So I kept pondering.

Let's say the collapse happens at the speed of light or faster (because, when talking about universe-scale things, laws such as speed of light don't necessarily apply) we don't really need to care about it, because we won't have any warning. It just happens, and from one moment when everything exists, everything stops to exist on the next.

If the collapse happens slower than the speed of light, things get more interesting. The farthest star we've detected so far (as of April 2018, anyway) is about 9 billion light-years away. The light we detect that originated from it has spent 9 000 000 000 years travelling. Let's say the collapse happens at 99.999% light speed. When it hits the star, light has just escaped it and races towards us, and when it finally reaches us, the collapse has spent those 9 billion years racing behind it, only 0.001% slower, and when we have a chance to notice that something is wrong, we'd have.. 90 000 years left. I'm pretty sure that's enough time to crypto currency mine ourselves to extinction.

But what would it look like? Would space just cease to exist, or would it contract? If it contracted uniformally, we wouldn't see anything weird happening, because we also exist in the same space. Well, laws of physics might break, but if that was the case, we wouldn't be around to notice it. Not for long, anyway.

How I'd imagine it would be that space contracts very steeply at the border, so it would look pretty much like a wall of black hole approaching us. How large the event horizon would be I am not qualified to figure out, but that border area just outside the event horizon would be rather strange. Stars would seem to accelerate steeply away from us and then disappear.

And it would be a wall, not a sphere, because the probability that we're at the center of the universe is rather small. We're likely to be somewhat off to some side, and thus the collapse would look like an approaching wall to us.

None of the above has any real physics behind it, because I don't have any theoretical physicists on call, but it's interesting to think about, nevertheless.

Now, what if the area outside the event horizon was really, really large? That would mean that it would seem like the universe was expanding, everything accelerating away from us.

Which it kinda does look like..

The Annoying Weight of Being Nice

January 9th, 2019 (permalink)

I consider myself a nice person. When someone asks for help, I tend to do so, unless they're approaching the matter as if they're owned something. A little bit of humility goes a long way.

Sometimes things that are difficult for someone are easy for me, and I go out of my way to help.

And then, there are situations where we do something wrong. We own our mistakes. We apologise. We try to make things better.

If, for example, our dog does something, we do something that nobody in this town seems to even consider: we apologise. We try to make things better. When I've been in situations where a dog escapes a yard and comes barking at me, scaring me, it seems like nobody apologises. (This has happend to me several times, different people, different dogs). In some cases they're just angry at me, as if I did something wrong that caused their dog to escape their yard and come barking at me.

At one point I held a door open for someone so they don't need to - and thus we both would continue our journey faster - and the person stopped and started calling me names for some reason.

I live in a town with both finnish and swedish speaking people. It feels like many of the swedish speaking people absolutely refuse to try to comprehend what I'm saying. When they talk, I try, with my limited swedish, but talking to them feels like they consider me a sub-human. If I say happy new year to people, the finnish speakers answer, the swedish speakers act as if I didn't exist. Mind me, not everybody, but this has occurred several times in the past few years.

It feels unfair. Why must I be nice when nobody else seems to bother?

It's because if everybody was nice, this would be a lot nicer place to live. I want to make the world a nicer place to live in.

It becomes even harder when people are not nice to you in a way that you can't publicly talk about it, because then you become the target, you're the bad guy, even though it's you who have been wronged.

There's freedom of speech, but not freedom of consequences. Even if you're in the right. People who are not nice to you might be able to be really not nice to you if you rock the boat.

And so it feels unfair.

I hurt. I feel bad. And I can't even talk about it. It's not physical. It's not material.

I'll be fine.

Alesis DM Lite Drumkit

January 6th, 2019 (permalink)

About six months ago I was visiting some store with my family and there was a test unit of the Alesis DM Lite kit on the store floor. My son tried it out and said he'd want one to play with. I checked the prices once we got home and it's pretty cheap - around 200-300 eur. I can't remember the exact price (and we bought a bundle anyway) and I can't seem to find it for sale anywhere anymore. Since the kit also works as a USB MIDI device I figure it's not a total loss if the kids get tired of it.

The kids got tired of it.

I plugged the kit to my PC and tried recording something to Reaper, and.. it didn't work out. For some reason the data wasn't in the format the program expected. I left that be for a few months, and came back to it just recently.

I figured I could use a MIDI feedback driver / virtual MIDI cable (the one I actually used was loopMIDI) and a simple program that listens to the drum kit and transforms the commands to whatever the DAW and/or VSTs expect. This was actually pretty easy to do using the RtMidi library (which I find much nicer to use than PortMidi - I'll have to change SoLoud piano demo to use RtMidi at some point).

After setting things up to transform the commands to whatever NI Polyplex wanted, things were much more fun. The drum kit itself contains a few different sample sets but nothing really "fun"; by using VSTs, you can have just about any kind of sound, and feed it through filter stacks too for even more crazy experience.

That's when the trouble started.

The connection to the drums seemed to die in 1-20 minutes, totally randomly. In the interest of brevity, here's a list of things I tried (but are not limited to):

  • Sending timing messages back to the drum kit.
  • Sending dummy note on messages back to the drum kit.
  • Sending actual note on messages back to the drum kit.
  • Disabling Win10 USB sleep power option in power plan.
  • Disabling Win10 USB sleep in device manager.
  • Restarting the USB port with windows driver kit devcon.exe.
  • Updating to Windows 10 version 1809.
  • Changing the drums to a powered USB2 hub from motherboard USB3.

The last bit seems to have solved the issue. Now, what I think happens, or has happened, is that in pre-win10 versions windows had this funny habbit of "checking" if USB devices are awake by shouting at them all the time, causing them to stay awake. This was pretty bad for power saving, but made it possible to make USB devices that left keeping the connection alive to windows.

Then Microsoft fixed that behavior, exposing the bugs. I've seen some USB devices which outright refuse to work unless you explicitly set the USB power saving off in device manager. For some reason turning those options off did not help in this case, which may be a bug in windows. There's also probably a bug in the drum kit itself that leads to the connection not being kept alive. If either of these bugs didn't exist, this wouldn't be a problem.

When I moved the cable to the powered hub, I took away windows' power of powering off the USB port. Moving from a USB3 to a USB2 port may also be a factor. In any case, there's a workaround, and there's bugs somewhere, possibly in multiple places.

The utility can be downloaded for windows here on github.

Well, That Was 2018, Let's Try Again Now

January 2nd, 2019 (permalink)

To rephrase myself from last year, let's hope 2019 ends up better than 2018 was.

2018 wasn't exactly a great year for me personally, and the global politics didn't look too great either. Maybe this year will be better?

Take a deep breath, and have a zen moment with the new year's demo:

If you have VR gear you may want to download the demo as it contains an OpenVR version too. A making of writeup is also available.

Last year my mother died, followed (within days) by my dog dying. On positive side I finally got over my SoLoud burnout and got a new release out, which fixes tons of bugs. I also got the SoLoud manual out to print, so if you want a physical copy of the book, just use your favorite online bookstore, it might be listed there.

I did not manage to write more, even though I hoped to.

I did, however, get a VR kit together and tinkered a bit with it (see the demo above for an example). I did not finish the zx spectrum game I've been tinkering on, but I managed to make a build system by accident.

I won the 20th TMDC. There has not been a TMDC after that.

So what will I try to do this year?

I want to release a game. As in, for money. Itch.io is a likely platform. Steam, I suppose, could also work since they publish basically everything now. I already have a bunch of game prototypes I could start from, I just have to pick something I can realistically finish in a very short timeframe. Don't hold your breath though, as game projects tend to eat deadlines for breakfast.

Since it's trendy, I'll try to form a new habit or two. I've been solving some puzzles in the mornings for a month or so, so I figured I could throw money at brilliant.org and work through their material in the mornings. Everything there seems to be bite-sized so it shouldn't be impossible to do just a little bit of that every morning.

My doctor has also told me to track my blood pressure. That's another thing that needs to be a habit that I do regularly or I won't remember to do it.

Those three things should be more than enough goals for one year.

Not resolutions.

Goals.