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
Calico.scp
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...
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?
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...
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!