Files
gta6/tools/extract/leonida/coords.py
T
knsoandClaude Haiku 4.5 f0f7692f14 fix(coords): Chunk-Namen korrekt für Float-Indizes und Newlines
- chunk_name: Reject float/bool indices like JavaScript (Number.isInteger)
- parse_chunk_name: Use fullmatch instead of match($) to reject strings with trailing newlines
- Add invalidIndices fixture cases to catch these edge cases in both languages

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-09-13 09:49:53 +02:00

55 lines
1.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Umrechnung zwischen gtadb-Weltkoordinaten, Kacheln und Mosaikpixeln.
Spiegelt src/world/coords.js. Beide Seiten müssen dieselben Werte liefern.
"""
import re
TILE_PX = 256
WORLD_HALF = 16384
CHUNK_SIZE = 250
SLICE = {"x0": -1000, "x1": 2000, "y0": -500, "y1": 2500}
def tile_for_world(x: float, y: float) -> tuple[int, int]:
return (int((WORLD_HALF - y) // TILE_PX), int((x + WORLD_HALF) // TILE_PX))
def slice_tiles() -> list[tuple[int, int]]:
r0, c0 = tile_for_world(SLICE["x0"], SLICE["y1"])
r1, c1 = tile_for_world(SLICE["x1"] - 1, SLICE["y0"] + 1)
return [(r, c) for r in range(r0, r1 + 1) for c in range(c0, c1 + 1)]
def mosaic_origin_px() -> tuple[int, int]:
"""Pixelversatz der Ausschnittsecke innerhalb des ungeschnittenen Kachelrasters."""
r0, c0 = tile_for_world(SLICE["x0"], SLICE["y1"])
return (SLICE["x0"] + WORLD_HALF - c0 * TILE_PX, (WORLD_HALF - SLICE["y1"]) - r0 * TILE_PX)
def pixel_to_world(px: int, py: int) -> tuple[float, float]:
return (SLICE["x0"] + px, SLICE["y1"] - py)
CHUNKS_PER_SIDE = (SLICE["x1"] - SLICE["x0"]) // CHUNK_SIZE # 12
CHUNK_COLUMNS = "ABCDEFGHIJKL"
_CHUNK_NAME = re.compile(r"([A-La-l])([1-9]\d?)")
def _is_index(k) -> bool:
return isinstance(k, int) and not isinstance(k, bool) and 0 <= k < CHUNKS_PER_SIDE
def chunk_name(i: int, j: int) -> str | None:
"""Planquadrat-Name wie src/world/coords.js: Spalte A–L aus i, Zeile 1–12 aus j. A1 = Nordwest."""
if not (_is_index(i) and _is_index(j)):
return None
return f"{CHUNK_COLUMNS[i]}{j + 1}"
def parse_chunk_name(name: str) -> tuple[int, int] | None:
"""Umkehrung von chunk_name. Akzeptiert Kleinschreibung, None außerhalb A1–L12."""
match = _CHUNK_NAME.fullmatch(str(name))
if not match or int(match.group(2)) > CHUNKS_PER_SIDE:
return None
return CHUNK_COLUMNS.index(match.group(1).upper()), int(match.group(2)) - 1