feat(airport): bind landings to the six visual flats
Entry side is the 60-degree wedge of the port, not the cell-edge direction. Levels regenerated; hint line joins the waiting plane to the on-board path.
This commit is contained in:
+20
-6
@@ -2,8 +2,8 @@
|
||||
import { DIRS, key, opposite, rotateBoard, trace, validateLevel as validateBase, canRotate } from '../../src/core.js';
|
||||
import { PORTS, position, pathPoints, sweepPath, hexPoints } from '../../src/path-math.js';
|
||||
|
||||
// Flat-top edges in screen space. Compass names hid that waiting bays sit on the six sides.
|
||||
export const SIDES = ['上方', '右上方', '右下方', '下方', '左下方', '左上方'];
|
||||
// Six visual flats of the pointy-top outline (bay normals at -60/0/60/120/180/-120).
|
||||
export const SIDES = ['右上方', '右方', '右下方', '左下方', '左方', '左上方'];
|
||||
export function boardRadius(cells) {
|
||||
return Math.max(0, ...cells.map(c => (Math.abs(c.q) + Math.abs(c.r) + Math.abs(c.q + c.r)) / 2));
|
||||
}
|
||||
@@ -25,6 +25,19 @@ export function waitingPose(plane, planes, radius) {
|
||||
const d = apronDistance(radius);
|
||||
return { x: bx * (d + offset), y: by * (d + offset), angle: Math.atan2(-by, -bx) + Math.PI / 2 };
|
||||
}
|
||||
// A boundary port belongs to the visual flat whose outward normal is nearest the
|
||||
// port's position, in 60° wedges centred on the bay normals. Ties fall on the
|
||||
// clockwise edge so a corner never belongs to two sides.
|
||||
export function visualSideFromPoint(x, y) {
|
||||
// 60° wedges starting at the top (-90°): that boundary belongs to side 0.
|
||||
let t = Math.atan2(y, x) + Math.PI / 2;
|
||||
t = ((t % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2);
|
||||
return Math.min(5, Math.floor(t / (Math.PI / 3) + 1e-12));
|
||||
}
|
||||
export function visualSide(q, r, portDir) {
|
||||
const p = position(q, r), n = PORTS[portDir];
|
||||
return visualSideFromPoint(p.x + n.x, p.y + n.y);
|
||||
}
|
||||
|
||||
// White tray outline in board-local units, shared by drawing, the edge-runway
|
||||
// reach and the runway-read tests. A convex hull, so ray crossings are unambiguous.
|
||||
@@ -79,8 +92,8 @@ export function boardAt(level, orientation) { return rotateBoard({ cells: level.
|
||||
export function departureGeometry(board, plane) {
|
||||
const route = trace(board, plane, [], true);
|
||||
if (route.status !== 'exit') return { ...route, points: pathPoints(route) };
|
||||
const side = route.steps.at(-1).exit;
|
||||
return { ...route, side, points: pathPoints(route) };
|
||||
const last = route.steps.at(-1), exitDir = last.exit;
|
||||
return { ...route, exitDir, side: visualSide(last.q, last.r, exitDir), points: pathPoints(route) };
|
||||
}
|
||||
export function catalogs(level) {
|
||||
if (catalogCache.has(level)) return catalogCache.get(level);
|
||||
@@ -91,7 +104,7 @@ export function catalogs(level) {
|
||||
if (departure.status !== 'exit') continue;
|
||||
arrivals.push({
|
||||
status: 'land', standId: stand.id, color: stand.color, q: stand.q, r: stand.r,
|
||||
dir: opposite(dir), side: departure.side,
|
||||
dir: opposite(dir), side: departure.side, exitDir: departure.exitDir,
|
||||
points: [...departure.points].reverse(), steps: [...departure.steps].reverse(),
|
||||
signature: `${stand.id}:${dir}:${departure.side}`,
|
||||
});
|
||||
@@ -138,8 +151,9 @@ export function checkDeparture(level, state, planeId) {
|
||||
const points = route.points;
|
||||
// Preserve the original long exit-ray collision check, without drawing a
|
||||
// 24-unit flight across the screen.
|
||||
const out = route.exitDir ?? route.side;
|
||||
const swept = route.status === 'exit'
|
||||
? [...points, { x: points.at(-1).x + PORTS[route.side].x * 24, y: points.at(-1).y + PORTS[route.side].y * 24 }]
|
||||
? [...points, { x: points.at(-1).x + PORTS[out].x * 24, y: points.at(-1).y + PORTS[out].y * 24 }]
|
||||
: points;
|
||||
const hit = sweepPath(swept, obstacles(state, planeId));
|
||||
if (hit) return { ...route, status: 'blocked', plane: { ...plane }, blocker: hit.blocker, collisionPoint: hit.point, travelPoints: hit.points };
|
||||
|
||||
Reference in New Issue
Block a user