"""Single source of truth for the wgt01 (Westgate Urban Foundation) tileset.

Pure Python, NO bpy — imported by build.py (Blender), make_wok.py and make_set.py
alike. Defines the 40-tile table, materials, the 12x12 test map, and the pure
functions that derive visuals (paint), heights, walkmesh quads and the NWN .set
mapping from a tile spec. Blender realises these; the .wok/.set tools consume the
same data so nothing drifts.

Coordinate convention (matches KICKOFF.md): tile centred at origin, X/Y in
[-5,+5], Z=0 ground, Z=1.5 raised tier, Z=-0.3 canal water. Z up.
"""

PREFIX = "twu01"  # Tileset Westgate Urban 01 (5-char resref prefix, AGENTS.md §1)
HALF = 5.0
Z_RAISED = 1.5
Z_WATER = -0.3

# --- prototype materials. Bright, DISTINCT per role so structure reads at a glance
# in the toolset (KICKOFF.md wants geometry not art; the old near-black palette made
# every tile invisible). Curb/edge is deliberately loud so canal/height edges pop.
COLORS = {
    "MAT_wet_cobble_dark": (0.42, 0.42, 0.47),   # cobble ground — cool mid grey
    "MAT_cobble_patch":    (0.50, 0.34, 0.22),   # repair patch — brown
    "MAT_plaza_slab":      (0.74, 0.68, 0.52),   # plaza — warm tan
    "MAT_sidewalk_stone":  (0.64, 0.65, 0.70),   # sidewalk — light grey
    "MAT_curb_dark_stone": (0.98, 0.55, 0.08),   # EDGES/curbs — loud orange (pop)
    "MAT_canal_black_water": (0.10, 0.38, 0.80), # canal — strong blue (real tga overrides)
    "MAT_void_black":      (0.03, 0.03, 0.05),
    "MAT_ramp_stone":      (0.80, 0.25, 0.68),   # ramps/stairs — magenta (height reads)
    "MAT_drain_iron":      (0.22, 0.24, 0.30),   # iron — dark steel
    "MAT_foundation_pad":  (0.58, 0.54, 0.34),   # lots — olive
    "MAT_debug_walkmesh":  (0.00, 0.80, 0.20),
    "MAT_debug_blocker":   (0.80, 0.10, 0.10),
}
CURB = "MAT_curb_dark_stone"

# NWN needs a real texture (bitmap) per trimesh; material name -> texture resref
# (<=16 chars, prefix eats 6). make_mdl.py writes a solid-colour .tga per entry.
TEXTURES = {
    "MAT_wet_cobble_dark":   "twu01_wcob",
    "MAT_cobble_patch":      "twu01_cpat",
    "MAT_plaza_slab":        "twu01_plaz",
    "MAT_sidewalk_stone":    "twu01_swlk",
    "MAT_curb_dark_stone":   "twu01_curb",
    "MAT_canal_black_water": "twu01_watr",
    "MAT_void_black":        "twu01_void",
    "MAT_ramp_stone":        "twu01_ramp",
    "MAT_drain_iron":        "twu01_iron",
    "MAT_foundation_pad":    "twu01_fnd",
    "MAT_debug_walkmesh":    "twu01_dwlk",
    "MAT_debug_blocker":     "twu01_dblk",
}


def texname(mat):
    return TEXTURES.get(mat, "twu01_wcob")


F, R, C, RP, MX = "FLAT", "RAISED", "CANAL", "RAMP", "MIXED"


def T(code, name, kind, edges, style, **kw):
    d = dict(code=code, name=name, kind=kind, edges=edges, style=style)
    d.update(kw)
    return d


# 40 tiles, KICKOFF.md order. edges = (N, E, S, W).
TILES = [
    # --- flat walkable ground ---
    T("CP1", "cobble_plain", "flat", (F, F, F, F), "solid", mat="MAT_wet_cobble_dark"),
    T("CP2", "cobble_plain_wet_variant", "flat", (F, F, F, F), "patches",
      mat="MAT_wet_cobble_dark", patch="MAT_cobble_patch"),
    T("CP3", "cobble_plain_broken_variant", "flat", (F, F, F, F), "patches",
      mat="MAT_wet_cobble_dark", patch="MAT_cobble_patch"),
    T("AL1", "narrow_alley_paving", "flat", (F, F, F, F), "alley"),
    T("PL1", "plaza_slab", "flat", (F, F, F, F), "solid", mat="MAT_plaza_slab"),
    T("DR1", "drain_detail", "flat", (F, F, F, F), "drain"),
    T("MU1", "muddy_repair_patch", "flat", (F, F, F, F), "mud"),
    T("LOT", "foundation_pad", "flat", (F, F, F, F), "solid", mat="MAT_foundation_pad"),
    # --- streets (fully walkable cobble with painted street grammar) ---
    T("ST_S", "straight_street", "flat", (F, F, F, F), "street", shape="straight"),
    T("ST_C", "street_corner", "flat", (F, F, F, F), "street", shape="corner"),
    T("ST_T", "street_t_junction", "flat", (F, F, F, F), "street", shape="t"),
    T("ST_X", "street_crossroads", "flat", (F, F, F, F), "street", shape="x"),
    T("ST_E", "street_end", "flat", (F, F, F, F), "street", shape="end"),
    T("ST_W", "wide_boulevard", "flat", (F, F, F, F), "street", shape="wide"),
    T("ST_G", "gate_mouth", "flat", (F, F, F, F), "street", shape="gate"),
    T("ST_D", "street_with_central_drain", "flat", (F, F, F, F), "street", shape="drain"),
    # --- sidewalk / curb / gutter ---
    T("SW_S", "sidewalk_straight", "flat", (F, F, F, F), "sidewalk"),
    T("SW_C", "sidewalk_corner", "flat", (F, F, F, F), "sidewalk"),
    T("SW_T", "sidewalk_t_split", "flat", (F, F, F, F), "sidewalk"),
    T("SW_X", "sidewalk_crossing", "flat", (F, F, F, F), "sidewalk"),
    T("SW_E", "sidewalk_end", "flat", (F, F, F, F), "sidewalk"),
    T("GD_S", "gutter_straight", "flat", (F, F, F, F), "gutter", shape="straight"),
    T("GD_C", "gutter_corner", "flat", (F, F, F, F), "gutter", shape="corner"),
    T("GD_X", "gutter_crossing_grate", "flat", (F, F, F, F), "gutter", shape="x", grate=True),
    # --- raised tiers / ramps / stairs ---
    T("UP1", "raised_terrace", "raised", (R, R, R, R), "raised"),
    T("UP2", "raised_plaza_variant", "raised", (R, R, R, R), "raised"),
    T("TE_S", "retaining_edge_straight", "retain", (R, MX, F, MX), "retain"),
    T("TE_C", "retaining_edge_outer_corner", "retain", (R, R, MX, MX), "retain"),
    T("TE_I", "retaining_edge_inner_corner", "retain", (R, MX, MX, R), "retain"),
    T("RA_S", "straight_ramp", "ramp", (R, RP, F, RP), "ramp"),
    T("RA_C", "corner_or_dogleg_ramp", "ramp", (R, RP, F, RP), "ramp"),  # simplified to straight ramp
    T("STA", "broad_stair", "stair", (R, MX, F, MX), "stair"),
    # --- canal / void / bridges ---
    T("CN1", "canal_black_water", "canal", (C, C, C, C), "canal"),
    T("CN2", "narrow_drainage_cut", "cut", (C, F, C, F), "cut"),
    T("CE_S", "canal_edge_straight", "canal_edge", (F, C, F, F), "canal_edge", which="E"),
    T("CE_C", "canal_edge_corner", "canal_edge", (F, C, C, F), "canal_edge", which="ES_outer"),
    # Inside (concave) corner: geometry (ES_inner) is canal in the SE quadrant only,
    # i.e. ONE canal corner at BR — the edge tuple would wrongly read as 3, so pin it.
    T("CE_I", "canal_edge_inside_corner", "canal_edge", (F, C, C, F), "canal_edge",
      which="ES_inner", corners=[("cobble", 0), ("cobble", 0), ("cobble", 0), ("canal", 0)]),
    # Diagonal "pinch": canal on TL+BR, land on TR+BL. Edge-derived corners can't
    # express a diagonal (see nwn_corners), so this one carries explicit corners.
    # Completes the 16-pattern cobble/canal paint matrix (canal_paint_matrix()).
    T("CE_D", "canal_edge_diagonal_pinch", "canal_edge", (C, C, C, C), "canal_edge",
      which="diag", corners=[("canal", 0), ("cobble", 0), ("cobble", 0), ("canal", 0)]),
    T("BR_1", "narrow_bridge", "bridge", (F, C, F, C), "bridge", hw=1.5),
    T("BR_2", "broad_bridge", "bridge", (F, C, F, C), "bridge", hw=3.0),
    T("SEW", "sewer_mouth_culvert", "canal_edge", (F, F, C, F), "sewer", which="S"),
]

# 12x12 test district (KICKOFF.md). Placed by row (south-ward) / col (east-ward).
MAP = [
    ["CN1", "CN1", "CE_S", "CE_S", "BR_2", "CE_S", "CE_S", "CN1", "CN1", "CN1", "CN1", "CN1"],
    ["CN1", "CE_C", "SW_S", "ST_S", "ST_S", "ST_S", "SW_S", "CE_C", "CN1", "CN1", "CN1", "CN1"],
    ["CE_S", "SW_S", "ST_C", "ST_S", "ST_T", "ST_S", "ST_C", "SW_S", "CE_S", "CE_S", "BR_1", "CE_S"],
    ["SW_S", "AL1", "AL1", "CP1", "PL1", "PL1", "CP1", "AL1", "AL1", "SW_S", "ST_S", "SW_S"],
    ["SW_S", "AL1", "LOT", "CP2", "PL1", "PL1", "CP2", "LOT", "AL1", "SW_S", "ST_S", "SW_S"],
    ["ST_S", "ST_S", "ST_T", "ST_S", "ST_X", "ST_S", "ST_T", "ST_S", "ST_S", "ST_C", "ST_S", "SW_S"],
    ["SW_S", "AL1", "LOT", "CP1", "ST_S", "ST_S", "CP1", "LOT", "AL1", "SW_S", "CP2", "SW_S"],
    ["SW_S", "AL1", "AL1", "CP1", "PL1", "PL1", "CP1", "AL1", "AL1", "SW_S", "CP2", "SW_S"],
    ["CE_S", "SW_S", "ST_C", "ST_S", "ST_T", "ST_S", "ST_C", "SW_S", "CE_S", "TE_S", "TE_C", "SW_S"],
    ["CN1", "CE_C", "SW_S", "CP1", "RA_S", "UP1", "UP1", "TE_S", "TE_S", "UP1", "RA_S", "SW_S"],
    ["CN1", "CN1", "CE_S", "CE_S", "BR_2", "CE_S", "CE_S", "CN1", "CE_S", "SW_S", "ST_S", "SW_S"],
    ["CN1", "CN1", "CN1", "CN1", "CN1", "CN1", "CN1", "CN1", "CN1", "SW_S", "ST_E", "SW_S"],
]


# --- geometry predicates -----------------------------------------------------

def _disk(x, y, cx, cy, r):
    return (x - cx) ** 2 + (y - cy) ** 2 <= r * r


def _road(shape, x, y, hw):
    ns = abs(x) <= hw
    ew = abs(y) <= hw
    if shape == "straight":
        return ns
    if shape in ("wide", "gate"):
        return abs(x) <= hw * 1.7
    if shape == "corner":
        return (ns and y >= -hw) or (ew and x <= hw)   # N arm + E arm
    if shape == "t":
        return ew or (ns and y <= hw)                  # E-W bar + stub to S
    if shape == "x":
        return ns or ew
    if shape == "end":
        return ns and y <= 0.0                          # enters from S, stops mid-tile
    if shape == "drain":
        return ns
    return False


def _land_fn(which):
    """Where a canal_edge tile is walkable land (True) vs canal (False)."""
    return {
        "E": lambda x, y: x <= 0.0,                       # canal on east
        "S": lambda x, y: y >= 0.0,                       # canal on south
        "ES_outer": lambda x, y: (x <= 0.0 and y >= 0.0), # land = NW quadrant only
        "ES_inner": lambda x, y: not (x > 0.0 and y < 0.0),  # land = all but SE quadrant
        "diag": lambda x, y: (x >= 0.0 and y >= 0.0) or (x <= 0.0 and y <= 0.0),  # land NE+SW
    }[which]


# --- visuals: paint(x, y) -> material name -----------------------------------

def make_paint(spec):
    st = spec["style"]
    if st == "solid":
        m = spec["mat"]
        return lambda x, y: m
    if st == "patches":
        base, patch = spec["mat"], spec["patch"]
        pts = [(-2, 2), (1.5, -1.5), (3, 2.5), (-2.5, -2)]
        return lambda x, y: patch if any(_disk(x, y, cx, cy, 1.3) for cx, cy in pts) else base
    if st == "alley":
        return lambda x, y: "MAT_wet_cobble_dark" if abs(x) <= 2.2 else "MAT_foundation_pad"
    if st == "drain":
        return lambda x, y: "MAT_drain_iron" if (abs(x) <= 1 and abs(y) <= 1) else "MAT_wet_cobble_dark"
    if st == "mud":
        return lambda x, y: "MAT_cobble_patch" if _disk(x, y, 0.5, -0.5, 2.4) else "MAT_wet_cobble_dark"
    if st == "street":
        shape, hw = spec["shape"], spec.get("hw", 1.6)

        def f(x, y):
            if _road(shape, x, y, hw):
                return "MAT_drain_iron" if (shape == "drain" and abs(x) <= 0.5) else "MAT_wet_cobble_dark"
            if _road(shape, x, y, hw + 1.0):
                return CURB
            return "MAT_sidewalk_stone"
        return f
    if st == "sidewalk":
        return lambda x, y: CURB if (abs(x) >= 4.2 or abs(y) >= 4.2) else "MAT_sidewalk_stone"
    if st == "gutter":
        shape, grate = spec["shape"], spec.get("grate", False)

        def f(x, y):
            if grate and abs(x) <= 0.9 and abs(y) <= 0.9:
                return "MAT_drain_iron"
            if _road(shape, x, y, 0.6):
                return "MAT_drain_iron"
            if _road(shape, x, y, 1.1):
                return CURB
            return "MAT_wet_cobble_dark"
        return f
    if st == "raised":
        return lambda x, y: "MAT_plaza_slab"
    if st == "retain":
        return lambda x, y: "MAT_plaza_slab" if y >= 0 else "MAT_wet_cobble_dark"
    if st in ("ramp", "stair"):
        return lambda x, y: "MAT_ramp_stone"
    if st == "canal":
        return lambda x, y: "MAT_wet_cobble_dark" if (abs(x) >= 4.4 or abs(y) >= 4.4) else "MAT_canal_black_water"
    if st == "cut":
        return lambda x, y: "MAT_canal_black_water" if abs(x) <= 0.9 else "MAT_wet_cobble_dark"
    if st == "canal_edge":
        land = _land_fn(spec["which"])
        return lambda x, y: "MAT_wet_cobble_dark" if land(x, y) else "MAT_canal_black_water"
    if st == "bridge":
        hw = spec.get("hw", 1.5)
        return lambda x, y: ("MAT_ramp_stone" if abs(x) <= hw
                             else (CURB if abs(x) <= hw + 0.3 else "MAT_canal_black_water"))
    if st == "sewer":
        return lambda x, y: (("MAT_drain_iron" if _disk(x, y, 0, -3.2, 1.6) else "MAT_wet_cobble_dark")
                             if y >= 0 else "MAT_canal_black_water")
    return lambda x, y: "MAT_wet_cobble_dark"


def height(spec, x, y):
    """Z of the visual top surface at (x, y). Drives the subdivided VIS plane."""
    k = spec["kind"]
    if k == "raised":
        return Z_RAISED
    if k in ("ramp", "stair"):
        return (y + HALF) / (2 * HALF) * Z_RAISED       # S=0 -> N=1.5
    if k == "retain":
        return Z_RAISED if y >= 0 else 0.0
    if k == "canal":
        return 0.0 if (abs(x) >= 4.4 or abs(y) >= 4.4) else Z_WATER
    if k == "cut":
        return Z_WATER if abs(x) <= 0.9 else 0.0
    if k == "canal_edge":
        return 0.0 if _land_fn(spec["which"])(x, y) else Z_WATER
    if k == "bridge":
        hw = spec.get("hw", 1.5)
        return 0.2 if abs(x) <= hw + 0.3 else Z_WATER
    if k == "sewer":
        return 0.0 if y >= 0 else Z_WATER
    if spec.get("style") == "sidewalk":
        return 0.15 if (abs(x) >= 4.2 or abs(y) >= 4.2) else 0.0   # low curb (KICKOFF 0.12-0.18)
    return 0.0


# --- walkmesh proxy: list of (verts4, walkable) ------------------------------

def _rect(x0, x1, y0, y1, z):
    return [(x0, y0, z), (x1, y0, z), (x1, y1, z), (x0, y1, z)]


def _ramp(x0, x1, y0, y1):
    z0 = (y0 + HALF) / (2 * HALF) * Z_RAISED
    z1 = (y1 + HALF) / (2 * HALF) * Z_RAISED
    return [(x0, y0, z0), (x1, y0, z0), (x1, y1, z1), (x0, y1, z1)]


def wok_quads(spec):
    k = spec["kind"]
    if k == "flat":
        return [(_rect(-5, 5, -5, 5, 0.0), True)]
    if k == "raised":
        return [(_rect(-5, 5, -5, 5, Z_RAISED), True)]
    if k in ("ramp", "stair"):
        return [(_ramp(-5, 5, -5, 5), True)]
    if k == "retain":
        return [(_rect(-5, 5, 0, 5, Z_RAISED), True), (_rect(-5, 5, -5, 0, 0.0), True)]
    if k == "canal":
        return [(_rect(-5, 5, -5, 5, 0.0), False)]
    if k == "cut":
        return [(_rect(-5, -0.9, -5, 5, 0.0), True),
                (_rect(0.9, 5, -5, 5, 0.0), True),
                (_rect(-0.9, 0.9, -5, 5, 0.0), False)]
    if k == "canal_edge":
        w = spec["which"]
        if w == "E":
            return [(_rect(-5, 0, -5, 5, 0.0), True), (_rect(0, 5, -5, 5, 0.0), False)]
        if w == "S":
            return [(_rect(-5, 5, 0, 5, 0.0), True), (_rect(-5, 5, -5, 0, 0.0), False)]
        if w == "ES_outer":
            return [(_rect(-5, 0, 0, 5, 0.0), True),
                    (_rect(0, 5, -5, 5, 0.0), False), (_rect(-5, 0, -5, 0, 0.0), False)]
        if w == "ES_inner":
            return [(_rect(-5, 0, -5, 5, 0.0), True), (_rect(0, 5, 0, 5, 0.0), True),
                    (_rect(0, 5, -5, 0, 0.0), False)]
        if w == "diag":                                    # NE+SW land, NW+SE canal
            return [(_rect(0, 5, 0, 5, 0.0), True), (_rect(-5, 0, -5, 0, 0.0), True),
                    (_rect(-5, 0, 0, 5, 0.0), False), (_rect(0, 5, -5, 0, 0.0), False)]
    if k == "bridge":
        hw = spec.get("hw", 1.5)
        return [(_rect(-hw, hw, -5, 5, 0.2), True),
                (_rect(-5, -hw, -5, 5, 0.0), False), (_rect(hw, 5, -5, 5, 0.0), False)]
    return [(_rect(-5, 5, -5, 5, 0.0), True)]


# --- visible mesh: subdivided 10x10 plane, faces tagged by material ----------
# Same source as build.py's build_vis so the Blender preview and the exported
# .mdl never drift. Faces are quads here; make_mdl.py triangulates on emit.

def vis_geometry(spec):
    """Return (verts, faces): 100-quad plane. faces = (a, b, c, d, matname)."""
    n, step = 10, 1.0
    paint = make_paint(spec)
    verts, vidx = [], {}

    def vi(i, j):
        if (i, j) not in vidx:
            x, y = -HALF + i * step, -HALF + j * step
            vidx[(i, j)] = len(verts)
            verts.append((x, y, height(spec, x, y)))
        return vidx[(i, j)]

    faces = []
    for i in range(n):
        for j in range(n):
            cx, cy = -HALF + (i + 0.5) * step, -HALF + (j + 0.5) * step
            faces.append((vi(i, j), vi(i + 1, j), vi(i + 1, j + 1), vi(i, j + 1),
                          paint(cx, cy)))
    return verts, faces


# --- NWN .set mapping (AGENTS.md §4, §7) -------------------------------------
# Minimal, deliberate vocabulary (AGENTS.md §2.1: every terrain pairing multiplies
# tile count). Ground tiles all share 'cobble' so they interconnect freely
# (KICKOFF.md: "entire tile can remain walkable for flexibility").
TERRAINS = ["cobble", "raised", "canal"]
CROSSERS = ["ramp", "canal_edge"]


def resref(spec):
    return f"{PREFIX}_{spec['code'].lower()}"   # <=16 chars, valid identifier


def group_name(spec):
    """Palette label for a tile — MUST be identical in the .itp leaf NAME and the
    .set [GROUP#] Name (name-sync gotcha, AGENTS.md §5). e.g. '[flat] cobble_plain'."""
    return f"[{spec['kind']}] {spec['name']}"


# --- paint pool vs palette features (AGENTS.md §2.3) --------------------------
# A tile in ANY [GROUP#] is excluded from the toolset's terrain-paint pool. So the
# tiles the brush auto-lays (fills + terrain transitions) must stay UNGROUPED; only
# click-place prefabs get grouped. Grouping *every* tile empties the fill pool ->
# the New Area wizard null-derefs ("Access violation ... Write of 00000040").
GROUND_CODES = {"CP1", "CP2", "CP3"}                   # cobble fill pool (Default/Floor)
CANAL_PAINT_CODES = {"CN1", "CE_S", "CE_C", "CE_I", "CE_D"}  # canal fill + edge transitions
PAINT_CODES = GROUND_CODES | CANAL_PAINT_CODES         # ungrouped, brush-laid terrain
TRANSITION_CODES = {"CE_S", "CE_C", "CE_I", "CE_D"}    # emit all 4 rotations (matrix)


def is_ground(spec):
    return spec["code"] in GROUND_CODES


def is_paint(spec):
    """True = ungrouped terrain tile the brush auto-picks (never a click-place feature)."""
    return spec["code"] in PAINT_CODES


def is_transition(spec):
    return spec["code"] in TRANSITION_CODES


def _rot_corners(c):   # one CCW 90° step: (TL,TR,BL,BR) -> (TR,BR,TL,BL)
    return (c[1], c[3], c[0], c[2])


def _rot_edges(e):     # one CCW 90° step: (N,E,S,W) -> (E,S,W,N)
    return (e[1], e[2], e[3], e[0])


def base_corners(spec):
    """(TL,TR,BL,BR) each (terrain, height) — explicit override or edge-derived."""
    if "corners" in spec:
        return tuple(tuple(c) for c in spec["corners"])
    c = nwn_corners(spec["edges"])
    return (c["TopLeft"], c["TopRight"], c["BottomLeft"], c["BottomRight"])


def placements(spec):
    """[(orient_deg, corners4, edges4)] backing this tile's [TILE#] entries. A
    transition tile emits each DISTINCT rotation (one model covers the whole
    rotation class via Orientation — the engine rotates model+walkmesh together),
    so the paint matrix is complete without extra models. Others place upright."""
    c, e = base_corners(spec), tuple(spec["edges"])
    if not is_transition(spec):
        return [(0, c, e)]
    seen, out = set(), []
    for k in range(4):
        if c not in seen:
            seen.add(c)
            out.append((k * 90, c, e))
        c, e = _rot_corners(c), _rot_edges(e)
    return out


def canal_paint_matrix():
    """(want, covered, missing) sets of 4-corner cobble/canal patterns. `missing`
    non-empty = a pattern the brush can request has no tile -> paint/New-Area crash.
    The generator hard-fails on this, so an incomplete matrix can never stage."""
    opts = [("cobble", 0), ("canal", 0)]
    want = {(tl, tr, bl, br) for tl in opts for tr in opts for bl in opts for br in opts}
    have = {c for s in TILES if is_paint(s) for _o, c, _e in placements(s)}
    return want, have & want, want - have


def _terr(e):
    return {"RAISED": "raised", "CANAL": "canal"}.get(e, "cobble")


def _corner(ea, eb):
    ts = {_terr(ea), _terr(eb)}
    terr = "raised" if "raised" in ts else ("canal" if "canal" in ts else "cobble")
    h = 1 if (ea == "RAISED" or eb == "RAISED") else 0
    return terr, h


def nwn_corners(edges):
    """Corner (terrain, height) for TopLeft/TopRight/BottomLeft/BottomRight.
    Top=N Bottom=S Left=W Right=E (AGENTS.md §4)."""
    n, e, s, w = edges
    return {
        "TopLeft": _corner(n, w), "TopRight": _corner(n, e),
        "BottomLeft": _corner(s, w), "BottomRight": _corner(s, e),
    }


def nwn_crosser(edge):
    return {"CANAL": "canal_edge", "RAMP": "ramp"}.get(edge, "")
