@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.mjs CHANGED
@@ -463,19 +463,18 @@ var applyStatusChange = createEffectHandler("STATUS_CHANGE", ({
463
463
  effect,
464
464
  payloads,
465
465
  makePlayersEffect,
466
- operationInTimelineIdx,
466
+ operationTurn,
467
467
  abilityMap,
468
468
  operation
469
469
  }) => {
470
470
  const maybeStatus = getSourceValue(effect.source, payloads);
471
471
  if (maybeStatus === "DEAD") {
472
472
  const ability = abilityMap.get(operation.abilityId);
473
- const turn = operationInTimelineIdx >= 0 ? operationInTimelineIdx : 0;
474
473
  const cause = effect.deathCause ?? (ability?.category === "STORYTELLER" ? "STORYTELLER" : "ABILITY");
475
474
  makePlayersEffect.forEach((player) => {
476
475
  player.isDead = true;
477
476
  player.deathCause = cause;
478
- player.deathTurn = turn;
477
+ player.deathTurn = operationTurn;
479
478
  });
480
479
  return;
481
480
  }
@@ -505,6 +504,521 @@ var effectHandlers = {
505
504
  STATUS_CHANGE: applyStatusChange
506
505
  };
507
506
 
507
+ // src/dsl/context.ts
508
+ var decoratePlayer = (player, characterMap, currentTurn) => {
509
+ const isDead = Boolean(player.isDead);
510
+ const retainsBypass = isDead && (player.retainsAbility === "ALIVENESS" || player.retainsAbility === "ALL");
511
+ const wasExecuted = isDead && player.deathCause === "EXECUTION";
512
+ return {
513
+ ...player,
514
+ characterKind: characterMap.get(player.characterId)?.kind,
515
+ reminderMarks: (player.reminders ?? []).map((r) => r.mark),
516
+ effectiveAlive: !isDead || retainsBypass,
517
+ diedThisTurn: isDead && typeof player.deathTurn === "number" && player.deathTurn === currentTurn,
518
+ wasExecuted,
519
+ wasExecutedToday: wasExecuted && player.deathTurn === currentTurn
520
+ };
521
+ };
522
+ var isExecutionTimeline = (tl) => {
523
+ if (!tl) return false;
524
+ return (tl.operations ?? []).some((op) => op.kind === "execution");
525
+ };
526
+ var buildDslContextFromDerived = (derived, payload = []) => {
527
+ const players = derived.players.map((p) => decoratePlayer(p, derived.characterMap, derived.currentTurn));
528
+ const effector = players.find((p) => p.seat === derived.effectorSeat);
529
+ const aliveCount = players.filter((p) => !p.isDead).length;
530
+ const deadCount = players.length - aliveCount;
531
+ const executedTodayCount = players.filter(
532
+ (p) => p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === derived.currentTurn
533
+ ).length;
534
+ const nominatedTodayCount = derived.todayNominations.length;
535
+ const playerDiedToday = players.some(
536
+ (p) => p.isDead && typeof p.deathTurn === "number" && p.deathTurn === derived.currentTurn
537
+ );
538
+ return {
539
+ effector,
540
+ players,
541
+ operations: {
542
+ all: derived.allOperations,
543
+ today: derived.todayOperations,
544
+ tonight: derived.tonightOperations,
545
+ yesterdayDay: derived.yesterdayDayOperations,
546
+ yesterdayNight: derived.yesterdayNightOperations
547
+ },
548
+ nominations: {
549
+ all: derived.allNominations,
550
+ today: derived.todayNominations,
551
+ yesterday: derived.yesterdayNominations
552
+ },
553
+ currentTimeline: {
554
+ turn: derived.currentTurn,
555
+ phase: derived.currentPhase,
556
+ time: derived.currentPhase === "NIGHT" ? "night" : derived.currentPhase === "DAY" ? "day" : void 0,
557
+ isFirstOfPhase: derived.isFirstOfPhase,
558
+ isExecution: (derived.currentPhase === "DAY" ? derived.todayOperations : derived.tonightOperations).some((op) => op.kind === "execution")
559
+ },
560
+ global: {
561
+ aliveCount,
562
+ deadCount,
563
+ executedTodayCount,
564
+ nominatedTodayCount,
565
+ executionHappenedToday: derived.todayOperations.some((op) => op.kind === "execution"),
566
+ playerDiedToday
567
+ },
568
+ payload
569
+ };
570
+ };
571
+ var buildDslContextFromLifetime = ({
572
+ snapshotSeatMap,
573
+ timelines,
574
+ nowTimelineIndex,
575
+ characterMap,
576
+ effector,
577
+ payloads
578
+ }) => {
579
+ const nowTl = timelines[nowTimelineIndex];
580
+ const currentTurn = nowTl?.turn ?? -1;
581
+ const currentTime = nowTl?.time;
582
+ const currentPhase = currentTime === "night" ? "NIGHT" : currentTime === "day" ? "DAY" : "NIGHT";
583
+ const playersRaw = snapshotSeatMap ? [...snapshotSeatMap.values()] : [];
584
+ const players = playersRaw.map((p) => decoratePlayer(p, characterMap, currentTurn));
585
+ const effectorDecorated = effector ? players.find((p) => p.seat === effector.seat) ?? decoratePlayer(effector, characterMap, currentTurn) : void 0;
586
+ const todayOps = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "day").flatMap((tl) => tl.operations ?? []);
587
+ const tonightOps = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "night").flatMap((tl) => tl.operations ?? []);
588
+ const yesterdayDayOps = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "day").flatMap((tl) => tl.operations ?? []);
589
+ const yesterdayNightOps = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "night").flatMap((tl) => tl.operations ?? []);
590
+ const allOps = timelines.flatMap((tl) => tl.operations ?? []);
591
+ const todayNoms = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee })));
592
+ const yesterdayNoms = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee })));
593
+ const allNoms = timelines.filter((tl) => tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({
594
+ nominator: n.nominator,
595
+ nominee: n.nominee,
596
+ turn: tl.turn
597
+ })));
598
+ const aliveCount = players.filter((p) => !p.isDead).length;
599
+ const deadCount = players.length - aliveCount;
600
+ const executedTodayCount = players.filter(
601
+ (p) => p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === currentTurn
602
+ ).length;
603
+ const playerDiedToday = timelines.some((tl) => {
604
+ if (tl.turn !== currentTurn || tl.time !== "day") return false;
605
+ return (tl.operations ?? []).some((op) => {
606
+ const kind = op.kind;
607
+ return kind === "execution" || kind === "death";
608
+ });
609
+ });
610
+ return {
611
+ effector: effectorDecorated,
612
+ players,
613
+ operations: {
614
+ all: allOps,
615
+ today: todayOps,
616
+ tonight: tonightOps,
617
+ yesterdayDay: yesterdayDayOps,
618
+ yesterdayNight: yesterdayNightOps
619
+ },
620
+ nominations: {
621
+ all: allNoms,
622
+ today: todayNoms,
623
+ yesterday: yesterdayNoms
624
+ },
625
+ currentTimeline: {
626
+ turn: currentTurn,
627
+ phase: currentPhase,
628
+ time: currentTime,
629
+ isFirstOfPhase: false,
630
+ isExecution: isExecutionTimeline(nowTl)
631
+ },
632
+ global: {
633
+ aliveCount,
634
+ deadCount,
635
+ executedTodayCount,
636
+ nominatedTodayCount: todayNoms.length,
637
+ executionHappenedToday: timelines.some(
638
+ (tl) => tl.turn === currentTurn && tl.time === "day" && isExecutionTimeline(tl)
639
+ ),
640
+ playerDiedToday
641
+ },
642
+ payload: payloads
643
+ };
644
+ };
645
+
646
+ // src/dsl/bindings.ts
647
+ var rootBindings = (ctx) => ({
648
+ effector: ctx.effector,
649
+ players: ctx.players,
650
+ operations: ctx.operations,
651
+ nominations: ctx.nominations,
652
+ currentTimeline: ctx.currentTimeline,
653
+ global: ctx.global,
654
+ payload: ctx.payload
655
+ });
656
+ var extendBindings = (parent, name, value) => ({
657
+ ...parent,
658
+ [name]: value
659
+ });
660
+
661
+ // src/dsl/operators.ts
662
+ var deepEqual = (a, b) => {
663
+ if (a === b) return true;
664
+ if (Array.isArray(a) && Array.isArray(b)) {
665
+ if (a.length !== b.length) return false;
666
+ for (let i = 0; i < a.length; i++) {
667
+ if (!deepEqual(a[i], b[i])) return false;
668
+ }
669
+ return true;
670
+ }
671
+ return false;
672
+ };
673
+ var asNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
674
+ var compareOrder = (lhs, rhs, cmp) => {
675
+ const l = asNumber(lhs);
676
+ const r = asNumber(rhs);
677
+ if (l === void 0 || r === void 0) return false;
678
+ return cmp(l, r);
679
+ };
680
+ var applyOperator = (op, lhs, rhs) => {
681
+ switch (op) {
682
+ case "EQ":
683
+ return deepEqual(lhs, rhs);
684
+ case "NE":
685
+ return !deepEqual(lhs, rhs);
686
+ case "LT":
687
+ return compareOrder(lhs, rhs, (l, r) => l < r);
688
+ case "LTE":
689
+ return compareOrder(lhs, rhs, (l, r) => l <= r);
690
+ case "GT":
691
+ return compareOrder(lhs, rhs, (l, r) => l > r);
692
+ case "GTE":
693
+ return compareOrder(lhs, rhs, (l, r) => l >= r);
694
+ case "IN":
695
+ return Array.isArray(rhs) && rhs.some((v) => deepEqual(v, lhs));
696
+ case "NOT_IN":
697
+ return Array.isArray(rhs) && !rhs.some((v) => deepEqual(v, lhs));
698
+ case "CONTAINS":
699
+ if (Array.isArray(lhs)) return lhs.some((v) => deepEqual(v, rhs));
700
+ if (typeof lhs === "string") return typeof rhs === "string" && lhs.includes(rhs);
701
+ return false;
702
+ case "MATCHES":
703
+ if (typeof lhs !== "string" || typeof rhs !== "string") return false;
704
+ try {
705
+ return new RegExp(rhs).test(lhs);
706
+ } catch {
707
+ return false;
708
+ }
709
+ case "EXISTS":
710
+ return lhs !== void 0 && lhs !== null;
711
+ case "TRUTHY":
712
+ return Boolean(lhs);
713
+ }
714
+ };
715
+ var applyCountOperator = (op, actual, expected) => {
716
+ switch (op) {
717
+ case "EQ":
718
+ return actual === expected;
719
+ case "NE":
720
+ return actual !== expected;
721
+ case "LT":
722
+ return actual < expected;
723
+ case "LTE":
724
+ return actual <= expected;
725
+ case "GT":
726
+ return actual > expected;
727
+ case "GTE":
728
+ return actual >= expected;
729
+ }
730
+ };
731
+
732
+ // src/dsl/path.ts
733
+ var MAX_STEPS = 32;
734
+ var stepInto = (current, step) => {
735
+ if (current === void 0 || current === null) return void 0;
736
+ if (step.kind === "field") {
737
+ if (typeof current !== "object") return void 0;
738
+ const record = current;
739
+ return record[step.name];
740
+ }
741
+ if (step.kind === "index") {
742
+ if (!Array.isArray(current)) return void 0;
743
+ return current[step.value];
744
+ }
745
+ return void 0;
746
+ };
747
+ var evaluatePath = (path, bindings) => {
748
+ if (path.steps.length > MAX_STEPS) return void 0;
749
+ let current = Object.prototype.hasOwnProperty.call(bindings, path.root) ? bindings[path.root] : void 0;
750
+ for (const step of path.steps) {
751
+ current = stepInto(current, step);
752
+ if (current === void 0) return void 0;
753
+ }
754
+ return current;
755
+ };
756
+
757
+ // src/dsl/eval.ts
758
+ var MAX_EXPR_DEPTH = 16;
759
+ var MAX_QUANTIFIER_SIZE = 256;
760
+ var resolveValue = (value, bindings) => {
761
+ if (!value) return void 0;
762
+ if (value.kind === "LITERAL") return value.value;
763
+ return evaluatePath(value.path, bindings);
764
+ };
765
+ var evalAtom = (atom, bindings, depth) => {
766
+ if (atom.type === "PATH") {
767
+ const lhs = evaluatePath(atom.path, bindings);
768
+ const rhs = resolveValue(atom.value, bindings);
769
+ return applyOperator(atom.op, lhs, rhs);
770
+ }
771
+ const source = evaluatePath(atom.source, bindings);
772
+ if (!Array.isArray(source)) {
773
+ return atom.type === "FORALL";
774
+ }
775
+ if (source.length > MAX_QUANTIFIER_SIZE) return false;
776
+ if (atom.type === "EXISTS") {
777
+ return source.some(
778
+ (item) => evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)
779
+ );
780
+ }
781
+ if (atom.type === "FORALL") {
782
+ return source.every(
783
+ (item) => evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)
784
+ );
785
+ }
786
+ let count = 0;
787
+ for (const item of source) {
788
+ if (!atom.where || evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)) {
789
+ count += 1;
790
+ }
791
+ }
792
+ return applyCountOperator(atom.op, count, atom.value);
793
+ };
794
+ var evalExpr = (expr, bindings, depth = 0) => {
795
+ if (depth > MAX_EXPR_DEPTH) return false;
796
+ switch (expr.op) {
797
+ case "AND":
798
+ return expr.children.every((child) => evalExpr(child, bindings, depth + 1));
799
+ case "OR":
800
+ return expr.children.some((child) => evalExpr(child, bindings, depth + 1));
801
+ case "NOT":
802
+ return !evalExpr(expr.child, bindings, depth + 1);
803
+ case "ATOM":
804
+ return evalAtom(expr.atom, bindings, depth + 1);
805
+ }
806
+ };
807
+ var evalDslExpr = (expr, ctx) => evalExpr(expr, rootBindings(ctx), 0);
808
+
809
+ // src/dsl/catalog.ts
810
+ var PLAYER_FIELDS = [
811
+ { name: "seat", type: "number", label: "\u5EA7\u4F4D\u53F7" },
812
+ { name: "alignment", type: "string", label: "\u9635\u8425", description: "GOOD / EVIL" },
813
+ { name: "isDead", type: "boolean", label: "\u662F\u5426\u6B7B\u4EA1" },
814
+ { name: "effectiveAlive", type: "boolean", label: "\u6709\u6548\u5B58\u6D3B", description: "\u8003\u8651 retainsAbility\uFF08ALIVENESS/ALL\uFF09\u540E\u662F\u5426\u6309\u5B58\u6D3B\u5904\u7406" },
815
+ { name: "diedThisTurn", type: "boolean", label: "\u672C\u56DE\u5408\u6B7B\u4EA1" },
816
+ { name: "wasExecuted", type: "boolean", label: "\u66FE\u88AB\u5904\u51B3" },
817
+ { name: "wasExecutedToday", type: "boolean", label: "\u4ECA\u65E5\u88AB\u5904\u51B3" },
818
+ { name: "characterId", type: "string", label: "\u89D2\u8272 ID" },
819
+ { name: "characterKind", type: "string", label: "\u89D2\u8272\u7C7B\u578B", description: "Townsfolk / Outsiders / Minions / Demons" },
820
+ { name: "deathCause", type: "string", label: "\u6B7B\u4EA1\u539F\u56E0", description: "EXECUTION / DEMON / ABILITY / STORYTELLER / OTHER" },
821
+ { name: "deathTurn", type: "number", label: "\u6B7B\u4EA1\u56DE\u5408" },
822
+ { name: "abilities", type: "string[]", label: "\u80FD\u529B ID \u5217\u8868" },
823
+ { name: "reminderMarks", type: "string[]", label: "\u63D0\u793A\u7269 mark \u5217\u8868" },
824
+ { name: "hasUsedDeadVote", type: "boolean", label: "\u662F\u5426\u4F7F\u7528\u8FC7\u6B7B\u4EA1\u6295\u7968" },
825
+ { name: "retainsAbility", type: "string", label: "\u80FD\u529B\u4FDD\u7559\u8303\u56F4" }
826
+ ];
827
+ var OPERATION_FIELDS = [
828
+ { name: "effector", type: "number", label: "\u65BD\u52A8\u8005\u5EA7\u4F4D" },
829
+ { name: "abilityId", type: "string", label: "\u80FD\u529B ID" },
830
+ { name: "kind", type: "string", label: "\u64CD\u4F5C\u79CD\u7C7B" },
831
+ { name: "payloads", type: "unknown[]", label: "Payload \u5217\u8868" }
832
+ ];
833
+ var NOMINATION_FIELDS = [
834
+ { name: "nominator", type: "number", label: "\u63D0\u540D\u8005\u5EA7\u4F4D" },
835
+ { name: "nominee", type: "number", label: "\u88AB\u63D0\u540D\u8005\u5EA7\u4F4D" },
836
+ { name: "turn", type: "number", label: "\u56DE\u5408", description: "\u4EC5 all \u96C6\u5408\u53EF\u7528" }
837
+ ];
838
+ var CURRENT_TIMELINE_FIELDS = [
839
+ { name: "turn", type: "number", label: "\u5F53\u524D\u56DE\u5408" },
840
+ { name: "phase", type: "string", label: "\u5F53\u524D\u9636\u6BB5", description: "NIGHT / DAY / DUSK / DAWN" },
841
+ { name: "time", type: "string", label: "\u5F53\u524D\u65F6\u6BB5", description: "night / day" },
842
+ { name: "isFirstOfPhase", type: "boolean", label: "\u662F\u5426\u672C\u9636\u6BB5\u9996\u6B21" },
843
+ { name: "isExecution", type: "boolean", label: "\u5F53\u524D\u65F6\u95F4\u7EBF\u542B\u5904\u51B3" }
844
+ ];
845
+ var GLOBAL_FIELDS = [
846
+ { name: "aliveCount", type: "number", label: "\u5B58\u6D3B\u73A9\u5BB6\u6570" },
847
+ { name: "deadCount", type: "number", label: "\u6B7B\u4EA1\u73A9\u5BB6\u6570" },
848
+ { name: "executedTodayCount", type: "number", label: "\u4ECA\u65E5\u88AB\u5904\u51B3\u4EBA\u6570" },
849
+ { name: "nominatedTodayCount", type: "number", label: "\u4ECA\u65E5\u63D0\u540D\u603B\u6570" },
850
+ { name: "executionHappenedToday", type: "boolean", label: "\u4ECA\u65E5\u662F\u5426\u5DF2\u53D1\u751F\u5904\u51B3" },
851
+ { name: "playerDiedToday", type: "boolean", label: "\u4ECA\u65E5\u662F\u5426\u6709\u73A9\u5BB6\u6B7B\u4EA1" }
852
+ ];
853
+ var OPERATIONS_FIELDS = [
854
+ { name: "all", type: "operation[]", label: "\u6240\u6709\u64CD\u4F5C" },
855
+ { name: "today", type: "operation[]", label: "\u4ECA\u65E5\u64CD\u4F5C" },
856
+ { name: "tonight", type: "operation[]", label: "\u4ECA\u591C\u64CD\u4F5C" },
857
+ { name: "yesterdayDay", type: "operation[]", label: "\u6628\u65E5\u767D\u5929\u64CD\u4F5C" },
858
+ { name: "yesterdayNight", type: "operation[]", label: "\u6628\u591C\u64CD\u4F5C" }
859
+ ];
860
+ var NOMINATIONS_FIELDS = [
861
+ { name: "all", type: "nomination[]", label: "\u6240\u6709\u63D0\u540D" },
862
+ { name: "today", type: "nomination[]", label: "\u4ECA\u65E5\u63D0\u540D" },
863
+ { name: "yesterday", type: "nomination[]", label: "\u6628\u65E5\u63D0\u540D" }
864
+ ];
865
+ var DSL_CATALOG = [
866
+ {
867
+ name: "effector",
868
+ type: "player",
869
+ label: "\u80FD\u529B\u53D1\u52A8\u8005",
870
+ description: "\u5F53\u524D\u8BC4\u4F30\u65F6\u7684\u80FD\u529B\u65BD\u653E\u8005\u5FEB\u7167",
871
+ fields: PLAYER_FIELDS
872
+ },
873
+ {
874
+ name: "players",
875
+ type: "player[]",
876
+ label: "\u5168\u90E8\u73A9\u5BB6",
877
+ description: "\u5F53\u524D\u5FEB\u7167\u4E0B\u6240\u6709\u73A9\u5BB6\uFF08\u5E94\u914D\u5408 EXISTS / FORALL / COUNT \u4F7F\u7528\uFF09"
878
+ },
879
+ {
880
+ name: "operations",
881
+ type: "object",
882
+ label: "\u5386\u53F2\u64CD\u4F5C\u96C6\u5408",
883
+ fields: OPERATIONS_FIELDS
884
+ },
885
+ {
886
+ name: "nominations",
887
+ type: "object",
888
+ label: "\u5386\u53F2\u63D0\u540D\u96C6\u5408",
889
+ fields: NOMINATIONS_FIELDS
890
+ },
891
+ {
892
+ name: "currentTimeline",
893
+ type: "object",
894
+ label: "\u5F53\u524D\u65F6\u95F4\u7EBF",
895
+ fields: CURRENT_TIMELINE_FIELDS
896
+ },
897
+ {
898
+ name: "global",
899
+ type: "object",
900
+ label: "\u5168\u5C40\u805A\u5408",
901
+ fields: GLOBAL_FIELDS
902
+ },
903
+ {
904
+ name: "payload",
905
+ type: "unknown[]",
906
+ label: "\u80FD\u529B Payload \u6570\u7EC4",
907
+ description: "\u5F53\u524D\u8C03\u7528 / \u64CD\u4F5C\u7684 payload \u539F\u59CB\u6570\u7EC4"
908
+ }
909
+ ];
910
+ var DSL_ITEM_FIELDS = {
911
+ player: PLAYER_FIELDS,
912
+ operation: OPERATION_FIELDS,
913
+ nomination: NOMINATION_FIELDS
914
+ };
915
+ var DSL_OPERATORS = [
916
+ { value: "EQ", label: "\u7B49\u4E8E", arity: "binary" },
917
+ { value: "NE", label: "\u4E0D\u7B49\u4E8E", arity: "binary" },
918
+ { value: "LT", label: "<", arity: "binary" },
919
+ { value: "LTE", label: "<=", arity: "binary" },
920
+ { value: "GT", label: ">", arity: "binary" },
921
+ { value: "GTE", label: ">=", arity: "binary" },
922
+ { value: "IN", label: "\u5C5E\u4E8E", arity: "binary" },
923
+ { value: "NOT_IN", label: "\u4E0D\u5C5E\u4E8E", arity: "binary" },
924
+ { value: "CONTAINS", label: "\u5305\u542B", arity: "binary" },
925
+ { value: "MATCHES", label: "\u6B63\u5219\u5339\u914D", arity: "binary" },
926
+ { value: "EXISTS", label: "\u5B58\u5728", arity: "unary" },
927
+ { value: "TRUTHY", label: "\u4E3A\u771F", arity: "unary" }
928
+ ];
929
+
930
+ // src/dsl/trace.ts
931
+ var MAX_QUANTIFIER_SIZE2 = 256;
932
+ var renderPath = (p) => p.steps.reduce((acc, s) => acc + (s.kind === "field" ? `.${s.name}` : `[${s.value}]`), p.root);
933
+ var resolveValue2 = (value, bindings) => {
934
+ if (!value) return void 0;
935
+ if (value.kind === "LITERAL") return value.value;
936
+ return evaluatePath(value.path, bindings);
937
+ };
938
+ var renderValue = (value, bindings) => {
939
+ if (!value) return void 0;
940
+ if (value.kind === "LITERAL") return value.value;
941
+ return { ref: renderPath(value.path), resolved: evaluatePath(value.path, bindings) };
942
+ };
943
+ var traceAtom = (atom, bindings) => {
944
+ if (atom.type === "PATH") {
945
+ const lhs = evaluatePath(atom.path, bindings);
946
+ const rhs = resolveValue2(atom.value, bindings);
947
+ const ok2 = applyOperator(atom.op, lhs, rhs);
948
+ return {
949
+ op: "ATOM",
950
+ atomType: "PATH",
951
+ ok: ok2,
952
+ path: renderPath(atom.path),
953
+ operator: atom.op,
954
+ lhs,
955
+ rhs: renderValue(atom.value, bindings)
956
+ };
957
+ }
958
+ const source = evaluatePath(atom.source, bindings);
959
+ const sourceArr = Array.isArray(source) ? source : [];
960
+ const sizeCapped = sourceArr.length > MAX_QUANTIFIER_SIZE2;
961
+ let matched = 0;
962
+ if (!sizeCapped) {
963
+ for (const item of sourceArr) {
964
+ const childBindings = extendBindings(bindings, atom.as, item);
965
+ if (!atom.where || traceExprOk(atom.where, childBindings)) matched += 1;
966
+ }
967
+ }
968
+ if (atom.type === "COUNT") {
969
+ const ok2 = !sizeCapped && applyCountOperator(atom.op, matched, atom.value);
970
+ return {
971
+ op: "ATOM",
972
+ atomType: "COUNT",
973
+ ok: ok2,
974
+ source: renderPath(atom.source),
975
+ size: sourceArr.length,
976
+ matched,
977
+ operator: atom.op,
978
+ expected: atom.value,
979
+ bindings: atom.as
980
+ };
981
+ }
982
+ const ok = atom.type === "EXISTS" ? matched > 0 : !sizeCapped && (sourceArr.length === 0 || matched === sourceArr.length);
983
+ return {
984
+ op: "ATOM",
985
+ atomType: atom.type,
986
+ ok,
987
+ source: renderPath(atom.source),
988
+ size: sourceArr.length,
989
+ matched,
990
+ bindings: atom.as
991
+ };
992
+ };
993
+ var traceExprOk = (expr, bindings) => {
994
+ switch (expr.op) {
995
+ case "AND":
996
+ return expr.children.every((c) => traceExprOk(c, bindings));
997
+ case "OR":
998
+ return expr.children.some((c) => traceExprOk(c, bindings));
999
+ case "NOT":
1000
+ return !traceExprOk(expr.child, bindings);
1001
+ case "ATOM":
1002
+ return traceAtom(expr.atom, bindings).ok;
1003
+ }
1004
+ };
1005
+ var traceExpr = (expr, bindings) => {
1006
+ if (expr.op === "AND") {
1007
+ const children = expr.children.map((c) => traceExpr(c, bindings));
1008
+ return { op: "AND", ok: children.every((c) => c.ok), children };
1009
+ }
1010
+ if (expr.op === "OR") {
1011
+ const children = expr.children.map((c) => traceExpr(c, bindings));
1012
+ return { op: "OR", ok: children.some((c) => c.ok), children };
1013
+ }
1014
+ if (expr.op === "NOT") {
1015
+ const child = traceExpr(expr.child, bindings);
1016
+ return { op: "NOT", ok: !child.ok, child };
1017
+ }
1018
+ return traceAtom(expr.atom, bindings);
1019
+ };
1020
+ var traceDslExpr = (expr, ctx) => traceExpr(expr, rootBindings(ctx));
1021
+
508
1022
  // src/effects/resolve-targets.ts
509
1023
  var isPayloadRef = (value) => {
510
1024
  if (!value || typeof value !== "object") {
@@ -550,7 +1064,23 @@ var resolveSelectorScope = (dynamicTarget, payloads) => {
550
1064
  (value) => value === "BOTH_SIDES" || value === "LEFT_SIDE" || value === "RIGHT_SIDE"
551
1065
  ) ?? "BOTH_SIDES";
552
1066
  };
553
- var matchesCondition = (player, condition, payloads, characterMap) => {
1067
+ var matchesExprCondition = (player, condition, playerSeatMap, characterMap, effector, payloads) => {
1068
+ const dslCtx = buildDslContextFromLifetime({
1069
+ snapshotSeatMap: playerSeatMap,
1070
+ timelines: [],
1071
+ nowTimelineIndex: -1,
1072
+ characterMap,
1073
+ effector,
1074
+ payloads
1075
+ });
1076
+ const decoratedPlayer = dslCtx.players.find((p) => p.seat === player.seat);
1077
+ const bindings = extendBindings(rootBindings(dslCtx), "it", decoratedPlayer ?? player);
1078
+ return evalExpr(condition.expr, bindings);
1079
+ };
1080
+ var matchesCondition = (player, condition, payloads, characterMap, playerSeatMap, effector) => {
1081
+ if (condition.kind === "EXPR") {
1082
+ return matchesExprCondition(player, condition, playerSeatMap, characterMap, effector, payloads);
1083
+ }
554
1084
  if (condition.field === "IS_DEAD") {
555
1085
  const expected = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "boolean");
556
1086
  if (typeof expected !== "boolean") {
@@ -566,6 +1096,18 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
566
1096
  const expectedSeat = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "number");
567
1097
  return typeof expectedSeat === "number" && player.seat === expectedSeat;
568
1098
  }
1099
+ if (condition.field === "ALIGNMENT") {
1100
+ const alignment = player.alignment;
1101
+ if (!alignment) {
1102
+ return false;
1103
+ }
1104
+ if (condition.operator === "IN") {
1105
+ const expectedAlignments = resolveDynamicValue(condition.value, payloads, (value) => Array.isArray(value) && value.every((item) => typeof item === "string"));
1106
+ return Array.isArray(expectedAlignments) && expectedAlignments.includes(alignment);
1107
+ }
1108
+ const expectedAlignment = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
1109
+ return typeof expectedAlignment === "string" && alignment === expectedAlignment;
1110
+ }
569
1111
  const character = characterMap.get(player.characterId);
570
1112
  const kind = character?.kind;
571
1113
  if (!kind) {
@@ -578,15 +1120,17 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
578
1120
  const expectedKind = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
579
1121
  return typeof expectedKind === "string" && kind === expectedKind;
580
1122
  };
581
- var passesWhere = (player, dynamicTarget, payloads, characterMap) => {
1123
+ var passesWhere = (player, dynamicTarget, payloads, characterMap, playerSeatMap, effector) => {
582
1124
  const where = dynamicTarget.where;
583
1125
  const conditions = where?.conditions ?? [];
584
1126
  if (conditions.length === 0) return true;
585
1127
  const isAllMode = where?.mode !== "ANY";
586
- const results = conditions.map((condition) => matchesCondition(player, condition, payloads, characterMap));
1128
+ const results = conditions.map(
1129
+ (condition) => matchesCondition(player, condition, payloads, characterMap, playerSeatMap, effector)
1130
+ );
587
1131
  return isAllMode ? results.every(Boolean) : results.some(Boolean);
588
1132
  };
589
- var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap) => {
1133
+ var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector) => {
590
1134
  const scope = resolveSelectorScope(dynamicTarget, payloads);
591
1135
  if (!anchorSeat) return { left: [], right: [] };
592
1136
  const anchorIdx = sortedPlayers.findIndex((player) => player.seat === anchorSeat);
@@ -597,13 +1141,13 @@ var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, c
597
1141
  if (scope === "LEFT_SIDE" || scope === "BOTH_SIDES") {
598
1142
  for (let step = 1; step < total; step += 1) {
599
1143
  const player = sortedPlayers[(anchorIdx + step) % total];
600
- if (passesWhere(player, dynamicTarget, payloads, characterMap)) left.push(player);
1144
+ if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) left.push(player);
601
1145
  }
602
1146
  }
603
1147
  if (scope === "RIGHT_SIDE" || scope === "BOTH_SIDES") {
604
1148
  for (let step = 1; step < total; step += 1) {
605
1149
  const player = sortedPlayers[(anchorIdx - step + total) % total];
606
- if (passesWhere(player, dynamicTarget, payloads, characterMap)) right.push(player);
1150
+ if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) right.push(player);
607
1151
  }
608
1152
  }
609
1153
  return { left, right };
@@ -677,10 +1221,12 @@ var resolveEffectTargets = ({
677
1221
  const sortedPlayers = getSortedPlayers(playerSeatMap);
678
1222
  const anchorSeat = getAnchorSeat(dynamicTarget, payloads, effector);
679
1223
  if (!dynamicTarget.selector) {
680
- const matches = sortedPlayers.filter((player) => passesWhere(player, dynamicTarget, payloads, characterMap));
1224
+ const matches = sortedPlayers.filter(
1225
+ (player) => passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)
1226
+ );
681
1227
  return pickBalanced({ left: matches, right: [] }, dynamicTarget, payloads);
682
1228
  }
683
- const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap);
1229
+ const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector);
684
1230
  return pickBalanced(sides, dynamicTarget, payloads);
685
1231
  }
686
1232
  return [];
@@ -826,7 +1372,7 @@ var evalPlayerState = (atom, ctx) => {
826
1372
  const single = players[0];
827
1373
  return evalPlayerStateOnPlayer(single, atom, ctx);
828
1374
  };
829
- var isExecutionTimeline = (tl) => {
1375
+ var isExecutionTimeline2 = (tl) => {
830
1376
  if (!tl) return false;
831
1377
  return (tl.operations ?? []).some((op) => op.kind === "execution");
832
1378
  };
@@ -854,7 +1400,7 @@ var evalTime = (atom, ctx) => {
854
1400
  if (spec.event === "NEXT_NIGHT" && tl.time === "night") return false;
855
1401
  if (spec.event === "NEXT_DUSK" && prevTime === "day" && tl.time === "night") return false;
856
1402
  if (spec.event === "NEXT_DAWN" && prevTime === "night" && tl.time === "day") return false;
857
- if (spec.event === "NEXT_EXECUTION" && isExecutionTimeline(tl)) return false;
1403
+ if (spec.event === "NEXT_EXECUTION" && isExecutionTimeline2(tl)) return false;
858
1404
  prevTime = tl.time;
859
1405
  }
860
1406
  return true;
@@ -895,7 +1441,7 @@ var evalGameState = (atom, ctx) => {
895
1441
  if (spec.kind === "EXECUTION_HAPPENED_TODAY") {
896
1442
  const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
897
1443
  if (typeof turn !== "number") return false;
898
- return ctx.timelines.some((tl) => tl.turn === turn && tl.time === "day" && isExecutionTimeline(tl));
1444
+ return ctx.timelines.some((tl) => tl.turn === turn && tl.time === "day" && isExecutionTimeline2(tl));
899
1445
  }
900
1446
  if (spec.kind === "PLAYER_DIED_TODAY") {
901
1447
  const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
@@ -910,7 +1456,7 @@ var evalGameState = (atom, ctx) => {
910
1456
  }
911
1457
  return true;
912
1458
  };
913
- var evalAtom = (atom, ctx) => {
1459
+ var evalAtom2 = (atom, ctx) => {
914
1460
  switch (atom.type) {
915
1461
  case "TIME":
916
1462
  return evalTime(atom, ctx);
@@ -929,18 +1475,30 @@ var evalAtom = (atom, ctx) => {
929
1475
  target: ctx.target,
930
1476
  snapshotSeatMap: ctx.snapshotSeatMap
931
1477
  }) : false;
1478
+ case "DSL": {
1479
+ if (!ctx.snapshotSeatMap) return true;
1480
+ const dslCtx = buildDslContextFromLifetime({
1481
+ snapshotSeatMap: ctx.snapshotSeatMap,
1482
+ timelines: ctx.timelines,
1483
+ nowTimelineIndex: ctx.nowTimelineIndex,
1484
+ characterMap: ctx.characterMap,
1485
+ effector: ctx.effector,
1486
+ payloads: ctx.payloads
1487
+ });
1488
+ return evalDslExpr(atom.expr, dslCtx);
1489
+ }
932
1490
  }
933
1491
  };
934
- var evalExpr = (expr, ctx) => {
1492
+ var evalExpr2 = (expr, ctx) => {
935
1493
  switch (expr.op) {
936
1494
  case "AND":
937
- return expr.children.every((child) => evalExpr(child, ctx));
1495
+ return expr.children.every((child) => evalExpr2(child, ctx));
938
1496
  case "OR":
939
- return expr.children.some((child) => evalExpr(child, ctx));
1497
+ return expr.children.some((child) => evalExpr2(child, ctx));
940
1498
  case "NOT":
941
- return !evalExpr(expr.child, ctx);
1499
+ return !evalExpr2(expr.child, ctx);
942
1500
  case "ATOM":
943
- return evalAtom(expr.atom, ctx);
1501
+ return evalAtom2(expr.atom, ctx);
944
1502
  }
945
1503
  };
946
1504
  var expressionReferencesTarget = (expr) => {
@@ -978,9 +1536,9 @@ var evaluateLifetime = ({
978
1536
  const expr = lifetime.expr;
979
1537
  const dependsOnTarget = expressionReferencesTarget(expr);
980
1538
  if (!dependsOnTarget) {
981
- return evalExpr(expr, { ...baseCtx, target: void 0 }) ? targets : [];
1539
+ return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
982
1540
  }
983
- return targets.filter((target) => evalExpr(expr, { ...baseCtx, target }));
1541
+ return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
984
1542
  };
985
1543
  var evaluatePrecondition = ({
986
1544
  expr,
@@ -1006,9 +1564,9 @@ var evaluatePrecondition = ({
1006
1564
  };
1007
1565
  const dependsOnTarget = expressionReferencesTarget(expr);
1008
1566
  if (!dependsOnTarget) {
1009
- return evalExpr(expr, { ...baseCtx, target: void 0 }) ? targets : [];
1567
+ return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
1010
1568
  }
1011
- return targets.filter((target) => evalExpr(expr, { ...baseCtx, target }));
1569
+ return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
1012
1570
  };
1013
1571
 
1014
1572
  // src/apply-operation.ts
@@ -1093,6 +1651,7 @@ var applyOperationToPlayers = ({
1093
1651
  abilityMap,
1094
1652
  nowTimelineIndex,
1095
1653
  operationInTimelineIdx: record.operationInTimelineIdx,
1654
+ operationTurn: timelines[record.operationInTimelineIdx]?.turn ?? 0,
1096
1655
  makePlayersEffect
1097
1656
  };
1098
1657
  handler(context);
@@ -1330,6 +1889,12 @@ var deriveContext = ({
1330
1889
  const yesterdayDayOperations = timelines.filter((tl) => tl.turn === last.turn - 1 && tl.time === "day").flatMap((tl) => tl.operations ?? []);
1331
1890
  const yesterdayNightOperations = timelines.filter((tl) => tl.turn === last.turn - 1 && tl.time === "night").flatMap((tl) => tl.operations ?? []);
1332
1891
  const todayNominations = timelines.filter((tl) => tl.turn === last.turn && tl.time === "day").flatMap((tl) => tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee }));
1892
+ 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 }));
1893
+ const allNominations = timelines.filter((tl) => tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({
1894
+ nominator: n.nominator,
1895
+ nominee: n.nominee,
1896
+ turn: tl.turn
1897
+ })));
1333
1898
  return {
1334
1899
  currentTurn: last.turn,
1335
1900
  currentPhase: TIME_TO_PHASE[last.time],
@@ -1341,6 +1906,8 @@ var deriveContext = ({
1341
1906
  yesterdayDayOperations,
1342
1907
  yesterdayNightOperations,
1343
1908
  todayNominations,
1909
+ yesterdayNominations,
1910
+ allNominations,
1344
1911
  effectorSeat: candidate.effector,
1345
1912
  effector,
1346
1913
  abilityId: candidate.abilityId,
@@ -1349,6 +1916,14 @@ var deriveContext = ({
1349
1916
  operationTimeMap
1350
1917
  };
1351
1918
  };
1919
+ var nominationsForActionScope = (scope, ctx) => {
1920
+ if (scope === "TONIGHT" || scope === "TODAY") return ctx.todayNominations;
1921
+ if (scope === "YESTERDAY") return ctx.yesterdayNominations;
1922
+ return ctx.allNominations.map((n) => ({ nominator: n.nominator, nominee: n.nominee }));
1923
+ };
1924
+ var extractPayloadSeats = (op) => (op.payloads ?? []).flatMap(
1925
+ (v) => typeof v === "number" ? [v] : Array.isArray(v) ? v.filter((x) => typeof x === "number") : []
1926
+ );
1352
1927
  var operationsForActionScope = (scope, ctx) => {
1353
1928
  if (scope === "TONIGHT") return ctx.tonightOperations;
1354
1929
  if (scope === "TODAY") return ctx.todayOperations;
@@ -1404,23 +1979,37 @@ var evalAtomAction = (atom, ctx) => {
1404
1979
  if (subjectSeats.size === 0) return `no players match subject ${atom.subject.ref}`;
1405
1980
  const actorSeats = atom.actor ? seatsMatchingTargetRef(atom.actor, ctx) : null;
1406
1981
  if (atom.action === "NOMINATED") {
1407
- const noms = ctx.todayNominations;
1982
+ const noms = nominationsForActionScope(atom.scope, ctx);
1408
1983
  const found = noms.some(
1409
1984
  (n) => subjectSeats.has(n.nominee) && (actorSeats === null || actorSeats.has(n.nominator))
1410
1985
  );
1411
- return found ? null : "no matching NOMINATED event in today";
1986
+ return found ? null : "no matching NOMINATED event in scope";
1987
+ }
1988
+ if (atom.action === "NOMINATES") {
1989
+ const noms = nominationsForActionScope(atom.scope, ctx);
1990
+ const found = noms.some(
1991
+ (n) => subjectSeats.has(n.nominator) && (actorSeats === null || actorSeats.has(n.nominee))
1992
+ );
1993
+ return found ? null : "no matching NOMINATES event in scope";
1412
1994
  }
1413
1995
  const scoped = operationsForActionScope(atom.scope, ctx);
1414
1996
  if (atom.action === "CHOSEN_AS_TARGET") {
1415
1997
  const found = scoped.some((op) => {
1416
1998
  if (actorSeats !== null && !actorSeats.has(op.effector)) return false;
1417
- const payloadSeats = (op.payloads ?? []).flatMap(
1418
- (v) => typeof v === "number" ? [v] : Array.isArray(v) ? v.filter((x) => typeof x === "number") : []
1419
- );
1999
+ const payloadSeats = extractPayloadSeats(op);
1420
2000
  return payloadSeats.some((s) => subjectSeats.has(s));
1421
2001
  });
1422
2002
  return found ? null : "no matching CHOSEN_AS_TARGET in scope";
1423
2003
  }
2004
+ if (atom.action === "CHOOSES_TARGET") {
2005
+ const found = scoped.some((op) => {
2006
+ if (!subjectSeats.has(op.effector)) return false;
2007
+ if (actorSeats === null) return true;
2008
+ const payloadSeats = extractPayloadSeats(op);
2009
+ return payloadSeats.some((s) => actorSeats.has(s));
2010
+ });
2011
+ return found ? null : "no matching CHOOSES_TARGET in scope";
2012
+ }
1424
2013
  const turnsInScope = (() => {
1425
2014
  if (atom.scope === "TONIGHT" || atom.scope === "TODAY") return [ctx.currentTurn];
1426
2015
  if (atom.scope === "YESTERDAY") return [ctx.currentTurn - 1];
@@ -1450,6 +2039,18 @@ var evalAtomAction = (atom, ctx) => {
1450
2039
  }
1451
2040
  return "no matching EXECUTED in scope";
1452
2041
  }
2042
+ if (atom.action === "EXECUTES") {
2043
+ 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 }));
2044
+ const found = scopedNoms.some((n) => {
2045
+ if (!subjectSeats.has(n.nominator)) return false;
2046
+ if (actorSeats !== null && !actorSeats.has(n.nominee)) return false;
2047
+ const executed = ctx.players.find(
2048
+ (p) => p.seat === n.nominee && p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === n.turn
2049
+ );
2050
+ return Boolean(executed);
2051
+ });
2052
+ return found ? null : "no matching EXECUTES in scope";
2053
+ }
1453
2054
  void scoped;
1454
2055
  return `unknown action ${atom.action}`;
1455
2056
  };
@@ -1486,7 +2087,7 @@ var evalAtomGlobalCount = (atom, ctx) => {
1486
2087
  }
1487
2088
  return compareCount2(actual, atom.operator, atom.value) ? null : `global count ${atom.subject}=${actual} fails ${atom.operator} ${atom.value}`;
1488
2089
  };
1489
- var evalAtom2 = (atom, ctx, customResolver) => {
2090
+ var evalAtom3 = (atom, ctx, customResolver) => {
1490
2091
  if (atom.type === "TIME") return evalAtomTime(atom, ctx);
1491
2092
  if (atom.type === "STATE") return evalAtomState(atom, ctx);
1492
2093
  if (atom.type === "ACTION") return evalAtomAction(atom, ctx);
@@ -1496,12 +2097,16 @@ var evalAtom2 = (atom, ctx, customResolver) => {
1496
2097
  if (!customResolver) return `no custom resolver registered for ${atom.resolverId}`;
1497
2098
  return customResolver({ resolverId: atom.resolverId, args: atom.args }, ctx) ? null : `custom resolver ${atom.resolverId} returned false`;
1498
2099
  }
2100
+ if (atom.type === "DSL") {
2101
+ const dslCtx = buildDslContextFromDerived(ctx);
2102
+ return evalDslExpr(atom.expr, dslCtx) ? null : "DSL expression returned false";
2103
+ }
1499
2104
  return "unknown atom";
1500
2105
  };
1501
- var evalExpr2 = (expr, ctx, customResolver) => {
2106
+ var evalExpr3 = (expr, ctx, customResolver) => {
1502
2107
  if (expr.op === "AND") {
1503
2108
  for (const child of expr.children) {
1504
- const reason = evalExpr2(child, ctx, customResolver);
2109
+ const reason = evalExpr3(child, ctx, customResolver);
1505
2110
  if (reason) return reason;
1506
2111
  }
1507
2112
  return null;
@@ -1509,17 +2114,17 @@ var evalExpr2 = (expr, ctx, customResolver) => {
1509
2114
  if (expr.op === "OR") {
1510
2115
  const reasons = [];
1511
2116
  for (const child of expr.children) {
1512
- const reason = evalExpr2(child, ctx, customResolver);
2117
+ const reason = evalExpr3(child, ctx, customResolver);
1513
2118
  if (!reason) return null;
1514
2119
  reasons.push(reason);
1515
2120
  }
1516
2121
  return `OR: all branches failed (${reasons.join("; ")})`;
1517
2122
  }
1518
2123
  if (expr.op === "NOT") {
1519
- const reason = evalExpr2(expr.child, ctx, customResolver);
2124
+ const reason = evalExpr3(expr.child, ctx, customResolver);
1520
2125
  return reason ? null : "NOT: inner expression was true";
1521
2126
  }
1522
- return evalAtom2(expr.atom, ctx, customResolver);
2127
+ return evalAtom3(expr.atom, ctx, customResolver);
1523
2128
  };
1524
2129
  var canInvokeAbility = ({
1525
2130
  ability,
@@ -1539,23 +2144,47 @@ var canInvokeAbility = ({
1539
2144
  if (derived.effector?.retainsAbility === "ALL") {
1540
2145
  return { allowed: true };
1541
2146
  }
1542
- const reason = evalExpr2(window, derived, customResolver);
2147
+ const reason = evalExpr3(window, derived, customResolver);
1543
2148
  return reason ? { allowed: false, reason } : { allowed: true };
1544
2149
  };
2150
+
2151
+ // src/trigger-action.ts
2152
+ var ACTIVE_TRIGGER_ACTIONS = /* @__PURE__ */ new Set([
2153
+ "NOMINATES",
2154
+ "CHOOSES_TARGET",
2155
+ "EXECUTES"
2156
+ ]);
2157
+ var isActiveTriggerAction = (action) => ACTIVE_TRIGGER_ACTIONS.has(action);
1545
2158
  export {
2159
+ ACTIVE_TRIGGER_ACTIONS,
2160
+ DSL_CATALOG,
2161
+ DSL_ITEM_FIELDS,
2162
+ DSL_OPERATORS,
1546
2163
  adjustValueAsNumber,
1547
2164
  adjustValueAsNumberArray,
2165
+ applyCountOperator,
1548
2166
  applyOperationToPlayers,
2167
+ applyOperator,
2168
+ buildDslContextFromDerived,
2169
+ buildDslContextFromLifetime,
1549
2170
  buildPlayerSeatMap,
1550
2171
  canInvokeAbility,
1551
2172
  copyPlayers,
1552
2173
  deriveContext,
2174
+ evalDslExpr,
2175
+ evalExpr as evalDslExprWithBindings,
2176
+ evaluatePath,
2177
+ extendBindings,
1553
2178
  getSourceValue,
1554
2179
  getValueFromPayloads,
2180
+ isActiveTriggerAction,
1555
2181
  isNumberOrNumberArray,
1556
2182
  isPlayerReminderArray,
1557
2183
  isReminder,
1558
2184
  resolveSourceValue,
1559
2185
  resolveTargetRef,
2186
+ rootBindings,
2187
+ traceDslExpr,
2188
+ traceExpr,
1560
2189
  transformEmptyArray
1561
2190
  };