This project aims to log various game data (and inputs) during a game of Tetris: The Grandmaster 2 Plus (TGM2p/TGM2+/TAP) when run in an emulator (MAME). TapTracker runs alongside the emulator and performs its logging in real-time. Windows support is experimental.
BlockTracker was my first attempt at this. It was done by interacting with scanmem as a child process, but ultimately, this meant every time I started up the game, I had to probe for the memory address holding level data (not unlike someone using cheat-engine) then pass the proper memory addresses to BlockTracker. Along the way I wrote quite possibly the worst code I’ve ever written, so despite it being such a small program, it wasn’t too pleasant for me to look at and maintain. (There was one upside though: this method was game-agnostic, so I could probe any TGM-style game for level data).
In contrast, TapTracker takes a more direct route by modifying a TGM-specific version of MAME to output relevant data.
First things first, get the source code from this repository:
git clone https://github.com/sanford1/TapTracker.git
After cloning, enter the created directory and retrieve all the submodules:
git submodule update --init --recursive
Building this project requires premake5, make, and glfw3:
premake5 gmake
make
If you don’t want to go through the trouble of building this by yourself (and you trust me enough): Latest Release
TapTracker no longer runs shmupmametgm as a child process, but TapTracker must be run after shmupmametgm has started. For now, TapTracker only logs stats for player 1. You must also enable TapTracker in modded shmupmametgm with the -taptracker
switch or the correct option in your mame.ini.
Command line arguments:
Usage: ./TapTracker [options] [arguments]
Options:
--js Set to 1 to enable joystick support. This setting has priority over the config file. (-1)
--config Set json config file path (config.json)
--pb Set "Personal Bests" file path (GoldST.txt)
--version Output version
--help Output help
On first run, TapTracker will create a (commented) json configuration file (config.json is the default file if you don’t specify a different path). You can use this file to create windows / layouts.
TapTracker also tracks your personal best times for sections and completed games. TapTracker will import/export a file named “GoldST.txt” by default.
Check bin/default_config.json (or run TapTracker without a config file) to generate a commented config file.
UI is still a work-in-progress.
The top portion graphs the time vs level during the last three sections. Below that is a table listing section times (newer, more speedrun-timer-like layout on the left). If joystick support is enabled, a second window is opened that displays joystick inputs per level (no keyboard support yet).
Old Video of TapTracker (without input viewer)
- [Partially complete] Document TAP states
- I’ve reverse engineered most of the TGM2+ states (read: I’ve figured out what they mean), but there may be edge cases that still confuse TapTracker.
- [Partially complete] Implement resizable layouts
- TapTracker only supports a single window size and layout right now, and it’s difficult to modify.
- [Partially complete] Rework OpenGL code
- It’s rigid and messy right now. Not to mention it also uses a ton of immediate mode. There’s an old git branch (gltest) that updates the 15-year-old rendering code to 14-year-old rendering code.
- Player 2 support
- Log stats for Player 2 too.
Diagram Generation- Create a Fumen diagram during play. A prototype (written in Python) can be found in the
autofumen
directory. This functionality is now built into shmupmametgm. MAME patch for Windows- No longer necessary, fork of shmupmametgm supports Windows (but not TapTracker!).
- More error logging
- Since in addition to the original TapTracker code, we have to reproduce some of tgm2p’s game logic, there may be unforeseen edge cases. We probably should catch those.
- Config files
- For configurable layouts and to possibly replace the command line arguments. YAML? JSON? Roll my own plain text data file?
Maybe something like this (plain text):
Window MainWindow Size 240x540 Layout 14.0 2.0 Ratio Graph 0.72 Fixed Table 130.0 Fixed CurrentState 14.0 Window ExtraWindow Size 180x120 Layout 4.0, 2.0 Ratio GameHistory 1.0 Window ButtonWindow Size 180x112 Layout 4.0 0.0 Ratio InputHistory 1.0
Or this (yaml):
joystick: enabled: yes axis-hori: 6 axis-vert: 7 button-a: 1 button-b: 2 button-c: 3 windows: - name: MainWindow width: 240 height: 540 layout: - type: graph ratio: 0.72 - type: table fixed: 130 - type: state fixed 14 - name: Extra width: 180 height: 120 layout: - type: history ratio: 1 - name: ButtonWindow width: 180 height: 112 layout: - type: buttons ratio: 1
- Adaptive draw functions
- Some draw routines are very static and don’t change with size.
All these addresses are for player 1. I still need to double check some of these.
- From a relatively hidden part of the TetrisConcept Wiki:
Address | Type | Description | Notes |
---|---|---|---|
0x06064B99 | int8_t | ARE/Line Clear Delay Time Remaining | |
0x06064BA8 | uint32_t | Random Number Generator State | |
0x06064BC8 | int32_t | Score | |
0x06064BE1 | uint8_t | DAS Counter | Can overflow(!) |
0x06064BE4 | int32_t | Total Time | |
0x06064BE8 | int32_t | Game Time | |
0x06064BF5 | int8_t | Block State | |
0x06064BF6 | int16_t | Current Block | |
0x06064BF8 | int16_t | Next Block | |
0x06064BFC | int16_t | Current Block X Position | |
0x06064C00 | int16_t | Current Block Y Position | |
0x06064C02 | int8_t | Gravity Left | |
0x06064C04 | int8_t[4] | Block History | |
0x06064C2A | int16_t | RO Badge Score | |
0x06064C2C | int16_t | Number of Blocks Rotated | |
0x06064C2E | int16_t | Current Block Rotation Count | |
0x06064C34 | int16_t | Current Block Alive Time |
- Extra:
Address | Type | Description | Notes |
---|---|---|---|
0x06064BFA | int8_t | Current Block Rotation State | |
0x06064BBA | int16_t | Player 1 Level | |
0x06064BEA | int16_t | Player 1 Timer | |
0x06079378 | int8_t | Internal Grade | |
0x06079379 | int8_t | Internal Grade Points | |
0x06064BD0 | int8_t | M-Roll Progress State | |
0x06066845 | int8_t | M-Roll Flag | |
0x06064C25 | int8_t | Section Index | |
0x06064BA4 | int16_t | Current Game Mode | See below for corresponding values |
Index | Block |
---|---|
2 | I |
3 | Z |
4 | S |
5 | J |
6 | L |
7 | O |
8 | T |
Value | Definition |
---|---|
17 | Failure state in the first half of the game (100-499). |
19 | Failure state in the second half of the game (500-999). |
31 | Failure state at the end of the game, currently in fading credit roll. |
34 | Garbage value when the game is still loading. |
48 | Neutral state. Value during the first section (0-100) and non-play game states. |
49 | Passing state during the first half of the game (100-499). |
51 | Passing state during the second half of the game (500-999). |
127 | Passing state at the end of the game, currently in the invisible credit roll. |
Value | Definition |
---|---|
0 | |
1 | |
2 | Tetromino can be controlled by the player. |
3 | Tetromino cannot be influenced anymore. |
4 | Tetromino is being locked to the playfield. |
5 | Block entry delay (ARE). |
7 | “Game Over” is being shown on screen. |
10 | No game has started, idle state. |
11 | Blocks are fading away when topping out (losing). |
13 | Blocks are fading away when completing a game. |
71 | Garbage value when the game is still loading. |
Mode | Value |
---|---|
No Game Mode | 0 |
Normal | 1 |
Master | 2 |
Doubles | 4 |
Tgm+ | 128 |
Death | 4096 |
Mask | Value | Bit-shift |
---|---|---|
Versus | 8 | (1 << 3) |
Credits | 16 | (1 << 4) |
20G | 32 | (1 << 5) |
Big | 64 | (1 << 6) |
Item | 512 | (1 << 9) |
TLS | 1024 | (1 << 10) |
Address | Type | Description | Notes |
---|---|---|---|
0x0017695D | Player 1 State | ||
0x0017699A | Player 1 Level | ||
0x0017698C | Player 1 Timer | ||
0x0017699C | Player 1 Grade | ||
0x00000000 | Player 1 Grade Points | ||
0x00000000 | Player 1 GM Flags | ||
0x00000000 | Player 1 In-Credit-Roll | ||
0x0017699E | Player 1 Section Num | ||
0x001769D4 | Player 1 Tetromino | ||
0x001769D2 | Player 1 Next Tetromino | ||
0x001769DA | Player 1 Current X | ||
0x001769DE | Player 1 Current Y | ||
0x001769D7 | Player 1 Rotation State |
enum tgmj_internal_state
{
TGMJ_IDLE = 0, // No game has started, just waiting...
TGMJ_ACTIVE = 20,
TGMJ_LOCKING = 30, // Cannot be influenced anymore
TGMJ_LINECLEAR = 40,
TGMJ_LINECLEAR2 = 50,
TGMJ_LOCKED = 60,
TGMJ_ENTRY = 10,
TGMJ_ENTRY2 = 100,
TGMJ_UNKNOWN = 110, // Probably a death state
TGMJ_FADING1 = 111, // Blocks greying out at when topping out
TGMJ_FADING2 = 112, // Blocks greying out at when topping out
TGMJ_NAME_ENTRY = 114,
TGMJ_DEAD = 115,
TGMJ_GAMEOVER = 116, // "Game Over" is being shown on screen.
TGMJ_READY0 = 90, // READY!
TGMJ_READY1 = 91, // READY!
TGMJ_READY2 = 92, // READY!
TGMJ_READY3 = 93, // READY!
TGMJ_READY4 = 94, // READY!
TGMJ_READY5 = 95, // GO!
TGMJ_READY6 = 96, // GO!
};
#define MODE_20G_MASK (1 << 0)
#define MODE_BIG_MASK (1 << 2)
#define MODE_UKI_MASK (1 << 3)
#define MODE_REV_MASK (1 << 4)
#define MODE_MONO_MASK (1 << 5)
#define MODE_TLS_MASK (1 << 7)