mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-23 20:34:49 -06:00
b76b2a98d0
The look of the next age — the modern equivalent of the ASCII->CP437 leap. Full Unicode is available now, but the whole stack (text frames, golden tests, the Watch's 1ch grid) assumes one glyph = one column, so the enabling piece is a WIDTH RULE, not the glyphs themselves. - textwidth.is_grid_safe: one code point, printable, East-Asian width not Wide/Fullwidth, no combining/format/control category. This is the one-glyph-one-column contract. Ambiguous-width glyphs are ACCEPTED on purpose — they ARE CP437 (the wall, the club-tree, the up-arrow forest) and render single-column on the Western-monospace metrics every surface uses; only genuinely double-width runes are barred. The loader enforces it on every map glyph; the player-name/free-text sanitizer enforces the same rule (the narrow ledger), so a wide name can't shear a frame. - Re-skin: water ~ -> ≋, inn -> ⌂, healer -> ✚, dungeon mouth -> ∩, and the other adventurer -> ☻ (CP437's own player glyph). The colour field the renderer has carried unused since v0.1 now has a second consumer. - Texture variants: grass and water vary by a deterministic per-coordinate hash, rendered identically in the Python frame builder and the Watch's JS. The two are kept in lockstep by shared hash constants + an agreement test that replays the JS arithmetic and asserts it equals the Python output for every variant over a grid — not a comment-coupled copy. - Watch glow-up: a Noto Sans Mono font stack and a UTC-hour day/night tint (the Vale darkens at dusk on the lobby TV). - The curated SAFE_PALETTE is enforced author-usable: a test asserts no palette glyph collides with the reserved player markers, so AUTHORING's generated appendix can't advertise a glyph the loader would reject. - Resume is identity-preserving: an existing character resumes by exact stored name without re-validating the width rule (which governs creation only) — resume must never lock anyone out. Tests 231 -> 283; width edges (CJK/emoji/combining/fullwidth), the Python<->JS lockstep, the palette/reserved guard, and resume-vs-create all pinned and revert-verified.
61 lines
2.3 KiB
Python
61 lines
2.3 KiB
Python
"""Deterministic terrain texturing (understone.screen.texture).
|
|
|
|
Pins the contract the Watch JS mirrors: a textured glyph is a pure function of
|
|
its cell coordinate (stable per cell), an un-listed glyph is returned
|
|
untouched, and the selection formula is ``(x * _HASH_X + y * _HASH_Y) % n``
|
|
derived from the module's hash constants. The formula is asserted against those
|
|
constants so a retune moves the test with it and a drift is caught.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from understone.screen.texture import _HASH_X, _HASH_Y, VARIANTS, textured
|
|
|
|
|
|
def test_untextured_glyph_is_unchanged() -> None:
|
|
"""A glyph with no VARIANTS row passes through verbatim (actors, walls)."""
|
|
for ch in "█@☻⌂$":
|
|
assert textured(ch, 3, 7) == ch
|
|
|
|
|
|
def test_same_coord_same_variant() -> None:
|
|
"""Texturing is position-only and stable: one cell always picks one glyph."""
|
|
first = textured(".", 12, 5)
|
|
for _ in range(5):
|
|
assert textured(".", 12, 5) == first
|
|
|
|
|
|
def test_variant_is_always_in_the_row() -> None:
|
|
"""Every selected glyph is one of the declared variants for its base."""
|
|
choices = VARIANTS["."]
|
|
for x in range(20):
|
|
for y in range(20):
|
|
assert textured(".", x, y) in choices
|
|
|
|
|
|
def test_a_row_uses_more_than_one_variant() -> None:
|
|
"""Across a row the hash spreads — the texture is not a single repeated glyph."""
|
|
seen = {textured(".", x, 0) for x in range(len(VARIANTS["."]) * 4)}
|
|
assert len(seen) > 1
|
|
|
|
|
|
def test_formula_matches_the_hash_constants() -> None:
|
|
"""The selection index is (x * _HASH_X + y * _HASH_Y) % len — the JS twin's formula.
|
|
|
|
Derived from the live ``_HASH_X`` / ``_HASH_Y`` constants (not the literal
|
|
31/17) and checked against the live VARIANTS rows, so it stays a formula
|
|
test that tracks a retune rather than a snapshot a table or constant edit
|
|
could silently invalidate.
|
|
"""
|
|
for base, choices in VARIANTS.items():
|
|
n = len(choices)
|
|
for x, y in [(0, 0), (1, 0), (0, 1), (12, 5), (7, 13), (255, 255)]:
|
|
assert textured(base, x, y) == choices[(x * _HASH_X + y * _HASH_Y) % n]
|
|
|
|
|
|
def test_origin_cell_is_the_base_glyph() -> None:
|
|
"""Cell (0,0) hashes to index 0, which is the base glyph (variants[0])."""
|
|
for base, choices in VARIANTS.items():
|
|
assert textured(base, 0, 0) == choices[0]
|
|
assert choices[0] == base
|