One of the important tenants of Rktcr is that -- while it is very hard -- it isn't impossible. So how do I keep the game possible, given the hundreds of generated worlds and tens of levels? As expected, the answer is extensive (semi-automatic) testing.
The core of this testing revolves around a database of paths -- recordings of player input that take the vehicle from portal [to gem] to portal, under different gravity directions and mirrorings, in each level. As of this writing, there are 1093 paths in the database. That's a lot of gameplay (in fact, it's all the gameplay I think is possible).
These paths have a few functions, but the most important is to make sure that any changes I make to the game don't (unintentionally) change what is possible in the game.
I have a helper program test_paths
that runs every path in the database and prints a hash of the game states along each path.
The output of test_paths
gets stored in my main git repository; before releasing a build (and, generally, after any change that I think might have messed with the physics), I run test_paths
and compare to the stored output.
If there are any differences, I know there is trouble afoot.
Of course, the paths aren't just for testing.
They also get converted into par times for the levels; for this I have a pair of utility modes -- rktcr update-claims
updates the par times to reflect the current paths database,
while rktcr check-claims
check the current par times against the paths database to make sure they are consistent
(this comes in handy when I change level geometry or physics, invalidating existing paths, as it lets me know what paths I need to recreate).
Aside: why not distribute paths instead of par times? Philosophical reasons, mainly. I prefer that games not know the answers they are asking you to provide.
These par times, in turn, are used by the aptly named world_gen
to generate worlds (groups of connected levels).
Of course, I want to hedge against the possibility that a world becomes unwinnable owing to new paths and par times.
For this, I have yet another utility program, world_test
, which loads each world in turn and makes sure that it is solvable in a reasonable time, given the current par times.
And that's how I keep Rktcr honest.
So do you generate the paths by manually playing and winning levels in different ways? Also, what game state do you verify in test_paths?
ReplyDeleteYes, path generation is manual -- coding up some sort of, say, RRT search would be interesting but would also take focus away from the core of the game.
DeleteThe `test_paths` program verifies contents of the DrawState at every tick of the path. This object contains the positions and velocities of every dynamic body, along with the current (slightly smoothed) thrusts from each jet.
(I also have code -- ./rktcr trim-paths -- that checks to see if the paths still work despite not having a matching game state -- this allows some path reuse even when there are small edits to the level geometry.)