Skip to content

MilkDrop fidelity: read preset files the way MilkDrop reads them - #614

Merged
CaYatur merged 3 commits into
mainfrom
milkdrop/read-580
Sep 23, 2026
Merged

CaYatur merged 3 commits into
mainfrom
milkdrop/read-580

Conversation

@CaYatur

@CaYatur CaYatur commented Sep 23, 2026

Copy link
Copy Markdown
Owner

What

With fidelity on, preset files are now read the way MilkDrop 2 reads them. The rules come from Nullsoft's code (jecassis/foo_vis_milk2 5b44cea): _GetLineByName, the GetFast readers, ReadCode and the Import functions, which are the same in the D3D9 code. With fidelity off, the old parser stays.

MilkDrop builds an index of the file's lines first. A line's name runs up to the first =, space or line break. It then looks keys up in its own order, trying the line after the previous read before scanning from the top. As a result:

  • Case: keys are case-sensitive, so PSVERSION_comp and fwarpAnimSpeed are never read.
  • Line shape:
    • an indented line gets an empty name and is never read;
    • key value is read;
    • key = value is not read.
  • Duplicates: a key written twice takes the next line or the first occurrence, not the last one.
  • Numbers:
    • integer keys are read with %d, so textured=0.05 is off (it was on for us);
    • float keys take the leading number, so .975; reads as 0.975.
  • Code:
    • numbered code ends at the first missing number;
    • equation lines lose // and \\ comments and are glued with nothing between them.

Which presets change. Readings differ in something the engine uses (readingsDiffer) for 32 corpus presets and none of ours. The causes are duplicated keys, fractions in integer keys, key case, a per-frame name written in the header, shapes past MilkDrop 2's four, a \\ comment and a gap in the numbering.

Where the reader is used.

  • The Preset records which reader built it (readAcc).
  • Every Preset built in src states its reading rule explicitly, and a test enforces this.
  • Turning fidelity over rebuilds the running preset only when the two readings differ.
  • The stage choice reads the file the same way.

Mash-ups read their donors the same way. A preset gives a part only if MilkDrop reads that part from it, and version lines come from the donor's MilkDrop reading. Because the same recipe can now produce different version lines, the rule version goes to 3.

Left out on purpose (listed in the ROADMAP)

  • Byte rules. MilkDrop reads bytes, while the engine gets decoded text. Its two byte rules (0xFF ends the file; a value over 251 characters splits into a second index line) can't be kept exactly, and neither affects a corpus file.
  • Float storage. Values stay double, where MilkDrop stores floats; the difference is below 1e-7.
  • Compile failures. A block that fails to compile is still recovered statement by statement. Our parser and MilkDrop's disagree on what counts as an error; for example, three presets call _aboeq, one of MilkDrop's internal functions that our compiler lacks. Dropping whole blocks would therefore drop some that MilkDrop runs.

Measured

  • Against main (last 10 of 60 frames):
    • control presets are identical with fidelity on and off;
    • of the 32 presets that read differently, 15 change with fidelity on (12 by more than 1%, up to 32%), and all 32 are identical with it off;
    • the 900-preset sample keeps its classes, preset for preset.
  • Mash-ups on the whole corpus:
    • the part test agrees with the engine for every preset and part (0.04 ms per test);
    • a mash-up of one preset reads as that preset for all 10,332;
    • 5,000 random mash-ups keep every part and stage of their donors.

Tests

  • 10 new tests; 23 of 23 mutations are caught.
  • npm test: 2197 of 2197 pass, and each commit passes on its own. The changed files also pass under Linux emulation.
  • npm run smoke: PASS, with the userData checksums unchanged.

Refs #580
Refs #579
Refs #560

With fidelity on, a preset file now goes through a reader that follows
MilkDrop's own (parseMilkMd2; state.cpp _GetLineByName, the GetFast
readers, ReadCode and the Import functions, the same in the D3D9
code). With fidelity off the old parser stays.

MilkDrop indexes the file line by line - a line's name runs to the
first '=', space or line break - and looks the keys up in its own
order, trying the line after the previous read before scanning from
the top. As a result:
- Keys are case-sensitive.
- An indented line has an empty name and is never read.
- `key value` is read, and `key = value` is not.
- A key written twice takes the next line or the first occurrence,
  not the last.
- Integer keys are %d, so textured=0.05 is off where we had it on.
- Float keys take the leading number, so `.975;` is 0.975.
- Numbered code ends at the first missing number.
- Equation lines lose `//` and `\` comments and are glued with
  nothing between them.

Readings differ in something the engine uses (readingsDiffer) for 32
corpus presets and none of ours. The Preset records which reader built
it (readAcc), and every Preset built in src names its rule. Turning
fidelity over rebuilds the running preset only when the two readings
differ, and the stage choice reads the file the same way.

Not done on purpose:
- The two byte rules (0xFF ends the file, values over 251 chars split
  in the index) cannot be kept exactly on decoded text, and neither
  touches a corpus file.
- Values stay double.
- Blocks that fail to compile are still recovered statement by
  statement, since our parser and MilkDrop's disagree on what an
  error is (_aboeq).

Measured against main, last 10 of 60 frames:
- control presets are identical with fidelity on and off;
- of the 32 presets that read differently, 15 change with fidelity on
  (12 by more than 1%), and all are identical with it off;
- the 900-preset sample keeps its classes.

9 new tests. The docs' test count follows.

Refs #580, #560
A preset gives a mash-up a part only if MilkDrop reads that part from
it: the part test now reads the file with the engine's MilkDrop reader
(parseMilkMd2) and a shader counts only if the engine's stage rule
draws it. The version lines written for a donated shader are the ones
MilkDrop reads from the donor (readVersions): a donor writing
PSVERSION_comp=3 gives version 2, and in a doubled PSVERSION_WARP the
line MilkDrop finds. The same recipe can give different version lines
than before, so the rule version goes to 3 and older saves keep their
files. Without the engine loaded the module falls back to its old text
tests.

On the whole corpus the part test agrees with the engine for every
preset and part (0.04 ms a test, was 0.014), a mash-up of one preset
reads as that preset for all 10,332, and 5,000 random mash-ups keep
every part and stage of their donors.

Refs #579, #580
README (both languages): the new reading with fidelity on. ROADMAP
#580: the reader, the mash-ups following it, the measurements, and
what was left out on purpose (byte rules, float values, whole-block
compile failure) with the new open items (_aboeq and the rest of
MilkDrop's internal functions, uv orientation). The #579 entry notes
the rule version 3 and the part test's new cost.

Refs #580, #579
@CaYatur
CaYatur merged commit 7050cc9 into main Sep 23, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant