So, I tried doing Ludum Dare 44, and I gave up on it, and I feel
like absolute crap so I'm going to attempt to write these feelings away
in a celebratory post-mortem “I failed!” post.

In typical me fashion, this post is going to be a jolly mix of a lot of
things - hopefully explaining what I was going for is going to make me feel
better about the end result.

The timeline

Ludum Dare always has awful timing for europeans.
This time, it1 started Saturday at 4:30AM, and ended Tuesday at 4:30AM.

On Saturday, I was expecting guests, so I started working on my entry at
about.. 7PM. I had been thinking about the theme for a while, and had no
ideas up until that point - also typical.

I worked until about midnight, when I sorta collapsed under the accumulated
fatigue of the past few months, and the intoxicating power of a single 25cl
beer bottle2.

I resumed work Sunday around noon (catching up on sleep) until about 7PM,
and I worked on it again on Monday from 11AM to about 7PM as well. So, all
in all, that's about… fifteen hours of work, if you take out lunch and
other duties (like dog walking).

I didn't expect to get a great entry out of it, but I was hoping to get
my hands on something that was fun for a few minutes.

Past entry review

I usually try to achieve two goals when doing Ludum Dare: try out a new piece
of technology, and tackle a game genre I like but have never done before.
(This is a flawless recipe for success, trust me!).

Let's review a few of my past entries to get an idea of the variety:

Thorny Weather (LD38)

I had fun making it, it plays somewhat nice, I was happy with the result even
though very few people played it. It was my first time using Excalibur,
but the rest of the stack was familiar: npm, webpack, TypeScript, electron.

Overall, the game is a solid 5/10. Nothing unique, not much content, Kenney's
assets makes it look better than it actually is. It didn't get enough ratings
to be ranked.

Zealot (LD40)

For Ludum Dare 40, I made Zealot,
a 1v1 card game. This is one of my favourite entries. It uses React/Redux and a ton of
homemade CSS to make the cards move the right way.

It even comes with a computer AI, because I sure as heck wasn't going to implement
matchmaking for a Ludum Dare entry.

I'd rate this an 8/10 - it came 34th overall! It was really, really polished, and
Corinne's artwork looks fantastic. The soundtrack
is just a bunch of my old tracks cobbled together.

TASball (LD41)

For Ludum Dare 41, I made Tasball,
which was 20% clicker game, 80% “write a program for a pinball-playing CPU”. It uses
React/Redux and Pixi.js, it was my first time using Planck
for the physics and tone for sound synthesis
(there were CPU instructions to play notes).

I went through a bunch of iterations for the code editor interface, and the level selector
even has neat little previews. The levels are actually SVG! A few friends made levels,
we used Inkscape and specially-named attributes to decide what was solid, what was a goal,
how much it was worth, etc.

The game came 581st overall, but 14th in innovation! It looks like ass, but I can't remember
playing a game like that before, so, yay, go us! (Sebastian Standke provided the initial
idea). After the jam I improved the game somewhat and turned it into an automatically-deployed
open source project (using ansible-pull).

If you're curious, you can play it here until I stop paying
for that server.

I rate TASball a 6/10 for effort.

Capitalism Simulator (LD42)

For Ludum Dare 42, I gave the LÖVE/moonscript
combo a shot - and failed miserably. It tries to be a transport simulator, sort-of
inspired by Simutrans (itself inspired by many other games,
but here we are), and it's just awful.

The game isn't balanced or fun, the art is programmer-made in GIMP (despite all my rage,
I cannot pixel art for the life of me), everything is either under-powered or over-powered,
I did think it was a shame that the end result was that shitty and I just wanted to
rewrite everything in TypeScript.

At the end of the day, Capitalism Simulator is a 2/10. I tried, I failed. Oh well.

Bamb (LD43)

For Ludum Dare 43, I tried to learn my lesson and went with something
simple (not a simulator of
some kind), and that worked out in the past (Zealot was great): a card game.
As it turns out, it doesn't really look like a card game, since the items are
square, but oh well.

We (it was a collab with Veld) had time to make
it look minimal but sort of aesthetic (we even bikeshed over the color scheme),
design and implement a tutorial, I had time to compose one original music
track3 for it.

Overall, for its simplicity and effectiveness (it came 28th overall, and
14th for innovation again), I also give Bamb an 8/10.4

About Ludum Dare 44

With old entries in mind, it's easier to understand why I tried
what I tried for Ludum Dare 44, and why I failed.

The theme is always a bad starting point when brainstorming for
ideas so I figured I'd find a link later5. Nobody liked
the clicker part of Tasball, but I didn't find that surprising,
because it wasn't really fully explored.

So I thought about doing a clicker game. I very much enjoy clicker/idle
games in general. If you ranked my number of hours in video games,
it would probably go:

12 idle games

League of Legends (shh)

The Binding of Isaac (all versions)

Like, six AAA franchises I actually enjoy

(But don't tell anyone, the games I choose to spend my time on to decompress
is a well-kept secret in the industry.)

Of course, nice visuals and nice juice when interacting with the numbers
also go a long way towards making clickers/idle games enjoyable, but I knew I didn't
have time for that.

Most importantly:

The most interesting part of clickers isn't actually the “numbers going up part”

Side-quests are the meat of idle games, whether it's just getting achievements
or something more elaborate like a full gardening mini-game7. The story
is also interesting, and adventure/exploration is always nice to give more depth.

“Amos”, you're starting to worry, “that sounds like an AWFUL lot of work”. Yep,
yep. I know. I know now. long sigh.

Getting started

So, the basic recipe for a clicker is pretty simple: click to make numbers go
up, spend numbers on facilities that make numbers go up by themselves, repeat
until you can't afford to buy facilities, then upgrades unlock, buy those,
rinse repeat.

If you want to get real fancy, you can add a “Prestige” system, where you can
restart the game from scratch, but with some permanent upgrades that you've
obtained in your previous playthroughs.

Prestiging was always out of scope because I wasn't sure if I'd have the time
to finish playing the game.8

Since ideas about making a fun unique little idle game weren't magically
popping in my head (I swear, they used to! I don't know what happened), I decided
to get something up and running.

I've been doing mostly Rust for the past month (I'm rewriting a project of
mine), and I've always been curious about WebAssembly.

I had already tried compiling Go9 to WASM, but the result was unusable:
a large blob that immediately tried reserving something like 2GB of RAM?
(Which, in the WASM world, is “a large Typed Array”). Anyway, Go's large
binary size apparently translates to the web ecosystem, which, no thanks.

But I had read somewhere (and then immediately forgotten) that Rust had
a native WASM target for its compiler. Which makes sense, because it's based
on LLVM, which, if you spend ten years figuring out its API, and another two
building it once, will let you target pretty much anything.

I search for “Rust frontend library” and immediately found THE gem10,
the yew project. At 7.2K stars and
easily a few dozen medium articles, you'd think it would be good enough for
a jam entry right?11

The README links to https://github.com/raphamorim/wasm-and-rust, which
instructs you to download a set of build scripts from Mozilla games (?)
for emscripten (??) so that you can build emscripten (???) yourself.
Fair enough, I got a brand new i7, sure. Half an hour later, I was done
reading various docs and it still wasn't finished, so I discovered,
hey, there are pre-built releases of emscripten, cool!

A few steps later in the docs, nothing worked correctly (if you think
JavaScript stack traces are bad, boy do I have bad news for you), and
I discovered “cargo web” by accident.

(In the meantime, I had gotten pissed that http-serve (the Python thing)
didn't set the correct mime type for .wasm files, so I was about to
write my own Go static http server once and for all - in, like, 20 lines,
and then I discovered that http-server (the JavaScript thing) actually
does set the correct mime type)

To generate JavaScript machinery to load it (because it has to be
loaded asynchronously, and it's a binary blob so we need to fetch() it)

To go the extra mile and generate an index.html file that references
said JavaScript machinery.

Sprinkle on top: the --auto-reload flag will reload the page on changes.
I expected it to use WebSockets like webpack does, but it turns out it just
repeatedly hits the server to grab the “build ID”, and if it doesn't match,
it's a location.reload() for you. Nice and simple (although noisy in the devtools).

It has been a nice enough workflow. There's also cargo web deploy, which
generates the ./target/deploy directory for you, ready to push it somewhere.14

However, I'm going to review a few aspects of the stack, for the curious in the
bunch. Keep in mind that anything working at all is a miracle at this stage,
and that some stuff will get better over time.15

Compilation times

I was right to be worried about compilation times.

A no-op build is 0.14s, which is honorable, but changing even one constant in
the code takes 1.3s. It might not seem like much, but it adds up. To give
some perspective, though, a complete build of the project as I left it, takes
a whopping 1 minute and twelve seconds.16

Auto reload

Auto reload is not hot module reload. For those you who aren't familiar with
React HMR: in JavaScript, you can have an app with some state, change the code to
one component, and have that component be seamlessly remounted with the new code,
using the same state.

So iteration is really fast - you can get the app in some state by clicking around,
and then mess in your editor, save, and a few ms later, you see the changes in your
browser. I've gotten used to that17, so I've really missed that fast iteration speed
in that project.

Bundle size

With a modern packer (that does dead code elimination, and minifying - hopefully with
a working minifier18), you can easily get a complete app in a few hundred KBs,
including whatever parts you use of your dependencies. Especially if it's just a fun jam game.

For comparison, Super Tasball uses fuse-box, and its
dev bundle is 4.1MiB. Its production bundle is 1.7MiB (452KiB gzipped).

For LD44, the current .wasm bundle size is 1.7MiB (478KiB gzipped) - so, about the same,
but Super Tasball is a vastly more complicated project. It ships with a sound synthesis
library, an SVG parser, a physics engine, etc. The LD44 is a bunch of constants and macros.

Error messages

Good error messages are life. They are love. I don't want to ever go out into the
wild without them. rustc has great error messages for example.

But getting an exception thrown in a Rust program compiled to WASM is not something I
relish.

This particular screenshot is of a sample panic!("whoops") invocation I made.
Notice the panic message anywhere? Nope? Me neither!

This was a lot of fun, especially when it panicked deep, deep inside a yew macro.
I lost a bunch of times to “comment out code / compile / reload” or “just reload
a bunch of times hoping the panic message makes it through” (sometimes it does!).

Again, I want to point out that it's a miracle any of this works at all - and for
what it's worth, I do think this is one of the areas that will get better over time.

Macros

Macros are like salt, when it gets in your wounds it really fucking hurts.

In my month-long exploration of Rust I went from positively loving macros,
to calling for the death of all of them, to resigning myself to use them as little
as possible.

(The following few paragraphs will be obscure if you're not well-versed into Rust
or dumb programming language stuff in general, sorry)

The worst macros, of course, are procedural macros - because they let you
mess with the AST in any way you see fit (even generate new identifiers in scope,
yeah!), so I've tried hard to stay away from them.

But yew uses macros for markup - it gives you a bastardized version of JSX. Let's
review:

So far, so good! All functions that render something must return
Html<Model> - I don't know why markup is parameterized by the model, don't
ask me. All text must be some rust strings? (or at least something that
implements fmt::Display), and rust expressions must be in brackets. That makes sense.

Uh oh, looks like someone macroed their way to a dark place. You need
to have a comma after every attribute. It's super duper easy to forget.

There's good news and bad news. The good news is: missing a comma is a compile-time
error. The bad news is: the diagnostic shows anything but:

That's just one of the many, many errors you can see if you miss a comma.
This is what happens when you take that “one cool hack” and build an entire framework
on top of it I guess. Anyway I've enjoyed having large swathes of my code squiggly-underlined in red, trying to figure out which comma I missed.

But wait! There's more! There's a lot of other mistakes you can do when writing
yewSX code. For example, you can have mismatched opening/closing tags. (Oh, I forgot
to mention: you know how rustfmt is great and formats everything? Yeah, that does
not apply to macros. It'll format bits of rust code inside brackets, but not
the actual markup - fair enough).

The only two bits of location information above are: 1) the JS machinery that
loads the wasm (but apparently also does other stuff because it's 32KiB?), and
2) the place where the macro is defined.

Again-again, it's impressive anything works at all, but, yeah, while iterating
on a codebase getting worryingly close to over 1KLOC, this was a huge pain.

Finally, related to errors, something happened that I didn't see coming: one of my
i64 overflowed, and it just… stopped the whole application (because panic).
That sort of makes sense, it was just… surprising.

Ok, so, how did it go

Well, dealing with jank is pretty much my job description so I was able to
navigate my way around the limitations of the stack and start building stuff
out pretty quickly.

I did get stuck for a few hours trying to define a custom type that behaves
like i64, but is formatted differently (different fmt::Display impl), and
can't be coerced/promoted to i64 automatically. I've learned a bunch more
about Rust, but I don't know that I'm happy about that.

So, I'm there building a clicker, and I figure, hey I got an idea to make it
unique. You're playing DEATH. The Grim Reaper. Sickle and everything - ooh, ominous.
You're harvesting souls, which are our cookiescurrency, hey, life is currency,
cool, theme taken care of.

Next logical step: look up what the birth rate and death rate are on earth nowadays.

Okay, so, births and deaths are expressed “per 1000 population” - that makes sense.
The problem I kept running into is that, well, that goes up pretty fucking quick.

Originally I thought well okay, there can totally be an aspect of the game where..
the earth can only sustain so many humans, and growth slows down after a while
(see right graph - I didn't have the graphs when I brainstormed, but that's
pretty much what I imagined). And maybe then the humans could.. develop technology?
And then the sustainable amount of population would go up? And you could optimize
your soul harvest that way? Idk.

So I thought okay numbers going up is fine but what can you actually do - what if..
the souls aren't actually yours. You're death - you're a facilitator. You take souls,
and you give them to either Heaven or Hell (lack of time = clichés, whatever).

As a result, if you spend souls on something else (like facilities, or upgrades..),
then.. you're in debt! And maybe, like19, when you're in debt to Heaven,
you can.. do some missions for them? And they can forgive part of the debt. Or
maybe you have to keep your “credit rating” above a certain level (for example, you
can't owe more than half the total population), otherwise you lose..

..and then it's fun! Because you can lose. So it's not just numbers going up. It's
a balancing act. You have to keep track of a bunch of things and uhh make sure they
line up properly.

And maybe Hell is less forgiving than Heaven with your debt, so you'd prioritize
paying them first. And also maybe Hell has better rewards (dark rewards!), so maybe
at some point you just stop dealing with Heaven altogether because you built defences
using your newfound dark powers and…

Yeah there's a lot of things to pick from here, but I pretty quickly went back to
“okay, okay, you're right, it's way too much, I need to start simple”. I needed to
find a handful of systems that I could implement and balance in the remaining timeframe.

So I implemented the basics. Harvest button you can click to get 1 soul: check.
Item you can buy so that you get more souls per click (SpC) - check. Item you can
buy so that you start getting souls per month (SpM) - check. Buy 1/10/100 buttons
because, numbers are gonna get large, better plan ahead. Even with this minimal
amount of UI, it was already painful to write with yew.20

Plus it wasn't fun. I was out of my element and running out of time so I
didn't feel like spending time messing with CSS or temporary component states to
add juice to the game. It was just boringly going up. A 1s tick rate was less
overwhelming for the player, but really boring. A 100ms tick rate was slightly
more lively (hiding the fact that numbers weren't interpolated), but threw the
balancing completely off.

No matter how much I struggled, the population either stayed desperately low and you
were out of souls to buy stuff, or it shot up waaaaaay to fast and there was no way
to keep up, let alone repay your debt to heaven or hell.

I tried and I tried and I tried simplifying stuff and moving things around.
Debt turned out to be hard to get right, because it was either right on the screen
and then there was information overload all over, or it was hidden in tabs and then
it wasn't obvious you were in debt.

So I added other stuff: I implemented upgrades for items (that took a while to figure
out, what with the borrow checker getting up in my stuff), I implemented a notification
system so Heaven/Hell could speak to you, I ran out of time before I actually implemented
objectives/quests.

Eventually I ended up like something that could have been a fun game, looked
decent-ish, but was just really, really bad:

Conclusion

There's no good conclusion to this. If you enjoyed this post, you might
want to read some of my other stuff, which is less ranty!

You can also follow me on Twitter,
I post a lot of development deep dives there whenever I'm working.

The 72h jam category. I never bother doing the 48h category because
I never have time to finish anything cool in 48h anyway, and I like being
able to work in groups or re-use assets to make the final product better. ↩︎

So much so that I've spent hundreds of hours trying out and coming up with my
own schemes for good HMR in electron. I've lost count of the number of projects
I've tried, forked, patched, then abandoned - in the end, it all goes back to
whatever the latest webpack is. It works okay, even though the dev dependencies
are a bit heavy. Of course, now that everything is set up correctly, I don't feel
like touching that codebase ever again because fatigue. Good job me! ↩︎

Turns out, minifying ECMAScript 2019 is hard, who knew? I've routinely run into
issues where the minifier that was blessed by the webpack team generated completely
whacked out code, that threw exceptions because of subtle scoping fuckups. It only
took a few months of people being puzzled at random breakage for them to bless another
minifier (must be 12th generation at this point), but that one also has
issues, distinct from the previous gen. TL;DR stuff is hard. ↩︎

These paragraphs are a lot more fun if you read them in the voice
of an excited fourteen year-old, which is basically what I am at the start of every jam. Before I promptly turned into a concerned fourty-year-old, and back into a
tired late-twenties human. ↩︎

Ok last disclaimer: I'm not actually blaming the yew devs, and I don't
want a response from them. I was trying something and it got hard and I'm the only
one to blame here. I just wanted to make that really, really clear. We clear? Ok good. ↩︎