fix(milkdrop): equations run by MilkDrop 2's compiler rules - #619
Merged
Merged
Conversation
…them With fidelity on, truth and equality tests use the closeness factor of MilkDrop 2's expression compiler (NSEEL_CLOSEFACTOR, 0.00001, in Nullsoft's ns-eel2): - if, !, &&, ||, while and bnot treat |x| >= 0.00001 as true; - the band and bor functions need |x| > 0.00001; - ==, != and equal compare |a - b| with it; - <, >, <=, >=, above and below stay exact; - NaN counts as false, and as equal, like the x87 compare. The compiler's operator functions compile too (_aboeq, _if, _and, _or, _not, _equal, _noteq, _below, _above, _beleq, _mod, _set, _addop and the other assignment forms, _mem, _gmem). _and and _or are && and ||, which skip the right side; band and bor evaluate both. Three corpus presets call _aboeq. Compiled code reads a mode object that all blocks of a preset share, so flipping fidelity switches the tests in the same frame. Internal names are decided when the code is parsed, so readingsDiffer reports presets that use them and the visualizer rebuilds those on a flip. Sprites follow the same switch, and the self-test compiles in the default mode. With fidelity off nothing changes. Refs #580, #560
With fidelity on, `%`, `%=` and _mod work the way MilkDrop 2's compiler does (nseel_asm_mod). Both sides become the integer part of their absolute value, and the division is unsigned, so the result is never negative: -7 % 3 is 1 in MilkDrop and was -1 here. MilkDrop runs its code with the FPU set to truncate, and a value that does not fit in 32 bits becomes the x87 integer indefinite, -2^31, where JavaScript's |0 wrapped it modulo 2^32. A divisor of 0 still gives 0. With fidelity off the old signed remainder stays. Refs #580, #560
With fidelity on, `&` and `|` (and _andop, _orop) convert both sides to 64-bit integers the way MilkDrop 2's compiler does (nseel_asm_and/or, fistp qword under truncation), where the engine used JavaScript's 32-bit |0. Values that fit in 32 bits give the same result and keep the fast path; beyond that the 64-bit value is used, and one that does not fit in 64 bits becomes -2^63. No corpus preset reaches those values: the 319 that use the operators render the same. Refs #580, #560
With fidelity on, memory access follows MilkDrop 2's compiler (_asm_megabuf, nseel-ram.c): - an index is trunc(x + 0.00001), so a computed 2.9999999 finds cell 3 and -1 finds cell 0; - megabuf holds 8,388,608 cells (128 blocks of 65,536; it was 1,048,576), and cells outside read 0 and drop writes; - gmegabuf is a single array of 2^20 cells that indices wrap around, negative ones included, since MilkDrop gives its compilers no separate global block. Memory is now kept in 4096-cell blocks allocated on first write, so the larger limit costs nothing until a preset uses it. With fidelity off the old index and limit stay. Refs #580, #560
With fidelity on, rand(n) returns a real number between 0 and max(1, floor(n)), as MilkDrop 2's compiler does (nseel_int_rand: genrand_int32() * (1.0 / 0xFFFFFFFF) * x). The integer branch belongs to NSEEL_EEL1_COMPAT_MODE, which neither MilkDrop codebase turns on; the int(rand(4)) that is common in the corpus is how presets ask for an integer. The engine returned an integer below n, so rand(1) was always 0. The generator stays our seeded one, so exports and thumbnails remain reproducible; only the range and type change. 5,361 corpus presets call rand. With fidelity off it still returns an integer. Refs #580, #560
ROADMAP (#580) describes the expression rules, the measurements against the previous step and the 900-preset sample, and rewrites the list of what is not done yet: variable scope, division by zero and the loop cap are next; newer syntax and unused functions are left out with reasons. The since-tag count follows the test total. Both READMEs gain a bullet. Refs #580, #560
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
With fidelity on, preset equations now run by the rules of MilkDrop 2's expression compiler (Nullsoft's ns-eel2, as BeatDrop 53d83ee carries it). The D3D11 fork evaluates expressions with projectM's re-implementation by default, which differs in places (
%keeps the sign, the memory index adds 0.0001), so the reference here is Nullsoft's compiler. With fidelity off nothing changes.What changes
if,!,&&,||,while,bnotcount |x| ≥ 0.00001 as true;band/borneed > 0.00001;==,!=,equalcompare with that tolerance;<,>,above,belowstay exact; NaN is false and equal. The compiler's operator names compile (_aboeq,_if,_and,_or,_equal,_set,_addop…,_mem,_gmem);_and/_orshort-circuit.%truncates both sides to unsigned integers (-7 % 3= 1,5 % 0.5= 0); values outside the signed 32-bit range become 2^31.&and|on 64-bit integers.megabufindex is floor(x + 0.00001), 8,388,608 cells, 0 outside;gmegabufhas 2^20 cells and wraps.rand(n)returns a real number in [0, n].All blocks of a preset and its sprites read one mode object, so flipping fidelity switches them in the same frame; presets that use the internal names are rebuilt on a flip (
readingsDiffer).Measured
Against the previous step, last 10 of 60 frames, 120 presets per group:
equal(sin(ang), 0), sin(π) = 1.2e-16). The three_aboeqpresets change by up to 29% — their code did not run before.%: 118/120 identical; one with negative samples (6%), one hashing past 2^31 (48%).&/|: 120/120. Memory: 119/120 (one computedgmegabufindex, 3%).rand(5,361 corpus presets): 92/120 identical, 14 change > 1%, max 18%, median of changed 1.07%. Thumbnails and exports of rand presets change as expected.Tests
25 new in
tests/milkdrop-md2-eel.test.js; 2227/2227 pass. 71/71 mutations caught across the five commits. The self-test's MilkDrop compile keeps its expected values (5,25,5,7,1,8,0).npm run smokefails the context-loss "new-canvas" phase on this machine today — and fails the same way (or worse) onmain(47935d2) and on 7050cc9, where it passed before; frames are throttled (one run: 20 frames in 2 s). It is environmental, not this change. userData checksums unchanged across all runs.Not done (documented in ROADMAP)
Variable scope per VM (next), division by zero (inf vs 0), the loop cap (six presets reach the engine's budget), newer syntax and unused functions (
?:,&=,invsqrt,memcpy…, no corpus use), the old grammar's%mapping (not followed; the corpus writes%as a remainder).Refs #580, #560