@bct-app/game-engine 0.1.18 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -20,20 +20,36 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ ACTIVE_TRIGGER_ACTIONS: () => ACTIVE_TRIGGER_ACTIONS,
24
+ DSL_CATALOG: () => DSL_CATALOG,
25
+ DSL_ITEM_FIELDS: () => DSL_ITEM_FIELDS,
26
+ DSL_OPERATORS: () => DSL_OPERATORS,
23
27
  adjustValueAsNumber: () => adjustValueAsNumber,
24
28
  adjustValueAsNumberArray: () => adjustValueAsNumberArray,
29
+ applyCountOperator: () => applyCountOperator,
25
30
  applyOperationToPlayers: () => applyOperationToPlayers,
31
+ applyOperator: () => applyOperator,
32
+ buildDslContextFromDerived: () => buildDslContextFromDerived,
33
+ buildDslContextFromLifetime: () => buildDslContextFromLifetime,
26
34
  buildPlayerSeatMap: () => buildPlayerSeatMap,
27
35
  canInvokeAbility: () => canInvokeAbility,
28
36
  copyPlayers: () => copyPlayers,
29
37
  deriveContext: () => deriveContext,
38
+ evalDslExpr: () => evalDslExpr,
39
+ evalDslExprWithBindings: () => evalExpr,
40
+ evaluatePath: () => evaluatePath,
41
+ extendBindings: () => extendBindings,
30
42
  getSourceValue: () => getSourceValue,
31
43
  getValueFromPayloads: () => getValueFromPayloads,
44
+ isActiveTriggerAction: () => isActiveTriggerAction,
32
45
  isNumberOrNumberArray: () => isNumberOrNumberArray,
33
46
  isPlayerReminderArray: () => isPlayerReminderArray,
34
47
  isReminder: () => isReminder,
35
48
  resolveSourceValue: () => resolveSourceValue,
36
49
  resolveTargetRef: () => resolveTargetRef,
50
+ rootBindings: () => rootBindings,
51
+ traceDslExpr: () => traceDslExpr,
52
+ traceExpr: () => traceExpr,
37
53
  transformEmptyArray: () => transformEmptyArray
38
54
  });
39
55
  module.exports = __toCommonJS(index_exports);
@@ -545,6 +561,521 @@ var effectHandlers = {
545
561
  STATUS_CHANGE: applyStatusChange
546
562
  };
547
563
 
564
+ // src/dsl/context.ts
565
+ var decoratePlayer = (player, characterMap, currentTurn) => {
566
+ const isDead = Boolean(player.isDead);
567
+ const retainsBypass = isDead && (player.retainsAbility === "ALIVENESS" || player.retainsAbility === "ALL");
568
+ const wasExecuted = isDead && player.deathCause === "EXECUTION";
569
+ return {
570
+ ...player,
571
+ characterKind: characterMap.get(player.characterId)?.kind,
572
+ reminderMarks: (player.reminders ?? []).map((r) => r.mark),
573
+ effectiveAlive: !isDead || retainsBypass,
574
+ diedThisTurn: isDead && typeof player.deathTurn === "number" && player.deathTurn === currentTurn,
575
+ wasExecuted,
576
+ wasExecutedToday: wasExecuted && player.deathTurn === currentTurn
577
+ };
578
+ };
579
+ var isExecutionTimeline = (tl) => {
580
+ if (!tl) return false;
581
+ return (tl.operations ?? []).some((op) => op.kind === "execution");
582
+ };
583
+ var buildDslContextFromDerived = (derived, payload = []) => {
584
+ const players = derived.players.map((p) => decoratePlayer(p, derived.characterMap, derived.currentTurn));
585
+ const effector = players.find((p) => p.seat === derived.effectorSeat);
586
+ const aliveCount = players.filter((p) => !p.isDead).length;
587
+ const deadCount = players.length - aliveCount;
588
+ const executedTodayCount = players.filter(
589
+ (p) => p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === derived.currentTurn
590
+ ).length;
591
+ const nominatedTodayCount = derived.todayNominations.length;
592
+ const playerDiedToday = players.some(
593
+ (p) => p.isDead && typeof p.deathTurn === "number" && p.deathTurn === derived.currentTurn
594
+ );
595
+ return {
596
+ effector,
597
+ players,
598
+ operations: {
599
+ all: derived.allOperations,
600
+ today: derived.todayOperations,
601
+ tonight: derived.tonightOperations,
602
+ yesterdayDay: derived.yesterdayDayOperations,
603
+ yesterdayNight: derived.yesterdayNightOperations
604
+ },
605
+ nominations: {
606
+ all: derived.allNominations,
607
+ today: derived.todayNominations,
608
+ yesterday: derived.yesterdayNominations
609
+ },
610
+ currentTimeline: {
611
+ turn: derived.currentTurn,
612
+ phase: derived.currentPhase,
613
+ time: derived.currentPhase === "NIGHT" ? "night" : derived.currentPhase === "DAY" ? "day" : void 0,
614
+ isFirstOfPhase: derived.isFirstOfPhase,
615
+ isExecution: (derived.currentPhase === "DAY" ? derived.todayOperations : derived.tonightOperations).some((op) => op.kind === "execution")
616
+ },
617
+ global: {
618
+ aliveCount,
619
+ deadCount,
620
+ executedTodayCount,
621
+ nominatedTodayCount,
622
+ executionHappenedToday: derived.todayOperations.some((op) => op.kind === "execution"),
623
+ playerDiedToday
624
+ },
625
+ payload
626
+ };
627
+ };
628
+ var buildDslContextFromLifetime = ({
629
+ snapshotSeatMap,
630
+ timelines,
631
+ nowTimelineIndex,
632
+ characterMap,
633
+ effector,
634
+ payloads
635
+ }) => {
636
+ const nowTl = timelines[nowTimelineIndex];
637
+ const currentTurn = nowTl?.turn ?? -1;
638
+ const currentTime = nowTl?.time;
639
+ const currentPhase = currentTime === "night" ? "NIGHT" : currentTime === "day" ? "DAY" : "NIGHT";
640
+ const playersRaw = snapshotSeatMap ? [...snapshotSeatMap.values()] : [];
641
+ const players = playersRaw.map((p) => decoratePlayer(p, characterMap, currentTurn));
642
+ const effectorDecorated = effector ? players.find((p) => p.seat === effector.seat) ?? decoratePlayer(effector, characterMap, currentTurn) : void 0;
643
+ const todayOps = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "day").flatMap((tl) => tl.operations ?? []);
644
+ const tonightOps = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "night").flatMap((tl) => tl.operations ?? []);
645
+ const yesterdayDayOps = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "day").flatMap((tl) => tl.operations ?? []);
646
+ const yesterdayNightOps = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "night").flatMap((tl) => tl.operations ?? []);
647
+ const allOps = timelines.flatMap((tl) => tl.operations ?? []);
648
+ const todayNoms = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee })));
649
+ const yesterdayNoms = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee })));
650
+ const allNoms = timelines.filter((tl) => tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({
651
+ nominator: n.nominator,
652
+ nominee: n.nominee,
653
+ turn: tl.turn
654
+ })));
655
+ const aliveCount = players.filter((p) => !p.isDead).length;
656
+ const deadCount = players.length - aliveCount;
657
+ const executedTodayCount = players.filter(
658
+ (p) => p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === currentTurn
659
+ ).length;
660
+ const playerDiedToday = timelines.some((tl) => {
661
+ if (tl.turn !== currentTurn || tl.time !== "day") return false;
662
+ return (tl.operations ?? []).some((op) => {
663
+ const kind = op.kind;
664
+ return kind === "execution" || kind === "death";
665
+ });
666
+ });
667
+ return {
668
+ effector: effectorDecorated,
669
+ players,
670
+ operations: {
671
+ all: allOps,
672
+ today: todayOps,
673
+ tonight: tonightOps,
674
+ yesterdayDay: yesterdayDayOps,
675
+ yesterdayNight: yesterdayNightOps
676
+ },
677
+ nominations: {
678
+ all: allNoms,
679
+ today: todayNoms,
680
+ yesterday: yesterdayNoms
681
+ },
682
+ currentTimeline: {
683
+ turn: currentTurn,
684
+ phase: currentPhase,
685
+ time: currentTime,
686
+ isFirstOfPhase: false,
687
+ isExecution: isExecutionTimeline(nowTl)
688
+ },
689
+ global: {
690
+ aliveCount,
691
+ deadCount,
692
+ executedTodayCount,
693
+ nominatedTodayCount: todayNoms.length,
694
+ executionHappenedToday: timelines.some(
695
+ (tl) => tl.turn === currentTurn && tl.time === "day" && isExecutionTimeline(tl)
696
+ ),
697
+ playerDiedToday
698
+ },
699
+ payload: payloads
700
+ };
701
+ };
702
+
703
+ // src/dsl/bindings.ts
704
+ var rootBindings = (ctx) => ({
705
+ effector: ctx.effector,
706
+ players: ctx.players,
707
+ operations: ctx.operations,
708
+ nominations: ctx.nominations,
709
+ currentTimeline: ctx.currentTimeline,
710
+ global: ctx.global,
711
+ payload: ctx.payload
712
+ });
713
+ var extendBindings = (parent, name, value) => ({
714
+ ...parent,
715
+ [name]: value
716
+ });
717
+
718
+ // src/dsl/operators.ts
719
+ var deepEqual = (a, b) => {
720
+ if (a === b) return true;
721
+ if (Array.isArray(a) && Array.isArray(b)) {
722
+ if (a.length !== b.length) return false;
723
+ for (let i = 0; i < a.length; i++) {
724
+ if (!deepEqual(a[i], b[i])) return false;
725
+ }
726
+ return true;
727
+ }
728
+ return false;
729
+ };
730
+ var asNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
731
+ var compareOrder = (lhs, rhs, cmp) => {
732
+ const l = asNumber(lhs);
733
+ const r = asNumber(rhs);
734
+ if (l === void 0 || r === void 0) return false;
735
+ return cmp(l, r);
736
+ };
737
+ var applyOperator = (op, lhs, rhs) => {
738
+ switch (op) {
739
+ case "EQ":
740
+ return deepEqual(lhs, rhs);
741
+ case "NE":
742
+ return !deepEqual(lhs, rhs);
743
+ case "LT":
744
+ return compareOrder(lhs, rhs, (l, r) => l < r);
745
+ case "LTE":
746
+ return compareOrder(lhs, rhs, (l, r) => l <= r);
747
+ case "GT":
748
+ return compareOrder(lhs, rhs, (l, r) => l > r);
749
+ case "GTE":
750
+ return compareOrder(lhs, rhs, (l, r) => l >= r);
751
+ case "IN":
752
+ return Array.isArray(rhs) && rhs.some((v) => deepEqual(v, lhs));
753
+ case "NOT_IN":
754
+ return Array.isArray(rhs) && !rhs.some((v) => deepEqual(v, lhs));
755
+ case "CONTAINS":
756
+ if (Array.isArray(lhs)) return lhs.some((v) => deepEqual(v, rhs));
757
+ if (typeof lhs === "string") return typeof rhs === "string" && lhs.includes(rhs);
758
+ return false;
759
+ case "MATCHES":
760
+ if (typeof lhs !== "string" || typeof rhs !== "string") return false;
761
+ try {
762
+ return new RegExp(rhs).test(lhs);
763
+ } catch {
764
+ return false;
765
+ }
766
+ case "EXISTS":
767
+ return lhs !== void 0 && lhs !== null;
768
+ case "TRUTHY":
769
+ return Boolean(lhs);
770
+ }
771
+ };
772
+ var applyCountOperator = (op, actual, expected) => {
773
+ switch (op) {
774
+ case "EQ":
775
+ return actual === expected;
776
+ case "NE":
777
+ return actual !== expected;
778
+ case "LT":
779
+ return actual < expected;
780
+ case "LTE":
781
+ return actual <= expected;
782
+ case "GT":
783
+ return actual > expected;
784
+ case "GTE":
785
+ return actual >= expected;
786
+ }
787
+ };
788
+
789
+ // src/dsl/path.ts
790
+ var MAX_STEPS = 32;
791
+ var stepInto = (current, step) => {
792
+ if (current === void 0 || current === null) return void 0;
793
+ if (step.kind === "field") {
794
+ if (typeof current !== "object") return void 0;
795
+ const record = current;
796
+ return record[step.name];
797
+ }
798
+ if (step.kind === "index") {
799
+ if (!Array.isArray(current)) return void 0;
800
+ return current[step.value];
801
+ }
802
+ return void 0;
803
+ };
804
+ var evaluatePath = (path, bindings) => {
805
+ if (path.steps.length > MAX_STEPS) return void 0;
806
+ let current = Object.prototype.hasOwnProperty.call(bindings, path.root) ? bindings[path.root] : void 0;
807
+ for (const step of path.steps) {
808
+ current = stepInto(current, step);
809
+ if (current === void 0) return void 0;
810
+ }
811
+ return current;
812
+ };
813
+
814
+ // src/dsl/eval.ts
815
+ var MAX_EXPR_DEPTH = 16;
816
+ var MAX_QUANTIFIER_SIZE = 256;
817
+ var resolveValue = (value, bindings) => {
818
+ if (!value) return void 0;
819
+ if (value.kind === "LITERAL") return value.value;
820
+ return evaluatePath(value.path, bindings);
821
+ };
822
+ var evalAtom = (atom, bindings, depth) => {
823
+ if (atom.type === "PATH") {
824
+ const lhs = evaluatePath(atom.path, bindings);
825
+ const rhs = resolveValue(atom.value, bindings);
826
+ return applyOperator(atom.op, lhs, rhs);
827
+ }
828
+ const source = evaluatePath(atom.source, bindings);
829
+ if (!Array.isArray(source)) {
830
+ return atom.type === "FORALL";
831
+ }
832
+ if (source.length > MAX_QUANTIFIER_SIZE) return false;
833
+ if (atom.type === "EXISTS") {
834
+ return source.some(
835
+ (item) => evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)
836
+ );
837
+ }
838
+ if (atom.type === "FORALL") {
839
+ return source.every(
840
+ (item) => evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)
841
+ );
842
+ }
843
+ let count = 0;
844
+ for (const item of source) {
845
+ if (!atom.where || evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)) {
846
+ count += 1;
847
+ }
848
+ }
849
+ return applyCountOperator(atom.op, count, atom.value);
850
+ };
851
+ var evalExpr = (expr, bindings, depth = 0) => {
852
+ if (depth > MAX_EXPR_DEPTH) return false;
853
+ switch (expr.op) {
854
+ case "AND":
855
+ return expr.children.every((child) => evalExpr(child, bindings, depth + 1));
856
+ case "OR":
857
+ return expr.children.some((child) => evalExpr(child, bindings, depth + 1));
858
+ case "NOT":
859
+ return !evalExpr(expr.child, bindings, depth + 1);
860
+ case "ATOM":
861
+ return evalAtom(expr.atom, bindings, depth + 1);
862
+ }
863
+ };
864
+ var evalDslExpr = (expr, ctx) => evalExpr(expr, rootBindings(ctx), 0);
865
+
866
+ // src/dsl/catalog.ts
867
+ var PLAYER_FIELDS = [
868
+ { name: "seat", type: "number", label: "\u5EA7\u4F4D\u53F7" },
869
+ { name: "alignment", type: "string", label: "\u9635\u8425", description: "GOOD / EVIL" },
870
+ { name: "isDead", type: "boolean", label: "\u662F\u5426\u6B7B\u4EA1" },
871
+ { name: "effectiveAlive", type: "boolean", label: "\u6709\u6548\u5B58\u6D3B", description: "\u8003\u8651 retainsAbility\uFF08ALIVENESS/ALL\uFF09\u540E\u662F\u5426\u6309\u5B58\u6D3B\u5904\u7406" },
872
+ { name: "diedThisTurn", type: "boolean", label: "\u672C\u56DE\u5408\u6B7B\u4EA1" },
873
+ { name: "wasExecuted", type: "boolean", label: "\u66FE\u88AB\u5904\u51B3" },
874
+ { name: "wasExecutedToday", type: "boolean", label: "\u4ECA\u65E5\u88AB\u5904\u51B3" },
875
+ { name: "characterId", type: "string", label: "\u89D2\u8272 ID" },
876
+ { name: "characterKind", type: "string", label: "\u89D2\u8272\u7C7B\u578B", description: "Townsfolk / Outsiders / Minions / Demons" },
877
+ { name: "deathCause", type: "string", label: "\u6B7B\u4EA1\u539F\u56E0", description: "EXECUTION / DEMON / ABILITY / STORYTELLER / OTHER" },
878
+ { name: "deathTurn", type: "number", label: "\u6B7B\u4EA1\u56DE\u5408" },
879
+ { name: "abilities", type: "string[]", label: "\u80FD\u529B ID \u5217\u8868" },
880
+ { name: "reminderMarks", type: "string[]", label: "\u63D0\u793A\u7269 mark \u5217\u8868" },
881
+ { name: "hasUsedDeadVote", type: "boolean", label: "\u662F\u5426\u4F7F\u7528\u8FC7\u6B7B\u4EA1\u6295\u7968" },
882
+ { name: "retainsAbility", type: "string", label: "\u80FD\u529B\u4FDD\u7559\u8303\u56F4" }
883
+ ];
884
+ var OPERATION_FIELDS = [
885
+ { name: "effector", type: "number", label: "\u65BD\u52A8\u8005\u5EA7\u4F4D" },
886
+ { name: "abilityId", type: "string", label: "\u80FD\u529B ID" },
887
+ { name: "kind", type: "string", label: "\u64CD\u4F5C\u79CD\u7C7B" },
888
+ { name: "payloads", type: "unknown[]", label: "Payload \u5217\u8868" }
889
+ ];
890
+ var NOMINATION_FIELDS = [
891
+ { name: "nominator", type: "number", label: "\u63D0\u540D\u8005\u5EA7\u4F4D" },
892
+ { name: "nominee", type: "number", label: "\u88AB\u63D0\u540D\u8005\u5EA7\u4F4D" },
893
+ { name: "turn", type: "number", label: "\u56DE\u5408", description: "\u4EC5 all \u96C6\u5408\u53EF\u7528" }
894
+ ];
895
+ var CURRENT_TIMELINE_FIELDS = [
896
+ { name: "turn", type: "number", label: "\u5F53\u524D\u56DE\u5408" },
897
+ { name: "phase", type: "string", label: "\u5F53\u524D\u9636\u6BB5", description: "NIGHT / DAY / DUSK / DAWN" },
898
+ { name: "time", type: "string", label: "\u5F53\u524D\u65F6\u6BB5", description: "night / day" },
899
+ { name: "isFirstOfPhase", type: "boolean", label: "\u662F\u5426\u672C\u9636\u6BB5\u9996\u6B21" },
900
+ { name: "isExecution", type: "boolean", label: "\u5F53\u524D\u65F6\u95F4\u7EBF\u542B\u5904\u51B3" }
901
+ ];
902
+ var GLOBAL_FIELDS = [
903
+ { name: "aliveCount", type: "number", label: "\u5B58\u6D3B\u73A9\u5BB6\u6570" },
904
+ { name: "deadCount", type: "number", label: "\u6B7B\u4EA1\u73A9\u5BB6\u6570" },
905
+ { name: "executedTodayCount", type: "number", label: "\u4ECA\u65E5\u88AB\u5904\u51B3\u4EBA\u6570" },
906
+ { name: "nominatedTodayCount", type: "number", label: "\u4ECA\u65E5\u63D0\u540D\u603B\u6570" },
907
+ { name: "executionHappenedToday", type: "boolean", label: "\u4ECA\u65E5\u662F\u5426\u5DF2\u53D1\u751F\u5904\u51B3" },
908
+ { name: "playerDiedToday", type: "boolean", label: "\u4ECA\u65E5\u662F\u5426\u6709\u73A9\u5BB6\u6B7B\u4EA1" }
909
+ ];
910
+ var OPERATIONS_FIELDS = [
911
+ { name: "all", type: "operation[]", label: "\u6240\u6709\u64CD\u4F5C" },
912
+ { name: "today", type: "operation[]", label: "\u4ECA\u65E5\u64CD\u4F5C" },
913
+ { name: "tonight", type: "operation[]", label: "\u4ECA\u591C\u64CD\u4F5C" },
914
+ { name: "yesterdayDay", type: "operation[]", label: "\u6628\u65E5\u767D\u5929\u64CD\u4F5C" },
915
+ { name: "yesterdayNight", type: "operation[]", label: "\u6628\u591C\u64CD\u4F5C" }
916
+ ];
917
+ var NOMINATIONS_FIELDS = [
918
+ { name: "all", type: "nomination[]", label: "\u6240\u6709\u63D0\u540D" },
919
+ { name: "today", type: "nomination[]", label: "\u4ECA\u65E5\u63D0\u540D" },
920
+ { name: "yesterday", type: "nomination[]", label: "\u6628\u65E5\u63D0\u540D" }
921
+ ];
922
+ var DSL_CATALOG = [
923
+ {
924
+ name: "effector",
925
+ type: "player",
926
+ label: "\u80FD\u529B\u53D1\u52A8\u8005",
927
+ description: "\u5F53\u524D\u8BC4\u4F30\u65F6\u7684\u80FD\u529B\u65BD\u653E\u8005\u5FEB\u7167",
928
+ fields: PLAYER_FIELDS
929
+ },
930
+ {
931
+ name: "players",
932
+ type: "player[]",
933
+ label: "\u5168\u90E8\u73A9\u5BB6",
934
+ description: "\u5F53\u524D\u5FEB\u7167\u4E0B\u6240\u6709\u73A9\u5BB6\uFF08\u5E94\u914D\u5408 EXISTS / FORALL / COUNT \u4F7F\u7528\uFF09"
935
+ },
936
+ {
937
+ name: "operations",
938
+ type: "object",
939
+ label: "\u5386\u53F2\u64CD\u4F5C\u96C6\u5408",
940
+ fields: OPERATIONS_FIELDS
941
+ },
942
+ {
943
+ name: "nominations",
944
+ type: "object",
945
+ label: "\u5386\u53F2\u63D0\u540D\u96C6\u5408",
946
+ fields: NOMINATIONS_FIELDS
947
+ },
948
+ {
949
+ name: "currentTimeline",
950
+ type: "object",
951
+ label: "\u5F53\u524D\u65F6\u95F4\u7EBF",
952
+ fields: CURRENT_TIMELINE_FIELDS
953
+ },
954
+ {
955
+ name: "global",
956
+ type: "object",
957
+ label: "\u5168\u5C40\u805A\u5408",
958
+ fields: GLOBAL_FIELDS
959
+ },
960
+ {
961
+ name: "payload",
962
+ type: "unknown[]",
963
+ label: "\u80FD\u529B Payload \u6570\u7EC4",
964
+ description: "\u5F53\u524D\u8C03\u7528 / \u64CD\u4F5C\u7684 payload \u539F\u59CB\u6570\u7EC4"
965
+ }
966
+ ];
967
+ var DSL_ITEM_FIELDS = {
968
+ player: PLAYER_FIELDS,
969
+ operation: OPERATION_FIELDS,
970
+ nomination: NOMINATION_FIELDS
971
+ };
972
+ var DSL_OPERATORS = [
973
+ { value: "EQ", label: "\u7B49\u4E8E", arity: "binary" },
974
+ { value: "NE", label: "\u4E0D\u7B49\u4E8E", arity: "binary" },
975
+ { value: "LT", label: "<", arity: "binary" },
976
+ { value: "LTE", label: "<=", arity: "binary" },
977
+ { value: "GT", label: ">", arity: "binary" },
978
+ { value: "GTE", label: ">=", arity: "binary" },
979
+ { value: "IN", label: "\u5C5E\u4E8E", arity: "binary" },
980
+ { value: "NOT_IN", label: "\u4E0D\u5C5E\u4E8E", arity: "binary" },
981
+ { value: "CONTAINS", label: "\u5305\u542B", arity: "binary" },
982
+ { value: "MATCHES", label: "\u6B63\u5219\u5339\u914D", arity: "binary" },
983
+ { value: "EXISTS", label: "\u5B58\u5728", arity: "unary" },
984
+ { value: "TRUTHY", label: "\u4E3A\u771F", arity: "unary" }
985
+ ];
986
+
987
+ // src/dsl/trace.ts
988
+ var MAX_QUANTIFIER_SIZE2 = 256;
989
+ var renderPath = (p) => p.steps.reduce((acc, s) => acc + (s.kind === "field" ? `.${s.name}` : `[${s.value}]`), p.root);
990
+ var resolveValue2 = (value, bindings) => {
991
+ if (!value) return void 0;
992
+ if (value.kind === "LITERAL") return value.value;
993
+ return evaluatePath(value.path, bindings);
994
+ };
995
+ var renderValue = (value, bindings) => {
996
+ if (!value) return void 0;
997
+ if (value.kind === "LITERAL") return value.value;
998
+ return { ref: renderPath(value.path), resolved: evaluatePath(value.path, bindings) };
999
+ };
1000
+ var traceAtom = (atom, bindings) => {
1001
+ if (atom.type === "PATH") {
1002
+ const lhs = evaluatePath(atom.path, bindings);
1003
+ const rhs = resolveValue2(atom.value, bindings);
1004
+ const ok2 = applyOperator(atom.op, lhs, rhs);
1005
+ return {
1006
+ op: "ATOM",
1007
+ atomType: "PATH",
1008
+ ok: ok2,
1009
+ path: renderPath(atom.path),
1010
+ operator: atom.op,
1011
+ lhs,
1012
+ rhs: renderValue(atom.value, bindings)
1013
+ };
1014
+ }
1015
+ const source = evaluatePath(atom.source, bindings);
1016
+ const sourceArr = Array.isArray(source) ? source : [];
1017
+ const sizeCapped = sourceArr.length > MAX_QUANTIFIER_SIZE2;
1018
+ let matched = 0;
1019
+ if (!sizeCapped) {
1020
+ for (const item of sourceArr) {
1021
+ const childBindings = extendBindings(bindings, atom.as, item);
1022
+ if (!atom.where || traceExprOk(atom.where, childBindings)) matched += 1;
1023
+ }
1024
+ }
1025
+ if (atom.type === "COUNT") {
1026
+ const ok2 = !sizeCapped && applyCountOperator(atom.op, matched, atom.value);
1027
+ return {
1028
+ op: "ATOM",
1029
+ atomType: "COUNT",
1030
+ ok: ok2,
1031
+ source: renderPath(atom.source),
1032
+ size: sourceArr.length,
1033
+ matched,
1034
+ operator: atom.op,
1035
+ expected: atom.value,
1036
+ bindings: atom.as
1037
+ };
1038
+ }
1039
+ const ok = atom.type === "EXISTS" ? matched > 0 : !sizeCapped && (sourceArr.length === 0 || matched === sourceArr.length);
1040
+ return {
1041
+ op: "ATOM",
1042
+ atomType: atom.type,
1043
+ ok,
1044
+ source: renderPath(atom.source),
1045
+ size: sourceArr.length,
1046
+ matched,
1047
+ bindings: atom.as
1048
+ };
1049
+ };
1050
+ var traceExprOk = (expr, bindings) => {
1051
+ switch (expr.op) {
1052
+ case "AND":
1053
+ return expr.children.every((c) => traceExprOk(c, bindings));
1054
+ case "OR":
1055
+ return expr.children.some((c) => traceExprOk(c, bindings));
1056
+ case "NOT":
1057
+ return !traceExprOk(expr.child, bindings);
1058
+ case "ATOM":
1059
+ return traceAtom(expr.atom, bindings).ok;
1060
+ }
1061
+ };
1062
+ var traceExpr = (expr, bindings) => {
1063
+ if (expr.op === "AND") {
1064
+ const children = expr.children.map((c) => traceExpr(c, bindings));
1065
+ return { op: "AND", ok: children.every((c) => c.ok), children };
1066
+ }
1067
+ if (expr.op === "OR") {
1068
+ const children = expr.children.map((c) => traceExpr(c, bindings));
1069
+ return { op: "OR", ok: children.some((c) => c.ok), children };
1070
+ }
1071
+ if (expr.op === "NOT") {
1072
+ const child = traceExpr(expr.child, bindings);
1073
+ return { op: "NOT", ok: !child.ok, child };
1074
+ }
1075
+ return traceAtom(expr.atom, bindings);
1076
+ };
1077
+ var traceDslExpr = (expr, ctx) => traceExpr(expr, rootBindings(ctx));
1078
+
548
1079
  // src/effects/resolve-targets.ts
549
1080
  var isPayloadRef = (value) => {
550
1081
  if (!value || typeof value !== "object") {
@@ -590,7 +1121,23 @@ var resolveSelectorScope = (dynamicTarget, payloads) => {
590
1121
  (value) => value === "BOTH_SIDES" || value === "LEFT_SIDE" || value === "RIGHT_SIDE"
591
1122
  ) ?? "BOTH_SIDES";
592
1123
  };
593
- var matchesCondition = (player, condition, payloads, characterMap) => {
1124
+ var matchesExprCondition = (player, condition, playerSeatMap, characterMap, effector, payloads) => {
1125
+ const dslCtx = buildDslContextFromLifetime({
1126
+ snapshotSeatMap: playerSeatMap,
1127
+ timelines: [],
1128
+ nowTimelineIndex: -1,
1129
+ characterMap,
1130
+ effector,
1131
+ payloads
1132
+ });
1133
+ const decoratedPlayer = dslCtx.players.find((p) => p.seat === player.seat);
1134
+ const bindings = extendBindings(rootBindings(dslCtx), "it", decoratedPlayer ?? player);
1135
+ return evalExpr(condition.expr, bindings);
1136
+ };
1137
+ var matchesCondition = (player, condition, payloads, characterMap, playerSeatMap, effector) => {
1138
+ if (condition.kind === "EXPR") {
1139
+ return matchesExprCondition(player, condition, playerSeatMap, characterMap, effector, payloads);
1140
+ }
594
1141
  if (condition.field === "IS_DEAD") {
595
1142
  const expected = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "boolean");
596
1143
  if (typeof expected !== "boolean") {
@@ -606,6 +1153,18 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
606
1153
  const expectedSeat = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "number");
607
1154
  return typeof expectedSeat === "number" && player.seat === expectedSeat;
608
1155
  }
1156
+ if (condition.field === "ALIGNMENT") {
1157
+ const alignment = player.alignment;
1158
+ if (!alignment) {
1159
+ return false;
1160
+ }
1161
+ if (condition.operator === "IN") {
1162
+ const expectedAlignments = resolveDynamicValue(condition.value, payloads, (value) => Array.isArray(value) && value.every((item) => typeof item === "string"));
1163
+ return Array.isArray(expectedAlignments) && expectedAlignments.includes(alignment);
1164
+ }
1165
+ const expectedAlignment = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
1166
+ return typeof expectedAlignment === "string" && alignment === expectedAlignment;
1167
+ }
609
1168
  const character = characterMap.get(player.characterId);
610
1169
  const kind = character?.kind;
611
1170
  if (!kind) {
@@ -618,15 +1177,17 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
618
1177
  const expectedKind = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
619
1178
  return typeof expectedKind === "string" && kind === expectedKind;
620
1179
  };
621
- var passesWhere = (player, dynamicTarget, payloads, characterMap) => {
1180
+ var passesWhere = (player, dynamicTarget, payloads, characterMap, playerSeatMap, effector) => {
622
1181
  const where = dynamicTarget.where;
623
1182
  const conditions = where?.conditions ?? [];
624
1183
  if (conditions.length === 0) return true;
625
1184
  const isAllMode = where?.mode !== "ANY";
626
- const results = conditions.map((condition) => matchesCondition(player, condition, payloads, characterMap));
1185
+ const results = conditions.map(
1186
+ (condition) => matchesCondition(player, condition, payloads, characterMap, playerSeatMap, effector)
1187
+ );
627
1188
  return isAllMode ? results.every(Boolean) : results.some(Boolean);
628
1189
  };
629
- var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap) => {
1190
+ var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector) => {
630
1191
  const scope = resolveSelectorScope(dynamicTarget, payloads);
631
1192
  if (!anchorSeat) return { left: [], right: [] };
632
1193
  const anchorIdx = sortedPlayers.findIndex((player) => player.seat === anchorSeat);
@@ -637,13 +1198,13 @@ var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, c
637
1198
  if (scope === "LEFT_SIDE" || scope === "BOTH_SIDES") {
638
1199
  for (let step = 1; step < total; step += 1) {
639
1200
  const player = sortedPlayers[(anchorIdx + step) % total];
640
- if (passesWhere(player, dynamicTarget, payloads, characterMap)) left.push(player);
1201
+ if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) left.push(player);
641
1202
  }
642
1203
  }
643
1204
  if (scope === "RIGHT_SIDE" || scope === "BOTH_SIDES") {
644
1205
  for (let step = 1; step < total; step += 1) {
645
1206
  const player = sortedPlayers[(anchorIdx - step + total) % total];
646
- if (passesWhere(player, dynamicTarget, payloads, characterMap)) right.push(player);
1207
+ if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) right.push(player);
647
1208
  }
648
1209
  }
649
1210
  return { left, right };
@@ -717,10 +1278,12 @@ var resolveEffectTargets = ({
717
1278
  const sortedPlayers = getSortedPlayers(playerSeatMap);
718
1279
  const anchorSeat = getAnchorSeat(dynamicTarget, payloads, effector);
719
1280
  if (!dynamicTarget.selector) {
720
- const matches = sortedPlayers.filter((player) => passesWhere(player, dynamicTarget, payloads, characterMap));
1281
+ const matches = sortedPlayers.filter(
1282
+ (player) => passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)
1283
+ );
721
1284
  return pickBalanced({ left: matches, right: [] }, dynamicTarget, payloads);
722
1285
  }
723
- const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap);
1286
+ const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector);
724
1287
  return pickBalanced(sides, dynamicTarget, payloads);
725
1288
  }
726
1289
  return [];
@@ -866,7 +1429,7 @@ var evalPlayerState = (atom, ctx) => {
866
1429
  const single = players[0];
867
1430
  return evalPlayerStateOnPlayer(single, atom, ctx);
868
1431
  };
869
- var isExecutionTimeline = (tl) => {
1432
+ var isExecutionTimeline2 = (tl) => {
870
1433
  if (!tl) return false;
871
1434
  return (tl.operations ?? []).some((op) => op.kind === "execution");
872
1435
  };
@@ -894,7 +1457,7 @@ var evalTime = (atom, ctx) => {
894
1457
  if (spec.event === "NEXT_NIGHT" && tl.time === "night") return false;
895
1458
  if (spec.event === "NEXT_DUSK" && prevTime === "day" && tl.time === "night") return false;
896
1459
  if (spec.event === "NEXT_DAWN" && prevTime === "night" && tl.time === "day") return false;
897
- if (spec.event === "NEXT_EXECUTION" && isExecutionTimeline(tl)) return false;
1460
+ if (spec.event === "NEXT_EXECUTION" && isExecutionTimeline2(tl)) return false;
898
1461
  prevTime = tl.time;
899
1462
  }
900
1463
  return true;
@@ -935,7 +1498,7 @@ var evalGameState = (atom, ctx) => {
935
1498
  if (spec.kind === "EXECUTION_HAPPENED_TODAY") {
936
1499
  const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
937
1500
  if (typeof turn !== "number") return false;
938
- return ctx.timelines.some((tl) => tl.turn === turn && tl.time === "day" && isExecutionTimeline(tl));
1501
+ return ctx.timelines.some((tl) => tl.turn === turn && tl.time === "day" && isExecutionTimeline2(tl));
939
1502
  }
940
1503
  if (spec.kind === "PLAYER_DIED_TODAY") {
941
1504
  const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
@@ -950,7 +1513,7 @@ var evalGameState = (atom, ctx) => {
950
1513
  }
951
1514
  return true;
952
1515
  };
953
- var evalAtom = (atom, ctx) => {
1516
+ var evalAtom2 = (atom, ctx) => {
954
1517
  switch (atom.type) {
955
1518
  case "TIME":
956
1519
  return evalTime(atom, ctx);
@@ -969,18 +1532,30 @@ var evalAtom = (atom, ctx) => {
969
1532
  target: ctx.target,
970
1533
  snapshotSeatMap: ctx.snapshotSeatMap
971
1534
  }) : false;
1535
+ case "DSL": {
1536
+ if (!ctx.snapshotSeatMap) return true;
1537
+ const dslCtx = buildDslContextFromLifetime({
1538
+ snapshotSeatMap: ctx.snapshotSeatMap,
1539
+ timelines: ctx.timelines,
1540
+ nowTimelineIndex: ctx.nowTimelineIndex,
1541
+ characterMap: ctx.characterMap,
1542
+ effector: ctx.effector,
1543
+ payloads: ctx.payloads
1544
+ });
1545
+ return evalDslExpr(atom.expr, dslCtx);
1546
+ }
972
1547
  }
973
1548
  };
974
- var evalExpr = (expr, ctx) => {
1549
+ var evalExpr2 = (expr, ctx) => {
975
1550
  switch (expr.op) {
976
1551
  case "AND":
977
- return expr.children.every((child) => evalExpr(child, ctx));
1552
+ return expr.children.every((child) => evalExpr2(child, ctx));
978
1553
  case "OR":
979
- return expr.children.some((child) => evalExpr(child, ctx));
1554
+ return expr.children.some((child) => evalExpr2(child, ctx));
980
1555
  case "NOT":
981
- return !evalExpr(expr.child, ctx);
1556
+ return !evalExpr2(expr.child, ctx);
982
1557
  case "ATOM":
983
- return evalAtom(expr.atom, ctx);
1558
+ return evalAtom2(expr.atom, ctx);
984
1559
  }
985
1560
  };
986
1561
  var expressionReferencesTarget = (expr) => {
@@ -1018,9 +1593,9 @@ var evaluateLifetime = ({
1018
1593
  const expr = lifetime.expr;
1019
1594
  const dependsOnTarget = expressionReferencesTarget(expr);
1020
1595
  if (!dependsOnTarget) {
1021
- return evalExpr(expr, { ...baseCtx, target: void 0 }) ? targets : [];
1596
+ return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
1022
1597
  }
1023
- return targets.filter((target) => evalExpr(expr, { ...baseCtx, target }));
1598
+ return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
1024
1599
  };
1025
1600
  var evaluatePrecondition = ({
1026
1601
  expr,
@@ -1046,9 +1621,9 @@ var evaluatePrecondition = ({
1046
1621
  };
1047
1622
  const dependsOnTarget = expressionReferencesTarget(expr);
1048
1623
  if (!dependsOnTarget) {
1049
- return evalExpr(expr, { ...baseCtx, target: void 0 }) ? targets : [];
1624
+ return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
1050
1625
  }
1051
- return targets.filter((target) => evalExpr(expr, { ...baseCtx, target }));
1626
+ return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
1052
1627
  };
1053
1628
 
1054
1629
  // src/apply-operation.ts
@@ -1370,6 +1945,12 @@ var deriveContext = ({
1370
1945
  const yesterdayDayOperations = timelines.filter((tl) => tl.turn === last.turn - 1 && tl.time === "day").flatMap((tl) => tl.operations ?? []);
1371
1946
  const yesterdayNightOperations = timelines.filter((tl) => tl.turn === last.turn - 1 && tl.time === "night").flatMap((tl) => tl.operations ?? []);
1372
1947
  const todayNominations = timelines.filter((tl) => tl.turn === last.turn && tl.time === "day").flatMap((tl) => tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee }));
1948
+ const yesterdayNominations = timelines.filter((tl) => tl.turn === last.turn - 1 && tl.time === "day").flatMap((tl) => tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee }));
1949
+ const allNominations = timelines.filter((tl) => tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({
1950
+ nominator: n.nominator,
1951
+ nominee: n.nominee,
1952
+ turn: tl.turn
1953
+ })));
1373
1954
  return {
1374
1955
  currentTurn: last.turn,
1375
1956
  currentPhase: TIME_TO_PHASE[last.time],
@@ -1381,6 +1962,8 @@ var deriveContext = ({
1381
1962
  yesterdayDayOperations,
1382
1963
  yesterdayNightOperations,
1383
1964
  todayNominations,
1965
+ yesterdayNominations,
1966
+ allNominations,
1384
1967
  effectorSeat: candidate.effector,
1385
1968
  effector,
1386
1969
  abilityId: candidate.abilityId,
@@ -1389,6 +1972,14 @@ var deriveContext = ({
1389
1972
  operationTimeMap
1390
1973
  };
1391
1974
  };
1975
+ var nominationsForActionScope = (scope, ctx) => {
1976
+ if (scope === "TONIGHT" || scope === "TODAY") return ctx.todayNominations;
1977
+ if (scope === "YESTERDAY") return ctx.yesterdayNominations;
1978
+ return ctx.allNominations.map((n) => ({ nominator: n.nominator, nominee: n.nominee }));
1979
+ };
1980
+ var extractPayloadSeats = (op) => (op.payloads ?? []).flatMap(
1981
+ (v) => typeof v === "number" ? [v] : Array.isArray(v) ? v.filter((x) => typeof x === "number") : []
1982
+ );
1392
1983
  var operationsForActionScope = (scope, ctx) => {
1393
1984
  if (scope === "TONIGHT") return ctx.tonightOperations;
1394
1985
  if (scope === "TODAY") return ctx.todayOperations;
@@ -1444,23 +2035,37 @@ var evalAtomAction = (atom, ctx) => {
1444
2035
  if (subjectSeats.size === 0) return `no players match subject ${atom.subject.ref}`;
1445
2036
  const actorSeats = atom.actor ? seatsMatchingTargetRef(atom.actor, ctx) : null;
1446
2037
  if (atom.action === "NOMINATED") {
1447
- const noms = ctx.todayNominations;
2038
+ const noms = nominationsForActionScope(atom.scope, ctx);
1448
2039
  const found = noms.some(
1449
2040
  (n) => subjectSeats.has(n.nominee) && (actorSeats === null || actorSeats.has(n.nominator))
1450
2041
  );
1451
- return found ? null : "no matching NOMINATED event in today";
2042
+ return found ? null : "no matching NOMINATED event in scope";
2043
+ }
2044
+ if (atom.action === "NOMINATES") {
2045
+ const noms = nominationsForActionScope(atom.scope, ctx);
2046
+ const found = noms.some(
2047
+ (n) => subjectSeats.has(n.nominator) && (actorSeats === null || actorSeats.has(n.nominee))
2048
+ );
2049
+ return found ? null : "no matching NOMINATES event in scope";
1452
2050
  }
1453
2051
  const scoped = operationsForActionScope(atom.scope, ctx);
1454
2052
  if (atom.action === "CHOSEN_AS_TARGET") {
1455
2053
  const found = scoped.some((op) => {
1456
2054
  if (actorSeats !== null && !actorSeats.has(op.effector)) return false;
1457
- const payloadSeats = (op.payloads ?? []).flatMap(
1458
- (v) => typeof v === "number" ? [v] : Array.isArray(v) ? v.filter((x) => typeof x === "number") : []
1459
- );
2055
+ const payloadSeats = extractPayloadSeats(op);
1460
2056
  return payloadSeats.some((s) => subjectSeats.has(s));
1461
2057
  });
1462
2058
  return found ? null : "no matching CHOSEN_AS_TARGET in scope";
1463
2059
  }
2060
+ if (atom.action === "CHOOSES_TARGET") {
2061
+ const found = scoped.some((op) => {
2062
+ if (!subjectSeats.has(op.effector)) return false;
2063
+ if (actorSeats === null) return true;
2064
+ const payloadSeats = extractPayloadSeats(op);
2065
+ return payloadSeats.some((s) => actorSeats.has(s));
2066
+ });
2067
+ return found ? null : "no matching CHOOSES_TARGET in scope";
2068
+ }
1464
2069
  const turnsInScope = (() => {
1465
2070
  if (atom.scope === "TONIGHT" || atom.scope === "TODAY") return [ctx.currentTurn];
1466
2071
  if (atom.scope === "YESTERDAY") return [ctx.currentTurn - 1];
@@ -1490,6 +2095,18 @@ var evalAtomAction = (atom, ctx) => {
1490
2095
  }
1491
2096
  return "no matching EXECUTED in scope";
1492
2097
  }
2098
+ if (atom.action === "EXECUTES") {
2099
+ const scopedNoms = atom.scope === "EVER" ? ctx.allNominations : atom.scope === "YESTERDAY" ? ctx.yesterdayNominations.map((n) => ({ ...n, turn: ctx.currentTurn - 1 })) : ctx.todayNominations.map((n) => ({ ...n, turn: ctx.currentTurn }));
2100
+ const found = scopedNoms.some((n) => {
2101
+ if (!subjectSeats.has(n.nominator)) return false;
2102
+ if (actorSeats !== null && !actorSeats.has(n.nominee)) return false;
2103
+ const executed = ctx.players.find(
2104
+ (p) => p.seat === n.nominee && p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === n.turn
2105
+ );
2106
+ return Boolean(executed);
2107
+ });
2108
+ return found ? null : "no matching EXECUTES in scope";
2109
+ }
1493
2110
  void scoped;
1494
2111
  return `unknown action ${atom.action}`;
1495
2112
  };
@@ -1526,7 +2143,7 @@ var evalAtomGlobalCount = (atom, ctx) => {
1526
2143
  }
1527
2144
  return compareCount2(actual, atom.operator, atom.value) ? null : `global count ${atom.subject}=${actual} fails ${atom.operator} ${atom.value}`;
1528
2145
  };
1529
- var evalAtom2 = (atom, ctx, customResolver) => {
2146
+ var evalAtom3 = (atom, ctx, customResolver) => {
1530
2147
  if (atom.type === "TIME") return evalAtomTime(atom, ctx);
1531
2148
  if (atom.type === "STATE") return evalAtomState(atom, ctx);
1532
2149
  if (atom.type === "ACTION") return evalAtomAction(atom, ctx);
@@ -1536,12 +2153,16 @@ var evalAtom2 = (atom, ctx, customResolver) => {
1536
2153
  if (!customResolver) return `no custom resolver registered for ${atom.resolverId}`;
1537
2154
  return customResolver({ resolverId: atom.resolverId, args: atom.args }, ctx) ? null : `custom resolver ${atom.resolverId} returned false`;
1538
2155
  }
2156
+ if (atom.type === "DSL") {
2157
+ const dslCtx = buildDslContextFromDerived(ctx);
2158
+ return evalDslExpr(atom.expr, dslCtx) ? null : "DSL expression returned false";
2159
+ }
1539
2160
  return "unknown atom";
1540
2161
  };
1541
- var evalExpr2 = (expr, ctx, customResolver) => {
2162
+ var evalExpr3 = (expr, ctx, customResolver) => {
1542
2163
  if (expr.op === "AND") {
1543
2164
  for (const child of expr.children) {
1544
- const reason = evalExpr2(child, ctx, customResolver);
2165
+ const reason = evalExpr3(child, ctx, customResolver);
1545
2166
  if (reason) return reason;
1546
2167
  }
1547
2168
  return null;
@@ -1549,17 +2170,17 @@ var evalExpr2 = (expr, ctx, customResolver) => {
1549
2170
  if (expr.op === "OR") {
1550
2171
  const reasons = [];
1551
2172
  for (const child of expr.children) {
1552
- const reason = evalExpr2(child, ctx, customResolver);
2173
+ const reason = evalExpr3(child, ctx, customResolver);
1553
2174
  if (!reason) return null;
1554
2175
  reasons.push(reason);
1555
2176
  }
1556
2177
  return `OR: all branches failed (${reasons.join("; ")})`;
1557
2178
  }
1558
2179
  if (expr.op === "NOT") {
1559
- const reason = evalExpr2(expr.child, ctx, customResolver);
2180
+ const reason = evalExpr3(expr.child, ctx, customResolver);
1560
2181
  return reason ? null : "NOT: inner expression was true";
1561
2182
  }
1562
- return evalAtom2(expr.atom, ctx, customResolver);
2183
+ return evalAtom3(expr.atom, ctx, customResolver);
1563
2184
  };
1564
2185
  var canInvokeAbility = ({
1565
2186
  ability,
@@ -1579,24 +2200,48 @@ var canInvokeAbility = ({
1579
2200
  if (derived.effector?.retainsAbility === "ALL") {
1580
2201
  return { allowed: true };
1581
2202
  }
1582
- const reason = evalExpr2(window, derived, customResolver);
2203
+ const reason = evalExpr3(window, derived, customResolver);
1583
2204
  return reason ? { allowed: false, reason } : { allowed: true };
1584
2205
  };
2206
+
2207
+ // src/trigger-action.ts
2208
+ var ACTIVE_TRIGGER_ACTIONS = /* @__PURE__ */ new Set([
2209
+ "NOMINATES",
2210
+ "CHOOSES_TARGET",
2211
+ "EXECUTES"
2212
+ ]);
2213
+ var isActiveTriggerAction = (action) => ACTIVE_TRIGGER_ACTIONS.has(action);
1585
2214
  // Annotate the CommonJS export names for ESM import in node:
1586
2215
  0 && (module.exports = {
2216
+ ACTIVE_TRIGGER_ACTIONS,
2217
+ DSL_CATALOG,
2218
+ DSL_ITEM_FIELDS,
2219
+ DSL_OPERATORS,
1587
2220
  adjustValueAsNumber,
1588
2221
  adjustValueAsNumberArray,
2222
+ applyCountOperator,
1589
2223
  applyOperationToPlayers,
2224
+ applyOperator,
2225
+ buildDslContextFromDerived,
2226
+ buildDslContextFromLifetime,
1590
2227
  buildPlayerSeatMap,
1591
2228
  canInvokeAbility,
1592
2229
  copyPlayers,
1593
2230
  deriveContext,
2231
+ evalDslExpr,
2232
+ evalDslExprWithBindings,
2233
+ evaluatePath,
2234
+ extendBindings,
1594
2235
  getSourceValue,
1595
2236
  getValueFromPayloads,
2237
+ isActiveTriggerAction,
1596
2238
  isNumberOrNumberArray,
1597
2239
  isPlayerReminderArray,
1598
2240
  isReminder,
1599
2241
  resolveSourceValue,
1600
2242
  resolveTargetRef,
2243
+ rootBindings,
2244
+ traceDslExpr,
2245
+ traceExpr,
1601
2246
  transformEmptyArray
1602
2247
  });