Skip to content

Core rewrite#4

Open
Viphor wants to merge 25 commits intomainfrom
core-rewrite
Open

Core rewrite#4
Viphor wants to merge 25 commits intomainfrom
core-rewrite

Conversation

@Viphor
Copy link
Copy Markdown
Owner

@Viphor Viphor commented May 2, 2026

This pull request introduces major architectural updates and documentation improvements to support a modular, three-tier crate structure for the dd40 voxel game project. It adds new foundation and implementation crates, formalizes architectural rules, and improves documentation for both contributors and automated tools. The changes clarify crate dependencies, outline planned architectural work, and update the workspace configuration accordingly.

Architectural and Documentation Updates:

  • Introduced a three-tier dependency model (foundation, implementation, binary) in .agents/skills/dd40-architecture/SKILL.md, replacing the previous single-core rule. Added explicit rules for plugin auto-satisfaction with the ensure_plugins! macro and clarified code placement guidelines. [1] [2]
  • Added CLAUDE.md to provide clear instructions and architectural guidance for Claude Code and new contributors, including commands, crate roles, system ordering, and documentation standards.
  • Updated INCONSISTENCIES.md to document new and existing architectural violations, referencing planned fixes in SPEC.md and reflecting the new crate structure (e.g., dd40_physics_core, dd40_character_core, and implementation crate interactions). [1] [2]

Workspace and Crate Additions:

  • Added new foundation and implementation crates to the workspace in Cargo.toml: dd40_physics_core, dd40_physics, dd40_character_core, dd40_player_movement, and dd40_character_interaction. Updated workspace dependencies accordingly. [1] [2]
  • Created crates/character_core with its own Cargo.toml and implemented a CharacterBuilder fluent builder pattern for constructing character bundles, promoting code clarity and modularity. [1] [2]

Reference additions:

Viphor and others added 13 commits May 1, 2026 16:14
Encapsulates the is_plugin_added / add_plugins default pattern that every
implementation plugin must use to auto-satisfy its runtime dependencies.
This is the foundational convention required before any crate extraction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the flat 'all crates depend on dd40_core' rule with Foundation /
Implementation / Binary tiers. Documents the ensure_plugins! pattern,
adds planned crates to the crate-roles table, and expands INCONSISTENCIES.md
with items 5-7 (physics in core, character types in core, player-gated
systems) that are tracked in SPEC.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extracts all physics vocabulary from dd40_core into a dedicated foundation
crate: Aabb, Velocity, GravityScale, Grounded, CharacterPosition,
TentativePosition, Impulse, PhysicsBody, CharacterCollider, PhysicsConfig,
PhysicsSet. PhysicsCorePlugin auto-adds CorePlugin via ensure_plugins!.
Physics systems remain in dd40_core::character::physics for now (Task 1.2).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copies integration, block_collision, character_collision, and spatial_cache
systems from dd40_core into the new dd40_physics implementation crate with
updated imports (dd40_physics_core::prelude::* and dd40_core::*). PhysicsPlugin
auto-adds CorePlugin and PhysicsCorePlugin via ensure_plugins!. All 49 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 1 of the three-tier architecture refactor (SPEC.md Tasks 1.1–1.3):

- dd40_physics_core: vocabulary crate (Aabb, Velocity, Grounded, CharacterPosition,
  Impulse, PhysicsBody, PhysicsConfig, PhysicsSet, etc.)
- dd40_physics: implementation crate (integration, block collision, character
  collision, spatial cache) with PhysicsPlugin that auto-adds its deps via
  ensure_plugins!
- dd40_core::character::physics now re-exports from dd40_physics_core (backward
  compat shim until Phase 2 removes it entirely)
- Removed circular dev-dep dd40_core → dd40_physics: controller integration
  tests moved to crates/physics/tests/character_controller.rs where dd40_core
  is a plain dep and TypeIds are consistent
- Added Logging and Circular Dev-Dependency rules to CLAUDE.md

All 237 workspace tests pass green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New foundation crate holding all character vocabulary: Player, Character,
MovementSpeed, JumpImpulse, SpawnPosition, CharacterInput, CharacterController,
CharacterRenderSet, CharacterBundle, CharacterBuilder, and the controller
system. CharacterCorePlugin auto-adds PhysicsCorePlugin via ensure_plugins!.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deletes crates/core/src/character/ entirely. All character vocabulary
(Player, Character, MovementSpeed, JumpImpulse, SpawnPosition,
CharacterInput, CharacterController, CharacterRenderSet, CharacterBundle,
CharacterBuilder) now comes exclusively from dd40_character_core.

Updated consumers: dd40_player, dd40_network (client + server), dd40_renderer.
Physics tests updated to add CharacterCorePlugin alongside PhysicsPlugin
since the controller system is no longer bundled into CorePlugin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace hand-rolled panics and missing plugin checks with ensure_plugins!:
- WorldPlugin: panic → ensure_plugins!(app, CorePlugin)
- DiskStoragePlugin: add ensure_plugins!(app, CorePlugin)
- RendererPlugin: add ensure_plugins!(app, CorePlugin)
- VanillaPalettePlugin: add ensure_plugins!(app, CorePlugin)
- DebugUiPlugin: add ensure_plugins!(app, CorePlugin)
- ServerNetworkPlugin: panic → ensure_plugins!(app, CorePlugin)
- ClientNetworkPlugin: add ensure_plugins!(app, CorePlugin)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…0_player

- Task 3.1: dd40_player_movement crate with camera, input, PlayerMode state,
  mouse sensitivity, FreeCam movement, and chunk loading systems
- Task 3.2: dd40_character_interaction crate with DDA raycast targeting,
  mining, and placement systems generalised to With<Character> (not With<Player>)
- Task 3.3: dd40_player slimmed to a thin wrapper that composes
  PlayerMovementPlugin + CharacterInteractionPlugin; retains update_debug_info
  (needs both physics and interaction types) and spawn_player

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…er_core

- Task 5.1: renderer LOD anchor now uses CharacterPosition (dd40_physics_core)
  instead of With<Player>; removes dd40_character_core dep from renderer
- Task 5.2: add PlayerId(u64) to dd40_character_core — stable network identity
  for correlating ECS entities with peer connections
- Task 5.3: move MiningState to dd40_character_core (pure vocabulary); renderer
  can now read mining progress without depending on dd40_character_interaction;
  dd40_character_interaction re-exports MiningState from character_core

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…troller

After the refactor, CorePlugin no longer auto-adds CharacterPlugin/controller.
ServerNetworkPlugin was only ensuring CorePlugin, so apply_character_controller
was never registered on the server. Characters fell under gravity but never
moved horizontally — lightyear confirmed the stationary server position each
tick, causing the client to be yanked back to spawn on every move.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PlayerRotation was registered with add_prediction(), so lightyear would
checkpoint and restore it during rollbacks. Since rotation always lags the
mouse by one RTT, every correction snapped the rotation backward.

Fix:
- PlayerRotation now has add_linear_interpolation() only (no prediction).
  The controlling client never experiences a rollback on rotation.
- sync_local_rotation (PostUpdate) writes CharacterInput.pitch/yaw directly
  to PlayerRotation each frame, after lightyear's replication receive and
  after player_input has refreshed CharacterInput. Render always sees the
  current-frame camera orientation with zero lag.
- apply_interpolated_rotation (Update) applies PlayerRotation → Transform
  rotation for remote (Interpolated) entities so other clients see smooth,
  server-replicated head direction via linear interpolation.

Pitch/yaw remain in PlayerInput so the server continues to receive them
and can replicate PlayerRotation to non-controlling clients.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…urrent state

CLAUDE.md:
- Remove *(planned)* from all five crates that now exist
- Note MiningState and PlayerId in dd40_character_core role description
- Note renderer's CharacterPosition LOD anchor
- Note dd40_player's known exception to Tier 1 isolation

INCONSISTENCIES.md:
- Archive resolved items 1, 3, 5, 6, 7 into a resolved table
- Keep item 4 (block crack animation) as open but note it's unblocked
- Rewrite dd40_renderer dep as a suggestion (D: key PlayerLocations by PlayerId)
- Add dd40_player tier exception as the new open inconsistency

SKILL.md:
- Add dd40_player exception note to Tier 1 section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Viphor Viphor requested a review from Copilot May 2, 2026 17:03
@Viphor Viphor self-assigned this May 2, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR performs a large architectural split of character and physics responsibilities out of dd40_core into new foundation/implementation crates, while also adopting the new auto-plugin dependency pattern across the workspace. It reshapes how gameplay, rendering, networking, and binaries compose shared systems and vocabulary.

Changes:

  • Extracts new foundation crates (dd40_physics_core, dd40_character_core) and implementation crates (dd40_physics, dd40_player_movement, dd40_character_interaction), then rewires existing crates to use them.
  • Replaces several manual plugin dependency checks with dd40_core::ensure_plugins! and updates client/server binaries to include the new physics layer explicitly.
  • Updates architecture/spec documentation (SPEC.md, INCONSISTENCIES.md, CLAUDE.md, skill docs) to describe the new three-tier crate model and plugin auto-satisfaction pattern.

Reviewed changes

Copilot reviewed 77 out of 78 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
crates/world/src/plugin.rs Swaps manual CorePlugin precondition for ensure_plugins!.
crates/vanilla_palette/src/lib.rs Auto-adds CorePlugin before palette plugins.
crates/server/src/main.rs Adds PhysicsPlugin to server composition.
crates/server/Cargo.toml Adds dd40_physics dependency.
crates/renderer/src/systems.rs Switches LOD anchor from player transform to CharacterPosition.
crates/renderer/src/lib.rs Auto-adds core/physics foundation plugins.
crates/renderer/Cargo.toml Replaces dd40_player dependency with dd40_physics_core.
crates/player_movement/src/systems.rs New camera/input/freecam/chunk-loading systems crate.
crates/player_movement/src/state.rs Introduces PlayerMode state.
crates/player_movement/src/plugin.rs Wires player movement systems and tests plugin auto-deps.
crates/player_movement/src/lib.rs Exposes movement plugin/components/state.
crates/player_movement/src/components.rs Adds camera rotation and mouse sensitivity components.
crates/player_movement/Cargo.toml Declares new movement crate dependencies.
crates/player/src/lib.rs Slims player crate into spawn/debug/wrapper responsibilities.
crates/player/src/block_interaction/mod.rs Removes old in-crate block interaction module.
crates/player/src/block_interaction/mining.rs Removes old in-crate mining implementation.
crates/player/Cargo.toml Replaces monolithic deps with split character/physics/player crates.
crates/physics_core/src/system_sets.rs Introduces PhysicsSet in foundation crate.
crates/physics_core/src/prelude.rs Re-exports physics-core vocabulary.
crates/physics_core/src/plugin.rs Registers physics-core types/resources/system ordering.
crates/physics_core/src/lib.rs Exposes physics-core modules.
crates/physics_core/src/components.rs Moves core physics components/config into new crate.
crates/physics_core/Cargo.toml Declares new physics-core crate.
crates/physics/tests/physics_behavior.rs Adds integration tests for physics plugin behavior.
crates/physics/tests/character_controller.rs Adds controller/physics integration tests.
crates/physics/src/spatial_cache.rs Updates spatial cache imports and visibility after split.
crates/physics/src/plugin.rs New implementation physics plugin composing simulation systems.
crates/physics/src/lib.rs Exposes physics modules/plugin/cache.
crates/physics/src/integration.rs Moves integration stage onto physics-core vocabulary.
crates/physics/src/character_collision.rs Moves character-collision stage onto split crates.
crates/physics/Cargo.toml Declares new physics implementation crate.
crates/network/src/shared/character.rs Repoints shared character bundle/controller imports.
crates/network/src/server/mod.rs Uses ensure_plugins! for server networking deps.
crates/network/src/server/character.rs Repoints server character replication to split crates.
crates/network/src/server/block_placement.rs Uses moved physics/cache types for placement validation.
crates/network/src/protocol.rs Repoints replicated types and changes PlayerRotation replication mode.
crates/network/src/client/spawn.rs Repoints player marker import.
crates/network/src/client/plugin.rs Uses ensure_plugins! for client networking deps.
crates/network/src/client/character.rs Repoints client character code and adds rotation sync/interpolation.
crates/network/Cargo.toml Adds split character/physics dependencies.
crates/debug_ui/src/lib.rs Auto-adds CorePlugin.
crates/core/src/plugin.rs Removes old character plugin wiring and keeps core-only responsibilities.
crates/core/src/macros.rs Adds ensure_plugins! macro and tests.
crates/core/src/lib.rs Removes character exports and exposes macros/collision shape.
crates/core/src/character/plugin.rs Deletes old character plugin from core.
crates/core/src/character/physics/mod.rs Deletes old in-core physics module.
crates/core/src/character/mod.rs Deletes old in-core character module.
crates/core/src/character/controller.rs Deletes old in-core character controller.
crates/core/src/character/builder.rs Deletes old in-core character builder.
crates/core/src/block/registry.rs Updates collision-shape import path.
crates/core/src/block/mod.rs Moves CollisionShape into block module.
crates/client/src/main.rs Adds PhysicsPlugin and comments out inspector plugins.
crates/client/Cargo.toml Adds dd40_physics dependency.
crates/chunk_storage/src/plugin.rs Auto-adds CorePlugin.
crates/character_interaction/src/targeting.rs New generic targeting/highlight/debug module.
crates/character_interaction/src/plugin.rs New interaction plugin/resources/messages/scheduling.
crates/character_interaction/src/placement.rs New generic placement module.
crates/character_interaction/src/mining.rs New generic mining module.
crates/character_interaction/src/lib.rs Exposes interaction crate API.
crates/character_interaction/Cargo.toml Declares new interaction crate.
crates/character_core/src/system_sets.rs Introduces CharacterRenderSet in foundation crate.
crates/character_core/src/prelude.rs Re-exports character-core vocabulary.
crates/character_core/src/plugin.rs Registers character-core types/render ordering/controller plugin.
crates/character_core/src/mining_state.rs Moves MiningState into character-core.
crates/character_core/src/lib.rs Exposes character-core modules.
crates/character_core/src/controller.rs Moves character controller into character-core.
crates/character_core/src/components.rs Moves character/player/jump/movement/spawn/player-id types.
crates/character_core/src/bundles.rs Moves CharacterBundle into character-core.
crates/character_core/src/builder.rs Moves CharacterBuilder into character-core.
crates/character_core/Cargo.toml Declares new character-core crate.
SPEC.md Adds detailed modular-architecture refactor spec.
INCONSISTENCIES.md Rewrites inconsistency tracking for new architecture.
Cargo.toml Adds new workspace members and workspace dependencies.
Cargo.lock Locks new crate graph/dependencies.
CLAUDE.md Adds contributor/tooling architecture guidance for new model.
.agents/skills/dd40-architecture/SKILL.md Updates project skill doc for three-tier architecture.

Comment on lines +197 to +225
/// Casts a ray from the camera and writes the result into [`TargetedBlock`].
pub(crate) fn update_targeted_block(
mut targeted: ResMut<TargetedBlock>,
config: Res<BlockInteractionConfig>,
camera_query: Query<&Transform, With<Camera3d>>,
cache: Res<ChunkCache>,
registry: Res<BlockRegistry>,
) {
let Ok(camera_transform) = camera_query.single() else {
targeted.pos = None;
targeted.face = None;
targeted.block_id = None;
return;
};

let origin = camera_transform.translation;
let direction = *camera_transform.forward();

match dda_raycast(origin, direction, config.max_distance, &cache, &registry) {
Some((pos, face, block_id)) => {
targeted.pos = Some(pos);
targeted.face = Some(face);
targeted.block_id = Some(block_id);
}
None => {
targeted.pos = None;
targeted.face = None;
targeted.block_id = None;
}
Comment on lines +65 to +68
app.insert_resource(BlockInteractionConfig::default())
.insert_resource(TargetedBlock::default())
.insert_resource(HeldBlock::default())
.insert_resource(MiningState::default())
Comment on lines +22 to +32
equipped_query: Query<&EquippedTool, With<Character>>,
registry: Res<BlockRegistry>,
tool_registry: Res<ToolRegistry>,
time: Res<Time>,
mut state: ResMut<MiningState>,
mut start_writer: MessageWriter<StartMiningRequest>,
mut abort_writer: MessageWriter<AbortMiningRequest>,
mut mine_writer: MessageWriter<MineBlockRequest>,
) {
let bare_hands = EquippedTool::default();
let tool = equipped_query.iter().next().copied().unwrap_or(bare_hands);
Comment thread crates/player/src/lib.rs
Comment on lines +15 to +19
pub use dd40_character_interaction::{
BlockFace, BlockInteractionConfig, CharacterInteractionPlugin as BlockInteractionPlugin,
HeldBlock, TargetedBlock as TargetBlock,
};
pub use dd40_player_movement::{CameraRotation, MouseSensitivity, PlayerMode as PlayerModeType};
Comment thread INCONSISTENCIES.md
| — | Physics systems lived in `dd40_core` | SPEC.md Phase 1 — extracted to `dd40_physics_core` + `dd40_physics` |
| — | Character types lived in `dd40_core` | SPEC.md Phase 2 — extracted to `dd40_character_core` |
| — | Block interaction and movement systems were player-gated | SPEC.md Phase 3 — `dd40_character_interaction` and `dd40_player_movement` created, filters changed to `With<Character>` |
| — | `PlayerId(u64)` did not exist | SPEC.md Task 5.2 — added to `dd40_character_core::components` |
Comment on lines +20 to +23
/// Unlike the old `BlockInteractionPlugin`, this plugin does **not** gate
/// systems on `PlayerMode`. That concern belongs to the caller — wire the
/// systems under whatever condition suits your control scheme (e.g. only while
/// `PlayerMode::Controller` for human players).
Comment thread crates/player/src/lib.rs
Comment on lines +107 to +117
app.add_plugins((PlayerMovementPlugin, CharacterInteractionPlugin));

// Clear interaction state when switching to FreeCam so highlights and
// mining progress don't linger. OnEnter lives here because it needs
// both PlayerMode (player_movement) and the interaction resources.
app.add_systems(
OnEnter(PlayerMode::FreeCam),
|mut targeted: ResMut<TargetedBlock>, mut mining: ResMut<MiningState>| {
*targeted = TargetedBlock::default();
*mining = MiningState::Idle;
},
Viphor and others added 12 commits May 2, 2026 22:32
The two app.update() calls in the test helper are non-obvious: the first
tick lets Bevy accumulate the manual duration into Time<Fixed>; the second
actually drains the bucket and runs FixedUpdate where physics systems live.
This comment was lost during the crate rename and is worth keeping for
anyone unfamiliar with Bevy's fixed-timestep scheduler.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
TentativePosition is a pipeline-internal scratch buffer used only by the
dd40_physics systems (integrate, block_collision, character_collision,
spatial_cache). It has no meaning outside the physics implementation crate
and should not be part of the public vocabulary exposed by dd40_physics_core.

Changes:
- Remove TentativePosition from dd40_physics_core (components, prelude)
- Remove it from PhysicsBody's #[require] (it can't stay there once the
  type moves to a downstream crate)
- Define it as pub(crate) in dd40_physics::integration
- Add an On<Add, PhysicsBody> observer in PhysicsPlugin that inserts
  TentativePosition automatically, preserving the spawn-time guarantee
  that #[require] previously provided
- Update imports in block_collision, character_collision, spatial_cache

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CharacterSpatialCache was living in dd40_physics (implementation tier),
making it inaccessible to other crates without pulling in the full
physics implementation. Moving it to dd40_physics_core (foundation tier)
makes it available as a shared resource.

- Add resources module to dd40_physics_core with CharacterSpatialCache
  and PhysicsConfig (relocated from components)
- Initialize CharacterSpatialCache in PhysicsCorePlugin instead of
  CharacterCollisionPlugin
- Inline update_character_spatial_cache into character_collision.rs,
  removing the now-empty spatial_cache module from dd40_physics
- Update prelude and remove dd40_network's direct physics dependency
physics_core, character_core, physics, player_movement, and
character_interaction were all missing per-crate READMEs. Add a
minimal README to each following the same format as the other crates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ayout

The previous version described the pre-refactor architecture: it listed
only 8 crates (now 16), used the old single-dependency rule, and showed
module trees for character/ and physics/ sub-modules that were extracted
out of dd40_core in the Phase 1-3 work.

Rewrite with:
- Three-tier dependency model table
- All 16 crates in the inventory (Tier 0/1/2 grouped)
- Accurate module trees for every crate
- dd40_player exception clearly noted

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- README.md: expand layout block to list all 16 crates with tier labels;
  remove claim that all crates depend only on dd40_core
- crates/core/README.md: remove stale character/ and physics/ module trees
  and the "physics engine exception" rationale (physics is in dd40_physics_core)
- crates/player/README.md: replace deleted block_interaction/ module tree
  with the current three-plugin structure; fix dependency list
- crates/renderer/README.md: remove stale note about dd40_player dep
  inconsistency (fixed in Phase 5); update dep list to include dd40_physics_core

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
character-physics.md:
- All import paths updated to dd40_physics_core::prelude and
  dd40_character_core::prelude (old dd40_core::character::* paths
  no longer exist)
- PhysicsSet: add missing InputSync stage (now 5 stages total)
- PhysicsConfig: add ground_friction and air_friction fields
- CharacterSpatialCache: note correct location in dd40_physics_core
- Builder example updated with correct imports

block-system.md:
- Remove broken `use dd40_core::character::physics::CollisionShape`
  import (CollisionShape is already in dd40_core::prelude::*)
- Fix "See also" path: vanilla_blocks.rs moved to dd40_vanilla_palette
- Update opening paragraph to name dd40_vanilla_palette correctly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The skill used "core vs non-core" binary terminology from before the
Phase 1-3 refactor. Update throughout:

- Tier classification: Foundation / Implementation / Binary
- Step 2: list all three foundation crates (core, physics_core,
  character_core) and the Tier 1 isolation rule
- Cargo.toml template: note that only foundation deps are permitted
- plugin.rs template: add ensure_plugins! call and #[derive(Default)]
- Step 7 next-steps sections: rename to Tier 0 / Tier 1
- Architectural rules: restate in tier language; add Default derive
  and ensure_plugins! as hard rules; note dd40_player exception

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Expand crate list from 6 to 16 with three-tier grouping
- Fix broken CollisionShape import (dd40_core::character::physics no
  longer exists; CollisionShape is in dd40_core::prelude::*)
- Replace BlockRenderingPlugin (removed) with RendererPlugin in the
  plugin-based architecture section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove "Player position/velocity" from the Coming Soon list — it is
already rendered via the DebugInfo system in dd40_player. Mention the
orientation gizmo and custom DebugInfo elements in the "What You Get"
section since they ship with DebugUiPlugin today.

Co-Authored-By: Claude Sonnet 4.6 <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