@
feat(quantik-game): фаза 3 — граф-уровни (движение по f(x)) + зоны Новый тип уровня: Квантик едет по кривой y=f(x), которую игрок собирает слайдерами коэффициентов, проходя сквозь зоны-препятствия. Движок (аддитивно): plot.runner → env-поля curve.runX/runY/runDone (f компилится 1 раз, питает И кривую, И бегунок-героя, без само-ссылки); type zone (forbidden/target/collect) → булево env-поле zone.hit. Грамматика выражений ЗАКРЫТА — никаких inzone()-предикатов, только именованные env-поля (модель t/tries из Ф0), без eval. Глава-созвездие functions из 5 уровней (луч/синус/парабола/модуль/экспонента), разблокировка 9/11/13/ 15/17 (цепочка проходима). validateSpec принимает zone+runner. Все 5 уровней независимо проверены на движке (2★ достижимы). npm test 253/8 baseline; custom-sims 26/26; lint:routes 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
This commit is contained in:
@@ -203,6 +203,34 @@ describe('/api/custom-sims', () => {
|
||||
assert.ok(txt.includes('<img'), 'escaped form present');
|
||||
});
|
||||
|
||||
it('accepts graph-level spec with zone + runner (Квантик Ф3)', async () => {
|
||||
const spec = {
|
||||
specVersion: 1,
|
||||
meta: { title: 'Граф-уровень' },
|
||||
viewport: { xmin: -1, xmax: 11, ymin: -1, ymax: 8 },
|
||||
params: [{ name: 'a', min: -1, max: 2, step: 0.05, value: 0.5 }],
|
||||
objects: [
|
||||
{ id: 'curve', type: 'plot', expr: 'a*x', var: 'x', range: [0, 10], runner: { duration: 5 } },
|
||||
{ id: 'ball', type: 'point', x: 'curve.runX', y: 'curve.runY', r: 7 },
|
||||
{ type: 'zone', id: 'pit', kind: 'forbidden', shape: 'rect', x: 5, y: 0, w: 4, h: 2, track: 'ball', label: 'яма' },
|
||||
{ type: 'zone', id: 'gate', kind: 'target', shape: 'circle', x: 10, y: 5, r: 1, track: 'ball' },
|
||||
],
|
||||
goal: { when: 'gate.hit', fail: 'pit.hit', stars: [{ when: 'gate.hit' }] },
|
||||
};
|
||||
const res = await inject('POST', '/api/custom-sims', { spec }, teacherToken);
|
||||
assert.equal(res.status, 201, `got ${res.status}: ${JSON.stringify(res.body)}`);
|
||||
const get = await inject('GET', `/api/custom-sims/${res.body.id}`, null, teacherToken);
|
||||
const objs = get.body.sim.spec.objects;
|
||||
assert.ok(objs.find(o => o.type === 'zone' && o.id === 'pit'), 'zone object preserved');
|
||||
assert.ok(objs.find(o => o.type === 'plot' && o.runner), 'runner block preserved');
|
||||
});
|
||||
|
||||
it('rejects unknown object type even with zone allowed (400)', async () => {
|
||||
const bad = { ...VALID_SPEC, objects: [{ type: 'zoney_fake', x: 0, y: 0 }] };
|
||||
const res = await inject('POST', '/api/custom-sims', { spec: bad }, teacherToken);
|
||||
assert.equal(res.status, 400, `got ${res.status}`);
|
||||
});
|
||||
|
||||
it('owner can DELETE own sim (then 404)', async () => {
|
||||
const del = await inject('DELETE', `/api/custom-sims/${simId}`, null, teacherToken);
|
||||
assert.equal(del.status, 200, `got ${del.status}`);
|
||||
|
||||
Reference in New Issue
Block a user