From d82511286c9cf469a5617ae34e4f112adf8c3e39 Mon Sep 17 00:00:00 2001 From: sganggs Date: Mon, 5 Oct 2026 18:26:49 +0800 Subject: [PATCH] =?UTF-8?q?feedback5=20K3:=20the=20Mechanist=20stand-in=20?= =?UTF-8?q?kit=20(standin-acfend.js)=20=E2=80=94=20=E7=B2=BE=E7=A0=94?= =?UTF-8?q?=E6=9D=90=E6=96=99=20/=20=E5=8F=8D=E9=A6=88=E8=A3=85=E7=94=B2?= =?UTF-8?q?=20with=20PRTS's=200.1=20s=20checks=20(the=20ASPD=20cut=20lasts?= =?UTF-8?q?=20while=20anyone=20blocks=20the=20enemy),=20S1=20=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E7=A8=B3=E5=AE=9A,=20S2=20=E4=B8=8D=E5=8F=98=E6=80=A7?= =?UTF-8?q?=E5=8E=9F=E7=90=86=20(=C3=972=20/=20=C3=973=20=E5=8F=8D?= =?UTF-8?q?=E9=A6=88=E8=A3=85=E7=94=B2),=20S3=20=E5=BA=94=E5=8A=9B?= =?UTF-8?q?=E5=80=92=E7=BD=AE=20(per-second=20arts=20to=20the=20enemies=20?= =?UTF-8?q?she=20blocks),=20module=20PRO-X;=20the=20data's=20=E9=87=8D?= =?UTF-8?q?=E8=A3=85=20trigger=20kept?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/sim/content/kits/index.js | 2 +- server/sim/content/kits/ops/standin-acfend.js | 116 ++++++++++ test/content/standin_acfend.test.js | 205 ++++++++++++++++++ 3 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 server/sim/content/kits/ops/standin-acfend.js create mode 100644 test/content/standin_acfend.test.js diff --git a/server/sim/content/kits/index.js b/server/sim/content/kits/index.js index 304c719d..73cb0682 100644 --- a/server/sim/content/kits/index.js +++ b/server/sim/content/kits/index.js @@ -64,7 +64,7 @@ export const KIT_FILES = Object.freeze([ * A stand-in without a file fights with the generic kit (content/generic.js) plus its unconditional stat talents * (genericTalents) — the eight 预备干员 need no file. */ -export const STANDIN_KIT_FILES = Object.freeze(['standin-acmedc.js']); +export const STANDIN_KIT_FILES = Object.freeze(['standin-acmedc.js', 'standin-acfend.js']); async function loadKitFile(file) { try { diff --git a/server/sim/content/kits/ops/standin-acfend.js b/server/sim/content/kits/ops/standin-acfend.js new file mode 100644 index 00000000..6f57f56b --- /dev/null +++ b/server/sim/content/kits/ops/standin-acfend.js @@ -0,0 +1,116 @@ +// server/sim/content/kits/ops/standin-acfend.js — Mechanist (char_610_acfend) 补位 stand-in kit: 4 星熊 (S2), 5 号角 / +// 6 余 (S3 + module PRO-X on the elite). Stand-in contract and rules: ../README.md "Stand-in kits". + +import { num, talentBb, traitBb, skillRec, statBuff, toggleBuff, up } from '../shared/tier1.js'; + +const S1 = 'skchr_acfend_1', S2 = 'skchr_acfend_2', S3 = 'skchr_acfend_3'; +/** 反馈装甲 on an enemy she blocks, and its S2 version (×talent_mult): one buff per target whatever applies it (同名). */ +const FEEDBACK_KEY = 'acfend:feedback'; +const FEEDBACK_S2_KEY = 'acfend:feedback2'; +/** PRTS Mechanist(卫戍协议) 天赋备注: 精研材料's HP ratio and 反馈装甲's block are checked every 0.1 s. */ +const CHECK_INTERVAL = 0.1; +/** S3 应力倒置 "每秒…": one pulse per second of the skill, the first 1 s after the cast [ASSUMED: no pulse at the cast]. */ +const STRESS_INTERVAL = 1; + +export default { + // Mechanist (铁卫, blocks 3) — numbers from data/backups.json (normal E2 Lv1 rank 4, elite E2 Lv60 rank 7; skill_table and + // PRTS Mechanist(卫戍协议) agree): + // 精研材料: DEF +def; below hp_ratio HP another +`acfend_t_1[extra].def` (module PRO-X Lv3: 0.2) — PRTS 备注 "生命比例每0.1秒 + // 检测一次". + // 反馈装甲: enemies she blocks ASPD `attack_speed` (−7). PRTS 备注: applied every 0.1 s to the enemies she blocks; then the + // TARGET checks every 0.1 s whether anyone still blocks it — it lasts until nobody blocks it (whoever blocked it), her + // S2 starts, or her S2 ends (a buff with its own 0.1 s check here, so it outlives her knock-out the same way). + // S1 结构稳定 (40 s): max HP +max_hp, DEF +def. + // S2 不变性原理 (20 s): DEF +def, 反馈装甲 ×talent_mult (−14 / −21). PRTS 备注: at the cast the talent's effect on the + // enemies she blocks ends and the enhanced one is applied; at the end every enhanced one she applied ends (the + // 0.1 s talent check then gives the enemies she still blocks the plain one again). + // S3 应力倒置 (30 s): DEF +def, block +block_cnt, every second ATK × atk_scale arts damage to each enemy she blocks. + // Module PRO-X (elite, trait addition): "阻挡敌人时防御力+20%" — DEF +def (trait blackboard) while she blocks. + // Triggers (data, nothing set here): every skill DEFAULT with the official TAKE_DAMAGE as rawRule — the owner's 重装 + // exception for the stand-ins (tools/build-data.mjs STANDIN_TRIGGER_DEVIATIONS): she casts with an enemy in range. + char_610_acfend: (bb, chess) => { + const t0 = talentBb(chess, 0), t1 = talentBb(chess, 1), tb = traitBb(chess); + const r1 = skillRec(chess, S1), r2 = skillRec(chess, S2), r3 = skillRec(chess, S3); + const fbAspd = num(t1.attack_speed, 0); + const fbMult = num(r2?.bb?.talent_mult, 1); + /** Her S2 runs (the enhanced 反馈装甲). */ + const s2On = (unit) => !!(unit.skill && unit.skill.active && unit.skill.id === S2); + /** A 反馈装甲 buff: it ends at the first 0.1 s check of its target that finds nobody blocking it. */ + const feedback = (battle, unit, e, key, aspd) => battle.addBuff(e, { + key, mods: { aspd }, source: unit, data: { v: aspd }, tags: ['talent'], interval: CHECK_INTERVAL, + onTick({ battle: b, unit: t, buff }) { if (!t.blockedBy) b.removeBuff(t, buff); }, + }); + /** Apply 反馈装甲 (its S2 version while S2 runs) to every enemy she blocks — the talent's 0.1 s application. */ + const applyFeedback = (battle, unit) => { + if (!fbAspd || !up(unit)) return; + const enhanced = s2On(unit); + for (const e of unit.blocking) { + if (!e.alive || e.blockedBy !== unit) continue; + if (enhanced) { + if (e.findBuff(FEEDBACK_S2_KEY)) continue; + battle.removeBuff(e, FEEDBACK_KEY); + feedback(battle, unit, e, FEEDBACK_S2_KEY, fbAspd * fbMult); + } else if (!e.findBuff(FEEDBACK_S2_KEY) && !e.findBuff(FEEDBACK_KEY)) { + feedback(battle, unit, e, FEEDBACK_KEY, fbAspd); + } + } + }; + const skills = {}; + if (r1) skills[S1] = { kind: 'duration', mods: { hpPct: num(r1.bb?.max_hp), defPct: num(r1.bb?.def) } }; + if (r2) { + skills[S2] = { + kind: 'duration', + mods: { defPct: num(r2.bb?.def) }, + onStart({ battle, unit }) { applyFeedback(battle, unit); }, + onEnd({ battle, unit }) { + for (const e of battle.enemies) { + const b = e.findBuff(FEEDBACK_S2_KEY); + if (b && b.source === unit) battle.removeBuff(e, b); + } + }, + }; + } + if (r3) { + const scale = num(r3.bb?.atk_scale, 0); + skills[S3] = { + kind: 'duration', + mods: { defPct: num(r3.bb?.def), blockCnt: num(r3.bb?.block_cnt) }, + onStart({ unit }) { unit.mem.acfendStress = 0; }, + onTick({ battle, unit, dt }) { + unit.mem.acfendStress = (unit.mem.acfendStress ?? 0) + dt; + while (unit.mem.acfendStress + 1e-6 >= STRESS_INTERVAL) { + unit.mem.acfendStress -= STRESS_INTERVAL; + const v = battle.blockedTargets(unit, { canHitFly: true }); + if (v.length) battle.fx('aoe', { x: unit.x, y: unit.y, id: unit.id, radius: 0.5, skill: S3 }); + for (const e of v) battle.dealDamage(unit, e, { amount: unit.s.atk * scale, type: 'arts', isSkill: true, tags: ['skill', 'stress'] }); + } + }, + onEnd({ unit }) { unit.mem.acfendStress = 0; }, + }; + } + return { + skills, + talents: [ + { install(battle, unit) { // 精研材料 + statBuff(battle, unit, 'acfend:t1', { defPct: num(t0.def) }); + const extra = num(t0['acfend_t_1[extra].def']), ratio = num(t0.hp_ratio, 0); + if (!extra || !(ratio > 0)) return; + const check = () => { + const want = up(unit) && unit.hpRatio < ratio - 1e-9; + const has = !!unit.findBuff('acfend:t1:extra'); + if (want && !has) battle.addBuff(unit, { key: 'acfend:t1:extra', mods: { defPct: extra }, tags: ['talent'] }); + else if (!want && has) battle.removeBuff(unit, 'acfend:t1:extra'); + }; + battle.every(CHECK_INTERVAL, check, { owner: unit }); + } }, + { install(battle, unit) { // 反馈装甲 + if (fbAspd) battle.every(CHECK_INTERVAL, () => applyFeedback(battle, unit), { owner: unit }); + } }, + ], + install(battle, unit) { + const md = num(tb.def, 0); // module PRO-X: 阻挡敌人时防御力+20% + if (md) toggleBuff(battle, unit, 'acfend:pro', () => unit.blocking.length > 0, { defPct: md }); + }, + }; + }, +}; diff --git a/test/content/standin_acfend.test.js b/test/content/standin_acfend.test.js new file mode 100644 index 00000000..7e68eb08 --- /dev/null +++ b/test/content/standin_acfend.test.js @@ -0,0 +1,205 @@ +// test/content/standin_acfend.test.js — the Mechanist (char_610_acfend) 补位 stand-in kit (kits/ops/standin-acfend.js) on +// every chess she replaces: 4 星熊 (S2), 5 号角 / 6 余 (S3, module PRO-X on the elite); S1 through the harness +// `standIn: { skillIndex: 0 }`. Numbers: data/backups.json (skill_table, PRTS Mechanist(卫戍协议) and its 备注: the 0.1 s +// checks of both talents, S2's swap of the talent effect). Her triggers: the owner's 重装 exception for the stand-ins. +// Run: node --test test/content/standin_acfend.test.js + +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js'; +import { unitForm } from '../../shared/standIn.js'; +import { TICK } from '../../server/sim/constants.js'; + +const load = (f) => JSON.parse(readFileSync(new URL(`../../data/${f}.json`, import.meta.url), 'utf8')); +const CHESS = load('chess'); +const BACKUPS = load('backups'); +const CHAR = 'char_610_acfend'; +const S1 = 'skchr_acfend_1', S2 = 'skchr_acfend_2'; +const RECORDS = BACKUPS.units[CHAR].standsIn.flatMap((a) => [a, a.replace(/_a$/, '_b')]); +const dummy = (o = {}) => enemyRec({ key: 'enemy_dummy', hp: 1e9, speed: 0, ...o }); +const DEFS = { + enemies: { + enemy_dummy: dummy(), + enemy_res: dummy({ key: 'enemy_res', res: 20 }), + enemy_fly: dummy({ key: 'enemy_fly', motion: 'FLY' }), + }, +}; +const close = (a, b, msg, eps = 1e-6) => assert.ok(Math.abs(a - b) <= eps * Math.max(1, Math.abs(b)), `${msg}: ${a} vs ${b}`); +const formOf = (id) => unitForm(BACKUPS, CHAR, CHESS[id].status); +const skillOf = (id, skillId) => formOf(id).skills.find((s) => s.skillId === skillId); +const moduleOf = (id) => (CHESS[id].backup.uniEquipId && CHESS[id].status.equipLevel > 0 ? formOf(id).modules.find((m) => m.uniEquipId === CHESS[id].backup.uniEquipId) : null); + +function battle(opts) { + const log = []; + const h = makeBattle({ + defs: DEFS, timeLimit: 300, autoFinish: false, seed: 5, flags: { dpPerSec: 0 }, ...opts, + setup(b) { + b.on('damaged', (c) => log.push({ t: b.time, src: c.source?.id ?? null, tgt: c.target.id, key: c.target.defId, amount: c.amount, type: c.type, tags: c.dmg?.tags ?? [], skill: !!c.dmg?.isSkill }), { priority: -999 }); + if (opts.setup) opts.setup(b); + }, + }); + h.dmg = log; + return h; +} +/** DEF % of her talent and module at this moment (精研材料 +10 %, its extra below half HP, PRO-X while blocking). */ +const passiveDefPct = (id, u) => { + const t0 = u.def.talents[0].bb; + const mod = moduleOf(id); + return t0.def + (u.hpRatio < t0.hp_ratio ? t0['acfend_t_1[extra].def'] : 0) + (mod && u.blocking.length ? mod.traitOverride.bb.def : 0); +}; + +// --------------------------------------------------------------------------------------------------------------- + +test('Mechanist fields on all 6 records she replaces: her body (+ PRO-X attributes on the 5 / 6 elites), the chess\'s backup skill, her own kit, the 重装 trigger', () => { + assert.equal(RECORDS.length, 6); + for (const id of RECORDS) { + const c = CHESS[id]; + const form = formOf(id); + const sk = form.skills.find((s) => s.index === c.backup.skillIndex); + const mod = moduleOf(id); + const h = battle({ units: [{ chessId: id, row: 10, col: 4, standIn: true }] }); + h.step(); + const u = h.unit(1); + assert.deepEqual([u.def.charId, u.def.standInFor, u.skill.id], [CHAR, c.charId, sk.skillId], id); + assert.ok(!u.kit.generic && u.kit.skillSource === 'skills', `${id}: her kit's own spec`); + assert.equal(!!u.def.raw.module?.active, !!mod, `${id}: module`); + assert.deepEqual([u.base.def, u.base.maxHp, u.base.blockCnt], [form.stats.def + (mod?.attr.def ?? 0), form.stats.maxHp + (mod?.attr.maxHp ?? 0), 3], `${id}: stats`); + // STANDIN_TRIGGER_DEVIATIONS: DEFAULT (the official TAKE_DAMAGE kept as rawRule) + assert.deepEqual([u.skill.rule, sk.trigger.rawRule], ['DEFAULT', 'TAKE_DAMAGE'], `${id}: trigger`); + assert.equal(u.profile.canHitFly, false, `${id}: ground-only`); + assert.equal(h.b.errors.length, 0); + } + assert.deepEqual([CHESS.chess_char_4_17_a, CHESS.chess_char_5_08_a, CHESS.chess_char_6_03_a].map((c) => c.backup.skillIndex), [1, 2, 2]); +}); + +test('her skills cast with an enemy she blocks before anything hurts her (the owner\'s 重装 exception) — never on an air unit she cannot reach', () => { + for (const id of ['chess_char_4_17_a', 'chess_char_5_08_b', 'chess_char_6_03_a']) { + const h = battle({ units: [{ chessId: id, row: 10, col: 4, standIn: true, carryState: { sp: 999 } }], enemies: [{ key: 'enemy_fly', pos: [10, 4] }] }); + const u = h.unit(1); + h.run(2); + assert.equal(u.skill.activations, 0, `${id}: an air unit on her tile is no target`); + h.spawn('enemy_dummy', { pos: [10, 4] }); // ATK 0: it never damages her + assert.ok(h.runUntil(() => u.skill.activations === 1, 3), `${id}: cast`); + assert.equal(h.dmg.filter((d) => d.tgt === u.id).length, 0, `${id}: untouched`); + checkInvariants(h.b); + } +}); + +test('精研材料: DEF +10 %; below 50 % HP another +10 % (+20 % with PRO-X Lv3), checked every 0.1 s', () => { + for (const [id, extra] of [['chess_char_4_17_a', 0.1], ['chess_char_5_08_b', 0.1], ['chess_char_6_03_b', 0.2]]) { + const h = battle({ units: [{ chessId: id, row: 10, col: 4, standIn: true }] }); + const u = h.unit(1); + h.step(); + assert.equal(u.def.talents[0].bb['acfend_t_1[extra].def'], extra, id); + close(u.s.def, u.base.def * 1.1, `${id}: +10 %`); + h.b.loseHp(u, u.hp * 0.55); + h.run(0.1 + TICK); + close(u.s.def, u.base.def * (1.1 + extra), `${id}: below half HP`); + h.b.heal(u, u, u.s.maxHp); + h.run(0.1 + TICK); + close(u.s.def, u.base.def * 1.1, `${id}: healed`); + checkInvariants(h.b); + } +}); + +test('反馈装甲: enemies she blocks ASPD −7 within 0.1 s; it ends once nobody blocks them — at her retreat too', () => { + const h = battle({ units: [{ chessId: 'chess_char_4_17_a', row: 10, col: 4, standIn: true }], enemies: [{ key: 'enemy_dummy', pos: [10, 4] }, { key: 'enemy_dummy', pos: [10, 4] }, { key: 'enemy_fly', pos: [10, 5] }] }); + const u = h.unit(1); + h.run(0.1 + TICK); + const [a, b, f] = h.b.enemies; + assert.deepEqual([a.s.aspd, b.s.aspd, f.s.aspd], [93, 93, 100], 'the two blocked enemies, not the air unit'); + assert.equal(a.findBuff('acfend:feedback').source, u); + h.b.retreat(u); + h.run(0.1 + TICK); + assert.deepEqual([a.s.aspd, b.s.aspd], [100, 100], 'released: gone at the next check'); + checkInvariants(h.b); +}); + +test('S1 结构稳定: 40 s of max HP / DEF +26 % (+40 % elite)', () => { + for (const [id, v] of [['chess_char_4_17_a', 0.26], ['chess_char_4_17_b', 0.4]]) { + assert.deepEqual([skillOf(id, S1).bb.max_hp, skillOf(id, S1).bb.def, skillOf(id, S1).duration], [v, v, 40]); + const h = battle({ units: [{ chessId: id, row: 10, col: 4, standIn: { skillIndex: 0 }, carryState: { sp: 999 } }], enemies: [{ key: 'enemy_dummy', pos: [10, 4] }] }); + const u = h.unit(1); + let cast = null; + h.b.on('skillStart', (c) => { if (c.unit === u) cast = h.b.time; }); + assert.ok(h.runUntil(() => cast != null, 3)); + h.step(); + close(u.s.maxHp, u.base.maxHp * (1 + v), `${id}: max HP`); + close(u.s.def, u.base.def * (1 + v + passiveDefPct(id, u)), `${id}: DEF`); + h.runUntil(() => !u.skill.active, 45); + close(h.b.time - cast, 40, `${id}: 40 s`, 0.01); + close(u.s.maxHp, u.base.maxHp, `${id}: max HP back`); + checkInvariants(h.b); + } +}); + +test('S2 不变性原理 (星熊\'s chess): 20 s of DEF +50 % / +65 %; 反馈装甲 ×2 / ×3 on the enemies she blocks, back to −7 after', () => { + for (const [id, v, mult] of [['chess_char_4_17_a', 0.5, 2], ['chess_char_4_17_b', 0.65, 3]]) { + const h = battle({ units: [{ chessId: id, row: 10, col: 4, standIn: true }], enemies: [{ key: 'enemy_dummy', pos: [10, 4] }, { key: 'enemy_dummy', pos: [10, 4] }] }); + const u = h.unit(1); + h.run(0.2); + assert.deepEqual(h.b.enemies.map((e) => e.s.aspd), [93, 93]); + let cast = null; + h.b.on('skillStart', (c) => { if (c.unit === u) cast = h.b.time; }); + assert.ok(h.runUntil(() => cast != null, 60)); + h.step(); + assert.equal(u.skill.id, S2); + close(u.s.def, u.base.def * (1 + v + passiveDefPct(id, u)), `${id}: DEF`); + assert.deepEqual(h.b.enemies.map((e) => e.s.aspd), [100 - 7 * mult, 100 - 7 * mult], `${id}: ×${mult} at the cast`); + assert.ok(h.b.enemies.every((e) => !e.findBuff('acfend:feedback') && e.findBuff('acfend:feedback2')), `${id}: the plain one replaced`); + h.runUntil(() => !u.skill.active, 25); + close(h.b.time - cast, 20, `${id}: 20 s`, 0.01); + h.run(0.1 + TICK); + assert.deepEqual(h.b.enemies.map((e) => e.s.aspd), [93, 93], `${id}: plain again`); + checkInvariants(h.b); + } +}); + +test('S3 应力倒置 (号角 / 余\'s chess): 30 s of DEF +50 % / +70 %, block +1, ATK × 40 % / 55 % arts to each enemy she blocks every second', () => { + for (const [id, v, scale] of [['chess_char_5_08_a', 0.5, 0.4], ['chess_char_6_03_b', 0.7, 0.55]]) { + const h = battle({ + units: [{ chessId: id, row: 10, col: 4, standIn: true, carryState: { sp: 999 } }], + enemies: [{ key: 'enemy_dummy', pos: [10, 4] }, { key: 'enemy_res', pos: [10, 4] }, { key: 'enemy_dummy', pos: [10, 4] }, { key: 'enemy_dummy', pos: [10, 4] }, { key: 'enemy_dummy', pos: [10, 4] }], + }); + const u = h.unit(1); + let cast = null; + h.b.on('skillStart', (c) => { if (c.unit === u) cast = h.b.time; }); + h.step(); + assert.equal(u.blocking.length, 3, 'blocks 3'); + assert.ok(h.runUntil(() => cast != null, 3)); + h.run(0.5); + assert.equal(u.s.blockCnt, 4); + assert.equal(u.blocking.length, 4, `${id}: blocks a 4th`); + close(u.s.def, u.base.def * (1 + v + passiveDefPct(id, u)), `${id}: DEF`); + h.runUntil(() => !u.skill.active, 35); + close(h.b.time - cast, 30, `${id}: 30 s`, 0.01); + const pulses = h.dmg.filter((d) => d.src === u.id && d.tags.includes('stress')); + const times = [...new Set(pulses.map((d) => Math.round((d.t - cast) * 1000) / 1000))]; + assert.equal(times.length, 30, `${id}: 30 pulses`); + times.forEach((t, i) => assert.ok(Math.abs(t - (i + 1)) <= TICK + 1e-6, `${id}: pulse ${i + 1} at ${t}`)); + assert.ok(pulses.every((d) => d.type === 'arts' && d.skill)); + const first = pulses.filter((d) => d.t === pulses[0].t); + assert.equal(first.length, 4, `${id}: each blocked enemy`); + for (const d of first) close(d.amount, u.s.atk * scale * (d.key === 'enemy_res' ? 0.8 : 1), `${id}: ${d.key}`); + h.run(0.2); + assert.equal(u.s.blockCnt, 3); + checkInvariants(h.b); + } +}); + +test('module PRO-X (5 号角 / 6 余 elites): DEF +20 % exactly while she blocks; none without it', () => { + for (const id of ['chess_char_4_17_b', 'chess_char_5_08_a', 'chess_char_5_08_b', 'chess_char_6_03_b']) { + const mod = moduleOf(id); + const h = battle({ units: [{ chessId: id, row: 10, col: 4, standIn: true }] }); + const u = h.unit(1); + h.step(); + close(u.s.def, u.base.def * 1.1, `${id}: not blocking`); + h.spawn('enemy_dummy', { pos: [10, 4] }); + h.run(0.2); + assert.equal(u.blocking.length, 1); + close(u.s.def, u.base.def * (1.1 + (mod ? 0.2 : 0)), `${id}: blocking`); + assert.equal(!!mod, id === 'chess_char_5_08_b' || id === 'chess_char_6_03_b', id); + checkInvariants(h.b); + } +});