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