> ## Documentation Index
> Fetch the complete documentation index at: https://docs.walletwall.org/llms.txt
> Use this file to discover all available pages before exploring further.

# WallRoom

> Step inside a controlled synthetic world and change one condition to see how vault migration readiness responds.

export const describeWallRoomTransition = (before, after) => {
  const comparison = compareWallRoomWorlds(before, after);
  const consequence = comparison.decisionChanged ? 'decision' : comparison.reasoningChanged ? 'reasoning' : 'none';
  const SIGNER_FIELD_LABEL = {
    confirmed: 'Confirmed able',
    possible: 'Not ruled out',
    unable: 'Confirmed unable',
    unobserved: 'Unobserved'
  };
  const consequenceText = consequence === 'none' ? 'Migration path stayed ' + after?.migrationPath + '.' : comparison.verdictChanged ? 'Migration path ' + before?.migrationPath + ' → ' + after?.migrationPath + '.' : consequence === 'decision' ? 'Migration path stayed ' + after?.migrationPath + ', but what is blocking it changed.' : 'Migration path stayed ' + after?.migrationPath + '; the evidence moved but not enough to change it.';
  const affectedText = comparison.changedStations.length > 0 ? comparison.changedStations.map(entry => entry.label + ' (' + entry.from + ' → ' + entry.to + ')').join('; ') : comparison.signerMathChanged ? 'Signer evidence (' + Object.entries(comparison.signerMathDelta).map(([field, {from, to}]) => SIGNER_FIELD_LABEL[field] + ' ' + from + ' → ' + to).join(', ') + ')' : 'Nothing — no requirement changed state in this world.';
  return {
    fromVerdict: before?.migrationPath,
    toVerdict: after?.migrationPath,
    consequence,
    consequenceText,
    affectedText,
    comparison
  };
};

export const buildWallRoomSplit = (world, variableId) => {
  const variable = WR_VARIABLES.find(entry => entry.id === variableId);
  if (!variable || !world) return null;
  const current = world[variableId];
  const counterfactual = variable.kind === 'boolean' ? !current : variable.counterfactual[current];
  if (counterfactual === undefined || counterfactual === current) return null;
  const readable = value => variable.kind === 'boolean' ? value ? 'Yes' : 'No' : (variable.options.find(option => option.value === value) || ({})).label || String(value);
  const worldB = {
    ...world,
    [variableId]: counterfactual
  };
  const a = resolveWallRoomScenario(world);
  const b = resolveWallRoomScenario(worldB);
  const comparison = compareWallRoomWorlds(a, b);
  const SIGNER_FIELD_LABEL = {
    confirmed: 'Confirmed able',
    possible: 'Not ruled out',
    unable: 'Confirmed unable',
    unobserved: 'Unobserved'
  };
  const deltaText = comparison.decisionChanged ? (comparison.verdictChanged ? 'Migration path ' + a.migrationPath + ' → ' + b.migrationPath + '. ' : 'Both worlds reach ' + a.migrationPath + ', but not for the same reason. ') + (!comparison.verdictChanged && comparison.blockersChanged ? 'World A is held by ' + (a.materialBlocker ? a.materialBlocker.label : 'nothing') + '; World B by ' + (b.materialBlocker ? b.materialBlocker.label : 'nothing') + '. ' : '') + b.headline : comparison.reasoningChanged ? 'Outcome unchanged — both worlds reach ' + a.migrationPath + '. The supporting evidence moved' + (Object.keys(comparison.signerMathDelta).length > 0 ? ': ' + Object.entries(comparison.signerMathDelta).map(([field, {from, to}]) => SIGNER_FIELD_LABEL[field] + ' ' + from + ' → ' + to).join(', ') + '.' : '.') + ' That did not cross the boundary needed to change the outcome.' : 'None. This condition does not change the outcome or the supporting evidence in this world.';
  return {
    variable,
    changedFrom: current,
    changedTo: counterfactual,
    labelA: readable(current),
    labelB: readable(counterfactual),
    worldA: world,
    worldB,
    a,
    b,
    comparison,
    deltaText
  };
};

export const compareWallRoomWorlds = (before, after) => {
  const stationKey = list => (list || []).map(entry => entry.station || entry.id).sort().join(',');
  const changedStations = [];
  for (const next of after?.requirements || []) {
    const prev = (before?.requirements || []).find(entry => entry.id === next.id);
    if (!prev) continue;
    if (prev.state !== next.state || prev.binding !== next.binding) {
      changedStations.push({
        id: next.id,
        label: next.label,
        from: prev.state,
        to: next.state,
        fromBinding: prev.binding,
        toBinding: next.binding
      });
    }
  }
  const axes = [];
  const verdictChanged = before?.migrationPath !== after?.migrationPath;
  if (verdictChanged) axes.push({
    axis: 'verdict',
    from: before?.migrationPath,
    to: after?.migrationPath
  });
  if (changedStations.length > 0) axes.push({
    axis: 'requirements',
    stations: changedStations
  });
  const blockersChanged = stationKey(before?.blockers) !== stationKey(after?.blockers);
  if (blockersChanged) {
    axes.push({
      axis: 'blockers',
      from: (before?.blockers || []).map(entry => entry.label),
      to: (after?.blockers || []).map(entry => entry.label)
    });
  }
  const unknownsChanged = stationKey(before?.materialUnknowns) !== stationKey(after?.materialUnknowns);
  if (unknownsChanged) {
    axes.push({
      axis: 'materialUnknowns',
      from: (before?.materialUnknowns || []).map(entry => entry.label),
      to: (after?.materialUnknowns || []).map(entry => entry.label)
    });
  }
  const SIGNER_MATH_FIELDS = ['confirmed', 'possible', 'unable', 'unobserved'];
  const signerMathDelta = {};
  for (const field of SIGNER_MATH_FIELDS) {
    const from = before?.signerMath?.[field];
    const to = after?.signerMath?.[field];
    if (from !== to) signerMathDelta[field] = {
      from,
      to
    };
  }
  const signerMathChanged = Object.keys(signerMathDelta).length > 0;
  if (signerMathChanged) axes.push({
    axis: 'signerMath',
    delta: signerMathDelta
  });
  const decisionChanged = verdictChanged || blockersChanged || unknownsChanged;
  const reasoningChanged = changedStations.length > 0 || signerMathChanged;
  return {
    differs: decisionChanged || reasoningChanged,
    decisionChanged,
    reasoningChanged,
    axes,
    changedStations,
    verdictChanged,
    blockersChanged,
    unknownsChanged,
    signerMathChanged,
    signerMathDelta
  };
};

export const resolveWallRoomScenario = input => {
  const TOTAL = 5;
  const QUORUM = 3;
  const SELF = 2;
  const CUSTODIED = 3;
  const AVAILABLE = 'AVAILABLE';
  const PARTIAL = 'PARTIAL';
  const BLOCKED = 'BLOCKED';
  const UNKNOWN = 'UNKNOWN';
  const SATISFIED = 'SATISFIED';
  const UNSATISFIED = 'UNSATISFIED';
  const REQUIRED = 'REQUIRED';
  const NOT_EXERCISED = 'NOT_EXERCISED';
  const ownField = key => input && typeof input === 'object' && Object.hasOwn(input, key) ? input[key] : undefined;
  const readTriState = (key, validValues) => {
    const value = ownField(key);
    return validValues.includes(value) ? value : 'unknown';
  };
  const readBoolean = (key, fallback) => {
    const value = ownField(key);
    if (value === undefined) return fallback;
    if (value === true || value === false) return value;
    throw new TypeError(`resolveWallRoomScenario: "${key}" must be a literal boolean or omitted (received ${JSON.stringify(value)}). ` + 'Absence uses the documented scenario default; any other value is rejected rather than coerced.');
  };
  const replacementRequired = readBoolean('replacementRequired', false);
  const custodianSupport = readTriState('custodianSupport', ['supported', 'unsupported', 'unknown']);
  const hardwareCompatibility = readTriState('hardwareCompatibility', ['compatible', 'incompatible', 'unknown']);
  const rotateInPlace = readBoolean('rotateInPlace', true);
  const upgradePathAvailable = readBoolean('upgradePathAvailable', true);
  const governanceQuorum = readBoolean('governanceQuorum', true);
  const custodyState = custodianSupport === 'supported' ? SATISFIED : custodianSupport === 'unsupported' ? UNSATISFIED : UNKNOWN;
  const selfAdoptionState = hardwareCompatibility === 'compatible' ? SATISFIED : hardwareCompatibility === 'incompatible' ? UNSATISFIED : UNKNOWN;
  const mechanismState = rotateInPlace || upgradePathAvailable ? SATISFIED : UNSATISFIED;
  const upgradeState = upgradePathAvailable ? SATISFIED : UNSATISFIED;
  const governanceState = governanceQuorum ? SATISFIED : UNSATISFIED;
  const confirmed = (selfAdoptionState === SATISFIED ? SELF : 0) + (custodyState === SATISFIED ? CUSTODIED : 0);
  const possible = (selfAdoptionState === UNSATISFIED ? 0 : SELF) + (custodyState === UNSATISFIED ? 0 : CUSTODIED);
  const unableSigners = (selfAdoptionState === UNSATISFIED ? SELF : 0) + (custodyState === UNSATISFIED ? CUSTODIED : 0);
  const unobservedSigners = (selfAdoptionState === UNKNOWN ? SELF : 0) + (custodyState === UNKNOWN ? CUSTODIED : 0);
  const coverageState = confirmed >= TOTAL ? SATISFIED : possible < QUORUM ? UNSATISFIED : confirmed >= QUORUM ? PARTIAL : UNKNOWN;
  const dependencyState = replacementRequired ? REQUIRED : SATISFIED;
  const blockers = [];
  const unknowns = [];
  const capabilities = [];
  if (governanceState === UNSATISFIED) {
    blockers.push({
      id: 'governanceAvailability',
      station: 'governanceAvailability',
      label: 'Governance authority',
      because: 'No governance quorum remains, so no change to the signing arrangement can be authorised at all.'
    });
  } else {
    capabilities.push({
      id: 'governanceAvailability',
      label: 'Governance authority',
      because: 'A quorum remains able to authorise a change.'
    });
  }
  if (mechanismState === UNSATISFIED) {
    blockers.push({
      id: 'rotationMechanism',
      station: 'rotationMechanism',
      label: 'Rotation mechanism',
      because: 'Neither in-place key rotation nor a contract upgrade path remains, so an authorised change has no mechanism to carry it.'
    });
  } else {
    capabilities.push({
      id: 'rotationMechanism',
      label: 'Rotation mechanism',
      because: rotateInPlace && upgradePathAvailable ? 'Both in-place rotation and the upgrade path remain available.' : rotateInPlace ? 'In-place key rotation remains available.' : 'The contract upgrade path remains available.'
    });
  }
  if (replacementRequired) {
    if (coverageState === UNSATISFIED) {
      blockers.push({
        id: 'signerCoverage',
        station: custodyState === UNSATISFIED && selfAdoptionState !== UNSATISFIED ? 'custodySupport' : 'signerCoverage',
        label: custodyState === UNSATISFIED && selfAdoptionState !== UNSATISFIED ? 'Custody dependency' : 'Signer coverage',
        because: custodyState === UNSATISFIED && selfAdoptionState === UNSATISFIED ? 'No signer is able to adopt the replacement scheme, so quorum is unreachable.' : custodyState === UNSATISFIED ? 'The ' + CUSTODIED + ' externally custodied signers cannot adopt the replacement scheme. Only ' + SELF + ' self-controlled signers remain, below the quorum of ' + QUORUM + '.' : 'The ' + SELF + ' self-controlled signers cannot adopt the replacement scheme, and the ' + CUSTODIED + ' custodied signers are not confirmed able either, so quorum is out of reach.'
      });
    } else if (coverageState === PARTIAL) {
      capabilities.push({
        id: 'signerCoverage',
        label: 'Signer coverage',
        because: 'Quorum is confirmed at ' + confirmed + ' of ' + TOTAL + ' signers, though not every signer is accounted for.'
      });
    } else if (coverageState === SATISFIED) {
      capabilities.push({
        id: 'signerCoverage',
        label: 'Signer coverage',
        because: 'All ' + TOTAL + ' signers are confirmed able to adopt the replacement scheme.'
      });
    }
  }
  if (custodyState === UNKNOWN) {
    unknowns.push({
      id: 'custodySupport',
      station: 'custodySupport',
      label: 'Custody support',
      material: replacementRequired,
      because: replacementRequired ? 'The ' + CUSTODIED + ' externally custodied signers are required to reach the quorum of ' + QUORUM + ', and their support for the replacement scheme has not been observed in this world.' : 'Not observed in this world. It is not binding while the current signing scheme stays valid.'
    });
  }
  if (selfAdoptionState === UNKNOWN) {
    unknowns.push({
      id: 'selfAdoption',
      station: 'signerCoverage',
      label: 'Signer hardware compatibility',
      material: replacementRequired,
      because: replacementRequired ? 'Whether the ' + SELF + ' self-controlled signers can hold the replacement scheme has not been observed in this world.' : 'Not observed in this world. It is not binding while the current signing scheme stays valid.'
    });
  }
  let migrationPath;
  if (blockers.length > 0) {
    migrationPath = BLOCKED;
  } else if (!replacementRequired) {
    migrationPath = AVAILABLE;
  } else if (coverageState === UNKNOWN) {
    migrationPath = UNKNOWN;
  } else if (coverageState === PARTIAL) {
    migrationPath = PARTIAL;
  } else {
    migrationPath = AVAILABLE;
  }
  const materialBlocker = blockers.length > 0 ? blockers[0] : null;
  const materialUnknowns = unknowns.filter(entry => entry.material);
  let headline;
  if (migrationPath === BLOCKED) {
    headline = materialBlocker.because;
  } else if (migrationPath === UNKNOWN) {
    headline = 'Quorum is not confirmed and not ruled out: ' + confirmed + ' of ' + TOTAL + ' signers are confirmed able to adopt the replacement, ' + possible + ' are not ruled out, and the quorum is ' + QUORUM + '. This world does not contain enough information to conclude.';
  } else if (migrationPath === PARTIAL) {
    const stem = 'Quorum is confirmed at ' + confirmed + ' of ' + TOTAL + ' signers, which is enough to enact the migration, but not every signer is accounted for. ';
    if (unableSigners > 0 && unobservedSigners > 0) {
      headline = stem + unableSigners + ' of the rest are confirmed unable to adopt the replacement, and the other ' + unobservedSigners + ' have not been observed either way.';
    } else if (unableSigners > 0) {
      headline = stem + 'The remaining ' + unableSigners + ' are confirmed unable to adopt the replacement and would be left behind.';
    } else {
      headline = stem + 'Whether the remaining ' + unobservedSigners + ' can follow has not been observed in this world, so it stays unknown.';
    }
  } else if (replacementRequired) {
    headline = 'Every signer is confirmed able to adopt the replacement, authority remains, and a mechanism remains to carry the change.';
  } else {
    headline = 'The current signing scheme stays valid, governance can still authorise a change, and at least one mechanism remains to carry one.';
  }
  const coverageReason = coverageState === SATISFIED ? null : coverageState === PARTIAL ? 'Quorum is confirmed at ' + confirmed + ' of ' + TOTAL + ', but ' + (unableSigners > 0 ? unableSigners + ' signer(s) are confirmed unable' : '') + (unableSigners > 0 && unobservedSigners > 0 ? ' and ' : '') + (unobservedSigners > 0 ? unobservedSigners + ' signer(s) are unobserved' : '') + '.' : coverageState === UNSATISFIED ? 'Quorum of ' + QUORUM + ' is out of reach: only ' + possible + ' of ' + TOTAL + ' signers are not ruled out.' : 'Quorum is neither confirmed (' + confirmed + ') nor ruled out (' + possible + ' not excluded, quorum ' + QUORUM + '). This world has not observed enough to say.';
  const requirements = [{
    id: 'cryptographicDependency',
    label: 'Signing dependency',
    state: dependencyState,
    detail: replacementRequired ? 'Replacement required' : 'Current scheme valid',
    binding: replacementRequired,
    reason: replacementRequired ? 'This world requires a replacement signing scheme. That is a requirement of the scenario, not a fault — it is what makes signer adoption binding.' : null
  }, {
    id: 'signerCoverage',
    label: 'Signer coverage',
    state: replacementRequired ? coverageState : NOT_EXERCISED,
    detail: replacementRequired ? confirmed + ' of ' + TOTAL + ' confirmed · quorum ' + QUORUM : 'Not exercised',
    binding: replacementRequired,
    reason: replacementRequired ? coverageReason : 'The current signing scheme stays valid in this world, so no signer is asked to adopt anything. This is not an unknown — it is a requirement the world never exercises.'
  }, {
    id: 'custodySupport',
    label: 'Custody support',
    state: custodyState,
    detail: custodyState === SATISFIED ? CUSTODIED + ' of ' + TOTAL + ' signers · confirmed able' : custodyState === UNSATISFIED ? CUSTODIED + ' of ' + TOTAL + ' signers · confirmed unable' : CUSTODIED + ' of ' + TOTAL + ' signers · not observed',
    binding: replacementRequired,
    reason: custodyState === SATISFIED ? null : custodyState === UNSATISFIED ? replacementRequired ? 'The ' + CUSTODIED + ' externally custodied signers cannot adopt the replacement scheme, and quorum is ' + QUORUM + '.' : 'Confirmed unable to adopt a replacement scheme. Not binding here, because the current scheme stays valid.' : replacementRequired ? 'Not observed in this world, and these ' + CUSTODIED + ' signers are needed to reach the quorum of ' + QUORUM + '.' : 'Not observed in this world. Not binding here, because the current scheme stays valid.'
  }, {
    id: 'rotationMechanism',
    label: 'Rotation mechanism',
    state: mechanismState,
    detail: rotateInPlace ? 'In-place rotation' : upgradePathAvailable ? 'Upgrade path only' : 'No mechanism',
    binding: true,
    reason: mechanismState === SATISFIED ? null : 'Neither in-place key rotation nor a contract upgrade path remains, so an authorised change has nothing to carry it.'
  }, {
    id: 'governanceAvailability',
    label: 'Governance authority',
    state: governanceState,
    detail: governanceQuorum ? 'Quorum available' : 'Quorum lost',
    binding: true,
    reason: governanceState === SATISFIED ? null : 'No governance quorum remains, so no change can be authorised at all.'
  }, {
    id: 'upgradeAuthority',
    label: 'Upgrade authority',
    state: upgradeState,
    detail: upgradePathAvailable ? 'Upgrade path open' : 'Upgrade path closed',
    binding: !rotateInPlace,
    reason: upgradeState === SATISFIED ? null : rotateInPlace ? 'The upgrade path is closed, but in-place key rotation still provides a mechanism, so this alone does not block a migration.' : 'The upgrade path is closed and in-place rotation is unavailable, so no mechanism remains.'
  }];
  return {
    migrationPath,
    requirements,
    blockers,
    unknowns,
    capabilities,
    materialBlocker,
    materialUnknowns,
    headline,
    signerMath: {
      confirmed,
      possible,
      unable: unableSigners,
      unobserved: unobservedSigners,
      quorum: QUORUM,
      total: TOTAL,
      selfControlled: SELF,
      externallyCustodied: CUSTODIED
    },
    binding: replacementRequired
  };
};

export const WR_BASELINE = Object.freeze({
  replacementRequired: false,
  custodianSupport: 'unknown',
  hardwareCompatibility: 'compatible',
  rotateInPlace: true,
  upgradePathAvailable: true,
  governanceQuorum: true
});

export const WR_STATIONS = Object.freeze([{
  id: 'cryptographicDependency',
  label: 'Signing dependency',
  row: 'top'
}, {
  id: 'signerCoverage',
  label: 'Signer coverage',
  row: 'top'
}, {
  id: 'custodySupport',
  label: 'Custody support',
  row: 'top'
}, {
  id: 'rotationMechanism',
  label: 'Rotation mechanism',
  row: 'bottom'
}, {
  id: 'governanceAvailability',
  label: 'Governance authority',
  row: 'bottom'
}, {
  id: 'upgradeAuthority',
  label: 'Upgrade authority',
  row: 'bottom'
}]);

export const WR_VARIABLES = Object.freeze([{
  id: 'replacementRequired',
  kind: 'boolean',
  label: 'Replacement signing scheme required',
  hint: 'A requirement about the world, not a fact about this treasury. Turning it on makes signer adoption binding.',
  baseline: false
}, {
  id: 'custodianSupport',
  kind: 'choice',
  label: 'Custodian supports the replacement scheme',
  hint: 'Three of five signers sit with an external custodian. Quorum is 3, so their support is load-bearing.',
  baseline: 'unknown',
  options: [{
    value: 'supported',
    label: 'Supported — confirmed in this world'
  }, {
    value: 'unsupported',
    label: 'Not supported — confirmed unable'
  }, {
    value: 'unknown',
    label: 'Unknown — not observed in this world'
  }],
  counterfactual: {
    supported: 'unsupported',
    unsupported: 'supported',
    unknown: 'unsupported'
  }
}, {
  id: 'hardwareCompatibility',
  kind: 'choice',
  label: 'Self-controlled signer hardware compatibility',
  hint: 'Whether the two self-controlled signers can hold the replacement scheme.',
  baseline: 'compatible',
  options: [{
    value: 'compatible',
    label: 'Compatible — confirmed in this world'
  }, {
    value: 'incompatible',
    label: 'Incompatible — confirmed unable'
  }, {
    value: 'unknown',
    label: 'Unknown — not observed in this world'
  }],
  counterfactual: {
    compatible: 'incompatible',
    incompatible: 'compatible',
    unknown: 'incompatible'
  }
}, {
  id: 'rotateInPlace',
  kind: 'boolean',
  label: 'Keys can rotate without moving funds',
  hint: 'One of two mechanisms that can carry a migration. Either one is sufficient.',
  baseline: true
}, {
  id: 'upgradePathAvailable',
  kind: 'boolean',
  label: 'Contract upgrade path remains available',
  hint: 'The second mechanism. Losing both leaves no way to enact a change.',
  baseline: true
}, {
  id: 'governanceQuorum',
  kind: 'boolean',
  label: 'Governance quorum remains available',
  hint: 'Authority to authorise the change at all.',
  baseline: true
}]);

export const WR_SCENARIO = Object.freeze({
  id: 'vault-migration',
  room: 'Vault Migration',
  subject: 'Multisig stablecoin treasury',
  environment: 'EVM',
  signingDependency: 'secp256k1',
  totalSigners: 5,
  quorum: 3,
  selfControlledSigners: 2,
  externallyCustodiedSigners: 3
});

export const WallRoom = ({idPrefix = 'wallroom'}) => {
  const STYLES = `
.ww-wr{--ww-wr-bg:#FAFAF0;--ww-wr-panel:#FFF7E8;--ww-wr-floor:#F3EADF;--ww-wr-ink:#2B2118;--ww-wr-muted:#59646A;--ww-wr-line:#B87333;--ww-wr-line-soft:#D9C7B3;--ww-wr-accent:#B84923;--ww-wr-ok:#526246;--ww-wr-warn:#8A5A1F;--ww-wr-risk:#8F2F1D;--ww-wr-unknown:#4A5A63;--ww-wr-required:#B87333;}
@media (prefers-color-scheme:dark){.ww-wr{--ww-wr-bg:#1B1512;--ww-wr-panel:#241C17;--ww-wr-floor:#201A16;--ww-wr-ink:#F2E9DF;--ww-wr-muted:#A7B0B5;--ww-wr-line:#8A5A1F;--ww-wr-line-soft:#4A3B30;--ww-wr-accent:#D9714B;--ww-wr-ok:#9AAB89;--ww-wr-warn:#D6A85A;--ww-wr-risk:#E0836B;--ww-wr-unknown:#8FA3AC;--ww-wr-required:#D6A85A;}}
.light .ww-wr,:root.light .ww-wr{--ww-wr-bg:#FAFAF0;--ww-wr-panel:#FFF7E8;--ww-wr-floor:#F3EADF;--ww-wr-ink:#2B2118;--ww-wr-muted:#59646A;--ww-wr-line:#B87333;--ww-wr-line-soft:#D9C7B3;--ww-wr-accent:#B84923;--ww-wr-ok:#526246;--ww-wr-warn:#8A5A1F;--ww-wr-risk:#8F2F1D;--ww-wr-unknown:#4A5A63;--ww-wr-required:#B87333;}
.dark .ww-wr,:root.dark .ww-wr{--ww-wr-bg:#1B1512;--ww-wr-panel:#241C17;--ww-wr-floor:#201A16;--ww-wr-ink:#F2E9DF;--ww-wr-muted:#A7B0B5;--ww-wr-line:#8A5A1F;--ww-wr-line-soft:#4A3B30;--ww-wr-accent:#D9714B;--ww-wr-ok:#9AAB89;--ww-wr-warn:#D6A85A;--ww-wr-risk:#E0836B;--ww-wr-unknown:#8FA3AC;--ww-wr-required:#D6A85A;}
.ww-wr{background:var(--ww-wr-bg);color:var(--ww-wr-ink);border:2px solid var(--ww-wr-line);border-radius:2px;padding:0;font-size:0.875rem;line-height:1.5;overflow:hidden;}
.ww-wr *{box-sizing:border-box;}
.ww-wr-mono{font-family:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;}
.ww-wr-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:0.5rem 0.9rem;padding:0.7rem 0.9rem;border-bottom:1px solid var(--ww-wr-line);background:var(--ww-wr-panel);}
.ww-wr-title{font-weight:700;letter-spacing:0.14em;text-transform:uppercase;font-size:0.8125rem;margin:0;}
.ww-wr-room{color:var(--ww-wr-muted);font-size:0.8125rem;}
.ww-wr-synth{margin-left:auto;font-family:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:0.6875rem;letter-spacing:0.1em;text-transform:uppercase;color:var(--ww-wr-bg);background:var(--ww-wr-accent);border:1px solid var(--ww-wr-accent);padding:0.15rem 0.45rem;border-radius:2px;}
.ww-wr-disclaimer{padding:0.5rem 0.9rem;border-bottom:1px dashed var(--ww-wr-line-soft);color:var(--ww-wr-muted);font-size:0.8125rem;background:var(--ww-wr-bg);}
.ww-wr-sect{padding:0.9rem;border-bottom:1px solid var(--ww-wr-line-soft);}
.ww-wr-sect:last-child{border-bottom:0;}
.ww-wr-h{font-weight:600;font-size:0.6875rem;letter-spacing:0.12em;text-transform:uppercase;color:var(--ww-wr-muted);margin:0 0 0.6rem;}
.ww-wr-floorplan{background:var(--ww-wr-floor);border:1px solid var(--ww-wr-line-soft);border-radius:2px;padding:0.8rem;background-image:linear-gradient(var(--ww-wr-line-soft) 1px,transparent 1px),linear-gradient(90deg,var(--ww-wr-line-soft) 1px,transparent 1px);background-size:1.5rem 1.5rem;background-position:-1px -1px;}
.ww-wr-bank{display:grid;grid-template-columns:1fr;gap:0.5rem;}
@media (min-width:34rem){.ww-wr-bank{grid-template-columns:repeat(3,1fr);}}
.ww-wr-station{background:var(--ww-wr-panel);border:1px solid var(--ww-wr-line-soft);border-left:3px solid var(--ww-wr-muted);border-radius:2px;padding:0.45rem 0.55rem;transition:border-color 140ms ease,box-shadow 140ms ease,transform 140ms ease;}
.ww-wr-station[data-tone="ok"]{border-left-color:var(--ww-wr-ok);}
.ww-wr-station[data-tone="partial"]{border-left-color:var(--ww-wr-warn);}
.ww-wr-station[data-tone="risk"]{border-left-color:var(--ww-wr-risk);}
.ww-wr-station[data-tone="unknown"]{border-left-color:var(--ww-wr-unknown);border-style:dashed;}
.ww-wr-station[data-tone="required"]{border-left-color:var(--ww-wr-required);}
.ww-wr-station[data-tone="muted"]{border-left-color:var(--ww-wr-line-soft);}
.ww-wr-station[data-tone="risk"][data-binding="yes"]{border-color:var(--ww-wr-risk);box-shadow:0 0 0 2px var(--ww-wr-risk);transform:translateY(-1px);}
.ww-wr-station[data-tone="unknown"][data-binding="yes"]{border-color:var(--ww-wr-unknown);box-shadow:0 0 0 1px var(--ww-wr-unknown);}
.ww-wr-st-why{display:block;margin-top:0.25rem;font-size:0.6875rem;line-height:1.4;color:var(--ww-wr-muted);}
.ww-wr-st-label{display:block;font-size:0.75rem;color:var(--ww-wr-muted);}
.ww-wr-st-state{display:block;font-family:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-weight:600;font-size:0.8125rem;letter-spacing:0.04em;}
.ww-wr-st-detail{display:block;font-size:0.6875rem;color:var(--ww-wr-muted);}
.ww-wr-st-flag{display:inline-block;margin-top:0.2rem;font-family:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:0.625rem;letter-spacing:0.08em;text-transform:uppercase;border:1px solid currentColor;border-radius:2px;padding:0 0.25rem;}
.ww-wr-core{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:0.4rem 0.8rem;margin:0.6rem 0;padding:0.55rem 0.7rem;border:2px solid var(--ww-wr-line);border-radius:2px;background:var(--ww-wr-bg);}
.ww-wr-core-name{font-weight:700;letter-spacing:0.06em;}
.ww-wr-core-meta{font-family:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:0.75rem;color:var(--ww-wr-muted);}
.ww-wr-conduit{height:0.75rem;border-left:1px dashed var(--ww-wr-line-soft);width:1px;margin:0 auto;}
.ww-wr-controls{display:grid;grid-template-columns:1fr;gap:0.55rem;}
@media (min-width:44rem){.ww-wr-controls{grid-template-columns:1fr 1fr;}}
.ww-wr-ctl{border:1px solid var(--ww-wr-line-soft);border-radius:2px;padding:0.5rem 0.6rem;background:var(--ww-wr-panel);}
.ww-wr-ctl[data-changed="yes"]{border-color:var(--ww-wr-accent);box-shadow:inset 3px 0 0 var(--ww-wr-accent);}
.ww-wr-ctl label{font-weight:600;display:block;}
.ww-wr-check{display:flex;align-items:flex-start;gap:0.5rem;}
.ww-wr-check input{margin-top:0.25rem;flex:0 0 auto;width:1rem;height:1rem;accent-color:var(--ww-wr-accent);}
.ww-wr-ctl select{width:100%;margin-top:0.25rem;padding:0.35rem 0.4rem;border:1px solid var(--ww-wr-line-soft);border-radius:2px;background:var(--ww-wr-bg);color:var(--ww-wr-ink);font:inherit;font-size:0.8125rem;}
.ww-wr-hint{display:block;color:var(--ww-wr-muted);font-size:0.75rem;margin-top:0.15rem;}
.ww-wr input:focus-visible,.ww-wr select:focus-visible,.ww-wr button:focus-visible{outline:2px solid var(--ww-wr-accent);outline-offset:2px;}
.ww-wr-out{border:1px solid var(--ww-wr-line-soft);border-left:4px solid var(--ww-wr-accent);border-radius:2px;background:var(--ww-wr-panel);padding:0.7rem 0.8rem;}
.ww-wr-verdict{display:flex;flex-wrap:wrap;align-items:baseline;gap:0.4rem 0.75rem;margin-bottom:0.5rem;}
.ww-wr-verdict-k{font-size:0.75rem;color:var(--ww-wr-muted);}
.ww-wr-verdict-v{font-family:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-weight:700;font-size:1.0625rem;letter-spacing:0.08em;}
.ww-wr-verdict-v[data-tone="ok"]{color:var(--ww-wr-ok);}
.ww-wr-verdict-v[data-tone="partial"]{color:var(--ww-wr-warn);}
.ww-wr-verdict-v[data-tone="risk"]{color:var(--ww-wr-risk);}
.ww-wr-verdict-v[data-tone="unknown"]{color:var(--ww-wr-unknown);}
.ww-wr-rows{display:grid;grid-template-columns:1fr;gap:0.3rem 0.9rem;margin:0;}
@media (min-width:34rem){.ww-wr-rows{grid-template-columns:auto 1fr;}}
.ww-wr-rows dt{font-size:0.75rem;font-weight:600;color:var(--ww-wr-muted);}
.ww-wr-rows dd{margin:0;}
.ww-wr-why{margin-top:0.6rem;padding-top:0.55rem;border-top:1px dashed var(--ww-wr-line-soft);}
.ww-wr-chain{display:flex;flex-direction:column;gap:0.2rem;margin:0 0 0.5rem;padding:0;list-style:none;}
.ww-wr-chain li{display:grid;grid-template-columns:auto 1fr;gap:0.5rem;align-items:baseline;}
.ww-wr-chain b{font-size:0.6875rem;letter-spacing:0.08em;text-transform:uppercase;color:var(--ww-wr-muted);font-weight:600;}
.ww-wr-actions{display:flex;flex-wrap:wrap;gap:0.5rem;margin-top:0.7rem;}
.ww-wr-btn{padding:0.35rem 0.7rem;border:1px solid var(--ww-wr-line);border-radius:2px;background:transparent;color:var(--ww-wr-ink);font:inherit;font-size:0.8125rem;cursor:pointer;}
.ww-wr-btn[data-tone="primary"]{border-color:var(--ww-wr-accent);color:var(--ww-wr-accent);font-weight:600;}
.ww-wr-split{display:grid;grid-template-columns:1fr;gap:0.6rem;margin-top:0.6rem;}
@media (min-width:44rem){.ww-wr-split{grid-template-columns:1fr 1fr;}}
.ww-wr-world{border:1px solid var(--ww-wr-line-soft);border-radius:2px;padding:0.6rem 0.7rem;background:var(--ww-wr-panel);}
.ww-wr-world[data-diff="yes"]{border-color:var(--ww-wr-accent);}
.ww-wr-world-h{font-family:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:0.6875rem;letter-spacing:0.12em;text-transform:uppercase;color:var(--ww-wr-muted);margin:0 0 0.35rem;}
.ww-wr-delta{margin-top:0.6rem;padding:0.5rem 0.6rem;border:1px dashed var(--ww-wr-accent);border-radius:2px;font-size:0.8125rem;}
.ww-wr-delta b{font-size:0.6875rem;letter-spacing:0.08em;text-transform:uppercase;color:var(--ww-wr-muted);}
.ww-wr-note{margin-top:0.7rem;padding-top:0.55rem;border-top:1px dashed var(--ww-wr-line-soft);color:var(--ww-wr-muted);font-size:0.75rem;}
.ww-wr-list{margin:0.2rem 0 0;padding-left:1.1rem;}
.ww-wr-list li{margin:0.12rem 0;}
@media (prefers-reduced-motion:reduce){.ww-wr-station{transition:none;}.ww-wr-station[data-tone="risk"][data-binding="yes"]{transform:none;}}
`;
  const TONE_FOR = {
    AVAILABLE: 'ok',
    PARTIAL: 'partial',
    BLOCKED: 'risk',
    UNKNOWN: 'unknown'
  };
  const STATION_TONE = {
    SATISFIED: 'ok',
    PARTIAL: 'partial',
    UNSATISFIED: 'risk',
    UNKNOWN: 'unknown',
    REQUIRED: 'required',
    NOT_EXERCISED: 'muted'
  };
  const STATION_CHIP = {
    PARTIAL: 'Partial',
    UNSATISFIED: 'Blocker',
    UNKNOWN: 'Not observed',
    REQUIRED: 'Required',
    NOT_EXERCISED: 'Not exercised'
  };
  const [world, setWorld] = useState({
    ...WR_BASELINE
  });
  const [lastChange, setLastChange] = useState(null);
  const [splitOn, setSplitOn] = useState('');
  const result = useMemo(() => resolveWallRoomScenario(world), [world]);
  const baselineResult = useMemo(() => resolveWallRoomScenario(WR_BASELINE), []);
  const setVar = (id, value, label, readable) => {
    setWorld(previousWorld => {
      const nextWorld = {
        ...previousWorld,
        [id]: value
      };
      const before = resolveWallRoomScenario(previousWorld);
      const after = resolveWallRoomScenario(nextWorld);
      setLastChange({
        id,
        label,
        readable,
        transition: describeWallRoomTransition(before, after)
      });
      return nextWorld;
    });
  };
  const reset = () => {
    setWorld({
      ...WR_BASELINE
    });
    setLastChange(null);
    setSplitOn('');
  };
  const stationView = WR_STATIONS.map(station => {
    const requirement = result.requirements.find(entry => entry.id === station.id);
    const state = requirement ? requirement.state : 'UNKNOWN';
    const binding = Boolean(requirement && requirement.binding);
    const tone = state === 'UNSATISFIED' && !binding ? 'muted' : STATION_TONE[state] || 'muted';
    const chip = STATION_CHIP[state] ? state === 'UNSATISFIED' && !binding ? 'Not binding' : STATION_CHIP[state] : null;
    return {
      ...station,
      state,
      binding,
      tone,
      chip,
      detail: requirement ? requirement.detail : '',
      reason: requirement ? requirement.reason : null
    };
  });
  const split = useMemo(() => splitOn ? buildWallRoomSplit(world, splitOn) : null, [splitOn, world]);
  const splitDiffers = Boolean(split && split.comparison.differs);
  const summaryRows = [{
    key: 'Material blocker',
    value: result.materialBlocker ? result.materialBlocker.label : 'None in this world'
  }, {
    key: 'Unknown',
    value: result.materialUnknowns.length > 0 ? result.materialUnknowns.map(entry => entry.label).join(' · ') : result.unknowns.length > 0 ? result.unknowns.map(entry => entry.label).join(' · ') + ' (not binding here)' : 'None in this world'
  }, {
    key: 'Signer arithmetic',
    value: result.signerMath.confirmed + ' confirmed · ' + result.signerMath.possible + ' not ruled out · quorum ' + result.signerMath.quorum + ' of ' + result.signerMath.total
  }];
  return <div className="ww-wr">
      <style>{STYLES}</style>

      <div className="ww-wr-head">
        <p className="ww-wr-title ww-wr-mono">WallRoom</p>
        <span className="ww-wr-room">{WR_SCENARIO.room} · {WR_SCENARIO.subject}</span>
        <span className="ww-wr-synth">Synthetic world</span>
      </div>

      <p className="ww-wr-disclaimer">
        Educational scenario. Not a real vault assessment. Every condition below is invented
        to show how readiness reasoning behaves when something changes — nothing here observes
        a real treasury, and no result establishes an external fact.
      </p>

      <div className="ww-wr-sect">
        <p className="ww-wr-h">The room</p>
        <div className="ww-wr-floorplan">
          <div className="ww-wr-bank">
            {stationView.filter(station => station.row === 'top').map(station => <div key={station.id} className="ww-wr-station" data-tone={station.tone} data-binding={station.binding ? 'yes' : 'no'}>
                <span className="ww-wr-st-label">{station.label}</span>
                <span className="ww-wr-st-state">{station.state}</span>
                <span className="ww-wr-st-detail">{station.detail}</span>
                {station.chip ? <span className="ww-wr-st-flag">{station.chip}</span> : null}
                {station.reason ? <span className="ww-wr-st-why">{station.reason}</span> : null}
              </div>)}
          </div>

          <div className="ww-wr-conduit" />

          <div className="ww-wr-core">
            <span className="ww-wr-core-name">{WR_SCENARIO.subject}</span>
            <span className="ww-wr-core-meta">
              {WR_SCENARIO.quorum}-of-{WR_SCENARIO.totalSigners} · {WR_SCENARIO.selfControlledSigners} self-controlled
              {' · '}{WR_SCENARIO.externallyCustodiedSigners} externally custodied · {WR_SCENARIO.signingDependency}
            </span>
          </div>

          <div className="ww-wr-conduit" />

          <div className="ww-wr-bank">
            {stationView.filter(station => station.row === 'bottom').map(station => <div key={station.id} className="ww-wr-station" data-tone={station.tone} data-binding={station.binding ? 'yes' : 'no'}>
                <span className="ww-wr-st-label">{station.label}</span>
                <span className="ww-wr-st-state">{station.state}</span>
                <span className="ww-wr-st-detail">{station.detail}</span>
                {station.chip ? <span className="ww-wr-st-flag">{station.chip}</span> : null}
                {station.reason ? <span className="ww-wr-st-why">{station.reason}</span> : null}
              </div>)}
          </div>
        </div>
      </div>

      <div className="ww-wr-sect">
        <p className="ww-wr-h">Change the world</p>
        <div className="ww-wr-controls">
          {WR_VARIABLES.map(variable => {
    const controlId = idPrefix + '-' + variable.id;
    const changed = lastChange && lastChange.id === variable.id ? 'yes' : 'no';
    if (variable.kind === 'boolean') {
      return <div key={variable.id} className="ww-wr-ctl" data-changed={changed}>
                  <div className="ww-wr-check">
                    <input id={controlId} type="checkbox" checked={world[variable.id] === true} onChange={event => setVar(variable.id, event.target.checked, variable.label, event.target.checked ? 'Yes' : 'No')} />
                    <span>
                      <label htmlFor={controlId}>{variable.label}</label>
                      <span className="ww-wr-hint">{variable.hint}</span>
                    </span>
                  </div>
                </div>;
    }
    return <div key={variable.id} className="ww-wr-ctl" data-changed={changed}>
                <label htmlFor={controlId}>{variable.label}</label>
                <select id={controlId} value={world[variable.id]} onChange={event => {
      const option = variable.options.find(entry => entry.value === event.target.value);
      setVar(variable.id, event.target.value, variable.label, option ? option.label : event.target.value);
    }}>
                  {variable.options.map(option => <option key={option.value} value={option.value}>{option.label}</option>)}
                </select>
                <span className="ww-wr-hint">{variable.hint}</span>
              </div>;
  })}
        </div>
        <div className="ww-wr-actions">
          <button type="button" className="ww-wr-btn" onClick={reset}>Reset the room</button>
        </div>
      </div>

      <div className="ww-wr-sect">
        <p className="ww-wr-h">Readiness consequence</p>
        <div className="ww-wr-out" aria-live="polite">
          <div className="ww-wr-verdict">
            <span className="ww-wr-verdict-k">Migration path</span>
            <span className="ww-wr-verdict-v" data-tone={TONE_FOR[result.migrationPath]}>{result.migrationPath}</span>
          </div>
          <dl className="ww-wr-rows">
            {summaryRows.map(row => <div key={row.key} style={{
    display: 'contents'
  }}>
                <dt>{row.key}</dt>
                <dd>{row.value}</dd>
              </div>)}
          </dl>

          <div className="ww-wr-why">
            {lastChange ? <ul className="ww-wr-chain">
                {[{
    k: 'Changed',
    v: lastChange.label + ' → ' + lastChange.readable
  }, {
    k: 'Affected',
    v: lastChange.transition.affectedText
  }, {
    k: 'Consequence',
    v: lastChange.transition.consequenceText
  }].map(step => <li key={step.k}><b>{step.k}</b><span>{step.v}</span></li>)}
              </ul> : null}
            <p style={{
    margin: 0
  }}><b style={{
    fontSize: '0.6875rem',
    letterSpacing: '0.08em',
    textTransform: 'uppercase',
    color: 'var(--ww-wr-muted)'
  }}>Why</b>{' '}{result.headline}</p>
            {result.materialUnknowns.length > 0 ? <ul className="ww-wr-list">
                {result.materialUnknowns.map(entry => <li key={entry.id}>{entry.label} is not observed in this world. Unknown is not the same as safe, and not the same as blocked — it means this world does not say.</li>)}
              </ul> : null}
          </div>
        </div>
      </div>

      <div className="ww-wr-sect">
        <p className="ww-wr-h">WorldSplitter</p>
        <div className="ww-wr-ctl">
          <label htmlFor={idPrefix + '-split'}>Split this world on one condition</label>
          <select id={idPrefix + '-split'} value={splitOn} onChange={event => setSplitOn(event.target.value)}>
            <option value="">Off — single world</option>
            {WR_VARIABLES.map(variable => <option key={variable.id} value={variable.id}>{variable.label}</option>)}
          </select>
          <span className="ww-wr-hint">
            Holds every other condition fixed and changes exactly one. A controlled counterfactual
            shows how the reasoning responds; it does not establish a fact about any real system.
          </span>
        </div>

        {split ? <div>
            <div className="ww-wr-split">
              {[{
    key: 'World A',
    label: split.labelA,
    result: split.a
  }, {
    key: 'World B',
    label: split.labelB,
    result: split.b
  }].map(entry => <div key={entry.key} className="ww-wr-world" data-diff={splitDiffers ? 'yes' : 'no'}>
                  <p className="ww-wr-world-h">{entry.key}</p>
                  <p style={{
    margin: '0 0 0.35rem'
  }}>
                    <span className="ww-wr-st-label">{split.variable.label}</span>
                    <span className="ww-wr-st-state">{entry.label}</span>
                  </p>
                  <p style={{
    margin: 0
  }}>
                    <span className="ww-wr-verdict-k">Migration path</span>{' '}
                    <span className="ww-wr-verdict-v" data-tone={TONE_FOR[entry.result.migrationPath]} style={{
    fontSize: '0.9375rem'
  }}>
                      {entry.result.migrationPath}
                    </span>
                  </p>
                  <p className="ww-wr-st-detail" style={{
    marginTop: '0.3rem'
  }}>
                    {entry.result.materialBlocker ? '↑ ' + entry.result.materialBlocker.label : entry.result.materialUnknowns.length > 0 ? '↑ ' + entry.result.materialUnknowns[0].label + ' not observed' : 'No material blocker'}
                  </p>
                </div>)}
            </div>
            <p className="ww-wr-delta">
              <b>Changed variable</b>{' '}{split.variable.label}: {split.labelA} → {split.labelB}.
              {' '}
              <b>Consequential difference</b>{' '}{split.deltaText}
            </p>
            <ul className="ww-wr-list">
              {split.comparison.changedStations.map(entry => <li key={entry.id}>
                  {entry.label}: <span className="ww-wr-mono">{entry.from}</span> → <span className="ww-wr-mono">{entry.to}</span>
                </li>)}
            </ul>
          </div> : null}
      </div>

      <div className="ww-wr-sect">
        <p className="ww-wr-note">
          WallRoom separates an assumption you set, an observation this world does or does not
          contain, and the conclusion that follows. Two of the controls are three-valued because a
          checkbox cannot express “not observed”, and recording an unobserved condition as a plain
          yes or no is the exact failure this room exists to show. Baseline migration path for this
          scenario is <span className="ww-wr-mono">{baselineResult.migrationPath}</span>.
        </p>
      </div>
    </div>;
};

[WallAtlas](/explore) maps what WalletWall's concepts are and how they relate. WallRoom
does the other half: it puts one system in front of you and lets you change it.

**Change one condition. Watch what moves.**

<WallRoom idPrefix="wallroom-vault-migration" />

## If WallRoom didn't load

The room requires JavaScript. The lesson it teaches, in plain text:

* A **3-of-5** multisig stablecoin treasury holds **2** self-controlled signers and **3** with an external custodian. Quorum cannot be reached without the custodian, so custody is a real dependency.
* Requiring a replacement signing scheme changes no fact about the treasury — it changes **which requirement is binding**.
* When quorum is neither confirmed nor ruled out, the answer is `UNKNOWN`. Not safe, not blocked.

Read the same ideas at their canonical pages: [Migration Readiness](/concepts/migration-readiness),
[Vault Boundaries & Disclosures](/vault/boundaries), and
[Wallet Evidence Model](/architecture/wallet-evidence-model).

## What this is

WallRoom runs a **synthetic world**. The treasury, the signer split, and the custody
arrangement are invented for teaching. Nothing here observes a real system, and no
result establishes an external fact — a controlled world shows how readiness reasoning
*behaves*, not what is true of anyone's vault.

It also invents no score. The outcome is one of four categorical states —
`AVAILABLE`, `PARTIAL`, `BLOCKED`, `UNKNOWN` — because the point is the reasoning
chain, not a number.

## The scenario

A multisig stablecoin treasury in an EVM environment, signing with `secp256k1`:

| Property                     | Value          |
| ---------------------------- | -------------- |
| Quorum                       | 3 of 5 signers |
| Self-controlled signers      | 2              |
| Externally custodied signers | 3              |

That split is the whole point. With a quorum of 3 and only 2 self-controlled signers,
the custodian is **load-bearing** — a quorum cannot be reached without it. Custody is a
real dependency in this world, not a decorative one.

## How the reasoning runs

```mermaid theme={null}
flowchart TD
  V["Conditions you set"] --> R["Requirement states"]
  R --> A["Signer arithmetic<br/>confirmed vs not-ruled-out"]
  A --> B["Blockers, unknowns, capabilities"]
  B --> O["Migration path"]
  O --> W["Explanation"]
```

Two numbers drive the signer requirement:

* **Confirmed** — signers observed *able* to adopt a replacement scheme.
* **Not ruled out** — confirmed signers plus the ones nothing is known about.

`UNKNOWN` is exactly the gap between them: quorum is not confirmed, and not ruled out
either. It is a real state, not a rounding of "probably fine".

## Requiring a replacement changes what is binding

Turning on **Replacement signing scheme required** changes no fact about the treasury.
It changes *which requirement is load-bearing*. While the current scheme stays valid,
signer adoption is latent — nobody has to adopt anything. The moment a replacement is
required, that latent property becomes a constraint, and the unknowns that were
harmless become material.

This is the same idea as [Independent Authority](/architecture/independent-authority):
a conclusion is only as strong as what it actually rests on, and changing the question
changes what it rests on.

## Unknown stays unknown

At least one condition in this room is genuinely unobserved, and WallRoom will not
resolve it for you.

<Info>
  **Unknown is not safe, and unknown is not blocked.** It means this world does not
  contain the information needed to conclude. Two of the controls are three-valued for
  exactly this reason — a checkbox cannot express "not observed", and recording an
  unobserved condition as a plain yes or no is the failure this room exists to show.
</Info>

The same rule governs real WalletWall surfaces: see
[Wallet Evidence Model](/architecture/wallet-evidence-model) for how unavailable data
is kept distinct from an observed zero.

## WorldSplitter

**WorldSplitter** holds every condition fixed and changes exactly one, then shows both
worlds side by side. It highlights the variable that changed and the consequence that
followed.

A controlled counterfactual is an argument about *reasoning*, not evidence about the
world. Two synthetic worlds differing in one condition demonstrate what that condition
decides — they do not establish that any real treasury is in either state.

## Where to go next

* [Migration Readiness](/concepts/migration-readiness) — the real tiers this scenario simplifies
* [Stablecoin Vault & Vault Simulator](/features/vault) — the product surface behind it
* [Vault Boundaries & Disclosures](/vault/boundaries) — what the vault work does not do
* [WallAtlas](/explore) — the concept map this room sits inside
