Files
ark-stronghold-protocol/docs/design/geometry-time.md
T
sganggs 75202776cc feedback6 R21B: 联防 plays on the round's battlefield again — its terrain, crates, water, devices and runes, as in 0.1.4 (the owner's decision of 2026-10-07, 「官服保留地形,改回去」; GitHub #244). 0.2.0's escaped-level map (8a436249, §25.6.4) is withdrawn: that map is the placeholder grid all 39 wave template levels carry, tile for tile; only the 11 stage levels have terrain. match/unitePhase.js _uniteOpts fields this.stageId in both combat modes, unite.js uniteStageId is removed, the client draws the 联防 on the board's stage again (screens/game.js: 0.1.4's ownStage / baseStage, no map switch, nothing to restore on leaving), the dev mock likewise. Kept from 0.2.0: own-board 突袭 / 乌尔比安 S3 landings (onOwnBoard, raidTile untouched), the bounty payee, the 联防 BGM, the SETTLE result box. data/stages.json keeps the two kind 'unite' records (no field uses them; engine tests use them as a plain two-halves road); tools/build-data.mjs comments only, an offline rebuild reproduces data/ byte for byte. The 联防 battle input is byte-identical to v0.1.4's in 20 cases (5 stages × 1 / 2 helpers × both combat modes); GitHub #41 item 3 (野鬃 pushing walkers back across the gate onto the other half) is back with v0.1.4's numbers, none off the map. Tests: test/match/feedback5-unite-map.test.js rewritten (6, all fail on the parent), test/e2e/coop.e2e.mjs checks the 联防 field's drawn stage; docs: DATA §12, META §4, PLAYING §5, design/geometry-time, I18N, the §25.6.4 pointer; golden: fields unite-1 (escaped_single → act1autochess_m02) and unite-2 (escaped_multi → act1autochess_m07), the stages of 0.1.4, and the co-op matches whose 联防 rounds come out differently on the round's map — coop2-NORMAL-3, coop2-HARD-4, coop3-ABYSS-5, coop4-ABYSS-7, coop2-NORMAL-8-serverrun, coop2-NORMAL-14-standins (now a victory)
2026-10-07 17:40:03 +08:00

7.2 KiB
Raw Blame History

DESIGN §3, §4 — Coordinates, fields, geometry and time

Part of DESIGN.md (the index; section numbers are global).

3. Coordinates, fields and geometry

  • Every stage is a 19-row × 21-col grid (data/stages.json). Row 0 is the bottom row, col 0 is the left. Tile (r,c) has world centre (x=c, y=r); 1 tile = 1 world unit. Unit positions are floats in this space.
  • Areas (research 05 §2.2):
    • Normal field (own board): rows 9–12, cols 2–10. Enemy gates S at (9,10) and (12,10) (R1–3 only the lower one); protection objective E at (9,2). Col 9 lane is not deployable (tile type decides).
    • Hand (整备区): row 7, cols 0–9 (10 regular slots, index = col). Temp slots: row 8, cols 4–8 (5 slots). Hand/temp tiles are never part of a battle.
    • Partner display cols 11–18 of rows 9–12 (the unite field uses them).
    • Boss field: rows 1–5; left half cols 2–10, right half cols 10–18.
    • Enemy preview pen rows 14–18 × cols 7–13 (row 16 empty): during prep the next round's enemies idle there (upper-gate enemies rows 17–18, lower-gate rows 14–15, ≤ 50 models, spawn-time order) — research 08 §4 / 09 §2.
  • Tile semantics come from data/stages.json legend: height (LOW/HIGH), buildable (ALL/MELEE/RANGED/NONE — the effective deploy type: the 深水区 tile_deepsea refuses deployment, NONE with the level's buildableType ALL kept, PRTS 深水区 地形信息 "拒绝部署(待补充)"; an active 特制水上平台 makes its tile deployable in the prep deploy map only, §21.3), ground-passable, fly-only, special terrain (mire, smog, deepsea, infection). The fenced tiles (围墙 tile_fence_bound / 围栏 tile_fence, legend b: LOW, buildable ALL, passable to flyers only) take units, which attack normally from there but block no ground enemy (PRTS 地形机制, §21.23). Melee chess may stand only on LOW tiles with buildable ALL/MELEE — except a melee chess whose trait reads 「可以放置于远程位」 (the 钩索师 / 推击手 branch trait: 歌蕾蒂娅, 崖心, 见行者, normal and elite, with any module or none), which may use the ranged tiles too (shared/highGround.js; the owner's decision of 2026-10-05, following PRTS — it reverses the 2026-10-04 one that allowed only elite 歌蕾蒂娅 with HOK-Y; on a 高台 it attacks and blocks nothing); ranged chess on ALL/RANGED and on LOW ALL/MELEE tiles ("所有行动内远程干员可部署在近战位"). Tokens follow their own position; a summon marked ownerRange (狼群, 流形: "只能部署在召唤者攻击范围内"; Mon3tr's 重构体 by her talent) goes only on a tile of its owner's attack range — the loadout grid rotated by the owner's facing around its tile (PlayerState.summonRange, §21.3) —, one marked ownerRangeOutside / rangedTilesOnly (凯尔希·思衡托's 战术锚点: "仅可以部署在凯尔希·思衡托攻击范围外的远程位") only on a 高台 outside it (summonExcluded, placement class high; 0.2.0).
  • Facing (corrected, see research 09 §1.2): like standard Arknights, every board piece has a direction dir ∈ UP|RIGHT|DOWN|LEFT chosen with the 4-direction deploy wheel after dropping it on a tile (also re-orientable in place); default RIGHT; persists across rounds. Range grid entries are [dRow, dCol] relative to facing RIGHT and are rotated: RIGHT (dr,dc), UP (dc,−dr), LEFT (−dr,−dc), DOWN (−dc,dr) (row 0 = bottom); rangeExtend applies along +dCol before rotating — to a running skill's range too, unless that skill ignores 攻击距离 (targeting.noRangeExtend, 信仰搅拌机 S3, §21.16). The right-side Final Assault player is mirrored col c→20−c with RIGHT↔LEFT (UP/DOWN unchanged).
  • Range test: an enemy is inside a grid range if the tile containing it (round(y), round(x)) is one of the range tiles; a huge enemy (data hitArea: 巨型单位, a 4.95 × 2.95 rectangle moved 1.0 up) if any tile its hit rectangle overlaps is one (PRTS 作战机制 "巨型BOSS单位的每一个占据的格子都可以让其本身通过格子判定"; sim/body.js, §19.4). Radius ranges (enemies, auras, AoE) use Euclidean distance in tiles — to a huge enemy's rectangle [ASSUMED] — except splash around a struck target, a 中点判定 on positions (PRTS 作战机制 "中点判定…案例:阻挡,酒神1天赋的1.3溅射半径"). A ranged enemy's normal attack takes an ally whose collider (radius 0.25, PRTS 作战机制 §碰撞体积 "我方干员碰撞体积基本均为以0.25格为半径的圆形") touches its range circle: centre distance ≤ rangeRadius + 0.25 (constants.js ALLY_COLLIDER_RADIUS, ai.js enemyAttack; §20.4).
  • Fields at runtime: a Battle receives a field rect (rows/cols subset) and the full stage grid. Normal battles use rows 9–12 × cols 0–10; unite uses rows 9–12 × cols 0–20 of the round's stage, both halves with their terrain and devices (0.2.0's escaped-level map, GitHub #41, withdrawn in 0.2.1: the owner's decision of 2026-10-07); boss uses rows 0–5 × cols 0–20 (solo boss uses the left half only). Pathfinding never leaves the rect.
  • Speed: tilesPerSecond = moveSpeed × MOVE_SCALE with MOVE_SCALE = 0.5 (in sim/constants.js, tunable).

4. Time model

  • Simulation time is game seconds. Fixed step TICK = 1/30 s.
  • Combat runs at 2× real time (forced, like the original): 60 ticks per real second — in the browser's runner (§14) or, for server-run fields, the server's accumulator (2 ticks every real 1/30 s, never more than 8 ticks per interval to avoid spirals).
  • Combat time limits come from data/config.json → modes[m].rounds[r].combatTimeLimit (= the level's maxPlayTime; also modes[m].combatTimeLimit[r]) and are real seconds of the forced-2× battle (verified by the balance pass: spawn schedules exceed the limit when read as game seconds). The server passes 2 × value game seconds to the Battle (server/match/gamedata.js → combatTimeLimit; config.combatTimeScale, default 2); the countdown players see equals the data value. When time runs out, living non-boss enemies count as leaked. 联防 uses the round's limit.
  • Final Assault / Hidden Core have no hard stop and their clocks are real seconds too: the boss level's levelMaxPlayTime (120 real s) is only the HUD countdown (m.public.deadline; the battle goes on past it), and the overtime drain (bossOvertimeAfter 150 / bossOvertimeDrainPerSec 1 = official bossTurnHpReduceTime) takes 1 team LP per whole real second from 150 real s (= 300 game s on the 2× field clock; first point at 151 s — gamedata.js bossOvertimeDue). m.public.overtimeAt = the ms epoch when the drain starts; both are placed on the field clock.
  • Solo pause (§14): while m.public.paused the running battle's field clock, the HUD deadline / overtimeAt and every server deadline of the battle stand still; on resume they move on by the paused time. Co-op battles never pause.
  • Prep/draft timers are real seconds, stored as absolute server deadlines (Date.now()-based) and sent to clients as deadline (ms epoch) + serverNow for clock-offset correction.
  • Snapshots to clients: every 3 ticks (= 20 Hz real) per watched field.