feedback5 O20: the 缚地 status (groundbind) — an enemy air unit counts as a ground unit and cannot move

ba.groundbind "目标变为地面单位,无法移动;使部分近地悬浮敌人掉落;对重量大于3的单位持续时间减半" (予愿安洁莉娜 S2 needs it):
- buffs.js: STATUS groundbind (flag groundbind + noMove, move x0), FLAG_KEYS, RESIST_STATUSES (as 束缚, [ASSUMED]);
- units.js isFlying: an enemy under 缚地 is a ground unit unless a 浮空 lifts it (same result as before without the flag);
- battle/status.js: 浮空 lands on a 缚地 data flyer (PRTS 异常效果 "…且不持有缚地异常…则Buff取消"), 缚地 halved on weight > 3;
- content/enemies: 掠海漂移体 / 吉兆飞鳞 drop on 缚地 as their PRTS text says (吉兆飞鳞 stays down while it holds);
- render/style.js: the 束缚 icon for it; docs/SIM.md (§2 isFlying, §3 flags / status table / 抵抗).
Nothing applies it yet: every golden family is unchanged. Test: test/sim/feedback5-groundbind.test.js.
This commit is contained in:
sganggs committed 2026-10-06 05:20:28 +08:00
1 parent 996f5d20fe
commit 83e879a335
8 files changed
+126 -25

No files matched your search

+86
View File
@@ -0,0 +1,86 @@
// test/sim/feedback5-groundbind.test.js — the 缚地 status (0.2.0, O20: 予愿安洁莉娜 S2, kits/ops/op-aglna2.js): gamedata_const
// ba.groundbind "目标变为地面单位,无法移动;使部分近地悬浮敌人掉落;对重量大于3的单位持续时间减半" — server/sim/buffs.js STATUS
// `groundbind` (flag `groundbind` + `noMove`), units.js Unit.isFlying (a 缚地 enemy is a ground unit unless a 浮空 lifts it),
// battle/status.js applyStatus (浮空 lands on a 缚地 data flyer — PRTS 异常效果 "若单位数据上为飞行单位且不持有缚地异常…则Buff
// 取消"; the weight halving), the 抵抗 list, and the two hovering enemies whose PRTS text names it (content/enemies:
// 掠海漂移体 爬行模式, 吉兆飞鳞 晕眩模式).
// Run: node --test test/sim/feedback5-groundbind.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
import { STATUS, RESIST_STATUSES, FLAG_KEYS } from '../../server/sim/buffs.js';
const approx = (a, b, msg, eps = 1e-6) => assert.ok(Math.abs(a - b) <= eps * Math.max(1, Math.abs(b)), `${msg}: ${a} vs ${b}`);
const ENEMIES = {
enemy_fly: enemyRec({ key: 'enemy_fly', hp: 1e9, speed: 0, mass: 0, motion: 'FLY' }),
enemy_flywalk: enemyRec({ key: 'enemy_flywalk', hp: 1e9, speed: 0.6, mass: 0, motion: 'FLY' }),
enemy_flyheavy: enemyRec({ key: 'enemy_flyheavy', hp: 1e9, speed: 0, mass: 4, motion: 'FLY' }),
};
function arena(units = []) {
const h = makeBattle({ defs: { enemies: ENEMIES }, timeLimit: 600, autoFinish: false, seed: 3, flags: { dpPerSec: 0 }, units });
h.step();
return h;
}
test('缚地 is a catalogue status: flag groundbind + noMove, move ×0, one of 抵抗\'s statuses', () => {
assert.deepEqual(STATUS.groundbind, { flags: { groundbind: true, noMove: true }, mods: { moveMul: 0 } });
assert.ok(FLAG_KEYS.includes('groundbind'));
assert.ok(RESIST_STATUSES.has('groundbind'));
});
test('缚地: an air unit is a ground unit (melee operators hit it) and cannot move; half as long on weight > 3; 抵抗 shortens it; 浮空 lands on a 缚地 flyer; back in the air when it ends', () => {
const h = arena([{ uid: 1, chessId: 'chess_char_1_02_a', row: 9, col: 4 }]);
const yak = h.unit(1);
const fl = h.spawn('enemy_flywalk', { routeIndex: 2 });
h.run(0.5);
assert.equal(fl.isFlying, true);
assert.equal(h.b.applyStatus(fl, 'levitate', { duration: 3 }), false, 'a data flyer takes no 浮空');
assert.ok(h.b.applyStatus(fl, 'groundbind', { duration: 5 }));
assert.deepEqual([fl.isFlying, !!fl.s.flags.noMove, fl.s.moveSpeed], [false, true, 0], 'ground unit, rooted');
const x0 = fl.x;
h.run(1);
approx(fl.x, x0, 'it does not move');
assert.ok(h.b.applyStatus(fl, 'levitate', { duration: 2 }), '浮空 lands on a 缚地 flyer');
assert.equal(fl.isFlying, true, 'levitated: an air unit again');
// a melee operator (ground-only, range: his own tile) hits a 缚地 flyer on his tile — and blocks it, a ground unit now
const near = h.spawn('enemy_fly', { pos: [9, 4] });
h.run(1);
assert.equal(yak.stats.dmg, 0, '角峰 cannot hit the flyer');
h.b.applyStatus(near, 'groundbind', { duration: 10 });
assert.ok(h.runUntil(() => yak.stats.dmg > 0, 5), '缚地: a ground unit he can hit');
assert.equal(near.blockedBy, yak, 'and block');
// halving and 抵抗
const heavy = h.spawn('enemy_flyheavy', { pos: [10, 9] });
h.b.applyStatus(heavy, 'groundbind', { duration: 10 });
approx(heavy.findBuff('groundbind').timeLeft, 5, 'weight 4: half');
const res = h.spawn('enemy_fly', { pos: [11, 9] });
h.b.applyStatus(res, 'resist', { duration: 99, value: 0.5 });
h.b.applyStatus(res, 'groundbind', { duration: 10 });
approx(res.findBuff('groundbind').timeLeft, 5, '抵抗 halves it');
h.run(6);
assert.equal(res.isFlying, true, 'back in the air when it ends');
checkInvariants(h.b);
assert.equal(h.b.errors.length, 0);
});
test('the hovering enemies (PRTS "受…缚地影响后"): 掠海漂移体 drops to its 爬行模式 for good; 吉兆飞鳞 is down while the 缚地 holds', () => {
const h = arena();
const sy = h.spawn('enemy_2025_syufo', { pos: [12, 8], routeIndex: 0, mods: { speedMul: 0 } });
const pa = h.spawn('enemy_10045_parrot', { pos: [12, 6], routeIndex: 0, mods: { speedMul: 0 } });
h.step();
assert.ok(sy.isFlying && pa.isFlying, 'both hover');
h.b.applyStatus(sy, 'groundbind', { duration: 4 });
h.b.applyStatus(pa, 'groundbind', { duration: 20 });
h.step();
assert.equal(sy.form, 'crawl', '掠海漂移体: 爬行模式');
assert.equal(pa.isFlying, false, '吉兆飞鳞: down');
h.run(10);
assert.equal(sy.isFlying, false, '掠海漂移体 stays down for good');
assert.equal(pa.isFlying, false, '吉兆飞鳞 down while the 缚地 holds (its 8 s stun over)');
h.run(12);
assert.equal(pa.isFlying, true, '吉兆飞鳞 floats again once nothing holds it');
checkInvariants(h.b);
assert.equal(h.b.errors.length, 0);
});