@
feat(quantik-game): фаза 4 — квантовые способности + SR-комнаты Глава-созвездие quantum (L12–L16) и фирменные механики — всё через безопасную модель спеки, движок и бэкенд НЕ тронуты (engine touch = 0): - Суперпозиция: два тела ball+ball2, goal.when требует ОБА (зеркальный закон). Туннелирование: forbidden-зона wall + fail wall.hit && tunnel<1; способность тратит энергию → setParam(tunnel,1). Коллапс/прицел: пунктир- plot предсказанной траектории на паузе. - Энергия — клиентский ресурс (localStorage quantik-energy, QuantikEnergy). - SR-комната: мини-сессия повторения флешкарт в модалке (НЕ iframe), LS.fcStudySession/fcReview; «Знаю/Легко» дают энергию; текст карт экранируется, картинки — по regex-вайтлисту. Все 5 уровней проверены на реальном движке (2★ достижимы; суперпозиция требует оба тела; туннель-гейт блокирует без заряда). npm test 253/8 baseline; lint:routes 0; цепочка разблокировки проходима. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
This commit is contained in:
@@ -46,24 +46,46 @@
|
||||
}
|
||||
|
||||
/* Тинтуем героя уровня (объект с id 'ball') цветом скина — БЕЗ исполнения,
|
||||
просто переписываем цветовые поля спеки-копии перед монтированием. */
|
||||
просто переписываем цветовые поля спеки-копии перед монтированием.
|
||||
Фаза 4: вторую копию суперпозиции (id 'ball2') тоже тинтуем, но осветлённым
|
||||
«фантомным» оттенком (полупрозрачность задаётся самой спекой). */
|
||||
function tintHeroSpec(spec, skinKey) {
|
||||
var color = skinColor(skinKey);
|
||||
var phantom = lighten(color, 0.42);
|
||||
// глубокая копия (спека — данные, без функций) чтобы не мутировать реестр
|
||||
var copy = JSON.parse(JSON.stringify(spec));
|
||||
if (Array.isArray(copy.objects)) {
|
||||
for (var i = 0; i < copy.objects.length; i++) {
|
||||
var o = copy.objects[i];
|
||||
if (o && o.id === 'ball') {
|
||||
if (!o) continue;
|
||||
if (o.id === 'ball') {
|
||||
o.color = color;
|
||||
if (o.glow) o.glowColor = color;
|
||||
if (o.trail) o.trailColor = color;
|
||||
} else if (o.id === 'ball2') {
|
||||
o.color = phantom;
|
||||
if (o.glow) o.glowColor = phantom;
|
||||
if (o.trail) o.trailColor = phantom;
|
||||
}
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
/* Осветлить hex-цвет к белому на долю t (0..1). Для «фантома» суперпозиции.
|
||||
Принимает #RGB/#RRGGBB; прочее возвращает как есть. */
|
||||
function lighten(hex, t) {
|
||||
if (typeof hex !== 'string') return hex;
|
||||
var m = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.exec(hex.trim());
|
||||
if (!m) return hex;
|
||||
var h = m[1];
|
||||
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
||||
var r = parseInt(h.slice(0, 2), 16), g = parseInt(h.slice(2, 4), 16), b = parseInt(h.slice(4, 6), 16);
|
||||
r = Math.round(r + (255 - r) * t); g = Math.round(g + (255 - g) * t); b = Math.round(b + (255 - b) * t);
|
||||
function hx(n) { var s = n.toString(16); return s.length === 1 ? '0' + s : s; }
|
||||
return '#' + hx(r) + hx(g) + hx(b);
|
||||
}
|
||||
|
||||
/* ── Inline SVG звезды ── */
|
||||
function starSvg(filled) {
|
||||
var fill = filled ? '#FBBF24' : 'none';
|
||||
@@ -164,6 +186,17 @@
|
||||
return String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
/* ── Открыть SR-комнату (повторение флешкарт → энергия) ────────────────────
|
||||
Делегирует в QuantikAbilities.openRestRoom; после закрытия обновляет HUD
|
||||
панели способностей (энергия могла измениться). */
|
||||
function openRest(host, abilities) {
|
||||
if (!global.QuantikAbilities || !global.QuantikAbilities.openRestRoom) return;
|
||||
global.QuantikAbilities.openRestRoom({
|
||||
host: host,
|
||||
onClose: function () { if (abilities) try { abilities.refresh(); } catch (_e) {} }
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Старт уровня ───────────────────────────────────────────────────────
|
||||
opts: { host, level, skin?, onNext?(level), onMap?(), hasNext?, resolveNext? }
|
||||
resolveNext?() -> Promise<{ hasNext, next }>: пересчитать следующий уровень
|
||||
@@ -180,6 +213,27 @@
|
||||
var spec = tintHeroSpec(level.spec, skin);
|
||||
var inst = global.SimEngine.mount(host, spec);
|
||||
|
||||
// ── Панель квантовых способностей + HUD энергии (Фаза 4) ──
|
||||
// Аддитивно: монтируется только если доступен модуль; кнопки сами решают,
|
||||
// уместны ли они для уровня (tunnel/aim-флаги). SR-комната открывается отсюда.
|
||||
var abilities = null;
|
||||
if (global.QuantikAbilities && global.QuantikAbilities.mountBar) {
|
||||
abilities = global.QuantikAbilities.mountBar({
|
||||
host: host,
|
||||
inst: inst,
|
||||
level: level,
|
||||
onOpenRest: function () { openRest(host, abilities); }
|
||||
});
|
||||
// Убираем панель при destroy инстанса (оборачиваем существующий destroy).
|
||||
if (abilities) {
|
||||
var _origDestroy = inst.destroy.bind(inst);
|
||||
inst.destroy = function () {
|
||||
try { abilities.destroy(); } catch (_e) {}
|
||||
return _origDestroy();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var overlayRef = null;
|
||||
function clearOverlay() {
|
||||
if (overlayRef && overlayRef.overlay && overlayRef.overlay.parentNode) {
|
||||
@@ -203,6 +257,7 @@
|
||||
overlayRef.btnAgain.addEventListener('click', function () {
|
||||
clearOverlay();
|
||||
try { inst.reset(); } catch (_e) {}
|
||||
if (abilities) try { abilities.resetAbilities(); } catch (_e) {}
|
||||
});
|
||||
overlayRef.btnNext.addEventListener('click', function () {
|
||||
clearOverlay();
|
||||
|
||||
Reference in New Issue
Block a user