卫戍协议:盟约 web remake — initial import

Online co-op browser remake of Arknights' Stronghold Protocol: Alliance
(client-simulated combat, official waves/bonds/items/strategies, 3D board,
facing wheel, operator loadout, official emotes, deployment scripts).
This commit is contained in:
sganggs committed 2026-09-29 16:21:31 +08:00
commit 01a44cd288
408 files changed
+308986

No files matched your search

+93
View File
@@ -0,0 +1,93 @@
// Small polyfills for the older browsers the client supports (Safari / iOS 15.0–15.3, Firefox ESR, older Chromium on
// Android tablets). Imported first by main.js (and the dev harnesses), so every later module — including the ones
// loaded on demand (render engine, battle sim) — sees them. Each one is installed only when the browser lacks it
// (feature detection, never UA sniffing) and is a faithful subset of the standard behaviour:
// Object.hasOwn Safari < 15.4, Firefox < 92
// Array/String/TypedArray .at() Safari < 15.4, Firefox < 90
// Array .findLast / .findLastIndex Safari < 15.4, Firefox < 104
// structuredClone Safari < 15.4, Firefox < 94 (JSON-safe values + Map/Set/Date; enough for
// the plain state objects the client clones)
// globalThis.queueMicrotask very old WebViews
// `installCompat(target)` is exported for tests (it patches the given global object instead).
/**
* Install the polyfills on `g` (default globalThis). Returns the names that were missing and got installed.
* @param {any} [g]
* @returns {string[]}
*/
export function installCompat(g = globalThis) {
const added = [];
const def = (obj, name, fn) => {
if (!obj || typeof obj[name] === 'function') return;
try {
Object.defineProperty(obj, name, { value: fn, writable: true, configurable: true, enumerable: false });
added.push(name);
} catch { /* frozen: leave it */ }
};
const O = g.Object || Object;
def(O, 'hasOwn', function hasOwn(obj, key) {
if (obj == null) throw new TypeError('Cannot convert undefined or null to object');
return Object.prototype.hasOwnProperty.call(Object(obj), key);
});
function at(i) {
const len = this.length >>> 0;
let k = Math.trunc(Number(i)) || 0;
if (k < 0) k += len;
if (k < 0 || k >= len) return undefined;
return typeof this === 'string' || this instanceof String ? String(this).charAt(k) : this[k];
}
def((g.Array || Array).prototype, 'at', at);
def((g.String || String).prototype, 'at', at);
const TA = g.Int8Array ? Object.getPrototypeOf(g.Int8Array.prototype) : null;
if (TA) def(TA, 'at', at);
def((g.Array || Array).prototype, 'findLast', function findLast(fn, thisArg) {
for (let i = (this.length >>> 0) - 1; i >= 0; i--) if (fn.call(thisArg, this[i], i, this)) return this[i];
return undefined;
});
def((g.Array || Array).prototype, 'findLastIndex', function findLastIndex(fn, thisArg) {
for (let i = (this.length >>> 0) - 1; i >= 0; i--) if (fn.call(thisArg, this[i], i, this)) return i;
return -1;
});
def(g, 'structuredClone', function structuredClone(value) { return cloneValue(value, new Map()); });
def(g, 'queueMicrotask', function queueMicrotask(fn) { Promise.resolve().then(fn).catch((err) => setTimeout(() => { throw err; })); });
return added;
}
/** Deep clone of plain data (objects, arrays, Map, Set, Date, RegExp, typed arrays), cycles preserved. */
function cloneValue(v, seen) {
if (v === null || typeof v !== 'object') {
if (typeof v === 'function' || typeof v === 'symbol') throw new TypeError('structuredClone: value could not be cloned');
return v;
}
if (seen.has(v)) return seen.get(v);
let out;
if (v instanceof Date) out = new Date(v.getTime());
else if (v instanceof RegExp) out = new RegExp(v.source, v.flags);
else if (ArrayBuffer.isView(v)) out = new v.constructor(v);
else if (v instanceof ArrayBuffer) out = v.slice(0);
else if (v instanceof Map) {
out = new Map();
seen.set(v, out);
for (const [k, x] of v) out.set(cloneValue(k, seen), cloneValue(x, seen));
return out;
} else if (v instanceof Set) {
out = new Set();
seen.set(v, out);
for (const x of v) out.add(cloneValue(x, seen));
return out;
} else if (Array.isArray(v)) {
out = new Array(v.length);
seen.set(v, out);
for (let i = 0; i < v.length; i++) out[i] = cloneValue(v[i], seen);
return out;
} else {
out = {};
seen.set(v, out);
for (const k of Object.keys(v)) out[k] = cloneValue(v[k], seen);
return out;
}
seen.set(v, out);
return out;
}
export const installedPolyfills = installCompat();