Skip to content

Feat: Density Functions#15

Merged
ItsMeBrianD merged 6 commits intomainfrom
feat/worldgen/density-functions
Sep 11, 2025
Merged

Feat: Density Functions#15
ItsMeBrianD merged 6 commits intomainfrom
feat/worldgen/density-functions

Conversation

@ItsMeBrianD
Copy link
Copy Markdown
Contributor

@ItsMeBrianD ItsMeBrianD commented Sep 7, 2025

Implements #2

Summary by CodeRabbit

  • New Features

    • Added Density Function blocks with many new builders (noise, clamp, range choice, shifted/old noise, splines, single/dual-input ops, references) and JSON export for worldgen.
    • Surface rules now support sequences, conditionals, and bandlands.
    • Expanded DSL: more permissive numeric/literal forms, richer identifiers, string tokens, and comment/whitespace handling.
  • Refactor

    • Unified builder tokens and modularized noise/density grammars; namespace keyword capitalized.
  • Tests

    • Added sample DensityFn snippet (spline).
  • Chores

    • Editor/LSP support added; vendored dependencies updated.

@ItsMeBrianD ItsMeBrianD added this to the Dimension Generation milestone Sep 7, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Sep 7, 2025

Walkthrough

Adds a new DensityFunctions ANTLR grammar and integrates it into the language and parser; expands Core_Lang with many builder rules and lexical tokens; refactors Noise and Surface grammars; implements numerous Go constructs for worldgen density functions, JSON export, traversal wiring, and updates vendor dependencies and tooling config.

Changes

Cohort / File(s) Summary
Grammar: core & lexical
grammar/Core_Lang.g4
Expanded core grammar: many new builder_* productions, resourceReference, number (Int
Grammar: density functions
grammar/DensityFunctions.g4, grammar/MinecraftMetascript.g4
New DensityFunctions grammar with many densityFn variants and builders; MinecraftMetascript.g4 imports it, adds densityFnBlock, and capitalizes Namespace.
Grammar: noise & surface
grammar/Noise.g4, grammar/Surface.g4
Noise refactored to noiseDefinition; builder amplitude moved to shared builder_Amplitudes. Surface replaces sharedBuilder_* with builder_*, adds sequence/If/Bandlands rules and reorganizes Top/Bottom builders.
Traversal & parser core
lang/traversal/Parser.go, lang/traversal/Construct.go, lang/traversal/Symbol.go, lang/File.go, lang/Project.go
Parser.Parse now returns ScriptContext and processes Namespace contents; Construct return refactor; NewSymbol adds _type param; File stores Script; Project adds side-effect import for density_functions.
Builder chain & shared builders
lang/builder_chain/BuilderChain.go, lang/builder_chain/SharedBuilder.go
Small debug print added; SharedBuilder_Add signature updated to new builder context type.
Worldgen density functions (new)
lang/constructs/worldgen/density_functions/*.go
Many new density-fn implementations and registrations (Constant, Math, SingleInput, DualInput, Noise, OldBlendedNoise, WeirdScaledSampler, ShiftedNoise, RangeChoice, Clamp, YClampedGradient, Spline, etc.), JSON marshaling and export wiring.
Noise traversal updates
lang/constructs/worldgen/noise/Noise.go
Noise building now uses NoiseDefinitionContext and builder_Amplitudes; added NoiseBlock traversal and amplitude parsing/diagnostics.
Surface rules & conditions
lang/constructs/worldgen/surface_rules/*.go
Updated builder ctx types to Builder_*, added SurfaceBlock traversal hook, and ReferenceRule marshaling fallback to reference JSON when unresolved.
Misc traversal tooling
lang/traversal/Parser.go changes
ExitEveryRule → ExitNamespace handler; constructs invoked per Namespace content block.
Tests & config
test.mms, .helix/languages.toml, go.mod
Adds test snippet, Helix MMS LSP config, and new go.mod dependency github.com/tliron/glsp v0.2.2 plus indirect deps.
Vendors: termenv & deps
vendor/github.com/muesli/termenv/*, vendor/.../go-colorful, vendor/.../runewidth, vendor/.../go-isatty, vendor/.../strcase, vendor/.../osc52
Adds termenv terminal API, color/profile, OSC52 copy, runewidth/isatty/strcase utilities, many helper files and licenses.
Vendors: websocket & goid & others
vendor/github.com/gorilla/websocket/*, vendor/github.com/petermattis/goid/*, various vendor additions
Adds full Gorilla WebSocket implementation and utilities, goid goroutine-id support, assorted vendor metadata and helper files.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Parser
  participant ANTLR as Grammar
  participant Registry as ConstructRegistry
  participant Scope
  participant DF as DensityFn Constructors
  participant Export as Exporter

  User->>Parser: Parse(file)
  Parser->>ANTLR: build AST (ScriptContext)
  Parser-->>User: return ScriptContext
  Parser->>Parser: ExitNamespace
  loop for each contentBlock
    Parser->>Registry: Construct(childPRC, namespace, scope)
    alt densityFnBlock
      Registry->>DF: build declarations & values
      DF->>Scope: diagnose errors
      DF-->>Registry: constructed density function
    else other blocks
      Registry-->>Registry: existing constructors
    end
  end
  DF->>Export: exportDensityFunction(symbol, rootDir)
  Export-->>DF: write JSON file
Loading
sequenceDiagram
  autonumber
  participant DF as DensityFn_Noise
  participant NoiseDef as NoiseDefinition / ResourceRef
  participant Inline as ExtractInlinedNoise
  participant Chain as BuilderChain

  DF->>NoiseDef: parse noise (inline or resource)
  alt inline
    DF->>Inline: register inline noise symbol
    Inline-->>DF: traversal.Reference
  else resource
    DF-->>DF: use resource traversal.Reference
  end
  DF->>Chain: apply builders (XZScale, YScale, ShiftX/Y/Z, ...)
  Chain-->>DF: populated fields
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Possibly related PRs

  • Feat: Implement Noise #12 — modifies Noise grammar and noise traversal/constructs; likely closely related to the Noise grammar refactor here.

Suggested labels

worldgen

Pre-merge checks (1 passed, 1 warning, 1 inconclusive)

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.13% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ❓ Inconclusive The description “Implements #2” only references an issue number without summarizing any of the actual changes in the grammar, code, or functionality, making it too vague to convey meaningful information about the pull request. Please expand the PR description to include a concise overview of the changes, such as the addition of density function grammar rules, the new Go constructs for density functions, and how these integrate into the existing world generation framework, so reviewers can understand the scope without referring back to issue #2.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title “Feat: Density Functions” succinctly and accurately indicates that this pull request introduces density functions support, which is the primary purpose of the extensive grammar and Go implementation changes; it is clear, concise, and directly related to the main change without unnecessary detail.

Poem

I nibble rules and hop through code,
Builders sprout where noodles flowed.
Spline and Noise and Clamp in tune,
JSON carrots for the moon.
A rabbit cheers — export complete soon! 🐇✨

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/worldgen/density-functions

Comment @coderabbitai help to get the list of available commands and usage tips.

@ItsMeBrianD
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Sep 7, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Nitpick comments (26)
wasm/mms.d.ts (1)

36-36: Prefer a generic value over object to avoid breaking changes and improve typing

Narrowing unknownobject can break callers that stored primitives; a generic preserves flexibility with strong typing.

Apply:

-export type MmsSymbol = {
+export type MmsSymbol<T = unknown> = {
   nameLocation: MmsTextLocation,
   contentLocation: MmsTextLocation,
-    value: object,
+    value: T,
   ref: MmsReference
   type: string
 }

Optionally, if this is always JSON-like, use T = Record<string, unknown> | unknown[].

grammar/Core_Lang.g4 (1)

7-7: LGTM; supports .5/- .5. Consider exponent support as a follow-up

The relaxed Float rule is good. If needed later, extend to allow scientific notation (e.g., 1e-3, .5e2).

test.mms (1)

1-6: Augment sample to exercise new float forms and non-default params

Add a case with decimals lacking a leading zero and some non-default args to validate the new Float rule and JSON output.

Example:

namespace xyz {
  DensityFn {
    old = OldBlendedNoise(xzScale = .25, yScale = -.5)
    c = Constant(-.125)
  }
}
lang/traversal/Construct.go (1)

49-50: No-op refactor; OK to keep for breakpointing/instrumentation

Assigning to val before return is functionally identical and can aid debugging.

grammar/MinecraftMetascript.g4 (1)

3-3: Tiny formatting nit in import list

Add a space after the comma for consistency/readability.

-import Surface,DensityFunctions, Noise, Core_Lang;
+import Surface, DensityFunctions, Noise, Core_Lang;
lang/constructs/worldgen/surface_rules/SurfaceConditions.go (1)

5-5: Drop unused fmt import after switching to diagnostics

Once stdout logging is removed, fmt can be removed from imports.

 import (
   "encoding/json"
-  "fmt"
   "reflect"
lang/traversal/Symbol.go (1)

29-36: NewSymbol invocations updated
All existing usages (e.g. NoiseFn.go) now include the required _type argument. Optional: introduce a NewTypedSymbol wrapper to centralize type‐setting logic.

lang/traversal/Parser.go (3)

31-36: Remove or gate debug prints

Unconditional stdout logs (“Parsing…”, “Done parsing”) are noisy in libraries/CLIs. Prefer a logger behind a verbosity flag or remove.

 func (p *Parser) Parse() (err error) {
-  fmt.Println("Parsing...")
   if p.parser.Script() == nil {
     return errors.New("failed to parse")
   }
-  fmt.Println("Done parsing")
   return nil
 }

39-46: Add type/length guards to prevent a hard cast panic

Casting inner to antlr.ParserRuleContext without checks can panic if grammar changes or listeners introduce non-rule children. Cheap guard improves resilience.

 func (p *Parser) ExitNamespace(ctx *grammar.NamespaceContext) {
   for _, contentBlockCtx := range ctx.AllContentBlocks() {
     if contentBlock, ok := contentBlockCtx.(*grammar.ContentBlocksContext); ok {
-      inner := contentBlock.GetChild(0)
-      ConstructRegistry.Construct(inner.(antlr.ParserRuleContext), p.namespace, p.scope)
+      if contentBlock.GetChildCount() == 0 {
+        continue
+      }
+      if inner, ok := contentBlock.GetChild(0).(antlr.ParserRuleContext); ok {
+        ConstructRegistry.Construct(inner, p.namespace, p.scope)
+      }
     }
   }
 }

5-5: Drop fmt import if prints are removed

Keep imports minimal.

 import (
   "errors"
-  "fmt"
 
   "github.com/minecraftmetascript/mms/lang/grammar"
lang/constructs/worldgen/density_functions/NoiseFn.go (4)

25-27: Fix mislabeled builder label for YScale.

The label string is "XzScale" while setting YScale; fix for clarity and consistent diagnostics.

-					func(ctx *grammar.SharedBuilder_YScaleContext, target *NoiseDensityFn, scope *traversal.Scope, namespace string) {
-						builder_chain.Builder_GetFloat(ctx, func(v float64) { target.YScale = v }, scope, "XzScale")
-					},
+					func(ctx *grammar.SharedBuilder_YScaleContext, target *NoiseDensityFn, scope *traversal.Scope, namespace string) {
+						builder_chain.Builder_GetFloat(ctx, func(v float64) { target.YScale = v }, scope, "YScale")
+					},

52-55: Avoid panicking on symbol registration failures.

Panic during construction will take down the whole traversal; prefer recording a diagnostic and continuing or returning a sentinel construct.

If you point me to the scope’s diagnostic API, I can wire a non-panicking path.


67-73: Avoid potential panic on type assertion for builder child.

Be defensive to avoid a crash if the child isn’t a ParserRuleContext.

-				builder_chain.Invoke(noiseFnBuilder, child.(antlr.ParserRuleContext), out, scope, currentNamespace)
+				if ctx, ok := child.(antlr.ParserRuleContext); ok {
+					builder_chain.Invoke(noiseFnBuilder, ctx, out, scope, currentNamespace)
+				}

41-43: Consider de-duping inline reference ids across files.

The id uses only line/column with a fixed namespace ("mms_inline"). If scopes span files, two files with the same coords can collide. Add a file-scoped salt (e.g., a hash of CurrentFile).

lang/constructs/worldgen/density_functions/ConstantFn.go (1)

33-39: Optional: use compact JSON for artifacts.

If these files are for machine consumption, consider json.Marshal to reduce size; keep MarshalIndent if human-readability is preferred.

lang/constructs/worldgen/density_functions/FunctionRef.go (2)

53-55: Nit: correct error text.

-		return nil, errors.New("reference is nil in ReferenceRule")
+		return nil, errors.New("reference is nil in ReferenceFunction")

60-62: Nit: prefer fmt.Errorf over errors.New(fmt.Sprintf(...)).

-		return nil, errors.New(
-			fmt.Sprintf("unable to resolve reference %s", c.Ref.String()),
-		)
+		return nil, fmt.Errorf("unable to resolve reference %s", c.Ref.String())
lang/constructs/worldgen/density_functions/OldNoise.go (2)

25-26: Copy-paste label mistakes in Builder_GetFloat.

Labels for diagnostics are all "XzScale". Align them to the actual field.

Apply:

-						builder_chain.Builder_GetFloat(ctx, func(v float64) { target.YScale = v }, scope, "XzScale")
+						builder_chain.Builder_GetFloat(ctx, func(v float64) { target.YScale = v }, scope, "YScale")
@@
-						builder_chain.Builder_GetFloat(ctx, func(v float64) { target.XzFactor = v }, scope, "XzScale")
+						builder_chain.Builder_GetFloat(ctx, func(v float64) { target.XzFactor = v }, scope, "XzFactor")
@@
-						builder_chain.Builder_GetFloat(ctx, func(v float64) { target.YFactor = v }, scope, "XzScale")
+						builder_chain.Builder_GetFloat(ctx, func(v float64) { target.YFactor = v }, scope, "YFactor")
@@
-						builder_chain.Builder_GetFloat(ctx, func(v float64) { target.SmearMultiplier = v }, scope, "XzScale")
+						builder_chain.Builder_GetFloat(ctx, func(v float64) { target.SmearMultiplier = v }, scope, "Smear")

Also applies to: 30-31, 35-36, 40-41


56-62: Guard the type assertion on child to avoid panic.

GetChild(0) isn’t guaranteed to be a ParserRuleContext.

Apply:

-				builder_chain.Invoke(noiseFnBuilder, child.(antlr.ParserRuleContext), out, scope, currentNamespace)
+				if pctx, ok := child.(antlr.ParserRuleContext); ok {
+					builder_chain.Invoke(noiseFnBuilder, pctx, out, scope, currentNamespace)
+				} else {
+					scope.DiagnoseSemanticError("Invalid OldBlendedNoise builder child", ctx)
+				}
lang/constructs/worldgen/density_functions/SingleInput.go (1)

37-72: Consider a lookup map for kind resolution.

The long switch is fine but a map[string]SingleInputDensityFnKind would simplify maintenance.

lang/constructs/worldgen/density_functions/DualInput.go (2)

31-43: Kind resolution OK; consider shared helper with SingleInput.

Both files duplicate token→kind mapping; a shared helper would reduce drift.


45-60: Minor consistency nit: explicit cast like SingleInput.

Optional: cast DensityFn(i) to antlr.ParserRuleContext for symmetry with SingleInput.

Apply:

-				val := traversal.ConstructRegistry.Construct(firstArg, currentNamespace, scope)
+				val := traversal.ConstructRegistry.Construct(firstArg.(antlr.ParserRuleContext), currentNamespace, scope)
lang/constructs/worldgen/density_functions/DensityFn.go (1)

115-127: Consider adding error handling for MkDir and MkFile operations

The directory and file creation operations don't check for errors. While the FileTreeLike interface might handle errors internally, it's good practice to verify that these operations succeed.

Additionally, consider extracting the hardcoded path structure to constants for better maintainability:

+const (
+	worldgenDir = "worldgen"
+	densityFunctionDir = "density_function"
+)
+
 func exportDensityFunction(symbol traversal.Symbol, rootDir *lib.FileTreeLike, condition json.Marshaler) error {
 	data, err := json.MarshalIndent(condition, "", "  ")
 	if err != nil {
 		return err
 	}
 	rootDir.
 		MkDir(symbol.GetReference().GetNamespace(), nil).
-		MkDir("worldgen", nil).
-		MkDir("density_function", nil).
+		MkDir(worldgenDir, nil).
+		MkDir(densityFunctionDir, nil).
 		MkFile(symbol.GetReference().GetName()+".json", string(data), nil)
 
 	return nil
 }
grammar/DensityFunctions.g4 (3)

52-53: Inconsistent casing in cache kind rule name

The rule DensityFn_CacheKind uses PascalCase while other similar rules use camelCase. Consider maintaining consistent naming conventions.

-DensityFn_CacheKind: '2d' | 'Once' | 'All';
-densityFn_Cache: 'Cache' NL* '(' NL* DensityFn_CacheKind NL* ',' NL* densityFn NL* ')';
+densityFn_CacheKind: '2d' | 'Once' | 'All';
+densityFn_Cache: 'Cache' NL* '(' NL* densityFn_CacheKind NL* ',' NL* densityFn NL* ')';

55-58: Leading OR operator in choice rule

The densityFn_DualInput rule has an unnecessary leading | operator which could be confusing.

 densityFn_DualInput: (
-    | 'Min'
+      'Min'
     | 'Max'
 ) NL* '(' NL* densityFn ',' NL* densityFn NL* ')';

77-84: Consider implementing TODO features

The comment block lists several unimplemented density function features. These appear to be important for complete worldgen support.

Would you like me to help implement any of these missing density function types or create issues to track them? The listed functions include:

  • weird_scaled_sampler
  • shifted_noise
  • range_choice
  • clamp
  • spline
  • y_clamped_gradient
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f944ac and 75cc770.

⛔ Files ignored due to path filters (8)
  • lang/grammar/MinecraftMetascript.interp is excluded by !lang/grammar/*
  • lang/grammar/MinecraftMetascript.tokens is excluded by !lang/grammar/*
  • lang/grammar/MinecraftMetascriptLexer.interp is excluded by !lang/grammar/*
  • lang/grammar/MinecraftMetascriptLexer.tokens is excluded by !lang/grammar/*
  • lang/grammar/minecraftmetascript_base_listener.go is excluded by !lang/grammar/*
  • lang/grammar/minecraftmetascript_lexer.go is excluded by !lang/grammar/*
  • lang/grammar/minecraftmetascript_listener.go is excluded by !lang/grammar/*
  • lang/grammar/minecraftmetascript_parser.go is excluded by !lang/grammar/*
📒 Files selected for processing (18)
  • grammar/Core_Lang.g4 (1 hunks)
  • grammar/DensityFunctions.g4 (1 hunks)
  • grammar/MinecraftMetascript.g4 (1 hunks)
  • lang/Project.go (1 hunks)
  • lang/constructs/worldgen/density_functions/ConstantFn.go (1 hunks)
  • lang/constructs/worldgen/density_functions/DensityFn.go (1 hunks)
  • lang/constructs/worldgen/density_functions/DualInput.go (1 hunks)
  • lang/constructs/worldgen/density_functions/FunctionRef.go (1 hunks)
  • lang/constructs/worldgen/density_functions/NoiseFn.go (1 hunks)
  • lang/constructs/worldgen/density_functions/OldNoise.go (1 hunks)
  • lang/constructs/worldgen/density_functions/SingleInput.go (1 hunks)
  • lang/constructs/worldgen/noise/Noise.go (1 hunks)
  • lang/constructs/worldgen/surface_rules/SurfaceConditions.go (2 hunks)
  • lang/traversal/Construct.go (1 hunks)
  • lang/traversal/Parser.go (2 hunks)
  • lang/traversal/Symbol.go (1 hunks)
  • test.mms (1 hunks)
  • wasm/mms.d.ts (1 hunks)
🔇 Additional comments (8)
lang/Project.go (1)

11-11: Side-effect import for density functions looks right

Init-time registration via blank import is appropriate. Verify there’s no init-order coupling with noise/surface_rules.

grammar/MinecraftMetascript.g4 (1)

10-10: Density functions are now first-class content blocks — looks good

The new alternative integrates cleanly with existing surface/noise blocks.

lang/constructs/worldgen/noise/Noise.go (1)

16-27: LGTM: recursive descent for NoiseBlock

Clean, minimal traversal that delegates to the registry for child rules. Matches the SurfaceBlock pattern.

lang/constructs/worldgen/density_functions/OldNoise.go (1)

68-74: Struct shape and JSON keys look correct.

Fields and JSON tags match the expected xz_scale/y_scale/xz_factor/y_factor/smear_scale_multiplier.

Also applies to: 79-84

lang/constructs/worldgen/density_functions/SingleInput.go (1)

96-108: LGTM on JSON and export integration.

Emits {type, argument} and delegates export correctly.

lang/constructs/worldgen/density_functions/DualInput.go (1)

80-94: LGTM on JSON structure.

Uses {type, argument1, argument2} as expected.

lang/constructs/worldgen/density_functions/DensityFn.go (1)

26-36: LGTM! Clean JSON marshaling implementation

The custom MarshalJSON implementation properly formats the output with appropriate field names and indentation.

grammar/DensityFunctions.g4 (1)

14-23: LGTM! Well-structured density function grammar

The grammar structure is clean and modular, with appropriate use of optional math operations and clear separation of different density function types.

Comment thread grammar/DensityFunctions.g4 Outdated
Comment on lines +3 to +12
import (
"encoding/json"
"reflect"

"github.com/antlr4-go/antlr/v4"
"github.com/minecraftmetascript/mms/lang/builder_chain"
"github.com/minecraftmetascript/mms/lang/grammar"
"github.com/minecraftmetascript/mms/lang/traversal"
"github.com/minecraftmetascript/mms/lib"
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Reject NaN/Inf constants before marshaling.

encoding/json can’t marshal NaN/Inf; guard to produce a clear error.

 import (
 	"encoding/json"
+	"fmt"
+	"math"
 	"reflect"
@@
 func (c ConstantDensityFn) MarshalJSON() ([]byte, error) {
+	if math.IsNaN(c.Value) || math.IsInf(c.Value, 0) {
+		return nil, fmt.Errorf("invalid constant value: %v", c.Value)
+	}
 	return json.MarshalIndent(struct {
 		Type     string  `json:"type"`
 		Argument float64 `json:"argument"`

Also applies to: 32-40

🤖 Prompt for AI Agents
In lang/constructs/worldgen/density_functions/ConstantFn.go around lines 3-12
and 32-40, the code may attempt to json.Marshal NaN/Inf which encoding/json
cannot marshal; add a guard that checks the constant float64 using math.IsNaN
and math.IsInf (import math) before marshaling and return or propagate a clear
error (e.g. "constant value is NaN/Inf") instead of attempting to marshal; apply
the same validation in both places where the constant is serialized so callers
receive a meaningful error rather than a panic from json.Marshal.

Comment thread lang/constructs/worldgen/density_functions/DensityFn.go
Comment thread lang/constructs/worldgen/density_functions/DensityFn.go Outdated
Comment on lines +92 to +102
var kind MathDensityFnKind
if operation := math.GetChild(0); operation != nil {
switch operation.(antlr.TerminalNode).GetText() {
case "*":
kind = MathDensityFn_Mul
case "+":
kind = MathDensityFn_Add
default:
scope.DiagnoseSemanticError("Unknown operator", ctx)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add nil check for GetChild(0) result

The code casts operation to antlr.TerminalNode without checking if the cast is successful. This could panic if the child is not a terminal node.

 				var kind MathDensityFnKind
 				if operation := math.GetChild(0); operation != nil {
-					switch operation.(antlr.TerminalNode).GetText() {
+					if termNode, ok := operation.(antlr.TerminalNode); ok {
+						switch termNode.GetText() {
 						case "*":
 							kind = MathDensityFn_Mul
 						case "+":
 							kind = MathDensityFn_Add
 						default:
 							scope.DiagnoseSemanticError("Unknown operator", ctx)
 						}
+					} else {
+						scope.DiagnoseSemanticError("Invalid operator node", ctx)
+					}
 				}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var kind MathDensityFnKind
if operation := math.GetChild(0); operation != nil {
switch operation.(antlr.TerminalNode).GetText() {
case "*":
kind = MathDensityFn_Mul
case "+":
kind = MathDensityFn_Add
default:
scope.DiagnoseSemanticError("Unknown operator", ctx)
}
}
var kind MathDensityFnKind
if operation := math.GetChild(0); operation != nil {
if termNode, ok := operation.(antlr.TerminalNode); ok {
switch termNode.GetText() {
case "*":
kind = MathDensityFn_Mul
case "+":
kind = MathDensityFn_Add
default:
scope.DiagnoseSemanticError("Unknown operator", ctx)
}
} else {
scope.DiagnoseSemanticError("Invalid operator node", ctx)
}
}
🤖 Prompt for AI Agents
In lang/constructs/worldgen/density_functions/DensityFn.go around lines 92 to
102, the code assumes math.GetChild(0) is an antlr.TerminalNode and casts
without checking which can panic; change the logic to perform a safe type
assertion (e.g., node, ok := operation.(antlr.TerminalNode)) and only call
GetText() when ok is true, otherwise call scope.DiagnoseSemanticError("Expected
terminal node for operator", ctx) (or set a sensible default/return) so
non-terminal children are handled safely.

Comment on lines +31 to +39
out.Ref.SetResolver(func() error {
if next, ok := scope.Get(*r); ok {
val := next.GetValue().(traversal.Construct)

out.Value = &val
}
return nil
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make resolver robust (missing symbol/type mismatch).

The current type assertion can panic, and missing symbols return nil error. Return precise errors instead.

-						out.Ref.SetResolver(func() error {
-							if next, ok := scope.Get(*r); ok {
-								val := next.GetValue().(traversal.Construct)
-
-								out.Value = &val
-							}
-							return nil
-						})
+						out.Ref.SetResolver(func() error {
+							next, ok := scope.Get(*r)
+							if !ok {
+								return fmt.Errorf("reference not found: %s", r.String())
+							}
+							val, ok := next.GetValue().(traversal.Construct)
+							if !ok {
+								return fmt.Errorf("symbol %s is not a density function", r.String())
+							}
+							out.Value = &val
+							return nil
+						})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
out.Ref.SetResolver(func() error {
if next, ok := scope.Get(*r); ok {
val := next.GetValue().(traversal.Construct)
out.Value = &val
}
return nil
})
}
out.Ref.SetResolver(func() error {
next, ok := scope.Get(*r)
if !ok {
return fmt.Errorf("reference not found: %s", r.String())
}
val, ok := next.GetValue().(traversal.Construct)
if !ok {
return fmt.Errorf("symbol %s is not a density function", r.String())
}
out.Value = &val
return nil
})
🤖 Prompt for AI Agents
In lang/constructs/worldgen/density_functions/FunctionRef.go around lines 31-39,
the resolver currently uses a direct type assertion that can panic and always
returns nil on missing symbols; change it to check scope.Get(*r) and then
perform a safe type assertion (v, ok := next.GetValue().(traversal.Construct)),
returning a descriptive error when the symbol is missing or the type assertion
fails (e.g., fmt.Errorf("symbol %q not found", *r) or fmt.Errorf("expected
traversal.Construct for %q, got %T", *r, next.GetValue())), and only assign
out.Value when the assertion succeeds. Ensure the resolver returns the specific
error instead of nil so callers can handle lookup/type errors.

Comment on lines +3 to +13
import (
"encoding/json"
"fmt"
"reflect"

"github.com/antlr4-go/antlr/v4"
"github.com/minecraftmetascript/mms/lang/builder_chain"
"github.com/minecraftmetascript/mms/lang/grammar"
"github.com/minecraftmetascript/mms/lang/traversal"
"github.com/minecraftmetascript/mms/lib"
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Guard against missing noise reference during JSON export.

If no inline noise or resource ref is provided, JSON will contain an empty noise id. Fail fast with a clear error.

 import (
 	"encoding/json"
+	"errors"
 	"fmt"
 	"reflect"
@@
 func (c NoiseDensityFn) MarshalJSON() ([]byte, error) {
+	if c.Noise.String() == "" {
+		return nil, errors.New("noise reference is empty in NoiseDensityFn")
+	}
 	return json.MarshalIndent(struct {
 		Type    string  `json:"type"`
 		Noise   string  `json:"noise"`

Also applies to: 86-98

YFactor float64 `json:"y_factor"`
Smear float64 `json:"smear_scale_multiplier"`
}{
Type: "minecraft:noise",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Wrong JSON type for OldBlendedNoise.

Should be minecraft:old_blended_noise, not minecraft:noise.

Apply:

-		Type:     "minecraft:noise",
+		Type:     "minecraft:old_blended_noise",
🤖 Prompt for AI Agents
In lang/constructs/worldgen/density_functions/OldNoise.go around line 85, the
JSON type for OldBlendedNoise is incorrectly set to "minecraft:noise"; change
the Type value to "minecraft:old_blended_noise" so the emitted JSON uses the
correct discriminator for OldBlendedNoise entries; update the string literal and
run any formatter/tests to ensure the change compiles and serializes as
expected.

Comment thread lang/constructs/worldgen/density_functions/SingleInput.go Outdated
Comment on lines +33 to +55
traversal.ConstructRegistry.Register(
reflect.TypeFor[*grammar.SurfaceBlockContext](),
func(ctx antlr.ParserRuleContext, currentNamespace string, scope *traversal.Scope) traversal.Construct {
blockCtx := ctx.(*grammar.SurfaceBlockContext)
for _, statementCtx := range blockCtx.GetChildren() {
statement, ok := statementCtx.(antlr.ParserRuleContext)
if !ok {
// TODO: Diagnose?
continue
}
targetCtx, ok := statement.GetChild(0).(antlr.ParserRuleContext)
if !ok {
// TODO: Diagnose?
continue
}

if traversal.ConstructRegistry.Construct(targetCtx, currentNamespace, scope) == nil {
fmt.Println("Failed to construct ", ctx.GetText(), reflect.TypeOf(targetCtx).Elem().Name())
}
}
return nil
},
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Potential panic + diagnostics to stdout — guard child access and use the diagnostics sink

  • Line 43 calls GetChild(0) without checking GetChildCount(); this can panic on empty nodes.
  • Lines 49–51 print to stdout. Route failures through scope.DiagnoseSemanticError to surface them uniformly.

Apply:

 func init() {
   traversal.ConstructRegistry.Register(
     reflect.TypeFor[*grammar.SurfaceBlockContext](),
     func(ctx antlr.ParserRuleContext, currentNamespace string, scope *traversal.Scope) traversal.Construct {
       blockCtx := ctx.(*grammar.SurfaceBlockContext)
       for _, statementCtx := range blockCtx.GetChildren() {
         statement, ok := statementCtx.(antlr.ParserRuleContext)
         if !ok {
           // TODO: Diagnose?
           continue
         }
-        targetCtx, ok := statement.GetChild(0).(antlr.ParserRuleContext)
+        if statement.GetChildCount() == 0 {
+          // TODO: Diagnose?
+          continue
+        }
+        child := statement.GetChild(0)
+        targetCtx, ok := child.(antlr.ParserRuleContext)
         if !ok {
           // TODO: Diagnose?
           continue
         }
 
-        if traversal.ConstructRegistry.Construct(targetCtx, currentNamespace, scope) == nil {
-          fmt.Println("Failed to construct ", ctx.GetText(), reflect.TypeOf(targetCtx).Elem().Name())
-        }
+        if traversal.ConstructRegistry.Construct(targetCtx, currentNamespace, scope) == nil {
+          scope.DiagnoseSemanticError(
+            "Failed to construct "+reflect.TypeOf(targetCtx).Elem().Name(),
+            targetCtx,
+          )
+        }
       }
       return nil
     },
   )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
traversal.ConstructRegistry.Register(
reflect.TypeFor[*grammar.SurfaceBlockContext](),
func(ctx antlr.ParserRuleContext, currentNamespace string, scope *traversal.Scope) traversal.Construct {
blockCtx := ctx.(*grammar.SurfaceBlockContext)
for _, statementCtx := range blockCtx.GetChildren() {
statement, ok := statementCtx.(antlr.ParserRuleContext)
if !ok {
// TODO: Diagnose?
continue
}
targetCtx, ok := statement.GetChild(0).(antlr.ParserRuleContext)
if !ok {
// TODO: Diagnose?
continue
}
if traversal.ConstructRegistry.Construct(targetCtx, currentNamespace, scope) == nil {
fmt.Println("Failed to construct ", ctx.GetText(), reflect.TypeOf(targetCtx).Elem().Name())
}
}
return nil
},
)
func init() {
traversal.ConstructRegistry.Register(
reflect.TypeFor[*grammar.SurfaceBlockContext](),
func(ctx antlr.ParserRuleContext, currentNamespace string, scope *traversal.Scope) traversal.Construct {
blockCtx := ctx.(*grammar.SurfaceBlockContext)
for _, statementCtx := range blockCtx.GetChildren() {
statement, ok := statementCtx.(antlr.ParserRuleContext)
if !ok {
// TODO: Diagnose?
continue
}
if statement.GetChildCount() == 0 {
// TODO: Diagnose?
continue
}
child := statement.GetChild(0)
targetCtx, ok := child.(antlr.ParserRuleContext)
if !ok {
// TODO: Diagnose?
continue
}
if traversal.ConstructRegistry.Construct(targetCtx, currentNamespace, scope) == nil {
scope.DiagnoseSemanticError(
"Failed to construct "+reflect.TypeOf(targetCtx).Elem().Name(),
targetCtx,
)
}
}
return nil
},
)
}
🤖 Prompt for AI Agents
In lang/constructs/worldgen/surface_rules/SurfaceConditions.go around lines 33
to 55, the code calls statement.GetChild(0) without checking GetChildCount()
(risking a panic on empty nodes) and prints errors to stdout; update the code to
first check statement.GetChildCount() > 0 before accessing child 0, handle the
unexpected shape by calling scope.DiagnoseSemanticError with a clear message
(including ctx.GetText() and the target node type) instead of fmt.Println, and
remove or replace the TODO comments with this diagnostics path so failures are
reported uniformly.

@ItsMeBrianD ItsMeBrianD marked this pull request as ready for review September 8, 2025 05:19
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
grammar/Surface.g4 (1)

85-88: Enable legacy Block and Sequence forms in grammar/Surface.g4

Update the surfaceRule_Block and surfaceRule_Sequence rules to accept both the new and legacy syntax:

-surfaceRule_Block: 'Block' '(' resourceReference ')';
-surfaceRule_Sequence: '[' NL* (surfaceRule NL*)* NL* ']';
+surfaceRule_Block: 'Block' NL* ( '(' NL* resourceReference NL* ')' | resourceReference ) NL*;
+surfaceRule_Sequence: ('Sequence' NL*)? '[' NL* (surfaceRule NL*)* NL* ']';

Run the surface-rule parser tests to ensure no regressions and that both Block(resource) and Block resource as well as Sequence […] and […] parse successfully.

♻️ Duplicate comments (2)
lang/constructs/worldgen/density_functions/DensityFn.go (2)

104-105: Remove debug print; rely on diagnostics

This leftover print leaks into stdout.

-					fmt.Println("Failed to process declaration")
 					return nil

126-146: Math operator handling: validate Arg2, safe operator cast, and graceful fallback

Avoid panics, diagnose issues, and return base value when math is invalid.

-				if math := fnCtx.DensityFn_Math(); math != nil {
+				if math := fnCtx.DensityFn_Math(); math != nil {
 					// This has attached maths
-					arg2 := traversal.ConstructRegistry.Construct(math.DensityFn(), currentNamespace, scope)
+					arg2 := traversal.ConstructRegistry.Construct(math.DensityFn(), currentNamespace, scope)
+					if arg2 == nil {
+						scope.DiagnoseSemanticError("Missing or invalid second operand for math density function", math)
+						return value
+					}
 
 					var kind MathDensityFnKind
-					if operation := math.GetChild(0); operation != nil {
-						switch operation.(antlr.TerminalNode).GetText() {
+					if operation := math.GetChild(0); operation != nil {
+						if term, ok := operation.(antlr.TerminalNode); ok {
+							switch term.GetText() {
 						case "*":
 							kind = MathDensityFn_Mul
 						case "+":
 							kind = MathDensityFn_Add
 						default:
 							scope.DiagnoseSemanticError("Unknown operator", ctx)
+							return value
 						}
+					} else {
+						scope.DiagnoseSemanticError("Invalid operator node", ctx)
+						return value
 					}
 					return &MathDensityFn{
 						Type: kind,
 						Arg1: value,
 						Arg2: arg2,
 					}
 				}
🧹 Nitpick comments (19)
lang/constructs/worldgen/noise/Noise.go (1)

61-79: Improve amplitude parsing UX and pre-size the slice

  • Include the offending token in the diagnostic to aid debugging.
  • Optionally pre-size the amplitudes slice to avoid reallocations for large lists.

Apply this diff for clearer diagnostics:

-						if err != nil {
-							scope.DiagnoseSemanticError(
-								"Invalid amplitude value",
-								amplitude,
-							)
+						if err != nil {
+							scope.DiagnoseSemanticError(
+								"Invalid amplitude value: "+amplitude.GetText(),
+								amplitude,
+							)

Optional pre-sizing (micro-optimization):

-			out := &Noise{
-				Amplitudes: make([]float64, 0),
-			}
+			// Pre-size amplitudes to reduce reallocations.
+			totalAmps := 0
+			for _, r := range noise.AllNoise_Builder() {
+				if amps := r.Builder_Amplitudes(); amps != nil {
+					totalAmps += len(amps.AllNumber())
+				}
+			}
+			out := &Noise{
+				Amplitudes: make([]float64, 0, totalAmps),
+			}

Optional guard after the loop:

if len(out.Amplitudes) == 0 {
    scope.DiagnoseSemanticError("Noise must define at least one amplitude", noise)
}
lang/builder_chain/SharedBuilder.go (1)

31-31: Rename helper to match new builder_ naming*

Now that it accepts Builder_AddContext, consider renaming SharedBuilder_Add → Builder_AddFlag (or similar) to avoid mixed conventions.

lang/constructs/worldgen/surface_rules/Condition_Noise.go (1)

56-65: Add semantic guard: require Min <= Max

Prevent invalid ranges early with a diagnostic.

Apply this diff right after the existing Require checks:

 			builder_chain.Require(
 				noiseBuildChain,
 				noise,
 				scope,
 				reflect.TypeFor[*grammar.Builder_MaxContext](),
 				".Max",
 			)
 
+			if out.Min > out.Max {
+				scope.DiagnoseSemanticError("Min must be <= Max", noise)
+			}
lang/constructs/worldgen/density_functions/ClampFn.go (2)

18-29: Rename builder var and fix user-facing diagnostics ("range choice" → "clamp").

Minor naming drift and copy/paste error in messages. Tighten for clarity.

-			rangeChoiceBuilder := builder_chain.NewBuilderChain[ClampDensityFn](
+			clampBuilder := builder_chain.NewBuilderChain[ClampDensityFn](
@@
-			for _, builderWrap := range densityFn.AllDensityFn_ClampBuilder() {
-				builder_chain.Invoke(rangeChoiceBuilder, builderWrap.GetChild(0).(antlr.ParserRuleContext), out, scope, currentNamespace)
-			}
+			for _, builderWrap := range densityFn.AllDensityFn_ClampBuilder() {
+				builder_chain.Invoke(clampBuilder, builderWrap.GetChild(0).(antlr.ParserRuleContext), out, scope, currentNamespace)
+			}
@@
-			builder_chain.Require(
-				rangeChoiceBuilder,
+			builder_chain.Require(
+				clampBuilder,
@@
-			builder_chain.Require(
-				rangeChoiceBuilder,
+			builder_chain.Require(
+				clampBuilder,
-			if input == nil {
-				scope.DiagnoseSemanticError("Missing input to range choice", ctx)
-			}
+			if input == nil {
+				scope.DiagnoseSemanticError("Missing input to clamp", ctx)
+			}
@@
-			if out.Input == nil {
-				scope.DiagnoseSemanticError("Invalid input to range choice", ctx)
-			}
+			if out.Input == nil {
+				scope.DiagnoseSemanticError("Invalid input to clamp", ctx)
+			}

Also applies to: 42-44, 46-59


46-59: Optional: validate clamp bounds.

Warn/error if Min > Max to catch obvious config mistakes.

 			builder_chain.Require(
 				clampBuilder,
 				densityFn,
 				scope,
 				reflect.TypeFor[*grammar.Builder_MaxContext](),
 				".Max",
 			)
+			if out.Min > out.Max {
+				scope.DiagnoseSemanticError("Clamp.Min cannot be greater than Clamp.Max", densityFn)
+			}
lang/constructs/worldgen/density_functions/YClampedGradientFn.go (2)

18-39: Rename builder var for accuracy; keep messages consistent.

Pure readability; aligns with the construct’s name.

-			rangeChoiceBuilder := builder_chain.NewBuilderChain[YClampedGradientDensityFn](
+			yClampedGradientBuilder := builder_chain.NewBuilderChain[YClampedGradientDensityFn](
@@
-			for _, builderWrap := range densityFn.AllDensityFn_YClampedGradientBuilder() {
-				builder_chain.Invoke(rangeChoiceBuilder, builderWrap.GetChild(0).(antlr.ParserRuleContext), out, scope, currentNamespace)
-			}
+			for _, builderWrap := range densityFn.AllDensityFn_YClampedGradientBuilder() {
+				builder_chain.Invoke(yClampedGradientBuilder, builderWrap.GetChild(0).(antlr.ParserRuleContext), out, scope, currentNamespace)
+			}
@@
-			builder_chain.Require(
-				rangeChoiceBuilder,
+			builder_chain.Require(
+				yClampedGradientBuilder,
@@
-			builder_chain.Require(
-				rangeChoiceBuilder,
+			builder_chain.Require(
+				yClampedGradientBuilder,
@@
-			builder_chain.Require(
-				rangeChoiceBuilder,
+			builder_chain.Require(
+				yClampedGradientBuilder,
@@
-			builder_chain.Require(
-				rangeChoiceBuilder,
+			builder_chain.Require(
+				yClampedGradientBuilder,

Also applies to: 44-46, 48-75


48-75: Optional: validate ranges (Bottom <= Top, Min <= Max).

Prevents inverted gradients and surprising outputs.

 			builder_chain.Require(
 				yClampedGradientBuilder,
 				densityFn,
 				scope,
 				reflect.TypeFor[*grammar.Builder_TopLiteralContext](),
 				".Top",
 			)
+			if out.Bottom > out.Top {
+				scope.DiagnoseSemanticError("YClampedGradient.Bottom cannot exceed .Top", densityFn)
+			}
+			if out.Min > out.Max {
+				scope.DiagnoseSemanticError("YClampedGradient.Min cannot exceed .Max", densityFn)
+			}
lang/constructs/worldgen/density_functions/SingleInput.go (3)

25-27: Confirm schema for Shift/ShiftA/ShiftB — likely need extra fields beyond just "argument".

If these map to Minecraft’s shift functions, they typically require noise inputs in addition to the child argument. Emitting only {type, argument} may produce invalid JSON. Please verify against the target game version and either:

  • split these into dedicated structs with required noise fields, or
  • extend this type to carry the extra fields conditionally.

65-66: Include offending token in the diagnostic.

Improves debuggability by showing what was parsed.

-                        scope.DiagnoseSemanticError("Invalid density function kind", ctx)
+                        scope.DiagnoseSemanticError("Invalid density function kind: "+k.GetText(), ctx)

76-83: Use “Invalid … target” when construction fails.

Differentiate missing vs. failed construction to guide users better.

-                if val == nil {
-                    scope.DiagnoseSemanticError("Missing density function target", ctx)
+                if val == nil {
+                    scope.DiagnoseSemanticError("Invalid density function target", ctx)
lang/constructs/worldgen/density_functions/WeirdScaledSamplerFn.go (1)

15-17: Spelling of context type (“Wierd”) — confirm grammar rule name.

If the grammar is later corrected to “Weird…Context”, this registration will break. Consider adding a second registration when/if the rule gets renamed to maintain compatibility.

lang/constructs/worldgen/density_functions/OldNoiseFn.go (1)

20-42: Incorrect labels passed to Builder_GetFloat.

All labels are “XzScale”; fix to reflect the actual fields to improve diagnostics.

-                    func(ctx *grammar.Builder_XZScaleContext, target *OldBlendedNoiseFn, scope *traversal.Scope, namespace string) {
-                        builder_chain.Builder_GetFloat(ctx, func(v float64) { target.XzScale = v }, scope, "XzScale")
+                    func(ctx *grammar.Builder_XZScaleContext, target *OldBlendedNoiseFn, scope *traversal.Scope, namespace string) {
+                        builder_chain.Builder_GetFloat(ctx, func(v float64) { target.XzScale = v }, scope, "XzScale")
                     },
                 ),
                 builder_chain.Build(
-                    func(ctx *grammar.Builder_YScaleContext, target *OldBlendedNoiseFn, scope *traversal.Scope, namespace string) {
-                        builder_chain.Builder_GetFloat(ctx, func(v float64) { target.YScale = v }, scope, "XzScale")
+                    func(ctx *grammar.Builder_YScaleContext, target *OldBlendedNoiseFn, scope *traversal.Scope, namespace string) {
+                        builder_chain.Builder_GetFloat(ctx, func(v float64) { target.YScale = v }, scope, "YScale")
                     },
                 ),
                 builder_chain.Build(
-                    func(ctx *grammar.Builder_XZFactorContext, target *OldBlendedNoiseFn, scope *traversal.Scope, namespace string) {
-                        builder_chain.Builder_GetFloat(ctx, func(v float64) { target.XzFactor = v }, scope, "XzScale")
+                    func(ctx *grammar.Builder_XZFactorContext, target *OldBlendedNoiseFn, scope *traversal.Scope, namespace string) {
+                        builder_chain.Builder_GetFloat(ctx, func(v float64) { target.XzFactor = v }, scope, "XzFactor")
                     },
                 ),
                 builder_chain.Build(
-                    func(ctx *grammar.Builder_YFactorContext, target *OldBlendedNoiseFn, scope *traversal.Scope, namespace string) {
-                        builder_chain.Builder_GetFloat(ctx, func(v float64) { target.YFactor = v }, scope, "XzScale")
+                    func(ctx *grammar.Builder_YFactorContext, target *OldBlendedNoiseFn, scope *traversal.Scope, namespace string) {
+                        builder_chain.Builder_GetFloat(ctx, func(v float64) { target.YFactor = v }, scope, "YFactor")
                     },
                 ),
                 builder_chain.Build(
-                    func(ctx *grammar.Builder_SmearContext, target *OldBlendedNoiseFn, scope *traversal.Scope, namespace string) {
-                        builder_chain.Builder_GetFloat(ctx, func(v float64) { target.SmearMultiplier = v }, scope, "XzScale")
+                    func(ctx *grammar.Builder_SmearContext, target *OldBlendedNoiseFn, scope *traversal.Scope, namespace string) {
+                        builder_chain.Builder_GetFloat(ctx, func(v float64) { target.SmearMultiplier = v }, scope, "SmearScaleMultiplier")
                     },
                 ),
lang/constructs/worldgen/density_functions/RangeChoiceFn.go (1)

18-49: Use the passed namespace param inside builders for consistency

The builders receive namespace string but use the outer currentNamespace. Prefer the param to avoid accidental capture bugs.

Example:

-						fn := traversal.ConstructRegistry.Construct(ctx.DensityFn(), currentNamespace, scope)
+						fn := traversal.ConstructRegistry.Construct(ctx.DensityFn(), namespace, scope)

Apply similarly in both InRange and OutRange builders.

lang/constructs/worldgen/density_functions/DensityFn.go (1)

75-79: Optional: avoid hard cast on NoiseDefinition

Type-asserting NoiseDefinition() directly can panic if grammar changes. Consider a checked assertion.

-			if noiseInlineCtx := inlineCtx.Noise(); noiseInlineCtx != nil {
-				return ExtractInlinedNoise(noiseInlineCtx.NoiseDefinition().(*grammar.NoiseDefinitionContext), currentNamespace, scope)
+			if noiseInlineCtx := inlineCtx.Noise(); noiseInlineCtx != nil {
+				if nd, ok := noiseInlineCtx.NoiseDefinition().(*grammar.NoiseDefinitionContext); ok {
+					return ExtractInlinedNoise(nd, currentNamespace, scope)
+				}
+				scope.DiagnoseSemanticError("Invalid inline noise definition node", inlineCtx)
 			}
grammar/Surface.g4 (1)

69-74: Retain legacy “AboveWater 2 1.5 Add” form (optional)

Current rule requires AboveWater() before builders; older tests/examples omit (). To avoid breaking, allow both.

-surfaceCondition_AboveWater: 'AboveWater' '(' ')' NL* (surfaceCondition_AboveWaterBuilder NL*)*;
+surfaceCondition_AboveWater:
+    ('AboveWater' '(' ')' | 'AboveWater')
+    NL* (surfaceCondition_AboveWaterBuilder NL*)*;
grammar/DensityFunctions.g4 (2)

71-76: Fix typo: Wierd → Weird (rule names).

Rule names use “Wierd” while the literal is “WeirdScaledSampler”. Standardize to “Weird” for consistency and to avoid confusing generated context type names.

Be sure to update any Go handlers that reference the generated context names.

-densityFn_WierdScaledSampler: 'WeirdScaledSampler' NL* '(' NL* densityFn NL* ')' NL* (densityFn_WierdScaledSamplerBuilder NL*)*;
+densityFn_WeirdScaledSampler: 'WeirdScaledSampler' NL* '(' NL* densityFn NL* ')' NL* (densityFn_WeirdScaledSamplerBuilder NL*)*;

-densityFn_WierdScaledSamplerBuilder: builder_Type1 | builder_Type2 | builder_Noise;
+densityFn_WeirdScaledSamplerBuilder: builder_Type1 | builder_Type2 | builder_Noise;

8-24: Allow chaining math operations; current grammar permits only one trailing op.

densityFn_Math? limits expressions to a single +/*. If chaining is desired (a + b + c, a * b * c), switch to repetition and allow optional NL between operator and rhs.

-) densityFn_Math?;
+) (densityFn_Math)*;
@@
-densityFn_Math: ('+' | '*') densityFn;
+densityFn_Math: ('+' | '*') NL* densityFn;

Also applies to: 93-93

lang/constructs/worldgen/density_functions/SplineFn.go (2)

31-35: Remove debug print leaking to stdout.

fmt.Println(locationCtx) is leftover debug noise.

-			fmt.Println(locationCtx)

100-105: Validate non-empty spline points.

Empty Points produces a valid-looking JSON but unusable spline. Consider erroring if no points were added.

I can wire a check after the loop that diagnoses an error when len(out.Points)==0.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 75cc770 and bab6514.

⛔ Files ignored due to path filters (8)
  • lang/grammar/MinecraftMetascript.interp is excluded by !lang/grammar/*
  • lang/grammar/MinecraftMetascript.tokens is excluded by !lang/grammar/*
  • lang/grammar/MinecraftMetascriptLexer.interp is excluded by !lang/grammar/*
  • lang/grammar/MinecraftMetascriptLexer.tokens is excluded by !lang/grammar/*
  • lang/grammar/minecraftmetascript_base_listener.go is excluded by !lang/grammar/*
  • lang/grammar/minecraftmetascript_lexer.go is excluded by !lang/grammar/*
  • lang/grammar/minecraftmetascript_listener.go is excluded by !lang/grammar/*
  • lang/grammar/minecraftmetascript_parser.go is excluded by !lang/grammar/*
📒 Files selected for processing (24)
  • grammar/Core_Lang.g4 (1 hunks)
  • grammar/DensityFunctions.g4 (1 hunks)
  • grammar/Noise.g4 (1 hunks)
  • grammar/Surface.g4 (2 hunks)
  • lang/builder_chain/BuilderChain.go (1 hunks)
  • lang/builder_chain/SharedBuilder.go (1 hunks)
  • lang/constructs/worldgen/density_functions/ClampFn.go (1 hunks)
  • lang/constructs/worldgen/density_functions/DensityFn.go (1 hunks)
  • lang/constructs/worldgen/density_functions/NoiseFn.go (1 hunks)
  • lang/constructs/worldgen/density_functions/OldNoiseFn.go (1 hunks)
  • lang/constructs/worldgen/density_functions/RangeChoiceFn.go (1 hunks)
  • lang/constructs/worldgen/density_functions/ShiftedNoiseFn.go (1 hunks)
  • lang/constructs/worldgen/density_functions/SingleInput.go (1 hunks)
  • lang/constructs/worldgen/density_functions/SplineFn.go (1 hunks)
  • lang/constructs/worldgen/density_functions/WeirdScaledSamplerFn.go (1 hunks)
  • lang/constructs/worldgen/density_functions/YClampedGradientFn.go (1 hunks)
  • lang/constructs/worldgen/noise/Noise.go (1 hunks)
  • lang/constructs/worldgen/surface_rules/Condition_AboveWater.go (1 hunks)
  • lang/constructs/worldgen/surface_rules/Condition_Noise.go (2 hunks)
  • lang/constructs/worldgen/surface_rules/Condition_StoneDepth.go (1 hunks)
  • lang/constructs/worldgen/surface_rules/Condition_VerticalGradient.go (1 hunks)
  • lang/constructs/worldgen/surface_rules/Condition_YAbove.go (1 hunks)
  • lang/traversal/Symbol.go (2 hunks)
  • test.mms (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • test.mms
  • lang/traversal/Symbol.go
  • lang/constructs/worldgen/density_functions/NoiseFn.go
🧰 Additional context used
🧬 Code graph analysis (16)
lang/constructs/worldgen/density_functions/YClampedGradientFn.go (6)
lang/traversal/Construct.go (2)
  • ConstructRegistry (55-57)
  • Construct (12-16)
lang/traversal/Scope.go (1)
  • Scope (11-16)
lang/builder_chain/BuilderChain.go (4)
  • NewBuilderChain (76-85)
  • Build (63-74)
  • Invoke (20-42)
  • Require (44-54)
lang/builder_chain/SharedBuilder.go (2)
  • Builder_GetInt (39-59)
  • Builder_GetFloat (61-82)
lang/traversal/Symbol.go (1)
  • Symbol (9-15)
wasm/mms.d.ts (1)
  • FileTreeLike (10-13)
lang/constructs/worldgen/density_functions/RangeChoiceFn.go (6)
lang/traversal/Construct.go (2)
  • ConstructRegistry (55-57)
  • Construct (12-16)
lang/traversal/Scope.go (1)
  • Scope (11-16)
lang/builder_chain/BuilderChain.go (4)
  • NewBuilderChain (76-85)
  • Build (63-74)
  • Invoke (20-42)
  • Require (44-54)
lang/builder_chain/SharedBuilder.go (1)
  • Builder_GetFloat (61-82)
lang/traversal/Symbol.go (1)
  • Symbol (9-15)
wasm/mms.d.ts (1)
  • FileTreeLike (10-13)
lang/constructs/worldgen/surface_rules/Condition_Noise.go (2)
lang/builder_chain/SharedBuilder.go (1)
  • Builder_GetFloat (61-82)
lang/builder_chain/BuilderChain.go (2)
  • Build (63-74)
  • Require (44-54)
lang/constructs/worldgen/surface_rules/Condition_StoneDepth.go (3)
lang/traversal/Scope.go (1)
  • Scope (11-16)
lang/builder_chain/SharedBuilder.go (1)
  • Builder_GetInt (39-59)
lang/builder_chain/BuilderChain.go (1)
  • Build (63-74)
lang/constructs/worldgen/surface_rules/Condition_YAbove.go (2)
lang/builder_chain/SharedBuilder.go (1)
  • Builder_GetInt (39-59)
lang/builder_chain/BuilderChain.go (1)
  • Build (63-74)
lang/constructs/worldgen/surface_rules/Condition_AboveWater.go (2)
lang/builder_chain/SharedBuilder.go (2)
  • Builder_GetInt (39-59)
  • SharedBuilder_Add (30-37)
lang/builder_chain/BuilderChain.go (1)
  • Build (63-74)
lang/constructs/worldgen/surface_rules/Condition_VerticalGradient.go (2)
lang/builder_chain/SharedBuilder.go (1)
  • Builder_GetVerticalAnchor (84-107)
lang/builder_chain/BuilderChain.go (1)
  • Build (63-74)
lang/constructs/worldgen/density_functions/ClampFn.go (6)
lang/traversal/Construct.go (2)
  • ConstructRegistry (55-57)
  • Construct (12-16)
lang/traversal/Scope.go (1)
  • Scope (11-16)
lang/builder_chain/BuilderChain.go (4)
  • NewBuilderChain (76-85)
  • Build (63-74)
  • Invoke (20-42)
  • Require (44-54)
lang/builder_chain/SharedBuilder.go (1)
  • Builder_GetFloat (61-82)
lang/traversal/Symbol.go (1)
  • Symbol (9-15)
wasm/mms.d.ts (1)
  • FileTreeLike (10-13)
lang/constructs/worldgen/density_functions/SingleInput.go (5)
lang/traversal/Construct.go (2)
  • ConstructRegistry (55-57)
  • Construct (12-16)
lang/traversal/Scope.go (1)
  • Scope (11-16)
lang/traversal/Symbol.go (1)
  • Symbol (9-15)
wasm/mms.d.ts (1)
  • FileTreeLike (10-13)
lang/constructs/worldgen/surface_rules/Rule_Sequence.go (1)
  • Type (47-50)
lang/constructs/worldgen/density_functions/ShiftedNoiseFn.go (5)
lang/traversal/Construct.go (2)
  • ConstructRegistry (55-57)
  • Construct (12-16)
lang/traversal/Scope.go (1)
  • Scope (11-16)
lang/builder_chain/BuilderChain.go (3)
  • NewBuilderChain (76-85)
  • Build (63-74)
  • Invoke (20-42)
lang/builder_chain/SharedBuilder.go (1)
  • Builder_GetFloat (61-82)
lang/traversal/Symbol.go (1)
  • Symbol (9-15)
lang/constructs/worldgen/density_functions/SplineFn.go (5)
lang/traversal/Scope.go (1)
  • Scope (11-16)
lang/traversal/Construct.go (2)
  • ConstructRegistry (55-57)
  • Construct (12-16)
lang/builder_chain/SharedBuilder.go (1)
  • Builder_GetFloat (61-82)
lang/traversal/Symbol.go (1)
  • Symbol (9-15)
wasm/mms.d.ts (1)
  • FileTreeLike (10-13)
lang/constructs/worldgen/density_functions/OldNoiseFn.go (6)
lang/traversal/Construct.go (2)
  • ConstructRegistry (55-57)
  • Construct (12-16)
lang/traversal/Scope.go (1)
  • Scope (11-16)
lang/builder_chain/BuilderChain.go (3)
  • NewBuilderChain (76-85)
  • Build (63-74)
  • Invoke (20-42)
lang/builder_chain/SharedBuilder.go (1)
  • Builder_GetFloat (61-82)
lang/traversal/Symbol.go (1)
  • Symbol (9-15)
wasm/mms.d.ts (1)
  • FileTreeLike (10-13)
lang/constructs/worldgen/density_functions/WeirdScaledSamplerFn.go (5)
lang/traversal/Construct.go (2)
  • ConstructRegistry (55-57)
  • Construct (12-16)
lang/traversal/Scope.go (1)
  • Scope (11-16)
lang/traversal/Reference.go (1)
  • Reference (30-36)
lang/constructs/worldgen/density_functions/DensityFn.go (1)
  • ExtractInlinedNoise (42-62)
wasm/mms.d.ts (1)
  • FileTreeLike (10-13)
lang/constructs/worldgen/noise/Noise.go (4)
lang/traversal/Construct.go (2)
  • ConstructRegistry (55-57)
  • Construct (12-16)
lang/traversal/Symbol.go (1)
  • ProcessDeclaration (44-69)
lang/builder_chain/SharedBuilder.go (1)
  • Builder_GetInt (39-59)
lang/constructs/worldgen/surface_rules/SurfaceConditions.go (1)
  • init (31-52)
lang/constructs/worldgen/density_functions/DensityFn.go (5)
lang/traversal/Construct.go (2)
  • Construct (12-16)
  • ConstructRegistry (55-57)
lang/traversal/Symbol.go (3)
  • Symbol (9-15)
  • NewSymbol (29-37)
  • ProcessDeclaration (44-69)
wasm/mms.d.ts (1)
  • FileTreeLike (10-13)
lang/traversal/Reference.go (2)
  • Reference (30-36)
  • NewReference (38-43)
lang/traversal/Location.go (1)
  • RuleLocation (42-57)
grammar/Surface.g4 (2)
lang/surface_rules_additional_test.go (2)
  • TestSurfaceRule_Sequence_ExportJSON (8-54)
  • TestSurfaceRule_If_AboveWater_JSON (56-104)
lang/parsing_and_export_test.go (1)
  • TestParseValidSurfaceRule_Block_AndExport (19-57)
🔇 Additional comments (19)
lang/constructs/worldgen/noise/Noise.go (1)

16-27: LGTM: traversal-only registration for NoiseBlockContext is correct

Recurses into child ParserRuleContexts and intentionally returns nil. This matches the pattern elsewhere for block/aggregate nodes.

lang/builder_chain/BuilderChain.go (1)

26-26: Remove noisy stdout print from Invoke
Confirmed this is the only fmt.Println (and the sole os.Stdout/os.Stderr reference) in the repo; remove it or guard it behind a debug logger/flag.
Apply this diff:

-	fmt.Println(ctxType.Name())
lang/constructs/worldgen/surface_rules/Condition_StoneDepth.go (1)

26-29: LGTM: builder context type migration is correct

Type updates to Builder_OffsetContext and Builder_AddContext align with grammar changes; logic unchanged.

Also applies to: 31-34

lang/constructs/worldgen/surface_rules/Condition_YAbove.go (1)

22-25: *LGTM: Builder_Context updates

MulInt/Add closures correctly migrated; behavior preserved.

Also applies to: 27-30

lang/constructs/worldgen/surface_rules/Condition_VerticalGradient.go (1)

22-25: LGTM: Top/Bottom builder migrations

Concrete Builder_TopContext/Builder_BottomContext usage matches grammar; extraction helpers remain correct.

Also applies to: 27-30

lang/constructs/worldgen/surface_rules/Condition_Noise.go (1)

21-24: LGTM: Min/Max builder type updates

Switch to Builder_MinContext/Builder_MaxContext is consistent with Core_Lang.g4.

Also applies to: 26-29

grammar/Noise.g4 (2)

8-12: LGTM: modularize with noiseDefinition and builder_Amplitudes

Refactor keeps observable grammar intact and aligns with builder_* rules.


8-12: Rename verification successful
No stale references to noise_Builder_Amplitudes were found; builder_Amplitudes remains correctly defined and consumed.

lang/constructs/worldgen/surface_rules/Condition_AboveWater.go (1)

21-24: LGTM: builder context renames and value extraction

Offset/Add/Mul migrations are consistent; parsing helpers used correctly.

Also applies to: 26-29, 31-39

lang/constructs/worldgen/density_functions/YClampedGradientFn.go (1)

88-102: JSON field mapping LGTM.

Tags correctly map to Minecraft schema: from_value/to_value and from_y/to_y.

lang/constructs/worldgen/density_functions/RangeChoiceFn.go (1)

123-125: LGTM: export path integration

Export hooks correctly delegate to shared exporter.

grammar/Surface.g4 (5)

45-46: Builder switch to builder_Max/Min looks good

Consistent with Core_Lang builder_* scheme.


54-56: StoneDepth builders updated correctly

Offset/Add via builder_* aligns with the new shared builders.


63-65: VerticalGradient builder update is consistent

Top/Bottom via builder_* is okay.


71-73: AboveWater builders aligned with builder_ tokens*

OK. Consider backward compatibility for syntax (see next comment).


76-76: YAbove builders updated

MulInt/Add via builder_* is fine.

lang/constructs/worldgen/density_functions/SplineFn.go (1)

69-75: No changes needed. go.mod declares Go 1.23, which already supports reflect.TypeFor (added in Go 1.20).

grammar/Core_Lang.g4 (2)

54-56: Literal ‘2d’ token vs Int/Identifier—double-check lexer precedence.

Using '2d' as a literal alongside Int and Identifier can be tricky in ANTLR. It usually works, but verify generation produces a single token for '2d' and not Int '2' + Identifier 'd'.

I recommend running the grammar generation and ensuring no token ambiguity/warnings are emitted for DensityFn_CacheKind.


17-18: Remove unreachable-alt suggestion: builder_MulInt is reachable
builder_MulInt is a separate parser rule invoked by surfaceCondition_YAboveBuilder in Surface.g4:76; it isn’t shadowed by builder_Mul and should be retained.

Likely an incorrect or invalid review comment.

Comment thread grammar/Core_Lang.g4
@@ -1,10 +1,38 @@
grammar Core_Lang;

import Noise, DensityFunctions, Surface;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Break circular grammar import (Core_Lang ↔ DensityFunctions).

Core_Lang imports DensityFunctions, and DensityFunctions imports Core_Lang. ANTLR import cycles are not supported and will fail generation. Move densityFn-dependent builders out of Core_Lang and drop the import.

-import Noise, DensityFunctions, Surface;
+import Noise, Surface;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import Noise, DensityFunctions, Surface;
import Noise, Surface;

Comment thread grammar/Core_Lang.g4 Outdated
builder_Smear: 'Smear' '(' number ')';
builder_Type1: '.Type1' '(' ')';
builder_Type2: '.Type2' '(' ')';
builder_Shift: '.Shift' '(' Axis ',' densityFn ')';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove densityFn-dependent builders from Core_Lang.

These reference densityFn (defined in DensityFunctions), causing the cycle. Define them in DensityFunctions.g4 instead (see paired diff there).

-builder_Shift: '.Shift' '(' Axis ',' densityFn ')';
@@
-builder_InRange: '.InRange' '(' densityFn ')';
-builder_OutRange: '.OutRange' '(' densityFn ')';

Also applies to: 25-26

🤖 Prompt for AI Agents
In grammar/Core_Lang.g4 around lines 13 (and also lines 25-26), there are
builder rules that reference densityFn (e.g., builder_Shift and the other
densityFn-dependent builders) which create a grammar cycle; remove these
densityFn-dependent builder rules from Core_Lang.g4 and instead add equivalent
rule definitions into DensityFunctions.g4 (so DensityFunctions owns
densityFn-related builders). Update Core_Lang.g4 by deleting those specific
lines/rules and ensure any imports or parser references still point to the new
rules in DensityFunctions.g4.

('Noise' NL* '(' NL* (resourceReference) NL* ')') | densityFn_InlineNoise
) NL* (densityFn_NoiseBuilder NL*)*;

densityFn_NoiseBuilder: builder_XZScale | builder_YScale;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Localize densityFn-dependent builders here to break grammar cycle (paired with Core_Lang.g4 change).

These builders reference densityFn; defining them in Core_Lang creates a circular import (Core_Lang ↔ DensityFunctions). Add them here and remove from Core_Lang.

 densityFn_NoiseBuilder: builder_XZScale | builder_YScale;
+
+// densityFn-scoped builders (moved from Core_Lang to avoid circular import)
+builder_Shift: '.Shift' '(' Axis ',' densityFn ')';
+builder_InRange: '.InRange' '(' densityFn ')';
+builder_OutRange: '.OutRange' '(' densityFn ')';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
densityFn_NoiseBuilder: builder_XZScale | builder_YScale;
densityFn_NoiseBuilder: builder_XZScale | builder_YScale;
// densityFn-scoped builders (moved from Core_Lang to avoid circular import)
builder_Shift: '.Shift' '(' Axis ',' densityFn ')';
builder_InRange: '.InRange' '(' densityFn ')';
builder_OutRange: '.OutRange' '(' densityFn ')';
🤖 Prompt for AI Agents
In grammar/DensityFunctions.g4 around line 52, localize the densityFn-dependent
builder rules (specifically builder_XZScale and builder_YScale) here instead of
in Core_Lang to break the circular grammar dependency; move the full rule
definitions for those builders from Core_Lang into this file, update any rule
references to point to these local definitions, and remove their definitions
from Core_Lang so Core_Lang only references densityFn (no reciprocal import).
Ensure rule names and token references remain identical and run the grammar
build to confirm no unresolved references.

Comment on lines +57 to +60
densityFn_DualInput: (
| 'Min'
| 'Max'
) NL* '(' NL* densityFn ',' NL* densityFn NL* ')';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove stray empty alternative in densityFn_DualInput (parsing bug).

The leading | introduces an empty alternative, making ( valid without Min/Max. This will parse invalid input and likely breaks intent.

Apply:

-densityFn_DualInput: (
-    | 'Min'
-    | 'Max'
-) NL* '(' NL* densityFn ',' NL* densityFn NL* ')';
+densityFn_DualInput: (
+      'Min'
+    | 'Max'
+) NL* '(' NL* densityFn ',' NL* densityFn NL* ')';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
densityFn_DualInput: (
| 'Min'
| 'Max'
) NL* '(' NL* densityFn ',' NL* densityFn NL* ')';
densityFn_DualInput: (
'Min'
| 'Max'
) NL* '(' NL* densityFn ',' NL* densityFn NL* ')';
🤖 Prompt for AI Agents
In grammar/DensityFunctions.g4 around lines 57 to 60, the densityFn_DualInput
rule contains a leading '|' which creates an empty alternative allowing '('
without 'Min' or 'Max'; remove the stray leading '|' so the alternatives are
enumerated properly (e.g. replace the block so the first alternative is 'Min'
and the second is 'Max') and keep the surrounding NL* and punctuation unchanged
to restore correct parsing.

Comment on lines +42 to +61
func ExtractInlinedNoise(noiseCtx *grammar.NoiseDefinitionContext, currentNamespace string, scope *traversal.Scope) *traversal.Reference {
noiseDef := traversal.ConstructRegistry.Construct(noiseCtx, currentNamespace, scope)
noiseRef := traversal.NewReference(
fmt.Sprintf("densityfn_noise_%d_%d", noiseCtx.GetStart().GetLine(), noiseCtx.GetStart().GetColumn()),
"mms_inline",
)
noiseSymbol := traversal.NewSymbol(
traversal.RuleLocation(noiseCtx, scope.CurrentFile),
traversal.RuleLocation(noiseCtx, scope.CurrentFile),
noiseDef,
noiseRef,
"Noise",
)
if _, ok := scope.Get(*noiseRef); !ok {
if err := scope.Register(noiseSymbol); err != nil {
// TODO: Improve behavior
panic(err)
}
}
return noiseRef
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don’t panic on user input; validate inline noise and diagnose

Panic on register error will crash parsing; also handle nil noiseDef.

 func ExtractInlinedNoise(noiseCtx *grammar.NoiseDefinitionContext, currentNamespace string, scope *traversal.Scope) *traversal.Reference {
-	noiseDef := traversal.ConstructRegistry.Construct(noiseCtx, currentNamespace, scope)
+	noiseDef := traversal.ConstructRegistry.Construct(noiseCtx, currentNamespace, scope)
+	if noiseDef == nil {
+		scope.DiagnoseSemanticError("Invalid inline noise definition", noiseCtx)
+		return nil
+	}
 	noiseRef := traversal.NewReference(
 		fmt.Sprintf("densityfn_noise_%d_%d", noiseCtx.GetStart().GetLine(), noiseCtx.GetStart().GetColumn()),
 		"mms_inline",
 	)
 	noiseSymbol := traversal.NewSymbol(
 		traversal.RuleLocation(noiseCtx, scope.CurrentFile),
 		traversal.RuleLocation(noiseCtx, scope.CurrentFile),
 		noiseDef,
 		noiseRef,
 		"Noise",
 	)
 	if _, ok := scope.Get(*noiseRef); !ok {
-		if err := scope.Register(noiseSymbol); err != nil {
-			// TODO: Improve behavior
-			panic(err)
-		}
+		if err := scope.Register(noiseSymbol); err != nil {
+			scope.DiagnoseSemanticError(fmt.Sprintf("Failed to register inline noise: %v", err), noiseCtx)
+			return nil
+		}
 	}
 	return noiseRef
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func ExtractInlinedNoise(noiseCtx *grammar.NoiseDefinitionContext, currentNamespace string, scope *traversal.Scope) *traversal.Reference {
noiseDef := traversal.ConstructRegistry.Construct(noiseCtx, currentNamespace, scope)
noiseRef := traversal.NewReference(
fmt.Sprintf("densityfn_noise_%d_%d", noiseCtx.GetStart().GetLine(), noiseCtx.GetStart().GetColumn()),
"mms_inline",
)
noiseSymbol := traversal.NewSymbol(
traversal.RuleLocation(noiseCtx, scope.CurrentFile),
traversal.RuleLocation(noiseCtx, scope.CurrentFile),
noiseDef,
noiseRef,
"Noise",
)
if _, ok := scope.Get(*noiseRef); !ok {
if err := scope.Register(noiseSymbol); err != nil {
// TODO: Improve behavior
panic(err)
}
}
return noiseRef
func ExtractInlinedNoise(noiseCtx *grammar.NoiseDefinitionContext, currentNamespace string, scope *traversal.Scope) *traversal.Reference {
noiseDef := traversal.ConstructRegistry.Construct(noiseCtx, currentNamespace, scope)
if noiseDef == nil {
scope.DiagnoseSemanticError("Invalid inline noise definition", noiseCtx)
return nil
}
noiseRef := traversal.NewReference(
fmt.Sprintf("densityfn_noise_%d_%d", noiseCtx.GetStart().GetLine(), noiseCtx.GetStart().GetColumn()),
"mms_inline",
)
noiseSymbol := traversal.NewSymbol(
traversal.RuleLocation(noiseCtx, scope.CurrentFile),
traversal.RuleLocation(noiseCtx, scope.CurrentFile),
noiseDef,
noiseRef,
"Noise",
)
if _, ok := scope.Get(*noiseRef); !ok {
if err := scope.Register(noiseSymbol); err != nil {
scope.DiagnoseSemanticError(fmt.Sprintf("Failed to register inline noise: %v", err), noiseCtx)
return nil
}
}
return noiseRef
}
🤖 Prompt for AI Agents
In lang/constructs/worldgen/density_functions/DensityFn.go around lines 42 to
61, the function panics on scope.Register errors and doesn't handle a nil
noiseDef; change it to validate noiseDef first (if noiseDef == nil return nil),
and replace the panic on Register error with non-fatal handling: log the error
(or use scope's logger if available) and return nil so parsing doesn't crash;
keep the rest of the registration logic the same and ensure the function
consistently returns nil on any failure to create or register the inline noise
reference.

Comment thread lang/constructs/worldgen/density_functions/ShiftedNoiseFn.go Outdated
Comment on lines +31 to +37
scope.DiagnoseSemanticError("Missing or invalid Axis", ctx)
return
}
childFnCtx := ctx.DensityFn()
if childFnCtx == nil {
scope.DiagnoseSemanticError("Missing or invalid Density Function", ctx)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Early-return on invalid density function; normalize error text/case.

Avoid assigning nil shifts after reporting errors; standardize messages.

Included in the diff above:

  • Return/continue after diagnostics for missing/invalid density function.
  • Lower-case axis normalization via strings.ToLower.
  • Consistent error strings (“axis”, “density function”).

Also applies to: 39-42, 44-57

Comment on lines +51 to +62
derivativeCtx := point.GetChild(2)
if derivative, ok := derivativeCtx.(*grammar.NumberContext); ok {
val, err := strconv.ParseFloat(derivative.GetText(), 64)
if err != nil {
scope.DiagnoseSemanticError("Invalid spline derivative", point)
return nil
}
out.Derivative = val
} else {
scope.DiagnoseSemanticError("Invalid spline derivative", point)
return nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Derivative parsing uses the wrong node (reads location instead of derivative).

GetChild(2) is the third child in the raw parse tree (tokens included), not the 3rd Number. For .Point(loc, value, deriv) this sets Derivative = loc. Parse the last Number instead.

-	derivativeCtx := point.GetChild(2)
-	if derivative, ok := derivativeCtx.(*grammar.NumberContext); ok {
-		val, err := strconv.ParseFloat(derivative.GetText(), 64)
-		if err != nil {
-			scope.DiagnoseSemanticError("Invalid spline derivative", point)
-			return nil
-		}
-		out.Derivative = val
-	} else {
-		scope.DiagnoseSemanticError("Invalid spline derivative", point)
-		return nil
-	}
+	// derivative is always the last Number in: .Point '(' number ',' (number|spline|ref) ',' number ')'
+	if nums := point.AllNumber(); len(nums) >= 2 {
+		if val, err := strconv.ParseFloat(nums[len(nums)-1].GetText(), 64); err == nil {
+			out.Derivative = val
+		} else {
+			scope.DiagnoseSemanticError("Invalid spline derivative", point)
+			return nil
+		}
+	} else {
+		scope.DiagnoseSemanticError("Missing spline derivative", point)
+		return nil
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
derivativeCtx := point.GetChild(2)
if derivative, ok := derivativeCtx.(*grammar.NumberContext); ok {
val, err := strconv.ParseFloat(derivative.GetText(), 64)
if err != nil {
scope.DiagnoseSemanticError("Invalid spline derivative", point)
return nil
}
out.Derivative = val
} else {
scope.DiagnoseSemanticError("Invalid spline derivative", point)
return nil
}
// derivative is always the last Number in: .Point '(' number ',' (number|spline|ref) ',' number ')'
if nums := point.AllNumber(); len(nums) >= 2 {
if val, err := strconv.ParseFloat(nums[len(nums)-1].GetText(), 64); err == nil {
out.Derivative = val
} else {
scope.DiagnoseSemanticError("Invalid spline derivative", point)
return nil
}
} else {
scope.DiagnoseSemanticError("Missing spline derivative", point)
return nil
}
🤖 Prompt for AI Agents
In lang/constructs/worldgen/density_functions/SplineFn.go around lines 51–62,
the code incorrectly uses point.GetChild(2) (a raw token-based child) to read
the derivative, which can pick up the location token; instead scan the children
from the end and pick the last child that is a *grammar.NumberContext (or
otherwise find the last Number node), parse its text with strconv.ParseFloat,
set out.Derivative, and keep the existing semantic error handling if parsing or
type assertion fails.

Comment on lines +32 to +66
for _, builderWrap := range densityFn.AllDensityFn_WierdScaledSamplerBuilder() {
builder := builderWrap.GetChild(0)
if builder == nil {
// TODO: Diagnose
continue
}
if _, ok := builder.(*grammar.Builder_Type1Context); ok {
out.ValueMapper = "type_1"
}
if _, ok := builder.(*grammar.Builder_Type2Context); ok {
out.ValueMapper = "type_2"
}
if noise, ok := builder.(*grammar.Builder_NoiseContext); ok {
if noise.ResourceReference() != nil {
ref := traversal.ConstructRegistry.Construct(noise.ResourceReference(), currentNamespace, scope)
if r, ok := ref.(*traversal.Reference); ok {
out.Noise = *r
} else {
scope.DiagnoseSemanticError("Invalid noise reference", noise)
// TODO: Diagnose
continue
}
} else if inline := noise.NoiseDefinition(); inline != nil {
ref := ExtractInlinedNoise(inline.(*grammar.NoiseDefinitionContext), currentNamespace, scope)
if ref != nil {
out.Noise = *ref
} else {
// TODO: Diagnose
scope.DiagnoseSemanticError("Invalid inline noise definition.", noise)
continue
}

}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enforce required fields and guard duplicate mapper builders.

Currently, ValueMapper and Noise can be omitted without diagnostics, yielding invalid JSON. Also, both type_1 and type_2 can be specified, silently overriding. Add checks and a simple duplicate guard.

-            for _, builderWrap := range densityFn.AllDensityFn_WierdScaledSamplerBuilder() {
+            seenMapper := false
+            for _, builderWrap := range densityFn.AllDensityFn_WierdScaledSamplerBuilder() {
                 builder := builderWrap.GetChild(0)
                 if builder == nil {
                     // TODO: Diagnose
                     continue
                 }
-                if _, ok := builder.(*grammar.Builder_Type1Context); ok {
-                    out.ValueMapper = "type_1"
-                }
-                if _, ok := builder.(*grammar.Builder_Type2Context); ok {
-                    out.ValueMapper = "type_2"
-                }
+                if _, ok := builder.(*grammar.Builder_Type1Context); ok {
+                    if seenMapper {
+                        scope.DiagnoseSemanticWarning("Duplicate rarity_value_mapper; last one wins", builder)
+                    }
+                    out.ValueMapper = "type_1"
+                    seenMapper = true
+                } else if _, ok := builder.(*grammar.Builder_Type2Context); ok {
+                    if seenMapper {
+                        scope.DiagnoseSemanticWarning("Duplicate rarity_value_mapper; last one wins", builder)
+                    }
+                    out.ValueMapper = "type_2"
+                    seenMapper = true
+                }
                 if noise, ok := builder.(*grammar.Builder_NoiseContext); ok {
                     if noise.ResourceReference() != nil {
                         ref := traversal.ConstructRegistry.Construct(noise.ResourceReference(), currentNamespace, scope)
                         if r, ok := ref.(*traversal.Reference); ok {
                             out.Noise = *r
                         } else {
                             scope.DiagnoseSemanticError("Invalid noise reference", noise)
                             // TODO: Diagnose
                             continue
                         }
                     } else if inline := noise.NoiseDefinition(); inline != nil {
                         ref := ExtractInlinedNoise(inline.(*grammar.NoiseDefinitionContext), currentNamespace, scope)
                         if ref != nil {
                             out.Noise = *ref
                         } else {
                             // TODO: Diagnose
                             scope.DiagnoseSemanticError("Invalid inline noise definition.", noise)
                             continue
                         }
 
                     }
                 }
             }
 
+            if out.ValueMapper == "" {
+                scope.DiagnoseSemanticError("Missing rarity_value_mapper (type_1 or type_2)", densityFn)
+            }
+            if out.Noise.String() == "" {
+                scope.DiagnoseSemanticError("Missing noise reference or inline noise", densityFn)
+            }
+
             return out
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for _, builderWrap := range densityFn.AllDensityFn_WierdScaledSamplerBuilder() {
builder := builderWrap.GetChild(0)
if builder == nil {
// TODO: Diagnose
continue
}
if _, ok := builder.(*grammar.Builder_Type1Context); ok {
out.ValueMapper = "type_1"
}
if _, ok := builder.(*grammar.Builder_Type2Context); ok {
out.ValueMapper = "type_2"
}
if noise, ok := builder.(*grammar.Builder_NoiseContext); ok {
if noise.ResourceReference() != nil {
ref := traversal.ConstructRegistry.Construct(noise.ResourceReference(), currentNamespace, scope)
if r, ok := ref.(*traversal.Reference); ok {
out.Noise = *r
} else {
scope.DiagnoseSemanticError("Invalid noise reference", noise)
// TODO: Diagnose
continue
}
} else if inline := noise.NoiseDefinition(); inline != nil {
ref := ExtractInlinedNoise(inline.(*grammar.NoiseDefinitionContext), currentNamespace, scope)
if ref != nil {
out.Noise = *ref
} else {
// TODO: Diagnose
scope.DiagnoseSemanticError("Invalid inline noise definition.", noise)
continue
}
}
}
}
// Enforce a single mapper and track if one has been seen.
seenMapper := false
for _, builderWrap := range densityFn.AllDensityFn_WierdScaledSamplerBuilder() {
builder := builderWrap.GetChild(0)
if builder == nil {
// TODO: Diagnose
continue
}
// Handle rarity value mappers, warning on duplicates.
if _, ok := builder.(*grammar.Builder_Type1Context); ok {
if seenMapper {
scope.DiagnoseSemanticWarning("Duplicate rarity_value_mapper; last one wins", builder)
}
out.ValueMapper = "type_1"
seenMapper = true
} else if _, ok := builder.(*grammar.Builder_Type2Context); ok {
if seenMapper {
scope.DiagnoseSemanticWarning("Duplicate rarity_value_mapper; last one wins", builder)
}
out.ValueMapper = "type_2"
seenMapper = true
}
// Handle noise reference or inline noise definition.
if noise, ok := builder.(*grammar.Builder_NoiseContext); ok {
if noise.ResourceReference() != nil {
ref := traversal.ConstructRegistry.Construct(noise.ResourceReference(), currentNamespace, scope)
if r, ok := ref.(*traversal.Reference); ok {
out.Noise = *r
} else {
scope.DiagnoseSemanticError("Invalid noise reference", noise)
// TODO: Diagnose
continue
}
} else if inline := noise.NoiseDefinition(); inline != nil {
ref := ExtractInlinedNoise(inline.(*grammar.NoiseDefinitionContext), currentNamespace, scope)
if ref != nil {
out.Noise = *ref
} else {
// TODO: Diagnose
scope.DiagnoseSemanticError("Invalid inline noise definition.", noise)
continue
}
}
}
}
// Ensure both a mapper and a noise source were provided.
if out.ValueMapper == "" {
scope.DiagnoseSemanticError("Missing rarity_value_mapper (type_1 or type_2)", densityFn)
}
if out.Noise.String() == "" {
scope.DiagnoseSemanticError("Missing noise reference or inline noise", densityFn)
}
return out
🤖 Prompt for AI Agents
In lang/constructs/worldgen/density_functions/WeirdScaledSamplerFn.go around
lines 32-66, add validation to ensure ValueMapper and Noise are required and
detect duplicate mapper builders: track whether a mapper was already set when
iterating builders and if a second mapper (type_1 or type_2) is encountered call
scope.DiagnoseSemanticError("Duplicate value mapper", builder) and
skip/continue; ensure that when processing noise you set out.Noise only once and
diagnose on invalid references as already partly done; after the loop validate
that out.ValueMapper and out.Noise have been set and if either is missing call
scope.DiagnoseSemanticError("Missing required field: ValueMapper" or "Missing
required field: Noise", the original context) and handle the error
(return/continue) so invalid JSON cannot be produced.

Comment on lines +32 to 43
noise := declaration.Noise()
if noise == nil {
// TODO: Diagnose?
return nil
}
noiseDef := noise.NoiseDefinition()
if noiseDef == nil {
// TODO: Diagnose?
return nil
}
s := traversal.ProcessDeclaration(declaration, noiseDef, scope, currentNamespace, "Noise")
if s == nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Diagnose missing noise/noiseDefinition instead of silently returning

Silent returns make failures hard to debug. Emit semantic errors when either node is absent.

Apply this diff:

-			noise := declaration.Noise()
-			if noise == nil {
-				// TODO: Diagnose?
-				return nil
-			}
-			noiseDef := noise.NoiseDefinition()
-			if noiseDef == nil {
-				// TODO: Diagnose?
-				return nil
-			}
+			noise := declaration.Noise()
+			if noise == nil {
+				scope.DiagnoseSemanticError("Missing noise in declaration", declaration)
+				return nil
+			}
+			noiseDef := noise.NoiseDefinition()
+			if noiseDef == nil {
+				scope.DiagnoseSemanticError("Missing noise definition", noise)
+				return nil
+			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
noise := declaration.Noise()
if noise == nil {
// TODO: Diagnose?
return nil
}
noiseDef := noise.NoiseDefinition()
if noiseDef == nil {
// TODO: Diagnose?
return nil
}
s := traversal.ProcessDeclaration(declaration, noiseDef, scope, currentNamespace, "Noise")
if s == nil {
noise := declaration.Noise()
if noise == nil {
scope.DiagnoseSemanticError("Missing noise in declaration", declaration)
return nil
}
noiseDef := noise.NoiseDefinition()
if noiseDef == nil {
scope.DiagnoseSemanticError("Missing noise definition", noise)
return nil
}
s := traversal.ProcessDeclaration(declaration, noiseDef, scope, currentNamespace, "Noise")
if s == nil {
🤖 Prompt for AI Agents
In lang/constructs/worldgen/noise/Noise.go around lines 32 to 43, the code
currently returns nil silently when declaration.Noise() or
noise.NoiseDefinition() is nil; change this to emit semantic errors instead of
returning silently: when noise is nil, call the project's semantic error
reporting mechanism (e.g. report/append a semantic error on scope or
currentNamespace) with a clear message like "missing Noise node on declaration";
when noiseDef is nil, emit a semantic error like "missing NoiseDefinition on
Noise node"; after reporting the error(s) return nil as before.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 35

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
lang/constructs/worldgen/surface_rules/Rule_Reference.go (1)

20-37: Avoid pointer-to-interface and unchecked type assertion in resolver

next.GetValue().(traversal.Construct) can panic. Also storing *traversal.Construct is an unnecessary pointer-to-interface. Use a safe assertion and keep Value as an interface.

@@
 type ReferenceRule struct {
 	traversal.Construct
 	Ref   *traversal.Reference
-	Value *traversal.Construct
+	Value traversal.Construct
 }
@@
 			if ref != nil {
 				ref.SetResolver(func() error {
 					if next, ok := scope.Get(*ref); ok {
-						val := next.GetValue().(traversal.Construct)
-						out.Value = &val
+						if v, ok := next.GetValue().(traversal.Construct); ok {
+							out.Value = v
+						}
 					}
 					return nil
 				})
 			}
lang/constructs/worldgen/density_functions/FunctionRef.go (1)

1-63: Use pointer grammar context types in ConstructRegistry.Register calls
Replace every reflect.TypeFor[grammar.*Context]() with reflect.TypeFor[*grammar.*Context]() in your ConstructRegistry.Register invocations (e.g. FunctionRef.go:21, Condition_AboveSurface.go:16, Rule_Block.go:16, and all other non-pointer registrations) to ensure proper context matching.

♻️ Duplicate comments (8)
lang/constructs/worldgen/density_functions/FunctionRef.go (2)

21-21: Fix registry key to pointer context type (prevents constructor dispatch).

Use the pointer type for the grammar context to match other registrations. This is a hard break otherwise.

-		reflect.TypeFor[grammar.DensityFn_ReferenceContext](),
+		reflect.TypeFor[*grammar.DensityFn_ReferenceContext](),

3-11: Harden resolver: nil scope, missing symbol, and unsafe type assertion can panic.

Add proper error returns and avoid pointer-to-interface assignment (see next comment). Also import fmt.

 import (
+	"fmt"
 	"errors"
 	"reflect"
@@
-						out.Ref.SetResolver(func() error {
-							if next, ok := scope.Get(*r); ok {
-								val := next.GetValue().(traversal.Construct)
-
-								out.Value = &val
-							}
-							return nil
-						})
+						out.Ref.SetResolver(func() error {
+							if scope == nil {
+								return fmt.Errorf("nil scope while resolving %s", r.String())
+							}
+							next, ok := scope.Get(*r)
+							if !ok {
+								return fmt.Errorf("reference not found: %s", r.String())
+							}
+							val, ok := next.GetValue().(traversal.Construct)
+							if !ok {
+								return fmt.Errorf("symbol %s is not a density function (%T)", r.String(), next.GetValue())
+							}
+							out.Value = val
+							return nil
+						})

Also applies to: 30-37

lang/constructs/worldgen/density_functions/RangeChoiceFn.go (4)

45-46: Fix diagnostic message: OutRange error mentions InRange

-							scope.DiagnoseSemanticError("Missing or invalid InRange Function", ctx)
+							scope.DiagnoseSemanticError("Missing or invalid OutRange Function", ctx)

53-58: Guard input cast to avoid panic and diagnose invalid input

-			input := densityFn.DensityFn()
-			if input == nil {
-				scope.DiagnoseSemanticError("Missing input to range choice", ctx)
-			} else {
-				out.Input = traversal.ConstructRegistry.Construct(input.(antlr.ParserRuleContext), currentNamespace, scope)
-			}
+			input := densityFn.DensityFn()
+			if input == nil {
+				scope.DiagnoseSemanticError("Missing input to range choice", ctx)
+			} else if rule, ok := input.(antlr.ParserRuleContext); ok {
+				out.Input = traversal.ConstructRegistry.Construct(rule, currentNamespace, scope)
+				if out.Input == nil {
+					scope.DiagnoseSemanticError("Invalid input density function", ctx)
+				}
+			} else {
+				scope.DiagnoseSemanticError("Invalid input node", ctx)
+			}

60-62: Defensive cast for builder child to avoid panics

-			for _, builderWrap := range densityFn.AllDensityFn_RangeChoiceBuilder() {
-				builder_chain.Invoke(rangeChoiceBuilder, builderWrap.GetChild(0).(antlr.ParserRuleContext), out, scope, currentNamespace)
-			}
+			for _, builderWrap := range densityFn.AllDensityFn_RangeChoiceBuilder() {
+				child := builderWrap.GetChild(0)
+				if child == nil {
+					scope.DiagnoseSemanticWarning("Unexpected empty builder node", builderWrap)
+					continue
+				}
+				if rule, ok := child.(antlr.ParserRuleContext); ok {
+					builder_chain.Invoke(rangeChoiceBuilder, rule, out, scope, currentNamespace)
+				} else {
+					scope.DiagnoseSemanticWarning("Unexpected builder node", builderWrap)
+				}
+			}

93-93: Validate range invariants (Min ≤ Max) before returning

-			return out
+			if out.Min > out.Max {
+				scope.DiagnoseSemanticError("Min must be less than or equal to Max for range_choice", densityFn)
+			}
+			return out
grammar/Core_Lang.g4 (1)

3-3: Break ANTLR import cycle with DensityFunctions.

Core_Lang imports DensityFunctions and defines rules that reference densityFn; if DensityFunctions also imports Core_Lang, generation will fail. Move densityFn-dependent builders into DensityFunctions and drop the import here.

-import Noise, DensityFunctions, Surface;
+import Noise, Surface;
@@
-builder_ShiftX: '.ShiftX' '(' densityFn ')';
-builder_ShiftY: '.ShiftY' '(' densityFn ')';
-builder_ShiftZ: '.ShiftZ' '(' densityFn ')';
@@
-builder_InRange: '.InRange' '(' densityFn ')';
-builder_OutRange: '.OutRange' '(' densityFn ')';

Define the removed builders in DensityFunctions.g4 where densityFn is defined.

Also applies to: 13-15, 27-28

grammar/DensityFunctions.g4 (1)

57-60: Fix empty alternative in DualInput (parsing bug).

Leading "|" introduces an empty alt, allowing "(" without "Min"/"Max".

Apply:

-densityFn_DualInput: (
-    | 'Min'
-    | 'Max'
-) NL* '(' NL* densityFn ',' NL* densityFn NL* ')';
+densityFn_DualInput:
+  ( 'Min' | 'Max' )
+  NL* '(' NL* densityFn ',' NL* densityFn NL* ')';
🧹 Nitpick comments (91)
vendor/github.com/lucasb-eyer/go-colorful/.gitignore (1)

5-8: Optional: drop .vscode exceptions to avoid editor files creeping in

The un-ignores for tasks.json/launch.json would allow those to be tracked if present under this path. Not a blocker, but if you’re curating vendor contents, consider removing the exceptions. If this mirrors upstream, keep as-is.

 .vscode/*
-!.vscode/tasks.json
-!.vscode/launch.json
vendor/github.com/mattn/go-isatty/go.test.sh (2)

6-12: Make package discovery deterministic and root-relative.

Running this script from inside vendor limits scope to that subtree. Optionally resolve and run from repo root; also avoid grep -v vendor by filtering at the Go level.

-for d in $(go list ./... | grep -v vendor); do
+repo_root=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
+cd "$repo_root"
+# Exclude vendor packages via go list pattern; quote iteration safely
+mapfile -t pkgs < <(go list ./... | grep -vE '/vendor(/|$)')
+for d in "${pkgs[@]}"; do
     go test -race -coverprofile=profile.out -covermode=atomic "$d"

1-12: Remove executable scripts from vendor
Verified that vendor/github.com/mattn/go-isatty/go.test.sh isn’t referenced by any CI or tooling (ran rg -n --hidden -C2 'go\.test\.sh|vendor/github\.com/.*/go-isatty/.*\.sh' with no matches). Relocate this script to a repo-local scripts/ directory to prevent accidental execution and keep the vendored tree strictly third-party.

vendor/github.com/gorilla/websocket/Makefile (3)

28-30: Add standard entry points (all/clean) to satisfy checkmake and improve UX

Provide "all" and "clean" targets; wire "all" to verification.

 .PHONY: verify
 verify: golangci-lint gosec govulncheck
+
+.PHONY: all clean
+all: verify
+clean:
+	rm -f coverage.coverprofile

31-34: Consider not running tests for vendored deps here

Running tests in vendor can be slow/flaky and is usually handled upstream. If you keep it, at least gate it.

-.PHONY: test
-test:
+.PHONY: vendor-test
+vendor-test:
 	@echo "##### Running tests"
 	go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./...

And avoid calling this target from top-level CI.


1-35: Confirm intent to maintain build tooling inside vendor

Files in vendor are overwritten by go mod vendor. If you need these targets long-term, move them to a top-level tooling dir and exclude ./vendor from project-wide scanners.

Happy to propose a top-level Makefile/CI job that runs these tools against your code (not vendor) and keeps versions pinned. Want me to draft it?

vendor/github.com/iancoleman/strcase/acronyms.go (3)

7-13: Normalize acronym keys to a canonical case (and document direction).

Lookups in camel.go are exact; normalizing keys prevents misses due to casing and clarifies intent that values should be lowercase “skeletons” (e.g., "ID" -> "id").

Apply:

 import (
-	"sync"
+	"strings"
+	"sync"
 )
 
 // ConfigureAcronym allows you to add additional words which will be considered acronyms
 func ConfigureAcronym(key, val string) {
-	uppercaseAcronym.Store(key, val)
+	// store with canonical uppercase key; val should be lowercase form (e.g., "ID" -> "id")
+	uppercaseAcronym.Store(strings.ToUpper(key), val)
 }

7-7: Prefer a zero-value declaration for sync.Map.

var uppercaseAcronym sync.Map is idiomatic and avoids an unnecessary composite literal.

-var uppercaseAcronym = sync.Map{}
+var uppercaseAcronym sync.Map

8-8: Clean up stray in-line example comment.

The indented //"ID": "id", is confusing beside a non-composite literal. Move it up into the doc comment for ConfigureAcronym.

vendor/github.com/iancoleman/strcase/camel.go (4)

38-41: Acronym handling only triggers on full-string matches; tokens like "user_id" won’t preserve “ID”.

Today you lookup Load(s) once. To honor acronyms at token boundaries, pre-normalize tokens with a case-insensitive map lookup, preserving separators.

Apply within this block:

-	a, hasAcronym := uppercaseAcronym.Load(s)
-	if hasAcronym {
-		s = a.(string)
-	}
+	// prefer token-level normalization; still allow full-string override
+	if a, ok := uppercaseAcronym.Load(strings.ToUpper(s)); ok {
+		s = a.(string)
+	} else {
+		s = normalizeAcronyms(s)
+	}

Add this helper in the same package (outside this function):

// normalizeAcronyms replaces tokens (split on _ . - space) using the acronym map, case-insensitively.
func normalizeAcronyms(s string) string {
	if s == "" {
		return s
	}
	var b strings.Builder
	b.Grow(len(s))
	start := 0
	for i := 0; i <= len(s); i++ {
		sep := i == len(s) || s[i] == '_' || s[i] == ' ' || s[i] == '-' || s[i] == '.'
		if sep {
			if i > start {
				tok := s[start:i]
				if a, ok := uppercaseAcronym.Load(strings.ToUpper(tok)); ok {
					tok = a.(string)
				}
				b.WriteString(tok)
			}
			if i < len(s) {
				b.WriteByte(s[i]) // keep separator
			}
			start = i + 1
		}
	}
	return b.String()
}

47-48: Avoid allocation from []byte(s) conversion.

Index the string directly; same ASCII semantics without an extra slice.

-	for i, v := range []byte(s) {
+	for i := 0; i < len(s); i++ {
+		v := s[i]

32-77: Document ASCII-only behavior and separators.

Call out that only A–Z/a–z/0–9 are handled and separators are _ - . space, which is fine but worth noting for callers expecting Unicode/other delimiters.


26-31: Vendor customization caveat.

Since this is a vendored fork with runtime configuration, consider upstreaming or wrapping to reduce churn on future go mod vendor updates.

vendor/github.com/muesli/termenv/.golangci.yml (1)

12-13: Unlimited issue caps may spam CI logs

Consider modest caps (e.g., max-issues-per-linter: 50, max-same-issues: 10) to avoid noisy CI if this file ever takes effect.

-  max-issues-per-linter: 0
-  max-same-issues: 0
+  max-issues-per-linter: 50
+  max-same-issues: 10
vendor/github.com/muesli/termenv/.golangci-soft.yml (1)

15-35: Style-heavy linters in vendor can create noise

Enabling many stylistic linters for third-party code risks distracting findings if ever scanned. Prefer excluding vendor at the root (see sibling comment) rather than tuning here.

vendor/github.com/mattn/go-runewidth/README.md (1)

27-27: Silence markdownlint for vendored docs instead of mutating upstream text

Bare URL warning (MD034) originates in vendor. Prefer ignoring vendor paths in lint config to avoid churn.

Add a repo-level config:

+--- /dev/null
++++ .markdownlint-cli2.yaml
+ignores:
+  - "vendor/**"
lang/constructs/worldgen/surface_rules/Rule_Reference.go (1)

54-65: Simplify marshaling with interface field; keep ref fallback

With Value as an interface, nil-check is enough.

 func (c *ReferenceRule) MarshalJSON() ([]byte, error) {
 	if c.Ref == nil {
 		return nil, errors.New("reference is nil in ReferenceRule")
 	}
 	if err := c.Ref.Resolve(); err != nil {
 		return nil, err
 	}
-	if c.Value == nil {
-		return c.Ref.MarshalJSON()
-	}
-	return (*c.Value).MarshalJSON()
+	if c.Value == nil {
+		return c.Ref.MarshalJSON()
+	}
+	return c.Value.MarshalJSON()
 }
vendor/github.com/gorilla/websocket/README.md (1)

13-13: Ignore markdownlint for vendor to avoid heading-level noise

MD001 comes from vendored README. Recommend excluding vendor in linter config rather than editing upstream docs.

(See suggested .markdownlint-cli2.yaml in prior comment.)

vendor/github.com/mattn/go-isatty/README.md (1)

12-30: Silence markdownlint for vendored docs instead of editing upstream content

Lint findings (tabs, fenced language, bare URL) stem from upstream README. Prefer excluding vendor from markdownlint to prevent churn.

Apply either approach:

Option A — markdownlint-cli2 config:

+ # .markdownlint-cli2.yaml
+ ignores:
+   - "vendor/**"

Option B — markdownlint ignore file:

+ # .markdownlintignore
+ vendor/**

Also applies to: 34-36, 50-50

.helix/languages.toml (2)

1-3: Prefer module-root run to avoid path fragility

Using “main.go” assumes a file at editor CWD. Running the module root is more robust.

-command = "go"
-args = ["run", "main.go", "lsp"]
+command = "go"
+args = ["run", ".", "lsp"]

5-11: Option: ship a prebuilt LSP binary for faster startup

“go run” recompiles on each launch; consider a small wrapper script or installing an mms-lsp binary on PATH to reduce editor startup latency.

vendor/github.com/lucasb-eyer/go-colorful/CHANGELOG.md (1)

38-38: Avoid linting vendor docs

Markdown link-fragment warnings in vendored CHANGELOGs are noise. Exclude vendor/** from markdownlint in CI.

Example (markdownlint-cli2 config):

+# .markdownlint-cli2.cjs
+export default {
+  "ignores": ["vendor/**"]
+}
vendor/github.com/petermattis/goid/goid_go1.5_amd64.s (1)

18-29: Drop go1.23 variant recommendation—go1.5 build tag applies to all Go ≥1.5
The existing goid_go1.5_amd64(.go/.s) files are selected by the go1.5 build tag on any Go version ≥1.5 (including Go 1.23), so no additional version‐specific files are needed. Usage of goid.Get() is limited to sasha-s/go-deadlock; you may optionally build-tag that package for debug-only builds.

vendor/github.com/gorilla/websocket/proxy.go (4)

60-66: Library code should not log close errors on error paths

Logging here is noisy and leaks into consumer logs; prefer best-effort close without logging.

Apply:

-	if err := conn.Close(); err != nil {
-		log.Printf("httpProxyDialer: failed to close connection: %v", err)
-	}
+	_ = conn.Close()

Also applies to: 71-76


37-43: Fallback if proxy host:port cannot be derived

Guard against an empty hostPort to avoid a nil-dial target.

Apply:

-	hostPort, _ := hostPortNoPort(hpd.proxyURL)
+	hostPort, _ := hostPortNoPort(hpd.proxyURL)
+	if hostPort == "" {
+		hostPort = hpd.proxyURL.Host
+	}

44-51: Consider sending Basic auth when only username is present

Many proxies accept an empty password; building "user:" can improve compatibility. If you prefer strictness, ignore.

Possible tweak:

-	if user := hpd.proxyURL.User; user != nil {
-		proxyUser := user.Username()
-		if proxyPassword, passwordSet := user.Password(); passwordSet {
-			credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
-			connectHeader.Set("Proxy-Authorization", "Basic "+credential)
-		}
-	}
+	if user := hpd.proxyURL.User; user != nil {
+		u := user.Username()
+		p, _ := user.Password() // empty if not set
+		credential := base64.StdEncoding.EncodeToString([]byte(u + ":" + p))
+		connectHeader.Set("Proxy-Authorization", "Basic "+credential)
+	}

67-69: Fix typo in comment

“becaue” → “because”.

Apply:

-	// Read response. It's OK to use and discard buffered reader here becaue
+	// Read response. It's OK to use and discard buffered reader here because
vendor/github.com/lucasb-eyer/go-colorful/README.md (1)

1-483: Exclude vendor docs from markdown lint

Third-party README triggers many markdownlint findings; exclude vendor/** to keep CI signal clean.

Add a markdownlint ignore:

+# .markdownlintignore
+vendor/**

Or in markdownlint-cli2 config:

# .markdownlint-cli2.yaml
globsIgnore:
  - "vendor/**"
vendor/github.com/mattn/go-isatty/isatty_windows.go (1)

78-83: Nit: fix typos in comments.

“undocomented” → “undocumented”; “windows Vista”/“Windows XP” capitalization for consistency.

-// getFileNameByHandle use the undocomented ntdll NtQueryObject to get file full name from file handler
-// since GetFileInformationByHandleEx is not available under windows Vista and still some old fashion
-// guys are using Windows XP, this is a workaround for those guys, it will also work on system from
+// getFileNameByHandle uses the undocumented ntdll NtQueryObject to get file full name from a file handle.
+// Since GetFileInformationByHandleEx is not available under Windows Vista and some old-fashioned
+// users are still on Windows XP, this is a workaround; it also works on systems from
vendor/github.com/iancoleman/strcase/snake.go (1)

66-115: ASCII-only semantics; consider documenting or guarding non-ASCII input.

Looping over []byte(s) and doing A–Z/a–z math assumes ASCII; multi-byte runes will be split and may produce odd results. If this is exposed to end users, either document ASCII-only support or switch to rune iteration with unicode helpers.

If you intend to support Unicode, I can draft a rune-safe version (using unicode.IsLetter/IsNumber and cases). Want me to open a follow-up?

vendor/github.com/gorilla/websocket/compression.go (4)

47-56: Guard against invalid compression levels before indexing the pool

If compressNoContextTakeover is called with an out-of-range level (e.g., handshake path not going through SetCompressionLevel), level-minCompressionLevel can index out of bounds. Add a defensive check/fallback.

Apply:

 func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser {
+	if !isValidCompressionLevel(level) {
+		level = defaultCompressionLevel
+	}
 	p := &flateWriterPools[level-minCompressionLevel]
 	tw := &truncWriter{w: w}
 	fw, _ := p.Get().(*flate.Writer)

108-123: Close underlying writer even on tail-mismatch to avoid resource leaks

On unexpected trailer bytes, Close returns early without closing w.tw.w. Prefer closing the underlying writer regardless, while preserving the most relevant error.

 func (w *flateWriteWrapper) Close() error {
   if w.fw == nil {
     return errWriteClosed
   }
   err1 := w.fw.Flush()
   w.p.Put(w.fw)
   w.fw = nil
-  if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
-    return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
-  }
-  err2 := w.tw.w.Close()
+  err2 := w.tw.w.Close()
+  if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
+    if err1 != nil {
+      return err1
+    }
+    return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
+  }
   if err1 != nil {
     return err1
   }
   return err2
 }

36-41: Avoid panic on Reset failure; propagate as error

Panicking in library code can bring down the host process. Consider returning a no-op reader with an error instead of panic(err).

 fr, _ := flateReaderPool.Get().(io.ReadCloser)
 if err := fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil); err != nil {
-    panic(err)
+    _ = fr.Close()
+    return io.NopCloser(io.MultiReader(strings.NewReader(""), strings.NewReader(""))) // or fallback to r
 }

Note: since this is vendored, only adopt if you’re intentionally diverging from upstream behavior.


139-141: Library logging can be noisy

The log.Printf on close error may spam application logs in hot paths. Consider gating logs behind a package-level logger or removing in your fork if noise becomes an issue.

vendor/github.com/gorilla/websocket/conn.go (2)

246-291: Set sane read limits at call sites

Conn defaults to unlimited message size unless SetReadLimit is called. Recommend configuring a bounded limit (e.g., 4–16 MiB) where this is used to mitigate memory DoS.

Example (outside vendor):

c.SetReadLimit(8 << 20) // 8 MiB

941-947: Close on oversize frames is correct; consider observability

When exceeding readLimit, a Close frame is sent, then ErrReadLimit returned. If you need metrics, wrap reads to count these events in your server code.

vendor/github.com/gorilla/websocket/join.go (1)

12-17: Reader UX: consider an io.ReadCloser variant

JoinMessages returns an io.Reader with no Close. Callers must stop reading to break out. If you need explicit cancellation, consider exposing a variant that also closes the underlying connection or the current inner reader on Close. Keep as a local helper outside vendor to avoid forking.

Also applies to: 25-42

vendor/github.com/gorilla/websocket/json.go (1)

49-60: ReadJSON EOF mapping is appropriate; consider wrapper if you need stricter decoding.

Mapping empty messages (EOF) to io.ErrUnexpectedEOF is sane. If you later need UseNumber/DisallowUnknownFields, prefer adding thin wrappers in your codebase rather than modifying vendor.

vendor/github.com/gorilla/websocket/client.go (2)

253-255: Nit: parameter name typo in type signature (add → addr).

Purely cosmetic; avoid touching vendor unless you’re carrying a patch anyway.

-var netDial func(network, add string) (net.Conn, error)
+var netDial func(network, addr string) (net.Conn, error)

168-176: Minor shadowing of err within TLS block; harmless but can confuse readers.

Consider avoiding short var re-declaration there in non-vendored forks.

Also applies to: 345-366

lang/File.go (2)

13-14: Storing the parse tree is useful; document lifecycle to avoid stale ASTs.

A brief comment on when Script is (re)assigned/cleared will help future maintainers.


16-29: Reset Script field before parsing
Clearing f.Script up front prevents retaining a stale script if parser.Parse errors—and is safe since f.Script is only set here.

 func (f *File) Parse() error {
-	err := f.Project.GlobalScope.PurgeFile(f.Path)
-	f.Diagnostics = make([]traversal.Diagnostic, 0)
+	// Reset per-parse state
+	f.Script = nil
+	err := f.Project.GlobalScope.PurgeFile(f.Path)
+	f.Diagnostics = make([]traversal.Diagnostic, 0)
@@
-	script, err := parser.Parse()
+	script, err := parser.Parse()
 	if err != nil {
 		return err
 	}
 	f.Script = script
vendor/github.com/muesli/termenv/termenv_posix.go (1)

10-17: Harden equality against historical IoctlGetInt big-endian quirk

On some 64-bit big-endian Linux arches, IoctlGetInt(TIOCGPGRP) used to return the PGID shifted into the upper 32 bits, causing false negatives. Casting both sides to 32 bits avoids that edge case without changing behavior elsewhere. See prior discussion in golang.org/x/sys/unix issue 60429. (github.com)

Apply within this range:

-	return pgrp == unix.Getpgrp()
+	// Compare as 32-bit pid_t width to avoid historical BE ioctl quirk.
+	return int32(pgrp) == int32(unix.Getpgrp())
vendor/github.com/muesli/termenv/hyperlink.go (1)

8-11: Guard against empty links; optionally sanitize name to prevent control-sequence injection

Emitting OSC8 with an empty link produces stray escapes; and untrusted names could inject control sequences.

 func (o *Output) Hyperlink(link, name string) string {
-	return OSC + "8;;" + link + ST + name + OSC + "8;;" + ST
+	if link == "" {
+		return name
+	}
+	// Optional: strip BEL/ESC from name to avoid terminal injection.
+	// name = strings.Map(func(r rune) rune { if r == 0x07 || r == 0x1b { return -1 }; return r }, name)
+	return OSC + "8;;" + link + ST + name + OSC + "8;;" + ST
 }

If you want the sanitization, I can wire the strings import for you.

vendor/github.com/petermattis/goid/goid.go (1)

31-35: Use a slightly larger buffer for safety

64 bytes is usually enough for the header, but 128–256 avoids edge truncation without meaningful cost.

 func getSlow() int64 {
-	var buf [64]byte
+	var buf [256]byte
 	return ExtractGID(buf[:runtime.Stack(buf[:], false)])
 }
vendor/github.com/mattn/go-runewidth/runewidth_posix.go (1)

53-56: Make locale prefix checks case-insensitive

strings.HasPrefix(locale, ...) can miss uppercase locale codes. Use a lowercased copy.

-	if max > 1 && (charset[0] != 'u' ||
-		strings.HasPrefix(locale, "ja") ||
-		strings.HasPrefix(locale, "ko") ||
-		strings.HasPrefix(locale, "zh")) {
+	lower := strings.ToLower(locale)
+	if max > 1 && (charset[0] != 'u' ||
+		strings.HasPrefix(lower, "ja") ||
+		strings.HasPrefix(lower, "ko") ||
+		strings.HasPrefix(lower, "zh")) {
 		return true
 	}
vendor/github.com/petermattis/goid/goid_go1.4.go (2)

16-17: Add modern //go:build guard alongside +build.

Future-proof the build constraints by adding the new-style guard; keep the legacy +build for backward compatibility.

+//go:build go1.4 && !go1.5
 // +build go1.4,!go1.5

22-23: Name the magic offset for clarity.

Make the 16-field offset explicit to aid future audits.

 var pointerSize = unsafe.Sizeof(uintptr(0))
+const goidFieldIndex = 16 // index of goid in runtime.G for Go 1.4

And:

- return *(*int64)(unsafe.Pointer(getg() + 16*pointerSize))
+ return *(*int64)(unsafe.Pointer(getg() + uintptr(goidFieldIndex)*pointerSize))
vendor/github.com/muesli/termenv/notification.go (1)

8-11: Sanitize/escape OSC777 args and surface write errors.

Unescaped title/body can break the control sequence (e.g., contain ';', BEL, or ST) and ignored errors hinder observability. Either sanitize inputs or document that only trusted/plain text is permitted; consider returning error from the method.

Proposed minimal change (vendor-local):

-package termenv
+package termenv
+
+import "strings"
@@
-func (o *Output) Notify(title, body string) {
-	_, _ = o.WriteString(OSC + "777;notify;" + title + ";" + body + ST)
+func (o *Output) Notify(title, body string) error {
+	t := sanitizeOSCArg(title)
+	b := sanitizeOSCArg(body)
+	_, err := o.WriteString(OSC + "777;notify;" + t + ";" + b + ST)
+	return err
 }

Add helper outside the range:

func sanitizeOSCArg(s string) string {
	// Remove ST terminators and BEL; escape semicolons to avoid field breaks.
	s = strings.ReplaceAll(s, ST, "")
	s = strings.ReplaceAll(s, "\a", "")
	s = strings.ReplaceAll(s, ";", `\;`)
	return s
}

And keep the package-level wrapper backwards compatible:

-func Notify(title, body string) {
-	output.Notify(title, body)
+func Notify(title, body string) {
+	_ = output.Notify(title, body)
 }

If you prefer not to modify vendor code, add a sanitized wrapper in our codebase and keep passing trusted strings only.

vendor/github.com/muesli/termenv/ansicolors.go (1)

23-281: Enforce palette length and immutability.

To prevent accidental edits, consider either:

  • switching to a fixed-size array [256]string, or
  • adding a length guard in init().

Option A (compile-time shape):

-var ansiHex = []string{
+var ansiHex = [...]string{
   // entries…
 }

Option B (runtime assert):

func init() {
	const want = 256
	if n := len(ansiHex); n != want {
		panic("termenv: ansiHex length mismatch; got " + strconv.Itoa(n))
	}
}

If changing vendor code is undesirable, add a unit test in our tree to assert the expected length at build time.

vendor/github.com/lucasb-eyer/go-colorful/happy_palettegen.go (1)

13-15: Optional: deterministic RNG for reproducible palettes

If reproducibility matters in tests/exports, allow injecting a *rand.Rand or seed.

I can sketch an alternative API, e.g., FastHappyPaletteWithRand(n int, r *rand.Rand).

vendor/github.com/muesli/termenv/README.md (1)

371-371: markdownlint: duplicate footnote reference definition [^vte]

There are two [^vte] definitions; markdownlint (MD053) flags this.

Prefer excluding vendor/ from markdownlint or deduplicate the footnote in this file. Example config:

+.markdownlint-cli2.yaml
+globs:
+  - "**/*.md"
+ignores:
+  - "vendor/**"
vendor/github.com/muesli/termenv/copy.go (1)

15-16: Optional: return errors instead of swallowing them

Silent failure makes troubleshooting hard, especially over SSH/muxers.

Consider CopyE/CopyPrimaryE variants returning error, and keep current as fire-and-forget wrappers.

Also applies to: 25-26

lang/traversal/Parser.go (1)

30-37: Optional: simplify nil-check and avoid unsafe cast

antlr parsers typically return non-nil contexts even on error; consider removing the nil branch or guarding the type assertion.

ctx := p.parser.Script()
sc, _ := ctx.(*grammar.ScriptContext)
return sc, nil // diagnostics listener captures errors
vendor/github.com/lucasb-eyer/go-colorful/colorgens.go (2)

20-24: Optional: add a safety cap to the retry loops

While unlikely, an infinite loop is theoretically possible; cap iterations and fall back.

-func WarmColor() (c Color) {
-	for c = randomWarm(); !c.IsValid(); c = randomWarm() {
-	}
-	return
-}
+func WarmColor() (c Color) {
+	for i := 0; i < 1024; i++ {
+		if c = randomWarm(); c.IsValid() {
+			return c
+		}
+	}
+	return Hsv(0, 0, 0.5) // neutral fallback
+}
@@
-func HappyColor() (c Color) {
-	for c = randomPimp(); !c.IsValid(); c = randomPimp() {
-	}
-	return
-}
+func HappyColor() (c Color) {
+	for i := 0; i < 1024; i++ {
+		if c = randomPimp(); c.IsValid() {
+			return c
+		}
+	}
+	return Hsv(0, 0, 0.5)
+}

Also applies to: 44-48


11-15: Optional: expose seeded variants for reproducibility

Consider FastWarmColorWithRand/HappyColorWithRand to allow tests/export stability.

I can draft minimal wrappers that take *rand.Rand without touching existing APIs.

Also applies to: 35-39

lang/constructs/worldgen/density_functions/FunctionRef.go (3)

51-54: Fix error message typo.

Class name is ReferenceFunction, not ReferenceRule.

-		return nil, errors.New("reference is nil in ReferenceRule")
+		return nil, errors.New("reference is nil in ReferenceFunction")

25-42: Confirm grammar guarantees ResourceReference is present; otherwise return early with diagnostic.

Currently we always return a non-nil construct even if Ref is nil, which guarantees a MarshalJSON error later. Prefer returning nil (or emit a diagnostic) when rr is missing.

Do you want me to wire a diagnostic into scope.Diagnostics when rr == nil and return nil here?


19-45: Add unit tests for resolution paths.

Please add tests covering: (a) missing symbol, (b) wrong symbol type, (c) successful resolution and JSON of underlying value, (d) nil rr.

I can scaffold tests under lang/constructs/worldgen/density_functions with a fake Scope/Symbol.

vendor/github.com/muesli/termenv/templatehelper.go (1)

14-16: Avoid shared mutable FuncMap in ASCII mode

Return a shallow copy to prevent accidental mutation across templates.

 func TemplateFuncs(p Profile) template.FuncMap {
 	if p == Ascii {
-		return noopTemplateFuncs
+		return cloneFuncMap(noopTemplateFuncs)
 	}
@@
 var noopTemplateFuncs = template.FuncMap{
@@
 }
+
+func cloneFuncMap(m template.FuncMap) template.FuncMap {
+	out := make(template.FuncMap, len(m))
+	for k, v := range m {
+		out[k] = v
+	}
+	return out
+}

Also applies to: 66-78

vendor/github.com/lucasb-eyer/go-colorful/hsluv.go (2)

118-126: Fix typos in doc comments

-// DistanceHSLuv calculates Euclidan distance in the HSLuv colorspace. No idea
+// DistanceHSLuv calculates Euclidean distance in the HSLuv color space. No idea
@@
-// DistanceHPLuv calculates Euclidean distance in the HPLuv colorspace. No idea
+// DistanceHPLuv calculates Euclidean distance in the HPLuv color space. No idea

Also applies to: 129-137


63-76: Optional: clamp L in HPLuvToLuvLCh for symmetry with HSLuvToLuvLCh

-	return l / 100.0, c / 100.0, h
+	return clamp01(l / 100.0), c / 100.0, h
vendor/github.com/muesli/termenv/termenv_unix.go (2)

160-167: Correct the comment: ST is ESC backslash.

Doc says “ST (ESC)”; should be “ST (ESC )”.

Also applies to: 205-209


116-138: Select timeout handling may drift after EINTR.

Select can mutate timeval; consider recomputing remaining timeout after EINTR for stricter timing. Low impact.

vendor/github.com/lucasb-eyer/go-colorful/soft_palettegen.go (3)

30-31: Typos in comment (“windows-stype”, tone).

Consider “windows-style Foo/FooEx.” Optional polish for vendored code.


121-125: Potential infinite loop if all samples marked used within an iteration.

Unlikely given len(samples) > colorsCount, but add a bounded retry or fallback to break ties.

Also applies to: 135-145


51-61: Capacity hint underestimates due to inclusive loops.

Harmless; consider +1 factors to reduce reallocs when ManySamples=false.

vendor/github.com/lucasb-eyer/go-colorful/hexcolor.go (2)

33-35: Return SQL NULL for nil receiver in Value

Prevents a panic when a nil *HexColor is used and preserves “unset” semantics as NULL.

-func (hc *HexColor) Value() (driver.Value, error) {
-	return Color(*hc).Hex(), nil
-}
+func (hc *HexColor) Value() (driver.Value, error) {
+	if hc == nil {
+		return nil, nil
+	}
+	return Color(*hc).Hex(), nil
+}

59-67: Nice: envconfig Decode helper

Useful for config ingestion. Consider also implementing encoding.TextMarshaler/TextUnmarshaler to cover more encoders (YAML/TOML). I can draft if you want.

vendor/github.com/muesli/termenv/color.go (2)

138-141: Guard against malformed xterm strings to avoid slicing panics

Add minimal validation after Split to ensure three components with length ≥2.

 	h := strings.Split(s, "/")
-	hex := fmt.Sprintf("#%s%s%s", h[0][:2], h[1][:2], h[2][:2])
+	if len(h) != 3 || len(h[0]) < 2 || len(h[1]) < 2 || len(h[2]) < 2 {
+		return RGBColor(""), ErrInvalidColor
+	}
+	hex := fmt.Sprintf("#%s%s%s", h[0][:2], h[1][:2], h[2][:2])
 	return RGBColor(hex), nil

55-68: ConvertToRGB silently ignores errors

For NoColor/unknown types we return zero color via ignored error. Consider explicit handling (return black or an error) to aid debugging.

If you want, I can scan call sites to confirm assumptions about allowed Color types.

vendor/github.com/muesli/termenv/style.go (1)

30-36: Constructor defaults to ANSI; consider profile-aware ctor

Minor: environments without color may prefer auto-detected profile (e.g., Ascii) here to avoid escape codes by default. Optional API addition: New(profile Profile, s ...string).

vendor/github.com/gorilla/websocket/server.go (1)

362-364: Fix comment typo.

“vio” → “via”.

-// writeHook is an io.Writer that records the last slice passed to it vio
+// writeHook is an io.Writer that records the last slice passed to it via
 // io.Writer.Write.
grammar/Core_Lang.g4 (3)

19-21: Consolidate Mul/MulInt to avoid duplicate constructs.

Both rules accept integers; keep one rule with number and drop the redundant Int-only variant.

-builder_Mul: '.Mul(' number ')';
-builder_MulInt: '.Mul(' Int ')';
+builder_Mul: '.Mul(' number ')';

10-10: Normalize builder naming (dot prefix).

All builders use a leading dot except Smear. Make it consistent or clarify intentional difference.

-builder_Smear: 'Smear' '(' number ')';
+builder_Smear: '.Smear' '(' number ')';

35-37: Improve literal robustness (optional).

  • Float doesn’t allow “1.”; consider supporting it.
  • String disallows escapes; consider allowing \", \\, etc.
-Float: ('-'? [0-9]* '.' [0-9]+);
+Float: '-'? ( [0-9]+ '.' [0-9]* | '.' [0-9]+ );
@@
-String: '"' ~[\r\n]* '"';
+String: '"' ( '\\' . | ~["\r\n] )* '"';
vendor/github.com/muesli/termenv/termenv.go (1)

27-39: Recognize Cygwin/MSYS terminals in TTY check.

Include IsCygwinTerminal for broader Windows shell support.

-	return isatty.IsTerminal(o.TTY().Fd())
+	fd := o.TTY().Fd()
+	return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)
grammar/DensityFunctions.g4 (5)

16-17: Rename Wierd → Weird in rule names for consistency.

Rule names use "Wierd" while the literal uses "WeirdScaledSampler". Align identifiers to avoid confusion and mismatches in generated context types.

Apply:

-    | densityFn_WierdScaledSampler
+    | densityFn_WeirdScaledSampler
-densityFn_WierdScaledSampler: 'WeirdScaledSampler' NL* '(' NL* densityFn NL* ')' NL* (densityFn_WierdScaledSamplerBuilder NL*)*;
+densityFn_WeirdScaledSampler: 'WeirdScaledSampler' NL* '(' NL* densityFn NL* ')' NL* (densityFn_WeirdScaledSamplerBuilder NL*)*;
-densityFn_WierdScaledSamplerBuilder: builder_Type1 | builder_Type2 | builder_Noise;
+densityFn_WeirdScaledSamplerBuilder: builder_Type1 | builder_Type2 | builder_Noise;

Note: This changes generated symbol names (e.g., DensityFn_WeirdScaledSamplerContext). Please update any Go code referencing the old names.

Also applies to: 71-74


8-23: Allow chaining math operations; current grammar permits only one.

If chaining like “A + B * C + D” should be valid, switch from “?” to “*” and consider precedence if needed.

Apply minimal change:

-) densityFn_Math?;
+) (NL* densityFn_Math)*;

Optionally, introduce precedence/associativity with explicit expr rules if required.

Also applies to: 93-93


78-83: Normalize NL handling inside parens for RangeChoice/Clamp.

To keep the DSL newline-agnostic, add NL* around inner tokens.

-densityFn_RangeChoice: 'RangeChoice' NL* '(' densityFn ')' NL* (densityFn_RangeChoiceBuilder NL*)*;
+densityFn_RangeChoice: 'RangeChoice' NL* '(' NL* densityFn NL* ')' NL* (densityFn_RangeChoiceBuilder NL*)*;

-densityFn_Clamp: 'Clamp' NL* '(' densityFn ')' NL* (densityFn_ClampBuilder NL*)*;
+densityFn_Clamp: 'Clamp' NL* '(' NL* densityFn NL* ')' NL* (densityFn_ClampBuilder NL*)*;

79-79: Move builder_InRange/builder_OutRange into DensityFunctions.g4

  • Remove their definitions from grammar/Core_Lang.g4 (lines 27–28).
  • In grammar/DensityFunctions.g4, alongside the other densityFn builders, add:
    builder_InRange: '.InRange' '(' NL* densityFn NL* ')';
    builder_OutRange: '.OutRange' '(' NL* densityFn NL* ')';

This ensures all densityFn-dependent builders live in one grammar and avoids a Core_Lang ↔ DensityFunctions import cycle.


46-53: No LL(*) ambiguity: alternatives are token-disjoint
The two densityFn_Noise alternatives are separated by lookahead on the next token—resourceReference requires an Identifier, while noiseDefinition starts with an Int—so the parser will always pick the correct branch and no grammar warnings should occur. (Optional: factor out the common Noise … into a shared rule in grammar/DensityFunctions.g4 for readability.)

vendor/github.com/muesli/termenv/output.go (2)

9-12: Global default Output is mutable without synchronization.

SetDefaultOutput writes a package-level var with no sync. If changed at runtime across goroutines, add sync/atomic or document single-threaded initialization only.

Also applies to: 60-62


195-197: Micro-opt: use io.StringWriter when available.

Avoid []byte allocation on WriteString.

-func (o Output) WriteString(s string) (int, error) {
-	return o.Write([]byte(s))
-}
+func (o Output) WriteString(s string) (int, error) {
+	if sw, ok := o.tty.(interface{ WriteString(string) (int, error) }); ok {
+		return sw.WriteString(s)
+	}
+	return o.Write([]byte(s))
+}
vendor/github.com/aymanbagabas/go-osc52/v2/osc52.go (1)

210-213: Doc typos.

“Settting” → “Setting”; “escapse” → “escape”.

-// Strings longer than the limit get ignored. Settting the limit to 0 or a
-// negative value disables the limit. Each terminal defines its own escapse
+// Strings longer than the limit get ignored. Setting the limit to 0 or a
+// negative value disables the limit. Each terminal defines its own escape
 // sequence limit.
vendor/github.com/mattn/go-runewidth/runewidth.go (4)

254-275: Wrap should respect grapheme clusters; current rune-based wrap can split emojis/combining marks

Use uniseg like StringWidth/Truncate to avoid breaking clusters.

Apply:

-func (c *Condition) Wrap(s string, w int) string {
-	width := 0
-	out := ""
-	for _, r := range s {
-		cw := c.RuneWidth(r)
-		if r == '\n' {
-			out += string(r)
-			width = 0
-			continue
-		} else if width+cw > w {
-			out += "\n"
-			width = 0
-			out += string(r)
-			width += cw
-			continue
-		}
-		out += string(r)
-		width += cw
-	}
-	return out
-}
+func (c *Condition) Wrap(s string, w int) string {
+	var out string
+	width := 0
+	g := uniseg.NewGraphemes(s)
+	for g.Next() {
+		cluster := g.Str()
+		// Handle explicit newlines as hard breaks.
+		if cluster == "\n" {
+			out += cluster
+			width = 0
+			continue
+		}
+		// Approximate cluster width by first non-zero rune width (same heuristic as StringWidth).
+		cw := 0
+		for _, r := range g.Runes() {
+			cw = c.RuneWidth(r)
+			if cw > 0 {
+				break
+			}
+		}
+		if width+cw > w {
+			out += "\n"
+			width = 0
+		}
+		out += cluster
+		width += cw
+	}
+	return out
+}

351-353: Fix LUT size doc typo: 557055 → 557056 bytes

Matches max/2 from 0x110000. Purely a doc nit.

Apply:

-// CreateLUT will create an in-memory lookup table of 557055 bytes for faster operation.
+// CreateLUT will create an in-memory lookup table of 557056 bytes for faster operation.

291-293: Docstring typo: FillRight says “filled in left”

Clarify direction for both method and top-level wrapper docs.

Apply:

-// FillRight return string filled in left by spaces in w cells
+// FillRight returns the string right-padded with spaces to width w (cells).

Also applies to: 346-349


155-174: Guard CreateLUT usage (thread-safety)
CreateLUT mutates Condition.combinedLut without synchronization while RuneWidth/StringWidth read it—calling CreateLUT concurrently with width calculations races. Initialize the LUT once (e.g. at startup on the main goroutine) and avoid concurrent CreateLUT calls.

vendor/github.com/lucasb-eyer/go-colorful/colors.go (1)

77-81: Doc nit: awkward wording

Tweak comment grammar for Clamped().

Apply:

-// Returns Clamps the color into valid range, clamping each value to [0..1]
-// If the color is valid already, this is a no-op.
+// Clamps the color into a valid range, restricting each channel to [0..1].
+// If the color is already valid, this is a no-op.
vendor/github.com/muesli/termenv/screen.go (1)

155-158: Doc fix: CursorForward moves horizontally, not “up”

Update comment to avoid confusion.

Apply:

-// CursorForward moves the cursor up a given number of lines.
+// CursorForward moves the cursor forward (right) by a given number of cells.

Comment thread go.mod
github.com/spf13/pflag v1.0.6 // indirect
github.com/tliron/commonlog v0.2.8 // indirect
github.com/tliron/kutil v0.3.11 // indirect
golang.org/x/crypto v0.15.0 // indirect
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Critical: golang.org/x/crypto v0.15.0 has multiple CRITICAL vulns — bump to a safe version

Static analysis flags several critical advisories on v0.15.0. Please upgrade x/crypto to the latest patched release and re-tidy.

Run to determine and apply the latest version, then verify vuln closure:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show latest available
go list -m -u -json golang.org/x/crypto | jq -r '.Path, ("current="+.Version), ("latest="+(.Update.version // "n/a"))'

# Upgrade to latest safe (edit if you must pin)
go get golang.org/x/crypto@latest
go mod tidy

# Optional: check for known vulns across deps (requires osv-scanner)
# osv-scanner --skip-git -r .

Length of output: 210


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Bypass vendor directory for upgrade check
go list -m -u -json golang.org/x/crypto -mod=mod | jq -r '.Path, ("current="+.Version), ("latest="+(.Update.version // "n/a"))'

# Upgrade to latest bypassing vendor
go get golang.org/x/crypto@latest -mod=mod
go mod tidy -mod=mod

# Optional: re-run vulnerability scan if osv-scanner is installed
# osv-scanner --skip-git -r .

Length of output: 109


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Force module mode to bypass vendor directory
export GOFLAGS=-mod=mod

# Show current and latest versions of x/crypto
go list -m -u -json golang.org/x/crypto | jq -r '.Path, ("current="+.Version), ("latest="+(.Update.version // "n/a"))'

# Upgrade to the latest version and tidy
go get golang.org/x/crypto@latest
go mod tidy

Length of output: 969


Critical: Upgrade applied but tidy failing due to missing grammar module

  • golang.org/x/crypto successfully upgraded from v0.15.0 → v0.42.0 (vulnerabilities addressed)
  • go mod tidy now errors on github.com/minecraftmetascript/mms/lang/grammar: ensure that package path exists or update the import/version for the grammar module before merging
🧰 Tools
🪛 OSV Scanner (2.1.0)

[CRITICAL] 28-28: golang.org/x/crypto 0.15.0: Man-in-the-middle attacker can compromise integrity of secure channel in golang.org/x/crypto

(GO-2023-2402)


[CRITICAL] 28-28: golang.org/x/crypto 0.15.0: Misuse of connection.serverAuthenticate may cause authorization bypass in golang.org/x/crypto

(GO-2024-3321)


[CRITICAL] 28-28: golang.org/x/crypto 0.15.0: Potential denial of service in golang.org/x/crypto

(GO-2025-3487)


[CRITICAL] 28-28: golang.org/x/crypto 0.15.0: Prefix Truncation Attack against ChaCha20-Poly1305 and Encrypt-then-MAC aka Terrapin

(GHSA-45x7-px36-x8w8)


[CRITICAL] 28-28: golang.org/x/crypto 0.15.0: golang.org/x/crypto Vulnerable to Denial of Service (DoS) via Slow or Incomplete Key Exchange

(GHSA-hcg3-q754-cr77)


[CRITICAL] 28-28: golang.org/x/crypto 0.15.0: Misuse of ServerConfig.PublicKeyCallback may cause authorization bypass in golang.org/x/crypto

(GHSA-v778-237x-gjrc)

🤖 Prompt for AI Agents
In go.mod around line 28, after upgrading golang.org/x/crypto, go mod tidy fails
because the import github.com/minecraftmetascript/mms/lang/grammar cannot be
resolved; verify that the module path and version exist (or correct the path),
then update go.mod to point to a valid module/version (or add a replace
directive to the correct local or upstream module path), run `go get
github.com/minecraftmetascript/mms@<valid-version>` (or `go mod edit
-replace=...` if using a fork/local copy), and finally run `go mod tidy` to
remove or fix broken imports; if the grammar package is no longer used, remove
its imports from source instead.

Comment on lines +88 to +89
densityFn_Spline: 'Spline' NL* (('(' NL* densityFn NL* ')' NL* (densityFn_SplinePoint NL*)* ) | '(' number ')');
densityFn_SplinePoint: '.Point' '(' number ',' (number | densityFn_Spline | resourceReference) ',' number ')';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Eliminate Spline ambiguity (both alts match Spline(number)).

As written, Spline(5) matches both alts since number ∈ densityFn_Constant. Require at least one .Point when using the densityFn alt, or drop the number-only alternative.

Apply:

-densityFn_Spline: 'Spline' NL* (('(' NL* densityFn NL* ')' NL* (densityFn_SplinePoint NL*)* ) | '(' number ')');
+densityFn_Spline:
+  'Spline' NL* (
+      '(' NL* densityFn NL* ')' NL* (densityFn_SplinePoint NL*)+
+    | '(' NL* number NL* ')'
+  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
densityFn_Spline: 'Spline' NL* (('(' NL* densityFn NL* ')' NL* (densityFn_SplinePoint NL*)* ) | '(' number ')');
densityFn_SplinePoint: '.Point' '(' number ',' (number | densityFn_Spline | resourceReference) ',' number ')';
densityFn_Spline:
'Spline' NL* (
'(' NL* densityFn NL* ')' NL* (densityFn_SplinePoint NL*)+
| '(' NL* number NL* ')'
);
🤖 Prompt for AI Agents
In grammar/DensityFunctions.g4 around lines 88-89, the Spline rule is ambiguous
because Spline(number) matches both alternatives; change the first alternative
that accepts '(' NL* densityFn NL* ')' NL* (densityFn_SplinePoint NL*)* to
require at least one .Point by making the trailing points repetition one-or-more
(i.e., change the '*' for densityFn_SplinePoint to '+') so Spline(number) no
longer matches that alt (alternatively remove the standalone '(' number ')'
alternative if you prefer the simpler syntax).

Comment on lines +13 to +17
type ReferenceFunction struct {
traversal.Construct
Ref *traversal.Reference
Value *traversal.Construct
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid pointer-to-interface; store Construct directly.

Pointers to interface are an anti-pattern and complicate JSON marshaling.

 type ReferenceFunction struct {
 	traversal.Construct
 	Ref   *traversal.Reference
-	Value *traversal.Construct
+	Value traversal.Construct
 }
@@
-	if c.Value == nil {
-		return c.Ref.MarshalJSON()
-	}
-	return (*c.Value).MarshalJSON()
+	if c.Value == nil {
+		return c.Ref.MarshalJSON()
+	}
+	return c.Value.MarshalJSON()

Also applies to: 58-61

🤖 Prompt for AI Agents
In lang/constructs/worldgen/density_functions/FunctionRef.go around lines 13-17
(and similarly for lines 58-61), the struct uses pointer-to-interface fields
which is an anti-pattern and breaks JSON marshaling; change the types to store
the interfaces directly (e.g., Ref traversal.Reference and Value
traversal.Construct) instead of *traversal.Reference and *traversal.Construct,
then update all code that constructs or accesses these fields to pass/assign the
interface value (or use & when you actually need an addressable concrete
implementation), remove or adapt nil checks to use the zero-value interface
checks, and adjust any JSON tags/encoding logic if necessary so marshaling works
with the direct interface fields.

Comment thread lang/traversal/Parser.go
Comment on lines +30 to 37
func (p *Parser) Parse() (*grammar.ScriptContext, error) {
fmt.Println("Parsing...")
if res := p.parser.Script(); res == nil {
return nil, errors.New("failed to parse")
} else {
fmt.Println("Done parsing")
return res.(*grammar.ScriptContext), nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove debug prints from library code

Stdout printing during parse is noisy and breaks CLI/editor integrations.

-func (p *Parser) Parse() (*grammar.ScriptContext, error) {
-	fmt.Println("Parsing...")
-	if res := p.parser.Script(); res == nil {
+func (p *Parser) Parse() (*grammar.ScriptContext, error) {
+	if res := p.parser.Script(); res == nil {
 		return nil, errors.New("failed to parse")
 	} else {
-		fmt.Println("Done parsing")
 		return res.(*grammar.ScriptContext), nil
 	}
 }

Also drop the unused import:

-import (
-	"errors"
-	"fmt"
-	"github.com/minecraftmetascript/mms/lang/grammar"
-	"github.com/antlr4-go/antlr/v4"
-)
+import (
+	"errors"
+	"github.com/minecraftmetascript/mms/lang/grammar"
+	"github.com/antlr4-go/antlr/v4"
+)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (p *Parser) Parse() (*grammar.ScriptContext, error) {
fmt.Println("Parsing...")
if res := p.parser.Script(); res == nil {
return nil, errors.New("failed to parse")
} else {
fmt.Println("Done parsing")
return res.(*grammar.ScriptContext), nil
}
package traversal
import (
"errors"
"github.com/minecraftmetascript/mms/lang/grammar"
"github.com/antlr4-go/antlr/v4"
)
func (p *Parser) Parse() (*grammar.ScriptContext, error) {
if res := p.parser.Script(); res == nil {
return nil, errors.New("failed to parse")
} else {
return res.(*grammar.ScriptContext), nil
}
}
🤖 Prompt for AI Agents
In lang/traversal/Parser.go around lines 30 to 37, remove the debug stdout
prints (the fmt.Println("Parsing...") and fmt.Println("Done parsing")) so
parsing is silent for library use, and remove the now-unused "fmt" import from
the file's import block; keep the error handling as-is and return the parsed
context or error as before.

Comment thread lang/traversal/Parser.go
Comment on lines +40 to +47
func (p *Parser) ExitNamespace(ctx *grammar.NamespaceContext) {
for _, contentBlockCtx := range ctx.AllContentBlocks() {
if contentBlock, ok := contentBlockCtx.(*grammar.ContentBlocksContext); ok {
inner := contentBlock.GetChild(0)
ConstructRegistry.Construct(inner.(antlr.ParserRuleContext), p.namespace, p.scope)

}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don’t assume first child is a rule; iterate rule-children safely

GetChild(0) may be a terminal; the type-assert can panic and you’ll skip additional content in the block.

 func (p *Parser) ExitNamespace(ctx *grammar.NamespaceContext) {
 	for _, contentBlockCtx := range ctx.AllContentBlocks() {
 		if contentBlock, ok := contentBlockCtx.(*grammar.ContentBlocksContext); ok {
-			inner := contentBlock.GetChild(0)
-			ConstructRegistry.Construct(inner.(antlr.ParserRuleContext), p.namespace, p.scope)
+			for i := 0; i < contentBlock.GetChildCount(); i++ {
+				if prc, ok := contentBlock.GetChild(i).(antlr.ParserRuleContext); ok {
+					ConstructRegistry.Construct(prc, p.namespace, p.scope)
+				}
+			}
 		}
 	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (p *Parser) ExitNamespace(ctx *grammar.NamespaceContext) {
for _, contentBlockCtx := range ctx.AllContentBlocks() {
if contentBlock, ok := contentBlockCtx.(*grammar.ContentBlocksContext); ok {
inner := contentBlock.GetChild(0)
ConstructRegistry.Construct(inner.(antlr.ParserRuleContext), p.namespace, p.scope)
}
}
func (p *Parser) ExitNamespace(ctx *grammar.NamespaceContext) {
for _, contentBlockCtx := range ctx.AllContentBlocks() {
if contentBlock, ok := contentBlockCtx.(*grammar.ContentBlocksContext); ok {
for i := 0; i < contentBlock.GetChildCount(); i++ {
if prc, ok := contentBlock.GetChild(i).(antlr.ParserRuleContext); ok {
ConstructRegistry.Construct(prc, p.namespace, p.scope)
}
}
}
}
}
🤖 Prompt for AI Agents
In lang/traversal/Parser.go around lines 40–47, the code assumes
contentBlock.GetChild(0) is a parser rule and type-asserts it directly, which
can panic for terminal nodes and ignores other rule-children; fix by iterating
over all children of contentBlock (0 .. GetChildCount()-1), for each child check
its dynamic type (or assert to antlr.ParserRuleContext safely) and only call
ConstructRegistry.Construct for children that are parser rule nodes, skipping
terminal nodes.

Comment on lines +153 to +158
if n == 0 {
panic("read returned no data")
}

return b[0], nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don’t panic on zero-length read; return an error.

Panic in vendored terminal IO is risky; return io.EOF instead.

-  if n == 0 {
-    panic("read returned no data")
-  }
+  if n == 0 {
+    return 0, io.EOF
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if n == 0 {
panic("read returned no data")
}
return b[0], nil
}
func (o *Output) readNextByte() (byte, error) {
if !o.unsafe {
if err := o.waitForData(OSCTimeout); err != nil {
return 0, err
}
}
var b [1]byte
n, err := o.TTY().Read(b[:])
if err != nil {
return 0, err //nolint:wrapcheck
}
if n == 0 {
return 0, io.EOF
}
return b[0], nil
}
🤖 Prompt for AI Agents
In vendor/github.com/muesli/termenv/termenv_unix.go around lines 153 to 158,
replace the panic on zero-length read with a proper error return: when n == 0
return a zero byte and io.EOF (i.e., return 0, io.EOF) instead of panicking;
ensure the io package is imported if not already.

Comment on lines +205 to +213
if oscResponse {
// OSC can be terminated by BEL (\a) or ST (ESC)
if b == BEL || strings.HasSuffix(response, string(ESC)) {
return response, true, nil
}
} else {
// cursor position response is terminated by 'R'
if b == 'R' {
return response, false, nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix OSC ST termination parsing (must be ESC backslash, not ESC alone).

Current logic returns on plain ESC and drops the trailing ‘\’, risking malformed responses and sync issues.

-    if oscResponse {
-      // OSC can be terminated by BEL (\a) or ST (ESC)
-      if b == BEL || strings.HasSuffix(response, string(ESC)) {
-        return response, true, nil
-      }
-    } else {
+    if oscResponse {
+      // OSC is terminated by BEL (\a) or ST (ESC \)
+      if b == BEL {
+        return response, true, nil
+      }
+      if b == ESC {
+        b2, err := o.readNextByte()
+        if err != nil {
+          return "", false, err
+        }
+        response += string(b2)
+        if b2 == '\\' {
+          return response, true, nil
+        }
+        // continue accumulating otherwise
+        continue
+      }
+    } else {
       // cursor position response is terminated by 'R'
       if b == 'R' {
         return response, false, nil
       }
     }
🤖 Prompt for AI Agents
In vendor/github.com/muesli/termenv/termenv_unix.go around lines 205 to 213, the
OSC termination logic currently treats a lone ESC as the ST terminator and
returns early, which drops the required trailing backslash and corrupts the
response; change the check so it only accepts the two-character ST sequence ESC
followed by '\' (i.e. verify the response ends with ESC+'\' or that the last two
bytes are ESC and backslash) and only then return the full response, true, nil;
keep the BEL handling as-is and do not alter the cursor-position ('R') branch.

Comment on lines +260 to +265
// first, send OSC query, which is ignored by terminal which do not support it
fmt.Fprintf(tty, OSC+"%d;?"+ST, sequence)

// then, query cursor position, should be supported by all terminals
fmt.Fprintf(tty, CSI+"6n")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Check write errors when emitting OSC/CSI.

If writes fail, subsequent reads will block until timeout.

- fmt.Fprintf(tty, OSC+"%d;?"+ST, sequence)
+ if _, err := fmt.Fprintf(tty, OSC+"%d;?"+ST, sequence); err != nil {
+   return "", fmt.Errorf("%s: %w", ErrStatusReport, err)
+ }
 
- fmt.Fprintf(tty, CSI+"6n")
+ if _, err := fmt.Fprintf(tty, CSI+"6n"); err != nil {
+   return "", fmt.Errorf("%s: %w", ErrStatusReport, err)
+ }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In vendor/github.com/muesli/termenv/termenv_unix.go around lines 260 to 265, the
two fmt.Fprintf calls that emit the OSC and CSI sequences do not check their
returned error values; if the writes fail, subsequent reads can block until
timeout. Update both writes to capture the (n, err) result and handle err
immediately (e.g., return the error or propagate it to the caller) instead of
proceeding to the read step; ensure any partial writes are treated as failures
and you do not attempt the blocking read when an error was returned from the
write calls.

Comment on lines +28 to +34
func Get() int64 {
// The goid is the 16th field in the G struct where each field is a
// pointer, uintptr or padded to that size. See runtime.h from the
// Go sources. I'm not aware of a cleaner way to determine the
// offset.
return *(*int64)(unsafe.Pointer(getg() + 16*pointerSize))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Confirm necessity of vendoring Go 1.4 runtime backdoor.

This unsafe, runtime-layout-dependent code is only for go1.4. If the project doesn’t support Go 1.4, consider dropping this file from the vendor set to reduce risk surface and maintenance.


🏁 Script executed:

#!/bin/bash
# Verify supported Go versions to justify keeping go1.4-specific vendor code.
if fd go.mod >/dev/null 2>&1; then
  echo "go.mod:"
  cat go.mod | sed -n '1,80p' | sed -n '/^module/p;/^go [0-9]\+\.[0-9]\+/p;/^toolchain /p'
else
  echo "No go.mod found; check CI config or build scripts for Go version pins."
fi

Length of output: 57


Drop Go 1.4–specific vendored file
The module’s go.mod specifies Go 1.23, so vendor/github.com/petermattis/goid/goid_go1.4.go will never be built. Remove that file to reduce maintenance and minimize unsafe, runtime‐layout–dependent code.

🤖 Prompt for AI Agents
In vendor/github.com/petermattis/goid/goid_go1.4.go around lines 28 to 34: this
file is a Go 1.4-specific, unsafe runtime-layout dependent implementation that
is never built because go.mod targets Go 1.23; delete the file from the
repository (git rm vendor/github.com/petermattis/goid/goid_go1.4.go), update the
vendor tree (regenerate vendor with go mod vendor or run the repo's standard
vendoring step), run go vet/go build to confirm no remaining references, and
commit the removal to avoid maintaining obsolete unsafe code.

Comment on lines +24 to +29
func ExtractGID(s []byte) int64 {
s = s[len("goroutine "):]
s = s[:bytes.IndexByte(s, ' ')]
gid, _ := strconv.ParseInt(string(s), 10, 64)
return gid
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Harden parsing to avoid panics on malformed input

ExtractGID assumes a well-formed runtime.Stack header; malformed input panics on slicing. Return a sentinel on failure.

 func ExtractGID(s []byte) int64 {
-	s = s[len("goroutine "):]
-	s = s[:bytes.IndexByte(s, ' ')]
-	gid, _ := strconv.ParseInt(string(s), 10, 64)
-	return gid
+	const prefix = "goroutine "
+	if !bytes.HasPrefix(s, []byte(prefix)) {
+		return -1
+	}
+	s = s[len(prefix):]
+	i := bytes.IndexByte(s, ' ')
+	if i < 0 {
+		return -1
+	}
+	gid, err := strconv.ParseInt(string(s[:i]), 10, 64)
+	if err != nil {
+		return -1
+	}
+	return gid
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func ExtractGID(s []byte) int64 {
s = s[len("goroutine "):]
s = s[:bytes.IndexByte(s, ' ')]
gid, _ := strconv.ParseInt(string(s), 10, 64)
return gid
}
func ExtractGID(s []byte) int64 {
const prefix = "goroutine "
if !bytes.HasPrefix(s, []byte(prefix)) {
return -1
}
s = s[len(prefix):]
i := bytes.IndexByte(s, ' ')
if i < 0 {
return -1
}
gid, err := strconv.ParseInt(string(s[:i]), 10, 64)
if err != nil {
return -1
}
return gid
}
🤖 Prompt for AI Agents
In vendor/github.com/petermattis/goid/goid.go around lines 24 to 29, ExtractGID
slices and indexes the input without validating it, which can panic on malformed
runtime.Stack output; change it to defensively validate before slicing: check
that the input is at least len("goroutine ")+1 bytes, verify bytes.IndexByte(s,
' ') returns >=0, use those safe indices to slice, handle strconv.ParseInt
errors and return a sentinel value (e.g. -1) on any failure instead of ignoring
errors; ensure no panics by guarding every slice/index operation and returning
the sentinel on validation or parse failure.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
flake.nix (2)

94-95: Bug: copying “.*” will match “.” and “..” and can fail or behave unpredictably.

Replace the two cp lines with a single robust copy that includes dotfiles.

-            cp $src/wasm/* $out/js -r
-            cp $src/wasm/.* $out/js -r
+            cp -r "$src/wasm/." "$out/js"

24-26: Align grammar directory references
The rule at lines 24–26 (and similarly at 54–57) points to grammars/Main_Lexer.g4, but that file isn’t present (only grammar/MinecraftMetascript.g4 exists). Update these paths to match the grammar/ directory or add the missing grammars/ files to prevent build failures.

🧹 Nitpick comments (1)
flake.nix (1)

69-69: Version bump is fine; dedupe the literal by introducing a single version var.

Avoid drift between default and wasm. Add a let-bound mmsVersion and reference it in both places.

Apply this diff within the selected lines:

-          version = "0.3.2";
+          version = mmsVersion;
-          version = "0.3.2";
+          version = mmsVersion;

Add this binding outside the selected ranges (near the existing let):

let
  mmsVersion = "0.3.2";
  pkgs = import nixpkgs { inherit system; };in

Also applies to: 82-82

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d2e7a7 and 770715e.

📒 Files selected for processing (3)
  • flake.nix (2 hunks)
  • grammar/Core_Lang.g4 (1 hunks)
  • grammar/DensityFunctions.g4 (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • grammar/DensityFunctions.g4
  • grammar/Core_Lang.g4

@ItsMeBrianD ItsMeBrianD merged commit 770715e into main Sep 11, 2025
1 check passed
@coderabbitai coderabbitai bot mentioned this pull request Oct 11, 2025
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