diff --git a/README.md b/README.md index 55e633d..5c0d760 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-111997.svg)](#build--distribution) [![Electron](https://img.shields.io/badge/Electron-43-47848F.svg)](https://www.electronjs.org/) [![Downloads](https://img.shields.io/github/downloads/CaYatur/SoundVisualizer/total?label=downloads)](https://github.com/CaYatur/SoundVisualizer/releases) -[![Tests](https://img.shields.io/badge/tests-2227%20passing-2ea043.svg)](#tests) +[![Tests](https://img.shields.io/badge/tests-2251%20passing-2ea043.svg)](#tests) [![cayadev.com](https://img.shields.io/badge/cayadev.com-e11d2a.svg)](https://cayadev.com) @@ -268,6 +268,12 @@ that asserts the bar profile has no step in it. `megabuf` and `gmegabuf` indices are rounded and bounded the way it does, `rand(n)` returns a real number (5,361 corpus presets call it), and the compiler's internal names such as `_aboeq` run. With fidelity off nothing changes. +- **Each block keeps MilkDrop's own variables.** With fidelity on, per-pixel code and a wave's + per-point code run in their own variable space the way MilkDrop's separate machines do: they see + only what MilkDrop hands them (time, audio, the mesh size, q1..q32 and for waves t1..t8), keep + their own variables and `megabuf`, and nothing they write leaks back. `reg00`..`reg99` are + shared by every block and preset, so shapes now see the random values init leaves there, and + `loop`/`while` stop at MilkDrop's 1,048,576 turns a call. - **Composite shaders get the hue colour MilkDrop gives them.** MilkDrop passes every composite shader four slowly drifting corner colours as `hue_shader`, whatever the preset's `fShader` says; `fShader` only scales them on the fixed pipeline. The engine applied `fShader` to shaders @@ -1320,7 +1326,7 @@ npm test npm start -- --smoke ``` -**2227 unit tests, all passing.** They are written to check answers, not to exercise lines: +**2251 unit tests, all passing.** They are written to check answers, not to exercise lines: - **Formulas** are checked against values derived by hand from their definitions — Viviani's curve staying on its sphere, the torus tube radius, Chladni's m↔n antisymmetry, every attractor diff --git a/README.tr.md b/README.tr.md index 6b087f7..09a9c8d 100644 --- a/README.tr.md +++ b/README.tr.md @@ -12,7 +12,7 @@ [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-111997.svg)](#paketleme--dağıtım) [![Electron](https://img.shields.io/badge/Electron-43-47848F.svg)](https://www.electronjs.org/) [![İndirme](https://img.shields.io/github/downloads/CaYatur/SoundVisualizer/total?label=indirme)](https://github.com/CaYatur/SoundVisualizer/releases) -[![Test](https://img.shields.io/badge/test-2227%20geçiyor-2ea043.svg)](#testler) +[![Test](https://img.shields.io/badge/test-2251%20geçiyor-2ea043.svg)](#testler) [![cayadev.com](https://img.shields.io/badge/cayadev.com-e11d2a.svg)](https://cayadev.com) @@ -261,6 +261,12 @@ profilinde basamak olmadığını doğruluyor. tam sayılarla çalışıyor, `megabuf` ve `gmegabuf` indisleri onun yuvarladığı ve sınırladığı gibi hesaplanıyor, `rand(n)` gerçel sayı döndürüyor (korpusta 5.361 preset çağırıyor) ve derleyicinin `_aboeq` gibi iç adları çalışıyor. Uyum kapalıyken hiçbir şey değişmiyor. +- **Her blok MilkDrop'taki kendi değişkenleriyle çalışıyor.** Uyum açıkken per-pixel kodu ve bir + dalganın per-point kodu, MilkDrop'un ayrı sanal makineleri gibi kendi değişken alanında koşuyor: + yalnız MilkDrop'un verdiklerini görüyor (zaman, ses, ağ ölçüsü, q1..q32, dalgada t1..t8), kendi + değişkenlerini ve `megabuf`ını tutuyor, yazdıkları geri sızmıyor. `reg00`..`reg99` her blokta ve + presette ortak; şekiller artık init'in oraya bıraktığı rastgele değerleri görüyor. `loop`/`while` + MilkDrop'taki gibi çağrı başına 1.048.576 turda duruyor. - **Birleştirme shader'ları MilkDrop'un verdiği ton rengini alıyor.** MilkDrop her birleştirme shader'ına `hue_shader` olarak dört köşede yavaşça gezinen renkler veriyor, presetin `fShader`ı ne derse desin; `fShader` onları yalnız sabit yolda ölçekliyor. Motor `fShader`ı shader'lara da @@ -1318,7 +1324,7 @@ npm test npm start -- --smoke ``` -**2227 birim testi, hepsi geçiyor.** Satır çalıştırmak için değil, cevap denetlemek için yazıldılar: +**2251 birim testi, hepsi geçiyor.** Satır çalıştırmak için değil, cevap denetlemek için yazıldılar: - **Formüller**, tanımlarından elle türetilmiş değerlerle sınanıyor — Viviani eğrisinin küre üzerinde kalması, simidin boru yarıçapı, Chladni'nin m↔n antisimetrisi, her çekicinin sınırlı diff --git a/ROADMAP.md b/ROADMAP.md index 074186c..44eedfd 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -129,9 +129,9 @@ npm test npm start -- --smoke ``` -- **2227 unit tests, all passing** on `main`. 703 of those shipped in v3.1.0; +- **2251 unit tests, all passing** on `main`. 703 of those shipped in v3.1.0; 105 came with v3.1.1; 163 came with v3.1.2; 157 came with v3.1.3 — 1128 at - that tag — 469 more with v3.1.4, most of them from the MilkDrop work, and 630 + that tag — 469 more with v3.1.4, most of them from the MilkDrop work, and 654 on `main` since. Formulas are checked against values derived by hand from their definitions — Viviani's curve staying on its sphere, the @@ -2034,8 +2034,8 @@ rest after. No version number yet. module's script tag each fails a test. - **Fidelity follow-ups (#580)** · the fixed composite, the stage rule, MilkDrop's defaults, the hue colour, the values that stay in the file, - MilkDrop's way of reading a file and its expression compiler's rules are - done; the other audits are next. + MilkDrop's way of reading a file, its expression compiler's rules and + the scope of expression variables are done; the other audits are next. - **Checked against the source.** Nullsoft's own code (jecassis/foo_vis_milk2 5b44cea) and, for the fixed pipeline's blend passes, the D3D9 code the D3D11 fork was ported from (BeatDrop @@ -2257,22 +2257,82 @@ rest after. No version number yet. features and renders the same alone before and after (clean, brightness 0.038); main itself classed it blown in the sample run, so its class there depends on the order of the run, not on this change. + - **Each block has MilkDrop's own variables.** MilkDrop gives every + block a virtual machine of its own (state.cpp:213-225): init and + per-frame share one, per-pixel has one, each wave has one for + per-frame and one for per-point, each shape has one. Variables and + `megabuf` belong to the machine; only `reg00`..`reg99` and `gmegabuf` + are shared by all of them and by both presets of a transition. With + fidelity on the engine now does the same: + - `reg00`..`reg99` live in one array for everything, as ns-eel2's + `nseel_globalregs`; with fidelity off each pool keeps its own; + - per-pixel gets time, fps, frame, progress, the six audio values, the + mesh and pixel sizes and the aspect once a frame before per-frame + runs, q1..q32 after it, and zoom..sy per vertex from what per-frame + left (milkdropfs.cpp:619-650, 1647-1660); it no longer sees + per-frame's variables, and what it writes no longer reaches + per-frame or the drawing; + - a wave's per-point gets time, audio and the frame counters before + the wave's per-frame runs, q1..q32 and t1..t8 after it + (milkdropfs.cpp:2405-2421), and keeps its own variables; + - waves and shapes read time and audio from before per-frame, not + the values per-frame rewrote; + - `loop` and `while` stop at 1,048,576 turns a call + (`NSEEL_LOOPFUNC_SUPPORT_MAXLEN`), and the engine's budget is wider + where MilkDrop allows it: 4,194,304 for init, 1,048,576 for + per-frame code, at most 4,194,304 a frame for one shape over all its + instances; per-pixel and per-point keep 1,024 a vertex, where + MilkDrop's cap would freeze the app. + Per-pixel and per-point code is compiled twice, once per pool, since + fidelity can flip while a preset runs. Thumbnails clear `gmegabuf` + and the registers before each render, so a thumbnail no longer + depends on the preset drawn before it; a video export opens a fresh + page, so it starts from zero too. Two MilkDrop layers in one window + share the registers, as they already shared `gmegabuf`. + - **Measured** against the previous step, the last ten of 60 frames, + each preset in a fresh page so nothing carries over. Registers: of 120 + presets that use them, 40 change, 38 by more than 1% — the common + idiom is init writing random values to `reg01`..`reg03` and shapes + reading them, and the shapes saw 0 and stacked their instances on one + point. Per-pixel: of 120 presets whose per-pixel code reads or writes + something per-frame uses (696 in the corpus by a text scan), 46 + change, median 94%; of the 70 whose per-pixel reads a per-frame + variable it never sets, 31. Per-point: of 120 wave presets, 6 change + — the text scan counts 868 but ignores statement order and whether the + wave is enabled; in the one preset checked, per-point sets the shared + name before reading it. Time and audio before per-frame: the three presets + the scan predicts change (5% to 28%). Loop cap: of the six presets + that ran out of budget, four change. Every group is identical with + fidelity off, and control groups are identical with it on. A preset + costs about 0.03 ms more to build; a 1,728-vertex frame costs the + same. The 900-preset sample keeps 899 of 900 classes. The one that + moves, LuxXx – Benefiscient Prescience (clean → blown), keeps its own + `x` and `y` in per-frame and hands them to per-pixel as the zoom + centre; the last vertex used to overwrite them every frame, and now + the centre stays where the beat put it, as in MilkDrop. Alone in a + fresh page it differs by 61% and is not blown (brightness 0.62), so + its class in the sample also depends on the order of the run. - **Not done yet:** the fixed warp path, the blur chain, borders and centre darkening, and the rest of the blend snap points; whether `uv` in preset shaders runs the way MilkDrop's does — read back from the screen, our `uv.y` is 1 at the top, where MilkDrop's texture coordinate is 0; sampling agrees, but a shader doing arithmetic on - `uv.y` may come out mirrored, which needs its own check; the scope of - expression variables — MilkDrop gives per-frame, per-pixel, each - wave's per-frame and per-point and each shape their own variables and - `megabuf`, and hands per-pixel only a fixed list, where the engine - shares one set between init, per-frame and per-pixel (next); division - by zero, which is infinity in MilkDrop and 0 here; the loop cap — - MilkDrop stops `loop` and `while` at 1,048,576 turns a call, the engine - at a budget, which six corpus presets reach (five in init, one in a - shape); the reference comparison with an external renderer, which - needs one installed and waits for the user's approval. Left out of - the compiler: syntax newer than MilkDrop 2's grammar (`?:`, `&=`, + `uv.y` may come out mirrored, which needs its own check; division by + zero and other results that are not finite — MilkDrop gives infinity + or NaN and keeps it in the variable, the engine gives 0; run with a + realistic clock, 910 corpus presets (8.8%) divide by zero at least + once in 40 frames, some every frame, and matching MilkDrop needs NaN + carried through the whole drawing path the way Direct3D 9 does, which + only the reference renderer can check; the same holds for the + functions that guard their result — `log` and `log10` of 0 or less, + `pow`, `exp` and `tan` past the float range give 0 here, and `asin` + and `acos` clamp their input, where MilkDrop's C library returns + infinity or NaN (not counted in the scan above); the reference comparison with + an external renderer, which needs one installed and waits for the + user's approval. Left out of the compiler: variable names compared on + their first 16 characters only (`NSEEL_MAX_VARIABLE_NAMELEN`) — 24 + corpus presets use names that long and none has two that would + collide; syntax newer than MilkDrop 2's grammar (`?:`, `&=`, `|=`, `^=`, `<<`, `$` constants) and the functions `invsqrt`, `memcpy`, `memset` and `freembuf`, which no corpus preset uses in code; and the old grammar's `%`, which ns-eel2's parser table maps to a @@ -2336,7 +2396,22 @@ rest after. No version number yet. wrong mask), the real `rand` and its repeatability, the Preset switching all its blocks, `readingsDiffer` seeing the names, sprites, and every expression compile - in `src` passing the mode. 71 of 71 mutations are caught. + in `src` passing the mode. 71 of 71 mutations are caught. The + variable scope adds 24 (`milkdrop-md2-scope.test.js`): registers seen + by shapes, waves and a second preset, their name rule, `Preset.get` + and `set`, `resetGlobals`; per-pixel not seeing per-frame's variables + and not leaking its own, keeping them across vertices and frames, + taking its inputs before per-frame and q after, its own `megabuf` + beside the shared `gmegabuf` and registers, zoom..sy from per-frame + and `zoom_base` only with fidelity off; the same for a wave's + per-point with t1..t8; waves and shapes reading time and audio from + before per-frame; the loop cap, the budgets and one shape's total over + its instances; and every one of them flipping with fidelity in the + same frame. The thumbnail page test checks the reset before each + engine, two wave tests pass a per-frame counter through t1 and t2 as + a MilkDrop preset has to, and the compile guard counts 13 calls. 51 + of 52 mutations are caught; the one left caps loops with fidelity off + too, which changes nothing, since that budget never exceeds the cap. ## v3.1.6 — Comprehensive video export diff --git a/src/shared/milkdrop.js b/src/shared/milkdrop.js index 059518b..fdc8d47 100644 --- a/src/shared/milkdrop.js +++ b/src/shared/milkdrop.js @@ -66,11 +66,38 @@ b[k & 4095] = v; return v; }, + clear() { blocks.length = 0; }, }; } // gmegabuf presetler arasında ORTAK: MilkDrop'ta da öyle. const GMEM = makeMem(); + /* reg00..reg99: ns-eel2'de SÜREÇ GENELİNDE tek bir dizi + (nseel-eval.c `nseel_globalregs[100]`; ad büyük/küçük harfe bakmadan + "reg" + iki rakam). Her sanal makine — per_frame, per_pixel, her dalga + ve şekil, geçişteki iki preset — aynı yüz gözü görüyor ve hiçbiri + sıfırlamıyor. Uyum açıkken böyle; kapalıyken eski kural: her havuzun + kendi reg'leri (Pool.persistent). Kip koşarken okunuyor, anahtar + çevrilince aynı karede değişiyor. */ + const REGS = new Float64Array(100); + const REG_RE = /^reg\d\d$/; + + /* Döngü sınırı: ns-eel2'de `loop` ve `while` ÇAĞRI BAŞINA en çok + 1.048.576 tur (ns-eel.h NSEEL_LOOPFUNC_SUPPORT_MAXLEN; asm + nseel_asm_repeat / _repeatwhile). Uyum açıkken bu sınır da uygulanıyor; + motorun koşu başına bütçesi (loopBudget / loopBudgetMd2) uygulamanın + donmaması için ayrıca duruyor. */ + const LOOP_CAP = 1048576; + // Uyum açıkken bir şeklin bir karede, bütün örnekleriyle harcayabileceği tur + const SHAPE_FRAME_LOOPS = 4194304; + /* Ortak bellekleri (gmegabuf, reg'ler) sıfırlar. MilkDrop bunu hiç + yapmıyor; ölçüm ve testler presetleri birbirinden yalıtmak için + çağırıyor. */ + function resetGlobals() { + GMEM.clear(); + REGS.fill(0); + } + const PUNCT = [ '<<', '>>', '<=', '>=', '==', '!=', '&&', '||', '+=', '-=', '*=', '/=', '%=', @@ -517,11 +544,14 @@ this.index = new Map(); this.names = []; this.values = new Float64Array(0); - /* megabuf presetin KENDİNE ait. Havuzda duruyor çünkü init, per_frame - ve per_pixel ayrı ayrı derleniyor ama aynı belleği paylaşmaları - gerekiyor — MilkDrop'ta da öyle. */ + /* megabuf havuzun, yani sanal makinenin. Havuzda duruyor çünkü init + ile per_frame ayrı ayrı derleniyor ama MilkDrop'ta aynı makinede + koşuyor ve aynı belleği görüyor. per_pixel MilkDrop'ta AYRI makine: + uyum açıkken kendi havuzunda (pvPool), kendi megabuf'ıyla; uyum + kapalıyken eski hâli, ana havuzu paylaşıyor. */ this.mem = makeMem(); - // Kare boyunca kalıcı olanlar (registerlar) — sıfırlamada korunur + /* Kare boyunca kalıcı olanlar (registerlar) — sıfırlamada korunur. + Yalnız uyum kapalıyken kullanılıyor; açıkken reg'ler REGS'te. */ this.persistent = new Set(); for (let i = 0; i < 100; i++) { const n = 'reg' + (i < 10 ? '0' + i : i); @@ -593,12 +623,24 @@ } case 'var': { const i = pool.id(node.name); + if (REG_RE.test(node.name)) { + const r = +node.name.slice(3), mode = cx.mode; + return (P) => (mode.md2 ? REGS[r] : P[i]); + } return (P) => P[i]; } case 'assign': { const i = pool.id(node.name); const rhs = emit(node.v, pool, cx); const F = cx.F; + if (REG_RE.test(node.name)) { + const r = +node.name.slice(3), mode = cx.mode; + return (P) => { + const v = F(rhs(P)); + if (mode.md2) REGS[r] = v; else P[i] = v; + return v; + }; + } return (P) => (P[i] = F(rhs(P))); } case 'un': { @@ -622,7 +664,7 @@ case 'loop': { const n = emit(node.n, pool, cx); const body = node.body.map((b) => emit(b, pool, cx)); - const budget = cx.budget; + const budget = cx.budget, mode = cx.mode; const len = body.length; /* Bütçe: bir preset per_pixel içinde loop(10000, …) yazabilir. Ağın 1271 düğümünde 60 fps ile bu kare başına 762 milyon işlem demek — @@ -632,6 +674,7 @@ return (P) => { let k = n(P) | 0; if (k < 0) k = 0; + if (mode.md2 && k > LOOP_CAP) k = LOOP_CAP; for (let i = 0; i < k; i++) { if (--budget.n < 0) break; for (let j = 0; j < len; j++) body[j](P); @@ -767,7 +810,8 @@ const body = a[0]; const budget = cx.budget; return (P) => { - for (;;) { + for (let it = 0; ; it++) { + if (mode.md2 && it >= LOOP_CAP) break; if (--budget.n < 0) break; const v = body(P); if (mode.md2 ? !T_MD2(v) : v === 0) break; @@ -868,6 +912,8 @@ const budget = { n: 0 }; const cx = { F, D, M, R, budget, mode }; const LOOP_BUDGET = Math.max(0, Number(o.loopBudget) || 65536); + // Uyum açıkken bütçe (verilmezse aynısı): MilkDrop'un izin verdiğine yakın + const LOOP_BUDGET_MD2 = Math.max(0, Number(o.loopBudgetMd2) || LOOP_BUDGET); let prog; try { @@ -884,12 +930,17 @@ skipped: skipped.length, statements: stmts.length, resetSeed: (sd) => { seed = (sd || 12345) >>> 0; }, - run: (P) => { + /* cap: bu koşunun bütçesi bundan büyük olamaz (şeklin kare toplamı). + Dönüş: harcanan tur. */ + run: (P, cap) => { const V = P || p.values; - budget.n = LOOP_BUDGET; + const b = mode.md2 ? LOOP_BUDGET_MD2 : LOOP_BUDGET; + const start = cap !== undefined && cap < b ? cap : b; + budget.n = start; try { for (let i = 0; i < prog.length; i++) prog[i](V); } catch (e) { /* çalışma anı hatası kareyi düşürmesin */ } + return start - Math.max(0, budget.n); }, }; } @@ -1498,6 +1549,31 @@ ]; const SHARED_LEGACY = ['vol', 'vol_att', 'meshx', 'meshy', 'aspectx', 'aspecty', 'pixelsx', 'pixelsy']; + /* per_pixel'in KENDİ sanal makinesine giden kare girdileri (#580). + + MilkDrop per_pixel'i ayrı bir makinede koşturuyor (state.cpp:214 + `m_pv_eel`). O makine kare denklemlerinin değişkenlerini görmüyor; + yalnız şunları alıyor: + • bu liste — kare başına bir kez, per_frame KOŞMADAN ÖNCE + (milkdropfs.cpp:619-634, yürütme 638-641): per_frame `bass`i + değiştirse de per_pixel dosyanın/sesin değerini görüyor; + • q1..q32 — per_frame BİTTİKTEN sonra (649-650); + • zoom..sy — her düğümde per_frame'in bıraktığı değerler + (1651-1660), x, y, rad, ang da düğümün kendisi. + Kendi değişkenleri ve megabuf'ı kareler boyunca kalıyor. Fare bizim + eklentimiz: alt bloklara gittiği gibi buraya da gidiyor. */ + const PV_IN = ['time', 'fps', 'frame', 'progress', 'bass', 'mid', 'treb', 'bass_att', 'mid_att', + 'treb_att', 'meshx', 'meshy', 'pixelsx', 'pixelsy', 'aspectx', 'aspecty', + 'mouse_x', 'mouse_y', 'mouse_down']; + + /* Dalga per_point'inin KENDİ sanal makinesine gidenler (#580; state.cpp:220 + `m_pp_eel`, değişkenler 424-454). Dalganın per_frame değişkenlerini + görmüyor; bu liste dalganın per_frame kodu KOŞMADAN ÖNCE, q1..q32 ve + t1..t8 SONRA kopyalanıyor (milkdropfs.cpp:2405-2421). Nokta başına + sample, value1, value2, x, y ve renk ayrıca kuruluyor (wavePoint). */ + const PP_IN = ['time', 'fps', 'frame', 'progress', 'bass', 'mid', 'treb', 'bass_att', 'mid_att', + 'treb_att', 'mouse_x', 'mouse_y', 'mouse_down']; + class Preset { constructor(text, opts) { const o = opts || {}; @@ -1597,11 +1673,21 @@ /* Döngü bütçesi bloğun KAÇ KEZ koştuğuna göre veriliyor: init bir kez, per_frame saniyede 60 kez, per_pixel ise ağın 1271 düğümünde yani saniyede ~76 bin kez. Tek bir sabit bütçe ya init'i boğardı ya da - per_pixel'de uygulamayı dondururdu. */ + per_pixel'de uygulamayı dondururdu. Uyum açıkken init ve kare + blokları MilkDrop'un çağrı sınırına göre daha geniş (loopBudgetMd2): + korpusta beş preset init'te ~1,07 milyon tur, bir şekil karede + ~103 bin tur istiyor. Düğüm ve nokta başına bütçe aynı — MilkDrop'un + çağrı sınırı orada uygulamayı dondururdu. */ const eel = this._eel(); - this.cInit = compile(this.file.init, this.pool, Object.assign({ seed: o.seed, loopBudget: 1048576 }, eel)); - this.cFrame = compile(this.file.perFrame, this.pool, Object.assign({ seed: o.seed, loopBudget: 65536 }, eel)); + this.cInit = compile(this.file.init, this.pool, Object.assign({ seed: o.seed, loopBudget: 1048576, loopBudgetMd2: 4194304 }, eel)); + this.cFrame = compile(this.file.perFrame, this.pool, Object.assign({ seed: o.seed, loopBudget: 65536, loopBudgetMd2: 1048576 }, eel)); this.cPixel = compile(this.file.perPixel, this.pool, Object.assign({ seed: o.seed, loopBudget: 1024 }, eel)); + /* Uyum açıkken per_pixel kendi havuzunda (PV_IN). Aynı kod iki kez + derleniyor: kapanışlar havuzun indislerini ve megabuf'ını derleme + anında bağlıyor, anahtar ise koşarken çevrilebiliyor. Hatalar + aynı metinden, bir kez sayılıyor. */ + this.pvPool = new Pool(); + this.cPixelMd2 = compile(this.file.perPixel, this.pvPool, Object.assign({ seed: o.seed, loopBudget: 1024 }, eel)); for (const c of [this.cInit, this.cFrame, this.cPixel]) { if (c.error) this.errors.push(c.error); } @@ -1694,9 +1780,13 @@ /* per_point saniyede samples×60 kez koşuyor; bütçe per_pixel'inkiyle aynı mantıkta, blok başına veriliyor. */ const eel = this._eel(); - wave.cInit = compile(w.init || '', pool, Object.assign({ seed: o.seed, loopBudget: 65536 }, eel)); - wave.cFrame = compile(w.per_frame || '', pool, Object.assign({ seed: o.seed, loopBudget: 65536 }, eel)); + wave.cInit = compile(w.init || '', pool, Object.assign({ seed: o.seed, loopBudget: 65536, loopBudgetMd2: 4194304 }, eel)); + wave.cFrame = compile(w.per_frame || '', pool, Object.assign({ seed: o.seed, loopBudget: 65536, loopBudgetMd2: 1048576 }, eel)); wave.cPoint = compile(w.per_point || '', pool, Object.assign({ seed: o.seed, loopBudget: 1024 }, eel)); + /* Uyum açıkken per_point kendi havuzunda, kendi megabuf'ıyla (PP_IN); + per_pixel gibi iki kez derleniyor. */ + wave.ppPool = new Pool(); + wave.cPointMd2 = compile(w.per_point || '', wave.ppPool, Object.assign({ seed: o.seed, loopBudget: 1024 }, eel)); for (const c of [wave.cInit, wave.cFrame, wave.cPoint]) { if (c.error) this.errors.push('wave ' + i + ': ' + c.error); } @@ -1754,8 +1844,8 @@ açıkken bu taban geçerli (shapeFrame; #580). */ shape.baseMd2 = Object.assign({}, shape.base, { g: g('g', 0), b: g('b', 0), g2: g('g2', 1) }); const eel = this._eel(); - shape.cInit = compile(s.init || '', pool, Object.assign({ seed: o.seed, loopBudget: 65536 }, eel)); - shape.cFrame = compile(s.per_frame || '', pool, Object.assign({ seed: o.seed, loopBudget: 65536 }, eel)); + shape.cInit = compile(s.init || '', pool, Object.assign({ seed: o.seed, loopBudget: 65536, loopBudgetMd2: 4194304 }, eel)); + shape.cFrame = compile(s.per_frame || '', pool, Object.assign({ seed: o.seed, loopBudget: 65536, loopBudgetMd2: 1048576 }, eel)); for (const c of [shape.cInit, shape.cFrame]) { if (c.error) this.errors.push('shape ' + i + ': ' + c.error); } @@ -1770,7 +1860,15 @@ değişkenleri ve ses girdileriyle sürüyor, o yüzden bunlar paylaşılmalı. */ _shareInto(pool) { const P = this.pool; - for (const k of SHARED_VARS) pool.set(k, P.get(k)); + /* Zaman ve ses: MilkDrop dalgaya ve şekle bunları kendi kaynağından + veriyor (milkdropfs.cpp LoadCustomWavePerFrameEvallibVars, + LoadCustomShapePerFrameEvallibVars), ana per_frame'in değiştirdiği + değerden değil. Uyum açıkken per_frame'den ÖNCEKİ değerler + okunuyor — per_pixel'in aldığı anlık görüntü (PV_IN hepsini + içeriyor). Korpusta 3 preset per_frame'de bunlardan birini yazıp + bir dalgada ya da şekilde okuyor. */ + const S = this.accurate !== false ? this.pvPool : P; + for (const k of SHARED_VARS) pool.set(k, S.get(k)); if (this.accurate === false) for (const k of SHARED_LEGACY) pool.set(k, P.get(k)); // Uyum açıkken per_frame'in bıraktığı q; kapalıyken havuzun o anki hâli const q = this.accurate !== false ? this._qFrame : null; @@ -1793,7 +1891,11 @@ w._tInit = captureT(P); } restoreT(P, w._tInit); + const V = w.ppPool; + for (const k of PP_IN) V.set(k, P.get(k)); w.cFrame.run(P.values); + for (let i = 1; i <= 32; i++) V.set('q' + i, P.get('q' + i)); + for (let i = 1; i <= 8; i++) V.set('t' + i, P.get('t' + i)); /* NOKTA SAYISI per_frame'den SONRA okunuyor. MilkDrop: nSamples = (int)*var_pf_samples; nSamples = std::min(512, nSamples); @@ -1816,7 +1918,8 @@ `out` her çağrıda YENİDEN KULLANILIYOR: 512 nokta için kare başına 512 nesne ayırmak kabul edilemezdi. */ wavePoint(w, sample, v1, v2, out) { - const P = w.pool; + const md2 = this._mode.md2; + const P = md2 ? w.ppPool : w.pool; P.set('sample', sample); P.set('value1', v1); P.set('value2', v2); @@ -1838,7 +1941,7 @@ P.set('r', w._ppColor.r); P.set('g', w._ppColor.g); P.set('b', w._ppColor.b); P.set('a', w._ppColor.a); } - w.cPoint.run(P.values); + (md2 ? w.cPointMd2 : w.cPoint).run(P.values); const o = out || {}; o.x = P.get('x'); o.y = P.get('y'); o.r = P.get('r'); o.g = P.get('g'); o.b = P.get('b'); o.a = P.get('a'); @@ -1862,21 +1965,42 @@ s._tInit = captureT(P); } restoreT(P, s._tInit); - s.cFrame.run(P.values); + /* Şeklin kare denklemi örnek başına koşuyor (1024'e kadar). Uyum + açıkken koşu başına bütçe MilkDrop'un çağrı sınırında; tek bir + şeklin bir karede harcayabileceği toplam ayrıca sınırlı + (SHAPE_FRAME_LOOPS), yoksa her örneğinde dönen bir preset + uygulamayı dondururdu. Korpustaki en büyük ihtiyaç ~103 bin tur. */ + if (this._mode.md2) { + if (instance === 0 || s._loopLeft === undefined) s._loopLeft = SHAPE_FRAME_LOOPS; + s._loopLeft -= s.cFrame.run(P.values, Math.max(0, s._loopLeft)); + } else { + s.cFrame.run(P.values); + } const o = out || {}; for (const k in b) o[k] = P.get(k); return o; } - // Havuzdaki değişkenlere kısayol - get(name) { return this.pool.get(name); } - set(name, v) { this.pool.set(name, v); } + /* Havuzdaki değişkenlere kısayol. Uyum açıkken reg'ler havuzda değil, + ortak REGS dizisinde. */ + get(name) { + if (this._mode.md2 && name.charCodeAt(0) === 114 && REG_RE.test(name)) return REGS[+name.slice(3)]; + return this.pool.get(name); + } + set(name, v) { + if (this._mode.md2 && name.charCodeAt(0) === 114 && REG_RE.test(name)) REGS[+name.slice(3)] = Number(v) || 0; + else this.pool.set(name, v); + } /* Kare başına: girdi değişkenlerini yaz, init'i (bir kez) ve per_frame'i koştur. inputs: { time, fps, frame, bass, mid, treb, bass_att, ... } */ frame(inputs) { const P = this.pool; if (inputs) for (const k in inputs) P.set(k, inputs[k]); + /* per_pixel'in girdileri per_frame'den ÖNCE (PV_IN). Anahtar kare + ortasında çevrilebildiği için kip ne olursa olsun dolduruluyor. */ + const V = this.pvPool; + for (const k of PV_IN) V.set(k, P.get(k)); const base = this.accurate ? this._pfBaseMd2 : this._pfBase; if (!this.initialised) { /* MilkDrop init'i koşturmadan önce de yerleşik adları yüklüyor @@ -1906,6 +2030,7 @@ dalgalara ve şekillere düğümlerin bıraktığı değeri geçiriyordu. */ if (!this._qFrame) this._qFrame = new Array(NUM_Q); for (let i = 0; i < NUM_Q; i++) this._qFrame[i] = P.get('q' + (i + 1)); + for (let i = 0; i < NUM_Q; i++) V.set('q' + (i + 1), this._qFrame[i]); return P; } @@ -1913,13 +2038,18 @@ değişkenleri okunur. Dönüş nesnesi HER ÇAĞRIDA YENİDEN KULLANILIR — 1728 düğüm için kare başına 1728 nesne ayırmak kabul edilemezdi. */ pixel(x, y, rad, ang, out) { - const P = this.pool; + /* Uyum açıkken per_pixel'in kendi havuzu: kare denklemlerinin + değişkenlerini görmüyor, yazdıkları da kare denklemlerine ve + çizime sızmıyor (PV_IN). */ + const md2 = this._mode.md2; + const P = md2 ? this.pvPool : this.pool; P.set('x', x); P.set('y', y); P.set('rad', rad); P.set('ang', ang); // Varsayılanlar her düğümde yeniden kurulur; presetler bunlara güvenir - P.set('zoom', P.get('zoom_base') || this._base.zoom); + // `zoom_base` motorun eski bir kaçamağı; MilkDrop'ta yok, korpusta kullanan yok + P.set('zoom', md2 ? this._base.zoom : (P.get('zoom_base') || this._base.zoom)); P.set('zoomexp', this._base.zoomexp); P.set('rot', this._base.rot); P.set('warp', this._base.warp); @@ -1929,7 +2059,7 @@ P.set('dy', this._base.dy); P.set('sx', this._base.sx); P.set('sy', this._base.sy); - this.cPixel.run(P.values); + (md2 ? this.cPixelMd2 : this.cPixel).run(P.values); const o = out || {}; o.zoom = P.get('zoom'); o.zoomexp = P.get('zoomexp'); @@ -2176,7 +2306,8 @@ const api = { tokenize, parse, compile, Pool, FUNCS, parseMilk, Preset, clampColor, colorNorm, md2Versions, stagePlan, genWarpText, genCompText, - echoFlipBits, fixedCompWeights, parseMilkMd2, readMilk, readVersions, readingsDiffer }; + echoFlipBits, fixedCompWeights, parseMilkMd2, readMilk, readVersions, readingsDiffer, + resetGlobals }; if (typeof module !== 'undefined' && module.exports) module.exports = api; if (typeof window !== 'undefined') window.SVMilkdrop = api; })(); diff --git a/src/thumbs/thumbs.js b/src/thumbs/thumbs.js index aebfc95..1edbcea 100644 --- a/src/thumbs/thumbs.js +++ b/src/thumbs/thumbs.js @@ -72,6 +72,11 @@ canvas.width = R.renderW; canvas.height = R.renderH; seedRandom(job.key); + /* gmegabuf ve reg'ler presetler arasında ortak (MilkDrop'ta da); küçük + resimler aynı sayfada art arda çiziliyor. Sıfırlanmasa bir resim + kendinden önce çizilenin bıraktığına bağlı olurdu — anahtar aynı, + görüntü farklı. */ + window.SVMilkdrop.resetGlobals(); const mode = new window.SVModes.milkdrop(canvas); const cfg = { milkdrop: { diff --git a/tests/milkdrop-block-reset.test.js b/tests/milkdrop-block-reset.test.js index ed72e10..274c600 100644 --- a/tests/milkdrop-block-reset.test.js +++ b/tests/milkdrop-block-reset.test.js @@ -87,12 +87,14 @@ test('dalga: per_point içindeki t noktalar boyunca BİRİKİYOR', () => { test('dalga: rezerve OLMAYAN değişken taşınmaya devam ediyor', () => { /* Yalnızca t1..t8 ve dosya değerleri sıfırlanıyor. MilkDrop\'un eel - değişken tablosunda gerisi duruyor ve presetler buna güveniyor. */ + değişken tablosunda gerisi duruyor ve presetler buna güveniyor. + per_point MilkDrop'ta ayrı makine, per_frame'in değişkenini görmüyor + (#580): değer ona t1 ile gidiyor. */ const p = preset([ 'wavecode_0_enabled=1', 'wave_0_init1=birikim = 0;', - 'wave_0_per_frame1=birikim = birikim + 1;', - 'wave_0_per_point1=x = birikim;', + 'wave_0_per_frame1=birikim = birikim + 1; t1 = birikim;', + 'wave_0_per_point1=x = t1;', ]); for (let f = 1; f <= 4; f++) { p.frame(INPUTS); diff --git a/tests/milkdrop-frame-reset.test.js b/tests/milkdrop-frame-reset.test.js index 56a15f4..06aa40d 100644 --- a/tests/milkdrop-frame-reset.test.js +++ b/tests/milkdrop-frame-reset.test.js @@ -63,6 +63,8 @@ test('preset yazarının kendi değişkeni KALICI', () => { }); test('reg00 kalıcı — MilkDrop\'ta kareler arası depo bu', () => { + // Uyum açıkken reg'ler süreç geneli; önceki testlerden kalan silinsin + MD.resetGlobals(); const p = mk('per_frame_1=reg00 = reg00 + 0.5;'); run(p, 6); assert.strictEqual(p.get('reg00'), 3); diff --git a/tests/milkdrop-md2-eel.test.js b/tests/milkdrop-md2-eel.test.js index 3de19c8..3c4953b 100644 --- a/tests/milkdrop-md2-eel.test.js +++ b/tests/milkdrop-md2-eel.test.js @@ -341,5 +341,5 @@ test('kaynakta her ifade derlemesi kipini veriyor', () => { assert.match(args, /\beel\b|mode:/, path.relative(ROOT, file) + ': ' + m[0] + args.slice(0, 90)); } } - assert.strictEqual(found, 11, 'derleme çağrısı sayısı (motor 8, sprite 2, öz test 1)'); + assert.strictEqual(found, 13, 'derleme çağrısı sayısı (motor 10, sprite 2, öz test 1)'); }); diff --git a/tests/milkdrop-md2-scope.test.js b/tests/milkdrop-md2-scope.test.js new file mode 100644 index 0000000..5d68dd0 --- /dev/null +++ b/tests/milkdrop-md2-scope.test.js @@ -0,0 +1,276 @@ +'use strict'; +/* #580: ifade değişkenlerinin KAPSAMI — MilkDrop 2'nin sanal makineleri. + + ns-eel2'de reg00..reg99 süreç genelinde tek dizi (nseel-eval.c + nseel_globalregs); per_frame, per_pixel, dalgalar, şekiller ve iki preset + aynı gözleri görüyor. Uyum kapalıyken her havuzun kendi reg'leri. */ +const test = require('node:test'); +const assert = require('node:assert'); +const MD = require('../src/shared/milkdrop.js'); + +const IN = { time: 1, frame: 1, fps: 60, bass: 1, mid: 1, treb: 1, bass_att: 1, mid_att: 1, treb_att: 1 }; +const mk = (body, acc) => new MD.Preset(body, { seed: 1, accurate: acc !== false }); + +test('reg: per_frame yazıyor, şekil ve dalga okuyor — yalnız uyum açıkken', () => { + const body = [ + 'per_frame_1=reg07 = 0.25;', + 'shapecode_0_enabled=1', + 'shape_0_per_frame1=x = reg07;', + 'wavecode_0_enabled=1', + 'wave_0_per_frame1=r = reg07;', + ].join('\n'); + MD.resetGlobals(); + const p = mk(body); + p.frame(IN); + assert.strictEqual(p.shapeFrame(p.shapes[0], 0).x, 0.25, 'şekil ortak reg\'i görüyor'); + p.waveFrame(p.waves[0]); + assert.strictEqual(p.waves[0].pool.get('r'), 0.25, 'dalga ortak reg\'i görüyor'); + MD.resetGlobals(); + const old = mk(body, false); + old.frame(IN); + assert.strictEqual(old.shapeFrame(old.shapes[0], 0).x, 0, 'uyum kapalı: şeklin kendi reg07\'si 0'); + assert.strictEqual(old.get('reg07'), 0.25, 'uyum kapalı: ana havuzda duruyor'); +}); + +test('reg: iki preset aynı gözleri paylaşıyor; ad büyük/küçük harfe bakmıyor', () => { + MD.resetGlobals(); + const a = mk('per_frame_1=REG42 = 3;'); + const b = mk('per_frame_1=monitor = reg42;'); + a.frame(IN); + b.frame(IN); + assert.strictEqual(b.get('monitor'), 3, 'b, a\'nın yazdığını okudu'); + assert.strictEqual(b.get('reg42'), 3, 'Preset.get ortak diziden okuyor'); + b.set('reg42', 5); + assert.strictEqual(a.get('reg42'), 5, 'Preset.set ortak diziye yazıyor'); + MD.resetGlobals(); + assert.strictEqual(a.get('reg42'), 0, 'resetGlobals reg\'leri siliyor'); +}); + +test('reg: yalnız "reg" + iki rakam; üç rakam ya da tek rakam sıradan değişken', () => { + MD.resetGlobals(); + const a = mk('per_frame_1=reg1 = 1; reg100 = 2; regab = 3; reg99 = 4;'); + const b = mk('per_frame_1=monitor = reg1 + reg100 + regab + reg99;'); + a.frame(IN); + b.frame(IN); + assert.strictEqual(b.get('monitor'), 4, 'yalnız reg99 ortak'); + assert.strictEqual(a.get('reg100'), 2, 'reg100 presetin kendi değişkeni'); +}); + +test('reg: anahtar koşarken çevrilince aynı karede kaynak değişiyor', () => { + MD.resetGlobals(); + const p = mk('per_frame_1=reg03 = reg03 + 1;'); + p.frame(IN); p.frame(IN); + assert.strictEqual(p.get('reg03'), 2); + p.accurate = false; + p.frame(IN); + assert.strictEqual(p.get('reg03'), 1, 'kapalıyken havuzun kendi reg03\'ü (0) üzerinden'); + p.accurate = true; + assert.strictEqual(p.get('reg03'), 2, 'açılınca ortak dizi yerinde duruyor'); +}); + +test('resetGlobals gmegabuf\'u da siliyor', () => { + MD.resetGlobals(); + const a = mk('per_frame_1=gmegabuf(12) = 7;'); + const b = mk('per_frame_1=monitor = gmegabuf(12);'); + a.frame(IN); + b.frame(IN); + assert.strictEqual(b.get('monitor'), 7); + MD.resetGlobals(); + b.frame(IN); + assert.strictEqual(b.get('monitor'), 0); +}); + +/* per_pixel'in kendi sanal makinesi (state.cpp:214 m_pv_eel). Girdiler + per_frame'den önce, q'lar sonra, zoom..sy düğüm başına per_frame'in + bıraktığından; kendi değişkenleri ve megabuf'ı kalıcı. */ +const IN2 = Object.assign({}, IN, { meshx: 48, meshy: 36, pixelsx: 640, pixelsy: 480, aspectx: 0.75, aspecty: 1 }); +const px = (p) => { p.frame(IN2); p.captureBase(); return p.pixel(0.5, 0.5, 0.2, 0.1, {}); }; + +test('per_pixel kare denklemlerinin değişkenini görmüyor', () => { + const body = 'per_frame_1=myv = 0.3;\nper_pixel_1=rot = myv;'; + assert.strictEqual(px(mk(body)).rot, 0, 'MilkDrop: kendi makinesinde myv 0'); + assert.strictEqual(px(mk(body, false)).rot, 0.3, 'uyum kapalı: tek havuz'); +}); + +test('per_pixel\'in yazdığı kare denklemlerine ve çizime sızmıyor', () => { + const body = 'fDecay=0.98\nper_frame_1=monitor = zz;\nper_pixel_1=decay = 0.1; zz = 5; wave_r = 0.2;'; + const p = mk(body); + px(p); px(p); + assert.deepStrictEqual([p.get('decay'), p.get('zz'), p.get('monitor')], [0.98, 0, 0]); + assert.strictEqual(p.get('wave_r') === 0.2, false, 'dalga rengi per_pixel\'den gelmiyor'); + const old = mk(body, false); + px(old); px(old); + assert.deepStrictEqual([old.get('zz'), old.get('monitor')], [5, 5], 'uyum kapalı: sızıyor'); +}); + +test('per_pixel\'in kendi değişkenleri düğümler ve kareler boyunca kalıyor', () => { + const p = mk('per_pixel_1=cnt = cnt + 1; dx = cnt;'); + p.frame(IN2); p.captureBase(); + p.pixel(0, 0, 0, 0, {}); p.pixel(0, 0, 0, 0, {}); + assert.strictEqual(p.pixel(0, 0, 0, 0, {}).dx, 3); + p.frame(IN2); p.captureBase(); + assert.strictEqual(p.pixel(0, 0, 0, 0, {}).dx, 4, 'kare başında sıfırlanmıyor'); +}); + +test('per_pixel girdileri per_frame\'den ÖNCE alıyor, q\'ları SONRA', () => { + const body = 'per_frame_1=bass = 5; meshx = 2; q1 = 0.7;\nper_pixel_1=rot = bass; dx = meshx; dy = q1; sx = aspectx; sy = pixelsy;'; + const o = px(mk(body)); + assert.deepStrictEqual([o.rot, o.dx, o.dy, o.sx, o.sy], [1, 48, 0.7, 0.75, 480]); + const l = px(mk(body, false)); + assert.deepStrictEqual([l.rot, l.dx, l.dy], [5, 2, 0.7], 'uyum kapalı: per_frame\'in değiştirdiği'); +}); + +test('per_pixel\'in q yazması kare denklemlerinin q\'suna dokunmuyor', () => { + const p = mk('per_frame_1=q2 = q2 + 1;\nper_pixel_1=q2 = 100;'); + px(p); px(p); + assert.strictEqual(p.get('q2'), 1, 'her kare init sonrası değerden (0) başlıyor, per_pixel\'in 100\'ü görünmüyor'); +}); + +test('megabuf per_pixel\'in kendisinin; gmegabuf ve reg ortak', () => { + MD.resetGlobals(); + const body = 'per_frame_1=megabuf(3) = 2; gmegabuf(3) = 4; reg10 = 0.4;\nper_pixel_1=rot = megabuf(3); dx = gmegabuf(3); dy = reg10;'; + const o = px(mk(body)); + assert.deepStrictEqual([o.rot, o.dx, o.dy], [0, 4, 0.4]); + MD.resetGlobals(); + const l = px(mk(body, false)); + assert.deepStrictEqual([l.rot, l.dx, l.dy], [2, 4, 0.4], 'uyum kapalı: tek megabuf, havuzun reg\'i'); +}); + +test('per_pixel zoom..sy\'yi per_frame\'in bıraktığından alıyor; zoom_base yalnız uyum kapalıyken', () => { + const body = 'per_frame_1=zoom = 1.2; rot = 0.3; zoom_base = 2;\nper_pixel_1=warp = zoom + rot;'; + const o = px(mk(body)); + assert.deepStrictEqual([o.zoom, o.rot], [1.2, 0.3]); + assert.ok(Math.abs(o.warp - 1.5) < 1e-12); + assert.strictEqual(px(mk(body, false)).zoom, 2, 'eski kaçamak'); + // per_pixel'in kendisi yazsa da sonraki düğümün zoom'u per_frame'inki + const q = mk('per_frame_1=zoom = 1.2;\nper_pixel_1=zoom_base = 3;'); + px(q); + assert.strictEqual(q.pixel(0.5, 0.5, 0.2, 0.1, {}).zoom, 1.2); +}); + +test('anahtar koşarken çevrilince per_pixel aynı karede havuz değiştiriyor', () => { + const p = mk('per_frame_1=myv = 0.3;\nper_pixel_1=rot = myv;'); + assert.strictEqual(px(p).rot, 0); + p.accurate = false; + p.captureBase(); + assert.strictEqual(p.pixel(0.5, 0.5, 0.2, 0.1, {}).rot, 0.3); + p.accurate = true; + assert.strictEqual(p.pixel(0.5, 0.5, 0.2, 0.1, {}).rot, 0); +}); + +/* Dalga per_point'inin kendi sanal makinesi (state.cpp:220 m_pp_eel): + zaman ve ses dalganın per_frame'inden ÖNCE, q ve t1..t8 SONRA. */ +const wv = (lines, acc) => mk(['wavecode_0_enabled=1'].concat(lines).join('\n'), acc); +const pt = (p, s) => { p.frame(IN2); p.waveFrame(p.waves[0]); return p.wavePoint(p.waves[0], s || 0, 0.1, 0.2, {}); }; + +test('per_point dalganın per_frame değişkenini görmüyor', () => { + const body = ['wave_0_per_frame1=myv = 0.3;', 'wave_0_per_point1=x = myv;']; + assert.strictEqual(pt(wv(body)).x, 0); + assert.strictEqual(pt(wv(body, false)).x, 0.3, 'uyum kapalı: tek havuz'); +}); + +test('per_point\'in yazdığı dalganın per_frame\'ine sızmıyor', () => { + const body = ['wave_0_per_frame1=r = zz * 0.1;', 'wave_0_per_point1=zz = 5;']; + const p = wv(body); + pt(p); pt(p); + assert.strictEqual(p.waves[0]._ppColor.r, 0); + const l = wv(body, false); + pt(l); pt(l); + assert.strictEqual(l.waves[0]._ppColor.r, 0.5, 'uyum kapalı: sızıyor'); +}); + +test('per_point\'in kendi değişkenleri noktalar ve kareler boyunca kalıyor', () => { + const p = wv(['wave_0_per_point1=cnt = cnt + 1; x = cnt;']); + pt(p); + assert.strictEqual(p.wavePoint(p.waves[0], 0.5, 0, 0, {}).x, 2); + assert.strictEqual(pt(p).x, 3, 'yeni karede sıfırlanmıyor'); +}); + +test('per_point girdileri dalganın per_frame\'inden ÖNCE, q ve t\'yi SONRA alıyor', () => { + const body = ['wave_0_per_frame1=bass = 7; time = 9; t3 = 0.4; q5 = 0.6;', + 'wave_0_per_point1=x = bass; y = time; r = t3; g = q5;']; + const o = pt(wv(body)); + assert.deepStrictEqual([o.x, o.y, o.r, o.g], [1, 1, 0.4, 0.6]); + const l = pt(wv(body, false)); + assert.deepStrictEqual([l.x, l.y, l.r, l.g], [7, 9, 0.4, 0.6]); +}); + +test('per_point\'in t yazması kalıcı değil; her kare per_frame\'in t\'sinden', () => { + const p = wv(['wave_0_init1=t4 = 10;', 'wave_0_per_point1=t4 = t4 + 1; x = t4;']); + assert.strictEqual(pt(p).x, 11); + assert.strictEqual(p.wavePoint(p.waves[0], 0.5, 0, 0, {}).x, 12, 'kare içinde noktadan noktaya taşınıyor'); + assert.strictEqual(pt(p).x, 11, 'yeni karede init\'in 10\'undan'); +}); + +test('per_point\'in megabuf\'ı kendisinin', () => { + const body = ['wave_0_per_frame1=megabuf(2) = 3;', 'wave_0_per_point1=x = megabuf(2);']; + assert.strictEqual(pt(wv(body)).x, 0); + assert.strictEqual(pt(wv(body, false)).x, 3); +}); + +test('anahtar koşarken çevrilince per_point aynı karede havuz değiştiriyor', () => { + const p = wv(['wave_0_per_frame1=myv = 0.3;', 'wave_0_per_point1=x = myv;']); + assert.strictEqual(pt(p).x, 0); + p.accurate = false; + assert.strictEqual(p.wavePoint(p.waves[0], 0, 0.1, 0.2, {}).x, 0.3); +}); + +test('dalga ve şekil zamanı ve sesi ana per_frame\'den ÖNCEKİ değerden alıyor', () => { + const body = ['per_frame_1=bass = 5; time = 9;', + 'shapecode_0_enabled=1', 'shape_0_per_frame1=x = bass; y = time;', + 'wavecode_0_enabled=1', 'wave_0_per_frame1=r = bass; g = time;'].join('\n'); + const p = mk(body); + p.frame(IN); + const s = p.shapeFrame(p.shapes[0], 0); + p.waveFrame(p.waves[0]); + assert.deepStrictEqual([s.x, s.y, p.waves[0]._ppColor.r, p.waves[0]._ppColor.g], [1, 1, 1, 1]); + const l = mk(body, false); + l.frame(IN); + const ls = l.shapeFrame(l.shapes[0], 0); + assert.deepStrictEqual([ls.x, ls.y], [5, 9], 'uyum kapalı: per_frame\'in değiştirdiği'); + p.accurate = false; + p.frame(IN); + assert.strictEqual(p.shapeFrame(p.shapes[0], 0).x, 5, 'anahtar koşarken çevrilince aynı karede'); +}); + +/* Döngü sınırı: ns-eel2'de loop ve while ÇAĞRI BAŞINA 1.048.576 tur + (NSEEL_LOOPFUNC_SUPPORT_MAXLEN). Motorun koşu başına bütçesi uyum + açıkken init'te 4.194.304, kare bloklarında 1.048.576. */ +test('loop ve while çağrı başına 1.048.576 turda kesiliyor; init iki çağrıyı da tamamlıyor', () => { + const body = 'per_frame_init_1=loop(2000000, a = a + 1); loop(2000000, b = b + 1); while(c = c + 1); while(d = d + 1);'; + const p = mk(body); + p.frame(IN); + assert.deepStrictEqual(['a', 'b', 'c', 'd'].map((k) => p.get(k)), [1048576, 1048576, 1048576, 1048576]); + const l = mk(body, false); + l.frame(IN); + assert.deepStrictEqual(['a', 'b', 'c', 'd'].map((k) => l.get(k)), [1048576, 0, 0, 0], 'uyum kapalı: tek bütçe ilk döngüde bitiyor'); +}); + +test('kare bloğunun bütçesi uyum açıkken 1.048.576', () => { + const body = 'per_frame_1=a = 0; loop(100000, a = a + 1);\nwavecode_0_enabled=1\nwave_0_per_frame1=b = 0; loop(100000, b = b + 1); r = b;'; + const p = mk(body); + p.frame(IN); + p.waveFrame(p.waves[0]); + assert.deepStrictEqual([p.get('a'), p.waves[0]._ppColor.r], [100000, 100000]); + const l = mk(body, false); + l.frame(IN); + l.waveFrame(l.waves[0]); + assert.deepStrictEqual([l.get('a'), l.waves[0]._ppColor.r], [65536, 65536]); +}); + +test('bir şeklin bir karede bütün örnekleriyle harcayabileceği tur sınırlı', () => { + const body = 'shapecode_0_enabled=1\nshapecode_0_num_inst=8\nshape_0_per_frame1=loop(1000000, reg00 = reg00 + 1);'; + MD.resetGlobals(); + const p = mk(body); + p.frame(IN); + for (let k = 0; k < 8; k++) p.shapeFrame(p.shapes[0], k); + assert.strictEqual(p.get('reg00'), 4194304, 'dört örnek 1 milyon, beşinci kalanı, gerisi hiç'); + MD.resetGlobals(); + p.frame(IN); + p.shapeFrame(p.shapes[0], 0); + assert.strictEqual(p.get('reg00'), 1000000, 'yeni karede sayaç baştan'); + const l = mk(body, false); + l.frame(IN); + for (let k = 0; k < 8; k++) l.shapeFrame(l.shapes[0], k); + assert.strictEqual(l.shapes[0].pool.get('reg00'), 8 * 65536, 'uyum kapalı: örnek başına eski bütçe'); +}); diff --git a/tests/milkdrop-thumbs.test.js b/tests/milkdrop-thumbs.test.js index 3723318..1e82774 100644 --- a/tests/milkdrop-thumbs.test.js +++ b/tests/milkdrop-thumbs.test.js @@ -272,7 +272,7 @@ test('yerleşik presetler Node\'da da okunuyor; sayfada kayıt bir kez', () => { function driver(opts) { const o = opts || {}; - const log = { modes: 0, disposed: 0, draws: [], randoms: [], done: [], drawn: null }; + const log = { modes: 0, disposed: 0, draws: [], randoms: [], done: [], drawn: null, resets: [] }; const jobs = []; function Canvas() { const c = { @@ -319,6 +319,8 @@ function driver(opts) { setTimeout, clearTimeout, Promise, Uint8Array, Float32Array, Math: pageMath, }; ctx.window.SVModes = { milkdrop: FakeMode }; + // Ortak bellekler: kaçıncı motordan ÖNCE sıfırlandığı yazılıyor + ctx.window.SVMilkdrop = { resetGlobals: () => log.resets.push(log.modes) }; ctx.window.SVDemoAudio = require('../src/shared/demo-audio.js'); ctx.window.thumbs = { onJob: (cb) => jobs.push(cb), @@ -350,6 +352,7 @@ test('çizim sayfası: her iş yeni motor, reçetedeki boyut ve kare sayısı, W assert.deepStrictEqual(d.log.blob, { type: 'image/webp', q: 0.8 }); await d.run(JOB(K('b'))); assert.deepStrictEqual([d.log.modes, d.log.disposed], [2, 2], 'iş başına yeni örnek, hepsi bırakıldı'); + assert.deepStrictEqual(d.log.resets, [0, 1], 'gmegabuf ve reg\'ler her motordan önce sıfırlandı'); }); test('çizim sayfası: rastgelelik anahtardan — aynı iş aynı sayıları görüyor, öncülü ne olursa olsun', async () => { diff --git a/tests/milkdrop-waves.test.js b/tests/milkdrop-waves.test.js index 4d75db0..a270b65 100644 --- a/tests/milkdrop-waves.test.js +++ b/tests/milkdrop-waves.test.js @@ -169,12 +169,13 @@ test('dalga init bloğu yalnızca bir kez koşar', () => { /* Sayaç `t2` DEĞİL, kendi adı: t1..t8 artık her karede `per_init`in bıraktığı değere dönüyor (MilkDrop'un yaptığı; bkz. milkdrop-block-reset.test.js). Ad rezerve olmadığı için havuzda - kalıyor ve init ikinci kez koşsaydı 5'e geri dönerdi. */ + kalıyor ve init ikinci kez koşsaydı 5'e geri dönerdi. per_point + MilkDrop'ta ayrı makine (#580): sayacı t2 üzerinden görüyor. */ const p = preset([ 'wavecode_0_enabled=1', 'wave_0_init1=sayac = 5;', - 'wave_0_per_frame1=sayac = sayac + 1;', - 'wave_0_per_point1=x = sayac;', + 'wave_0_per_frame1=sayac = sayac + 1; t2 = sayac;', + 'wave_0_per_point1=x = t2;', ]); p.frame(INPUTS); p.waveFrame(p.waves[0]);