@adam0white/human-framework 2.9.0 → 2.10.0

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/will/will.js CHANGED
@@ -562,10 +562,49 @@ const voiceTerm = (c, voiceId) => c?.terms.find((t) => t.source === `suggestion:
562
562
  * group-advice findings only in shape (advice is weighed by trust in each adviser, not by headcount); it does not model conformity to a majority, persuasion between the voices, or the person asking for advice.
563
563
  */
564
564
  function evaluate(p, input, ctx, given, draw) {
565
- const W = WILL_DEFAULTS;
566
565
  const cmd = ctx.command;
567
566
  // A commanding voice is not also weighed as a suggestion in the same decision.
568
567
  const suggestions = cmd ? given.filter((s) => s.voiceId !== cmd.voiceId) : given;
568
+ const prepared = prepareVetoes(p, input, ctx, suggestions);
569
+ const { considered, affById } = prepared;
570
+ const selection = selectOption(p, considered, ctx, draw);
571
+ const ev = {
572
+ chosenId: selection.winner?.affordanceId ?? null,
573
+ considered,
574
+ resolutions: [],
575
+ autonomyDelta: 0,
576
+ voices: [],
577
+ };
578
+ if (cmd && arbitrateCommand(p, ctx, cmd, suggestions, prepared, selection, ev))
579
+ return ev;
580
+ if (suggestions.length === 0)
581
+ return withCommandRefusal(ev, cmd);
582
+ if (inBreak(p)) {
583
+ // During a mental break no voice reaches them; nothing is counted for or against any of them.
584
+ ev.resolutions = suggestions.map((s) => ({
585
+ voiceId: s.voiceId,
586
+ verdict: 'refused',
587
+ kind: 'cannot',
588
+ reason: 'break',
589
+ says: '',
590
+ }));
591
+ ev.voices = suggestions.map((s) => ({ id: s.voiceId, pressure: 0, accepted: 0, refused: 0 }));
592
+ if (ev.resolutions.length === 1)
593
+ ev.suggestion = ev.resolutions[0];
594
+ return withCommandRefusal(ev, cmd);
595
+ }
596
+ const judged = judgeVoices(p, ctx, suggestions, prepared, selection);
597
+ arbitrateInsistence(p, judged, considered, affById, ev);
598
+ ev.resolutions = judged.map((j) => j.res);
599
+ ev.voices = judged.map((j) => j.side);
600
+ const chosen = considered.find((c) => c.affordanceId === ev.chosenId);
601
+ ev.suggestion =
602
+ creditedResolution(ev.resolutions, chosen) ??
603
+ (ev.resolutions.length === 1 ? ev.resolutions[0] : undefined);
604
+ return withCommandRefusal(ev, cmd);
605
+ }
606
+ /** Apply capacity, conscience, per-voice distrust and omission vetoes, in that order. */
607
+ function prepareVetoes(p, input, ctx, suggestions) {
569
608
  const affById = new Map(ctx.affordances.map((a) => [a.id, a]));
570
609
  const duties = closingDuties(p, ctx);
571
610
  const servesDuty = (aff) => duties.some((c) => keepsDuty(p, c, aff, ctx.now));
@@ -609,6 +648,11 @@ function evaluate(p, input, ctx, given, draw) {
609
648
  out.vetoed = veto;
610
649
  return out;
611
650
  });
651
+ return { affById, duties, baseVeto, distrustOf, considered };
652
+ }
653
+ /** Select once using the supplied draw; prediction passes no draw and stays deterministic. */
654
+ function selectOption(p, considered, ctx, draw) {
655
+ const W = WILL_DEFAULTS;
612
656
  // Selection: inertia for the current activity, then argmax or softmax.
613
657
  const live = considered.filter((c) => !c.vetoed);
614
658
  const current = p.activity;
@@ -636,67 +680,55 @@ function evaluate(p, input, ctx, given, draw) {
636
680
  }
637
681
  }
638
682
  }
639
- const ev = {
640
- chosenId: winner?.affordanceId ?? null,
641
- considered,
642
- resolutions: [],
643
- autonomyDelta: 0,
644
- voices: [],
645
- };
646
- if (cmd) {
647
- const co = commandOutcome(p, considered, ctx, cmd, winner, score, baseVeto, duties);
648
- ev.command = co;
649
- if (co.holds && co.targetAffordanceId !== undefined) {
650
- // Direct control: the commanded option is done; every other voice is set aside (no counters) and a
651
- // distrust veto another voice caused does not stand in the way.
652
- ev.chosenId = co.targetAffordanceId;
653
- for (const c of considered)
654
- if (c.vetoed?.reason === 'distrust')
655
- delete c.vetoed;
656
- const res = {
657
- voiceId: cmd.voiceId,
658
- verdict: 'commanded',
659
- reason: 'command',
660
- says: '',
661
- margin: co.margin,
662
- };
663
- if (co.ownAffordanceId !== null && co.ownAffordanceId !== co.targetAffordanceId)
664
- res.insteadAffordanceId = co.ownAffordanceId;
665
- const all = [
666
- res,
667
- ...suggestions.map((s) => ({
668
- voiceId: s.voiceId,
669
- verdict: 'refused',
670
- kind: 'cannot',
671
- reason: 'commanded',
672
- says: '',
673
- })),
674
- ].sort((a, b) => cmpStr(a.voiceId, b.voiceId));
675
- ev.resolutions = all;
676
- ev.voices = all.map((r) => ({ id: r.voiceId, pressure: 0, accepted: 0, refused: 0 }));
677
- ev.suggestion = res;
678
- return ev;
679
- }
680
- }
681
- if (suggestions.length === 0)
682
- return withCommandRefusal(ev, cmd);
683
- if (inBreak(p)) {
684
- // During a mental break no voice reaches them; nothing is counted for or against any of them.
685
- ev.resolutions = suggestions.map((s) => ({
686
- voiceId: s.voiceId,
687
- verdict: 'refused',
688
- kind: 'cannot',
689
- reason: 'break',
683
+ return { winner, ranked, score };
684
+ }
685
+ /** Return true when a command holds and completes the evaluation ahead of all suggestions. */
686
+ function arbitrateCommand(p, ctx, cmd, suggestions, prepared, selection, ev) {
687
+ const { considered, baseVeto, duties } = prepared;
688
+ const { winner, score } = selection;
689
+ const co = commandOutcome(p, considered, ctx, cmd, winner, score, baseVeto, duties);
690
+ ev.command = co;
691
+ if (co.holds && co.targetAffordanceId !== undefined) {
692
+ // Direct control: the commanded option is done; every other voice is set aside (no counters) and a
693
+ // distrust veto another voice caused does not stand in the way.
694
+ ev.chosenId = co.targetAffordanceId;
695
+ for (const c of considered)
696
+ if (c.vetoed?.reason === 'distrust')
697
+ delete c.vetoed;
698
+ const res = {
699
+ voiceId: cmd.voiceId,
700
+ verdict: 'commanded',
701
+ reason: 'command',
690
702
  says: '',
691
- }));
692
- ev.voices = suggestions.map((s) => ({ id: s.voiceId, pressure: 0, accepted: 0, refused: 0 }));
693
- if (ev.resolutions.length === 1)
694
- ev.suggestion = ev.resolutions[0];
695
- return withCommandRefusal(ev, cmd);
703
+ margin: co.margin,
704
+ };
705
+ if (co.ownAffordanceId !== null && co.ownAffordanceId !== co.targetAffordanceId)
706
+ res.insteadAffordanceId = co.ownAffordanceId;
707
+ const all = [
708
+ res,
709
+ ...suggestions.map((s) => ({
710
+ voiceId: s.voiceId,
711
+ verdict: 'refused',
712
+ kind: 'cannot',
713
+ reason: 'commanded',
714
+ says: '',
715
+ })),
716
+ ].sort((a, b) => cmpStr(a.voiceId, b.voiceId));
717
+ ev.resolutions = all;
718
+ ev.voices = all.map((r) => ({ id: r.voiceId, pressure: 0, accepted: 0, refused: 0 }));
719
+ ev.suggestion = res;
720
+ return true;
696
721
  }
722
+ return false;
723
+ }
724
+ /** Judge each voice against the same selected option before resolving competing insistence. */
725
+ function judgeVoices(p, ctx, suggestions, prepared, selection) {
726
+ const W = WILL_DEFAULTS;
727
+ const { considered, affById, distrustOf } = prepared;
728
+ const { winner, ranked, score } = selection;
697
729
  const winnerAff = winner ? affById.get(winner.affordanceId) : undefined;
698
730
  const reason = dominantTerm(winner);
699
- const judged = suggestions.map((s) => {
731
+ return suggestions.map((s) => {
700
732
  const suggested = considered.filter((c) => {
701
733
  const aff = affById.get(c.affordanceId);
702
734
  return aff !== undefined && suggestionTargets(s, aff);
@@ -815,6 +847,10 @@ function evaluate(p, input, ctx, given, draw) {
815
847
  : { affordanceId: bestSuggested.affordanceId, label: 'later' },
816
848
  }));
817
849
  });
850
+ }
851
+ /** Settle competing insistence in the existing voice order, preserving equal-trust/strength ties. */
852
+ function arbitrateInsistence(p, judged, considered, affById, ev) {
853
+ const W = WILL_DEFAULTS;
818
854
  // Rule (a): at most one insisted option is complied with.
819
855
  const insisting = judged.filter((j) => j.comply !== undefined);
820
856
  let forcer;
@@ -882,13 +918,6 @@ function evaluate(p, input, ctx, given, draw) {
882
918
  }
883
919
  }
884
920
  }
885
- ev.resolutions = judged.map((j) => j.res);
886
- ev.voices = judged.map((j) => j.side);
887
- const chosen = considered.find((c) => c.affordanceId === ev.chosenId);
888
- ev.suggestion =
889
- creditedResolution(ev.resolutions, chosen) ??
890
- (ev.resolutions.length === 1 ? ev.resolutions[0] : undefined);
891
- return withCommandRefusal(ev, cmd);
892
921
  }
893
922
  /** Add the commanding voice's refusal (a command that did not hold) to the resolutions, in voice-id order. */
894
923
  function withCommandRefusal(ev, cmd) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adam0white/human-framework",
3
- "version": "2.9.0",
3
+ "version": "2.10.0",
4
4
  "description": "Deterministic, explainable simulated people: body, needs, affect, memory, beliefs, skills, habits, relationships, values and a will that can refuse a suggestion",
5
5
  "keywords": [
6
6
  "simulation",