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 header.PNG


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:


enableFudgeAim1   0x00

sequence2         0x3120, 0x3121

cueCode1          0x13

sequence2         0x3122, 0x3129

cueCode1          0x16

sequence2         0x312A, 0x312B

cueCode1          0x14

sequence2         0x312C, 0x3133

cueCode1          0x15



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


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:


enableFudgeAim1   0x00

sequence2         0x3120, 0x3121

cueCode1          stepHandL

sequence2         0x3122, 0x3129

cueCode1          stepFootR

sequence2         0x312A, 0x312B

cueCode1          stepHandR

sequence2         0x312C, 0x3133

cueCode1          stepFootL



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:

















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:


startBlockCallback2      0x02, 0x01

sequence2                0x226A, 0x2286



playAction2              0x544, 0x01



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.


playAction2          0x14A, 0x01



rand2                0x02, 0x04

sequence2            0x00, 0x0A


          rand2            0x00, 0x01




sequence2            0x0B, 0x14


          rand2            0x00, 0x01





playAction2          0x93, 0x01


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).


playAction2      0x8E

rand2            0x01, 0x7


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!