Skip to content

Run Unity 2D projects as Codename One applications - #5976

Open
shai-almog wants to merge 34 commits into
masterfrom
unity-compat
Open

shai-almog wants to merge 34 commits into
masterfrom
unity-compat

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Summary

A Unity 2D project (C# scripts, scenes, prefabs, sprites, project settings) dropped into common/src/main/unity, or imported with cn1:import-unity-project, builds unmodified as a Codename One application for every target. No Unity player and no .NET runtime ship with the app.

  • maven/cil-translator: reads the assembly the C# compiler produced and writes one class file per C# type; compiles scenes, prefabs, animator controllers, clips, tile maps and particle systems to reflection-free Java (UnityAppImpl).
  • maven/unity-compat: the runtime. A .NET base library in Java, the UnityEngine API over com.codename1.gaming (2D rendering, Box2D physics with exact casts and overlap queries, Animator, tile maps, particles, uGUI, touch, audio, PlayerPrefs, Resources), plus our own implementations of the documented TextMesh Pro and Cinemachine API.
  • Build wiring: UnityProjectBuilder in build-engine, the compile-unity and import-unity-project Maven goals, the Gradle compileUnity task, the archetype profile, and release publishing behind -Dunity-compat (the modules need the .NET SDK; the framework build does not).
  • Java interop: UnityApplication (createForm, onProjectInstalled, onStarted, callInFrame), so a game sits inside a Codename One form and uses the rest of the framework.
  • Docs: a new developer guide chapter, Unity-Interop.asciidoc.

Clean room: no Unity source, assemblies or package source were read or shipped; the stub UnityEngine.dll is compiled from our own sources.

Changes outside the new modules

Bugs this work uncovered, fixed at the root, each with a regression test:

  • ParparVM JavaScript: wide stack shuffles (POP2/DUP2/DUP_X2), MULTIANEWARRAY dimension pops, a call result compared via FCMP/DCMP evaluated more than once, SWAP losing the lower value.
  • ParparVM native: arraycopy/clone of an array of primitive arrays took the primitive path.
  • GameView: a key repeat released a held key after 800 ms; a focused hosted child kept the multi-touch arrays from the view.
  • Box2D: chain against chain threw in World.popContact; the broad phase allocated a merge buffer every step above 31 pairs (now an in-place sort, checked against Arrays.sort over randomized pair sets).

Behavior changes that affect every application:

  • Native builds compile generated C with -ffp-contract=off.
  • The JavaScript target rounds every float result with Math.fround.

Both make float arithmetic match the JVM bit for bit. Their performance cost on ordinary applications has not been measured.

Verification

  • Seven in-tree samples (scripts/unity-compat-samples) produce byte-identical traces on the JVM, ParparVM native and JavaScript (maven/integration-tests/unity-compat-test.sh --parparvm).
  • Five open-source Unity games build unmodified with identical traces on all three targets: Fruitopia, Asteroids, Pong, Snake, Sokoban. They are not in the tree.
  • cil-translator and unity-compat: SpotBugs 0, Checkstyle 0, PMD forbidden list clean, cast-semantics clean.
  • Core unit tests: 7,702 run, 1 failure (MapViewMarkerLabelTest timeout on a loaded machine; passes alone).
  • Developer guide gates pass locally (asciidoctor, Vale, LanguageTool, snippets, xrefs).

Not verified

  • .github/workflows/unity-compat.yml and the release.yml changes have never run on CI.
  • Nothing has run on a real device; audio, sign-in and share from the bridge samples are untested there.
  • The Java samples in the guide are copies of code that ran, but docs CI does not compile them.
  • The tree was rebased onto current master after the last local test run.
  • Core GameInput still reads edge flags that a press landing mid-frame can miss on ports that render off the EDT; left for a separate decision.

🤖 Generated with Claude Code

A Unity project's Assets and ProjectSettings in common/src/main/unity build
unmodified: the .NET SDK compiles the C# scripts, maven/cil-translator turns
the assembly into Java class files and compiles every scene, prefab,
animator controller, clip, tile map and particle system to reflection-free
Java, and maven/unity-compat is the runtime they run against (a .NET base
library, the UnityEngine API over com.codename1.gaming, and our own
TextMesh Pro and Cinemachine implementations). Maven (compile-unity,
import-unity-project) and Gradle (compileUnity) share UnityProjectBuilder.
Both modules sit behind the unity-compat profile and are published by the
release.

Float results have to match C# on every target, so native builds get
-ffp-contract=off and the JavaScript target rounds float results with
Math.fround.

Bugs this uncovered and fixes outside the new modules, each with a test:
- ParparVM JavaScript: wide stack shuffles, MULTIANEWARRAY dimension pops,
  a call result compared with FCMP/DCMP evaluated more than once, SWAP
  losing the lower value.
- ParparVM native: arraycopy/clone of an array of primitive arrays took
  the primitive path.
- GameView: a key repeat released a held key; a focused hosted child kept
  the multi-touch arrays from the view.
- Box2D: chain against chain threw in World.popContact; the broad phase
  allocated a merge buffer on every step above 31 pairs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@shai-almog

shai-almog commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 193 screenshots: 193 matched.
Native Windows port, REAL shipping pipeline: the hellocodenameone screenshot suite rendered by a binary CROSS-COMPILED on Linux (clang-cl + xwin, WebView2 linked) and RUN on a Windows x64 runner. Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 30ms / native 2ms = 15.0x speedup
SIMD float-mul (64K x300) java 31ms / native 2ms = 15.5x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 38.000 ms
Base64 CN1 decode 45.000 ms
Base64 SIMD encode 51.000 ms
Base64 encode ratio (SIMD/CN1) 1.342x (34.2% slower)
Base64 SIMD decode 46.000 ms
Base64 decode ratio (SIMD/CN1) 1.022x (2.2% slower)
Image encode benchmark iterations 100
Image createMask (SIMD off) 4.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.750x (25.0% faster)
Image applyMask (SIMD off) 10.000 ms
Image applyMask (SIMD on) 17.000 ms
Image applyMask ratio (SIMD on/off) 1.700x (70.0% slower)
Image modifyAlpha (SIMD off) 18.000 ms
Image modifyAlpha (SIMD on) 12.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.667x (33.3% faster)
Image modifyAlpha removeColor (SIMD off) 18.000 ms
Image modifyAlpha removeColor (SIMD on) 13.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.722x (27.8% faster)

@shai-almog

shai-almog commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 178 screenshots: 178 matched.

Native Android coverage

  • 📊 Line coverage: 17.38% (30557/175811 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 15.68% (144310/920428), branch 10.63% (9945/93555), complexity 8.21% (7424/90392), method 11.99% (5144/42889), class 20.77% (1060/5104)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • com.codename1.androidcompat.rt.com.codename1.androidcompat.rt.TextXmlPullParser – 0.00% (0/623 lines covered)
      • com.codename1.androidcompat.androidx.constraintlayout.core.widgets.com.codename1.androidcompat.androidx.constraintlayout.core.widgets.Flow – 0.00% (0/576 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 17.38% (30557/175811 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 15.68% (144310/920428), branch 10.63% (9945/93555), complexity 8.21% (7424/90392), method 11.99% (5144/42889), class 20.77% (1060/5104)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • com.codename1.androidcompat.rt.com.codename1.androidcompat.rt.TextXmlPullParser – 0.00% (0/623 lines covered)
      • com.codename1.androidcompat.androidx.constraintlayout.core.widgets.com.codename1.androidcompat.androidx.constraintlayout.core.widgets.Flow – 0.00% (0/576 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 327ms / native 212ms = 1.5x speedup
SIMD float-mul (64K x300) java 257ms / native 165ms = 1.5x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 96.000 ms
Base64 CN1 decode 83.000 ms
Base64 native encode 441.000 ms
Base64 encode ratio (CN1/native) 0.218x (78.2% faster)
Base64 native decode 308.000 ms
Base64 decode ratio (CN1/native) 0.269x (73.1% faster)
Image encode benchmark status skipped (SIMD unsupported)

@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ✅ ByteCodeTranslator: 0 findings (no issues)
    • ✅ android: 0 findings (no issues)
    • ✅ android-compat: 0 findings (no issues)
    • ✅ android-res-compiler: 0 findings (no issues)
    • ✅ backend: 0 findings (no issues)
    • ✅ backend-test: 0 findings (no issues)
    • ✅ build-engine: 0 findings (no issues)
    • ✅ build-hint-catalog: 0 findings (no issues)
    • ✅ build-hint-tools: 0 findings (no issues)
    • ✅ codenameone-gradle-plugin: 0 findings (no issues)
    • ✅ codenameone-maven-plugin: 0 findings (no issues)
    • ✅ core-unittests: 0 findings (no issues)
    • ✅ ios: 0 findings (no issues)
    • ✅ javac: 0 findings (no issues)
    • ✅ project-model: 0 findings (no issues)
  • ✅ PMD: 0 findings (no issues) [Report archive]
  • ✅ Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@shai-almog

shai-almog commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 193 screenshots: 193 matched.
Native Windows port (x64 / Intel-AMD): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, SSE2 SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 44ms / native 3ms = 14.6x speedup
SIMD float-mul (64K x300) java 44ms / native 2ms = 22.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 60.000 ms
Base64 CN1 decode 73.000 ms
Base64 SIMD encode 91.000 ms
Base64 encode ratio (SIMD/CN1) 1.517x (51.7% slower)
Base64 SIMD decode 82.000 ms
Base64 decode ratio (SIMD/CN1) 1.123x (12.3% slower)
Image encode benchmark iterations 100
Image createMask (SIMD off) 6.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.500x (50.0% faster)
Image applyMask (SIMD off) 16.000 ms
Image applyMask (SIMD on) 21.000 ms
Image applyMask ratio (SIMD on/off) 1.313x (31.3% slower)
Image modifyAlpha (SIMD off) 24.000 ms
Image modifyAlpha (SIMD on) 17.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.708x (29.2% faster)
Image modifyAlpha removeColor (SIMD off) 31.000 ms
Image modifyAlpha removeColor (SIMD on) 24.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.774x (22.6% faster)

ParparVM vs HotSpot (JDK 25): Windows x64

Runner CPU: AMD64 Family 25 Model 17 Stepping 1, AuthenticAMD (baseline windows-x64@amd64-family-25-model-17-authenticamd)

Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 15% (time) / 15% (RAM) away from its baseline in vm/selfhost/perf-baseline/ fails: above it is a regression, below it an improvement that has to be rebaselined (a row whose calibration runs were noisier carries a wider tolerance), and for RAM the change must also exceed 0.05x in absolute terms. Both run unpinned on all of the runner's CPUs, with their own default thread counts.

Benchmark Cores Time RAM Status
hello (WinHelloMain) 4 1.44x (base 1.38x, +5.0%) 0.82x (base 0.83x, -1.1%) ok
translator (self) 4 0.63x (base 0.70x, -10.1%) 0.55x (base 0.55x, +0.5%) ok
intArithmetic 4 1.10x (base 1.10x, +0.4%) 0.05x (base 0.06x, -3.0%) ok
longArithmetic 4 1.08x (base 1.08x, +0.0%) 0.05x (base 0.06x, -1.0%) ok
mathTranscendental 4 0.85x (base 0.85x, +0.2%) 0.06x (base 0.06x, +1.7%) ok
arraySequential 4 1.74x (base 1.72x, +1.0%) 0.38x (base 0.38x, +0.1%) ok
arrayRandom 4 1.02x (base 1.01x, +0.7%) 0.23x (base 0.23x, +0.1%) ok
objectAllocation 4 1.31x (base 1.25x, +4.5%) 0.29x (base 0.29x, -2.4%) ok
valueEscape 4 0.10x (base 0.10x, -1.3%) 0.04x (base 0.04x, -0.3%) ok
hashMapChurn 4 1.30x (base 1.39x, -6.8%) 0.10x (base 0.10x, +3.9%) ok
stringBuilding 4 1.38x (base 1.36x, +2.0%) 0.27x (base 0.28x, -4.3%) ok
recursion 4 1.38x (base 1.43x, -3.3%) 0.06x (base 0.06x, +0.7%) ok
quicksort 4 1.08x (base 1.11x, -3.0%) 0.12x (base 0.12x, -0.4%) ok

Result: no regression

@shai-almog

shai-almog commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 193 screenshots: 193 matched.
Native Linux port (x64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub x64 runner. Baseline: scripts/linux/screenshots.

ParparVM vs HotSpot (JDK 25): Linux x64

Runner CPU: AMD EPYC 9V74 80-Core Processor (baseline linux-x64@amd-epyc-9v74-80-core-processor)

Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 15% (time) / 15% (RAM) away from its baseline in vm/selfhost/perf-baseline/ fails: above it is a regression, below it an improvement that has to be rebaselined (a row whose calibration runs were noisier carries a wider tolerance), and for RAM the change must also exceed 0.05x in absolute terms. Both run unpinned on all of the runner's CPUs, with their own default thread counts.

Benchmark Cores Time RAM Status
hello (LinuxHelloMain) 4 1.07x (base 0.90x, +19.7%) 0.90x (base 0.96x, -6.6%) ok
translator (self) 4 0.62x (base 0.63x, -0.9%) 0.46x (base 0.46x, -1.0%) ok
intArithmetic 4 1.10x (base 1.10x, -0.0%) 0.05x (base 0.06x, -2.0%) ok
longArithmetic 4 1.08x (base 1.08x, +0.0%) 0.07x (base 0.06x, +35.4%) ok
mathTranscendental 4 1.09x (base 1.09x, -0.1%) 0.08x (base 0.07x, +13.1%) ok
arraySequential 4 2.63x (base 2.81x, -6.3%) 0.36x (base 0.35x, +0.3%) ok
arrayRandom 4 1.06x (base 1.02x, +3.2%) 0.21x (base 0.21x, -1.6%) ok
objectAllocation 4 1.20x (base 1.21x, -0.1%) 0.33x (base 0.39x, -14.5%) ok
valueEscape 4 0.10x (base 0.10x, -0.0%) 0.04x (base 0.06x, -23.5%) ok
hashMapChurn 4 1.07x (base 1.10x, -2.7%) 0.11x (base 0.10x, +8.6%) ok
stringBuilding 4 1.48x (base 1.49x, -0.3%) 0.26x (base 0.26x, +0.6%) ok
recursion 4 1.24x (base 1.24x, +0.0%) 0.05x (base 0.06x, -2.3%) ok
quicksort 4 1.07x (base 1.07x, +0.0%) 0.11x (base 0.11x, -0.3%) ok

Result: no regression

@shai-almog

shai-almog commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 193 screenshots: 193 matched.
Native Linux port (arm64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub arm64 runner. Baseline: scripts/linux/screenshots-arm.

ParparVM vs HotSpot (JDK 25): Linux arm64

Runner CPU: Neoverse-N2 (baseline linux-arm64@neoverse-n2)

Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 15% (time) / 15% (RAM) away from its baseline in vm/selfhost/perf-baseline/ fails: above it is a regression, below it an improvement that has to be rebaselined (a row whose calibration runs were noisier carries a wider tolerance), and for RAM the change must also exceed 0.05x in absolute terms. Both run unpinned on all of the runner's CPUs, with their own default thread counts.

Benchmark Cores Time RAM Status
hello (LinuxHelloMain) 4 1.01x (base 0.93x, +8.9%) 0.95x (base 0.87x, +9.5%) ok
translator (self) 4 0.75x (base 0.70x, +8.0%) 0.48x (base 0.45x, +7.2%) ok
intArithmetic 4 1.04x (base 1.04x, -0.0%) 0.03x (base 0.03x, -0.6%) ok
longArithmetic 4 0.79x (base 0.79x, -0.1%) 0.03x (base 0.03x, -2.2%) ok
mathTranscendental 4 1.10x (base 1.10x, +0.3%) 0.03x (base 0.03x, +2.3%) ok
arraySequential 4 0.38x (base 0.36x, +6.2%) 0.37x (base 0.37x, +0.1%) ok
arrayRandom 4 0.94x (base 0.94x, +0.0%) 0.20x (base 0.20x, -0.0%) ok
objectAllocation 4 1.92x (base 1.77x, +8.6%) 0.36x (base 0.37x, -1.5%) ok
valueEscape 4 0.51x (base 0.51x, -0.1%) 0.03x (base 0.03x, -3.2%) ok
hashMapChurn 4 0.83x (base 0.84x, -1.2%) 0.12x (base 0.12x, +0.7%) ok
stringBuilding 4 1.35x (base 1.36x, -0.5%) 0.26x (base 0.26x, -0.0%) ok
recursion 4 1.41x (base 1.43x, -1.6%) 0.03x (base 0.04x, -1.8%) ok
quicksort 4 1.01x (base 0.99x, +1.3%) 0.09x (base 0.09x, +0.8%) ok

Result: no regression

@shai-almog

shai-almog commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 193 screenshots: 193 matched.
Native Windows port (arm64 / Apple Silicon - Arm): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, NEON SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 50ms / native 3ms = 16.6x speedup
SIMD float-mul (64K x300) java 49ms / native 3ms = 16.3x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 60.000 ms
Base64 CN1 decode 70.000 ms
Base64 SIMD encode 78.000 ms
Base64 encode ratio (SIMD/CN1) 1.300x (30.0% slower)
Base64 SIMD decode 70.000 ms
Base64 decode ratio (SIMD/CN1) 1.000x (0.0% slower)
Image encode benchmark iterations 100
Image createMask (SIMD off) 6.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.500x (50.0% faster)
Image applyMask (SIMD off) 19.000 ms
Image applyMask (SIMD on) 20.000 ms
Image applyMask ratio (SIMD on/off) 1.053x (5.3% slower)
Image modifyAlpha (SIMD off) 18.000 ms
Image modifyAlpha (SIMD on) 12.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.667x (33.3% faster)
Image modifyAlpha removeColor (SIMD off) 21.000 ms
Image modifyAlpha removeColor (SIMD on) 13.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.619x (38.1% faster)

ParparVM vs HotSpot (JDK 25): Windows arm64

Runner CPU: ARMv8 (64-bit) Family 8 Model D49 Revision 0, MICROSOFT CORPORATION (baseline windows-arm64@armv8-64-bit-family-8-model-d49-microsoft-corporation)

Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 15% (time) / 15% (RAM) away from its baseline in vm/selfhost/perf-baseline/ fails: above it is a regression, below it an improvement that has to be rebaselined (a row whose calibration runs were noisier carries a wider tolerance), and for RAM the change must also exceed 0.05x in absolute terms. Both run unpinned on all of the runner's CPUs, with their own default thread counts.

Benchmark Cores Time RAM Status
hello (WinHelloMain) 4 1.10x (base 1.11x, -1.0%) 1.30x (base 1.26x, +3.4%) ok
translator (self) 4 0.84x (base 0.84x, +0.7%) 0.48x (base 0.51x, -6.7%) ok
intArithmetic 4 1.04x (base 1.04x, -0.1%) 0.06x (base 0.06x, +1.2%) ok
longArithmetic 4 0.79x (base 0.80x, -0.2%) 0.06x (base 0.06x, +1.6%) ok
mathTranscendental 4 0.74x (base 0.72x, +3.1%) 0.06x (base 0.06x, +0.2%) ok
arraySequential 4 0.37x (base 0.46x, -19.4%) 0.39x (base 0.39x, +0.0%) ok
arrayRandom 4 0.94x (base 0.94x, +0.0%) 0.23x (base 0.23x, +0.0%) ok
objectAllocation 4 1.74x (base 2.81x, -37.9%) 0.33x (base 0.43x, -23.6%) ok
valueEscape 4 0.76x (base 0.76x, +0.1%) 0.05x (base 0.05x, +2.0%) ok
hashMapChurn 4 1.02x (base 0.98x, +3.3%) 0.12x (base 0.12x, -3.4%) ok
stringBuilding 4 1.52x (base 1.43x, +6.2%) 0.27x (base 0.27x, -0.6%) ok
recursion 4 1.49x (base 1.46x, +1.5%) 0.06x (base 0.07x, -0.6%) ok
quicksort 4 1.01x (base 1.00x, +0.5%) 0.12x (base 0.12x, -0.1%) ok

Result: no regression

@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T10:08:32.557504Z e6598ef Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • ✅ Tests: 875 total, 0 failed, 57 skipped

Benchmark Results

  • Execution Time: 9091 ms

  • Hotspots (Top 20 sampled methods):

    • 12.48% java.lang.Object.hashCode (173 samples)
    • 10.39% com.codename1.tools.translator.IteratorEscape.mangle (144 samples)
    • 6.49% java.util.ArrayList.indexOf (90 samples)
    • 6.20% java.lang.System.identityHashCode (86 samples)
    • 4.11% com.codename1.tools.translator.BytecodeMethod.equals (57 samples)
    • 3.61% com.codename1.tools.translator.JavascriptReachability.enqueueResolved (50 samples)
    • 3.25% java.util.AbstractCollection.containsAll (45 samples)
    • 2.60% java.lang.StringBuilder.append (36 samples)
    • 1.59% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (22 samples)
    • 1.44% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (20 samples)
    • 1.44% java.io.FileOutputStream.writeBytes (20 samples)
    • 1.44% java.util.HashMap.hash (20 samples)
    • 1.30% com.codename1.tools.translator.NativeSymbolIndex.<init> (18 samples)
    • 1.30% java.util.HashMap.putVal (18 samples)
    • 1.30% com.codename1.tools.translator.BytecodeMethod.optimize (18 samples)
    • 1.15% com.codename1.tools.translator.classfile.analysis.Analyzer.analyze (16 samples)
    • 1.08% com.codename1.tools.translator.bytecodes.Invoke.findMethodUp (15 samples)
    • 1.01% java.io.FileOutputStream.open0 (14 samples)
    • 1.01% com.codename1.tools.translator.Parser.cullMethods (14 samples)
    • 0.94% com.codename1.tools.translator.IteratorEscape.ctorOnlyStoresParamsIntoThis (13 samples)
  • ⚠️ Coverage report not generated.

Static Analysis

  • ✅ SpotBugs: no findings (report was not generated by the build).
  • ⚠️ PMD report not generated.
  • ⚠️ Checkstyle report not generated.

Generated automatically by the PR CI workflow.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5639acb341

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +102 to +104
UnityRuntime.begin();

view = new UnityGameView();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Install view services before waking the scene

Calling UnityRuntime.begin() before the UnityGameView is created and initialized runs every script's Awake/OnEnable while PlayerPrefs still uses its transient in-memory fallback and AudioSource has no output. Consequently, saved preferences read in Awake appear missing, writes made there disappear once the store is installed, and every scene AudioSource with Play On Awake marks itself playing without ever sending audio to the device. Initialize these view-backed services before waking the first scene.

Useful? React with 👍 / 👎.

Comment on lines +92 to +93
if (negative) {
value = -value;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve Int32.MinValue as a valid parse result

For the valid input "-2147483648", negating the scanned magnitude produces Long.MIN_VALUE, which is also used as the invalid-input sentinel. Parse therefore throws FormatException, and TryParse returns false, instead of producing Integer.MIN_VALUE. Use a distinct parse-status indicator so the minimum Int32 remains representable.

Useful? React with 👍 / 👎.

Comment on lines +1539 to +1540
c.resumeAt = 0f;
c.phase = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Defer null yields until the following frame

When StartCoroutine is called from Start or Update, it immediately advances the iterator and adds it with phase == 0 and resumeAt == 0; the same frame's later runCoroutines(0) then advances it again. Thus a first yield return null resumes in the current frame rather than the next frame as promised, so initialization or frame-based loops can execute twice in one frame. Record the frame at which a null/default yield may next resume.

Useful? React with 👍 / 👎.

Comment on lines +86 to +87
public static float get_timeSinceLevelLoad() {
return time;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reset timeSinceLevelLoad when changing scenes

get_timeSinceLevelLoad() returns the application-wide Time.time, but changeScene() never resets that clock. After loading a second scene, scripts therefore see time since application startup rather than time since the current scene was loaded, which breaks scene-local timers and spawn schedules. Track a separate scene-load timestamp or reset a dedicated level clock on every load.

Useful? React with 👍 / 👎.

Comment on lines +51 to +52
public static boolean get_isMobilePlatform() {
return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Report mobile platforms on device builds

Application.isMobilePlatform is hard-coded to false, including when the generated application runs on Android or iOS. Unity scripts commonly use this property to enable touch controls, mobile layouts, or device-specific behavior, so those paths remain disabled on the very device builds this compatibility layer targets. Have the host install or derive the actual platform value instead of returning a constant.

Useful? React with 👍 / 👎.

Comment on lines 147 to +148
xvfb-run -a mvn deploy -Psign-artifacts -Dgpg.passphrase=$MAVEN_GPG_PASSPHRASE \
-Plocal-dev-javase -Darchetype.test.skip=true -DskipTests \
-Plocal-dev-javase -Dunity-compat -Darchetype.test.skip=true -DskipTests \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Version the profile-only modules before release deployment

The release step first runs update-version.sh, whose mvn versions:set invocation does not activate unity-compat, and only activates this profile in the later deploy command. These two modules are therefore absent from the version-update reactor and retain their explicit 8.0-SNAPSHOT parent and artifact versions; once the root POM has the tag version, activating them for deploy produces a parent-version mismatch or publishes under snapshot coordinates instead of the tag. Run the version update with this profile active or explicitly update these two POMs before deployment.

Useful? React with 👍 / 👎.

Comment on lines +1026 to +1027
mv.visitJumpInsn(IFNE, ok);
invoke(INVOKESTATIC, Names.INTEROP, "divideByZero", "()V", false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject signed division overflow before emitting JVM division

The generated check covers only a zero divisor. For int.MinValue / -1 and long.MinValue / -1, CIL signed division must throw OverflowException, while JVM IDIV/LDIV silently returns the minimum value. Translated C# therefore continues with a corrupt arithmetic result and cannot catch the expected exception; add the minimum-value/negative-one check before emitting the division opcode.

Useful? React with 👍 / 👎.

Comment on lines +60 to +61
public static int Abs(int v) {
return v < 0 ? -v : v;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Throw on Math.Abs of the minimum signed value

System.Math.Abs(int.MinValue) must throw OverflowException, because the positive magnitude is not representable, but negating it here wraps and returns the same negative value; the long overload has the same defect. Scripts using the documented System.Math surface can therefore propagate a negative value from an operation guaranteed to be nonnegative. Detect the minimum values and throw the runtime's OverflowException before negating.

Useful? React with 👍 / 👎.

shai-almog and others added 2 commits October 9, 2026 19:55
… perf baseline

On the Ubuntu runner dotnet is linked into /usr/bin, so dropping every PATH
directory that holds it also dropped env, bash and mvn. The directory is now
shadowed by links to everything in it except dotnet.

objectAllocation RAM on Neoverse-N2 is bimodal; the row is recalibrated from
this run and three master runs with a wider RAM tolerance. No improvement is
claimed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review findings (rationale for the ones left alone is in comments at the
code concerned):
- Install the view's services (PlayerPrefs store, audio, platform) before
  the first scene wakes.
- A coroutine started from Start or Update resumes its first null yield on
  the next frame.
- Time.timeSinceLevelLoad restarts on a scene load.
- Application.platform and isMobilePlatform come from the host platform.
- Signed division and remainder of MinValue by -1 throw OverflowException,
  as does System.Math.Abs of MinValue.

JavaScript screenshots: XYChart rotated the canvas and rotated it back,
which with float-rounded angles leaves a transform 1e-14 off identity, and
the JavaScript port draws DOM text only for an exact identity. XYChart and
ChartComponent now restore the transform they were handed, and the port's
DOM text decision tolerates that residue.

A differential fixture against the JVM found four older JavaScript target
bugs: int to float/double read an un-normalized int, float/double to int
wrapped instead of saturating, a static read before its first write was
undefined, and Float.toString printed the double.

The offscreen check now runs on every scene sample in the integration
test. It found: a flick release delivered at the press point because
CodenameOneImplementation aliased the shared pointer arrays, GameView
missing single finger presses after the first drag, and a sub-image cache
keyed by corner only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e numeric fixture

The JavaScript target now truncates a float product the way the JVM does, so
the six theme screens that size from one move by a pixel; the goldens are this
run's CI captures. JsNumericDifferentialApp compares narrowing compound
assignments against the JVM on purpose and joins the other compiler fixtures
CodeQL does not read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

shai-almog commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 214 screenshots: 214 matched.
✅ JavaScript-port screenshot tests passed.

…lLoad

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

shai-almog commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 171 screenshots: 171 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 175 screenshots: 175 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 535 seconds

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 66ms / native 2ms = 33.0x speedup
SIMD float-mul (64K x300) java 77ms / native 2ms = 38.5x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 99.000 ms
Base64 CN1 decode 124.000 ms
Base64 native encode 857.000 ms
Base64 encode ratio (CN1/native) 0.116x (88.4% faster)
Base64 native decode 466.000 ms
Base64 decode ratio (CN1/native) 0.266x (73.4% faster)
Base64 SIMD encode 108.000 ms
Base64 encode ratio (SIMD/CN1) 1.091x (9.1% slower)
Base64 SIMD decode 81.000 ms
Base64 decode ratio (SIMD/CN1) 0.653x (34.7% faster)
Base64 encode ratio (SIMD/native) 0.126x (87.4% faster)
Base64 decode ratio (SIMD/native) 0.174x (82.6% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 19.000 ms
Image createMask (SIMD on) 16.000 ms
Image createMask ratio (SIMD on/off) 0.842x (15.8% faster)
Image applyMask (SIMD off) 57.000 ms
Image applyMask (SIMD on) 62.000 ms
Image applyMask ratio (SIMD on/off) 1.088x (8.8% slower)
Image modifyAlpha (SIMD off) 59.000 ms
Image modifyAlpha (SIMD on) 42.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.712x (28.8% faster)
Image modifyAlpha removeColor (SIMD off) 57.000 ms
Image modifyAlpha removeColor (SIMD on) 66.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.158x (15.8% slower)

@shai-almog

shai-almog commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 236 screenshots: 236 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@shai-almog

shai-almog commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 176 screenshots: 176 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 1883 seconds

Build and Run Timing

Metric Duration
Simulator Boot 98000 ms
Simulator Boot (Run) 1000 ms
App Install 15000 ms
App Launch 1000 ms
Test Execution 445000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 115ms / native 4ms = 28.7x speedup
SIMD float-mul (64K x300) java 104ms / native 4ms = 26.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 71.000 ms
Base64 CN1 decode 73.000 ms
Base64 native encode 637.000 ms
Base64 encode ratio (CN1/native) 0.111x (88.9% faster)
Base64 native decode 408.000 ms
Base64 decode ratio (CN1/native) 0.179x (82.1% faster)
Base64 SIMD encode 64.000 ms
Base64 encode ratio (SIMD/CN1) 0.901x (9.9% faster)
Base64 SIMD decode 88.000 ms
Base64 decode ratio (SIMD/CN1) 1.205x (20.5% slower)
Base64 encode ratio (SIMD/native) 0.100x (90.0% faster)
Base64 decode ratio (SIMD/native) 0.216x (78.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 5.000 ms
Image createMask (SIMD on) 2.000 ms
Image createMask ratio (SIMD on/off) 0.400x (60.0% faster)
Image applyMask (SIMD off) 73.000 ms
Image applyMask (SIMD on) 59.000 ms
Image applyMask ratio (SIMD on/off) 0.808x (19.2% faster)
Image modifyAlpha (SIMD off) 53.000 ms
Image modifyAlpha (SIMD on) 86.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.623x (62.3% slower)
Image modifyAlpha removeColor (SIMD off) 155.000 ms
Image modifyAlpha removeColor (SIMD on) 162.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.045x (4.5% slower)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8a51fcb4b1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +87 to +88
public static boolean Any(IEnumerable_1 source, Class t) {
return walk(source).MoveNext();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Dispose enumerators used by LINQ terminal operators

When the source is a custom enumerator or a compiler-generated iterator with a finally block, terminal operations that stop early—such as this Any, First, Contains, ElementAt, and Take—abandon the enumerator without calling Dispose(), even though IEnumerator_1 implements IDisposable. The iterator's cleanup therefore never runs after an early result or predicate exception; wrap enumeration in try/finally and dispose the enumerator on every exit path.

Useful? React with 👍 / 👎.

Comment on lines +94 to +98
public static Object GetValues(Type enumType) {
Type t = enumType(enumType);
int[] out = new int[t.values.length];
for (int i = 0; i < out.length; i++) {
out[i] = (int) t.values[i];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return arrays matching the enum's underlying type

For any enum whose underlying type is not int—for example, enum E : long { Huge = 1L << 40 }—Enum.GetValues always returns an int[] and truncates every member. Translated callers cast the result to the enum's normalized JVM array type (for this example, long[]), so the usual (E[])Enum.GetValues(typeof(E)) either fails with a cast error or receives corrupted values. Construct the primitive array corresponding to the enum's recorded underlying type instead.

Useful? React with 👍 / 👎.

Comment on lines +100 to +104
for (int i = 1; i < out.length; i++) {
int v = out[i];
int j = i - 1;
while (j >= 0 && out[j] > v) {
out[j + 1] = out[j];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Sort enum values by unsigned magnitude

When an enum contains negative members, such as the common flags value All = -1, this signed comparison places that member before zero. Enum.GetValues orders members by the unsigned magnitude of their binary representation, so { All = -1, None = 0, One = 1 } must enumerate as None, One, All, whereas this code returns All, None, One. Use an unsigned comparison appropriate to the enum's underlying width.

Useful? React with 👍 / 👎.

Comment on lines +139 to +143
} else if (c == '}') {
if (i + 1 < n && format.charAt(i + 1) == '}') {
i++;
}
sb.append('}');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject unmatched closing braces in String.Format

For a malformed composite format such as "value }", .NET throws FormatException, but this branch appends the unmatched brace and returns normally. This changes control flow for scripts that validate or recover from user-provided format strings; only a doubled }} should emit a literal closing brace, while a standalone } must be rejected.

Useful? React with 👍 / 👎.

Comment on lines +55 to +59
public static Type $of(Class type, String fullName) {
Type t = (Type) byClass.get(type);
if (t == null) {
t = new Type(fullName, type, null, null, false);
byClass.put(type, t);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep primitive System.Type instances distinct

The cache key is only the JVM Class, but multiple distinct .NET primitives share one box class: int and uint both use Integer.class, long and ulong use Long.class, and byte and sbyte use Byte.class. Consequently, whichever type is requested first is returned for both, making typeof(int) == typeof(uint) true and potentially reporting the wrong FullName. Include the .NET type identity in the cache key rather than keying solely by the erased Java class.

Useful? React with 👍 / 👎.

Comment on lines +43 to +45
static long number(Object value) {
if (value instanceof EnumBox) {
return ((EnumBox) value).value;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve enum type identity in reflective operations

This conversion discards the EnumBox.type, so APIs such as Enum.IsDefined, Enum.GetName, HasFlag, and CompareTo accept a boxed member of an unrelated enum and operate only on its numeric value. For example, firstEnum.HasFlag(secondEnum) can return true when their bits overlap, whereas .NET throws ArgumentException for differing enum types. Validate the box's enum type against the requested or receiver type before extracting its number.

Useful? React with 👍 / 👎.

Comment on lines +230 to +232
public void set_time(float value) {
startedAt = Time.unscaledTime - value;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Seek the audio output when setting AudioSource.time

When a playing source executes audio.time = value, this setter only changes the runtime's bookkeeping and never seeks the device's Media, so the audible clip continues from its old position while scripts observe a different position. The bookkeeping is also immediately wrong when pitch != 1, because get_time() multiplies elapsed time by pitch but this setter does not divide the requested clip position by pitch. Add a seek operation to AudioOutput and derive startedAt consistently with the playback rate.

Useful? React with 👍 / 👎.

Comment on lines +360 to +362
public static String Trim(String s) {
return s.trim();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Trim the full .NET whitespace set

String.trim() only removes characters at or below U+0020, whereas System.String.Trim() removes Unicode whitespace. Inputs surrounded by characters such as non-breaking space (U+00A0) or em space (U+2003) therefore remain untrimmed, even though this runtime already recognizes them in Char_.IsWhiteSpace. Scan the ends using the .NET whitespace predicate instead of delegating to Java's trim().

Useful? React with 👍 / 👎.

Comment on lines +121 to +128
String wanted = value.trim();
for (int i = 0; i < t.names.length; i++) {
if (ignoreCase ? t.names[i].equalsIgnoreCase(wanted) : t.names[i].equals(wanted)) {
return new EnumBox(t, t.values[i]);
}
}
try {
return new EnumBox(t, Long.parseLong(wanted));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Parse comma-separated enum member names

Enum.Parse accepts a comma-separated list of member names, which is the standard way to parse flags such as "Read, Write"; this implementation only checks the entire string against one member and then attempts numeric parsing. Valid combined values therefore throw ArgumentException. Split on commas, trim each name, resolve each with the requested case sensitivity, and combine their underlying values.

Useful? React with 👍 / 👎.

Comment on lines +57 to +60
if (t instanceof NegativeArraySizeException) {
return new OverflowException();
}
return t;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Wrap Java argument errors for translated catch blocks

Several public runtime APIs throw java.lang.IllegalArgumentException for invalid C# calls, including PlayerPrefs, SceneManager, Input, physics queries, and Instantiate, but this dispatcher leaves that throwable unchanged. Because generated catch clauses test against the custom System.Exception hierarchy, even catch (Exception) cannot catch these failures and the raw JVM exception escapes the script. Throw the corresponding compatibility exception at each API or map Java argument exceptions here.

Useful? React with 👍 / 👎.

@shai-almog

shai-almog commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 176 screenshots: 176 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 1407 seconds

Build and Run Timing

Metric Duration
Simulator Boot 54000 ms
Simulator Boot (Run) 1000 ms
App Install 14000 ms
App Launch 5000 ms
Test Execution 433000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 64ms / native 4ms = 16.0x speedup
SIMD float-mul (64K x300) java 56ms / native 3ms = 18.6x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 77.000 ms
Base64 CN1 decode 65.000 ms
Base64 native encode 712.000 ms
Base64 encode ratio (CN1/native) 0.108x (89.2% faster)
Base64 native decode 843.000 ms
Base64 decode ratio (CN1/native) 0.077x (92.3% faster)
Base64 SIMD encode 58.000 ms
Base64 encode ratio (SIMD/CN1) 0.753x (24.7% faster)
Base64 SIMD decode 51.000 ms
Base64 decode ratio (SIMD/CN1) 0.785x (21.5% faster)
Base64 encode ratio (SIMD/native) 0.081x (91.9% faster)
Base64 decode ratio (SIMD/native) 0.060x (94.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 6.000 ms
Image createMask (SIMD on) 4.000 ms
Image createMask ratio (SIMD on/off) 0.667x (33.3% faster)
Image applyMask (SIMD off) 54.000 ms
Image applyMask (SIMD on) 63.000 ms
Image applyMask ratio (SIMD on/off) 1.167x (16.7% slower)
Image modifyAlpha (SIMD off) 44.000 ms
Image modifyAlpha (SIMD on) 21.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.477x (52.3% faster)
Image modifyAlpha removeColor (SIMD off) 66.000 ms
Image modifyAlpha removeColor (SIMD on) 49.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.742x (25.8% faster)

…seline

Base library: LINQ operators dispose their enumerators on every exit, Enum
values keep their underlying type and unsigned order, Enum.Parse takes comma
lists, HasFlag/CompareTo/IsDefined refuse another enum's member, String.Format
rejects a lone closing brace, Trim follows Char.IsWhiteSpace, typeof(int) and
typeof(uint) are distinct, a float key's two zeros are one key, and a Java
exception thrown by the runtime is caught by a script's catch (Exception).
AudioSource.time seeks the device's media and accounts for pitch.

The macOS draw-gradient golden is this branch's CI capture: with
-ffp-contract=off the mutable-image ellipse edge now equals the on-screen one.

Linux x64 perf baseline rows recalibrated per runner CPU from PR and master
runs; the shift is present on master at the base commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f0abf10218

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +931 to +934
Image image = (Image) paintedTexts.get(key);
if (image == null) {
image = paint(d, width, height);
paintedTexts.put(key, image);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Bound the cache for dynamically changing text

When a Text component displays a changing value such as a timer, score, or frame counter, every distinct string creates an Image and stores it permanently in paintedTexts; the map is never cleared or evicted. A frequently updated label therefore retains thousands of raster images and can exhaust device memory during an otherwise normal game session. Keep only current/recent entries or use a bounded cache.

Useful? React with 👍 / 👎.

Comment on lines +1578 to +1582
if (yielded instanceof WaitForSeconds) {
c.resumeAt = Time.time + ((WaitForSeconds) yielded).seconds;
} else if (yielded instanceof Coroutine) {
c.waitingFor = (Coroutine) yielded;
} else if (yielded instanceof WaitForFixedUpdate) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Execute yielded IEnumerator instances before resuming parents

When a coroutine uses the common yield return ChildRoutine() form, the yielded value is an IEnumerator, but this chain recognizes only a started Coroutine; it consequently treats the child iterator like null, never executes it, and resumes the parent on the next frame. Add nested-enumerator handling so the parent waits while the yielded iterator is driven to completion.

Useful? React with 👍 / 👎.

Comment on lines +69 to +70
public static float get_unscaledDeltaTime() {
return unscaledDeltaTime;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return the fixed unscaled step during FixedUpdate

When Time.unscaledDeltaTime is read from FixedUpdate, Unity reports the unscaled duration of that fixed step, but this getter always returns the enclosing render frame's dt. At nonmatching render/physics rates—or when several fixed steps run in one frame—fixed-update timers and movement therefore advance by the render delta once per physics step, producing speed and scheduling errors.

Useful? React with 👍 / 👎.

Comment on lines +572 to +575
if (f.isDirectory()) {
collectStamped(f, prefix + f.getName() + "/", out);
} else {
out.add(prefix + f.getName() + ":" + f.length() + ":" + f.lastModified());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include file contents in the Unity build digest

In the new Unity incremental build cache, a source or asset rewritten with the same byte length and preserved modification time—such as after restoring or synchronizing files with timestamps intact—produces the same stamp even if its contents changed. run() then accepts state.txt and skips compilation, leaving stale translated classes or resources in the application; hash the file bytes rather than only name, size, and timestamp.

Useful? React with 👍 / 👎.

Comment on lines +39 to +44
public List_1() {
items = new Object[4];
}

public List_1(int capacity) {
items = new Object[capacity < 4 ? 4 : capacity];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Implement the IEnumerable List constructor

When valid C# uses new List<T>(source)—including the new List<int>(numbers) array case explicitly anticipated by ArrayView—the translator emits a call to the erased List_1(IEnumerable_1) constructor, but this runtime class exposes only the parameterless and capacity constructors. The Unity project therefore fails translation with a missing runtime method instead of constructing the list; add the enumerable constructor and dispose its enumerator after copying.

Useful? React with 👍 / 👎.

Comment on lines +850 to +855
String simple = meta.getName().substring(0, meta.getName().length() - ".cs.meta".length());
for (CilAssembly assembly : scripts) {
for (TypeDef t : assembly.types()) {
String full = t.fullName();
if (full.equals(simple) || full.endsWith("." + simple)) {
scriptsByGuid.put(guid, t);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resolve same-named scripts by their source file

When a valid Unity project contains two scripts with the same filename in different folders and namespaces, each .cs.meta GUID is mapped by simple class name and this loop always selects the first matching TypeDef. Both GUIDs can therefore resolve to the same script class, causing scene objects to receive the wrong behaviour and serialized fields from the other script to be dropped. Use source/PDB information or another unambiguous file-to-type mapping.

Useful? React with 👍 / 👎.

Comment on lines +733 to +738
GameObject go = (GameObject) objects.get(i);
if (go.destroyed || !go.activeInHierarchy()) {
continue;
}
if ((name != null && !go.name.equals(name)) || (tag != null && !go.tag.equals(tag))) {
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.

P2 Badge Traverse hierarchy paths in GameObject.Find

When a script calls GameObject.Find("Canvas/Panel/Button"), Unity interprets the slash-separated argument as a hierarchy path, but this implementation compares the whole argument only against each object's leaf name. Such lookups always return null unless an object literally contains slashes in its name, breaking a standard way scripts locate nested scene objects; resolve the root and traverse children as the existing Transform.Find implementation does.

Useful? React with 👍 / 👎.

Comment on lines +204 to +208
public java.lang.Object GetComponentInChildren(Class type) {
if (!active) {
return null;
}
java.lang.Object c = GetComponent(type);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Search the inactive root in child component queries

When GetComponentInChildren is invoked on an inactive GameObject, Unity still searches that root object even though the one-argument overload excludes its inactive descendants, but this early return skips the root as well. Managers querying an inactive prefab or temporarily disabled object therefore receive null despite the requested component being attached directly to it; GetComponentsInChildren has the same issue through collect, so both traversals should always inspect their starting object.

Useful? React with 👍 / 👎.

Comment on lines +62 to +64
public static boolean IsDigit(char c) {
return c >= '0' && c <= '9';
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize Unicode decimal digits in Char.IsDigit

For decimal digits outside ASCII, such as Arabic-Indic ٣ or full-width 5, System.Char.IsDigit must return true because they have the Unicode decimal-digit category, but this implementation accepts only 0 through 9. Translated validation and parsing code therefore rejects digit strings that work in .NET; use a Unicode digit-category check rather than an ASCII range.

Useful? React with 👍 / 👎.

@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 86a52fd46d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +197 to +198
cw.visitField(ACC_PUBLIC | (f.isStatic() ? ACC_STATIC : 0), fieldName, names.descriptor(f.type), null,
null).visitEnd();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep static state separate for each closed generic type

Because all constructed generic types are erased to one JVM class, emitting an ordinary Java static field here makes instantiations such as Cache<int> and Cache<string> share the same storage and static initializer. Writing Cache<int>.Value can therefore change Cache<string>.Value, silently corrupting common generic singleton, cache, and registry patterns; specialize static storage by closed type or reject generic types with static state rather than sharing it.

Useful? React with 👍 / 👎.

Comment on lines +155 to +158
public static String GetName(Type enumType, Object value) {
Type t = enumType(enumType);
int at = t.indexOf(t.held(number(value)));
return at < 0 ? null : t.names[at];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject mismatched values passed to Enum.GetName

When value is another enum type or an integer of a different width, this unconditionally extracts and truncates its number, so calls such as Enum.GetName(typeof(ByteEnum), (int)1) can return a member instead of throwing. .NET requires the value to be the requested enum type or exactly its underlying type, otherwise it raises ArgumentException, so translated exception handling and validation diverge; apply the same type checks already used by IsDefined before looking up the number. See the Enum.GetName contract.

Useful? React with 👍 / 👎.

Comment on lines +1838 to +1839
if (yielded instanceof WaitForSeconds) {
c.resumeAt = Time.time + ((WaitForSeconds) yielded).seconds;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Start WaitForSeconds timing at the end of the frame

When WaitForSeconds is yielded from Start, FixedUpdate, or a collision callback, Time.time has not yet been advanced for the current frame, so this deadline counts the current frame's duration toward the wait. A coroutine started in Start during a 0.1-second frame with a one-second wait can therefore resume after only about 0.9 seconds following that frame, and multiple fixed steps make the error larger. Unity starts this wait at the end of the current frame, so defer establishing the deadline until then or account for the pending frame time. See the WaitForSeconds timing contract.

Useful? React with 👍 / 👎.

Comment on lines +241 to +245
if (playing && clip != null) {
startedFrom = position();
startedAt = paused ? pausedAt : now();
}
pitch = value;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply pitch changes to an active audio voice

When a script changes source.pitch after Play(), this updates only the runtime's playback clock; AudioOutput is never notified, so the audible voice continues at its original pitch while time, looping, and expiration are calculated at the new speed. Games that animate pitch therefore hear no change and can see isPlaying become false before or after the sound actually ends. Add an output operation that updates the active voice's playback rate when this setter runs, as required by Unity's AudioSource.pitch behavior.

Useful? React with 👍 / 👎.

lastMouseY = mouseY;
// GetAxis eases towards the key's value at Unity's default rate of
// three units a second, and snaps through zero on a reversal.
float step = 3f * dt;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor key-axis gravity and sensitivity settings

When a key-driven axis in InputManager.asset uses non-default gravity or sensitivity, the generated axis retains only its key mappings and this hard-coded step makes both acceleration and recentering run at three units per second. Input.GetAxis therefore responds at the wrong rate in projects that tune those fields, affecting movement and control feel; carry each axis's configured gravity and sensitivity into the runtime instead. The Unity Input Manager manual defines these fields as the rates toward the target and neutral values.

Useful? React with 👍 / 👎.

if (clip.loop) {
float b = (from - at) / length;
float a = (until - at) / length;
due = floor(a) > floor(b) || (from == 0f && at == 0f);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fire events for every crossed animation loop

When one animator tick spans more than one loop because of a short clip, high animator speed, or a long frame, this boolean only records that at least one event boundary was crossed and invokes the event once. The additional loop occurrences are lost even though Unity processes the event once per animation loop, so event-driven attacks, sounds, and counters under-fire during frame stalls; count the crossed boundaries and invoke the event for each one. See Unity's AnimationEvent documentation.

Useful? React with 👍 / 👎.

…, events

Review findings:

- A static field of a generic type was one Java static for every
  instantiation, so Singleton<GameManager> and Singleton<AudioManager>
  shared their instance. Each constructed type now gets its own static
  context (GenericStatics): a subclass per closed type whose class
  initializer runs the type's static constructor once, as .NET does, passed
  to static methods as a hidden last parameter and kept by instances. Forms
  that cannot be keyed fail the build naming the site.
- Time.time advances at the start of the frame, so a WaitForSeconds,
  Invoke or delayed Destroy begun in Start or a fixed step no longer counts
  the frame it was started in.
- Enum.GetName compares the value at full width instead of truncating it to
  the enum's storage.
- Key axes take gravity, sensitivity and snap from InputManager.asset.
- An animation event fires once per loop a tick crosses, in order.
- AudioSource stops every voice it started; a pitch the device cannot apply
  is a build note.

Found on the way: `instance == null` inside a generic type constrained to a
class failed bytecode verification.

The macOS translator RAM row is rebaselined from this PR's runs and four
master controls: master measured 0.68 and 0.55 on two runs of one commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 01ce0c0887

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +2430 to +2431
if (erased && declared.kind == CilType.Kind.SZARRAY && Names.isTypeVariable(declared.element)
&& s.kind == CilType.Kind.SZARRAY && names.norm(s.element).isPrimitive()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Support primitive arrays in generic CopyTo calls

When translated code calls an exposed generic member such as new List<int> { 1 }.CopyTo(new int[1]), this condition unconditionally raises TranslationException, so an ordinary supported List<T> operation prevents the Unity project from building. Add a primitive-array bridge that copies/unboxes the erased elements into the caller's array, analogous to the conversion already performed for generic array return values.

Useful? React with 👍 / 👎.

}

public static String New(char c, int count) {
StringBuilder sb = new StringBuilder(count);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject negative string repetition counts explicitly

When translated code executes new string(c, count) with a negative runtime count, StringBuilder throws NegativeArraySizeException, which Interop.wrap converts to OverflowException; .NET instead requires ArgumentOutOfRangeException for this constructor. Validate count before allocating so translated catch blocks observe the expected exception.

Useful? React with 👍 / 👎.

Comment on lines +125 to +126
if (looping.containsKey(key)) {
play(key.intValue(), clip, volume, 1f, true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve volume changes across emulated audio loops

When a looped source changes volume or mute after playback begins, AudioSource updates the current voice through output.volume, but this restart uses the original volume captured by the first play call. At the next loop boundary the sound therefore jumps back to its initial level (and a muted source can become audible again); retain the current per-voice volume and use it when restarting the media.

Useful? React with 👍 / 👎.

Comment on lines +1079 to +1081
if (p.contact == contact) { // NOPMD CompareObjectsWithEquals
p.contact = null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Retain an active contact for multi-fixture pairs

When two colliders have multiple simultaneous fixture contacts, as with a decomposed polygon or composite collider, ending the contact currently stored in p.contact clears it even if p.count shows that another contact remains. Subsequent OnCollisionStay2D callbacks are built with a null contact and therefore report zero contact points and no normal while the colliders are still touching, which can break grounding and collision-response scripts; retain or select one of the surviving contacts until the pair fully separates.

Useful? React with 👍 / 👎.

Review findings:

- A primitive array passed where a generic member takes T[]
  (List<int>.CopyTo(int[])) failed the build. It is passed as an array of
  the right boxes and copied back after the call; List<T>.CopyTo gains its
  four-argument overload. A constructor now runs the after-call steps a
  method does.
- new string(c, negative) throws ArgumentOutOfRangeException, and the
  string members around it throw the exception types .NET does.
- A looped sound keeps its current volume, and its mute, across the
  restart at each loop.
- A collider pair touching through several contacts keeps reporting one
  of the survivors to OnCollisionStay2D until it separates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 002982eac3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return minInclusive;
}
long r = next() & 0xffffffffL;
return (int) (lo + r % span);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Sample integer ranges without modulo bias

When the requested span does not divide 2^32, reducing one unsigned 32-bit sample with % span makes some results more likely than others. For example, with Range(0, 1500000000), values below 1294967296 have three possible source samples while the remaining values have only two, so individual low values are 50% more likely; this can visibly skew procedural selection. Use rejection sampling or another unbiased bounded-integer method.

Useful? React with 👍 / 👎.

Comment on lines +502 to +503
if (a instanceof Integer) {
return Int32_.CompareTo(((Integer) a).intValue(), ((Integer) b).intValue());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Throw ArgumentException for mismatched comparable values

When translated code invokes non-generic IComparable.CompareTo with a non-null value of the wrong runtime type, such as ((IComparable)1).CompareTo("x"), these casts throw Java ClassCastException; Interop.wrap then exposes that as InvalidCastException. The .NET primitive and string implementations instead throw ArgumentException, so translated catch clauses take the wrong path. Validate that b has the same boxed type before dispatching.

Useful? React with 👍 / 👎.

Comment on lines +499 to +500
if (b == null) {
return 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Check the comparable receiver before the null argument

When both operands are null, as in IComparable value = null; value.CompareTo(null), MethodTranslator rewrites the interface call to this static helper and this early return answers 1 before the receiver check at the end. An instance call on a null receiver must throw NullReferenceException; check a before applying the rule that a valid comparable sorts after a null argument.

Useful? React with 👍 / 👎.

Comment on lines +2113 to +2116
|| name.equals("System.Collections.Generic.ICollection`1")
|| name.equals("System.Collections.Generic.IList`1")
|| name.equals("System.Collections.Generic.IReadOnlyCollection`1")
|| name.equals("System.Collections.Generic.IReadOnlyList`1")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Implement collection interfaces on array views

When an array is assigned or passed as ICollection<T> (for example, ICollection<int> values = new int[3]), this branch deliberately replaces it with ArrayView, but ArrayView implements only IEnumerable_1. A subsequent values.Count call is therefore emitted as an ICollection_1 interface invocation on an object that does not implement that interface and fails at runtime with IncompatibleClassChangeError; IList<T> and the read-only interfaces listed here have the same problem. Either make the view implement the advertised interfaces with array-appropriate behavior or do not claim these conversions are supported.

Useful? React with 👍 / 👎.

// that keys overlap, the display takes the release of any key but
// the last two pressed for a stray one and drops it, and the key
// it belonged to stays down for good.
Display.getInstance().setMultiKeyMode(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore the global multi-key mode when the view detaches

When a UnityGameView is embedded in a larger Codename One application, initializing it unconditionally changes Display's process-wide multiKeyMode, and deinitialize() never restores the previous value. After navigating away, ordinary/native UI therefore continues using the alternate key-release behavior that Display explicitly does not enable by default because it complicates native navigation. Record whether this view enabled the mode and restore it on detach, without disabling a mode that was already owned by the host.

Useful? React with 👍 / 👎.

Comment on lines +247 to +248
if (now != null && recorded != null && now.digest.equals(recorded.digest) && staged.isDirectory()
&& appImpl.isFile()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Rebuild when auxiliary Unity cache directories are missing

When a partial build cache retains state.txt, the staged classes, and UnityAppImpl.java but loses target/unity/resources or target/unity/runtime, this condition treats the cache as complete. A missing resources directory makes install() remove previously installed images, while a missing runtime directory prevents it from filtering runtime-owned value classes out of the staged output, so the resulting package can lack assets or contain duplicate runtime definitions. Require both auxiliary directories to exist before taking the unchanged-project path.

Useful? React with 👍 / 👎.

Comment on lines +267 to +268
while (t != null) {
java.lang.Object c = t.gameObject.GetComponent(type);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip inactive parents in component lookups

When an inactive parent has the requested component, this unconditional walk returns it from the no-argument GetComponentInParent<T>(). Unity's default overload searches the object itself regardless of state but includes only active parent GameObjects; inactive parents are included only by the separate includeInactive overload, as specified by Unity's API contract. Skip inactive ancestors here so scripts do not bind to disabled hierarchy branches unexpectedly.

Useful? React with 👍 / 👎.

Review findings:

- Random.Range over integers redraws a sample past the last whole multiple
  of the span instead of reducing it with a biased modulo. Small spans
  almost never redraw, so seeded sequences are unchanged.
- IComparable.CompareTo throws NullReferenceException for a null receiver
  before looking at its argument, and ArgumentException for an argument of
  another type, as .NET does.
- An array viewed as ICollection<T> implements it (Count; Clear throws
  NotSupportedException, as an array does).
- UnityGameView restores Display's multi-key mode when the last view goes,
  if it was the views that turned it on.
- The unchanged-project fast path requires every part of the output it
  would otherwise write, so a partial cache is compiled again.
- GetComponentInParent skips inactive ancestors; the object itself is
  always searched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e85e5a6440

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +313 to +314
public static boolean Equals(String a, String b) {
return op_Equality(a, b);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Null-check the receiver of instance string equality

When a null variable statically typed as string calls value.Equals((string)null), the translator routes the instance overload and static string.Equals(a, b) through this same helper, so this returns true instead of throwing NullReferenceException. The translator already gives object.Equals instance calls a separate $equals helper for this distinction; string instance calls need the same receiver check without changing the null-tolerant static overload.

Useful? React with 👍 / 👎.

Comment on lines +42 to +43
public static void set_volume(float value) {
AudioSource.listenerVolume = value < 0f ? 0f : value > 1f ? 1f : value;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply listener-volume changes to active voices

When music or a one-shot is already playing and a script changes AudioListener.volume, this setter only updates the scalar used by future play calls; it never updates the active output voices, so mute/master-volume sliders leave existing sounds at their old loudness until they restart. Unity defines this property as controlling the game sound volume, so the output needs a global-volume operation or all live voices must be updated immediately. See Unity's AudioListener.volume documentation.

Useful? React with 👍 / 👎.

Comment on lines +37 to +40
@Override
protected boolean $sameType(Delegate other) {
return other instanceof Action_1;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Distinguish closed generic delegate types

When code calls Delegate.Combine or Delegate.Remove with delegates such as Action<int> and Action<string>, both values are erased to Action_1, so this check incorrectly treats them as the same delegate type. Combine consequently returns a multicast delegate instead of throwing ArgumentException, and invoking it can pass an Integer to the string handler and fail later with an unrelated cast error. Preserve the closed type arguments in the delegate identity; .NET requires the delegate types to match exactly, even when variance makes them assignment-compatible.

Useful? React with 👍 / 👎.

Comment on lines +3648 to +3650
if ("1".equals(type)) {
line(sb, RUNTIME + "Input.$mouseAxis(" + string(text(axis.get("m_Name"))) + ", (int) "
+ integer(axis.get("axis"), "0") + "L, " + f(axis.get("sensitivity"), "0.1") + ");");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor inverted mouse axes from InputManager

When a project sets invert: 1 on a mouse axis such as Mouse Y, this generated call carries only the axis number and sensitivity, and the runtime Axis has no inversion state, so both Input.GetAxis and GetAxisRaw retain the original sign. Camera and aiming controls configured as inverted in Unity therefore move in the opposite direction in the translated application; propagate the setting and negate the raw axis value. Unity's Input Manager documentation specifies that Invert swaps positive and negative axis values.

Useful? React with 👍 / 👎.

Comment on lines +87 to +90
public void ClearDepth() {
minDepth = Float.NEGATIVE_INFINITY;
maxDepth = Float.POSITIVE_INFINITY;
useDepth = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve filter bounds when clearing filters

When a script calls ClearDepth to temporarily disable filtering and later sets useDepth = true, these assignments replace its configured range with unbounded values, so the re-enabled query unexpectedly matches all depths. Unity specifies that ClearDepth only turns off useDepth and leaves minDepth and maxDepth unchanged; ClearNormalAngle has the same defect for its associated angles. Preserve the configured bounds and only clear the enable flags. See Unity's ContactFilter2D contract.

Useful? React with 👍 / 👎.

shai-almog and others added 3 commits October 14, 2026 10:11
…s, axis invert

- An instance string Equals/CompareTo on a null receiver throws
  NullReferenceException; static string.Equals stays null-tolerant.
  Equals(string, StringComparison) added.
- AudioListener.volume scales every voice already playing.
- Delegates made where the closed type is known carry it, so Combine of
  Action<int> and Action<string> (or a variant Action<object>) throws as
  .NET does.
- InputManager invert is honoured for key and mouse axes.
- ContactFilter2D.ClearDepth/ClearNormalAngle keep the bounds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ster runs

pr/6010.json set 0.175x from a single run; master's own runs 38346386877
and 38226452649 on that model measure 0.267x, as does this PR's run
38519282184.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cf20fd1ab8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
CilType n = names.norm(type);
mv.visitLdcInsn(org.objectweb.asm.Type.getObjectType(classToken(n)));
mv.visitLdcInsn(String.valueOf(n.typeName() == null ? n.toString() : n.typeName()).replace('/', '+'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve generic arguments in emitted Type identities

For a GENERICINST, CilType.typeName() returns only the generic definition name, so this emits the same class/name pair for every construction and Type.$of returns one cached object. Consequently, expressions such as typeof(List<int>) == typeof(List<string>) incorrectly evaluate true, and any supported dispatch or dictionary keyed by a closed generic Type conflates unrelated types. Include the constructed arguments in the emitted identity.

Useful? React with 👍 / 👎.

private static void sameType(Delegate a, Delegate b) {
Object x = a.$typeKey;
Object y = b.$typeKey;
if (!a.$sameType(b) || x != y && x != null && y != null && !x.equals(y)) { // NOPMD CompareObjectsWithEquals

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not treat missing delegate type keys as wildcards

When a delegate is created inside generic code, delegateTypeKey() deliberately leaves $typeKey null, and this condition rejects a mismatch only when both keys are non-null. Thus delegates returned by G<int> and G<string> can be passed to Delegate.Combine(Delegate, Delegate) and accepted as the same erased Action_1 type, even though .NET must throw ArgumentException; invoking the result can then fail with an unrelated argument cast. Fresh evidence beyond the earlier closed-delegate issue is the current null-key path for open generic creation, which still bypasses the new key comparison.

Useful? React with 👍 / 👎.

Comment on lines +61 to +62
m_LookAt = v.m_LookAt;
m_Follow = v.m_Follow;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remap Cinemachine targets when cloning hierarchies

When Instantiate copies a GameObject hierarchy whose virtual camera follows or looks at a Transform within that hierarchy, these direct assignments retain the source Transform. copyTree has already registered the copied components, and the $copyFrom contract requires internal Unity references to pass through UnityRuntime.$remap; otherwise the cloned camera tracks the original object instead of its cloned sibling. Remap both fields, which will still preserve references to targets outside the copied hierarchy.

Useful? React with 👍 / 👎.

}

private static void copyIfChanged(File from, File to) throws IOException {
if (to.isFile() && to.length() == from.length() && to.lastModified() >= from.lastModified()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Compare installed outputs by content before skipping copies

When a staged class or resource changes to different bytes of the same size while the installed output has an equal or newer timestamp—for example, two builds within one filesystem timestamp tick—this returns without copying it. The state digest can correctly trigger translation or scene compilation, but installation then leaves the previous class or image in classesOut, causing the packaged application to use stale output. Compare the file contents, or always replace an output after its producing step reruns, rather than treating this metadata as identity.

Useful? React with 👍 / 👎.

// A copy, because a listener may add or remove one.
Object[] now = listeners.toArray();
for (int i = 0; i < now.length; i++) { // NOPMD ForLoopCanBeForeach
((UnityAction) now[i]).Invoke();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip destroyed runtime UnityEvent listeners

When a listener added with AddListener targets a Unity object that is later destroyed while the event owner survives, this unconditionally invokes the retained delegate, so managed code on the destroyed component continues to run. Unity's InvokableCall checks AllowInvoke and suppresses delegates whose UnityEngine.Object target has been destroyed, matching the check already applied to persistent listeners above (Unity reference source); apply the same target check to runtime listeners, including multicast invocation lists.

Useful? React with 👍 / 👎.

Comment on lines +141 to +142
public void InvokeRepeating(String methodName, float time, float repeatRate) {
UnityRuntime.invokeLater(this, methodName, time, repeatRate > 0f ? repeatRate : 0f);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject invalid InvokeRepeating rates

When repeatRate is zero, negative, or a positive value no greater than 0.00001f, this either converts the request into a one-shot invocation or accepts a rate Unity rejects, so a configuration error silently calls the method with different timing. Unity's MonoBehaviour.InvokeRepeating throws when the rate is not greater than 0.00001f (Unity reference source); validate the rate instead of passing zero to the ordinary invocation scheduler.

Useful? React with 👍 / 👎.

shai-almog and others added 2 commits October 14, 2026 11:41
…ode, event and clone fixes

- typeof of a constructed generic type carries its arguments, so
  typeof(List<int>) != typeof(List<string>); one cached Type per closed type.
- Delegates made in generic code that has a per-closed-type context get a
  closed type key; the remaining null-key cases are documented.
- UnityEvent skips runtime listeners whose target is destroyed and ignores
  a duplicate AddListener, as documented.
- CinemachineVirtualCamera remaps Follow/LookAt when a hierarchy is cloned.
- UnityProjectBuilder replaces every output after a step reran, and writes
  its state after install.
- InvokeRepeating comment states what the docs specify.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RAM is bimodal per round (~0.118x or ~0.04x); run 38536963726 had 3 of 5
rounds low and failed the gate as an improvement. Pooled with the PR's
earlier run and three master runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 821ae3e82d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +281 to +283
pitch = value;
if (playing && output != null) {
output.pitch(voice, value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clamp AudioSource pitch to Unity's supported range

When a script assigns a value outside [-3, 3], such as source.pitch = 10, this stores and forwards the value unchanged, so get_pitch(), playback position, and isPlaying advance at 10× speed. Unity clamps AudioClip pitch to [-3, 3], so clamp the value before updating the playback clock or output voice. See Unity's AudioSource.pitch contract.

Useful? React with 👍 / 👎.

Comment on lines +95 to +97
public static String Concat(Object[] parts) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < parts.length; i++) { // NOPMD ForLoopCanBeForeach

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate null Concat arrays before iterating

When translated code calls string.Concat((object[])null), dereferencing parts.length throws a Java NullPointerException, which Interop.wrap exposes as NullReferenceException. .NET requires ArgumentNullException for a null array, so catch clauses observe the wrong exception; add an explicit null check here and in the sibling String[] overload. See the .NET String.Concat(Object[]) contract.

Useful? React with 👍 / 👎.

Comment on lines +748 to +751
float r = raw(axes[i]);
if (r != 0f) {
v = r;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Select the strongest duplicate raw axis

When InputManager.asset defines multiple axes with the same name, this returns the last nonzero raw value rather than the value with the largest absolute magnitude. For example, if an earlier keyboard definition reports 1 and a later mouse definition reports 0.2, GetAxisRaw incorrectly returns 0.2, while GetAxis immediately below applies the correct selection rule. Compare magnitudes here as well; Unity specifies that same-named axes are queried together and the largest absolute value is returned in the Input Manager manual.

Useful? React with 👍 / 👎.

Comment on lines +924 to +925
public void Play() {
begun = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Propagate parameterless particle controls to children

When a parent particle system calls Play() without arguments, this implementation starts only that system, although Unity's parameterless form uses withChildren = true; child particle systems therefore remain stopped in common effects composed from a root and several child emitters. The parameterless Stop, Pause, and Clear methods have the same mismatch. Route these default overloads through child propagation, as specified by Unity's ParticleSystem.Play contract.

Useful? React with 👍 / 👎.

Comment on lines +331 to +332
public void CompressBounds() {
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Make CompressBounds shrink the exposed tilemap bounds

When tiles at an edge are removed, or when grow() has added its eight-cell spare padding, calling CompressBounds() leaves origin and size unchanged because this method is a no-op. Scripts that use those properties to iterate the occupied area therefore continue traversing empty cells and observe bounds different from Unity; shrink the storage to the remaining occupied cells, as required by Unity's Tilemap.CompressBounds contract.

Useful? React with 👍 / 👎.

Comment on lines +310 to +313
@Override
public void Reset() {
throw new NotSupportedException();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reset list enumerators instead of rejecting the operation

When translated code obtains a List<T> enumerator through IEnumerator and calls Reset(), this always throws NotSupportedException; the .NET list enumerator instead verifies that the list was not modified and resets its index and current value, allowing enumeration to start over. Implement that behavior so valid callers do not fail solely after translation; see the .NET reference implementation.

Useful? React with 👍 / 👎.

…w axis, null params, pitch clamp

- List and Dictionary enumerators reset (InvalidOperationException once
  the collection changed), as .NET does.
- Tilemap bounds are kept apart from the storage and CompressBounds
  shrinks them to the occupied cells.
- Parameterless ParticleSystem Play/Stop/Pause/Clear include children.
- GetAxisRaw over same-named axes answers the largest magnitude.
- string.Concat/Format with a null array or format throw
  ArgumentNullException.
- AudioSource.pitch is clamped to [-3, 3].

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0890827888

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +370 to +372
float speed = pitch < 0f ? -pitch : pitch;
if (clip == null || !((paused ? pausedAt : now()) - startedAt // NOPMD LogicInversion
< (clip.get_length() - startedFrom) / (speed > 0f ? speed : 1f))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Compute expiration for zero and reverse pitch

When pitch is zero, position() remains stationary but this fallback divisor of 1f still expires a non-looping voice after the ordinary remaining duration; with a negative pitch, expiration is likewise measured toward the clip's end even though playback is moving toward time zero. For example, starting a ten-second clip at time = 2 and pitch = -1 reports isPlaying for eight seconds instead of two, while pitch = 0 eventually reports false despite paused playback. Base expiration on pitch direction and never advance it for zero pitch; Unity explicitly describes negative and zero pitch as reverse and paused playback in the AudioSource pitch contract.

Useful? React with 👍 / 👎.

Comment on lines +294 to +295
if (!touchesArrive) {
routeFormTouch(e.getX(), e.getY(), true, true, false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Ignore form-level presses outside the game view

In an embedded GameView, this fallback listener receives every press on the top-level form, including presses on sibling controls. Until touchesArrive becomes true—and permanently on ports where the direct multi-touch callback is why this fallback is needed—clicking a toolbar or other host UI therefore marks the game's pointer down and can trigger mouse-driven gameplay. Only start a fallback gesture when the press lies within the view bounds, while still allowing an outside release to finish a gesture that began inside.

Useful? React with 👍 / 👎.

Comment on lines +1586 to +1587
if (best != null && canvas.sortingOrder < bestOrder) {
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.

P2 Badge Match canvas hit testing to canvas draw order

When overlapping canvases differ in render mode or sorting layer, this chooses the hit canvas using only sortingOrder. Rendering in DrawList.inOrder instead places overlay canvases after camera canvases and orders camera content by group, sorting layer, sorting order, and depth, so a camera-space canvas with a larger numeric order can receive a click through an overlay canvas that is visibly on top; camera canvases on different sorting layers can be inverted similarly. Rank raycast candidates with the same ordering keys used for drawing.

Useful? React with 👍 / 👎.

Comment on lines +79 to +83
output = o;
if (o != null) {
o.listenerVolume(listenerVolume);
}
// An output that arrives in the middle of a pause -- a view shown

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore active voices when reinstalling the audio output

When a UnityGameView leaves a form, deinitialize() stops every device voice and clears the output without calling AudioSource.Stop, so each source retains its logical playing state and playback position. When the same view is shown again, installServices() supplies a fresh output, but this setter only applies listener volume and pause state and never recreates or seeks those retained voices. Consequently, background music reports isPlaying == true after navigation away and back while remaining silent indefinitely unless the script explicitly calls Play again; reinstall active source voices at their current positions or preserve them across the detach.

Useful? React with 👍 / 👎.

…rection; GameView host presses

- AudioSource re-creates every playing source's voice at its position
  when a different output is installed (a view shown again).
- UI raycast ranks canvases with the same keys as drawing.
- A paused (pitch 0) voice never expires; a reversed one ends at 0.
- GameView's form-level fallback starts a gesture only for a press inside
  the view; a drag or release outside still ends one begun inside.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 184be1d936

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +125 to +126
public static boolean Any(IEnumerable_1 source, Func_2 predicate, Class t) {
IEnumerator_1 e = walk(source);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate null LINQ delegates before walking the source

When predicate is null, this overload returns false for an empty source and throws a translated NullReferenceException only after encountering an element, rather than consistently throwing ArgumentNullException before enumeration. This affects the other predicate/selector overloads in this class as well and can send translated exception handling down the wrong path; validate each delegate before calling walk, as required by the .NET Enumerable.Any contract.

Useful? React with 👍 / 👎.

Comment on lines +278 to +280
public static Object ElementAt(IEnumerable_1 source, int index, Class t) {
IEnumerator_1 e = walk(source);
try {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject negative ElementAt indexes before enumeration

When index is negative, at == index can never succeed, so this method consumes the entire source before throwing; on an infinite iterator it never returns, and on a stateful iterator it runs side effects that .NET never performs. Validate the index before obtaining or advancing the enumerator, since the ElementAt(IEnumerable<T>, int) contract requires an immediate ArgumentOutOfRangeException for a negative index.

Useful? React with 👍 / 👎.

Comment on lines +537 to +540
if (v > 1e15)
{
return negative ? "-Infinity" : "Infinity";
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep large finite Unity values finite when formatting

For any finite component whose magnitude exceeds 1e15, this branch emits Infinity, so values such as new Vector3(1e20f, 0, 0).ToString() become indistinguishable from actual infinity and can corrupt logs or string-based serialization. Unity formats these components with invariant F2 (and colors with F3), which preserves finite values; use a formatting path that handles the full float range rather than treating a long-conversion limit as infinity, matching the Unity Vector3.ToString implementation.

Useful? React with 👍 / 👎.

Comment on lines +194 to +195
public static void Clear(Object array, int index, int count) {
if (array instanceof MdArray) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Throw ArgumentNullException for a null Array.Clear target

When translated code calls Array.Clear(null, index, count), vectorLength throws NullReferenceException, so a caller catching ArgumentNullException observes the wrong path. Add an explicit null check before dispatching to multidimensional or vector storage (including the struct-array helper), as the .NET Array.Clear contract requires ArgumentNullException for a null array.

Useful? React with 👍 / 👎.

Comment on lines +1728 to +1730
// Unity runs a coroutine up to its first yield before StartCoroutine
// returns.
boolean ended = advance(c);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Refuse coroutines on inactive GameObjects

When another object, event, or retained reference calls StartCoroutine on a component whose GameObject is already inactive in the hierarchy, this unconditionally advances the iterator to its first yield, allowing arbitrary coroutine side effects before runCoroutines notices the inactive owner and stops it. Unity does not start coroutines on inactive GameObjects, consistent with its documented rule that deactivating a GameObject stops its attached coroutines; check owner.gameObject.activeInHierarchy before calling advance so the routine never executes in this case (Unity StartCoroutine documentation).

Useful? React with 👍 / 👎.

… text

- StartCoroutine on an object inactive in the hierarchy runs nothing and
  answers null.
- Array.Clear, Enumerable delegate overloads, Concat and ElementAt check
  their arguments before touching the source, as .NET does.
- Value types format any finite component in full (F2/F3 invariant);
  only a real infinity prints "Infinity".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6598efee7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +449 to +453
protected void deinitialize() {
if (holdsMultiKey) {
holdsMultiKey = false;
releaseMultiKey();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Release held keys when detaching the view

When this view is removed while a physical key is held, its later release is delivered to the new focus owner rather than this component, but deinitialize() leaves both the held map and the queued/runtime key state intact. Showing the same view again therefore makes Input.GetKey remain true, and the next press of that key is ignored because it is already present in held; clear the local state and enqueue or otherwise apply releases for every held Unity key during detachment.

Useful? React with 👍 / 👎.

Comment on lines +454 to +457
if (audio != null) {
audio.stopAll();
AudioSource.$output(null);
audio = null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve another view's active audio output

When two UnityGameView instances are shown, the most recently initialized one installs its UnityGameAudio as the process-wide AudioSource output, but detaching either view unconditionally replaces that output with null. Detaching the older view therefore disconnects the still-visible newer view, so subsequent AudioSource.Play calls are silent; clear the output only if this view still owns it, or manage audio ownership with the same multi-view accounting used above.

Useful? React with 👍 / 👎.

Comment on lines +1041 to +1042
mv.visitMethodInsn(INVOKEVIRTUAL, c, "$copy", "()L" + c + ";", false);
mv.visitFieldInsn(PUTFIELD, name, fieldName, descriptor);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remap Unity references nested in serialized structs

When a serialized script field is a struct that itself contains a UnityEngine.Object reference, such as a Transform pointing to a sibling in the prefab, this path invokes the ordinary value $copy() and never runs that nested reference through UnityRuntime.$remap. Instantiating the hierarchy therefore leaves the clone's struct pointing into the source hierarchy instead of the cloned sibling; emit a serialization-aware struct copy here, and use it for struct-array elements, that remaps nested Unity references.

Useful? React with 👍 / 👎.

Comment on lines +697 to +698
UnityRuntime.runFrameCalls();
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Discard paused key presses before resuming

When a key is pressed and released while pauseWanted is true, this branch returns without consuming pending; the first resumed frame then replays both events, making Unity scripts observe GetKeyDown and GetKeyUp for input that happened entirely while paused. Drain paused key events here, ignoring new presses while retaining only releases needed to clear keys that were already held before the pause, analogous to the mouse handling.

Useful? React with 👍 / 👎.

Comment on lines +117 to +119
public static boolean op_Implicit(RaycastHit2D hit) {
return hit.collider != null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Treat destroyed colliders as invalid raycast hits

When a script retains a RaycastHit2D and its collider is subsequently destroyed, the Java reference remains non-null, so this operator continues returning true and if (hit) can traverse the destroyed collider. Unity's custom object equality makes a destroyed object compare equal to null, while a raycast hit is valid only when its collider is valid; use Object.op_Implicit(hit.collider) rather than Java reference nullness. See Unity's Object equality contract and RaycastHit2D boolean contract.

Useful? React with 👍 / 👎.

Comment on lines +88 to +91
@Override
public void delete(String key) {
Preferences.delete(PREFIX + key);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Remove deleted keys from the preference index

When a game repeatedly calls DeleteKey and later sets the same key again, this removes only the value, so set() sees it as absent and appends another copy of its name to INDEX. The persistent index therefore grows without bound and makes DeleteAll progressively slower, potentially reaching platform preference-size limits in reset-heavy applications; remove the key from the index when deleting it or avoid appending names already recorded there.

Useful? React with 👍 / 👎.

…cast hits, nested struct remap, prefs index

- Key presses made while paused or with the clock held are dropped and
  only releases pass on; a view taken off its form releases every held key.
- Detaching one of two views clears the audio output only if it is still
  that view's.
- RaycastHit2D is false once its collider is destroyed.
- Instantiate remaps Unity references inside serialized structs (fields,
  arrays, lists) instead of a plain value copy.
- PlayerPrefs.DeleteKey removes the name from the persisted index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants