Shared-payload TOC pattern

DS game only γ¨γ‚“γŒγ‚Šγƒœγ‚¦γ‚·γ¨γŠγ—γ‚ƒγ‚Œγͺまほう぀かい The field guide documents the DS game's internals.

Multiple TOC entries point to one payload to deduplicate identical lines. 703 of 1,328 message files use this; re-emit must preserve it.

The pattern

A RESO TOC entry is a (length, data_offset) pair (variant=1, 8 bytes per entry). The format allows two TOC entries to carry the same data_offset and length, in which case they share a single physical payload. The engine reads the payload identically regardless of which TOC entry index requested it.

Empirical scan across data/message/**/*.ofs:

MetricValue
Files scanned1,328
Files using payload sharing703 (52.9%)
Distinct shared groups2,890
Pre-canonicalisation row pairs13,422 (6,696 already consistent + 6,726 needing alignment)

Example: msg51016.ofs

Original file: 3,472 bytes. RESO header variant=1, file_count=100. Of the 100 TOC entries, only 27 distinct data_offset values.

Entry range Original data_offset Payload length Shared by N entries
0..90x033015810
10..190x03D02210
20..290x03F046810
30..390x05D03410
40..490x06003410
60..690x063047210
73,830x08A0602
75,85,950x08F0163
76,86,920x09001443
78,880x0A30382
79,89,940x0A601063
96..990x0D70304

Unique payload bytes: 2,526. Phantom bytes saved by sharing: 11,412 (3.5Γ— the payload section if expanded out).

Why this matters for re-emission

Without dedup, a fresh payload is written per TOC entry, so a 100-entry-but-27-payload original becomes a 90+ distinct-payload rebuilt file. msg51016.ofs ballooned from 3,472 B β†’ 19,728 B before the dedup fix. Across the corpus the unfixed re-emit added β‰ˆ3.54 MB of pure layout waste.

Dedup logic in re-emit

From src/translator/reemit.py::rewrite_reso_container (variant-1 header, 8-byte entries):

header_entry_size = 16 if h.variant == 0 else 8
payload_start     = 0x10 + h.file_count * header_entry_size

new_header        = bytearray()
payload_section   = bytearray()
payload_offsets   : dict[bytes, int] = {}   # payload-bytes -> offset

for ei in range(h.file_count):
    payload = new_payloads.get(ei, b"")
    if len(payload) == 0:
        new_header += struct.pack("<II", 0, 0)
    elif payload in payload_offsets:                     # ← REUSE OFFSET
        offset = payload_offsets[payload]
        new_header += struct.pack("<II", len(payload), offset)
    else:                                                # ← NEW PAYLOAD
        offset = payload_start + len(payload_section)
        payload_offsets[payload] = offset
        payload_section += payload
        pad = (16 - (len(payload) % 16)) % 16            # ← 16-B align
        payload_section += b"\x00" * pad
        new_header += struct.pack("<II", len(payload), offset)
    if header_entry_size == 16:                          # variant-0: copy type_tag + unk
        new_header += raw[0x10 + ei*16 + 8 : 0x10 + ei*16 + 16]

Canonicalisation

Dedup only helps when the per-entry EN payloads happen to be byte-identical. Because each entry was translated independently, two entries that shared a JP payload often produced different EN paraphrases. Canonicalisation walks each original shared group and forces all members to the same EN string before re-emit.

Selection rule per group (from shared_payload_canonicalization.md):

  1. If any row in the group is marked manual, that row wins.
  2. Else: longest en_text wins (most likely to fit budget).
  3. Tie-break: lowest entry_id.
MetricValue
Rows changed by canonicalisation6,726
Manual rows preserved3
Structural mismatches0
Bytes recovered from re-emit~1.52 MB (43% of bloat)

Top growers (before dedup + canonicalisation)

FileOriginalPatched (naive)Dedup savings
msg510163,47219,72811,412
msg420163,18419,31210,510
msg4904311,04027,1689,952
msg4804312,36828,46410,836
msg5004311,18426,91210,330
msg4304313,00828,6568,144
msg410163,13618,5128,988
msg400163,20018,3848,878
msg470163,20018,3689,596
msg480163,34418,1929,132

All ten files are msg<N>016.ofs or msg<N>043.ofs in the per-chapter career directories. The shared-group pattern is the engine's "pick one variant by (npc_id mod N)" table.

Source