@bct-app/game-engine 0.1.18 → 0.1.20

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);
@@ -503,19 +519,18 @@ var applyStatusChange = createEffectHandler("STATUS_CHANGE", ({
503
519
  effect,
504
520
  payloads,
505
521
  makePlayersEffect,
506
- operationInTimelineIdx,
522
+ operationTurn,
507
523
  abilityMap,
508
524
  operation
509
525
  }) => {
510
526
  const maybeStatus = getSourceValue(effect.source, payloads);
511
527
  if (maybeStatus === "DEAD") {
512
528
  const ability = abilityMap.get(operation.abilityId);
513
- const turn = operationInTimelineIdx >= 0 ? operationInTimelineIdx : 0;
514
529
  const cause = effect.deathCause ?? (ability?.category === "STORYTELLER" ? "STORYTELLER" : "ABILITY");
515
530
  makePlayersEffect.forEach((player) => {
516
531
  player.isDead = true;
517
532
  player.deathCause = cause;
518
- player.deathTurn = turn;
533
+ player.deathTurn = operationTurn;
519
534
  });
520
535
  return;
521
536
  }
@@ -545,6 +560,521 @@ var effectHandlers = {
545
560
  STATUS_CHANGE: applyStatusChange
546
561
  };
547
562
 
563
+ // src/dsl/context.ts
564
+ var decoratePlayer = (player, characterMap, currentTurn) => {
565
+ const isDead = Boolean(player.isDead);
566
+ const retainsBypass = isDead && (player.retainsAbility === "ALIVENESS" || player.retainsAbility === "ALL");
567
+ const wasExecuted = isDead && player.deathCause === "EXECUTION";
568
+ return {
569
+ ...player,
570
+ characterKind: characterMap.get(player.characterId)?.kind,
571
+ reminderMarks: (player.reminders ?? []).map((r) => r.mark),
572
+ effectiveAlive: !isDead || retainsBypass,
573
+ diedThisTurn: isDead && typeof player.deathTurn === "number" && player.deathTurn === currentTurn,
574
+ wasExecuted,
575
+ wasExecutedToday: wasExecuted && player.deathTurn === currentTurn
576
+ };
577
+ };
578
+ var isExecutionTimeline = (tl) => {
579
+ if (!tl) return false;
580
+ return (tl.operations ?? []).some((op) => op.kind === "execution");
581
+ };
582
+ var buildDslContextFromDerived = (derived, payload = []) => {
583
+ const players = derived.players.map((p) => decoratePlayer(p, derived.characterMap, derived.currentTurn));
584
+ const effector = players.find((p) => p.seat === derived.effectorSeat);
585
+ const aliveCount = players.filter((p) => !p.isDead).length;
586
+ const deadCount = players.length - aliveCount;
587
+ const executedTodayCount = players.filter(
588
+ (p) => p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === derived.currentTurn
589
+ ).length;
590
+ const nominatedTodayCount = derived.todayNominations.length;
591
+ const playerDiedToday = players.some(
592
+ (p) => p.isDead && typeof p.deathTurn === "number" && p.deathTurn === derived.currentTurn
593
+ );
594
+ return {
595
+ effector,
596
+ players,
597
+ operations: {
598
+ all: derived.allOperations,
599
+ today: derived.todayOperations,
600
+ tonight: derived.tonightOperations,
601
+ yesterdayDay: derived.yesterdayDayOperations,
602
+ yesterdayNight: derived.yesterdayNightOperations
603
+ },
604
+ nominations: {
605
+ all: derived.allNominations,
606
+ today: derived.todayNominations,
607
+ yesterday: derived.yesterdayNominations
608
+ },
609
+ currentTimeline: {
610
+ turn: derived.currentTurn,
611
+ phase: derived.currentPhase,
612
+ time: derived.currentPhase === "NIGHT" ? "night" : derived.currentPhase === "DAY" ? "day" : void 0,
613
+ isFirstOfPhase: derived.isFirstOfPhase,
614
+ isExecution: (derived.currentPhase === "DAY" ? derived.todayOperations : derived.tonightOperations).some((op) => op.kind === "execution")
615
+ },
616
+ global: {
617
+ aliveCount,
618
+ deadCount,
619
+ executedTodayCount,
620
+ nominatedTodayCount,
621
+ executionHappenedToday: derived.todayOperations.some((op) => op.kind === "execution"),
622
+ playerDiedToday
623
+ },
624
+ payload
625
+ };
626
+ };
627
+ var buildDslContextFromLifetime = ({
628
+ snapshotSeatMap,
629
+ timelines,
630
+ nowTimelineIndex,
631
+ characterMap,
632
+ effector,
633
+ payloads
634
+ }) => {
635
+ const nowTl = timelines[nowTimelineIndex];
636
+ const currentTurn = nowTl?.turn ?? -1;
637
+ const currentTime = nowTl?.time;
638
+ const currentPhase = currentTime === "night" ? "NIGHT" : currentTime === "day" ? "DAY" : "NIGHT";
639
+ const playersRaw = snapshotSeatMap ? [...snapshotSeatMap.values()] : [];
640
+ const players = playersRaw.map((p) => decoratePlayer(p, characterMap, currentTurn));
641
+ const effectorDecorated = effector ? players.find((p) => p.seat === effector.seat) ?? decoratePlayer(effector, characterMap, currentTurn) : void 0;
642
+ const todayOps = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "day").flatMap((tl) => tl.operations ?? []);
643
+ const tonightOps = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "night").flatMap((tl) => tl.operations ?? []);
644
+ const yesterdayDayOps = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "day").flatMap((tl) => tl.operations ?? []);
645
+ const yesterdayNightOps = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "night").flatMap((tl) => tl.operations ?? []);
646
+ const allOps = timelines.flatMap((tl) => tl.operations ?? []);
647
+ const todayNoms = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee })));
648
+ const yesterdayNoms = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee })));
649
+ const allNoms = timelines.filter((tl) => tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({
650
+ nominator: n.nominator,
651
+ nominee: n.nominee,
652
+ turn: tl.turn
653
+ })));
654
+ const aliveCount = players.filter((p) => !p.isDead).length;
655
+ const deadCount = players.length - aliveCount;
656
+ const executedTodayCount = players.filter(
657
+ (p) => p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === currentTurn
658
+ ).length;
659
+ const playerDiedToday = timelines.some((tl) => {
660
+ if (tl.turn !== currentTurn || tl.time !== "day") return false;
661
+ return (tl.operations ?? []).some((op) => {
662
+ const kind = op.kind;
663
+ return kind === "execution" || kind === "death";
664
+ });
665
+ });
666
+ return {
667
+ effector: effectorDecorated,
668
+ players,
669
+ operations: {
670
+ all: allOps,
671
+ today: todayOps,
672
+ tonight: tonightOps,
673
+ yesterdayDay: yesterdayDayOps,
674
+ yesterdayNight: yesterdayNightOps
675
+ },
676
+ nominations: {
677
+ all: allNoms,
678
+ today: todayNoms,
679
+ yesterday: yesterdayNoms
680
+ },
681
+ currentTimeline: {
682
+ turn: currentTurn,
683
+ phase: currentPhase,
684
+ time: currentTime,
685
+ isFirstOfPhase: false,
686
+ isExecution: isExecutionTimeline(nowTl)
687
+ },
688
+ global: {
689
+ aliveCount,
690
+ deadCount,
691
+ executedTodayCount,
692
+ nominatedTodayCount: todayNoms.length,
693
+ executionHappenedToday: timelines.some(
694
+ (tl) => tl.turn === currentTurn && tl.time === "day" && isExecutionTimeline(tl)
695
+ ),
696
+ playerDiedToday
697
+ },
698
+ payload: payloads
699
+ };
700
+ };
701
+
702
+ // src/dsl/bindings.ts
703
+ var rootBindings = (ctx) => ({
704
+ effector: ctx.effector,
705
+ players: ctx.players,
706
+ operations: ctx.operations,
707
+ nominations: ctx.nominations,
708
+ currentTimeline: ctx.currentTimeline,
709
+ global: ctx.global,
710
+ payload: ctx.payload
711
+ });
712
+ var extendBindings = (parent, name, value) => ({
713
+ ...parent,
714
+ [name]: value
715
+ });
716
+
717
+ // src/dsl/operators.ts
718
+ var deepEqual = (a, b) => {
719
+ if (a === b) return true;
720
+ if (Array.isArray(a) && Array.isArray(b)) {
721
+ if (a.length !== b.length) return false;
722
+ for (let i = 0; i < a.length; i++) {
723
+ if (!deepEqual(a[i], b[i])) return false;
724
+ }
725
+ return true;
726
+ }
727
+ return false;
728
+ };
729
+ var asNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
730
+ var compareOrder = (lhs, rhs, cmp) => {
731
+ const l = asNumber(lhs);
732
+ const r = asNumber(rhs);
733
+ if (l === void 0 || r === void 0) return false;
734
+ return cmp(l, r);
735
+ };
736
+ var applyOperator = (op, lhs, rhs) => {
737
+ switch (op) {
738
+ case "EQ":
739
+ return deepEqual(lhs, rhs);
740
+ case "NE":
741
+ return !deepEqual(lhs, rhs);
742
+ case "LT":
743
+ return compareOrder(lhs, rhs, (l, r) => l < r);
744
+ case "LTE":
745
+ return compareOrder(lhs, rhs, (l, r) => l <= r);
746
+ case "GT":
747
+ return compareOrder(lhs, rhs, (l, r) => l > r);
748
+ case "GTE":
749
+ return compareOrder(lhs, rhs, (l, r) => l >= r);
750
+ case "IN":
751
+ return Array.isArray(rhs) && rhs.some((v) => deepEqual(v, lhs));
752
+ case "NOT_IN":
753
+ return Array.isArray(rhs) && !rhs.some((v) => deepEqual(v, lhs));
754
+ case "CONTAINS":
755
+ if (Array.isArray(lhs)) return lhs.some((v) => deepEqual(v, rhs));
756
+ if (typeof lhs === "string") return typeof rhs === "string" && lhs.includes(rhs);
757
+ return false;
758
+ case "MATCHES":
759
+ if (typeof lhs !== "string" || typeof rhs !== "string") return false;
760
+ try {
761
+ return new RegExp(rhs).test(lhs);
762
+ } catch {
763
+ return false;
764
+ }
765
+ case "EXISTS":
766
+ return lhs !== void 0 && lhs !== null;
767
+ case "TRUTHY":
768
+ return Boolean(lhs);
769
+ }
770
+ };
771
+ var applyCountOperator = (op, actual, expected) => {
772
+ switch (op) {
773
+ case "EQ":
774
+ return actual === expected;
775
+ case "NE":
776
+ return actual !== expected;
777
+ case "LT":
778
+ return actual < expected;
779
+ case "LTE":
780
+ return actual <= expected;
781
+ case "GT":
782
+ return actual > expected;
783
+ case "GTE":
784
+ return actual >= expected;
785
+ }
786
+ };
787
+
788
+ // src/dsl/path.ts
789
+ var MAX_STEPS = 32;
790
+ var stepInto = (current, step) => {
791
+ if (current === void 0 || current === null) return void 0;
792
+ if (step.kind === "field") {
793
+ if (typeof current !== "object") return void 0;
794
+ const record = current;
795
+ return record[step.name];
796
+ }
797
+ if (step.kind === "index") {
798
+ if (!Array.isArray(current)) return void 0;
799
+ return current[step.value];
800
+ }
801
+ return void 0;
802
+ };
803
+ var evaluatePath = (path, bindings) => {
804
+ if (path.steps.length > MAX_STEPS) return void 0;
805
+ let current = Object.prototype.hasOwnProperty.call(bindings, path.root) ? bindings[path.root] : void 0;
806
+ for (const step of path.steps) {
807
+ current = stepInto(current, step);
808
+ if (current === void 0) return void 0;
809
+ }
810
+ return current;
811
+ };
812
+
813
+ // src/dsl/eval.ts
814
+ var MAX_EXPR_DEPTH = 16;
815
+ var MAX_QUANTIFIER_SIZE = 256;
816
+ var resolveValue = (value, bindings) => {
817
+ if (!value) return void 0;
818
+ if (value.kind === "LITERAL") return value.value;
819
+ return evaluatePath(value.path, bindings);
820
+ };
821
+ var evalAtom = (atom, bindings, depth) => {
822
+ if (atom.type === "PATH") {
823
+ const lhs = evaluatePath(atom.path, bindings);
824
+ const rhs = resolveValue(atom.value, bindings);
825
+ return applyOperator(atom.op, lhs, rhs);
826
+ }
827
+ const source = evaluatePath(atom.source, bindings);
828
+ if (!Array.isArray(source)) {
829
+ return atom.type === "FORALL";
830
+ }
831
+ if (source.length > MAX_QUANTIFIER_SIZE) return false;
832
+ if (atom.type === "EXISTS") {
833
+ return source.some(
834
+ (item) => evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)
835
+ );
836
+ }
837
+ if (atom.type === "FORALL") {
838
+ return source.every(
839
+ (item) => evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)
840
+ );
841
+ }
842
+ let count = 0;
843
+ for (const item of source) {
844
+ if (!atom.where || evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)) {
845
+ count += 1;
846
+ }
847
+ }
848
+ return applyCountOperator(atom.op, count, atom.value);
849
+ };
850
+ var evalExpr = (expr, bindings, depth = 0) => {
851
+ if (depth > MAX_EXPR_DEPTH) return false;
852
+ switch (expr.op) {
853
+ case "AND":
854
+ return expr.children.every((child) => evalExpr(child, bindings, depth + 1));
855
+ case "OR":
856
+ return expr.children.some((child) => evalExpr(child, bindings, depth + 1));
857
+ case "NOT":
858
+ return !evalExpr(expr.child, bindings, depth + 1);
859
+ case "ATOM":
860
+ return evalAtom(expr.atom, bindings, depth + 1);
861
+ }
862
+ };
863
+ var evalDslExpr = (expr, ctx) => evalExpr(expr, rootBindings(ctx), 0);
864
+
865
+ // src/dsl/catalog.ts
866
+ var PLAYER_FIELDS = [
867
+ { name: "seat", type: "number", label: "\u5EA7\u4F4D\u53F7" },
868
+ { name: "alignment", type: "string", label: "\u9635\u8425", description: "GOOD / EVIL" },
869
+ { name: "isDead", type: "boolean", label: "\u662F\u5426\u6B7B\u4EA1" },
870
+ { name: "effectiveAlive", type: "boolean", label: "\u6709\u6548\u5B58\u6D3B", description: "\u8003\u8651 retainsAbility\uFF08ALIVENESS/ALL\uFF09\u540E\u662F\u5426\u6309\u5B58\u6D3B\u5904\u7406" },
871
+ { name: "diedThisTurn", type: "boolean", label: "\u672C\u56DE\u5408\u6B7B\u4EA1" },
872
+ { name: "wasExecuted", type: "boolean", label: "\u66FE\u88AB\u5904\u51B3" },
873
+ { name: "wasExecutedToday", type: "boolean", label: "\u4ECA\u65E5\u88AB\u5904\u51B3" },
874
+ { name: "characterId", type: "string", label: "\u89D2\u8272 ID" },
875
+ { name: "characterKind", type: "string", label: "\u89D2\u8272\u7C7B\u578B", description: "Townsfolk / Outsiders / Minions / Demons" },
876
+ { name: "deathCause", type: "string", label: "\u6B7B\u4EA1\u539F\u56E0", description: "EXECUTION / DEMON / ABILITY / STORYTELLER / OTHER" },
877
+ { name: "deathTurn", type: "number", label: "\u6B7B\u4EA1\u56DE\u5408" },
878
+ { name: "abilities", type: "string[]", label: "\u80FD\u529B ID \u5217\u8868" },
879
+ { name: "reminderMarks", type: "string[]", label: "\u63D0\u793A\u7269 mark \u5217\u8868" },
880
+ { name: "hasUsedDeadVote", type: "boolean", label: "\u662F\u5426\u4F7F\u7528\u8FC7\u6B7B\u4EA1\u6295\u7968" },
881
+ { name: "retainsAbility", type: "string", label: "\u80FD\u529B\u4FDD\u7559\u8303\u56F4" }
882
+ ];
883
+ var OPERATION_FIELDS = [
884
+ { name: "effector", type: "number", label: "\u65BD\u52A8\u8005\u5EA7\u4F4D" },
885
+ { name: "abilityId", type: "string", label: "\u80FD\u529B ID" },
886
+ { name: "kind", type: "string", label: "\u64CD\u4F5C\u79CD\u7C7B" },
887
+ { name: "payloads", type: "unknown[]", label: "Payload \u5217\u8868" }
888
+ ];
889
+ var NOMINATION_FIELDS = [
890
+ { name: "nominator", type: "number", label: "\u63D0\u540D\u8005\u5EA7\u4F4D" },
891
+ { name: "nominee", type: "number", label: "\u88AB\u63D0\u540D\u8005\u5EA7\u4F4D" },
892
+ { name: "turn", type: "number", label: "\u56DE\u5408", description: "\u4EC5 all \u96C6\u5408\u53EF\u7528" }
893
+ ];
894
+ var CURRENT_TIMELINE_FIELDS = [
895
+ { name: "turn", type: "number", label: "\u5F53\u524D\u56DE\u5408" },
896
+ { name: "phase", type: "string", label: "\u5F53\u524D\u9636\u6BB5", description: "NIGHT / DAY / DUSK / DAWN" },
897
+ { name: "time", type: "string", label: "\u5F53\u524D\u65F6\u6BB5", description: "night / day" },
898
+ { name: "isFirstOfPhase", type: "boolean", label: "\u662F\u5426\u672C\u9636\u6BB5\u9996\u6B21" },
899
+ { name: "isExecution", type: "boolean", label: "\u5F53\u524D\u65F6\u95F4\u7EBF\u542B\u5904\u51B3" }
900
+ ];
901
+ var GLOBAL_FIELDS = [
902
+ { name: "aliveCount", type: "number", label: "\u5B58\u6D3B\u73A9\u5BB6\u6570" },
903
+ { name: "deadCount", type: "number", label: "\u6B7B\u4EA1\u73A9\u5BB6\u6570" },
904
+ { name: "executedTodayCount", type: "number", label: "\u4ECA\u65E5\u88AB\u5904\u51B3\u4EBA\u6570" },
905
+ { name: "nominatedTodayCount", type: "number", label: "\u4ECA\u65E5\u63D0\u540D\u603B\u6570" },
906
+ { name: "executionHappenedToday", type: "boolean", label: "\u4ECA\u65E5\u662F\u5426\u5DF2\u53D1\u751F\u5904\u51B3" },
907
+ { name: "playerDiedToday", type: "boolean", label: "\u4ECA\u65E5\u662F\u5426\u6709\u73A9\u5BB6\u6B7B\u4EA1" }
908
+ ];
909
+ var OPERATIONS_FIELDS = [
910
+ { name: "all", type: "operation[]", label: "\u6240\u6709\u64CD\u4F5C" },
911
+ { name: "today", type: "operation[]", label: "\u4ECA\u65E5\u64CD\u4F5C" },
912
+ { name: "tonight", type: "operation[]", label: "\u4ECA\u591C\u64CD\u4F5C" },
913
+ { name: "yesterdayDay", type: "operation[]", label: "\u6628\u65E5\u767D\u5929\u64CD\u4F5C" },
914
+ { name: "yesterdayNight", type: "operation[]", label: "\u6628\u591C\u64CD\u4F5C" }
915
+ ];
916
+ var NOMINATIONS_FIELDS = [
917
+ { name: "all", type: "nomination[]", label: "\u6240\u6709\u63D0\u540D" },
918
+ { name: "today", type: "nomination[]", label: "\u4ECA\u65E5\u63D0\u540D" },
919
+ { name: "yesterday", type: "nomination[]", label: "\u6628\u65E5\u63D0\u540D" }
920
+ ];
921
+ var DSL_CATALOG = [
922
+ {
923
+ name: "effector",
924
+ type: "player",
925
+ label: "\u80FD\u529B\u53D1\u52A8\u8005",
926
+ description: "\u5F53\u524D\u8BC4\u4F30\u65F6\u7684\u80FD\u529B\u65BD\u653E\u8005\u5FEB\u7167",
927
+ fields: PLAYER_FIELDS
928
+ },
929
+ {
930
+ name: "players",
931
+ type: "player[]",
932
+ label: "\u5168\u90E8\u73A9\u5BB6",
933
+ description: "\u5F53\u524D\u5FEB\u7167\u4E0B\u6240\u6709\u73A9\u5BB6\uFF08\u5E94\u914D\u5408 EXISTS / FORALL / COUNT \u4F7F\u7528\uFF09"
934
+ },
935
+ {
936
+ name: "operations",
937
+ type: "object",
938
+ label: "\u5386\u53F2\u64CD\u4F5C\u96C6\u5408",
939
+ fields: OPERATIONS_FIELDS
940
+ },
941
+ {
942
+ name: "nominations",
943
+ type: "object",
944
+ label: "\u5386\u53F2\u63D0\u540D\u96C6\u5408",
945
+ fields: NOMINATIONS_FIELDS
946
+ },
947
+ {
948
+ name: "currentTimeline",
949
+ type: "object",
950
+ label: "\u5F53\u524D\u65F6\u95F4\u7EBF",
951
+ fields: CURRENT_TIMELINE_FIELDS
952
+ },
953
+ {
954
+ name: "global",
955
+ type: "object",
956
+ label: "\u5168\u5C40\u805A\u5408",
957
+ fields: GLOBAL_FIELDS
958
+ },
959
+ {
960
+ name: "payload",
961
+ type: "unknown[]",
962
+ label: "\u80FD\u529B Payload \u6570\u7EC4",
963
+ description: "\u5F53\u524D\u8C03\u7528 / \u64CD\u4F5C\u7684 payload \u539F\u59CB\u6570\u7EC4"
964
+ }
965
+ ];
966
+ var DSL_ITEM_FIELDS = {
967
+ player: PLAYER_FIELDS,
968
+ operation: OPERATION_FIELDS,
969
+ nomination: NOMINATION_FIELDS
970
+ };
971
+ var DSL_OPERATORS = [
972
+ { value: "EQ", label: "\u7B49\u4E8E", arity: "binary" },
973
+ { value: "NE", label: "\u4E0D\u7B49\u4E8E", arity: "binary" },
974
+ { value: "LT", label: "<", arity: "binary" },
975
+ { value: "LTE", label: "<=", arity: "binary" },
976
+ { value: "GT", label: ">", arity: "binary" },
977
+ { value: "GTE", label: ">=", arity: "binary" },
978
+ { value: "IN", label: "\u5C5E\u4E8E", arity: "binary" },
979
+ { value: "NOT_IN", label: "\u4E0D\u5C5E\u4E8E", arity: "binary" },
980
+ { value: "CONTAINS", label: "\u5305\u542B", arity: "binary" },
981
+ { value: "MATCHES", label: "\u6B63\u5219\u5339\u914D", arity: "binary" },
982
+ { value: "EXISTS", label: "\u5B58\u5728", arity: "unary" },
983
+ { value: "TRUTHY", label: "\u4E3A\u771F", arity: "unary" }
984
+ ];
985
+
986
+ // src/dsl/trace.ts
987
+ var MAX_QUANTIFIER_SIZE2 = 256;
988
+ var renderPath = (p) => p.steps.reduce((acc, s) => acc + (s.kind === "field" ? `.${s.name}` : `[${s.value}]`), p.root);
989
+ var resolveValue2 = (value, bindings) => {
990
+ if (!value) return void 0;
991
+ if (value.kind === "LITERAL") return value.value;
992
+ return evaluatePath(value.path, bindings);
993
+ };
994
+ var renderValue = (value, bindings) => {
995
+ if (!value) return void 0;
996
+ if (value.kind === "LITERAL") return value.value;
997
+ return { ref: renderPath(value.path), resolved: evaluatePath(value.path, bindings) };
998
+ };
999
+ var traceAtom = (atom, bindings) => {
1000
+ if (atom.type === "PATH") {
1001
+ const lhs = evaluatePath(atom.path, bindings);
1002
+ const rhs = resolveValue2(atom.value, bindings);
1003
+ const ok2 = applyOperator(atom.op, lhs, rhs);
1004
+ return {
1005
+ op: "ATOM",
1006
+ atomType: "PATH",
1007
+ ok: ok2,
1008
+ path: renderPath(atom.path),
1009
+ operator: atom.op,
1010
+ lhs,
1011
+ rhs: renderValue(atom.value, bindings)
1012
+ };
1013
+ }
1014
+ const source = evaluatePath(atom.source, bindings);
1015
+ const sourceArr = Array.isArray(source) ? source : [];
1016
+ const sizeCapped = sourceArr.length > MAX_QUANTIFIER_SIZE2;
1017
+ let matched = 0;
1018
+ if (!sizeCapped) {
1019
+ for (const item of sourceArr) {
1020
+ const childBindings = extendBindings(bindings, atom.as, item);
1021
+ if (!atom.where || traceExprOk(atom.where, childBindings)) matched += 1;
1022
+ }
1023
+ }
1024
+ if (atom.type === "COUNT") {
1025
+ const ok2 = !sizeCapped && applyCountOperator(atom.op, matched, atom.value);
1026
+ return {
1027
+ op: "ATOM",
1028
+ atomType: "COUNT",
1029
+ ok: ok2,
1030
+ source: renderPath(atom.source),
1031
+ size: sourceArr.length,
1032
+ matched,
1033
+ operator: atom.op,
1034
+ expected: atom.value,
1035
+ bindings: atom.as
1036
+ };
1037
+ }
1038
+ const ok = atom.type === "EXISTS" ? matched > 0 : !sizeCapped && (sourceArr.length === 0 || matched === sourceArr.length);
1039
+ return {
1040
+ op: "ATOM",
1041
+ atomType: atom.type,
1042
+ ok,
1043
+ source: renderPath(atom.source),
1044
+ size: sourceArr.length,
1045
+ matched,
1046
+ bindings: atom.as
1047
+ };
1048
+ };
1049
+ var traceExprOk = (expr, bindings) => {
1050
+ switch (expr.op) {
1051
+ case "AND":
1052
+ return expr.children.every((c) => traceExprOk(c, bindings));
1053
+ case "OR":
1054
+ return expr.children.some((c) => traceExprOk(c, bindings));
1055
+ case "NOT":
1056
+ return !traceExprOk(expr.child, bindings);
1057
+ case "ATOM":
1058
+ return traceAtom(expr.atom, bindings).ok;
1059
+ }
1060
+ };
1061
+ var traceExpr = (expr, bindings) => {
1062
+ if (expr.op === "AND") {
1063
+ const children = expr.children.map((c) => traceExpr(c, bindings));
1064
+ return { op: "AND", ok: children.every((c) => c.ok), children };
1065
+ }
1066
+ if (expr.op === "OR") {
1067
+ const children = expr.children.map((c) => traceExpr(c, bindings));
1068
+ return { op: "OR", ok: children.some((c) => c.ok), children };
1069
+ }
1070
+ if (expr.op === "NOT") {
1071
+ const child = traceExpr(expr.child, bindings);
1072
+ return { op: "NOT", ok: !child.ok, child };
1073
+ }
1074
+ return traceAtom(expr.atom, bindings);
1075
+ };
1076
+ var traceDslExpr = (expr, ctx) => traceExpr(expr, rootBindings(ctx));
1077
+
548
1078
  // src/effects/resolve-targets.ts
549
1079
  var isPayloadRef = (value) => {
550
1080
  if (!value || typeof value !== "object") {
@@ -590,7 +1120,23 @@ var resolveSelectorScope = (dynamicTarget, payloads) => {
590
1120
  (value) => value === "BOTH_SIDES" || value === "LEFT_SIDE" || value === "RIGHT_SIDE"
591
1121
  ) ?? "BOTH_SIDES";
592
1122
  };
593
- var matchesCondition = (player, condition, payloads, characterMap) => {
1123
+ var matchesExprCondition = (player, condition, playerSeatMap, characterMap, effector, payloads) => {
1124
+ const dslCtx = buildDslContextFromLifetime({
1125
+ snapshotSeatMap: playerSeatMap,
1126
+ timelines: [],
1127
+ nowTimelineIndex: -1,
1128
+ characterMap,
1129
+ effector,
1130
+ payloads
1131
+ });
1132
+ const decoratedPlayer = dslCtx.players.find((p) => p.seat === player.seat);
1133
+ const bindings = extendBindings(rootBindings(dslCtx), "it", decoratedPlayer ?? player);
1134
+ return evalExpr(condition.expr, bindings);
1135
+ };
1136
+ var matchesCondition = (player, condition, payloads, characterMap, playerSeatMap, effector) => {
1137
+ if (condition.kind === "EXPR") {
1138
+ return matchesExprCondition(player, condition, playerSeatMap, characterMap, effector, payloads);
1139
+ }
594
1140
  if (condition.field === "IS_DEAD") {
595
1141
  const expected = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "boolean");
596
1142
  if (typeof expected !== "boolean") {
@@ -606,6 +1152,18 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
606
1152
  const expectedSeat = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "number");
607
1153
  return typeof expectedSeat === "number" && player.seat === expectedSeat;
608
1154
  }
1155
+ if (condition.field === "ALIGNMENT") {
1156
+ const alignment = player.alignment;
1157
+ if (!alignment) {
1158
+ return false;
1159
+ }
1160
+ if (condition.operator === "IN") {
1161
+ const expectedAlignments = resolveDynamicValue(condition.value, payloads, (value) => Array.isArray(value) && value.every((item) => typeof item === "string"));
1162
+ return Array.isArray(expectedAlignments) && expectedAlignments.includes(alignment);
1163
+ }
1164
+ const expectedAlignment = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
1165
+ return typeof expectedAlignment === "string" && alignment === expectedAlignment;
1166
+ }
609
1167
  const character = characterMap.get(player.characterId);
610
1168
  const kind = character?.kind;
611
1169
  if (!kind) {
@@ -618,15 +1176,17 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
618
1176
  const expectedKind = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
619
1177
  return typeof expectedKind === "string" && kind === expectedKind;
620
1178
  };
621
- var passesWhere = (player, dynamicTarget, payloads, characterMap) => {
1179
+ var passesWhere = (player, dynamicTarget, payloads, characterMap, playerSeatMap, effector) => {
622
1180
  const where = dynamicTarget.where;
623
1181
  const conditions = where?.conditions ?? [];
624
1182
  if (conditions.length === 0) return true;
625
1183
  const isAllMode = where?.mode !== "ANY";
626
- const results = conditions.map((condition) => matchesCondition(player, condition, payloads, characterMap));
1184
+ const results = conditions.map(
1185
+ (condition) => matchesCondition(player, condition, payloads, characterMap, playerSeatMap, effector)
1186
+ );
627
1187
  return isAllMode ? results.every(Boolean) : results.some(Boolean);
628
1188
  };
629
- var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap) => {
1189
+ var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector) => {
630
1190
  const scope = resolveSelectorScope(dynamicTarget, payloads);
631
1191
  if (!anchorSeat) return { left: [], right: [] };
632
1192
  const anchorIdx = sortedPlayers.findIndex((player) => player.seat === anchorSeat);
@@ -637,13 +1197,13 @@ var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, c
637
1197
  if (scope === "LEFT_SIDE" || scope === "BOTH_SIDES") {
638
1198
  for (let step = 1; step < total; step += 1) {
639
1199
  const player = sortedPlayers[(anchorIdx + step) % total];
640
- if (passesWhere(player, dynamicTarget, payloads, characterMap)) left.push(player);
1200
+ if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) left.push(player);
641
1201
  }
642
1202
  }
643
1203
  if (scope === "RIGHT_SIDE" || scope === "BOTH_SIDES") {
644
1204
  for (let step = 1; step < total; step += 1) {
645
1205
  const player = sortedPlayers[(anchorIdx - step + total) % total];
646
- if (passesWhere(player, dynamicTarget, payloads, characterMap)) right.push(player);
1206
+ if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) right.push(player);
647
1207
  }
648
1208
  }
649
1209
  return { left, right };
@@ -717,10 +1277,12 @@ var resolveEffectTargets = ({
717
1277
  const sortedPlayers = getSortedPlayers(playerSeatMap);
718
1278
  const anchorSeat = getAnchorSeat(dynamicTarget, payloads, effector);
719
1279
  if (!dynamicTarget.selector) {
720
- const matches = sortedPlayers.filter((player) => passesWhere(player, dynamicTarget, payloads, characterMap));
1280
+ const matches = sortedPlayers.filter(
1281
+ (player) => passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)
1282
+ );
721
1283
  return pickBalanced({ left: matches, right: [] }, dynamicTarget, payloads);
722
1284
  }
723
- const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap);
1285
+ const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector);
724
1286
  return pickBalanced(sides, dynamicTarget, payloads);
725
1287
  }
726
1288
  return [];
@@ -866,7 +1428,7 @@ var evalPlayerState = (atom, ctx) => {
866
1428
  const single = players[0];
867
1429
  return evalPlayerStateOnPlayer(single, atom, ctx);
868
1430
  };
869
- var isExecutionTimeline = (tl) => {
1431
+ var isExecutionTimeline2 = (tl) => {
870
1432
  if (!tl) return false;
871
1433
  return (tl.operations ?? []).some((op) => op.kind === "execution");
872
1434
  };
@@ -894,7 +1456,7 @@ var evalTime = (atom, ctx) => {
894
1456
  if (spec.event === "NEXT_NIGHT" && tl.time === "night") return false;
895
1457
  if (spec.event === "NEXT_DUSK" && prevTime === "day" && tl.time === "night") return false;
896
1458
  if (spec.event === "NEXT_DAWN" && prevTime === "night" && tl.time === "day") return false;
897
- if (spec.event === "NEXT_EXECUTION" && isExecutionTimeline(tl)) return false;
1459
+ if (spec.event === "NEXT_EXECUTION" && isExecutionTimeline2(tl)) return false;
898
1460
  prevTime = tl.time;
899
1461
  }
900
1462
  return true;
@@ -935,7 +1497,7 @@ var evalGameState = (atom, ctx) => {
935
1497
  if (spec.kind === "EXECUTION_HAPPENED_TODAY") {
936
1498
  const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
937
1499
  if (typeof turn !== "number") return false;
938
- return ctx.timelines.some((tl) => tl.turn === turn && tl.time === "day" && isExecutionTimeline(tl));
1500
+ return ctx.timelines.some((tl) => tl.turn === turn && tl.time === "day" && isExecutionTimeline2(tl));
939
1501
  }
940
1502
  if (spec.kind === "PLAYER_DIED_TODAY") {
941
1503
  const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
@@ -950,7 +1512,7 @@ var evalGameState = (atom, ctx) => {
950
1512
  }
951
1513
  return true;
952
1514
  };
953
- var evalAtom = (atom, ctx) => {
1515
+ var evalAtom2 = (atom, ctx) => {
954
1516
  switch (atom.type) {
955
1517
  case "TIME":
956
1518
  return evalTime(atom, ctx);
@@ -969,18 +1531,30 @@ var evalAtom = (atom, ctx) => {
969
1531
  target: ctx.target,
970
1532
  snapshotSeatMap: ctx.snapshotSeatMap
971
1533
  }) : false;
1534
+ case "DSL": {
1535
+ if (!ctx.snapshotSeatMap) return true;
1536
+ const dslCtx = buildDslContextFromLifetime({
1537
+ snapshotSeatMap: ctx.snapshotSeatMap,
1538
+ timelines: ctx.timelines,
1539
+ nowTimelineIndex: ctx.nowTimelineIndex,
1540
+ characterMap: ctx.characterMap,
1541
+ effector: ctx.effector,
1542
+ payloads: ctx.payloads
1543
+ });
1544
+ return evalDslExpr(atom.expr, dslCtx);
1545
+ }
972
1546
  }
973
1547
  };
974
- var evalExpr = (expr, ctx) => {
1548
+ var evalExpr2 = (expr, ctx) => {
975
1549
  switch (expr.op) {
976
1550
  case "AND":
977
- return expr.children.every((child) => evalExpr(child, ctx));
1551
+ return expr.children.every((child) => evalExpr2(child, ctx));
978
1552
  case "OR":
979
- return expr.children.some((child) => evalExpr(child, ctx));
1553
+ return expr.children.some((child) => evalExpr2(child, ctx));
980
1554
  case "NOT":
981
- return !evalExpr(expr.child, ctx);
1555
+ return !evalExpr2(expr.child, ctx);
982
1556
  case "ATOM":
983
- return evalAtom(expr.atom, ctx);
1557
+ return evalAtom2(expr.atom, ctx);
984
1558
  }
985
1559
  };
986
1560
  var expressionReferencesTarget = (expr) => {
@@ -1018,9 +1592,9 @@ var evaluateLifetime = ({
1018
1592
  const expr = lifetime.expr;
1019
1593
  const dependsOnTarget = expressionReferencesTarget(expr);
1020
1594
  if (!dependsOnTarget) {
1021
- return evalExpr(expr, { ...baseCtx, target: void 0 }) ? targets : [];
1595
+ return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
1022
1596
  }
1023
- return targets.filter((target) => evalExpr(expr, { ...baseCtx, target }));
1597
+ return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
1024
1598
  };
1025
1599
  var evaluatePrecondition = ({
1026
1600
  expr,
@@ -1046,9 +1620,9 @@ var evaluatePrecondition = ({
1046
1620
  };
1047
1621
  const dependsOnTarget = expressionReferencesTarget(expr);
1048
1622
  if (!dependsOnTarget) {
1049
- return evalExpr(expr, { ...baseCtx, target: void 0 }) ? targets : [];
1623
+ return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
1050
1624
  }
1051
- return targets.filter((target) => evalExpr(expr, { ...baseCtx, target }));
1625
+ return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
1052
1626
  };
1053
1627
 
1054
1628
  // src/apply-operation.ts
@@ -1133,6 +1707,7 @@ var applyOperationToPlayers = ({
1133
1707
  abilityMap,
1134
1708
  nowTimelineIndex,
1135
1709
  operationInTimelineIdx: record.operationInTimelineIdx,
1710
+ operationTurn: timelines[record.operationInTimelineIdx]?.turn ?? 0,
1136
1711
  makePlayersEffect
1137
1712
  };
1138
1713
  handler(context);
@@ -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
  });