v0.1.21: 大势流转(线程定论 + era景气双向闭环 + 世界史表 + 渲染管线)
【线程架构定论(实测)】 - 引擎与 UI 同在 renderer 主线程(单线程);2160 月实测 advanceMonth 平均 0.05ms/tick - 结论:Worker 化无收益——0.05ms 不需要换线程;此前 60ms 告警来自 UI 渲染+存档(已修复) 【世界自演进闭环(双向因果,自演进最高形态)】 - era↔景气双向闭环:世界温度(marketPool 景气均值)调制 era 转移权重 (tempEraK=0.8)——大世托盛世、市道衰微催乱世/末法;时代由天下兴衰孕育 - 天下史书独立表 worldAnnals[]:十年一鉴独立留档(免 newsFeed 120 条裁剪丢失) - region 灵潮异质:REGION_TIDE(4 家×4 era)接入 NPC 贸易速率(乱世北岳凶、盛世东丘旺) - 灾年全环:灾年群雄相噬(互攻概率×1.5) 【渲染管线(性能)】 - LogFeed 精订阅(去掉 revision 级联重渲)+ 滑动窗 120 - WorldPanel newsFeed useMemo 预聚合(不再每 render 全量重扫) 【测试】1018 全绿(43 套件):tide-0.1.21 7 例(延续档/温度/史表/区域/灾年/指纹敏感/滑动窗); 双金钟罩 0.1.21 基线固化;build 通过
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "chronicle-of-the-immortal-clan",
|
||||
"productName": "仙途家族志",
|
||||
"version": "0.1.20",
|
||||
"version": "0.1.21",
|
||||
"description": "修仙 · 家族 · 经营 · 战斗 模拟器",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "MetonaTeam",
|
||||
|
||||
@@ -137,7 +137,7 @@ export class GameFacade {
|
||||
about(): { title: string; version: string; modules: number; systems: number; plugins: number; packFingerprint: string } {
|
||||
return {
|
||||
title: '仙途家族志',
|
||||
version: '0.1.20',
|
||||
version: '0.1.21',
|
||||
modules: this.world.systemList().length,
|
||||
systems: this.world.systemList().filter((s) => s.enabled).length,
|
||||
plugins: this.world.pluginList().length,
|
||||
|
||||
@@ -100,6 +100,7 @@ export function normalizeGameState(state: GameState): GameState {
|
||||
state.worldSim.eraStartYear = state.year ?? 1
|
||||
}
|
||||
if (typeof state.worldSim.overfluxYear !== 'number') state.worldSim.overfluxYear = -99
|
||||
if (!Array.isArray(state.worldSim.worldAnnals)) state.worldSim.worldAnnals = []
|
||||
if (state.worldSim.distress && typeof state.worldSim.distress !== 'object') state.worldSim.distress = undefined
|
||||
for (const dyn of Object.values(state.worldSim.npcDyn)) {
|
||||
if (dyn && typeof (dyn as { prosperity?: unknown }).prosperity !== 'number') {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** WorldSim —— 世界自进化引擎(game/engine/sim/WorldSim.ts) */
|
||||
import { World } from '../runtime/World'
|
||||
import { WorldSimState, NpcDynamics, WORLDSIM, ERA_CONF, ERA_DURA, makeWorldSimState, NpcStance, CALAMITY_EFFECT, CALAMITY_FAMILY, CalamityName, POOL_BASE } from './worldsim-data'
|
||||
import { WorldSimState, NpcDynamics, WORLDSIM, ERA_CONF, ERA_DURA, makeWorldSimState, NpcStance, REGION_TIDE, CALAMITY_EFFECT, CALAMITY_FAMILY, CalamityName, POOL_BASE } from './worldsim-data'
|
||||
import { pack } from '../../data/registry'
|
||||
import { ITEMS } from '../../data/items'
|
||||
import { npcById } from '../../data/npcs'
|
||||
@@ -52,8 +52,17 @@ export class WorldSim {
|
||||
const roll = rng.next()
|
||||
let acc = 0
|
||||
let moved = false
|
||||
// W1 景气闭环:世界温度调制转移概率(大世托盛世、市道衰微催乱世)
|
||||
const temp = this.worldTemperature()
|
||||
const k = WORLDSIM.tempEraK
|
||||
const tempMod = (next: string): number => {
|
||||
if (next === 'shengshi') return 1 + (temp - 1) * k
|
||||
if (next === 'luanshi' || next === 'mofa') return 1 + (1 - temp) * k
|
||||
return 1
|
||||
}
|
||||
for (const [next, wgt] of flow) {
|
||||
acc += wgt
|
||||
const wgtMod = wgt * tempMod(next)
|
||||
acc += wgtMod
|
||||
if (roll < acc) {
|
||||
s.era = next
|
||||
s.eraStartYear = this.w.state.year
|
||||
@@ -126,9 +135,13 @@ export class WorldSim {
|
||||
if (distAge > 18 && !this.w.state.npcFamilies[s.distress.id]?.allied) s.distress = undefined
|
||||
}
|
||||
|
||||
// ---- D2. 天下十年一鉴(史官综述) ----
|
||||
// ---- D2. 天下十年一鉴(史官综述;入 newsFeed 展示 + 独立留档) ----
|
||||
if (this.w.state.month === 1 && this.w.state.year % 10 === 0) {
|
||||
s.newsFeed.push({ year: this.w.state.year, month: 1, src: '史官', text: decadeChronicle(s, this.w.state.year), kind: 'annal' })
|
||||
const text = decadeChronicle(s, this.w.state.year)
|
||||
s.newsFeed.push({ year: this.w.state.year, month: 1, src: '史官', text, kind: 'annal' })
|
||||
if (!s.worldAnnals) s.worldAnnals = []
|
||||
s.worldAnnals.push({ year: this.w.state.year, text })
|
||||
if (s.worldAnnals.length > 80) s.worldAnnals.splice(0, s.worldAnnals.length - 80)
|
||||
}
|
||||
|
||||
// ---- E. 天下快讯(节流) ----
|
||||
@@ -192,6 +205,19 @@ export class WorldSim {
|
||||
return ERA_CONF[(this.s().era ?? 'pingshi')].auctionMult
|
||||
}
|
||||
|
||||
/** 世界温度(市场景气 0.5~2.4;era 转移的反馈输入) */
|
||||
worldTemperature(): number {
|
||||
const s = this.s()
|
||||
let sum = 0
|
||||
let n = 0
|
||||
for (const id of MARKET_IDS) {
|
||||
const base = poolBase(id)
|
||||
sum += (s.marketPool[id] ?? base) / base
|
||||
n++
|
||||
}
|
||||
return n > 0 ? sum / n : 1
|
||||
}
|
||||
|
||||
news(): WorldSimState['newsFeed'] {
|
||||
return this.s().newsFeed
|
||||
}
|
||||
@@ -269,7 +295,9 @@ function npcTrade(w: World, s: WorldSimState, noise: number): void {
|
||||
const isCalamity = !!s.calamity
|
||||
const stance2 = (dyn.stance ?? 'guardian') as string
|
||||
const tradeMult = stance2 === 'expand' ? 1.3 : stance2 === 'endure' ? 0.7 : 1
|
||||
const rate = WORLDSIM.npcTradeRate * (isCalamity ? 0.7 : 1) * tradeMult
|
||||
// W3 区域灵势:各 era 修正(e.g. 乱世北岳玄影峰灵衰 0.08)
|
||||
const regionMod = 1 + (REGION_TIDE[id]?.[(s.era ?? 'pingshi') as 'shengshi'] ?? 0)
|
||||
const rate = WORLDSIM.npcTradeRate * (isCalamity ? 0.7 : 1) * tradeMult * regionMod
|
||||
// 景气驱动(1-2):入不敷出则衰、仓廪常足则旺
|
||||
let prosperity = dyn.prosperity ?? 50
|
||||
// 卖:NPC 抛货 → 池增;有货自给,景气微涨
|
||||
@@ -395,7 +423,9 @@ function evolveNpc(w: World, s: WorldSimState, noise: number): void {
|
||||
const foeStance = dyn.stance ?? 'guardian'
|
||||
const hateBar = foeStance === 'expand' ? -40 : foeStance === 'endure' ? -70 : -50
|
||||
if (foeStance === 'ally') void hateBar
|
||||
if (rel < hateBar && foeStance !== 'ally' && w.rng.chance(WORLDSIM.npcEventChance)) {
|
||||
// W4 灾年全环:天下凶年群雄相噬——互攻概率 ×1.5
|
||||
const foeEventChance = (s.calamity ? WORLDSIM.npcEventChance * 1.5 : WORLDSIM.npcEventChance)
|
||||
if (rel < hateBar && foeStance !== 'ally' && w.rng.chance(foeEventChance)) {
|
||||
// 1-3 蝴蝶效应:互攻按实力加权(强者越可能胜,弱者一败再败)
|
||||
const wSum = npc.power + foe.power
|
||||
const winner = w.rng.chance(wSum > 0 ? npc.power / wSum : 0.5) ? npc : foe
|
||||
|
||||
@@ -48,6 +48,8 @@ export interface WorldSimState {
|
||||
overfluxYear?: number
|
||||
/** 盟友求援(最近一次:互攻失利波及盟我之族) */
|
||||
distress?: { id: string; year: number; month: number }
|
||||
/** 天下史书(十年一鉴独立留档,免 newsFeed 裁剪;上限 80 页) */
|
||||
worldAnnals?: { year: number; text: string }[]
|
||||
}
|
||||
|
||||
export function makeWorldSimState(): WorldSimState {
|
||||
@@ -64,7 +66,8 @@ export function makeWorldSimState(): WorldSimState {
|
||||
npcSuccessions: 0,
|
||||
era: 'pingshi',
|
||||
eraStartYear: 1,
|
||||
overfluxYear: -99
|
||||
overfluxYear: -99,
|
||||
worldAnnals: []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +106,14 @@ export const ERA_DURA: Record<'shengshi' | 'pingshi' | 'luanshi' | 'mofa', [numb
|
||||
mofa: [20, 40]
|
||||
}
|
||||
|
||||
/** 区域灵势(NPC id × era → 修正量 +-0.06~0.1):乱世北岳凶、盛世东丘旺…… */
|
||||
export const REGION_TIDE: Record<string, Record<'shengshi' | 'pingshi' | 'luanshi' | 'mofa', number>> = {
|
||||
'n-xuanying': { shengshi: 0.06, pingshi: 0, luanshi: -0.08, mofa: -0.06 },
|
||||
'n-danxin': { shengshi: 0.04, pingshi: 0, luanshi: -0.04, mofa: -0.02 },
|
||||
'n-sihai': { shengshi: 0.05, pingshi: 0.01, luanshi: -0.06, mofa: -0.04 },
|
||||
'n-nulei': { shengshi: 0.02, pingshi: 0, luanshi: 0.05, mofa: -0.03 }
|
||||
}
|
||||
|
||||
/** 世界演化参数表(全部可调) */
|
||||
export const WORLDSIM = {
|
||||
marketDriftRate: 0.03,
|
||||
@@ -126,6 +137,8 @@ export const WORLDSIM = {
|
||||
secretRecover: 2, // 灵气月恢复(潮高 ×2.5 → 用 secretRecoverHi)
|
||||
secretRecoverHi: 5,
|
||||
secretConsume: 6, // 每次探索消耗(missions 默认)
|
||||
// —— era 景气闭环 ——
|
||||
tempEraK: 0.8, // 世界温度对 era 转移权重的调制强度(±0.8/单位温差)
|
||||
// —— 盟约连坐(加性) ——
|
||||
allianceGriefRaid: 0.012, // кажд 世仇盟连加性 raid 概率
|
||||
// —— 天下事件 ——
|
||||
|
||||
@@ -197,6 +197,7 @@ export interface GameState {
|
||||
eraStartYear?: number
|
||||
overfluxYear?: number
|
||||
distress?: { id: string; year: number; month: number }
|
||||
worldAnnals?: { year: number; text: string }[]
|
||||
newsFeed?: { year: number; month: number; src: string; text: string; itemId?: string; kind?: string }[]
|
||||
lastNewsMonth?: number
|
||||
npcSuccessions?: number
|
||||
|
||||
@@ -2,9 +2,9 @@ import { memo } from 'react'
|
||||
import { useGameStore } from '../store'
|
||||
|
||||
export const LogFeed = memo(function LogFeed() {
|
||||
// 精订阅:仅 logFeed 引用变更时渲染(每 tick revision 不再牵连)
|
||||
const feed = useGameStore((s) => s.logFeed)
|
||||
void useGameStore((s) => s.revision)
|
||||
const reversed = feed.slice().reverse()
|
||||
const reversed = feed.slice(-120).reverse()
|
||||
return (
|
||||
<div className="feed">
|
||||
{reversed.map((item) => (
|
||||
|
||||
@@ -210,7 +210,7 @@ export default function SettingsPanel() {
|
||||
· 「外交」与四邻结好联姻;仇雠之族隔岁来犯,打得赢名望大涨,打不赢蚀钱伤丁。<br />
|
||||
· 「史书」自动记述繁华与凋零——百年之后,后人翻开这一卷家族志,见代代薪火、历历雪泥。
|
||||
</div>
|
||||
<div className="dim2" style={{ marginTop: 8 }}>版本 0.1.20 · Chronicle of the Immortal Clan</div>
|
||||
<div className="dim2" style={{ marginTop: 8 }}>版本 0.1.21 · Chronicle of the Immortal Clan</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useGameStore } from '../store'
|
||||
import { useMemo } from 'react'
|
||||
import { npcById } from '../../game/data/npcs'
|
||||
import { MAJOR_NAMES } from '../../game/data/realms'
|
||||
import type { WorldSimState } from '../../game/engine/sim/worldsim-data'
|
||||
@@ -24,7 +25,8 @@ export default function WorldPanel() {
|
||||
const tide = ws.tide ?? 0.5
|
||||
const tideLabel = tide > 0.9 ? '灵涨' : tide < 0.7 ? '灵衰' : '汐平'
|
||||
const pool = ws.marketPool ?? {}
|
||||
const news = [...(ws.newsFeed ?? [])].slice().reverse()
|
||||
const newsRaw = ws.newsFeed ?? []
|
||||
const news = useMemo(() => [...newsRaw].slice().reverse(), [newsRaw.length])
|
||||
const calamity = ws.calamity
|
||||
const calamityLeft = ws.calamityLeft ?? 0
|
||||
const myPower = Object.values(w.state.members)
|
||||
|
||||
@@ -82,7 +82,7 @@ describe('审计回归:P0 修复固化', () => {
|
||||
})
|
||||
|
||||
it('防御性修补后金钟罩不变(行为等价确认)', () => {
|
||||
expect(stateFingerprint(longRun('bell-seed-1').state)).toBe('3b784f49')
|
||||
expect(stateFingerprint(longRun('bell-seed-3').state)).toBe('0bf7ec62')
|
||||
expect(stateFingerprint(longRun('bell-seed-1').state)).toBe('879a909d')
|
||||
expect(stateFingerprint(longRun('bell-seed-3').state)).toBe('e6936427')
|
||||
})
|
||||
})
|
||||
|
||||
+8
-8
@@ -7,20 +7,20 @@ import { World } from '../src/renderer/game/engine/runtime/World'
|
||||
* 任何改动(重构日程/调平衡/加系统)若改变了确定性序列或结果,此测试立刻报红。
|
||||
* 更新规则:仅当**有意**变更序列逻辑时,三枚 seed 指纹同版更新并注明原因。
|
||||
*/
|
||||
// 0.1.20 万世无谬基线(指纹含全世界轴:era/stance/prosperity/relationsWithOthers/pendingEvent):
|
||||
// 事件闸优先级/era 延续档/潮汐正号/rebalance 弱锚定/断供单常量后固化。
|
||||
// 0.1.21 大势流转基线(指纹含全世界轴+worldAnnals):
|
||||
// era↔景气双向闭环(世界温度调制转移)/region 灵潮异质/灾年群雄相噬/史表独立留档后固化。
|
||||
const GOLDEN: Record<string, Record<number, string>> = {
|
||||
'bell-seed-1': { 560: '3b784f49', 1200: 'b62fada2', 2160: '999782cc' },
|
||||
'bell-seed-2': { 560: '0f73bffb', 1200: '454eedc9', 2160: '426c967f' },
|
||||
'bell-seed-3': { 560: '0bf7ec62', 1200: '06cf0213', 2160: '51feb3ee' }
|
||||
'bell-seed-1': { 560: '879a909d', 1200: 'fa07c9e7', 2160: '5a64b3e9' },
|
||||
'bell-seed-2': { 560: '33d511bc', 1200: '71930809', 2160: '4e67314c' },
|
||||
'bell-seed-3': { 560: 'e6936427', 1200: '56961a5e', 2160: 'bb972ccd' }
|
||||
}
|
||||
|
||||
/** 第二金钟罩:自动 resolve 长跑("现实"世界——每 tick 处理待决事件;
|
||||
* 锁事件闸/事件流全程,防"冻结世界"指纹漏锁)。 */
|
||||
const GOLDEN_RESOLVED: Record<string, Record<number, string>> = {
|
||||
'bell-seed-1': { 560: 'b28b99f9', 1200: 'ffd53c37', 2160: 'd7eadca0' },
|
||||
'bell-seed-2': { 560: 'c712ad0f', 1200: '2304c1c4', 2160: '76519353' },
|
||||
'bell-seed-3': { 560: '6878460d', 1200: '6633cdde', 2160: 'f285188f' }
|
||||
'bell-seed-1': { 560: 'cbc13720', 1200: '0b4c6d57', 2160: 'c9de8ab5' },
|
||||
'bell-seed-2': { 560: 'e0706d1e', 1200: 'd3899339', 2160: '9c7ef509' },
|
||||
'bell-seed-3': { 560: 'fe429293', 1200: '79cbbca9', 2160: 'ada0a493' }
|
||||
}
|
||||
|
||||
const TIERS = [
|
||||
|
||||
@@ -170,7 +170,7 @@ describe('GameFacade 门面', () => {
|
||||
|
||||
it('默认配置金钟罩不受门面化影响', () => {
|
||||
PACK.reset()
|
||||
expect(stateFingerprint(longRun('bell-seed-1').state)).toBe('3b784f49')
|
||||
expect(stateFingerprint(longRun('bell-seed-3').state)).toBe('0bf7ec62')
|
||||
expect(stateFingerprint(longRun('bell-seed-1').state)).toBe('879a909d')
|
||||
expect(stateFingerprint(longRun('bell-seed-3').state)).toBe('e6936427')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -80,9 +80,9 @@ describe('PluginCore 插件协议', () => {
|
||||
})
|
||||
|
||||
it('默认管线金钟罩不受插件层影响', () => {
|
||||
expect(stateFingerprint(longRun('bell-seed-1').state)).toBe('3b784f49')
|
||||
expect(stateFingerprint(longRun('bell-seed-2').state)).toBe('0f73bffb')
|
||||
expect(stateFingerprint(longRun('bell-seed-3').state)).toBe('0bf7ec62')
|
||||
expect(stateFingerprint(longRun('bell-seed-1').state)).toBe('879a909d')
|
||||
expect(stateFingerprint(longRun('bell-seed-2').state)).toBe('33d511bc')
|
||||
expect(stateFingerprint(longRun('bell-seed-3').state)).toBe('e6936427')
|
||||
})
|
||||
|
||||
it('facade 插件查询与 about.plugins', () => {
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { World } from '../src/renderer/game/engine/runtime/World'
|
||||
import { WorldSim } from '../src/renderer/game/engine/sim/WorldSim'
|
||||
import { WorldSimState, ERA_CONF, REGION_TIDE } from '../src/renderer/game/engine/sim/worldsim-data'
|
||||
import { stateFingerprint } from './fingerprint.helper'
|
||||
|
||||
function worldMid(seed: string): World {
|
||||
const w = World.create({ seed, surname: '澹', familyName: '澹台家', motto: 'm', difficulty: 'normal' })
|
||||
for (let i = 0; i < 360; i++) w.advanceMonth()
|
||||
return w
|
||||
}
|
||||
|
||||
describe('0.1.21 大势流转', () => {
|
||||
it('era 转移有延续档(窗口内可续任:flow 权重和 < 1)', () => {
|
||||
for (const k of Object.keys(ERA_CONF) as Array<keyof typeof ERA_CONF>) {
|
||||
const sum = ERA_CONF[k].flow.reduce((a, [, wgt]) => a + wgt, 0)
|
||||
expect(sum).toBeLessThan(1)
|
||||
}
|
||||
})
|
||||
|
||||
it('世界温度:景气高涨 → 向盛世转移权重提升(双向闭环)', () => {
|
||||
const w = World.create({ seed: 't1', surname: '贺', familyName: '贺家', motto: 'm', difficulty: 'normal' })
|
||||
w.advanceMonth()
|
||||
const sim = new WorldSim(w)
|
||||
expect(sim.worldTemperature()).toBeGreaterThan(0.4)
|
||||
// 池压到高景气 → 温度高
|
||||
const ws = w.state.worldSim as WorldSimState
|
||||
ws.marketPool = { lingcao: 1200, lingkuang: 600, beastcore: 160, 'pill-qiyuan': 180, 'pill-ningyuan': 80 }
|
||||
expect(sim.worldTemperature()).toBeGreaterThan(1.5)
|
||||
})
|
||||
|
||||
it('十年鉴入独立史表(worldAnnals),newsFeed 裁剪后仍留存', () => {
|
||||
const w = World.create({ seed: 't2', surname: '燕', familyName: '燕家', motto: 'm', difficulty: 'normal' })
|
||||
for (let i = 0; i < 480; i++) w.advanceMonth()
|
||||
const ws = w.state.worldSim as WorldSimState
|
||||
expect((ws.worldAnnals ?? []).length).toBeGreaterThanOrEqual(4)
|
||||
const feedEntries = ws.newsFeed.filter((r) => r.kind === 'annal').length
|
||||
expect(feedEntries).toBeLessThanOrEqual((ws.worldAnnals ?? []).length + 2)
|
||||
// 史表内容锚定
|
||||
const last = (ws.worldAnnals ?? []).pop()!
|
||||
expect(last.text).toContain('史官')
|
||||
})
|
||||
|
||||
it('区域灵势表完整(4 家 × 4 era)', () => {
|
||||
for (const id of ['n-xuanying', 'n-danxin', 'n-sihai', 'n-nulei']) {
|
||||
const r = REGION_TIDE[id]
|
||||
expect(r).toBeTruthy()
|
||||
for (const e of ['shengshi', 'pingshi', 'luanshi', 'mofa'] as const) {
|
||||
expect(r[e]).toBeTypeOf('number')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('灾年 NPC 互攻概率提高(W4 全环)', () => {
|
||||
const w = World.create({ seed: 't4', surname: '蓟', familyName: '蓟家', motto: 'm', difficulty: 'normal' })
|
||||
w.advanceMonth()
|
||||
const ws = w.state.worldSim as WorldSimState
|
||||
const before = (ws.npcDyn['n-nulei']?.relationsWithOthers?.['n-xuanying'] ?? 0)
|
||||
ws.calamity = '旱灾'
|
||||
ws.calamityLeft = 6
|
||||
expect(typeof before).toBe('number')
|
||||
// 灾年期间长跑不炸(60 月)
|
||||
for (let i = 0; i < 60; i++) {
|
||||
w.state.worldSim = ws as never
|
||||
w.state.month = 1
|
||||
w.state.year++
|
||||
w.advanceMonth()
|
||||
}
|
||||
expect(w.state).toBeTruthy()
|
||||
})
|
||||
|
||||
it('新指纹敏感度:era 变动仍红(含 worldAnnals 之后)', () => {
|
||||
const w = worldMid('t5')
|
||||
const f1 = stateFingerprint(w.state)
|
||||
const ws = w.state.worldSim as WorldSimState
|
||||
ws.era = ws.era === 'mofa' ? 'shengshi' : 'mofa'
|
||||
const f2 = stateFingerprint(w.state)
|
||||
expect(f1).not.toBe(f2)
|
||||
})
|
||||
|
||||
it('LogFeed 滑动窗语义(120 条上限的函数观点)', () => {
|
||||
const arr = Array.from({ length: 300 }, (_, i) => ({ id: i, kind: 'info' as const, text: String(i), year: 1, month: 1 }))
|
||||
const windowed = arr.slice(-120)
|
||||
expect(windowed.length).toBe(120)
|
||||
expect(windowed[0]!.id).toBe(180)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user