Files
tdkpin/original/tools/generate_game_setup_collision_data.py
ddidderr 8b99e9607c feat(reconstruction): complete binary-backed C recovery
Replace the partial mechanics transcriptions with a separate, readable C11
reconstruction of the complete Win16 image while preserving the original raw
Ghidra export as immutable evidence. Cover all ordinary and overlapping entry
points, Borland runtime behavior, Win16 imports, segmented data, callbacks,
resources, indirect control flow, physics, rendering, persistence, and
startup/shutdown lifecycles.

Add deterministic extraction and audit tooling plus address-linked ledgers for
functions, imports, DGROUP ranges and objects, relocations, resources, and
callbacks. The final gate records zero raw, partial, restored, unknown,
blocked, or unclassified required units. Keep the semantic-fidelity boundary
explicit: the portable C is not claimed to reproduce a byte-identical Borland
NE build.

Add strict focused harnesses for every reconstructed C unit, exact resource
round-trip checks, and a 16-bit Borland Real48 reference probe. No Rust source
or Cargo metadata is changed in this phase.

Test Plan:
- `bash original/tools/test_reconstructed_c.sh` -- passed
- `bash original/tools/probe_real48_reference.sh` -- passed bit-for-bit
- `python3 original/tools/audit_reconstruction.py --require-complete` -- passed
- `git diff --cached --check` -- passed
- `git diff HEAD -- '*.rs' Cargo.toml Cargo.lock` -- empty
2026-08-23 16:41:17 +02:00

129 lines
4.1 KiB
Python

#!/usr/bin/env python3
"""Generate typed 1000:0270 collision-call data from preserved raw evidence."""
from __future__ import annotations
import argparse
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
RAW = ROOT / "TDKPIN_GHIDRA_COMPLETE_RAW.c"
OUTPUT = ROOT / "reconstructed" / "tdkpin_game_setup_collision_data.inc"
def calls_from_raw() -> list[list[int]]:
text = RAW.read_text(encoding="utf-8")
start = text.index("/* ===== 1000:0270 ")
end = text.index("/* ===== 1000:51f5 ", start)
text = text[start:end]
needle = "FUN_1008_0138("
calls: list[list[int]] = []
position = 0
while True:
begin = text.find(needle, position)
if begin < 0:
break
cursor = begin + len(needle)
depth = 1
while depth:
character = text[cursor]
if character == "(":
depth += 1
elif character == ")":
depth -= 1
cursor += 1
arguments = text[begin + len(needle) : cursor - 1]
words = [
int(item.strip(), 0) & 0xFFFF
for item in arguments.replace("\n", "").split(",")
]
if len(words) != 40:
raise RuntimeError(f"expected 40 words, found {len(words)}")
calls.append(words)
position = cursor
if len(calls) != 177:
raise RuntimeError(f"expected 177 calls, found {len(calls)}")
return calls
def u16(value: int) -> str:
return f"0x{value:04x}u"
def i32(low: int, high: int) -> str:
value = low | high << 16
return f"(int32_t)UINT32_C(0x{value:08x})"
def real48(words: list[int]) -> str:
bytes_ = []
for word in words:
bytes_.extend((word & 0xFF, word >> 8))
return "{{" + ", ".join(f"0x{byte:02x}" for byte in bytes_) + "}}"
def initializer(words: list[int], ordinal: int) -> str:
fields = [
f".render_bottom={u16(words[0])}",
f".render_right={u16(words[1])}",
f".render_top={u16(words[2])}",
f".render_left={u16(words[3])}",
f".fallback_y_adjust={i32(words[4], words[5])}",
f".fallback_x_adjust={i32(words[6], words[7])}",
f".layer_mask={u16(words[8])}",
f".score_low={u16(words[9])}",
f".score_high={u16(words[10])}",
f".contact_state={u16(words[11])}",
f".flags={u16(words[12])}",
f".response_kick={real48(words[13:16])}",
f".response_auxiliary={real48(words[16:19])}",
f".response_tangent={real48(words[19:22])}",
f".response_normal={real48(words[22:25])}",
f".radius={i32(words[25], words[26])}",
f".point2_y={i32(words[27], words[28])}",
f".point2_x={i32(words[29], words[30])}",
f".point1_y={i32(words[31], words[32])}",
f".point1_x={i32(words[33], words[34])}",
f".coordinate_mode={u16(words[35])}",
f".subtype={u16(words[36])}",
f".type={u16(words[37])}",
f".active={u16(words[38])}",
f".index={u16(words[39])}",
]
return (
f" /* call {ordinal:03d}, record {words[39]:3d} */ "
"{" + ", ".join(fields) + "},"
)
def generated_text() -> str:
calls = calls_from_raw()
lines = [
"/* Generated by tools/generate_game_setup_collision_data.py.",
" * Source: TDKPIN_GHIDRA_COMPLETE_RAW.c, 1000:0270 call order.",
" * Do not hand-edit; run the generator and its --check mode.",
" */",
"static const TdkpinCollisionInitArgs g_game_setup_collision_calls[177] = {",
]
lines.extend(initializer(words, index + 1) for index, words in enumerate(calls))
lines.append("};")
lines.append("")
return "\n".join(lines)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--check", action="store_true")
arguments = parser.parse_args()
generated = generated_text()
if arguments.check:
if not OUTPUT.exists() or OUTPUT.read_text(encoding="utf-8") != generated:
raise SystemExit(f"generated collision data is stale: {OUTPUT}")
return 0
OUTPUT.write_text(generated, encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())