makeComparatorKit = (compareRemotables = (_x, _y) => NaN) => {
  /** @type {PartialCompare} */
  const comparator = (left, right) => {
    if (sameValueZero(left, right)) {
      return 0;
    }
    const leftStyle = passStyleOf(left);
    const rightStyle = passStyleOf(right);
    if (leftStyle !== rightStyle) {
      return compareNumerics(
        passStyleRanks[leftStyle].index,
        passStyleRanks[rightStyle].index,
      );
    }
    /* eslint-disable @endo/restrict-comparison-operands --
     * We know `left` and `right` are comparable.
     */
    switch (leftStyle) {
      case 'remotable': {
        return compareRemotables(left, right);
      }
      case 'undefined':
      case 'null':
      case 'error':
      case 'promise': {
        // For each of these passStyles, all members of that passStyle are tied
        // for the same rank.
        return 0;
      }
      case 'boolean':
      case 'bigint':
      case 'string': {
        // Within each of these passStyles, the rank ordering agrees with
        // JavaScript's relational operators `<` and `>`.
        if (left < right) {
          return -1;
        } else {
          assert(left > right);
          return 1;
        }
      }
      case 'symbol': {
        return comparator(
          nameForPassableSymbol(left),
          nameForPassableSymbol(right),
        );
      }
      case 'number': {
        return compareNumerics(left, right);
      }
      case 'copyRecord': {
        // Lexicographic by inverse sorted order of property names, then
        // lexicographic by corresponding values in that same inverse
        // order of their property names. Comparing names by themselves first,
        // all records with the exact same set of property names sort next to
        // each other in a rank-sort of copyRecords.

        // The copyRecord invariants enforced by passStyleOf ensure that
        // all the property names are strings. We need the reverse sorted order
        // of these names, which we then compare lexicographically. This ensures
        // that if the names of record X are a subset of the names of record Y,
        // then record X will have an earlier rank and sort to the left of Y.
        const leftNames = recordNames(left);
        const rightNames = recordNames(right);

        const result = comparator(leftNames, rightNames);
        if (result !== 0) {
          return result;
        }
        const leftValues = recordValues(left, leftNames);
        const rightValues = recordValues(right, rightNames);
        return comparator(leftValues, rightValues);
      }
      case 'copyArray': {
        // Lexicographic
        const len = Math.min(left.length, right.length);
        for (let i = 0; i < len; i += 1) {
          const result = comparator(left[i], right[i]);
          if (result !== 0) {
            return result;
          }
        }
        // If all matching elements were tied, then according to their lengths.
        // If array X is a prefix of array Y, then X has an earlier rank than Y.
        return comparator(left.length, right.length);
      }
      case 'byteArray': {
        // ByteArrays compare by shortlex.
        // - first, if they are of unequal length, then the shorter is less.
        // - then, among byteArrays of equal length, by lexicographic comparison
        //   of their bytes in ascending order.
        const { byteLength: leftLen } = left;
        const { byteLength: rightLen } = right;
        if (leftLen < rightLen) {
          return -1;
        }
        if (leftLen > rightLen) {
          return 1;
        }

        // Account for gaps in the @endo/immutable-arraybuffer shim.
        const leftArray =
          Object.getPrototypeOf(left) === ArrayBuffer.prototype
            ? new Uint8Array(left)
            : new Uint8Array(left.slice(0));
        const rightArray =
          Object.getPrototypeOf(right) === ArrayBuffer.prototype
            ? new Uint8Array(right)
            : new Uint8Array(right.slice(0));
        for (let i = 0; i < leftLen; i += 1) {
          const leftByte = leftArray[i];
          const rightByte = rightArray[i];
          if (leftByte < rightByte) {
            return -1;
          }
          if (leftByte > rightByte) {
            return 1;
          }
        }
        return 0;
      }
      case 'tagged': {
        // Lexicographic by `[Symbol.toStringTag]` then `.payload`.
        const labelComp = comparator(getTag(left), getTag(right));
        if (labelComp !== 0) {
          return labelComp;
        }
        return comparator(left.payload, right.payload);
      }
      default: {
        throw Fail`Unrecognized passStyle: ${q(leftStyle)}`;
      }
    }
    /* eslint-enable */
  };

  /** @type {RankCompare} */
  const outerComparator = (x, y) =>
    // When the inner comparator returns NaN to indicate incomparability,
    // replace that with 0 to indicate a tie.
    /** @type {Exclude<PartialComparison, NaN>} */ (comparator(x, y) || 0);

  /** @type {RankCompare} */
  const antiComparator = (x, y) => outerComparator(y, x);

  memoOfSorted.set(outerComparator, new WeakSet());
  memoOfSorted.set(antiComparator, new WeakSet());
  comparatorMirrorImages.set(outerComparator, antiComparator);
  comparatorMirrorImages.set(antiComparator, outerComparator);

  return harden({ comparator: outerComparator, antiComparator });
}
@param {PartialCompare} [compareRemotables]
A comparator for assigning an internal order to remotables.
It defaults to a function that always returns `NaN`, meaning that all
remotables are incomparable and should tie for the same rank by
short-circuiting without further refinement (e.g., not only are `r1` and `r2`
tied, but so are `[r1, 0]` and `[r2, "x"]`).
@returns {RankComparatorKit}