Page on my attempts to speedun the Madoka Magica TPS games. So far, I am currently speedrunning Mami Tomoe TPS.
A personal leaderboard containing my times in the game. Scores from modded versions of the TPS games won't be counted but I'll still add them. Scores I achieved without screenshot or video proof will not be added. I plan to eventually get some of these scores to speedrun.com at some point.
NC=Not Counted
Stage 1
| # | Time | Date | Platform |
|---|---|---|---|
| 1 | 38.60 seconds | May 7, 2024 | Android |
| 2 | 39.00 seconds | April 9, 2024 | Android |
| 3 | 41.03 seconds | April 7, 2024 | Android |
| 4 | 42.53 seconds | April 6, 2024 | Emulator (Nox) |
| 5 | 46.53 seconds | July 10, 2022 | Android |
| NC | 54.86 seconds | August 29, 2023 | Android (Modded; Battler TPS) |
Stage 2
| # | Time | Date | Platform |
|---|---|---|---|
| 1 | 1:21.16 | May 7, 2024 | Android |
Stage 3
Stage 4
Stage 5
Stage 6
Stage 7
Stage 8
Stage 9
Stage 10
Overall
TBD
Important note: After defeating a boss, there is some extra time spent waiting for the health bar to go down, this is in fact counted as part of the in-game timer. The moment you load into the game before the fade-in to the actual level is also counted by the timer.
in scene/SceneBase.java
public static boolean saveSaveData() {
ByteReaderWriter bw = new ByteReaderWriter(saveFileSize);
bw.writeInt(0);
bw.writeInt(continueStage);
bw.writeInt(continueDifficulty);
bw.writeInt(continueScore);
bw.writeInt(continueTime);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 10; j++) {
bw.writeInt(bestScore[i][j]);
bw.writeInt(bestTime[i][j]);
bw.writeInt(bestRank[i][j]);
}
}
boolean ret = CommonHelper.writeStorage(saveFileName, bw.getData());
bw.close();
return ret;
}
public static boolean loadSaveData() {
byte[] data = CommonHelper.readStorage(saveFileName);
if (data == null) {
return false;
}
ByteReader br = new ByteReader(data);
int readInt = br.readInt();
continueStage = br.readInt();
continueDifficulty = br.readInt();
continueScore = br.readInt();
continueTime = br.readInt();
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 10; j++) {
bestScore[i][j] = br.readInt();
bestTime[i][j] = br.readInt();
bestRank[i][j] = br.readInt();
}
}
br.close();
return true;
}
in system/ByteReaderWriter.java
protected byte[] data;
protected int point;
public void writeInt(int val) {
this.data[this.point + 3] = (byte) (val >> 24);
this.data[this.point + 2] = (byte) (val >> 16);
this.data[this.point + 1] = (byte) (val >> 8);
this.data[this.point + 0] = (byte) (val);
this.point += 4;
}
private static final int[] referenceScore = {4000, 4500, 4500, 5000, 4000, 5000, 5000, 4500, 4500, 5000};
private static final int[] referenceTime = {1800, 2700, 3600, 3600, 5400, 4500, 3600, 3600, 4500, 10800};
if difficulty==2
refTime += 1800;
bestScore[difficulty][currentStage] = Math.max(bestScore[difficulty][currentStage], this.score);
bestTime[difficulty][currentStage] = Math.min(bestTime[difficulty][currentStage], this.gameTime);
bestRank[difficulty][currentStage] = Math.min(bestRank[difficulty][currentStage], this.clearRank);
this.score += (((3600 - Math.max(0, Math.min(this.gameTime - refTime, 3600))) * 2000) / 3600) + ((this.playerObject.getHp() * 1000) / this.playerObject.getHpMax());
this.gameTime = Math.min(178200, this.gameTime + 1);
File size: 512 bytes
Each integer takes up 1 byte (8 bits) each.
File Coverage
0x00 -> 0x13 total scores
0x13 -> 0x17B level scores
0x17C -> 0x1FF rest of the file
Time-keeping method is currently unclear. I don't know if it's seconds or frame/tick per second.
The code first writes the integer 0, then the following:
* continueStage
* continueDifficulty
* continueScore (the default value is 0)
* continueTime (the default value is 0)
* bestScore[i][j] (the default value is 0)
* bestTime[i][j] (the default value is 20 BF 02 00 or 32)
* bestRank[i][j] (the default value is 3)
** i = level difficulty (0-3)
** j = current level (0-10)
My Homura save file stats
This is the raw values of the save file of my emulator copy of Homura TPS. I only played one stage.
0
continueStage=1 (level 1)
continueDifficulty=0 (easy)
continueScore=0
continueTime=0
bestScore[0][0] = 73 0D 00 00 (3443)
bestTime[0][0] = 3C 19 00 00 (6460 or 60?)
bestRank[0][0] = 02 00 00 00 (2)
bestScore[0][1] = 0
bestTime[0][1] = 20 BF 02 00 (32)
bestRank[0][1] = 3
bestScore[0][2] = 0
bestTime[0][2] = 20 BF 02 00 (32)
bestRank[0][2] = 3
(...)
Loading Level Files
The game loads in a .mgm binary file from the /raw/ folder in the .apk. The game takes the current level from a variable which starts at 0 and ends at 9 (stage0 seems to count as a tutorial, so only 9 levels are counted?). From there, it calls the file for that level. It turns the file from a resource into a byte array and reads the data as integers. It constructs level data based on how many textures there are, what textures, what values for the textures, etc. The .java file handling this is GameField.java
MGM Format Specification
.mgm is the format used by some games that have been developed by the company D-Mebius.
It can be identified with the file signature/magic number 0C 00 00 00.
More Information: https://gist.github.com/calmevening/ac64418d08272b874822eeaa30666286
Bomb Usage
I save all Tiro Finale bombs for the final boss.
Enemy Weapon Drops
Certain enemies drop certain weapons and other pickups. The things I prioritize most is the Tiro Finale bomb pickup, the next being the Shot weapon, then the Fire weapon. I disregard the rest of the item drops.
Notes
Unlike Homura TPS, Mami Tomoe can only jump when prompted.
Stage 1
| # | Time | Date | Platform |
|---|---|---|---|
| 1 | 1:21.36 | May 8, 2024 | Android |
| 2 | 1:24.86 | May 5, 2024 | Android |
| 3 | 1:25.20 | May 5, 2024 | Android |
| 4 | 1:29.06 | May 5, 2024 | Android |
Stage 2
| # | Time | Date | Platform |
|---|---|---|---|
| 1 | 2:01.66 | May 8, 2024 | Android |
| 2 | 2:15.43 | May 5, 2024 | Android |
Stage 3
Stage 4
Stage 5
Stage 6
Stage 7
Stage 8
Stage 9
Stage 10
| # | Time | Date | Platform |
|---|---|---|---|
| 1 | 2:34.10 | May 7, 2024 | Android |
| 2 | 3:38.76 | May 5, 2024 | Android |
Overall
May 5 total: ~28 minutesDon't use the Pipe Bombs
what the title says. The pipe bombs do less damage than the Spread weapon and also slows you down when killing a boss.Weapon Management
The only weapons you should ever get are Rapid and Spread. Everything else will just slow you down. If you pickup a third weapon, it overrides whatever you were using at the time, so it's optimal to avoid any rougue pickups dropped by enemies and witches.Enemy Spawn Order
Not killing enemies will result in only half of the intended amount of enemies to spawn; killing the first few enemies in the first level spawns a few more, and then a few more, not killing the first set skips those two other spawns. Though if you're willing to take a risk, certain enemies can be killed in order to spawn other enemies, if done right, you can get extra ammo for the Rapid and Spread this way.Notes

© 2025