December Adventure 2024
The following in my journal for the December adventure. The most recent post will always be a the top. You can jump to the start here.
The End
Well, I had a lot of fun doing this. But unfortunately, I kinda forgot about it. Like all things that demand routine, I eventually lose the interest to up keep them. I made a lot of progress on LVERA and learning how to use Vera to make games.
I ended up getting side tracked making a different project in Godot. The new year is here, thus the December Adventure is over.
December 22th (Day 22)
(back to latest day)I don't think I did any coding yesterday? Did do some art.
Anyways, Chrono Inertia used a lot of Lua to iterate over my game objects. It was convient at the time, but I'm feeling myself out grow it. I have one especially nasty loop to handle bullet collisions. Additionally, I want to flatten down my object handling so I can see ways to generalize it.
Also we really need to rename "zombies" to "enemies". They aren't really zombies anymore.
I decided my first targets would be some easy loops for drawing and moving zombies:
|#port, on draw zombies, needs, @draw zombies| |#port body, on draw zombies, lua| local zombies = state.zombies for _, zombie in ipairs(zombies) do love.graphics.setColor(0.145, 0.133, 0.169) love.graphics.circle("fill", zombie.x + 5, zombie.y + 5, zombie.r) love.graphics.setColor(0.882, 0.314, 0.282) love.graphics.circle("fill", zombie.x, zombie.y, zombie.r) love.graphics.setColor(1, 1, 1) end |#port, on delete killed zombies, needs, @delete killed zombies| |#port body, on delete killed zombies, lua| local zombies = state.zombies local survivers = {} for _, zombie in ipairs(zombies) do if not zombie.dead then table.insert(survivers, zombie) end end state.zombies = survivers |#port, on track player, needs, @track player| |#port body, on track player, lua| local player = state.player local zombies = state.zombies for _, zombie in ipairs(zombies) do zombie.angle = math.atan2(player.y - zombie.y, player.x - zombie.x) end |#port, on move zombies, needs, @move zombies| |#port body, on move zombies, lua| local zombies = state.zombies for _, zombie in ipairs(zombies) do zombie.x = zombie.x + state.dt * math.cos(zombie.angle) * 310 zombie.y = zombie.y + state.dt * math.sin(zombie.angle) * 310 end |#port, on do player collisions, needs, @do player collisions| @player hit |#port body, on do player collisions, lua| local player = state.player local zombies = state.zombies for _, zombie in ipairs(zombies) do if not zombie.dead then local min_distance = player.r + zombie.r local distance = math.sqrt((player.x - zombie.x) ^ 2 + (player.y - zombie.y) ^ 2) if distance < min_distance then counters["@player hit"] = 1 zombie.dead = true break end end end
I don't like there ports for a few reason:
- I'm repeatedly iterating over my zombies array.
- I want these ports to handle a selected entity, rather than bulk process.
- I'd rather describe the process of iterating in Vera.
The first one is just uncessary waste. The second one allows me to thin my ports out, reducing them to the smallest bits of logic I need to do in Lua. Finally, the last point lets my write out my logic using "plain english" rather than code.
I ended up making the process harder than it needed to be cause I wanted to implement comparisons in Vera. But, after debugging that, the rest flowed smoothly. I'm quite happy with the code describing my looping process:
|_| Generic loop set up and tear down |set up zombies loop| , @zombie index , @get zombie count , compare @zombie index to @zombie count |clean up zombies loop| , clear @zombie index , clear @zombie count |_| The loop needed for drawing zombies |drawing zombie, @zombie index > @zombie count| |drawing zombie, @zombie index = @zombie count| , @draw zombie |drawing zombie, @zombie index < @zombie count| , @draw zombie , draw next zombie |draw next zombie| , @zombie index , drawing zombie , compare @zombie index to @zombie count |clean up draw zombies loop| , clean up zombies loop |_| trigger for drawing zombies |draw zombies| , clear @zombie index , set up zombies loop , drawing zombie , clean up draw zombies loop |_| The loop needed for updating zombies |updating zombie, @zombie index > @zombie count| |updating zombie, @zombie index = @zombie count| , zombie updates |updating zombie, @zombie index < @zombie count| , zombie updates , update next zombie |zombie updates| , zombie tracks the player , zombie moves towards player , zombie checks for collision with player |zombie tracks the player, game is paused| , game is paused |zombie tracks the player| , @zombie track player |zombie moves towards player| , @move zombie |zombie checks for collision with player| , @check zombie hit player |update next zombie, @zombie hit player| , @player hit |update next zombie| , @zombie index , updating zombie , compare @zombie index to @zombie count |clean up update zombies loop| , clean up zombies loop |_| trigger for updating zombies |update zombies| , clear @zombie index , set up zombies loop , updating zombie , clean up update zombies loop
Without Mira, generalizing this further is gonna take some deeper redesigning. I don't want to do that just yet, tho. Yumaikas might have just found the missing piece we need to progress on Mira. Additionally, I want to clean up my other loops and implement some debugging tools.
We should definitely add some of the creature comfort code gen passes to LVERA. Having to hand write(back to latest day)clear
operations is a bit tedious.Granted, I haven't found anything truely painful enough to warent the side tracking. The old experience with ports was so bad it made serious usage of LVERA hard. Also, hand writing constants sucked. Viznut was so tedious to do.
December 20th (Day 11 through Day 20)
(back to latest day)Whoops, I forgot to do my log. So, [paper shuffling] I've been busy. On the 11th, I decided to try remaking an old demo from the original Nova prototypes. It was also a remake of an old Scratch game. Well, fast forward 10 days, and it's kinda ballooned into a whole game:
I also started work on a sideventure in Godot. I've been wanting to make a little VN thing again.
It honestly started out as an accident. My pause system was bugged and the player could keep shooting. But, then I realized it was kind fun, so I leaned into it. Despite being so early into development, adding features and refining my game was quite easy with LVERA.
I have plans to develop Chrono Inertia out into a fuller game. Quality of life improvements, enemy variety, and special effects. I've also got some idea for better entity handling. I didn't expect LVERA to be so powerful so earily into it's existance, but it's got me gunning for a full release of Chrono Inertia.
I don't think I could be happier about getting derailed.
(back to latest day)December 10th (Day 10)
(back to latest day)Today was a boat load of rewriting (pun intended) of LVERA. I've finally cleaned up it's fact representation. Additionally, I removed computing the initial state of the program from the parser to the compiler, so the parse step is even simpler.
This change also meant removing the hack and work arounds I had been using for annotations as well as constants. I'll have to rebuild them in the code generator, but I decided to try out Wryl's approach for breaking down large constants. The idea is similiar to trying to find what coins you need to form a number. This process can turn a impossible to represent number into a more compressed form.
The compiler output is pretty neat to see. However, this process introduces noticable overhead. So, I'll be working on making the code generator aware of constants. The preformance benefits are to good to lose.
(back to latest day)December 9th (Day 9)
I did not get enough sleep. Didn't do much. Did start a small sketch of a game. But, it was mostly just fiddling with UI in Godot until I felt I had a base I could build up from.
December 8th (Day 8)
(back to latest day)I managed to power through a game of Tic-Tac-Toe in Maude. I don't have many positive things to say about the language. It's documentation is absurdly obtuse. Eventually, I just kinda pulled the parts I needed to make a game of Tic-Tac-Toe.
I ran into a frustrating footgun. As you go through the Maude manual, you get used to variables just binding data. But when you get to objects, they suddenly become identifiers for those objects. However, this only applies to variables who's name is longer than a letter.
I lost like an hour of my life to this asinine decision. Serious what the fuck? Why the fuck???
So, uh, no that was a me error. I uh... didop RowX .
when I should havevar RowX .
...
I also now thoroughly appreciate Nova's eval order. Maude's order is effectively non-deterministic. By it's nature all rules are concurrently active. There is some definition of "fairness" I could never figure out. But it made reasoning about things painfully hard.
I worked around this by turning my Game object into a state machine. Transitioning it through out my application. But, anytime I looked away for just a little to long, my place was totally lost. It's definitely made me strong believer that practicle and usable rewriting systems need user defined orders.
I will say the object system was kinda nice. But, it's definitely weighed down by the rest of the language.
Also, I cannot believe this language allows you to forget to fully initialize objects... All this formal verification mumbo jumbo you let me make that mistake?
My curiousity with Maude has been sated. I'm heading back to Vera ._.
(back to latest day)December 7th (Day 6 and Day 7)
(back to latest day)Last night was a bit of an intense sketch session with LVERA. I wanted to figure out a means of working with objects from Vera. Additionally, I wanted to experiment with giving Vera as much control over Lua as I could. The first step in this involved rewriting my main loop a little bit. For the text editor shown previously (Day 4 and Day 5), I was using this ugly bit of code for polling events:
function machine.on_poll_inputs(counters) events = { key_presses = {} , key_releases = {} , text_inputed = {} } love.event.pump() for name, a, b, c, d, e, f in love.event.poll() do if name == "quit" then counters["@quit"] = 1 events["quit"] = {a} elseif name == "keypressed" then events.key_presses[a] = {b, c} elseif name == "keyreleased" then events.key_releases[a] = {b} elseif name == "textinput" then table.insert(events.text_inputed, a) else events[name] = {a, b, c, d , e, f} end end end
What matters is it got the job done. However, there is a lot to desire here. Firstly, this bit of code is extremely wasteful. It makes dozens of tables a frame. Additionally, it depends on Lua to gather all the events. I would rather have Vera control this dispatching. Additionally, giving control to Vera grants me a chance to reduce the garbage generated from event polling. After some sketching, I arrived at this solution:
function machine.on_start_polling(_) love.event.pump() state.event_iter = love.event.poll() end function machine.on_poll_input(counters) local name, a, b, c, d, e, f = state.event_iter() if name then local counter = "@event " .. name if counters[counter] then counters[counter] = 1 state.event_args[1] = a state.event_args[2] = b state.event_args[3] = c state.event_args[4] = d state.event_args[5] = e state.event_args[6] = f end else counters["@no events left"] = 1 end end
This bit of code (should) be as wasteful as love.run
(only wasting an iterator and the values from it). The code now constructs LÖVE's event iterator, stores it in my global state, then polls it in a different event. Each call to on_poll_input
will do the following:
-
Poll the event iterator.
- If there was an event then:
- If there is a counter for this event then:
- Raise flag that an event happened.
- Store event arguments.
- If there is a counter for this event then:
- If there was no event then:
- Signal that there are no more events.
- If there was an event then:
The other challenge was working with objects. For this task I choose to keep it simple and just work with circles. I came up with the following scheme for interacting with them:
|#port, on new circle, , needs, @new circle , takes, @new circle x , @new circle y , @new circle r , @new circle speed| |#port, on draw circle, needs, @draw circle , reads, @circle index| |#port, on move circle, needs, @move circle , reads, @circle index|
I also wrote some code to generate them randomly.
The idea here is that I feed @new circle
the initial state of the circle. It calls out to some Lua code to construct the data. Then, I have two other built-in rules handle moving and drawing. Those two rules expect @circle index
to point towards what entity you want to interact with. I feel you could generalize this further, but you will absolutely need a compiler pass for it to not suck.
This is where I had much more ambitious goals, but turns out I needed to figure out the basics first. This day will be important for later.
When I got up this morning, I decided to mess with the new event handling and add key press logging:
... |#port, on save key pressed, needs, @save key pressed| |#port, on print key pressed, needs, @print key pressed| ... |draw each circle, iteration| , @circle index , @draw circle , draw each circle |draw each circle| |draw circles| , reset @circle index , copy circle count to iteration , draw each circle |print fps| @print fps |print key pressed| , @print key pressed |drawing scene| , draw circles , print fps , print key pressed ... |handling input, @event keypressed| , @save key pressed , spawn 100 circles |spawn 100 circles| , make circles:100 |make circles, @once| make circle, circle count, @once |make circles, @once| ...
The end result is this fun little demo:
That's basically all for Day 6, and that small bit of coding is my Day 7. Not gonna do anything big for today. I still want to do hot reloading of rules. So, I might extend this program for my Day 8.
(back to latest day)December 5th (Day 5)
(back to latest day)I decided I wasn't satisfied with my small text editor experiment. So, I went back and expanded upon it. It now supports fully improved 2D motion. I wrote about it on my mastodon account.
This originally started as a scrappy demo, but I decided I wanted to implement more than just lateral movement. My first attempt at any kind of text editing was a train-wreck. Managing my state, as well as handling the events and conditions, was a ball of barbwire. However, this time around things feel like they are going smoothly.
LVERA's built-in rules allow me to cleanly pull my events and stateful actions apart. I remember struggling to keep my code legible. Wires from different parts of my state were getting tangled everywhere. There is a good reason why my code has so many line by line comments. The actions I wanted to do were intractable from their implementation.
Using Vera as my scaffolding, I got to enjoy having a "plain english" descriptions of my logic. Then using built-in rules, I could fill in the gaps as needed bit by bit. This was a much nicer experience than having to deal with the whole hog all at once. I like to think the code for it all is pretty easy to follow:
I'll will have to revisit this. Basic text editing is something my little project will need. But I need to move onto a new challenge and more important challenge: entity management.
Originally, my work and interaction with Nova started in a language called Mira. I spent a few days remaking my old zombie game using Mira. It went quiet well, until performance problems happened. Bad performance problems happened. But, these problems resulted in Vera as a solution which has been much sturdier.
However, since Mira has been put in the background, handling and managing objects in Nova has been left unexplored for a couple months now. However, this project will need the ability to manage events for entities. So, maybe it's time to resurrect zombies.nv
?
December 4th (Day 4)
(back to latest day)Continuing to hack away at Vera stuff. Late last night / early this morning, I wrote an extremely basic text inputting system in LOVE using LVERA. After some fiddling around, I have extended it to support some more editing features. The cursor can be moved through the 2D space of the text, lines can be insert and deleted as well. This is going much, much better than my first attempt.
As always, I've pushed my changes to the LVERA repo. Slowly taking steps towards my grander goals.
(back to latest day)December 3rd (Day 3)
(back to latest day)Today's task was getting something working in LÖVE. This is the first step toward my goal for December Adventure. After a bit of fiddling and ironing out mistakes on the Lua side of things, I finally have a hello world screen:
Before you ask, I did *not* any kind of string handling in LVERA. That will have to happen in some form. The application I want to make requires it. I've got a few, application specific, ideas for it. It definitely won't be a general approach to string for Vera nor The Nova Family. Finally, I updated the my Collatz conjecture example to show off ports a bit more.
(back to latest day)Oh, I also did a bunch of work on the Nova wiki.
December 2nd (Day 2)
(back to latest day)I realized dating and numbering these is redundant but oop.
Some small work done on LVERA to add support for basic importing. I haven't yet pushed those changes, and it's already past bed time so, oop. However, I spent most of the day messing with the Vera playground. Sierra, recently added support for some basic UI elements.
This opened up the door for a small group collab session where we built a etch-a-sketch like program. Then, we later hot-patched a generated Vera program to add support for external controls. Being able to add a button that restarted, what for other languages would be, a while loop is a new realm of possibilities.
I'm hoping to get started on my actual project tomorrow. I think I have all the pieces set to build something interesting.
(back to latest day)December 1st (Day 1)
(back to latest day)Okay, technically I'm writing this at 12:51 AM on December 2nd, but nearly everything was done on December 1st.
Everyone knows tomorrow only starts when you go to bed and wake up :V
I've ripped appart LVERA's ports, annotations. and compiler. Ports have been replaced by stubs. Annotations have been replaced by compiler passes. And the compiler backend is now a module you give to lvera:compile(backend)
. The end result is the following experience on the Nova and Lua sides:
|_| Convert our parity encoding back into unary, looping as needed. |show x| , copy x to @number , print number |copy x to @number| x -> @number:9007199254740991 |x -> @number, x| @number, temporary x |x -> @number| |temporary x| x |print number| @print number |move, 2| move, x |move, 1| x |move | hailstone |_| Compute 3x + 1 |odd, 2| odd, x:6 |odd | hailstone, x |_| compute the parity of x |eval, x, 1| eval, 2 |eval, x | eval, 1 |_| resolve our base cases 3x + 1 and x / 2 |eval, 2, 1| odd, 2, x:3 |eval | move |hailstone| show x, eval |#port, on print number , needs, @print number, clears, @print number , takes, @number| || hailstone || x:27
package.path = package.path .. ";./src/?.lua" local Lvera = require "lvera" local lua_generator = require "generators.lua" local lvera = Lvera.new() lvera:load("samples/hailstone.nv") lvera:add_pass(require "passes.ports" (lua_generator.ports)) lvera:add_pass(require "passes.constants") lvera:add_pass(require "passes.eliminate-dead-code") local compiled_lua = lvera:compile(lua_generator) local out_file = io.open("generated.lua", "w+") out_file:write(compiled_lua) local machine = load(compiled_lua)() function machine.on_print_number(_, number) print(number) end machine:run()
This should give me much more flexible control over extending Nova. Additionally, ports have been freed from twiddling counters by hand. My next task is probably going to be merging in other Nova files and starting some graphics. Next goal is gonna be something like:
|#include| notecards/graphics.nv |set color to white| , @color r:255 , @color g:255 , @color b:255 |draw a circle| @draw a circle |display the frame| @display || @circle x:400, @circle y:300, , set color to white , draw a circle , display the frame(back to latest day)
November 27th (Day -4)
(back to latest day)I've created this section in my wiki section for December Adventure. What I want to focus on is a bit of a double-hitter. My primary focus will be a small visual-narrative tool written in Lua. My secondary focus is refining my implementation of Nova: LVERA.
For a few years now, I've yearned for a tool that focused around turning drawings directly into interactive elements. I've tried various tools such as Bitsy, Flicksy 2, Decker. None have quite scratched the itch I have. They all have a bit too much "editor" to them. One too many menus to navigate. A weird bit of disjointedness.
I want some that feels more like a painting program than a game editor.
Each of them comes with a bit of scripting. Bitsy and Flicksy both use a bespoke and limited scripting language. These languages can do some basic logic, but I've found them cumbersome for logic-intensive projects. Decker utilizes the programming langauge called lil.
It's a compact yet powerful language. It doesn't hold you back from diving head on into complex logic. However, I've found lil pushed me into "thing like a programmer" more than I liked. I want something that doesn't force me to ponder design patterns and encapsulation.
I also had the misfortune of hitting a bug that ended my attempt at participating in Dec(k) Month. This bug has since been fixed, but I've found returning to Decker difficult after that.
However, there are a few "small tools" that have stuck with me: Flick Game and Kooltool. Flick Game presents itself as a canvas, some brushes, a fixed 16-color palette, and fixed 16-scene list. Each color can be assigned a sign to transition to when you click on it. This enables you to make tiny branching narratives.
Kooltool is a interactive tile-based world that you can navigate, explore, and edit. It's highly experimental, however, it provides fuel for the imagination. The editor provides you a sense of space and navigation. Every tile is a canvas you can draw on, automatically updating other copies. You can write text, draw, create tiles, and create entities seamlessly. It's rough around the edges, but I've been enamored with it.
Then, Devine completely blew me away with Tote. Tote is a visual rewriting system. It's fully capable of general computing while only using user created glyphs. It blended art and logic into a magical cocktail. I was completely swept off my feet.
Seeing Devine craft Tote has inspired me to try again. Additionally, I am now armed with knowledge on rewriting system. Interactive fiction relies heavily on managing and manipulating a chunk of global state to drive a story. This is a task that rewriting excells at.
Additionally, by leveraging Nova, I should have something that you could theoretically port and glue into other systems with (relative) ease.
November was supposed to be my time to build something. But, the distress from the US (that's where I live) elections washed out my motivation. However, life continues, I feel inspired. I'm going to create. Hopefully, I end up with something neat at the end.
(back to latest day)