SL2 Analyzer
Read a FromSoftware .sl2 save and report what is in it
Loading...
Searching...
No Matches
gen_ds3_npcs Namespace Reference

Functions

 address (fid)
 Flag id -> (distance into the flag region, bit), or None outside groups 0..9.
 
 label_for (names)
 The best label for a flag, given every row that carries it.
 
 main ()
 

Variables

 BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
 SRC = os.path.join(BASE, "test", "flag_data", "files8", "dark_souls_3_flags.csv")
 
 OUT = os.path.join(BASE, "db_ds3", "npcs.json")
 
int GROUP_STRIDE = 128
 
int GROUP_ZERO = 111
 
dict TITLE
 

Detailed Description

Build `db_ds3/npcs.json` — Dark Souls III NPC deaths, hostility and quest states.

Sources, both in `test/flag_data/files8/dark_souls_3_flags.csv`: Smithbox's committed
`Documentation/DS3/Info - QWC Flags.txt`, which is FromSoft's own English descriptions,
and rows extracted from the decompiled `.emevd` (`20006002` awaits a `CharacterDead` then
sets a flag; `20006000` sets `HostileNPC` then a flag).

THE BASE IS THE INTERESTING PART, and it is derived rather than taken from anywhere. Three
DS3 common-group bases had been pinned one differential at a time; they are not
independent. A group holds `n < 1000`, so it occupies 128 bytes, and groups 0..9 are packed
128 apart inside the `k = 0` category:

    base(g) = 111 + 128 * g          # g < 10

`6 -> 879` and `9 -> 1263` fall straight out of that, which is two of the three already
known. It predicts `1 -> 239`, and group 1 is where most of these flags live. Checked three
ways: at 239 all 161 group-1 flags are MONOTONE across a 79-save ladder with 43 ever set,
the only rival candidate is non-monotone eleven times, and -- the one that settles it --
flag `1218` "character 3000700 killed" first reads set in exactly the 33:31:38 snapshot,
which is where Ringfinger Leonhard's own Red Eye Orb pickup flag turns on. 3000700 is
Leonhard. The whole family dates correctly besides: `4500701` (Ariandel) at 45:06,
`5100810` (Ringed City) at 56:59, `5000705` (Dreg Heap) at 62:49.

TWO WARNINGS THAT MUST SURVIVE INTO THE REPORT.

  * The QWC descriptions are NOT reliable per row. The same ladder that confirmed the base
    shows `1158` -- which the source calls "after killing Leonhard" -- never setting on a
    run where Leonhard demonstrably died. The EMEVD-derived row is right and the English
    label is on the wrong one. Where a flag has both, the English is kept because it is
    what a reader can use, and the entity id is appended so a wrong label is checkable.
  * `character 3000700 killed` is an entity id, not a name. Rows with no English label
    print that, because the alternative is inventing a name.

Only flags in groups 0..9 are emitted. The `70000` and `73xxx`-`74xxx` shop and handover
groups are two orders of magnitude past the single-digit grid, so this shortcut cannot
reach them and each needs its own anchor -- a differential either side of handing something
to the Shrine Handmaid.

Run from repo root: python3 tools/gen_ds3_npcs.py

Function Documentation

◆ address()

gen_ds3_npcs.address (   fid)

Flag id -> (distance into the flag region, bit), or None outside groups 0..9.

Definition at line 70 of file gen_ds3_npcs.py.

Referenced by main().

Here is the caller graph for this function:

◆ label_for()

gen_ds3_npcs.label_for (   names)

The best label for a flag, given every row that carries it.

An English description beats an entity id, because it is the half a reader can act on. The entity ids are appended when there is a description AND ids, so a label the ladder later proves wrong – and one already is – can be checked against the entity that actually died rather than quietly believed.

Definition at line 85 of file gen_ds3_npcs.py.

Referenced by main().

Here is the caller graph for this function:

◆ main()

gen_ds3_npcs.main ( )

Definition at line 96 of file gen_ds3_npcs.py.

References address(), label_for(), and main().

Referenced by main().

Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ BASE

gen_ds3_npcs.BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

Definition at line 49 of file gen_ds3_npcs.py.

◆ SRC

gen_ds3_npcs.SRC = os.path.join(BASE, "test", "flag_data", "files8", "dark_souls_3_flags.csv")

Definition at line 50 of file gen_ds3_npcs.py.

◆ OUT

gen_ds3_npcs.OUT = os.path.join(BASE, "db_ds3", "npcs.json")

Definition at line 51 of file gen_ds3_npcs.py.

◆ GROUP_STRIDE

int gen_ds3_npcs.GROUP_STRIDE = 128

Definition at line 55 of file gen_ds3_npcs.py.

◆ GROUP_ZERO

int gen_ds3_npcs.GROUP_ZERO = 111

Definition at line 56 of file gen_ds3_npcs.py.

◆ TITLE

dict gen_ds3_npcs.TITLE
Initial value:
1= {
2 "npc_death": "NPCs killed",
3 "npc_turned_hostile": "NPCs turned hostile",
4 "npc_quest": "Questline states",
5 "npc_handover": "Items handed over",
6 "area_entered": "Areas entered",
7 "world_state": "World state",
8 "dlc_ownership": "DLC owned",
9}

Definition at line 58 of file gen_ds3_npcs.py.