What's new

Twinkle Star Sprites: Trainer Rom (v0.3 released)

ekorz

Multi Boyz 4 Pi
Immortal
Multi Boyz
Joined
Aug 21, 2016
Messages
5,324
Reaction score
8,165
Location
Boston, MA
Update! V0.3 is out, file is attached

New stuff:
Fever Toggle
Max Power Toggle
Lua Hitbox Script for Fightcade
More info

...also I 'finished' Cake Fighter so go find that too!


Old v0.1 post:
Never despair; I'm still working on Cake Fighter! During that (continuing) research I documented a lot of Twinkle Star Sprites (TSS) code -- so when @Swirl dropped me a line and let me know the TSS community struggles for training options, I thought I could help. With @Swirl's expert direction I cleaned up some of the existing debug menus and built a "TSS Trainer" -we will call it v0.1- that hopefully the TSS community will enjoy, as well as some folks here. Heck, maybe I'll even use it and stop being such a scrub. *edit: here is a wonderfully comprehensive guide for the trainer @Swirl has created as well. Thanks also to Sudden Desu for all the original research that uncovered the debug menus in the first place.

1640191849562.png

(a Victory pose, if you're not familiar with it, like me)

Basics
Press C+D during game-play to bring up the menu, or to
Toggle options with left or right
Exit with C+D
The Zako sub-menu enables after the round starts, i.e. "Go!", by selecting either ZAKO or BLOK and then pressing C+D to 'exit'
Choose options in the Zako sub-menu by holding D and using the directions/joystick for UDLR
Select/engage a Zako sub-menu selection by pressing C

Menu Options
MUTEKI (invincibility) On/Off - You can still get hit but you don't die i.e. round-over
CTRL - Human/CPU/Off
CHR-NO - Select your Character
CHR-COL - Select character color (Normal or Reversed)
ZAKO* - Select the enemy patterns (Auto, Zako, Blok)
DB-DISP - Shows CPU usage meter and two charts**
NODEATH - Prevents Death from spawning (On/Off)
SLOW - Slow motion (On/Off)

1640191994614.png


*ZAKO sub-menus are documented here under "ZAKO and BLOCK test", we just cleaned up the UI a bit by removing extraneous text and less useful info.
**The original DB-DISP displays three values (check guide for info). We now render the "unknown gameplay monitor" here as well since those values are potentially relevant. Also see the guide as it will be updated as the community figures those out!

There is also an optional file which disables the music driver. It's been reported that (at least on emulated systems) disabling the music results in less slowdown during very frenetic gameplay. It's helpful to train without slowdown, so if that's your aim then you'd use this file too.

Lunar IPS patches are attached here in a zipfile, which has two folders:
/mame_rom: (works on MAME, FBNeo, etc)
apply 224-p1.ips to /roms/twinspri/224-p1.p1
(optional music mute) 224-m1.ips to /roms/twinspri/224-m1.m1

/ds_multi: (works on the Darksoft MVS Multi)
apply prom.ips to the /games/twinspri/prom file
(optional music mute) apply m1rom.ips to the /games/twinspri/m1rom file

If anyone wants to fiddle with the details, or if I forget wtf I did and want to read up on it again, here are some more thorough notes on the patchwork.
Consolidated Trainer Notes

Offsets are +100000 in the actual file because neo geo
Debug Menu Lookup Table - 017E6C
Like many other tables in the game, the target gets built from a register offset, and the value in the table is used as a target address
example: toggling Muteki byte on/off

Code:
017E6C: 0001 7C4C                ori.b   #$4c, D1 ; muteki lookup table address, points to the logic related to the debug menu selection

                        ; muteki toggle

017C4C: 0807 0002                btst    #$2, D7
017C50: 6608                     bne     $17c5a
017C52: 197C 0001 4181           move.b  #$1, ($4181,A4) ; the actual invulnerablilty / muteki byte (10c181 or 10d181)
017C58: 6004                     bra     $17c5e
017C5A: 422C 4181                clr.b   ($4181,A4)
017C5E: 4E75                     rts

                        getting the value from the lookup table

017C14: 41FA 0256                lea     ($256,PC) ; ($17e6c), A0  ; 17e6c being the lookup table. entries are offset by their menu position (x4)
017C18: moveq   #$0, D0
017C1A: move.b  ($4180,A4), D0 ; 'menu position' byte
017C1E: add.w   D0, D0 ; offset x2
017C20: add.w   D0, D0 ; offset x2 more
017C22: movea.l (A0,D0.w), A0 ; get long word located at offset address e.g. 0001 7C4C from 017E6C (if offset was 0)
017C26: jsr     (A0) ; jump to 017C4C

Debug Menu Start - 017EB0

Generally speaking, the pattern for each menu item is:
check if the item is selected,
change the render color (or skip)
set address for the text to render
send the result to the rendering subroutine $cdbe
then render the toggling part of the ui, the same way
then skip a line

I modified the code with a branch to the end after as many items as I wanted to render, to save time from nopping them all. The last thing needed is to edit how many items the game thinks are in the menu before looping.

Code:
017C2C: 532C 4180                subq.b  #1, ($4180,A4) ; 4180+108000 = 10c180 ; this is the menu position byte for debug menu
017C30: 6418                     bcc     $17c4a
017C32: 197C 0010 4180           move.b  #$10, ($4180,A4) ; change this #$10 byte to 07 or whatever to reduce the menu 'size'
017C38: 6010                     bra     $17c4a
017C3A: 522C 4180                addq.b  #1, ($4180,A4) ; increment
017C3E: 0C2C 0010 4180           cmpi.b  #$10, ($4180,A4) ; change this #$10 byte to 07 also to reduce the menu 'size'
017C44: 6304                     bls     $17c4a
017C46: 422C 4180                clr.b   ($4180,A4) ; loop back to 0
017C4A: 4E75                     rts

Due to how the script was built over several conversations, there is some cruft.
The original entry "Slow" was renamed into "Zako" and the related addresses were edited.
Then we ended up putting "Slow" back in, on top of the entry "Diff". Oh wellz.
I overwrote "Dif-Not" with a patch for toggling Death.
The newly added 'Death byte' is a boolean I dropped at 10b366 in user memory In code it's referenced as $3666 ususally with a4+offset.
Where the Death code normally begins (023EBE, a jump to the death subroutine), I instead jump out to empty memory at db900 and do an evaluation of the new death byte. If it's zero, I run Death
Code:
jsr     $23da8
, if it's 01, I skip over the death subroutine and return to the stack.

We also added the "unknown" display into the DB-DISP on/off toggle. That's a patch at 01799C which just jumps to the monitor, which sets the addresses i overwrote anyway, and then naturally returns with RTS.

There's a cheat that disables the audio driver (somehow) with a patch of the music driver's code at 04C2 from 32 to C9. I didn't try to disassemble the audio driver to figure out why it works, but it disables music. This apparently helps with slowdown, and I'd argue that perhaps on only emulated systems this is true. In any case, if you want to try this 'no slowdown' version you can replace your 224-m1 file with the patched version, and no music will play.
FAQ
Q: Can you build a version for the Neo SD
A: No
 

Attachments

Last edited:
Hey all,

In the past, our TSS netplay community didn't have many ways to practice and learn all thirty-six patterns. The only ways to practice and learn was to watch a Youtube playlist with all thirty-six formations, use savestates to isolate and practice individual patterns, or UniBios to give the CPU Infinite Energy and make the rounds longer allowing all formations to cycle. New players often asked if there was a dedicated training mode or something to help get a more hands on approach, but I had to tell them nothing like that existed... until now!

When Sudden Desu discovered that Debug Mode existed and seeing the features it had, I wondered if it was possible to clean up the existing menus. Seeing that @ekorz was working on making Cake Fighter a thing, they definitely made this Trainer a reality.

ekorz managed to clean up and keep the important options readily available such as: selecting and spawning specific patterns, character change on command, and even a monitor that tells you the current CPU usage, current formation, countdown to Fever orb or Star Coin formation, and in-game time. Accessing all of these options is very seamless.

While I was playtesting, I spent these past couple of days writing up a very detailed guide on how to use the trainer and I've documented and included animated GIFs* to complement my explanations. The guide can be accessed over here: https://docs.google.com/document/d/1k8kBUJfGd1g5rbcCqS_6qCtkzSk2FGAixgWwtmbfAgs/edit

I advise viewing the guide through desktop, mobile completely messes up the formatting.

Once again, thank you so much for taking the time to get this together; this will definitely help our players of all skill levels to go about thoughtful practice and teach others along the way.

* I had to skim through all my play footage, highlight the relevant parts, render the frames into PNGs, then convert the into GIFs. It was quite the process... but definitely worth it.
 
1642131841861.png

improved hitboxes showing enemy chain hitboxes, player bullets, bonus items like $ and bomb, boss attacks in a separate color, etc etc.
still in testing but it's looking pretty decent!

...and at the prompting of a moderator of the tss discord, I looked into the code and legit found a bug in the game's actual hitbox logic -- for one specific scenario. One of the boss attacks is severely glitched, and nobody ever knew what was up. Well, it was basically a typo in assembly. I'm considering releasing a patch in some form or another, but i want to wrap this lua script and one more trainer update. good times!
 
New trainer and full hitbox script coming shortly, but I thought some of you would also be entertained by an honest to goodness BUG that turned up in TSS hit calculations. If you don't like long posts TLDR: reading is good for your brain, try it. So it started with me posting some hitbox progress at the TSS discord, and a Mod asking about a specific attack I'd never heard of:

1643166406902.png


This boss character shoots lightning at you (is that fair?) but is apparently known to not kill you sometimes. Or kill you when it the lightning sprites don't touch you. People don't like it. This, for example, does not kill you, even though the blue hitbox shows an intersection / kill. I've made the right side lightning blue because it's of particular interest here...

1643166619115.png

I thought the issue might have something to do with the hitbox being slightly outside of the play window, but further tests invalidated that. Using slow motion, save states, etc. I got to fiddling with the particulars. On situations like the above, you'd sometimes die but only in between the lightning, so I supposed that maybe my hitbox script just had a bug or something. And honestly it did, like five of them. But I fixed them!

Some time later, this happened:
1643166664125.png


The lightning clearly zigzags to the left of the player, and the kill happened anyway. If you zoom on that photo you'll see a yellow dot in the center of the heart, which was the hit spot. It was also curious that my hitbox script (newly debugged!) drew such a large, and valid, kill zone.

Diving into the actual hitbox code was revealing. Without posting too much assembly, the gist of it is that each sprite in the game has an x-coordinate, a y-coordinate, and a library that gives you offsets in both directions. So if your Y coordinate was 10, and the Y1 offset was 4, and the Y2 offset was 7, then you'd have a hitbox height from 10-4 to 10+7, or from 6 to 17. Same deal with the X coordinates. First the game calculates the player's hitbox, then evaluates it compared to the sprites on the screen. Since enemies come down from the top of the screen mostly, it starts evaluating the Y coordinates first, disregarding anything without a Y value overlap. If the Y's do overlap, it starts checking to see if the X values overlap. If both overlap in any combination, it's a hit.

Except, clearly, not with the lightning.

There was a strange branch of code I never could trigger that reversed how the X hitbox was evaluated. Say the X1 was 1, and the X2 was 6. Usually you'd take your center X coordinate and subtract 1 to get the left side, then add 6 to get the right side. Not in this code though, it was subtracting 6 to get the left side, and adding 1 to get the right. Nothing in the game used this part of the logic, but I set a breakpoint and behold the lightning triggered it. Not just the lightning though, the right side lightning.

Why? Well, it turns out the game uses the same sprites for the lightning regardless of what side it's being shot from. It's just flipped on the Y axis.
1643164193033.png
<<---same sprite--->>
1643164215654.png


When building a hitbox for the mirrored one, if the x offsets are not the same value, you want it to extend the other direction. So you should adjust your math, which is what this branch was doing. Or, at least that's what I assumed it was doing when I built the hitbox code, and really what I thought it was doing the first time I read the code. This pattern happens a lot, so here's the pseudocode except for the first instruction, since it's the important part.

Code:
move.w  (-$4,A2), D1 ; Move X1 data from memory, to register D1. it's at the address A2 offset -$4
...                  ; mathy math, add or subtract as necessary
...                     ; if hit, goto hit, otherwise...
move.w  (-$8,A2), D1     ; Move X2 data from memory, to register D1. it's at the address A2 offset -$8
...                     ; mathy math, add or subtract as necessary
...                     ; if hit, goto hit, otherwise...

The mirrored code should basically be this, but reverse. Check the X2 first, then X1. Subtract the offsets etc. And it is... except for the first command of the right side of the hitbox, that loads in that side's offset. There's basically a typo.

Code:
move.w  (-$8,A2), D1 ; Move X2 data from memory, to register D1. it's at the address A2 offset -$8
...                  ; mathy math, add or subtract as necessary
...                     ; if hit, goto hit, otherwise...
sub.w   (-$4,A2), D1    ; Uh... subtract? Ok. Subtract the value at A2 offset -$4 from the thing already in register D1
...                     ; mathy math, add or subtract as necessary
...                     ; if hit, goto hit, otherwise...

So, 'sub.w' instead of 'move.w'. And that also explains why this thing is so erratic. Depending on what was left in D1 from the previous operation, and the subsequent 'mathy math' step, you get a mix of situations. But the operations do start with the position of the boss, and his right horn...

1643165716819.png


When the boss is firing to the left, you tend to get larger killzones that stray from the sprite. And when the boss fires to the right, what you tend to get is actually inverted hixboxes. So again focus on the right side lightning. On the lightning's left edge, there's an accurately drawn vertical line. It's a valid hitbox border. It is, accurately highlited in green below, the LEFT side of the hitbox. Because of this math error, the RIGHT SIDE of the hitbox is sometimes drawn even farther left than that left-side-hitbox. So, you get an inverted hitbox that can never kill you. The white box below shows what the game is evaluating, but to be clear you'd need to be (at the same moment) both right of the right side and left of the left side of the box for it to kill you.

1643167104456.png


And yes, sometimes the math can build a valid right-side-of-the-hitbox. It's just sometimes wildly wrong compared to the sprite displayed.

How did this bug ship? Basically no other sprites in the game have any sort of non-symmetry, so this code just doesn't run except for this boss's attack. There is actually a lot of code to handle a 'mirror' bit for all kinds of objects in the game (and yes this bug got copy/pasted!), but I think none of those other objects ever 'mirror' so they never trigger it.

If you enjoyed this and want to see it in action, you can try it out with the trainer pretty easily. Pick Nanja and turn on Max Power. Send a boss to the other side. Basically there is no way for it to kill you if you're on the right side of the screen, especially midway up. (sometimes the left lightning can get you if you're near the bottom!) If you play it in FightCade with FBNeo you can turn on the hitboxes and see it in action. Slow mo really bring it into focus as well.

Ok, so we found a bug! Now what? I will publish a Mame cheat with the next release, in case you want to turn this bug off and screw around with the patched version of the game in Mame. I won't provide patched roms because I think the community just wants to stick with the version they have, bugs and all. But it was fun to uncover this so I thought I'd publish it here too.

The bug exists in the NeoGeoCD version of TSS. I was curious if it had been patched, so I looked. I also tried checking the Saturn version but the SH-2 code lost me. An exercise left to the reader...
 
Last edited:
Well this took a while, but I finally got around to updating the guide; includes new images and GIFs to reflect the latest additions.

https://docs.google.com/document/d/1k8kBUJfGd1g5rbcCqS_6qCtkzSk2FGAixgWwtmbfAgs/view

(Best viewed on Desktop, sorry Mobile users)

The latest additions to the Trainer Package V0.3:

Hitbox Lua (for Fightcade FBNeo)

m70m1MW.png


Historically speaking, we never knew what the hitboxes were in this game. Most of the information that I came across were guesstimates and word of mouth. In 2018 I spent a couple of months playing the game with UniBios enabled and forced the CPU to constantly summon Boss Attacks and EX-Attacks; this is not the ideal way to play, but it gave me a better understanding of how every attack behaved, and gave me an idea of what was/wasn't safe (i.e. EX Attacks had an explosion, how big was the hitbox and how long did it last, etc.) which I applied to online/versus play. If anyone had any questions on how to handle certain attacks, I explained what needed to be done especially if the screen got super busy.

In 2020, another player and I planned on making a page that had our hitbox guesstimates of every object and character in the game, but we held off on it because we didn't have all the definite information we needed.

-

Shortly after the initial release of the Trainer, I asked ekorz if it was possible to make a lua script to show the hitboxes of all the objects and characters in the game. Most of the lua scripts we saw were readily available for fighting games (i.e. ST, KoF, Garou) but there wasn't much for shmups. I went back to Jedpossum's Github and saw there was a repository of various scripts they made in the past, one of which was a DariusG script and figured it could be used as a starting point.

About a week later, ekorz made a test version and it was sent out to a few users within our Discord. One of our players from Japan did extensive testing with all of the characters and really broke the game down to see how the script behaved which helped ekorz squash the bugs and iron out the final version.

Fast forward to today, we finally have the hitboxes documented and readily available. Our players checked out the script and we learned several things:
1.) We didn't know all the characters share the same exact shot hitbox (with and without stun); initially it was believed they had different hitboxes due to their shape and size, but that doesn't seem to be the case. For example, we thought Sprites (or Schmitt) had a width advantage versus (let's say) Kim with hitting certain formations because of how their shots sprites looked, but the only difference between all of them is the travel speed.
2.) Nanja's Boss has a lightning attack that's bugged; it's known to make contact with the player despite being on the opposite side of the attack which has happened to a few of us before.
ekorz explains why this happens.
3.) Mevious' Boss has a tail/chain attack that has a vulnerable hitbox.
4.) Nanja's EX Attack hitbox stays the same despite the animation. I personally thought as the Nanja is hopping, the hitbox gets smaller allowing you to squeeze by, but it stays the same.
5.) Schmitt's EX Attack has three different hitboxes as the rocket travels from bottom to top of the screen; there's the initial launch, it speeds up a little bit, and then goes into full speed. The hitbox gets longer on the bottom and alternates.
6.) Macky's EX Attack is a tall rectangle for a circular character which we thought was hilarious; explains why it hits hard at full throttle.

Within the Google Document, I included the following hitbox images: Player, Shot, Formation (Bubble and without), Coin (Bomb, Star, Money), Explosion (very important), Fireball, EX Attack, and Boss.

I didn't include each individual Boss Attack just yet, I need to figure out a proper way to present it. Will be added at a later date since we'll need* it anyways.

CmaSHCx.gif


Fever Mode (Toggleable On/Off)

I asked if it were possible to add this option into the menu. I can openly admit and say late game matches (150+ seconds), once the opponent gets Fever, I tend to struggle a bit since the Formations and Fireballs travel at high speeds. The Fireballs change their trajectory so fast in Fever mode, you have very little room to react; especially if they travel in a parabolic angle while the game speed is constantly changing and Formations come in at the worst moments, it's very difficult to manage at high level play. I wanted to practice and be better at surviving Fever Mode since that's another important aspect of the game.

MAX POW (Toggleable On/Off)

If you're practicing with a friend or by yourself against the CPU, you can launch a few Level 2s or Boss Attacks here and there to see how everything behaves. Since most fighting games with a training mode allow you to have maximum gauge, it made sense to include it in menu.

-

As mentioned in my last post, we have a Discord group where we tend to meetup and run a few sessions on Fightcade. Our player base is scattered with people from Japan, France, Spain, Canada, and America. We're always happy to see new (or familiar) faces come through to the lobby. With the resources we have available now, this is a great time to pick up and play.

Once again, thank you @ekorz for taking the time to get this Trainer Package together. This will definitely help players of all skill levels go about thoughtful practice and teach others along the way.

* In the future we may create a wiki or something similar to better organize everything. Still looking into it.
 
Last edited:
Thanks @Swirl for the epic documentation and for sharing this all with the community! I updated the first post with a zipfile containing the Lua script and IPS patches for darksoft (prom) and mame/fbneo/fightcade (224-p1.p1). Also the TSS discord has a full trainer package download last time I checked, if you're having any trouble. Have fun!

Code:
*new maxpow bytes at $366a,A4 (10b66a)     10c66a

in the player position eval (once per ship)
007E8
jump instead to DB8D0

4EF9 000D B8D0  (jmp $db8D0)
B9 4E 0D 00 D0 B8

at DB8D0

; 4A2C 366A         tst.b   ($366A,A4)
; 2C4A 6A36

; 6706              beq to the other stuff
; 06 67

; 397C 3000 408A     set the player's counter to 0003, ($4252,A4)* (move.b)
; 7C39 0030 8A40

do the thing that was there:
007E86: 426E 0136                clr.w   ($136,A6)
007E8A: 7200                     moveq   #$0, D1

;6E 42 36 01 00 72

;4e75 rts
;754e

Code:
new fever bytes at $3668,A4 (10b668)     10c668
*new maxpow bytes at $366a,A4 (10b66a)     10c66a

When there is a value in the timer, the game engages fever
When there is no value left in the timer, it disengages it
so adding time is the least amount of work you need to do

the menu section should add time to the fever counter for each player
that memory location is $4252,A4 - 10C252 or 10D252
make that 0003, that's all it needs to be because:

in the place where fever counter usually subtracts
0x02A946
jump to 0db920
4EF9 000D B920  (jmp $db920)

; WAS 6C 53 52 42 6C 66
; NOW F9 4E 0D 00 20 B9

do a check instead

; 4A2C 3668         tst.b   ($3668,A4)
; 2C4A 6636

; 6706              beq to the subtract step
; 06 67

; 397C 0003 4252     set the player's counter to 0003, ($4252,A4)* (move.b)
; 7C39 0300 5242

; 536C 4252         subq.w  #1, ($4252,A4) ; subtract one from the fever timer
; 6C 53 52 42

; 6706       branch on equal zero (i.e. no time left)
; 06 67

; 4EF9 0002 a9b8  (jmp $db920) not equal zero? jmp here 2a9b8
; F9 4E 02 00 B8 A9

; 4EF9 0002 a94c equal zero? jump here 02A94C
; F9 4E 02 00 4C A9

;2C 4A 66 36 07 67 EC 33 52 42 03 00 6C 53 52 42 75 4E

*if you shoot an orb then it won't keep adding time

ok that works

we need a menu option, and when it's enabled it also needs to write 0003
on every pass

i'm at 17D42


.... patching the round-start to also check the byte
patch at 016C7A

2E 42 51 42 AE 42 54 42 6E 42 52 42 2E 42 40 42 08 70 B9 4E 00 00

4EB9 000D B950 ; jsr
B94E 0D00 50B9



422E 4251          ;      clr.b   ($4251,A6)
2E42 5142

42AE 4254         ;       clr.l   ($4254,A6) ; i used the space it occupied, put it back
AE42 5442


; 4A2E 3668         tst.b   ($3668,A4)
; 2E4A 6836

6604 ; beq

426E 4252                clr.w   ($4252,A6)
6E42 5242

4e75 rts
754e


...also for player 2
.... patching the round-start to also check the byte
patch at 016C9A

2E 42 51 52 AE 42 54 52 6E 42 52 52 2E 42 40 52 08 70 B9 4E 00 00

4EB9 000D B964 ; jsr
B94E 0D00 64B9



422E 5251          ;      clr.b   ($4251,A6)
2E42 5152

42AE 5254         ;       clr.l   ($4254,A6) ; i used the space it occupied, put it back
AE42 5452


; 4A2E 4668         tst.b   ($4668,A4)
; 2E4A 6846

6604 ; beq
04 66

426E 4252                clr.w   ($4252,A6)
6E42 5242

4e75 rts
754e
 
Last edited:
Back
Top