re: mission scoring and the S/A/B/C/D rank thresholds
sub_8230D1F8 -- the loader that contaminated the AA_/AV_ offset search -- is the stage-settings loader. Reading its 122 field NAMES instead of its offsets found the scoring block, which nothing in docs/re owned. 24 IDXD objects per language pack x 6 = 144, each holding Score_Easy / Score_Normal / Score_Hard: 72 records per pack on one 22-field schema, no variants. Difficulty moves 10 of the 22 fields and never the five RankScore_* thresholds -- the rank bar is per stage, difficulty scales the earning rate (x0.5 / x1.0 / x2.0) and the penalties. 23 of 24 objects differ from the commonest Normal record; 9 of 72 records zero the scoring entirely. Not settled: which object is which stage. None of AUTO_SETTINGS's 28 filenames resolves to any of the 24 under 19 prefixes, and 24 vs 28 is unexplained. New doc structures/mission-scoring.md, regenerator mission_scoring.py, artefact data/mission-scoring.txt. Twelve artefacts now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
96
tools/re-capture/mission_scoring.py
Normal file
96
tools/re-capture/mission_scoring.py
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Regenerate docs/re/data/mission-scoring.txt.
|
||||
|
||||
The per-stage mission scoring / rank-threshold tables: 24 IDXD objects per
|
||||
language pack, each holding Score_Easy / Score_Normal / Score_Hard with one
|
||||
22-field schema. Loader: sub_8230D1F8.
|
||||
|
||||
Run: python3 mission_scoring.py > ../../docs/re/data/mission-scoring.txt
|
||||
"""
|
||||
import sys, os, glob, collections
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from unit_substructures import pak_entries
|
||||
import unitgroup as U
|
||||
|
||||
DAT = '/work/sylph_extract/dat'
|
||||
KEY = 'RankScore_S'
|
||||
DIFFS = ('Score_Easy', 'Score_Normal', 'Score_Hard')
|
||||
|
||||
|
||||
def main():
|
||||
percopy = {}
|
||||
for p in sorted(glob.glob(os.path.join(DAT, '*.pak'))):
|
||||
n = sum(1 for _, s in pak_entries(p)
|
||||
if s[:4] == b'IDXD' and KEY.encode() in s)
|
||||
if n:
|
||||
percopy[os.path.basename(p)] = n
|
||||
|
||||
P = os.path.join(DAT, 'GP_MAIN_GAME_E.pak')
|
||||
tables = []
|
||||
for h, s in pak_entries(P):
|
||||
if s[:4] != b'IDXD' or KEY.encode() not in s:
|
||||
continue
|
||||
try:
|
||||
recs = U.parse(s)
|
||||
except Exception:
|
||||
continue
|
||||
tables.append((h, {r['squadron']: U.named(r) for r in recs
|
||||
if r['squadron'] in DIFFS}))
|
||||
tables.sort()
|
||||
|
||||
print('# Mission scoring / rank thresholds (regenerated by mission_scoring.py)')
|
||||
print()
|
||||
print('IDXD objects carrying %s, per pack: %s' % (KEY, percopy))
|
||||
print('total across the disc : %d' % sum(percopy.values()))
|
||||
print('difficulty records per object : %s' % sorted(
|
||||
{tuple(sorted(t)) for _, t in tables}))
|
||||
|
||||
schemas = collections.Counter()
|
||||
for _, t in tables:
|
||||
for d in DIFFS:
|
||||
schemas[tuple(sorted(t[d]))] += 1
|
||||
print('distinct field sets : %d' % len(schemas))
|
||||
fields = sorted(schemas.most_common(1)[0][0])
|
||||
print('records : %d (%d fields each)' % (
|
||||
sum(schemas.values()), len(fields)))
|
||||
|
||||
# how much does each field actually vary?
|
||||
print()
|
||||
print('value census per field (over %d records):' % sum(schemas.values()))
|
||||
for f in fields:
|
||||
c = collections.Counter(t[d][f] for _, t in tables for d in DIFFS)
|
||||
vals = ' '.join('%s=%d' % (k, v) for k, v in sorted(
|
||||
c.items(), key=lambda kv: (-kv[1], kv[0])))
|
||||
print(' %-32s %2d distinct %s' % (f, len(c), vals[:110]))
|
||||
|
||||
# does difficulty move anything?
|
||||
print()
|
||||
print('fields that differ between the three difficulty records:')
|
||||
moved = collections.Counter()
|
||||
for _, t in tables:
|
||||
for f in fields:
|
||||
if len({t[d][f] for d in DIFFS}) > 1:
|
||||
moved[f] += 1
|
||||
print(' %s' % (dict(moved) if moved else 'NONE — Easy/Normal/Hard are identical everywhere'))
|
||||
|
||||
# per-object variation
|
||||
print()
|
||||
print('objects whose Normal record differs from the commonest Normal record:')
|
||||
base = collections.Counter(tuple(sorted(t['Score_Normal'].items()))
|
||||
for _, t in tables).most_common(1)[0][0]
|
||||
odd = [(h, t) for h, t in tables
|
||||
if tuple(sorted(t['Score_Normal'].items())) != base]
|
||||
print(' %d of %d objects' % (len(odd), len(tables)))
|
||||
for h, t in odd:
|
||||
diff = {f: t['Score_Normal'][f] for f in fields
|
||||
if t['Score_Normal'][f] != dict(base)[f]}
|
||||
print(' %#010x %s' % (h, diff))
|
||||
|
||||
print()
|
||||
print('the commonest Normal record:')
|
||||
for k, v in sorted(dict(base).items()):
|
||||
print(' %-32s %s' % (k, v))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user