Skip to content

[Density Functions] mul and add with 0 neglect propagation of non-finite values #78

Description

@annhilati

Describe the bug

There is a behavior discrepancy between Vanilla Minecraft and Deepslate when using a density function that multiplies a constant 0 with a non-finite value such as NaN or Infinity

In Vanilla Minecraft, the function 0 * invert(0) + 5 results in NaN, which ultimately generates an empty world (Air) because NaN > 0 is false.
In Deepslate, the same density function evaluates to 5, incorrectly suggesting a solid world.

Technical Details & Root Cause

The discrepancy stems from how Vanilla Minecraft optimizes mul operations compared to Deepslate:

  1. In Deepslate:
    When parsing a mul density function, it is unconditionally instantiated as an Ap2 class. The compute method of Ap2 contains a short-circuit:

    case 'mul': return a === 0 ? 0 : a * this.argument2.compute(context)

    Because the first argument is 0, the short-circuit prevents the evaluation of the second argument (invert(0)), neglecting results other than 0 (even if non-finite).

  2. In Vanilla Minecraft:
    When Vanilla parses a mul (or add) operation where one of the arguments is a constant, it optimizes it into a MulOrAdd class instead of a standard TwoArgumentSimpleFunction (Ap2).
    MulOrAdd implements the PureTransformer interface, which evaluates the inner function first, before applying the constant:

    // PureTransformer implementation
    default double compute(final FunctionContext context) {
        return this.transform(this.input().compute(context)); 
    }
    // MulOrAdd implementation
    public double transform(final double input) {
        return switch (this.specificType) {
            case MUL -> input * this.argument;
            // ...
        };
    }

    Because the dynamic input is evaluated first, invert(0) results in Double.POSITIVE_INFINITY. The transformation then multiplies Infinity * 0.0, which strictly yields Double.NaN.

Expected behavior

Deepslate should mimic Vanilla Minecraft's parsing optimization. If one argument of add or mul is a constant, it should be wrapped in a MulOrAdd-like structure that evaluates the dynamic input first to ensure parity with edge-cases involving Infinity and NaN.

Steps to reproduce

Use a custom dimension or density function evaluating 0 * invert(0) + 5.

{
  "type": "minecraft:add",
  "argument1": {
    "type": "minecraft:mul",
    "argument1": 0,
    "argument2": {
      "type": "minecraft:invert",
      "argument": 0
    }
  },
  "argument2": 5
}
  • Vanilla MC: Empty world (Density is NaN).
  • Deepslate: Solid world (Density is 5).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions