Bay 12 Games Forum

Please login or register.

Login with username, password and session length
Advanced search  
Pages: 1 [2]

Author Topic: Historical figure personality analysis science  (Read 3958 times)

Quietust

  • Bay Watcher
  • Does not suffer fools gladly
    • View Profile
    • QMT Productions
Re: Historical figure personality analysis science
« Reply #15 on: November 30, 2021, 01:13:33 pm »

Something that might be of interest to you: during my analysis of version 0.28.181.40d, I discovered that a few specific personality traits did indeed have an impact on worldgen behavior:
* High values of EXCITEMENT_SEEKING / ACHIEVEMENT_STRIVING and low values of CAUTIOUSNESS would make civ leaders more likely to declare wars in general
* High values of EXCITEMENT_SEEKING / ACHIEVEMENT_STRIVING / ANGER / SELF_EFFICACY would make civ leaders more likely to declare wars, even in the face of insurmountable odds

I managed to locate the same logic in version 0.47.05, and it's very similar:
* High values of CONFIDENCE, AMBITION, THOUGHTLESSNESS, and EXCITEMENT_SEEKING increase the chances of declaring wars in general
* High values of CONFIDENCE, AMBITION, THOUGHTLESSNESS, and ANGER_PROPENSITY increase the chances of declaring "unwinnable" wars
There's an additional check against EXCITEMENT_SEEKING and AMBITION by itself, and it seems to be in the general proximity of code for establishing alliances.
« Last Edit: November 30, 2021, 02:30:10 pm by Quietust »
Logged
P.S. If you don't get this note, let me know and I'll write you another.
It's amazing how dwarves can make a stack of bones completely waterproof and magmaproof.
It's amazing how they can make an entire floodgate out of the bones of 2 cats.

IndigoFenix

  • Bay Watcher
  • All things die, but nothing dies forever.
    • View Profile
    • Boundworlds: A Browser-Based Multiverse Creation and Exploration Game
Re: Historical figure personality analysis science
« Reply #16 on: November 30, 2021, 03:46:34 pm »

Something that might be of interest to you: during my analysis of version 0.28.181.40d, I discovered that a few specific personality traits did indeed have an impact on worldgen behavior:
* High values of EXCITEMENT_SEEKING / ACHIEVEMENT_STRIVING and low values of CAUTIOUSNESS would make civ leaders more likely to declare wars in general
* High values of EXCITEMENT_SEEKING / ACHIEVEMENT_STRIVING / ANGER / SELF_EFFICACY would make civ leaders more likely to declare wars, even in the face of insurmountable odds

I managed to locate the same logic in version 0.47.05, and it's very similar:
* High values of CONFIDENCE, AMBITION, THOUGHTLESSNESS, and EXCITEMENT_SEEKING increase the chances of declaring wars in general
* High values of CONFIDENCE, AMBITION, THOUGHTLESSNESS, and ANGER_PROPENSITY increase the chances of declaring "unwinnable" wars
There's an additional check against EXCITEMENT_SEEKING and AMBITION by itself, and it seems to be in the general proximity of code for establishing alliances.

Are you able to actually see the code?  How did you figure all this out?
Also, can you figure out why the brainscan script can't find any patterns?
Though, I guess if personality is only relevant for leaders making whole-civ or whole-site decisions, then it would make sense (there are never enough leaders to find a meaningful average).  Still, I would expect that personality should play a role in individual behaviors other than starting wars or making alliances.

Quietust

  • Bay Watcher
  • Does not suffer fools gladly
    • View Profile
    • QMT Productions
Re: Historical figure personality analysis science
« Reply #17 on: November 30, 2021, 04:25:34 pm »

Are you able to actually see the code?  How did you figure all this out?
I identified the function for creating history_event_collection_warst (which is extremely easy to find because of runtime type information, or RTTI), and I traced that backwards to find the function used for declaring wars (or accepting/refusing peace treaties) during worldgen, then I found the function call that takes a unit_personality and an array of personality facet weights and figured out which weights were being assigned.

Of course, this was only possible because I had already located the relevant code in version 0.28.181.40d so I knew exactly what I was looking for.

The code itself is just x86 assembly language with some annotations, looking mostly like this:
Code: [Select]
1405031ea ad8    48 8d 8d b0     LEA      RCX=>weights2,[RBP + 0x1b0]
                   01 00 00
 1405031f1 ad8    e8 2a 91 f8 ff  CALL     init_personality_weights                                    void init_personality_weights(in
 1405031f6 ad8    b8 01 00 00 00  MOV      EAX,0x1
 1405031fb ad8    89 85 68 02     MOV      dword ptr [RBP + weights2[46]+0x9d8],EAX
                   00 00
 140503201 ad8    89 85 fc 01     MOV      dword ptr [RBP + weights2[19]+0x9d8],EAX
                   00 00
 140503207 ad8    89 85 04 02     MOV      dword ptr [RBP + weights2[21]+0x9d8],EAX
                   00 00
 14050320d ad8    89 85 50 02     MOV      dword ptr [RBP + weights2[40]+0x9d8],EAX
                   00 00
 140503213 ad8    4c 8b 6d f0     MOV      R13,qword ptr [RBP + local_9e8+0x9d8]
 140503217 ad8    49 8b 8d 30     MOV      RCX,qword ptr [R13 + 0x130]
                   01 00 00
 14050321e ad8    48 8d 95 b0     LEA      RDX=>weights2,[RBP + 0x1b0]
                   01 00 00
 140503225 ad8    48 8b 49 18     MOV      RCX,qword ptr [RCX + 0x18]
 140503229 ad8    e8 b2 d8 40 00  CALL     unit_personality::getOverallScore                           int getOverallScore(unit_persona
and this:
Code: [Select]
14050326e ad8    48 8d 8d b0     LEA      RCX=>weights2,[RBP + 0x1b0]
                   01 00 00
 140503275 ad8    e8 c6 90 f8 ff  CALL     FUN_14048c340                                               undefined FUN_14048c340()
 14050327a ad8    b8 01 00 00 00  MOV      EAX,0x1
 14050327f ad8    89 85 68 02     MOV      dword ptr [RBP + weights2[46]+0x9d8],EAX
                   00 00
 140503285 ad8    89 85 fc 01     MOV      dword ptr [RBP + weights2[19]+0x9d8],EAX
                   00 00
 14050328b ad8    89 85 04 02     MOV      dword ptr [RBP + weights2[21]+0x9d8],EAX
                   00 00
 140503291 ad8    89 85 c4 01     MOV      dword ptr [RBP + weights2[5]+0x9d8],EAX
                   00 00
 140503297 ad8    49 8b 8d 30     MOV      RCX,qword ptr [R13 + 0x130]
                   01 00 00
 14050329e ad8    48 8d 95 b0     LEA      RDX=>weights2,[RBP + 0x1b0]
                   01 00 00
 1405032a5 ad8    48 8b 49 18     MOV      RCX,qword ptr [RCX + 0x18]
 1405032a9 ad8    e8 32 d8 40 00  CALL     unit_personality::getOverallScore                           int getOverallScore(unit_persona
(the indices on the "weights" and "weights2" arrays identify which traits are being evaluated - the "unit_personality::getOverallScore" function is being called against historical_figure.info.personality)

Also, can you figure out why the brainscan script can't find any patterns?
I haven't looked at your script, so I don't know why it wouldn't have found anything.
Logged
P.S. If you don't get this note, let me know and I'll write you another.
It's amazing how dwarves can make a stack of bones completely waterproof and magmaproof.
It's amazing how they can make an entire floodgate out of the bones of 2 cats.

IndigoFenix

  • Bay Watcher
  • All things die, but nothing dies forever.
    • View Profile
    • Boundworlds: A Browser-Based Multiverse Creation and Exploration Game
Re: Historical figure personality analysis science
« Reply #18 on: December 01, 2021, 12:54:24 am »

--snip--

Oh, wow. So is there a way of translating that into math? Is it just adding up the values of those four traits?

And is there a way of doing something similar for other events?  War was one that, as I said, I wasn't able to average out properly because there are too few war declaring rulers per world. But I did try looking for history_event_collection_theftst and history_event_collection_abductionst and could find no statistically significant deviation from the average kobold or goblin personality.

I'm not sure exactly which events are tied to book writing, seeking immortality, or defiling temples, but when I scan a specific race for units that wrote at least one book, know at least one secret, or have a vampire curse (provided there are enough of them) I could find no deviations from the norm of that race, suggesting that whatever factors are involved in selecting these, personality and values are not among them, which seems wrong.  There were also no connections between personality and whether one had a spouse or a lover (I thought maybe LOVE_PROPENSITY would play a role), a grudge (ditto for HATE_PROPENSITY), the presence of any other relationship, or the number of said relationships.

All my script does is scan all historical figures for individuals that meet a certain set of parameters (for instance, scan all units with race HUMAN and who wrote at least one book), add up all their personality, value, and skill traits, and then divide those by total units scanned to find the average.  The theory being that units associated with a particular action should have the traits involved in that decision be higher/lower than their racial average.  I know I'm getting the right units because their SKILLS deviate from the average as expected (if I scan for all cheesemakers, they have, on average, a very high cheesemaking skill) but as far as I can tell, personality and values are irrelevant.

Quietust

  • Bay Watcher
  • Does not suffer fools gladly
    • View Profile
    • QMT Productions
Re: Historical figure personality analysis science
« Reply #19 on: December 01, 2021, 08:44:23 am »

So is there a way of translating that into math? Is it just adding up the values of those four traits?
The actual math is done inside this pair of functions:
Code: [Select]
                             int __thiscall unit_personality::getOverallScore(unit_pe
             int               EAX:4           <RETURN>
             unit_personali    RCX:8 (auto)    this
             int32_t[50] *     RDX:8           weights
                              unit_personality::getOverallScore
 140910ae0   0    48 89 5c 24 08  MOV      qword ptr [RSP + local_res8],RBX
 140910ae5   0    48 89 6c 24 10  MOV      qword ptr [RSP + local_res10],RBP
 140910aea   0    48 89 74 24 18  MOV      qword ptr [RSP + local_res18],RSI
 140910aef   0    57              PUSH     RDI
 140910af0 008    48 83 ec 20     SUB      RSP,0x20
 140910af4 028    33 f6           XOR      ESI,ESI
 140910af6 028    48 8b fa        MOV      RDI,RDX
 140910af9 028    0f b7 de        MOVZX    EBX,SI
 140910afc 028    48 8b e9        MOV      RBP,RCX
 140910aff 028    90              NOP
                              LAB_140910b00                                      XREF[1]:   140910b34(j) 
 140910b00 028    44 8b 07        MOV      R8D,dword ptr [RDI]
 140910b03 028    45 85 c0        TEST     R8D,R8D
 140910b06 028    7e 0f           JLE      LAB_140910b17
 140910b08 028    0f bf d3        MOVSX    EDX,BX
 140910b0b 028    48 8b cd        MOV      RCX,RBP
 140910b0e 028    e8 3d 00 00 00  CALL     unit_personality::checkTrait                                int checkTrait(unit_personality
 140910b13 028    03 f0           ADD      ESI,EAX
 140910b15 028    eb 12           JMP      LAB_140910b29
                              LAB_140910b17                                      XREF[1]:   140910b06(j) 
 140910b17 028    79 10           JNS      LAB_140910b29
 140910b19 028    41 f7 d8        NEG      R8D
 140910b1c 028    0f bf d3        MOVSX    EDX,BX
 140910b1f 028    48 8b cd        MOV      RCX,RBP
 140910b22 028    e8 29 00 00 00  CALL     unit_personality::checkTrait                                int checkTrait(unit_personality
 140910b27 028    2b f0           SUB      ESI,EAX
                              LAB_140910b29                                      XREF[2]:   140910b15(j), 140910b17(j) 
 140910b29 028    66 ff c3        INC      BX
 140910b2c 028    48 83 c7 04     ADD      RDI,0x4
 140910b30 028    66 83 fb 32     CMP      BX,0x32
 140910b34 028    7c ca           JL       LAB_140910b00
 140910b36 028    48 8b 5c 24 30  MOV      RBX,qword ptr [RSP + local_res8+0x28]
 140910b3b 028    8b c6           MOV      EAX,ESI
 140910b3d 028    48 8b 74 24 40  MOV      RSI,qword ptr [RSP + local_res18+0x28]
 140910b42 028    48 8b 6c 24 38  MOV      RBP,qword ptr [RSP + local_res10+0x28]
 140910b47 028    48 83 c4 20     ADD      RSP,0x20
 140910b4b 008    5f              POP      RDI
 140910b4c   0    c3              RET

...

                             int __thiscall unit_personality::checkTrait(unit_persona
             int               EAX:4           <RETURN>
             unit_personali    RCX:8 (auto)    this
             personality_fa    EDX:4           type
             int               R8D:4           rolls
                              unit_personality::checkTrait
 140910b50   0    48 89 5c 24 10  MOV      qword ptr [RSP + local_res10],RBX
 140910b55   0    57              PUSH     RDI
 140910b56 008    48 83 ec 20     SUB      RSP,0x20
 140910b5a 028    41 8b d8        MOV      EBX,R8D
 140910b5d 028    4c 63 c2        MOVSXD   R8,EDX
 140910b60 028    48 8b 91 58     MOV      RDX,qword ptr [RCX + 0x158]
                   01 00 00
 140910b67 028    42 0f b7 84     MOVZX    EAX,word ptr [RCX + R8*0x2 + 0x80]
                   41 80 00 00 00
 140910b70 028    48 85 d2        TEST     RDX,RDX
 140910b73 028    74 16           JZ       LAB_140910b8b
 140910b75 028    66 42 03 04 42  ADD      AX,word ptr [RDX + R8*0x2]
 140910b7a 028    79 04           JNS      LAB_140910b80
 140910b7c 028    33 c0           XOR      EAX,EAX
 140910b7e 028    eb 0b           JMP      LAB_140910b8b
                              LAB_140910b80                                      XREF[1]:   140910b7a(j) 
 140910b80 028    66 83 f8 64     CMP      AX,0x64
 140910b84 028    7e 05           JLE      LAB_140910b8b
 140910b86 028    b8 64 00 00 00  MOV      EAX,0x64
                              LAB_140910b8b                                      XREF[3]:   140910b73(j), 140910b7e(j),
                                                                                             140910b84(j) 
 140910b8b 028    33 ff           XOR      EDI,EDI
 140910b8d 028    85 db           TEST     EBX,EBX
 140910b8f 028    7e 5d           JLE      LAB_140910bee
                              LAB_140910b91                                      XREF[4]:   14106e98c(*), 14106e99c(*),
                                                                                             141ef2ec0(*), 141ef2ec8(*) 
 140910b91 028    48 89 74 24 30  MOV      qword ptr [RSP + local_res8+0x28],RSI
 140910b96 028    0f bf f0        MOVSX    ESI,AX
 140910b99 028    ff c6           INC      ESI
 140910b9b 028    0f 1f 44 00 00  NOP      dword ptr [RAX + RAX*0x1]
                              LAB_140910ba0                                      XREF[1]:   140910be7(j) 
 140910ba0 028    83 fe 01        CMP      ESI,0x1
 140910ba3 028    77 04           JA       LAB_140910ba9
 140910ba5 028    33 c0           XOR      EAX,EAX
 140910ba7 028    eb 38           JMP      LAB_140910be1
                              LAB_140910ba9                                      XREF[1]:   140910ba3(j) 
 140910ba9 028    e8 12 b5 0e 00  CALL     mt_trandom                                                  int32_t mt_trandom(void)
 140910bae 028    44 8b c0        MOV      R8D,EAX
 140910bb1 028    b8 03 00 00 00  MOV      EAX,0x3
 140910bb6 028    41 f7 e0        MUL      R8D
 140910bb9 028    41 8b c0        MOV      EAX,R8D
 140910bbc 028    2b c2           SUB      EAX,EDX
 140910bbe 028    d1 e8           SHR      EAX,1
 140910bc0 028    03 c2           ADD      EAX,EDX
 140910bc2 028    33 d2           XOR      EDX,EDX
 140910bc4 028    c1 e8 1e        SHR      EAX,0x1e
 140910bc7 028    69 c0 ff ff     IMUL     EAX,EAX,0x7fffffff
                   ff 7f
 140910bcd 028    44 2b c0        SUB      R8D,EAX
 140910bd0 028    b8 ff ff ff 7f  MOV      EAX,0x7fffffff
 140910bd5 028    f7 f6           DIV      ESI
 140910bd7 028    33 d2           XOR      EDX,EDX
 140910bd9 028    8d 48 01        LEA      ECX,[RAX + 0x1]
 140910bdc 028    41 8b c0        MOV      EAX,R8D
 140910bdf 028    f7 f1           DIV      ECX
                              LAB_140910be1                                      XREF[1]:   140910ba7(j) 
 140910be1 028    03 f8           ADD      EDI,EAX
 140910be3 028    ff cb           DEC      EBX
 140910be5 028    85 db           TEST     EBX,EBX
 140910be7 028    7f b7           JG       LAB_140910ba0
 140910be9 028    48 8b 74 24 30  MOV      RSI,qword ptr [RSP + local_res8+0x28]
                              LAB_140910bee                                      XREF[3]:   140910b8f(j), 141ef2ecc(*),
                                                                                             141ef2ed4(*) 
 140910bee 028    8b c7           MOV      EAX,EDI
 140910bf0 028    48 8b 5c 24 38  MOV      RBX,qword ptr [RSP + local_res10+0x28]
 140910bf5 028    48 83 c4 20     ADD      RSP,0x20
 140910bf9 008    5f              POP      RDI
 140910bfa   0    c3              RET

Also, I mentioned that it's all x86 assembly, but Ghidra is also able to produce a reasonable approximation of C code:
Code: [Select]
int __thiscall unit_personality::getOverallScore(unit_personality *this,int32_t (*weights) [50])

{
  int iVar1;
  short sVar2;
  int iVar3;
 
  iVar3 = 0;
  sVar2 = 0;
  do {
    iVar1 = (*weights)[0];
    if (iVar1 < 1) {
      if (iVar1 < 0) {
        iVar1 = checkTrait(this,(int)sVar2,-iVar1);
        iVar3 -= iVar1;
      }
    }
    else {
      iVar1 = checkTrait(this,(int)sVar2,iVar1);
      iVar3 += iVar1;
    }
    sVar2 += 1;
    weights = (int32_t (*) [50])(*weights + 1);
  } while (sVar2 < 0x32);
  return iVar3;
}

int __thiscall
unit_personality::checkTrait(unit_personality *this,personality_facet_type type,int rolls)

{
  uint16_t uVar1;
  uint uVar2;
  int iVar3;
 
  uVar1 = (&(this->traits).LOVE_PROPENSITY)[type];
  if (this->temporary_trait_changes != NULL) {
    uVar1 += (&this->temporary_trait_changes->LOVE_PROPENSITY)[type];
    if ((short)uVar1 < 0) {
      uVar1 = 0;
    }
    else {
      if (100 < (short)uVar1) {
        uVar1 = 100;
      }
    }
  }
  iVar3 = 0;
  if (0 < rolls) {
    do {
      if ((int)(short)uVar1 + 1U < 2) {
        uVar2 = 0;
      }
      else {
        uVar2 = mt_trandom();
        uVar2 = (uVar2 % 0x7fffffff) /
                ((int)(0x7fffffff / (ulonglong)((int)(short)uVar1 + 1U)) + 1U);
      }
      iVar3 += uVar2;
      rolls += -1;
    } while (0 < rolls);
  }
  return iVar3;
}
The actual trait checks are random, but during worldgen they will be under the influence of one of the worldgen seeds (probably the History one) so they'll still be consistent.

I should note that all of these functions started out with names like "FUN_140910ae0" and "FUN_140910b50" - it was only through manual analysis that the types of all the different function parameters were identified (typically by starting from code where the data types can be trivially identified, and then tracing outwards from there) and meaningful names could be assigned to the functions themselves.

It also greatly helps that a small part of the source code for the Linux version is available (the "g_src" directory, which mainly only contains the code for rendering graphics and handling keyboard/mouse input).
Logged
P.S. If you don't get this note, let me know and I'll write you another.
It's amazing how dwarves can make a stack of bones completely waterproof and magmaproof.
It's amazing how they can make an entire floodgate out of the bones of 2 cats.
Pages: 1 [2]