npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bct-app/game-engine

v0.3.6

Published

Game engine utilities for BCT

Readme

@bct-app/game-engine

血染钟楼(BCT)的规则引擎:从操作记录重放出游戏状态,并在配置一次发动时回答「能不能发动、候选是谁、谁会拦截、谁会响应」。 服务端、管理端、玩家端和小程序共用同一套结算逻辑。

安装

pnpm add @bct-app/game-engine

运行时只依赖 @bct-app/game-dsl(条件求值),对 @bct-app/game-model 只用类型;不依赖 Node、DOM 或任何平台 API。 同时提供 ESM 与 CommonJS 入口。

核心约定

  • 操作是唯一的真相来源:玩家状态、事件、光环全部由重放操作推导(deriveState),引擎不持有可变状态。 TGameLibrary(能力与角色定义)是唯一的例外,它是定义不是状态。
  • 判定在提交时冻结:失能(impairedBy)、仅宣称(claimOnly)、拦截是否放弃(declined)都在提交那一刻判定、写进操作, 重放时只跟随记录,不重新判定。之后改动更早的操作,用 auditDecisions 找出不再成立的判定。
  • 两种效果:TRIGGERED 是落地即成事实的变更;CONTINUOUS 每次推导按存续条件重新判断(投毒者死了,毒自然消失)。
  • 认定分两种口径:候选、建议、匹配按「可能被认定为」(MAY)算,结算只认真实身份(STRICT),definite 的规则两边都算。
  • 说书人:发动者座位 < 1 表示说书人。场外角色(小怪宝、瘟疫医生让说书人获得的爪牙)的能力也由说书人执行, 拦截和事件里的「来源角色」仍是那个角色。说书人还可以裁定任何一份效果不生效(以拦截的形式记录,isStorytellerRuling)。

快速上手

import {
  createLibrary, deriveState, canInvokeAbility, resolveCandidates, validateSelections,
  planOutputInterceptions, findEventReactions, findPhaseEndReactions,
} from '@bct-app/game-engine';

const library = createLibrary(abilities, characters);           // 能力与带能力的角色
const state = deriveState({ initialPlayers, timelines, library, offTableCharacters });

// 按「那一刻」展示或编辑每条记录:一次重放,按需取每条顶层操作之前的盘面
// before(k, i) 等同于 deriveState 重放 timelinesBefore(timelines, k, i)
const moments = deriveMoments({ initialPlayers, timelines, library, offTableCharacters });
moments.before(timelineIndex, operationIndex);

// 能不能发动(死亡、次数、首夜、发动条件)
canInvokeAbility({ abilityId, effectorSeat, state, timelines, library });

// 某个输入的候选(玩家座位或角色 id),以及提交前的校验
resolveCandidates({ input, effectorSeat, state, timelines, library, selections });
validateSelections({ abilityId, branchId, selections, effectorSeat, state, timelines, library });

// 提交一次操作前:哪些产出会被谁拦截
const { outputs } = planOutputInterceptions(operation, { state, timelines, library });

// 提交后:新产生的事件有哪些能力要响应
for (const event of state.events) findEventReactions(event, { state, timelines, library });

// 推进到下一个时段前:阶段结束时自动判定的能力(镇长、涡流)
findPhaseEndReactions({ state, timelines, library });

导出一览

| 分类 | 主要导出 | | --- | --- | | 库与推导 | createLibrary、findBranch、abilityInputs、needsPrompt、deriveState、deriveMoments、timelinesBefore、eventTypesOf、applyEffect | | 发动与候选 | canInvokeAbility、isClaimOnly、prefillSelections、resolveCandidates、resolveRegistrationOnlyCandidates、validateSelections、resolveReferences | | 拦截与事件 | planOutputInterceptions、buildProposal、findInterceptors、findEventReactions、findPhaseEndReactions、findImpairments、findInvocationImpairment | | 审计与裁定 | auditDecisions、isStorytellerRuling、findRulingAbility | | 条件上下文 | buildBaseContext、withInputs、decoratePlayer、evaluateCondition | | 提名与处决 | nominationVoteCount、votesRequired、executionBlock、findExecutionAbilities、isExecutionBranch | | 提示与展示 | activeHints、hintsForInvocation、displayReminders、grimoireSnapshot、describeGrimoire、describeOperation | | 夜序 | resolveNightOrder、toNightOrderEntries、nightAbilityIds、normalizeCharacterKey | | 工具 | heldAbilities、isEffectivelyAlive、offTableOwner、sourceCharacterOf、toSeatArray、toIdArray、isFirstNight |

完整列表见 src/index.ts,参数与返回值以类型声明和源码注释为准。

条件能读什么

条件用 @bct-app/game-dsl 求值,上下文由引擎构建。所有条件都能读:

  • effector:被求值能力的持有者(拦截时是拦截方,不是发起方)
  • players:全部玩家快照(含 characterKind、registrable、impaired、effectiveAlive、邻座等)
  • global:存活人数、今天处决次数等
  • currentTimeline、operations(all / today / tonight / yesterdayDay / yesterdayNight)

按场景额外有:inputs(本次选择)、invocation(本次发动是否仅宣称)、proposal(拦截时被拦的变更)、 event(事件响应时的事件)、candidate(候选过滤时的候选)、source 与 cast(持续效果的施放者与施放时机)、 grantedBy(认定规则所在状态的授予者)。

deriveState 重放时,每条操作按它发生那一刻的记录结算:currentTimeline、operations、global 只包含之前的时段 和所在时段里截至该顶层操作(含其拦截与事件响应)的记录,之后才记的操作看不到。所以只重放到某一刻和重放整局, 这条操作的结算结果一致。持续效果的存续条件与被动光环是在重放结束后按当下判定的,读的是整局记录。

开发

pnpm exec turbo run build --filter=@bct-app/game-engine...   # 连同 game-model、game-dsl 一起构建
pnpm --filter @bct-app/game-engine test:run

规则建模的约定见仓库根目录 AGENTS.md 的「领域模型与架构修改」:新增角色能力优先用通用能力模型表达,不写角色专属逻辑。