Hit tiles: positions round half to even (PRTS 作战机制); enemies held just inside the field edge

This commit is contained in:
Cloudnyco committed 2026-10-11 21:33:44 +08:00
1 parent bc50cac01f
commit 119a04888a
13 files changed
+113 -24

No files matched your search

+2 -1
View File
@@ -6,6 +6,7 @@
import { ROWS, COLS } from '../constants.js';
import { absoluteRangeKeys, canTargetEnemy, extendedGrid, enemyStealthed } from '../targeting.js';
import { bodyKeys, bodyInKeys, bodyInRadius } from '../body.js';
import { roundHalfEven } from '../detmath.js';
export class BattleQueries {
/**
@@ -21,7 +22,7 @@ export class BattleQueries {
for (const k of bodyKeys(e)) { const b = this._eb[k]; if (!b.length) this._ebUsed.push(k); b.push(e); }
continue;
}
const r = Math.round(e.y), c = Math.round(e.x);
const r = roundHalfEven(e.y), c = roundHalfEven(e.x); // the tile body.js gives a point enemy
if (r < 0 || r >= ROWS || c < 0 || c >= COLS) continue;
const k = r * COLS + c;
const b = this._eb[k];
+6 -6
View File
@@ -4,7 +4,7 @@
// and the list compaction.
// Installed on Battle.prototype by server/sim/Battle.js (a method container: never instantiated; `this` is the battle).
import { MAX_ALIVE_ENEMIES } from '../constants.js';
import { MAX_ALIVE_ENEMIES, FIELD_EDGE_INSET } from '../constants.js';
import { Unit } from '../units.js';
import { compileRoute } from '../ai.js';
import { unitInfo } from '../snapshot.js';
@@ -201,11 +201,11 @@ export class BattleSpawns {
}
_clampPos(e) {
const R = this.rect;
if (e.x < R.c0 - 0.5) e.x = R.c0 - 0.5;
if (e.x > R.c1 + 0.5) e.x = R.c1 + 0.5;
if (e.y < R.r0 - 0.5) e.y = R.r0 - 0.5;
if (e.y > R.r1 + 0.5) e.y = R.r1 + 0.5;
const R = this.rect, m = 0.5 - FIELD_EDGE_INSET; // inside the edge: on the edge tile, never halfway (body.js)
if (e.x < R.c0 - m) e.x = R.c0 - m;
if (e.x > R.c1 + m) e.x = R.c1 + m;
if (e.y < R.r0 - m) e.y = R.r0 - m;
if (e.y > R.r1 + m) e.y = R.r1 + m;
}
_compactEnemies() {
+7 -6
View File
@@ -1,8 +1,9 @@
// server/sim/body.js — where a unit can be hit: the ONE helper behind every range test that targets enemies (user
// playtest #5 item 10: "大体型的关底boss实际受击判定范围很小…原版是很大的一片区域").
//
// Regular units are a point: in a grid range when the tile containing their position (round(y), round(x)) is a range
// tile, in a radius when their position is (DESIGN §3). Huge units (巨型单位 — enemy data `hitArea`, tools/build-data.mjs
// Regular units are a point: in a grid range when the tile containing their position is a range tile — the position
// rounded half to even (detmath roundHalfEven; PRTS 作战机制 "受击方坐标经round函数取整(四舍六入五成双)得到其所属格
// 坐标") —, in a radius when their position is (DESIGN §3). Huge units (巨型单位 — enemy data `hitArea`, tools/build-data.mjs
// HIT_AREAS, from PRTS "巨型单位:受击判定区域为长4.95、宽2.95的长方形,向上偏移1.0") are hit anywhere on a rectangle
// `w` tiles along the columns × `h` along the rows, centred on their position moved `dx` columns right and `dy` rows
// up:
@@ -24,7 +25,7 @@
// `hitRect`).
import { ROWS, COLS } from './constants.js';
import { hypot } from './detmath.js';
import { hypot, roundHalfEven } from './detmath.js';
/**
* Validated hit area of a data record (`{ w, h, dx, dy }`, tiles) or null for a regular (point) unit.
@@ -47,7 +48,7 @@ export function hitRect(u) {
/** Tile key of a unit's position (-1 off the grid). */
function posKey(u) {
const r = Math.round(u.y), c = Math.round(u.x);
const r = roundHalfEven(u.y), c = roundHalfEven(u.x);
return r < 0 || r >= ROWS || c < 0 || c >= COLS ? -1 : r * COLS + c;
}
@@ -68,7 +69,7 @@ export function bodyKeys(u) {
/** Does the unit occupy tile (r, c)? */
export function bodyOnTile(u, r, c) {
const R = hitRect(u);
if (!R) return Math.round(u.y) === r && Math.round(u.x) === c;
if (!R) return roundHalfEven(u.y) === r && roundHalfEven(u.x) === c;
return c + 0.5 > R.x0 + 1e-9 && c - 0.5 < R.x1 - 1e-9 && r + 0.5 > R.y0 + 1e-9 && r - 0.5 < R.y1 - 1e-9;
}
@@ -100,7 +101,7 @@ export function bodyInRadius(u, x, y, r) {
* around it; …): "周围八格"-style square areas centred on a tile.
*/
export function bodyTileReach(u, r, c) {
if (!u.hitArea) return Math.max(Math.abs(Math.round(u.y) - r), Math.abs(Math.round(u.x) - c));
if (!u.hitArea) return Math.max(Math.abs(roundHalfEven(u.y) - r), Math.abs(roundHalfEven(u.x) - c));
let best = Infinity;
for (const k of bodyKeys(u)) {
const kr = Math.floor(k / COLS), kc = k % COLS;
+9
View File
@@ -220,6 +220,15 @@ export const MAX_ALIVE_ENEMIES = 600;
/** Heal-over-time / regen events are aggregated and only emitted when they reach this amount. */
export const REGEN_EVENT_MIN = 1;
/**
* How far inside the field rectangle an enemy is held (tiles; Battle._clampPos). The rectangle's edge lies exactly
* halfway between two tiles, where the tile of a position — rounded half to even (body.js; PRTS 作战机制 四舍六入五成双)
* — would depend on whether the row / column number is even. Held this little inside, an enemy at the edge is always on
* the edge tile. 2^-20 (about a millionth of a tile) is exact in binary and far above the rounding error of a position,
* yet too small to change a slide of 0.1-tile steps against the edge.
*/
export const FIELD_EDGE_INSET = 1 / 1048576;
/** Row offset mapping board rows (9..12) onto boss-field rows (2..5). */
export const BOSS_ROW_OFFSET = -7;
+11
View File
@@ -46,6 +46,17 @@ export function hypot(x, y) {
return Math.sqrt(x * x + y * y);
}
/**
* x rounded to an integer, ties to even (2.5 → 2, 3.5 → 4, −2.5 → −2): the rule PRTS 作战机制 gives for the tile of a
* target — 受击方坐标经round函数取整(四舍六入五成双)得到其所属格坐标. Math.round rounds ties up (2.5 → 3). Exact; never −0.
* @param {number} x
*/
export function roundHalfEven(x) {
const f = Math.floor(x);
if (x - f !== 0.5) return Math.round(x) + 0;
return (f % 2 === 0 ? f : f + 1) + 0;
}
// ---- integer powers ----------------------------------------------------------------------------------------------
const SPLIT = 134217729; // 2^27 + 1 (Dekker)
+3 -2
View File
@@ -17,6 +17,7 @@
import { COLS, ROWS } from './constants.js';
import { normDir, rotateOffset } from './dir.js';
import { bodyDist } from './body.js';
import { roundHalfEven } from './detmath.js';
/**
* Build the absolute tile-key list of a grid for a unit standing at (r,c) facing `dir` ('UP'|'RIGHT'|'DOWN'|'LEFT';
@@ -50,9 +51,9 @@ export function absoluteRangeKeys(grid, r, c, dir, extend = 0) {
// record range (shared/loadoutRecord.js attackRangeGrid), re-exported here for the battle (Battle._refreshRange).
export { extendedGrid } from '../../shared/loadoutRecord.js';
/** Tile key of a unit's current position. */
/** Tile key of a unit's current position (rounded half to even, as body.js). */
export function tileKeyOf(u) {
const r = Math.round(u.y), c = Math.round(u.x);
const r = roundHalfEven(u.y), c = roundHalfEven(u.x);
if (r < 0 || r >= ROWS || c < 0 || c >= COLS) return -1;
return r * COLS + c;
}