From 3c2860e3762840c3edcb2779addc4d67bba3894b Mon Sep 17 00:00:00 2001 From: chazmm Date: Tue, 8 Sep 2026 16:33:14 +0100 Subject: [PATCH] Resolve original HUD fonts through native VLT font-manager mappings --- docs/native-scoring-hud.md | 16 ++++++++----- tools/prepare_hud.py | 47 +++++++++++++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/docs/native-scoring-hud.md b/docs/native-scoring-hud.md index 52b047b1..33d9a547 100644 --- a/docs/native-scoring-hud.md +++ b/docs/native-scoring-hud.md @@ -49,7 +49,7 @@ the ignored private output directory. Example preparation (data extraction only): ```powershell -python tools/prepare_hud.py --game --output assets/private/hud +python tools/prepare_hud.py --game --output assets/private/hud --collections /private/stock/skater-collections.json ``` The original movie is authored at 1280 by 720. Its score, line, stance, @@ -69,11 +69,15 @@ timeline execution and rendering with multiplicative and additive colors. Superclass preloads and supported action coverage also remain incomplete. The audit's dummy host intentionally does not establish native binding parity. -The movie references `FuturaOuterGlow`, absent under that exact name from the -five extracted bitmap-font banks. The old extractor infers an alias to -`Futura Glow` from matching menu metrics; that is not yet evidence of native -font-server resolution. The manifest reports it as unresolved rather than -silently selecting a substitute. +Font resolution is recovered from FontManager initialization `82808AE8`, +APT-name lookup `82809208` and loading `82809308`. VLT class +`FECFBCAF356518C4` maps the APT name (field `340832CCFD9FDEB4`, layout +8) to +`FileName` (layout +4). In particular, `FuturaOuterGlow` maps to `futurashadow`, +not the prior extractor's inferred `Futura Glow`. The prepared movie has no +unresolved font references. The manifest retains VLT record provenance and +native scale/offset fields. The community symbol calling `82809168` +GetFontByAptName is misleading: that function searches the filename column; +`82809208` searches the APT-name column. Pause, map-change generation reset, resource reuse and the final task-specific release build/launcher remain to be integrated and checked. No game/recomp, diff --git a/tools/prepare_hud.py b/tools/prepare_hud.py index 6d8f2eb8..55c76722 100644 --- a/tools/prepare_hud.py +++ b/tools/prepare_hud.py @@ -8,16 +8,50 @@ import argparse import hashlib import json import sys +import struct from vendor.skate3_ui.project import extract_project from vendor.skate3_ui.scene_graph import AssetCache, SceneFlattener from vendor.skate3_ui.actions import Actions +from asset_pipeline.vlt import hash64 -def prepare(game: Path, output: Path): +def font_mapping(collections: Path, cache: AssetCache) -> dict: + """FontManager82808AE8 table; APT-name lookup82809208, load82809308. + + Resolve the authored FileName, never infer family aliases from metrics. + """ + data = json.loads(collections.read_text(encoding='utf-8')) + result = {} + for row in data['collections']: + if row['class'] != 'Hash_FECFBCAF356518C4': + continue + def field(name): + return row['fields'].get(name, row['fields'].get(f'Hash_{hash64(name):016X}')) + apt_name = row['fields']['Hash_340832CCFD9FDEB4']['data'] + filename = field('FileName')['data'] + matches = [r for r in cache.manifest['bundles'] + if 'font' in r and Path(r['name']).name == filename] + if not matches: + continue # The debug font is not a packaged front-end bitmap bank. + if len(matches) != 1 or apt_name in result: + raise ValueError(f'Ambiguous native font record: {apt_name}') + scale = {name: struct.unpack('>f',bytes.fromhex(field(name)['data']))[0] + for name in ('ScaleX','ScaleY','OffsetX','OffsetY')} + target = matches[0]['font']['family'] + provenance = {'class': row['class'], 'key': row['key'], + 'source_sha256': row['sha256'], 'file_name':filename, + 'native_layout':scale} + cache.add_font_alias(apt_name,target,provenance) + result[apt_name] = provenance + return result + + +def prepare(game: Path, output: Path, collections: Path): extract_project(game, output, prefixes=("data/fe/source/screens/hud2/", "data/fe/source/controls/"), update=True) cache = AssetCache(output) + mappings = font_mapping(collections,cache) name = "data/fe/source/screens/hud2/trickdisplay2" bundle = cache.load_bundle(name) apt_path = output / "raw" / (name + ".apt") @@ -42,17 +76,16 @@ def prepare(game: Path, output: Path): elif c['type_name'] == 'font': family = c['font']['name'] asset = cache.font_asset(family) - # Preserve this explicit source limitation. Preview.18 inferred a - # metric alias in HighlightText; that is not proof of retail HUD - # glyph/raster substitution. Never silently substitute a font. fonts[family] = asset result = { 'format': 'skate3-scoring-hud', 'version': 1, 'source': {'bundle': name, 'apt_sha256': hashlib.sha256(apt_path.read_bytes()).hexdigest(), - 'const_sha256': hashlib.sha256(const_path.read_bytes()).hexdigest()}, + 'const_sha256': hashlib.sha256(const_path.read_bytes()).hexdigest(), + 'collections_sha256': hashlib.sha256(collections.read_bytes()).hexdigest()}, 'characters': list(bundle['characters'].values()), 'shapes': shapes, 'fonts': fonts, 'actions': blocks, + 'font_mappings':mappings, 'unresolved_fonts': [family for family, asset in fonts.items() if asset is None], } target = output / 'runtime' / 'trickdisplay.json' @@ -68,5 +101,7 @@ if __name__ == '__main__': p = argparse.ArgumentParser(description=__doc__) p.add_argument('--game', type=Path, required=True) p.add_argument('--output', type=Path, required=True) + p.add_argument('--collections', type=Path, required=True, + help='Owned private/stock/skater-collections.json') args = p.parse_args() - prepare(args.game.resolve(), args.output.resolve()) + prepare(args.game.resolve(), args.output.resolve(), args.collections.resolve())