SCRIPTS

Oh boy, one of the greatest mysteries of the Petz world. Known better as "SCP" or "personality files", scripts are a complex matrix of numbers that serve as a sort of coding language. This is an absolute behemoth of a topic, and reversing it is still a work in progress, so read on if you're interested. Hope you like numbers.

Species VS Breed

First, we need to understand how SCPs are actually constructed in memory. One might think that it's a matter of "take the breedfile's SCP", but that's not entirely true! In fact, the breed SCP is just an overlay that goes on top of the species SCP. In other words, the breed's SCP overwrites some aspects of the species SCP, but not all aspects. Internally these are referred to as "parent" (species) and "child" (breed) script files.

 

Let's take a look at the headers of Cat.scp and Calico.scp.

Cat.scp

cat scp header.PNG

Calico.scp

calico scp header.PNG

The highlighted bytes in red denote "number of actions" (in hexadecimal, of course). We can see here that Cat.scp has 0x692 (1,682) actions, whereas Calico.scp only has 0x18 (24)! A huge disparity! It seems like the Calico breed doesn't change very many things from the base cat behavior, haha.

The highlighted bytes in green are important, having something to do with defining which animations are valid as transitions, I think? More on that in a second...

cat action.PNG

An action is a data structure of 32 bytes, divided into 16 words (group of 2 bytes).

Here's a breakdown on what all the numbers mean:

0x05      action ID


0x01    number of scripts


0x82    starting animation

0x92    ending animation


0x01    animation loop modifier *


0x03    currently unknown **

0xFFFF  currently unknown **


0x58    starting offset of first script

* this has various effects

** these two are connected, but purpose is unclear.

they are usually 0x03 and 0xFFFF.

if the start/end anims are both 0x01, these two values will change.

Something I've observed is that the start/end animation values can NOT be higher than the very first value in the SCP -- in the case of Cat.scp, that value is 0xE3, and for Calico.scp it's 0xE4. That's 227/228 in decimal, and there's 492 cat animations, so I'm not sure what's up with that. Maybe animations after that point are layered animations (such as breathing), and as such they can't be used? However, this leads to an interesting point...

When loading the scripts, the game looks at the first value in the species SCP -- for dogs this would be 0xC1, for cats it's 0xE3 -- and multiplies that by the first value in the breed SCP (so, for example, 0xE3 * 0xE4 = 0xCAC2). The game will then allocate that many dwords (group of 4 bytes) in memory. There just so happens to be one dword for every single possible combination of start/end anim values! So, when the game is looking to build a transition from one action to another, it will look at the current action's end animation and try to find an action that will transition from that into the next. So, if we're currently ending an action with the 0x3D animation, and we're going into an action that has a start animation of 0x4D, the game will do a lookup to see if there's an intermediary action that starts with 0x3D and ends with 0x4D! So many different possibilities for behavior!

In addition, one action can have many scripts associated with it. When the state machine calls an action, if it has multiple scripts, the game will pick one to execute. For those who like technical details: When a pet is out, it has an internal value that ticks upwards from 0 - 150 (decimal), and when it reaches 150 it resets back to 0. There's a table of 150 seemingly random numbers inside each breedfile. The game will use that upward-ticking value to select one of those random numbers, do some math on it, and the result will determine which script to use.

Actions VS Scripts

As stated above, actions point to scripts, and they can have multiple scripts associated with them. The scripts themselves, however, are a LOT more complex. So, here's Cat.scp. Notice how the file format changes after the red 0xB1D9?

cat scripts.PNG

The 0xB1D9 marks where the actions end and the scripts begin. That number is the size of the species script section in dwords (group of 4 bytes). When the game overwrites species actions with breed actions, this number gets added to the action's final value, the one that denotes where its first script starts. This is because we don't want to point that action to a random point within the species script section, we want the script from the breed that the value is meant to point to. In memory, the game simply places the breed scripts immediately after the species scripts, so that's why we add that number to any overwritten action's offset.

As for the scripts themselves...

cat script stuff.PNG

Each individual script begins with a value that defines the size of the script (including itself) in dwords. Then, we have the script itself. Every script starts with the value 0x40000000 and ends with 0x40000063. These are start and end markers, respectively. In the middle, we have a combination of various 40-numbers, called verbs, and normal values that are used as parameters (if applicable). So what are verbs, and what do they do?

Verbs, Cues, Fudgers...

Verbs can be best described as various commands that tell the game what to do when it's executing a script. There are 100 total verbs -- 0x40000000 through 0x40000063 -- although some of them are unused. Thanks to a handy table buried in the game's exe, aptly named "theVerbName", I've been able to determine what each verb is named. Note that I'm not too sure why these are called "verbs", but nevertheless...

00 = startPos

01 = actionDone0

02 = actionStart1

03 = alignScripts0

04 = alignBallToPtSetup3

05 = alignBallToPtGo0

06 = alignBallToPtStop0

07 = alignFudgeBallToPtSetup2

08 = blendToFrame3

09 = cueCode1

0A = debugCode1

0B = disableFudgeAim1

0C = disableSwing0

0D = doneTalking0

0E = doneTalking1

0F = enableFudgeAim1

10 = enableSwing1

11 = endBlock0

12 = endBlockAlign0

13 = glueScripts0

14 = glueScriptsBall1

15 = interruptionsDisable0

16 = interruptionsEnable0

17 = lookAtLocation2

18 = lookAtLocationEyes2

19 = lookAtRandomPt0

1A = lookAtRandomPtEyes0

1B = lookAtSprite1

1C = lookAtSpriteEyes1

1D = lookAtUser0

1E = lookForward0

1F = lookForwardEyes0

20 = null0

21 = null1

22 = null2

 

23 = null3

 

24 = null4

 

25 = null5

 

26 = null6

 

27 = playAction2

 

28 = playActionRecall2

 

29 = playActionStore2

 

2A = playLayeredAction3

 

2B = playLayeredAction4

 

2C = playLayeredActionCallback5

 

2D = playLayeredActionCallback6

 

2E = playTransitionToAction1

 

2F = rand2

 

30 = resetFudger1

 

31 = resumeFudging1

 

32 = resumeLayerRotation1

 

33 = sequence2

34 = sequenceToEnd1

35 = sequenceToStart1

36 = setBlendOffset3

37 = setFudgeAimDefaults5

38 = setFudgerDrift2

39 = setFudgerRate2

3A = setFudgerTarget2

3B = setFudgerNow2

3C = setHeadTrackAcuteness

3D = setHeadTrackMode1

3E = setLayeredBaseFrame2

3F = setMotionScale1

40 = setMotionScale2

41 = setReverseHeadTrack1

42 = setRotationPivotBall1


 

43 = soundEmptyQueue0

 

44 = soundLoop1

 

45 = soundSetPan1

 

46 = soundPlay1

 

47 = soundPlay2

 

48 = soundPlay3

 

49 = soundPlay4

 

4A = soundPlay5

 

4B = soundQueue1

 

4C = soundQueue2

 

4D = soundQueue3

 

4E = soundQueue4

 

4F = soundQueue5

 

50 = soundSetDefltVocPitch1

 

51 = soundSetPitch1

 

52 = soundSetVolume1

 

53 = soundStop0

 

54 = startListening0

 

55 = startBlockLoop1

 

56 = startBlockCallback2

 

57 = startBlockChance1

 

58 = startBlockDialogSynch0

 

59 = startBlockElse0

 

5A = startBlockListen0

 

5B = stopFudging1

 

5C = suspendFudging1

 

5D = suspendLayerRotation1

 

5E = tailSetNeutral1

 

5F = tailSetRestoreNeutral1

 

60 = tailSetWag1

 

61 = targetSprite4

 

62 = throwMe0

 

63 = endPos

It appears as though the numbers in each name (i.e. playAction2) match the number of parameters they take.

So, here's what a script looks like when we change the verb numbers into their corresponding human names:

startPos


enableFudgeAim1   0x00


sequence2         0x3120, 0x3121


cueCode1          0x13


sequence2         0x3122, 0x3129


cueCode1          0x16


sequence2         0x312A, 0x312B


cueCode1          0x14


sequence2         0x312C, 0x3133


cueCode1          0x15

                  0x3134


endPos

As we can see, sequence2 is a verb that takes two parameters. These parameters are animation frame numbers (and so is the verb-less last value, 0x3134). The game looks these up in the species' BHD file, and from there it can get the appropriate frames from an individual BDT (animation) file to play. Note that the two parameters represent a range of values, so for example, 0x3122 through 0x3129.

We also see an interesting verb called cueCode1 followed by a single parameter. Believe it or not, I was able to locate a similar reference table containing names for cue codes (again, aptly named "theCueName")! Cues appear to be various triggers that can give the game more information about what is happening.

00 = introDone


01 = introNotDone


02 = grabObject


03 = releaseObject


04 = lookAtInterest


05 = lookAtInteractor


06 = useObject


07 = swatObject


08 = gnawObject


09 = scratchObject


0A = digHole


0B = fillHole


0C = trip


0D = snoreActive


0E = snoreIn


0F = snoreOut


10 = snoreDream


11 = ateFood


12 = scare


13 = stepHandL

14 = stepHandR

15 = stepFootL


16 = stepFootR


17 = stompHandL


18 = stompHandR


19 = stompFootL


1A = stompFootR


1B = land


1C = scuff


1D = showLinez


1E = hideLinez


1F = NONE


20 = cursor


21 = shelf


22 = otherPet


23 = focusSprite1


24 = focusSprite2


25 = focusSprite3


26 = percentChance


27 = ifSoundAdult


28 = isAdoptionKit

Taking another look at our script, we can fill these details in:

startPos


enableFudgeAim1   0x00


sequence2         0x3120, 0x3121


cueCode1          stepHandL


sequence2         0x3122, 0x3129


cueCode1          stepFootR


sequence2         0x312A, 0x312B


cueCode1          stepHandR


sequence2         0x312C, 0x3133


cueCode1          stepFootL

                  0x3134


endPos

From this, without even seeing the animation, we can surmise that this is a walk sequence.

Finally, I'd like to talk about the verb I have yet to touch on -- enableFudgeAim1. There are many verbs that reference a thing called the "Fudger". From what I can gather, the Fudger is in charge of literally fudging certain values -- so, for example, it will change a 50 into a random number between 40 - 60. This may be an effort to have pets appear more "alive". And, before you ask, yes there is a third and final table I've dug up called theFudgeName. Note that a majority of these appear to be unused in Petz.

00 = rotation


01 = roll


02 = tilt


03 = headRotation


04 = headTilt


05 = headCock


06 = rEyelidHeight


07 = lEyelidHeight


08 = rEyelidTilt


09 = lEyelidTilt


0A = eyeTargetX


0B = eyeTargetY


0C = XTrans


0D = YTrans


0E = scaleX


0F = scaleY


10 = scaleZ


11 = ballScale


12 = masterScale


13 = rEyeSizeXXX


14 = lEyeSizeXXX


15 = rArmSizeXXX


16 = lArmSizeXXX


17 = rLegSizeXXX


18 = lLegSizeXXX


19 = rHandSizeXXX


1A = lHandSizeXXX


1B = rFootSizeXXX


1C = lFootSizeXXX


1D = headSizeXXX


1E = bodyExtend


1F = frontLegExtend


20 = hindLegExtend


21 = faceExtend


22 = headEnlarge


23 = headEnlargeBalance

24 = earExtend

25 = footEnlarge

26 = footEnlargeBalance

27 = preRotation


28 = preRoll


29 = addBallz0


2A = addBallzFlower1


2B = addBallzHeart2


2C = addBallzQuestion3


2D = addBallzExclamation4


2E = addBallzLightBulbOff5


2F = addBallzStickMan6


30 = addBallzCrossbones7


31 = addBallzLightning8


32 = addBallzBrokenHeart9


33 = addBallzSnowOne10


34 = addBallzSnowTwo11


35 = addBallzSnowThree12


36 = addBallzLightBulbOn13


37 = addBallzTears14


38 = addBallzOddLove15


39 = morph


3A = bothEyelidHeights


3B = bothEyelidTilts


3C = bothEyeSizes


3D = bothArmSizes


3E = bothLegSizes


3F = rightLimbSizes


40 = leftLimbSizes


41 = allLimbSizes


42 = bothHandSizes


43 = bothFeetSizes


44 = rightDigitSizes


45 = leftDigitSizes


46 = allDigitSizes


47 = allFudgers

Of these, the ones I've only seen a handful get used in Cat.scp. Not sure about other SCPs. Of the ones that are used, it's mostly stuff like eyelid tilt, eye target, head cock, etc. I've never played Oddballz but many of the fudgers sound like they'd be more useful in that game.

Blocks and Action Calls

I'd now like to touch on the following verbs:

startBlockLoop1

startBlockCallback2

startBlockChance1

startBlockDialogSynch0

startBlockElse0

startBlockListen0

endBlock0

endBlockAlign0

playAction2

playActionStore2

playActionRecall2

playLayeredAction3

playLayeredAction4

playLayeredActionCallback5

playLayeredActionCallback6

playTransitionToAction1

On the left, we have a collection of verbs dedicated to defining "blocks" in a script. This appears to be similar to an "if/else" statement in programming. Blocks can be nested within one another, allowing a single script to play out differently depending on RNG (presumably). Let's take a look at some examples from Cat.scp:

startPos

startBlockCallback2      0x02, 0x01

sequence2                0x226A, 0x2286

endBlock0

startBlockElse0

playAction2              0x544, 0x01

endBlock0

endPos

This script appears to have a choice between two blocks, startBlockCallback2 and startBlockElse0. We can see that the first block will play an animation, and the second block will call another action.

startPos

playAction2          0x14A, 0x01

lookAtRandomPt0

startBlockLoop1

rand2                0x02, 0x04

sequence2            0x00, 0x0A

          startBlockLoop1

          rand2            0x00, 0x01

          lookAtRandomPt0

          endBlock0

 

sequence2            0x0B, 0x14

          startBlockLoop1

          rand2            0x00, 0x01

          lookAtRandomPt0

          endBlock0

endBlock0

 

playAction2          0x93, 0x01

endPos

This script appears to rely on RNG to determine how many times the code within each startBlockLoop1 will loop. I believe rand2 takes two arguments representing a range of values (i.e. pick a number between 2 and 4), and in this case the result of rand2 is defining how many times a given block will loop. We can see that in the nested startBlockLoop1 blocks, rand2 is choosing between 0 and 1. This may serve the purpose of defining if the block will run its code at all (1) or not (0). Also, we can see here that blocks can be nested within one another, again much like loops in programming. The limit for nesting appears to be 10 before the game will declare a nesting overflow (I haven't tested that, but I saw some assembly code that suggested it).

startPos

playAction2      0x8E

rand2            0x01, 0x7

endPos

Finally, we can see that scripts have the ability to call other actions. As seen in the earlier scripts, playAction2 takes two arguments -- an action number and, presumably, the number of times to play said action. In our script above, it looks like rand2 is filling in the second parameter with anywhere from 1 to 7.

Also, the verbs playActionStore2 and playActionRecall2 seem to be a mechanism for saving an action and then using it later. I'm not too clear on what advantage this might have, but it exists nonetheless.

That's all for now! I'll update this page as I learn more about SCPs in the future. Thanks for reading!