Stunts - the Game > Stunts Reverse Engineering

Brain dump of a lost inquiry


A fried HD has just taken with it the preliminary results of some restunts-related investigations about the game engine that I had performed recently. This post is a brain dump of whatever I can remember from the findings, in case anyone else (possibly even myself, once my disgust over this data loss incident subsides) wants to follow the leads. I will use restunts terminology liberally and (as I am writing this from memory) inaccurately -- any field/subroutine names I mention might be wrong. If I get to recall things better, I will update this post accordingly.

Keep your backups up to date...

* (Preliminary note: "trackdata*" are the twenty-odd data blocks which hold all sorts of track related information used by the game engine.)
* trackdata01 isn't, unlike some restunts annotations (motivated by its 900-byte size) suggest, a copy of the track map. I don't remember right now exactly what each of the early trackdata did. What was clear is that each byte in them corresponds to an element, in driving order, with alternate dual-way paths coming after the "main"/straight path, from last to first (which IIRC matches the order the cursor moves along the paths when you press "C" on the track editor). One of the trackdata holds the index of the next element, with 0xFF being used for dead-end paths. Another one, if I'm not mistaken, holds the index of the start of alternate paths for dual-ways splits, being 0xFF otherwise. The indexing matches that of a well-documented later trackdata (was it 16?) which holds element codes, also in the same driving order.
* The 900-byte size of the early trackdatas, which is not actually related to the size of the grid, is the reason why very complex multi-way tracks (I think I posted one example of these in the Bliss thread) are subject to buggy behaviour, such as valid paths not being detected and crashes in attempts to race against the opponents. In effect, there is a maximum limit of 900 elements (possibly slightly below than that, as IIRC some dual-way elements are counted twice) to the sum of the lengths of all paths in the track. This limit is not enforced by the game.
* One of the early trackdatas (was it 03?) sometimes changes while driving. It sets the path taken by the opponen. More on that later.
* Speaking of the opponents, a large chunk of the opponent AI is neatly contained in a branch of one of the opponent-named methods. It can be located by tracking down where the "sped" data (the maximum speeds through the elements for the opponents) is used. That leads to a clear decision point on whether to brake or accelerate depending on the difference between the actual speed and the target one.
* The update_grip subroutine, which in relative terms is not very difficult to make sense of, does perform the calculations which, from steer, grip and speed, enact skidding and spinning. From it, it becomes possible to understand the angle variables in carstate (fields 20 -- steering wheel input -- 36 and 40, and possibly some others). IIRC there is one with the direction change from steering, and another with the additional delta from spinning. Limit speeds for each behaviour and speed loss from skidding are also possible to figure out.
* A large part of the lost results were related to update_car_state, the subroutine which updates the car position according to the car state and its location on the track. While my 2010 investigation of it had stopped at the build_track_object call (which is responsible for materialising track elements), this time I had gone through and made sense of nearly the whole thing... Here are a few random findings:

* One test which is particularly easy to identify is the one which checks whether all wheels are on water. While the location of that test was already known, it is worth pointing out that it leads to a crash-related function, where the branches correspond to all kinds of crash events, as well as (IIRC) the lap conclusion event.
* After the build_track_object call, there is a thick jungle of code which is used to figure out interactions (including potential crashes) with track surfaces, walls and miscellaneous objects. There are lots of interesting things to be mined from there. One highlight are angle and speed limits for crashes. For instance, hitting a wall will never lead to a crash below 30mph, and always will above 100mph. In between those speeds, it depends on the angle between car and wall. The limit pitch angle for crashes against the ground (the one which was probably raised between Stunts 1.0 and 1.1) is also plain to see somewhere near the end of the branch dealing with car-surface interactions.
* While there is a minimum speed for wall crashes (30mph), no such limit applies to trackside objects such as pillars and trees. Opponent cars are handled a bit differently, depending on speed and angle, contact with another car may merely push you away. This sort of collision is handled by a subroutine called somewhere near the end of update_car_state.
* There is some confusion in the 2010 annotations to update_car_state with regards to the signs of the angles used in the rotation matrices. That confusion can be cleared away by realising some of the angles are in world coordinates, while others use the car reference frame (and thus have opposite signs). IIRC there examples of each of the coordinate systems being used in the carstate angles.
* There is one subroutine (bto_auxiliary, IIRC) called from build_track_object whose role is setting up miscellaneous non-wall trackside objects (such as tree trunks, start/finish pillars, etc.) for collision detection purposes. Wall gliding tricks are possible because there are no such objects for most bridge elements (one execption is the base of u/d corks), and so if you run into a wall while running parallel to the track direction on the edge of the road you simply won't find anything to crash with.
* Still on track side objects: they are defined by four values: width, height, length and a cutoff radius for collision detetcion. The values for trees, pillars, etc. are hardcoded somewhere in the data section of the executable. The "dimensions for car-car collisions" specified in CAR*.RES that the Wiki talks about also use this format.
* There is one special kind of "ghost" trackside object that you can't crash against, though collisions has other side effects. They are placed in seemingly arbitrary positions in tiles specified by one of the trackdatas (was it 10?). The whole thing is weird enough that it stands out while reading update_car_state. My theory (almost, but not quite, confirmed) is that these ghost objects are used as invisible checkpoints that, if hit, change the path chosen by the opponent. They are set in pseudo-random positions in a reproducible way, which would explain why, even though the opponents can take different paths in different races, they always follow a consistent path when the same lap is replayed. (By the way, I consider that the coolest and most elegant trick I have seen so far in the code.)
* The rc* values in carstate are used for fine adjustment of wheel position (though two of them are actually unused). One of them is related to the suspension travel vertical displacement you can see when looking from the side at a falling car. Another one adds extra vertical displacement to the wheels of a mid-air cair. This displacement is unequal between front and rear wheels, in a way that makes flying cars rotate downwards and eventually begin falling (changing these values can be used to manipulate flight time).
* Speaking of displacements, there is one place (at some early point of either update_speed or in update_car_state -- I don't remember exactly) where the overall displacement of the car is multiplied by a constant factor. Changing it can be used to e.g. set up a turbo mode, in which the car moves twice as fast than what the speedometer says, and the grip limits are adjusted in the appropriate proportions.
* Another trackdata (was it 09?) holds the positions of the cameras in the F4 view. There is a fair bit of interesting camera-related stuff which is not too difficult to track down, including a whole subroutine which adjusts the positions of the external cameras accodring to the car position and track location (one way to find it is looking where the weird "camera offset" data referenced from the track object data structures is used). There is also some camera-related data in gamestate and IIRC also in carstate (in particular, if I'm not mistaken one of the unknown vectors in gamestate is the position of the F2 camera).
* Quite a few branches of update_car_state are skipped if a certain "input mode" flag is not in its usual, waiting-for-driving-input, state (that is e.g. what makes it impossible to crash after crossing the finish line). If you track down the places in which this flage is not in its usual state, one nice thing that will be revealed is how the car is rolled out of the truck -- that is, the mechanics of accelerating it a little bit and then slowing down so that it stops at the right place.

Wow!  Looks like a whole lot is known about the internal workings of Stunts. I would gladly start writing a Stunts-like game if I were more comfortable with some things (namely, triangle collision, 3D triangle-based rendering). My idea is that, if one kept some concepts simple like in Stunts (textureless single-coloured triangles, no shadows, no gradients), one could produce a very fast 3D engine with high resolution and without the need to rely on 3D drivers and graphics acceleration. A tweaked Newtonian engine could incorporate the elements we see in Stunts physics to recreate the feeling of driving in Stunts, while not necessarily producing the exact same results. A portable engine, with no dependencies, would be amazing. If this much is already understood, maybe we could put forces together to create it.


--- Quote from: Cas on January 05, 2017, 10:21:16 PM ---My idea is that, if one kept some concepts simple like in Stunts (textureless single-coloured triangles, no shadows, no gradients), one could produce a very fast 3D engine with high resolution and without the need to rely on 3D drivers and graphics acceleration. A tweaked Newtonian engine could incorporate the elements we see in Stunts physics to recreate the feeling of driving in Stunts, while not necessarily producing the exact same results.

--- End quote ---

I know that is not quite like what you have in mind, but have you already seen the LambdaCube Stunts? (Hackage page; YouTube video; there is also a JavaScript/WebGL port) In that case, the LambdaCube team used Stunts as a tech demo for their 3D engine, plugging in a physics engine (Bullet, or JigLibJS2 in the case of the JavaScript port) to make it playable.

That's a very interesting one. I know that there have been many different approaches to recreating Stunts. Many of them have been mentioned in this forum. Maybe the best known is Ultimate Stunts, but all are too different from Stunts.

In this particular case, it appears at first sight that the game is much more similar to Stunts because it uses its original 3D models, but it's actually entirely different: the physics are more like that of other car games and the engine is both hardware and software demanding, since it uses 3D hardware effects and if I understood correctly, it's built upon third party libraries (too many dependencies). These things are nice, but I think that, for a recreation of Stunts to stick, it has to be... well... "Stunts". Only a few things can be changed that people would accept such as:

- Make the game run natively on newer OSs
- Allow higher pixel resolution
- Maybe change the collision and physics engine as long as they still appear to be the same (that is, it's not that important if replays are incompatible with the new one, but it is important that the game still feels like Stunts). Changing the engine would allow more practical car design, making the game more flexible
- Allow mods

I've made a triangle drawing function that I could translate to assembly to make it lightning fast, but then comes all the 3D and collision and I fear that will kill me, ha, ha.


[0] Message Index

Go to full version