My 28th Ludum Dare's Postmortem: "Winnerman Scams a Game Show"
After obtaining the washer and dryer set, three cans of Chef Boyardoes, and a big thumbs up, Winnerman heads home to reflect upon his actions.
Giving out goofy awards to the games I rated.
The recent news of Mike planning to stop hosting scheduled LD events had me feeling a little nostalgic for the old website. Some friends and I decided to give (in addition to the feedback) a goofy "Award" to each game we rate. (Y'all remember that feature of the old site?)

It was fun to scribble something together for those, and a lot of people seemed to appreciate it. I also like how I can look at all of these and remember the game I drew it for. I'm definitely doing this in future jams as well.
Goals for Ludum Dare 59:
Over the past decade of Ludum Dare, I've made way too many platformers. With that in mind, here were my goals.
• Do not make a platformer.
• Add some really cool screen transitions.
Alas, I only achieved one of these goals. I ran out of time, so the screen transitions didn't quite make the cut. But let's see how the rest of the game turned out.
The concept:
My interpretation of "Signal" is a replay device sending input signals to a console. But since I'm not actually emulating stuff, we just have a TAS timeline for you to supply inputs, undo mistakes, and frame advance with, etc.
I've been doing a lot of TAS shenanigans lately, so this seemed like a pretty fun idea to me.
An example of an NES emulator with a TAS timeline supplying inputs.
Though I think I can safely say this was my most ambitious Ludum Dare in a while. I don't think I realized when I came up with the idea just what would be required to pull it off. If the player is able to frame advance, or frame rewind, then I'm going to need to make a fully functional system of savestates to go with the timeline that I need to program.
How the finished game looks with the timeline on the right.
How does the timeline work?
I just recently added a TAS Timeline to the NES emulator I've been working on, so I had a plan on how this would work. First of all, the timeline will control the entire rest of the game. The timeline's frame advance function will trigger the Update() functions inside every minigame's script. This keeps the game on tight rails, so the user can pause, frame advance, or rewind to their heart's content. But rewinding would actually be a doozy. In order to step backwards, you need to know what the state of everything was on a previous frame, which means we need to record this info as "Save States".
How do save states work?
In an ideal world with infinite RAM, I could just save every variable of every script on every object in the hierarchy, then load all that when you jump to a previous frame, and voila! But knowing that most computers don't have infinite RAM, I'll need to optimize the savestates.
This game has 6 minigames, (and a handful of "cutscene" screens between minigames...) And each of them runs independently of each other. These minigames all have their own unique scripts too. Ideally I would make a parent "minigame class" which the games would inherit stuff from, but this was made in a weekend and I don't got time for that!
Instead, every class has their own SaveState() and LoadState() function, which the Timeline.cs class can run for each game individually. It's sloppy, it's not in a loop (because I didn't do that inheritance thing), and it takes multiple hundreds of lines to get this working, but hey, it worked!
Let's take a look at the SaveState() and LoadState() functions for the "Typing Minigame", inside TypeGameManager.cs
public List<byte> SaveState()
{
List<byte> State = new List<byte> ();
State.Add (PressedButton);
State.Add (AppearTimer);
State.Add (Chet.AnimTimer);
State.Add (Chet.TieSpinAnimTimer);
State.Add (Chet.CurrentAnim);
Here we're creating a list of bytes. This list will contain all the important info that needs to be initialzied when loading a state. We record what button was most recently pressed, so when we load the state we can set up which button needs to be graphically pushed down. We record some timers, which are bytes and not floats! Instead of being sane, the timers in this game are all simply a frame count as a byte! I record some animation info for Chet, the game show host. I record a boolean tracking if the on-screen keyboard should be on screen or not, the number of characters the player has typed so far, if you have submitted or not, some timers, and then finally each character in the typed word individually.
State.Add ((byte)(GameOnScreen ? 1 : 0));
State.Add ((byte)TypedWord.Length);
State.Add ((byte)(Submit ? 1 : 0));
State.Add (Timer_Seconds);
State.Add (Timer_SubSecond);
char[] CharArray = TypedWord.ToCharArray ();
for (int i = 0; i < CharArray.Length; i++) {
State.Add ((byte)CharArray[i]);
}
return State;
}
And then when we load this state, we need to make sure the order is the same:
``` public void LoadState(List State) { PressedButton = State [0]; for (int i = 0; i < AllTheButtons.Length; i++) { AllTheButtons [i].SR.sprite = AllTheButtons [i].Unpressed; AllTheButtons [i].TM.transform.localPosition = new Vector2 (0, 0.08f); } if (PressedButton != 255) { AllTheButtons [PressedButton].SR.sprite = AllTheButtons [PressedButton].Pressed; AllTheButtons [PressedButton].TM.transform.localPosition = new Vector3 (0, -0.047f, 0); } AppearTimer = State [1]; AppearTimerFloat = ((AppearTimer + 0f) / 60f); Holder.transform.localPosition = new Vector3(0,DataHolder.CustomLerp(-13,0,AppearTimerFloat,1),0);
Chet.AnimTimer = State [2];
Chet.TieSpinAnimTimer = State [3];
Chet.CurrentAnim = State [4];
Chet.ForceAnim (Chet.CurrentAnim);
GameOnScreen = State [5] == 1;
int CharCount = State [6];
Submit = State [7] == 1;
Timer_Seconds = State [8];
Timer_SubSecond = State [9];
TypedWord = "";
for (int i = 0; i < CharCount; i++) {
TypedWord += (char)State [10 + i];
}
TM.text = TypedWord;
} ``` The first byte we recorded was what button we pressed, so that's the first byte we read off the list. Then to initialize the graphics for the button being pressed, I iterate over all the buttons and set them to "unpressed". Then I set the graphics for the target button, assuming it exists. (A value of 255 meaning we haven't pushed anything yet.)
I pull off some timers and move the on-screen keyboard to where it should be with that timer value. You'll notice that since the timers are all stored as bytes, I just create a float out of the value and pass that into a Parabolic Lerp function I made.
I pull off the animation data for Chet, and run a routine to initialize his animation stuff.
I pull off the remaining stuff, and each character individually.
And just like that, we have a fully functional savestate... for one of the minigames. I had to do this for each minigame, as well as the intermission cutscenes to record how far the dialogue is.
The absolute worst one of the minigames to make this savestate info for was the basketball one. I'm using Unity's built in physics for the basketball, and finding out how to transform that data into individual bytes was a nightmare. I basically attempt to convert the float to a ushort and record the low/high byte separately. In hindsight, does Unity3D support signed shorts? I bet I could just cast a float to one... I way over-thought it.
float BigVelX = Mathf.Floor (BallRB.velocity.x * 512); // 65536/512 = 128. The ball will move that fast
Ball_VelX_Hi = (byte)((Mathf.RoundToInt(BigVelX) & 0xFF00)>>8); // Pull off high byte, shifted over.
Ball_VelX_Lo = (byte)(Mathf.RoundToInt(BigVelX) & 0x00FF); // Pull off the low byte.
Now I have the horizontal speed stored as an unsigned short. Converting this back to a float was a bit messy:
bool NegateX = false;
if (Ball_VelX_Hi >= 128) { // If the speed was actually negative.
NegateX = true;
Ball_VelX_Hi = (byte)(256 - Ball_VelX_Hi); // Make the number positive.
Ball_VelX_Lo = (byte)(256 - Ball_VelX_Lo); // We'll multiply the float by -1 later.
}
BallRB.velocity = new Vector3 (
(0f + Ball_VelX_Lo + Ball_VelX_Hi * 256) / 512f, // We multiplied by 512 earlier, so divide by 512f
(0f + Ball_VelY_Lo + Ball_VelY_Hi * 256) / 512f, // we did this for the Y axis too.
0);
if (NegateX) {
BallRB.velocity = new Vector3 (-BallRB.velocity.x, BallRB.velocity.y); // Flip if negative.
}
So yeah that sure was something. It worked though!
The blunder
It wouldn't be Ludum Dare without one.
The biggest issue was the Duck Hunt minigame, by far! So the game is designed to tell you hints whenever you fail a minigame. However, these hints only arrive if you let the failure cutscene fully play out. Every time I watched someone play the game, they fail the minigame and instantly pause the timeline.
Oh no!
It also doesn't help that this game has the biggest red herring ever, and I didn't have time to record specific dialogue when you actually shoot the duck. So now everyone successfully shoots the duck and the game show host says "Oh, it looks like Winnerman doesn't know how to use a gun!" But like- they hit the duck. Chet, what do you mean they don't know how to use a gun! And everyone pauses the timeline when he starts saying that, so the never get to the part where Winnerman says "Is there something else I can shoot?"
So yeah- that's a pretty big blunder. Also somehow the "Winnerman's Live Reaction" button broke during that minigame, because of course it would.
Second Blunder
My reaction upon watching people play the boss fight
The boss fight in this game was a bit rushed. There's not a lot of clear direction what you need to do, and also I locked the timeline, including the Winnerman's Live Reaction button. So this is the only point in the game where you can't press a button to figure out what to do, and it's less than obvious. Chet Quizzly has a big eye that you can shoot, but he has a sheild, so hitting the eye does nothing. I didn't add splash text saying "Blocked!" or anything, so someone could easily assume you just gotta shoot the eye enough times.
Instead, you need to shoot Chet's hand which he's using to manipulate the timeline. I had plans for the fight to continue after that point, as Chet would say something like "You know- without this hand even I cannot undo time!" and then there would be a final one-shot duel. I ran out of time, so shooting his hand just ends the fight.
We Do a Little Reflecting:
In classic 100th_Coin postmortem fashion, let's talk about all those notes I take for myself and how I completely forgot about them.
LD58: "Make a game in a genre you are unfamiliar with."
Last Ludum Dare I made a point and click adventure, and it was my first time doing that. The key piece of advice was to just try anything that isn't a platformer. No platformers. And I can happily say I got experimental with it, and this isn't a platformer. Woo hoo!
LD57: “Scope Smaller, or break the game into small rooms.”
Scoping smaller might as well not be in my dictionary at this point. It wouldn't be Ludum Dare if I wasn't pushing myself to my absolute limits and scoping as stupidly large as possible.
That being said, this game was broken into a series of individual rooms, so you can say I followed some of that advice, as least.
LD54: “Full in-game tutorial for ANY unique mechanics. It must be explained.”
YES! I had a tutorial, and people appear to understand the mechanic when they play the game. YES YES YES!
LD53: “Get feedback before the deadline.”
Per the tradition, my roommates were out of town and I didn't have time to reach out to others for testing. heh.
Let's see what that would have taught me though:
- The Duck Game's red herring need to have different dialogue options.
- The boss fight needs to be visually clear that shooting the eye is not the solution.
Basically the stuff I mentioned in the blunder sections.
LD52: “Focus on a single mechanic, and let the story come second.”
Now this is awkward. This note defined some of my best Ludum Dare games, but in my post-platormer era, I'm worried this note on letting the story come second is in direct conflict with the games I'm starting to make. The story here was directly tied to the single mechanic, and I think I said something very similar in my previous postmortem.
I'm not ready to remove this notes from the reflection pool, but these are feeling less relevant, and I'm not sure what to do about that.
And for fun, let's dig into ye old archives like last time to see what other ancient notes applied here:
LD50: “The exhaustion of the 48th hour is real.”
Good lord, it's wild how many of my LD Games seemingly fall apart in the final few hours. I didn't have much time for play testing this one, really just hoping that it all works out. When I finally got to test it out, I realized an issue that occurs when using the timeline to go backwards beyond the minigame you are currently in. Specifically, suppose you are currently in minigame B, and go backwards into minigame A. once minigame A ends flowing into Minigame B, you would find that minigame B was not reset. The savestates are only recorded on frames in which the minigame is active, so loading a frame before the minigame was loaded likewise cannot reset the minigame with data that wasn't recorded. My solution involved making an "initial savestate" for each minigame, but wow I didn't want to have to do that by hand (because when you count the intermission cutscene savestates as well, it was like- 20 things needing to be set up or something.) And automating the process of creating these initial save states was not easy either.
LD41: "Sound effects are super important, especially for simple feedback like hitting enemies"
You know- I could have used that note during the boss fight.
Those are pretty much the only relevant ones this time around.
So what's the takeaway?
What did I really learn this time?
I think it's something I should have picked up on during many previous jams, but I'm finally saying it. I need to make a Minimum Viable Product way earlier during these jams. I keep waiting until way too late before I start playtesting the game as a whole. All the biggest issues that I keep running into during the final few hours wouldn't be that big of an issue if I wasn't so fatigued by the time I discover them.
If I'm being realistic, I don't foresee myself scoping smaller. It's not what I do. But In an effort to prevent the burnout of the final few hours from destroying me completely, I need to make something playable long before the deadline. Even a version of this game with two minigames connected would have helped me find the null-savestate issue when backtracking, and I would have found it with a lot of time to spare, and before I had to set up an initial state for 20 objects.
Reflection 2: How'd I do boogaloo.
So this was an obvious artwork downgrade from my previous entry, and it's not that surprising. I didn't spend 20 hours on the art, I spent 20 hours on setting up the timeline mechanic and save states.

And while this wasn't my best artwork in an LD game, I still think my art is improving. For the first time ever, I drew a "goofy face" that actually made me chuckle out loud.
Stay mewing, Winnerman.
This was my ninth LD game to have acapella music. I think this game had some good jokes, and I think this was some of my best voice acting in an LD game. I put some real emotion into Chet's final lines during the Duck Hunt game. I wish I saved a bit more time for music composition, as that was rushed during the final few hours. This resulted in myself re-using the same music track for the title screen, and there was no music at all in the victory cutscene. Oh well!
Results:
3rd in Innovation
I also got 5th in humor, and 10th in audio. Not bad.
After discussing the results with Winnerman, we both agree that this game has won the Third Place is Still Winning award!

I'll also treasure the awards my friends drew for me, including the Most Unshootable Duck award from @jeremy-ryan, and the Duality of Cherries award from @mathstr0fficial!

Final reflection
I'm pretty happy with the trophy in innovation. This is the best I've done there since Ribbon back in LD47.
I'm also happy with the game as a whole. This was my second Ludum Dare in a row where I forced myself to avoid making a platformer, and I'm going to keep doing that as much as I can. As with last time, I feel like I learned a lot while making this game, which I find incredibly important.
I've been thinking a lot about my LD51 game. Ignoring the fact that it is a platformer, I remember that game being feature-complete in just a few hours, while the rest of the jam was spent building the levels, finding myself with a lot of free time at the end, and just decorating or adding accessibility features. I'm not yet sure how to make a non-platformer-game efficiently, and I think that's something I need to work on. This game was rushed until the deadline, and I think it really could have used some polish.
In conclusion, not my best work, but I got the opportunity to combine two of my hobbies into a TAS-themed video game, and I think that's rad. Winnerman might not have won the game jam, but he'll always have the washer and dryer set.