@bct-app/game-engine 0.1.17 → 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") {
@@ -572,11 +1103,6 @@ var resolveDynamicValue = (value, payloads, isExpected) => {
572
1103
  var getSortedPlayers = (playerSeatMap) => {
573
1104
  return [...playerSeatMap.values()].sort((left, right) => left.seat - right.seat);
574
1105
  };
575
- var getCircularDistance = (anchorIdx, targetIdx, total) => {
576
- const clockwise = (targetIdx - anchorIdx + total) % total;
577
- const anticlockwise = (anchorIdx - targetIdx + total) % total;
578
- return Math.min(clockwise, anticlockwise);
579
- };
580
1106
  var getAnchorSeat = (dynamicTarget, payloads, effector) => {
581
1107
  const anchor = dynamicTarget.anchor;
582
1108
  if (!anchor || anchor.from === "EFFECTOR") {
@@ -595,43 +1121,23 @@ var resolveSelectorScope = (dynamicTarget, payloads) => {
595
1121
  (value) => value === "BOTH_SIDES" || value === "LEFT_SIDE" || value === "RIGHT_SIDE"
596
1122
  ) ?? "BOTH_SIDES";
597
1123
  };
598
- var getCandidatesBySelector = (dynamicTarget, sortedPlayers, anchorSeat, payloads) => {
599
- const selector = dynamicTarget.selector;
600
- if (!selector) {
601
- return sortedPlayers;
602
- }
603
- const scope = resolveSelectorScope(dynamicTarget, payloads);
604
- if (!anchorSeat) {
605
- return [];
606
- }
607
- const anchorIdx = sortedPlayers.findIndex((player) => player.seat === anchorSeat);
608
- if (anchorIdx < 0) {
609
- return [];
610
- }
611
- const total = sortedPlayers.length;
612
- if (scope === "LEFT_SIDE") {
613
- const left2 = [];
614
- for (let step = 1; step < total; step += 1) {
615
- left2.push(sortedPlayers[(anchorIdx + step) % total]);
616
- }
617
- return left2;
618
- }
619
- if (scope === "RIGHT_SIDE") {
620
- const right2 = [];
621
- for (let step = 1; step < total; step += 1) {
622
- right2.push(sortedPlayers[(anchorIdx - step + total) % total]);
623
- }
624
- return right2;
625
- }
626
- const left = [];
627
- const right = [];
628
- for (let step = 1; step < total; step += 1) {
629
- left.push(sortedPlayers[(anchorIdx + step) % total]);
630
- right.push(sortedPlayers[(anchorIdx - step + total) % total]);
631
- }
632
- return [...left, ...right];
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);
633
1136
  };
634
- var matchesCondition = (player, condition, payloads, characterMap) => {
1137
+ var matchesCondition = (player, condition, payloads, characterMap, playerSeatMap, effector) => {
1138
+ if (condition.kind === "EXPR") {
1139
+ return matchesExprCondition(player, condition, playerSeatMap, characterMap, effector, payloads);
1140
+ }
635
1141
  if (condition.field === "IS_DEAD") {
636
1142
  const expected = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "boolean");
637
1143
  if (typeof expected !== "boolean") {
@@ -647,6 +1153,18 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
647
1153
  const expectedSeat = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "number");
648
1154
  return typeof expectedSeat === "number" && player.seat === expectedSeat;
649
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
+ }
650
1168
  const character = characterMap.get(player.characterId);
651
1169
  const kind = character?.kind;
652
1170
  if (!kind) {
@@ -659,55 +1177,74 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
659
1177
  const expectedKind = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
660
1178
  return typeof expectedKind === "string" && kind === expectedKind;
661
1179
  };
662
- var filterByWhere = (candidates, dynamicTarget, payloads, characterMap) => {
1180
+ var passesWhere = (player, dynamicTarget, payloads, characterMap, playerSeatMap, effector) => {
663
1181
  const where = dynamicTarget.where;
664
1182
  const conditions = where?.conditions ?? [];
665
- if (conditions.length === 0) {
666
- return candidates;
667
- }
1183
+ if (conditions.length === 0) return true;
668
1184
  const isAllMode = where?.mode !== "ANY";
669
- return candidates.filter((player) => {
670
- const results = conditions.map((condition) => matchesCondition(player, condition, payloads, characterMap));
671
- return isAllMode ? results.every(Boolean) : results.some(Boolean);
672
- });
1185
+ const results = conditions.map(
1186
+ (condition) => matchesCondition(player, condition, payloads, characterMap, playerSeatMap, effector)
1187
+ );
1188
+ return isAllMode ? results.every(Boolean) : results.some(Boolean);
673
1189
  };
674
- var applySort = (candidates, dynamicTarget, payloads, sortedPlayers, anchorSeat) => {
1190
+ var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector) => {
675
1191
  const scope = resolveSelectorScope(dynamicTarget, payloads);
676
- if (scope !== "BOTH_SIDES") {
677
- return [...candidates];
678
- }
679
- if (!anchorSeat || sortedPlayers.length <= 1) {
680
- return [...candidates].sort((left, right) => left.seat - right.seat);
681
- }
682
- const seatIndexMap = new Map(sortedPlayers.map((player, idx) => [player.seat, idx]));
683
- const anchorIdx = seatIndexMap.get(anchorSeat);
684
- if (typeof anchorIdx !== "number") {
685
- return [...candidates].sort((left, right) => left.seat - right.seat);
686
- }
687
- return [...candidates].sort((left, right) => {
688
- const leftIdx = seatIndexMap.get(left.seat);
689
- const rightIdx = seatIndexMap.get(right.seat);
690
- if (typeof leftIdx !== "number" || typeof rightIdx !== "number") {
691
- return left.seat - right.seat;
1192
+ if (!anchorSeat) return { left: [], right: [] };
1193
+ const anchorIdx = sortedPlayers.findIndex((player) => player.seat === anchorSeat);
1194
+ if (anchorIdx < 0) return { left: [], right: [] };
1195
+ const total = sortedPlayers.length;
1196
+ const left = [];
1197
+ const right = [];
1198
+ if (scope === "LEFT_SIDE" || scope === "BOTH_SIDES") {
1199
+ for (let step = 1; step < total; step += 1) {
1200
+ const player = sortedPlayers[(anchorIdx + step) % total];
1201
+ if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) left.push(player);
692
1202
  }
693
- const leftDistance = getCircularDistance(anchorIdx, leftIdx, sortedPlayers.length);
694
- const rightDistance = getCircularDistance(anchorIdx, rightIdx, sortedPlayers.length);
695
- if (leftDistance !== rightDistance) {
696
- return leftDistance - rightDistance;
1203
+ }
1204
+ if (scope === "RIGHT_SIDE" || scope === "BOTH_SIDES") {
1205
+ for (let step = 1; step < total; step += 1) {
1206
+ const player = sortedPlayers[(anchorIdx - step + total) % total];
1207
+ if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) right.push(player);
697
1208
  }
698
- return left.seat - right.seat;
699
- });
1209
+ }
1210
+ return { left, right };
700
1211
  };
701
- var pickTargets = (sortedCandidates, dynamicTarget, payloads) => {
702
- const dedupedCandidates = [...new Map(sortedCandidates.map((player) => [player.seat, player])).values()];
1212
+ var pickBalanced = (sides, dynamicTarget, payloads) => {
703
1213
  const configuredLimit = resolveDynamicValue(dynamicTarget.limit, payloads, (value) => typeof value === "number");
1214
+ const seen = /* @__PURE__ */ new Set();
1215
+ const out = [];
1216
+ const pushUnique = (player) => {
1217
+ if (!player || seen.has(player.seat)) return false;
1218
+ seen.add(player.seat);
1219
+ out.push(player);
1220
+ return true;
1221
+ };
704
1222
  if (typeof configuredLimit === "undefined") {
705
- return dedupedCandidates;
706
- }
707
- if (configuredLimit <= 0) {
708
- return [];
1223
+ sides.left.forEach(pushUnique);
1224
+ sides.right.forEach(pushUnique);
1225
+ return out;
1226
+ }
1227
+ if (configuredLimit <= 0) return [];
1228
+ let li = 0;
1229
+ let ri = 0;
1230
+ let preferLeft = true;
1231
+ while (out.length < configuredLimit && (li < sides.left.length || ri < sides.right.length)) {
1232
+ if (preferLeft && li < sides.left.length) {
1233
+ if (pushUnique(sides.left[li])) li += 1;
1234
+ else li += 1;
1235
+ } else if (!preferLeft && ri < sides.right.length) {
1236
+ if (pushUnique(sides.right[ri])) ri += 1;
1237
+ else ri += 1;
1238
+ } else if (li < sides.left.length) {
1239
+ if (pushUnique(sides.left[li])) li += 1;
1240
+ else li += 1;
1241
+ } else if (ri < sides.right.length) {
1242
+ if (pushUnique(sides.right[ri])) ri += 1;
1243
+ else ri += 1;
1244
+ }
1245
+ preferLeft = !preferLeft;
709
1246
  }
710
- return dedupedCandidates.slice(0, configuredLimit);
1247
+ return out;
711
1248
  };
712
1249
  var resolveEffectTargets = ({
713
1250
  effect,
@@ -740,10 +1277,14 @@ var resolveEffectTargets = ({
740
1277
  const dynamicTarget = effect.target;
741
1278
  const sortedPlayers = getSortedPlayers(playerSeatMap);
742
1279
  const anchorSeat = getAnchorSeat(dynamicTarget, payloads, effector);
743
- const candidates = getCandidatesBySelector(dynamicTarget, sortedPlayers, anchorSeat, payloads);
744
- const filtered = filterByWhere(candidates, dynamicTarget, payloads, characterMap);
745
- const ordered = applySort(filtered, dynamicTarget, payloads, sortedPlayers, anchorSeat);
746
- return pickTargets(ordered, dynamicTarget, payloads);
1280
+ if (!dynamicTarget.selector) {
1281
+ const matches = sortedPlayers.filter(
1282
+ (player) => passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)
1283
+ );
1284
+ return pickBalanced({ left: matches, right: [] }, dynamicTarget, payloads);
1285
+ }
1286
+ const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector);
1287
+ return pickBalanced(sides, dynamicTarget, payloads);
747
1288
  }
748
1289
  return [];
749
1290
  };
@@ -888,7 +1429,7 @@ var evalPlayerState = (atom, ctx) => {
888
1429
  const single = players[0];
889
1430
  return evalPlayerStateOnPlayer(single, atom, ctx);
890
1431
  };
891
- var isExecutionTimeline = (tl) => {
1432
+ var isExecutionTimeline2 = (tl) => {
892
1433
  if (!tl) return false;
893
1434
  return (tl.operations ?? []).some((op) => op.kind === "execution");
894
1435
  };
@@ -916,7 +1457,7 @@ var evalTime = (atom, ctx) => {
916
1457
  if (spec.event === "NEXT_NIGHT" && tl.time === "night") return false;
917
1458
  if (spec.event === "NEXT_DUSK" && prevTime === "day" && tl.time === "night") return false;
918
1459
  if (spec.event === "NEXT_DAWN" && prevTime === "night" && tl.time === "day") return false;
919
- if (spec.event === "NEXT_EXECUTION" && isExecutionTimeline(tl)) return false;
1460
+ if (spec.event === "NEXT_EXECUTION" && isExecutionTimeline2(tl)) return false;
920
1461
  prevTime = tl.time;
921
1462
  }
922
1463
  return true;
@@ -957,7 +1498,7 @@ var evalGameState = (atom, ctx) => {
957
1498
  if (spec.kind === "EXECUTION_HAPPENED_TODAY") {
958
1499
  const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
959
1500
  if (typeof turn !== "number") return false;
960
- 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));
961
1502
  }
962
1503
  if (spec.kind === "PLAYER_DIED_TODAY") {
963
1504
  const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
@@ -972,7 +1513,7 @@ var evalGameState = (atom, ctx) => {
972
1513
  }
973
1514
  return true;
974
1515
  };
975
- var evalAtom = (atom, ctx) => {
1516
+ var evalAtom2 = (atom, ctx) => {
976
1517
  switch (atom.type) {
977
1518
  case "TIME":
978
1519
  return evalTime(atom, ctx);
@@ -991,18 +1532,30 @@ var evalAtom = (atom, ctx) => {
991
1532
  target: ctx.target,
992
1533
  snapshotSeatMap: ctx.snapshotSeatMap
993
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
+ }
994
1547
  }
995
1548
  };
996
- var evalExpr = (expr, ctx) => {
1549
+ var evalExpr2 = (expr, ctx) => {
997
1550
  switch (expr.op) {
998
1551
  case "AND":
999
- return expr.children.every((child) => evalExpr(child, ctx));
1552
+ return expr.children.every((child) => evalExpr2(child, ctx));
1000
1553
  case "OR":
1001
- return expr.children.some((child) => evalExpr(child, ctx));
1554
+ return expr.children.some((child) => evalExpr2(child, ctx));
1002
1555
  case "NOT":
1003
- return !evalExpr(expr.child, ctx);
1556
+ return !evalExpr2(expr.child, ctx);
1004
1557
  case "ATOM":
1005
- return evalAtom(expr.atom, ctx);
1558
+ return evalAtom2(expr.atom, ctx);
1006
1559
  }
1007
1560
  };
1008
1561
  var expressionReferencesTarget = (expr) => {
@@ -1040,9 +1593,9 @@ var evaluateLifetime = ({
1040
1593
  const expr = lifetime.expr;
1041
1594
  const dependsOnTarget = expressionReferencesTarget(expr);
1042
1595
  if (!dependsOnTarget) {
1043
- return evalExpr(expr, { ...baseCtx, target: void 0 }) ? targets : [];
1596
+ return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
1044
1597
  }
1045
- return targets.filter((target) => evalExpr(expr, { ...baseCtx, target }));
1598
+ return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
1046
1599
  };
1047
1600
  var evaluatePrecondition = ({
1048
1601
  expr,
@@ -1068,9 +1621,9 @@ var evaluatePrecondition = ({
1068
1621
  };
1069
1622
  const dependsOnTarget = expressionReferencesTarget(expr);
1070
1623
  if (!dependsOnTarget) {
1071
- return evalExpr(expr, { ...baseCtx, target: void 0 }) ? targets : [];
1624
+ return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
1072
1625
  }
1073
- return targets.filter((target) => evalExpr(expr, { ...baseCtx, target }));
1626
+ return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
1074
1627
  };
1075
1628
 
1076
1629
  // src/apply-operation.ts
@@ -1392,6 +1945,12 @@ var deriveContext = ({
1392
1945
  const yesterdayDayOperations = timelines.filter((tl) => tl.turn === last.turn - 1 && tl.time === "day").flatMap((tl) => tl.operations ?? []);
1393
1946
  const yesterdayNightOperations = timelines.filter((tl) => tl.turn === last.turn - 1 && tl.time === "night").flatMap((tl) => tl.operations ?? []);
1394
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
+ })));
1395
1954
  return {
1396
1955
  currentTurn: last.turn,
1397
1956
  currentPhase: TIME_TO_PHASE[last.time],
@@ -1403,6 +1962,8 @@ var deriveContext = ({
1403
1962
  yesterdayDayOperations,
1404
1963
  yesterdayNightOperations,
1405
1964
  todayNominations,
1965
+ yesterdayNominations,
1966
+ allNominations,
1406
1967
  effectorSeat: candidate.effector,
1407
1968
  effector,
1408
1969
  abilityId: candidate.abilityId,
@@ -1411,6 +1972,14 @@ var deriveContext = ({
1411
1972
  operationTimeMap
1412
1973
  };
1413
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
+ );
1414
1983
  var operationsForActionScope = (scope, ctx) => {
1415
1984
  if (scope === "TONIGHT") return ctx.tonightOperations;
1416
1985
  if (scope === "TODAY") return ctx.todayOperations;
@@ -1466,23 +2035,37 @@ var evalAtomAction = (atom, ctx) => {
1466
2035
  if (subjectSeats.size === 0) return `no players match subject ${atom.subject.ref}`;
1467
2036
  const actorSeats = atom.actor ? seatsMatchingTargetRef(atom.actor, ctx) : null;
1468
2037
  if (atom.action === "NOMINATED") {
1469
- const noms = ctx.todayNominations;
2038
+ const noms = nominationsForActionScope(atom.scope, ctx);
1470
2039
  const found = noms.some(
1471
2040
  (n) => subjectSeats.has(n.nominee) && (actorSeats === null || actorSeats.has(n.nominator))
1472
2041
  );
1473
- 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";
1474
2050
  }
1475
2051
  const scoped = operationsForActionScope(atom.scope, ctx);
1476
2052
  if (atom.action === "CHOSEN_AS_TARGET") {
1477
2053
  const found = scoped.some((op) => {
1478
2054
  if (actorSeats !== null && !actorSeats.has(op.effector)) return false;
1479
- const payloadSeats = (op.payloads ?? []).flatMap(
1480
- (v) => typeof v === "number" ? [v] : Array.isArray(v) ? v.filter((x) => typeof x === "number") : []
1481
- );
2055
+ const payloadSeats = extractPayloadSeats(op);
1482
2056
  return payloadSeats.some((s) => subjectSeats.has(s));
1483
2057
  });
1484
2058
  return found ? null : "no matching CHOSEN_AS_TARGET in scope";
1485
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
+ }
1486
2069
  const turnsInScope = (() => {
1487
2070
  if (atom.scope === "TONIGHT" || atom.scope === "TODAY") return [ctx.currentTurn];
1488
2071
  if (atom.scope === "YESTERDAY") return [ctx.currentTurn - 1];
@@ -1512,6 +2095,18 @@ var evalAtomAction = (atom, ctx) => {
1512
2095
  }
1513
2096
  return "no matching EXECUTED in scope";
1514
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
+ }
1515
2110
  void scoped;
1516
2111
  return `unknown action ${atom.action}`;
1517
2112
  };
@@ -1548,7 +2143,7 @@ var evalAtomGlobalCount = (atom, ctx) => {
1548
2143
  }
1549
2144
  return compareCount2(actual, atom.operator, atom.value) ? null : `global count ${atom.subject}=${actual} fails ${atom.operator} ${atom.value}`;
1550
2145
  };
1551
- var evalAtom2 = (atom, ctx, customResolver) => {
2146
+ var evalAtom3 = (atom, ctx, customResolver) => {
1552
2147
  if (atom.type === "TIME") return evalAtomTime(atom, ctx);
1553
2148
  if (atom.type === "STATE") return evalAtomState(atom, ctx);
1554
2149
  if (atom.type === "ACTION") return evalAtomAction(atom, ctx);
@@ -1558,12 +2153,16 @@ var evalAtom2 = (atom, ctx, customResolver) => {
1558
2153
  if (!customResolver) return `no custom resolver registered for ${atom.resolverId}`;
1559
2154
  return customResolver({ resolverId: atom.resolverId, args: atom.args }, ctx) ? null : `custom resolver ${atom.resolverId} returned false`;
1560
2155
  }
2156
+ if (atom.type === "DSL") {
2157
+ const dslCtx = buildDslContextFromDerived(ctx);
2158
+ return evalDslExpr(atom.expr, dslCtx) ? null : "DSL expression returned false";
2159
+ }
1561
2160
  return "unknown atom";
1562
2161
  };
1563
- var evalExpr2 = (expr, ctx, customResolver) => {
2162
+ var evalExpr3 = (expr, ctx, customResolver) => {
1564
2163
  if (expr.op === "AND") {
1565
2164
  for (const child of expr.children) {
1566
- const reason = evalExpr2(child, ctx, customResolver);
2165
+ const reason = evalExpr3(child, ctx, customResolver);
1567
2166
  if (reason) return reason;
1568
2167
  }
1569
2168
  return null;
@@ -1571,17 +2170,17 @@ var evalExpr2 = (expr, ctx, customResolver) => {
1571
2170
  if (expr.op === "OR") {
1572
2171
  const reasons = [];
1573
2172
  for (const child of expr.children) {
1574
- const reason = evalExpr2(child, ctx, customResolver);
2173
+ const reason = evalExpr3(child, ctx, customResolver);
1575
2174
  if (!reason) return null;
1576
2175
  reasons.push(reason);
1577
2176
  }
1578
2177
  return `OR: all branches failed (${reasons.join("; ")})`;
1579
2178
  }
1580
2179
  if (expr.op === "NOT") {
1581
- const reason = evalExpr2(expr.child, ctx, customResolver);
2180
+ const reason = evalExpr3(expr.child, ctx, customResolver);
1582
2181
  return reason ? null : "NOT: inner expression was true";
1583
2182
  }
1584
- return evalAtom2(expr.atom, ctx, customResolver);
2183
+ return evalAtom3(expr.atom, ctx, customResolver);
1585
2184
  };
1586
2185
  var canInvokeAbility = ({
1587
2186
  ability,
@@ -1601,24 +2200,48 @@ var canInvokeAbility = ({
1601
2200
  if (derived.effector?.retainsAbility === "ALL") {
1602
2201
  return { allowed: true };
1603
2202
  }
1604
- const reason = evalExpr2(window, derived, customResolver);
2203
+ const reason = evalExpr3(window, derived, customResolver);
1605
2204
  return reason ? { allowed: false, reason } : { allowed: true };
1606
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);
1607
2214
  // Annotate the CommonJS export names for ESM import in node:
1608
2215
  0 && (module.exports = {
2216
+ ACTIVE_TRIGGER_ACTIONS,
2217
+ DSL_CATALOG,
2218
+ DSL_ITEM_FIELDS,
2219
+ DSL_OPERATORS,
1609
2220
  adjustValueAsNumber,
1610
2221
  adjustValueAsNumberArray,
2222
+ applyCountOperator,
1611
2223
  applyOperationToPlayers,
2224
+ applyOperator,
2225
+ buildDslContextFromDerived,
2226
+ buildDslContextFromLifetime,
1612
2227
  buildPlayerSeatMap,
1613
2228
  canInvokeAbility,
1614
2229
  copyPlayers,
1615
2230
  deriveContext,
2231
+ evalDslExpr,
2232
+ evalDslExprWithBindings,
2233
+ evaluatePath,
2234
+ extendBindings,
1616
2235
  getSourceValue,
1617
2236
  getValueFromPayloads,
2237
+ isActiveTriggerAction,
1618
2238
  isNumberOrNumberArray,
1619
2239
  isPlayerReminderArray,
1620
2240
  isReminder,
1621
2241
  resolveSourceValue,
1622
2242
  resolveTargetRef,
2243
+ rootBindings,
2244
+ traceDslExpr,
2245
+ traceExpr,
1623
2246
  transformEmptyArray
1624
2247
  });