Skip to content

fix(linux): make Bloom actually run on Linux — device limits, screenshots, window close#146

Merged
proggeramlug merged 4 commits into
mainfrom
codex/linux-worker
Jul 27, 2026
Merged

fix(linux): make Bloom actually run on Linux — device limits, screenshots, window close#146
proggeramlug merged 4 commits into
mainfrom
codex/linux-worker

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Brings the Linux target up to working order. Validated on AMD Phoenix1 / RADV / XWayland with the golden-image suite, Bloom Jump, and the Bloom Shooter.

What was broken

1. Device limits/features had drifted behind Windows and bloom_shared::attach (5b156cd)

bloom_init_window on Linux requested wgpu::Limits::default() plus only max_bind_groups. Everything else the renderer leans on was missing:

  • max_storage_buffers_per_shader_stage — the PT kernel binds 9 against a default of 8. This was a hard crash on startup, not a graceful fallback. Any game reaching PT init aborted:
    In Device::create_bind_group_layout, label = 'pt_layout'
    Too many bindings of type StorageBuffers in Stage ShaderStages(COMPUTE), limit is 8, count was 9
    
  • max_sampled_textures_per_shader_stage / max_samplers_per_shader_stage — the refractive/translucent profile binds 19 sampled textures against a default cap of 16. Latent on Linux; the identical shortfall is what stopped the shooter opening on macOS (see the comment in attach.rs).
  • PT-2 TEXTURE_BINDING_ARRAY + non-uniform indexing, and the binding-array element budget — never requested, so textured path-trace hit shading silently compiled the card-only stub.

Also adds COPY_SRC to the swapchain usage. bloom_take_screenshot reads the swapchain back via copy_texture_to_buffer; without the usage bit that is a validation error that aborts the process — confirmed by removing it again and getting a non-unwinding panic + core dump on the first takeScreenshot() call.

2. Duplicate exports broke every bloom/models consumer on every platform (8d6132d)

src/models/index.ts declared BUCKET_* twice with conflicting values, and unloadModel twice. Perry emits one LLVM function per exported const, so clang rejected the module (invalid redefinition of function ...__BUCKET_OPAQUE), Perry linked it as empty _perry_init_* stubs, and games failed with dozens of undefined references (createMesh, drawModelTransform, setAmbientLight, compileMaterialInstancedBucket, …). The Bloom Shooter could not be built at all.

Kept the block matching the live wire mapping in Renderer::compile_material_instanced_bucket (0=Opaque, 1=Cutout, 2=Additive, 3=Transparent) — the numbering the shooter's BUCKET_ADDITIVE / BUCKET_CUTOUT call sites depend on. The removed block carried Phase 4b numbering, which matched neither the FFI nor the Rust Bucket discriminants. Nothing referenced BUCKET_REFRACTIVE; compileRefractiveMaterial covers that bucket. A scan of src/**/*.ts finds no other duplicate top-level exports.

Not Linux-specific — this broke macOS and Windows identically.

3. Closing the window killed the game instead of shutting it down (fb79f52)

WM_DELETE_WINDOW was never added to the window's WM_PROTOCOLS, so the titlebar ✕ gave the WM no polite way to ask us to quit. It dropped the X connection and Xlib's default IO-error handler called exit(1) mid-frame:

XIO:  fatal IO error 62 (Timer expired) on X server ":0"

windowShouldClose() never went true, so closeWindow() and every shutdown path after it (audio teardown, save-on-exit) were skipped.

Verification

  • cargo test --release in native/shared: 138/138 pass, including the 9 golden-image renders on real hardware
  • tools/validate-ffi.js: ok linux: 473 exports cover 472 manifest functions (the 2 remaining failures are pre-existing watchos/web bloom_is_key_repeated gaps on main, untouched here)
  • Bloom Shooter runs at 59 FPS — terrain, water, 120k grass blades, animated character, 128 physics bodies, nav mesh + flow field, and ssgi trace backend = hw-ray-query (HW ray query granted via Vulkan)
  • takeScreenshot is pixel-exact: requested (220,80,60) → written (220,80,60)
  • Sending a real WM_DELETE_WINDOW to the shooter now exits 0 with no XIO error (previously exit 1 + XIO fatal)

Known gaps found but deliberately not changed here

  • examples/pong no longer compiles on any platform — it uses Color.White / Color.Black, but GH Duplicate identifier Color in core/index.ts #53 deliberately stopped re-exporting that palette (Color is now the RGBA type only). Fix is Colors.* / ColorConstants.*. Separate concern from this PR.
  • EN-019 acceptance Per-mesh velocity buffer + motion blur #2 (Linux HiDPI) cannot pass on a Wayland session — XWayland reports 5120×2880 over 1354×762 mm = exactly 96 DPI, so display_scale() correctly returns 1.0. That is the documented EN-019b gap, not a code bug.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Improved Linux X11 window closing behavior so selecting “close” from the window manager reliably exits the app.
    • Enabled advanced textured rendering by requesting additional graphics feature support and higher device limits where available.
    • Updated swapchain configuration to improve screenshot/readback compatibility.
  • Bug Fixes
    • Fixed material rendering category (“bucket”) numbering to ensure correct assignments and prevent module/linker issues related to duplicate exports.
  • Chores
    • Removed a redundant exported model unload wrapper to avoid duplicate definitions.

Ralph Kuepper and others added 3 commits July 27, 2026 06:22
bloom_init_window on Linux had drifted behind the Windows crate and
bloom_shared::attach, which both negotiate these already:

- max_storage_buffers_per_shader_stage: the PT kernel binds 9 (accum +
  moments + reservoir ping-pongs on top of instance/geo data) against
  wgpu's default of 8. This was a hard crash on startup, not a fallback
  — every game reaching PT init aborted with "Too many bindings of type
  StorageBuffers in Stage ShaderStages(COMPUTE), limit is 8, count was 9".
- max_sampled_textures/samplers_per_shader_stage: raised to the adapter's
  limits. Latent until now — the refractive/translucent profile binds 19
  sampled textures against a default cap of 16. The identical shortfall
  is what stopped the shooter opening on macOS (see attach.rs).
- PT-2 TEXTURE_BINDING_ARRAY + non-uniform indexing, and the binding-array
  element budget: never requested here, so textured path-trace hit shading
  silently compiled the card-only stub.

Also add COPY_SRC to the swapchain usage. bloom_take_screenshot reads the
swapchain back via copy_texture_to_buffer; without the usage bit that is a
validation error that aborts the process (verified by removing it again:
non-unwinding panic + core dump on the first takeScreenshot call).

Verified on AMD Phoenix1 / RADV / XWayland: 138/138 shared tests pass
including the 9 golden-image renders, and the Bloom Shooter runs at 59 FPS
with `ssgi trace backend = hw-ray-query`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…sumer

src/models/index.ts declared BUCKET_* twice with conflicting values and
unloadModel twice. Perry emits one LLVM function per exported const, so
clang rejected the module outright ("invalid redefinition of function
...__BUCKET_OPAQUE"). Perry then linked the whole module as empty
_perry_init_* stubs, so every game importing bloom/models failed to link
with dozens of undefined references (createMesh, drawModelTransform,
setAmbientLight, compileMaterialInstancedBucket, ...). The Bloom Shooter
could not be built at all.

Kept the BUCKET_* block that matches the live wire mapping decoded by
Renderer::compile_material_instanced_bucket (0=Opaque, 1=Cutout,
2=Additive, 3=Transparent) — the numbering the shooter's BUCKET_ADDITIVE
and BUCKET_CUTOUT call sites depend on. The removed block carried Phase 4b
numbering (TRANSPARENT=1, REFRACTIVE=2, ADDITIVE=3, CUTOUT=4), which
matched neither the FFI nor the Rust Bucket enum's discriminants. Refractive
has no wire value in that FFI; compileRefractiveMaterial covers it, and
nothing referenced BUCKET_REFRACTIVE.

Both unloadModel definitions were byte-identical; kept the documented one.
A scan of src/**/*.ts finds no other duplicate top-level exports.

Not Linux-specific — this broke macOS and Windows identically.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cleanly

The X11 backend never put WM_DELETE_WINDOW in the window's WM_PROTOCOLS, so
the titlebar ✕ / Alt-F4 / session logout gave the WM no polite way to ask the
game to quit. It dropped the X connection instead, and Xlib's default IO-error
handler called exit(1) from under the running frame:

    XIO:  fatal IO error 62 (Timer expired) on X server ":0"
          after 23355 requests (23354 known processed) with 0 events remaining.

`windowShouldClose()` never went true, so `closeWindow()` and everything the
game does after its loop — audio teardown, save-on-exit — were skipped. Only
DestroyNotify was handled, which arrives too late to be useful here.

Intern the atoms once at window creation, XSetWMProtocols before mapping, and
translate the resulting ClientMessage into `should_close`.

Verified with the Bloom Shooter: sending a real WM_DELETE_WINDOW ClientMessage
now exits 0 with no XIO error in the log (previously exit 1 + XIO fatal).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fdf18422-f45a-4294-aea6-283c50a4a0d5

📥 Commits

Reviewing files that changed from the base of the PR and between fb79f52 and 8bacd1d.

📒 Files selected for processing (1)
  • src/models/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/models/index.ts

📝 Walkthrough

Walkthrough

Linux X11 shutdown handling and wgpu initialization now support close events, path-tracing resource requirements, and framebuffer readback. Model exports remove duplicate declarations and align instanced-material bucket IDs.

Changes

Linux window runtime

Layer / File(s) Summary
X11 close protocol handling
native/linux/src/lib.rs
Caches and registers WM close atoms, then sets engine().should_close for matching ClientMessage events.
wgpu feature and surface initialization
native/linux/src/lib.rs
Conditionally enables PT-2 texture features, raises adapter-based limits, and adds COPY_SRC surface usage.

Model export cleanup

Layer / File(s) Summary
Model exports and bucket IDs
src/models/index.ts
Removes the duplicate unloadModel export and conflicting bucket block, then exports renderer-aligned instanced-material bucket IDs.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WindowManager
  participant x11_impl
  participant Engine
  x11_impl->>WindowManager: Register WM_DELETE_WINDOW
  WindowManager->>x11_impl: Send matching ClientMessage
  x11_impl->>Engine: Set should_close
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main Linux fixes: device limits, screenshot usage, and WM close handling.
Description check ✅ Passed The description covers the summary, validation, and reviewer notes, and is detailed enough despite not using the exact template.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/linux-worker

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@native/linux/src/lib.rs`:
- Around line 680-684: Update the Linux swapchain configuration to use
RENDER_ATTACHMENT combined with only the COPY_SRC bit supported by
surface_caps.usages. In the Linux screenshot path, detect when COPY_SRC is
unavailable and return the existing unsupported-screenshot error instead of
attempting copy_texture_to_buffer.
- Around line 631-658: Update the adapter-limit handling before request_device
to validate Bloom’s fixed minimum budgets: at least 19 sampled textures and 9
storage buffers per shader stage. Reject initialization with a clear error or
select the existing fallback renderer when adapter_limits cannot satisfy either
minimum; do not lower required_limits to an unsupported adapter value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ba7264fb-c126-477f-960c-1ac73410d8ae

📥 Commits

Reviewing files that changed from the base of the PR and between e498433 and fb79f52.

⛔ Files ignored due to path filters (1)
  • native/linux/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • native/linux/src/lib.rs
  • src/models/index.ts

Comment thread native/linux/src/lib.rs
Comment on lines +631 to +658
// The refractive/translucent material profile binds up to 19
// sampled textures in the fragment stage (5 material maps + env/
// BRDF/3 shadow cascades/env-diffuse + planar reflection + 3 texture
// arrays + the group-4 scene_color/scene_depth/impulse/motion inputs).
// wgpu's default is 16. Raise to whatever the adapter actually
// supports — every real Vulkan/GL GPU exposes ≥128 — so opaque/
// transparent materials are unaffected and refractive ones link.
let adapter_limits = adapter.limits();
required_limits.max_sampled_textures_per_shader_stage = required_limits
.max_sampled_textures_per_shader_stage
.max(adapter_limits.max_sampled_textures_per_shader_stage);
required_limits.max_samplers_per_shader_stage = required_limits
.max_samplers_per_shader_stage
.max(adapter_limits.max_samplers_per_shader_stage);
// PT-2: binding arrays have their own element budget, default 0.
// Take whatever the adapter offers; the renderer checks the
// granted value against its fixed array size before compiling
// the textured kernel variant.
if required_features.contains(pt_tex_mask) {
required_limits.max_binding_array_elements_per_shader_stage =
adapter_limits.max_binding_array_elements_per_shader_stage;
}
// PT-4: the path-trace kernel binds 9 storage buffers (accum +
// moments + reservoir ping-pongs on top of instance/geo data);
// the wgpu default limit is 8.
required_limits.max_storage_buffers_per_shader_stage = required_limits
.max_storage_buffers_per_shader_stage
.max(adapter_limits.max_storage_buffers_per_shader_stage.min(16));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching lib.rs:"
fd 'lib\.rs$' . || true

echo
echo "Relevant section in native/linux/src/lib.rs:"
sed -n '600,675p' native/linux/src/lib.rs

echo
echo "Search for required_limits and DeviceDescriptor/Adapter configuration:"
rg -n "required_limits|max_sampled_textures|max_samplers|max_storage_buffers|DeviceDescriptor|request_adapter|request_device" native/linux/src/lib.rs native -g '*.rs' | head -200

Repository: Bloom-Engine/engine

Length of output: 22087


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Search for Bloom renderer resource budget constants/usages:"
rg -n "19 sampled|128|PT-2|PT-4|path-trace kernel|9 storage|max_binding_array_elements_per_shader_stage|req.*binding|fixed array size|max_binding_array_elements" native/shared native/linux native/windows native/web | head -200

echo
echo "Check native/windows and shared attach mirroring:"
sed -n '750,802p' native/windows/src/lib.rs
sed -n '155,182p' native/shared/src/attach.rs

Repository: Bloom-Engine/engine

Length of output: 15576


🌐 Web query:

Bloom Engine native linux renderer 19 sampled textures 9 storage buffers path-trace kernel

💡 Result:

The Bloom Engine is a cross-platform graphics engine written in TypeScript that compiles to native binaries (macOS, Windows, Linux, iOS, tvOS) and WASM for the web [1][2]. Its renderer is built on the wgpu API [1][2], which abstracts graphics backends such as Vulkan, Metal, DirectX 12, and WebGPU [1]. Regarding your query about a "19 sampled textures 9 storage buffers path-trace kernel" configuration, there is no public documentation or technical evidence indicating that the Bloom Engine utilizes a specific hardcoded configuration of 19 sampled textures and 9 storage buffers for a path-tracing kernel. The engine's current development focus involves GPU-driven rendering techniques, including indirect multi-draw and GPU culling [3], as well as advanced lighting features like global illumination probes and variance-driven refinement [4][5]. While the engine explores compute-heavy tasks like global illumination feedback loops (WSRC) [4], it explicitly notes that some "exotic" features such as native hardware ray tracing are currently off the table due to the constraints of the wgpu abstraction layer [2]. Technical discussions within the Bloom Engine repository often reference compute shader dispatch and binding patterns [4][5][3], but do not align with the specific numbers mentioned in your query. It is possible that these numbers refer to a custom implementation or a specific project configuration not part of the core Bloom Engine codebase.

Citations:


Reject adapters below Bloom’s fixed resource budgets.

The Linux/Windows path-tracing setup documents hard requirements of 19 sampled textures and 9 storage buffers, but the device request currently downgrades to the adapter’s lower limits. Query the requested limits against these minima before request_device; if an adapter cannot support them, fail initialization clearly or select a fallback renderer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@native/linux/src/lib.rs` around lines 631 - 658, Update the adapter-limit
handling before request_device to validate Bloom’s fixed minimum budgets: at
least 19 sampled textures and 9 storage buffers per shader stage. Reject
initialization with a clear error or select the existing fallback renderer when
adapter_limits cannot satisfy either minimum; do not lower required_limits to an
unsupported adapter value.

Comment thread native/linux/src/lib.rs
Comment on lines +680 to +684
// COPY_SRC: bloom_take_screenshot reads the swapchain back;
// without it the readback copy is a validation error that
// aborts the process the first time a game calls it.
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_SRC,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | sed -n '1,120p'

echo "== target file size/outline =="
wc -l native/linux/src/lib.rs || true
ast-grep outline native/linux/src/lib.rs --view compact 2>/dev/null | sed -n '1,220p' || true

echo "== relevant lines =="
sed -n '620,730p' native/linux/src/lib.rs | cat -n

Repository: Bloom-Engine/engine

Length of output: 10276


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== screenshot and screenshot-related locations =="
rg -n "bloom_take_screenshot|bloom_register_geisterhand_screenshot|COPY_SRC|swapchain|swap_chain|SurfaceCapabilities|surface_caps|configure" native/linux/src/lib.rs native/linux -g '*.rs' || true

echo "== renderer surface usage/configuration context =="
rg -n "SurfaceConfiguration|RENDER_ATTACHMENT|COPY_SRC|bloom_take_screenshot|screen reader|readback|present" --glob '*.rs' .

echo "== likely screenshots section =="
sed -n '1140,1284p' native/linux/src/lib.rs | cat -n

Repository: Bloom-Engine/engine

Length of output: 37222


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== windows surface config around COPY_SRC =="
sed -n '790,835p' native/windows/src/lib.rs | cat -n

echo "== web copy-src handling =="
sed -n '160,195p' native/web/src/lib.rs | cat -n

echo "== shared attach =="
sed -n '220,255p' native/shared/src/attach.rs | cat -n

echo "== shared renderer surface config =="
sed -n '1989,2032p' native/shared/src/renderer/mod.rs | cat -n

echo "== screenshot implementation in shared renderer =="
sed -n '11160,11245p' native/shared/src/renderer/mod.rs | cat -n

echo "== wasm screenshot =="
sed -n '334,350p' native/web/src/parity_ffi.rs | cat -n

Repository: Bloom-Engine/engine

Length of output: 13276


🌐 Web query:

wgpu SurfaceCapabilities usages TextureUsages configure surface copy-src documentation

💡 Result:

To use a surface texture as a source for copy operations (e.g., using COPY_SRC), you must ensure that your SurfaceConfiguration includes the appropriate TextureUsages when calling surface.configure. 1. Query Surface Capabilities: Before configuring the surface, retrieve the supported texture usages for your specific adapter by calling surface.get_capabilities(adapter) [1][2]. This returns a SurfaceCapabilities struct, which contains a usages field specifying which TextureUsages are supported by the surface [3][2]. Note that TextureUsages::RENDER_ATTACHMENT is the only usage guaranteed to be supported across all surfaces [3][4][5]. 2. Configure the Surface: When you call surface.configure, you must provide a SurfaceConfiguration [1][4]. The usage field in this struct defines the allowed operations for textures retrieved from the surface via get_current_texture [4][5]. To perform a copy-src operation (such as copy_texture_to_buffer), the SurfaceConfiguration's usage must include TextureUsages::COPY_SRC [6][7]. 3. Important Considerations: - Compatibility: Always check the usages returned by get_capabilities before configuring [2]. If you request a usage not supported by the underlying surface/adapter, the configuration may fail or cause runtime issues [1][8]. - Performance and Driver Behavior: Different backends (Vulkan, DX12, Metal) have varying levels of support for surface usages beyond RENDER_ATTACHMENT [8]. Always ensure the requested flags are subset of the capabilities returned by the surface [2]. - COPY_SRC Usage: TextureUsages::COPY_SRC specifically allows a texture to be used as the source in command encoder operations like copy_texture_to_buffer or copy_texture_to_texture [6][7]. Example flow: let caps = surface.get_capabilities(&adapter); let config = wgpu::SurfaceConfiguration { usage: caps.usages & wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT, format: caps.formats, //... other fields }; surface.configure(&device, &config);

Citations:


Check COPY_SRC support before configuring the Linux swapchain.

surface.configure(...) requires the requested usages to be present in surface_caps.usages; RENDER_ATTACHMENT is not the surface’s only guaranteed usage flag. Configuring with unguarded COPY_SRC can fail window initialization on surfaces that only support render attachment, even though the Linux screenshot path relies on copy_texture_to_buffer of the current swapchain texture. Use RENDER_ATTACHMENT | (surface_caps.usages & TextureUsages::COPY_SRC) as the configured usage, and fail the screenshot path with an unsupported-screenshot error when COPY_SRC is unavailable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@native/linux/src/lib.rs` around lines 680 - 684, Update the Linux swapchain
configuration to use RENDER_ATTACHMENT combined with only the COPY_SRC bit
supported by surface_caps.usages. In the Linux screenshot path, detect when
COPY_SRC is unavailable and return the existing unsupported-screenshot error
instead of attempting copy_texture_to_buffer.

@proggeramlug
proggeramlug merged commit 82fd842 into main Jul 27, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant