Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .agents/docs/2026-11-15-add-jolt-mwrap-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Add mcpplibs.mwrap.jolt (jolt-m 5.6.0)

## Form

- Third-party upstream [jrouwe/JoltPhysics](https://github.com/jrouwe/JoltPhysics), release v5.6.0, MIT.
- Form A external mwrap module repository [mcpplibs/jolt-m](https://github.com/mcpplibs/jolt-m), package
`mcpplibs.mwrap.jolt`, version 5.6.0 (the first indexed release of the identity), tag `mwrap-5.6.0`; release asset
`jolt-m-5.6.0.tar.gz` (`ea4b7052…9c49`), CN mirror `gitcode.com/mcpp-res/mwrap-jolt` with identical bytes.
- Implementation ownership: the library has no compat package in this index, so the wrapper vendors `Jolt/` from the
release archive (checksums of every file in `upstream/documents.json`) and builds it as upstream's CMake build does
with `CROSS_PLATFORM_DETERMINISTIC` on (`JPH_CROSS_PLATFORM_DETERMINISTIC`, `-ffp-contract=off`, no fast math),
with `JPH_NO_DEBUG` (the same library and module names in every profile: upstream turns on its asserts and
debug-only members when `NDEBUG` is not defined), and without the parts upstream builds only in its Debug and
Release configurations or as options: no debug renderer, profiler, object stream or GPU compute backend (DX12,
Vulkan, Metal and their CPU emulation are not vendored). The SIMD level is the target's baseline. It deploys
upstream's README and LICENSE with its own LICENSE/NOTICE (`runtime.deploy`). On wasm32 it links with a 1 MiB stack
and growing memory.

## Module

- `mcpplibs.mwrap.jolt`: every name declared at namespace scope in `JPH` and its nested namespaces by the headers under
`<Jolt/…>` (the debug renderer's, the compute shaders' shared C++/HLSL headers and a list of macro invocations
excepted), by `using` in the namespace upstream declares it in — `JPH` (450 types, 127 functions and operators, 6
objects), `ClosestPoint`, `HalfFloatConversion`, `ScaleHelpers`, `StreamUtils`, `literals`.
- Internal-linkage adaptation: 50 `static` or non-`inline` `constexpr` objects (`cMaxPhysicsJobs`,
`cMaxPhysicsBarriers`, `cDefaultConvexRadius`, `JPH_PI`, the half-float layout, the height field's constants …) are
declared again under their names, with their types and values, in `mcpplibs::mwrap::jolt` and its nested namespaces
of the same names; a constant array as a `std::array` of its elements. Two `static` narrow-phase helpers
(`ActiveEdges::FixNormal`, `IsEdgeActive`) are not exported.
- A name declared on some targets only is exported under upstream's own condition: `JPH::FPControlWord` (SSE and Arm,
not wasm32).
- No `.macros` module: Jolt's macros are configuration switches and code generation (`JPH_DECLARE_RTTI_*`,
`JPH_OVERRIDE_NEW_DELETE`, `JPH_ASSERT`).
- The export list is derived by `tools/surface.py` from the headers as clang reads them (its JSON AST), recorded in
`tools/surface.json`, and audited in CI: the module is the record on every row, and on the Linux LLVM row the record
is derived again and compared.

## Verification

- Wrapper CI (Linux x86_64 and arm64 with GCC 16.1 and LLVM 23.1.3, macOS LLVM, Windows LLVM, each in the dev and
release profiles; wasm32-emscripten under node): a stack knocked down by a ball, a ray and a point query, a world
saved and restored running on to the uninterrupted run's bits, a scene's hash after 200 steps pinned and the same
on every row with 0, 1 and 4 workers; the adapted constants; header mixing; the export audit; the default template
from an installed copy with a strict build.
- This index: consumer `tests/mwrap/jolt-world`; the installed-template check with `--deploy` (`STATIC_NOTICES`).
5 changes: 5 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1733,6 +1733,11 @@ jobs:
shell: bash
run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.manifold@3.5.5.1 --deploy

- name: Installed jolt wrapper archive, actual default world template and its relocated notices
if: contains(format(' {0} ', env.MEMBERS), ' jolt-world ') && matrix.toolchain == 'default'
shell: bash
run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.jolt@5.6.0 --deploy

- name: Installed libjpeg-turbo wrapper archive, actual default decoding template and its relocated notices
if: contains(format(' {0} ', env.MEMBERS), ' libjpeg-turbo-decode ') && matrix.toolchain == 'default'
shell: bash
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@

### Added

- **mcpplibs.mwrap.jolt 5.6.0(新包)。** jolt-m:Jolt Physics 5.6.0(刚体与软体物理:形状、约束、查询、角色、载具)的 C++23
模块,以 `export using` 在上游的命名空间里导出 `<Jolt/…>` 下各头文件的全部公开名字(`JPH` 450 个类型、127 个函数与运算符、
6 个对象,及嵌套命名空间 `ClosestPoint`、`HalfFloatConversion`、`ScaleHelpers`、`StreamUtils`、`literals` 的名字);50 个内部链接的
常量(`cMaxPhysicsJobs`、`cDefaultConvexRadius`、`JPH_PI` 等;常量数组为 `std::array`)以原名位于 `mcpplibs::mwrap::jolt`。导出清单
由 clang 读头文件推出并在 CI 中审计;`JPH::FPControlWord` 只在上游声明它的平台(SSE、Arm)导出。封装仓库从上游 5.6.0 发布包取
源码并以跨平台确定模式构建(`JPH_CROSS_PLATFORM_DETERMINISTIC`、`-ffp-contract=off`、不用快速数学,`JPH_NO_DEBUG` 使各配置相同):
同一场景在 Linux x86-64 与 arm64、macOS、Windows、wasm32 上,dev 与 release,0、1、4 个工作线程,哈希相同(钉住)。不含调试
渲染器、性能剖析、对象流与 GPU 计算后端。消费者测试 `tests/mwrap/jolt-world`,安装后模板检查带 `--deploy`。GLOBAL 为 GitHub
发布资产,CN 为 GitCode `mcpp-res/mwrap-jolt` 镜像,sha256 相同。
- **mcpplibs.mwrap.basis_universal 2.50.1(新包)。** basis-universal-m:Basis Universal 2.50 转码器(把 KTX2 与 `.basis` 纹理转成
像素或 GPU 自己的压缩格式)的 C++23 模块,`<transcoder/basisu_transcoder.h>` 在 `basist`、`basisu` 及其内部命名空间声明的
603 个名字(148 个类型、273 个函数、70 个对象、112 个无作用域枚举的枚举项)以原名导出;头文件里 139 个内部链接的标量常量
Expand Down
1 change: 1 addition & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ members = [
"tests/mwrap/meshoptimizer-simplify",
"tests/mwrap/cgltf-inspect",
"tests/mwrap/manifold-boolean",
"tests/mwrap/jolt-world",
"tests/mwrap/basis-universal-transcode",
"tests/mwrap/libjpeg-turbo-decode",
"tests/mwrap/libarchive-bundle",
Expand Down
50 changes: 50 additions & 0 deletions pkgs/mwrap/jolt.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
-- Immutable upstream-aligned mwrap source release (jolt-m mwrap-5.6.0, the first release of upstream 5.6.0);
-- GLOBAL and CN bytes are identical.
--
-- The wrapper is Apache-2.0; the library it vendors and links statically,
-- Jolt Physics 5.6.0, is MIT: the package deploys its README and LICENSE
-- beside programs (runtime.deploy).
package = {
spec = "1",
namespace = "mcpplibs.mwrap",
name = "jolt",
description = "jolt-m: Jolt Physics 5.6.0 (rigid and soft body physics: shapes, constraints, queries, characters, vehicles), cross-platform deterministic, as a C++23 module",
licenses = { "Apache-2.0" },
repo = "https://github.com/mcpplibs/jolt-m",
type = "package",
extensions = {
autoupdate = {
tag = "mwrap-${version}",
url = "https://github.com/mcpplibs/jolt-m/releases/download/${tag}/jolt-m-${version}.tar.gz",
},
},
xpm = {
linux = {
["5.6.0"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/jolt-m/releases/download/mwrap-5.6.0/jolt-m-5.6.0.tar.gz",
CN = "https://gitcode.com/mcpp-res/mwrap-jolt/releases/download/5.6.0/jolt-m-5.6.0.tar.gz",
},
sha256 = "ea4b7052c13888142594a01966081bda80a2dc8a32e9e093479cfacaba939c49",
},
},
macosx = {
["5.6.0"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/jolt-m/releases/download/mwrap-5.6.0/jolt-m-5.6.0.tar.gz",
CN = "https://gitcode.com/mcpp-res/mwrap-jolt/releases/download/5.6.0/jolt-m-5.6.0.tar.gz",
},
sha256 = "ea4b7052c13888142594a01966081bda80a2dc8a32e9e093479cfacaba939c49",
},
},
windows = {
["5.6.0"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/jolt-m/releases/download/mwrap-5.6.0/jolt-m-5.6.0.tar.gz",
CN = "https://gitcode.com/mcpp-res/mwrap-jolt/releases/download/5.6.0/jolt-m-5.6.0.tar.gz",
},
sha256 = "ea4b7052c13888142594a01966081bda80a2dc8a32e9e093479cfacaba939c49",
},
},
},
}
1 change: 1 addition & 0 deletions tests/mwrap/check_installed_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"mcpplibs.mwrap.meshoptimizer": ("meshoptimizer.cppm", "upstream/meshoptimizer/LICENSE.md", b"MIT License", b"Arseny Kapoulkine"),
"mcpplibs.mwrap.cgltf": ("cgltf.cppm", "upstream/cgltf/LICENSE", b"Permission is hereby granted", b"Kuhlmann"),
"mcpplibs.mwrap.manifold": ("manifold.cppm", "upstream/manifold/LICENSE", b"Apache License", b"The Manifold Authors"),
"mcpplibs.mwrap.jolt": ("jolt.cppm", "upstream/jolt/LICENSE", b"Permission is hereby granted", b"Jolt Physics is Copyright (c) 2021 Jorrit"),
"mcpplibs.mwrap.libjpeg_turbo": ("libjpeg_turbo.cppm", "upstream/libjpeg-turbo/README.ijg", b"Independent JPEG Group", b"libjpeg-turbo Project"),
"mcpplibs.mwrap.basis_universal": ("basis_universal.cppm", "upstream/basis_universal/NOTICE", b"Binomial LLC", b"Basis Universal"),
"mcpplibs.mwrap.libc": ("libc.cppm", None, b"", b"ISO/IEC 9899:2018"),
Expand Down
15 changes: 15 additions & 0 deletions tests/mwrap/jolt-world/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "jolt-world"
version = "5.6.0"
description = "Consume the immutable Jolt Physics wrapper: a world of bodies stepped, queried, saved and restored to the same bits, with one worker and several"

[indices]
# Use the complete candidate built-in index prepared by tests/run_members.sh.
# An explicit empty table overrides the legacy workspace redirect.

[dependencies]
mcpplibs.mwrap.jolt = "5.6.0"

# Jolt's inline math compiled here too, as the library is built.
[build]
cxxflags = ["-ffp-contract=off"]
109 changes: 109 additions & 0 deletions tests/mwrap/jolt-world/tests/behavior.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// The installed Jolt Physics wrapper: a floor, a stack of boxes and a ball thrown at it; stepped until the stack
// falls, a ray cast down onto it, the world saved and restored to the uninterrupted run's bits, and the same bits
// with one worker and with three; the adapted constants.
import std;
import mcpplibs.mwrap.jolt;

namespace jolt = mcpplibs::mwrap::jolt;

namespace {

constexpr JPH::ObjectLayer still = 0, moving = 1;

bool map(JPH::BroadPhaseLayerInterfaceTable& broad, JPH::ObjectLayerPairFilterTable& pairs) {
broad.MapObjectToBroadPhaseLayer(still, JPH::BroadPhaseLayer(0));
broad.MapObjectToBroadPhaseLayer(moving, JPH::BroadPhaseLayer(1));
pairs.EnableCollision(moving, still);
pairs.EnableCollision(moving, moving);
return true;
}
struct Layers {
JPH::BroadPhaseLayerInterfaceTable broad{2, 2};
JPH::ObjectLayerPairFilterTable pairs{2};
bool mapped = map(broad, pairs);
JPH::ObjectVsBroadPhaseLayerFilterTable object_vs_broad{broad, 2, pairs, 2};
};

struct Scene {
JPH::TempAllocatorImpl temp{4 * 1024 * 1024};
std::unique_ptr<JPH::JobSystem> jobs;
JPH::PhysicsSystem system;
std::vector<JPH::BodyID> bodies;
};

void make(Scene& scene, const Layers& layers, int workers) {
if (workers == 0) scene.jobs = std::make_unique<JPH::JobSystemSingleThreaded>(jolt::cMaxPhysicsJobs);
else scene.jobs = std::make_unique<JPH::JobSystemThreadPool>(jolt::cMaxPhysicsJobs, jolt::cMaxPhysicsBarriers, workers);
scene.system.Init(256, 0, 256, 256, layers.broad, layers.object_vs_broad, layers.pairs);
JPH::BodyInterface& bodies = scene.system.GetBodyInterface();
scene.bodies.push_back(bodies.CreateAndAddBody(
JPH::BodyCreationSettings(new JPH::BoxShape(JPH::Vec3(20, 1, 20)), JPH::RVec3(0, -1, 0), JPH::Quat::sIdentity(), JPH::EMotionType::Static, still),
JPH::EActivation::DontActivate));
for (int level = 0; level < 5; ++level)
scene.bodies.push_back(bodies.CreateAndAddBody(JPH::BodyCreationSettings(new JPH::BoxShape(JPH::Vec3(0.5f, 0.5f, 0.5f)), JPH::RVec3(0, 0.5f + level * 1.0f, 0),
JPH::Quat::sIdentity(), JPH::EMotionType::Dynamic, moving),
JPH::EActivation::Activate));
JPH::BodyCreationSettings ball(new JPH::SphereShape(0.5f), JPH::RVec3(-8, 2, 0), JPH::Quat::sIdentity(), JPH::EMotionType::Dynamic, moving);
ball.mLinearVelocity = JPH::Vec3(20, 1, 0);
scene.bodies.push_back(bodies.CreateAndAddBody(ball, JPH::EActivation::Activate));
}

void step(Scene& scene, int steps) {
for (int index = 0; index < steps; ++index) scene.system.Update(1.0f / 60.0f, 1, &scene.temp, scene.jobs.get());
}

std::uint64_t bits(const Scene& scene) {
std::uint64_t hash = 0xcbf29ce484222325ull;
const JPH::BodyInterface& bodies = scene.system.GetBodyInterface();
for (const JPH::BodyID body : scene.bodies) {
const JPH::RVec3 at = bodies.GetPosition(body);
const JPH::Quat turn = bodies.GetRotation(body);
for (const float value : {float(at.GetX()), float(at.GetY()), float(at.GetZ()), turn.GetX(), turn.GetY(), turn.GetZ(), turn.GetW()}) {
hash ^= std::bit_cast<std::uint32_t>(value);
hash *= 0x100000001b3ull;
}
}
return hash;
}

} // namespace

int main() {
JPH::RegisterDefaultAllocator();
JPH::Factory::sInstance = new JPH::Factory();
JPH::RegisterTypes();
int failed = 0;
{
const Layers layers;
Scene straight;
make(straight, layers, 0);
JPH::RayCastResult hit;
if (!straight.system.GetNarrowPhaseQuery().CastRay(JPH::RRayCast{JPH::RVec3(0, 20, 0), JPH::Vec3(0, -40, 0)}, hit) || hit.mBodyID != straight.bodies[5]) failed = 1;
step(straight, 180);
const std::uint64_t expected = bits(straight);
if (straight.system.GetBodyInterface().GetPosition(straight.bodies[5]).GetY() > 3.0f && !failed) failed = 2; // the top box fell

Scene rolled;
make(rolled, layers, 0);
step(rolled, 60);
JPH::StateRecorderImpl saved;
rolled.system.SaveState(saved);
step(rolled, 60);
saved.Rewind();
if (!rolled.system.RestoreState(saved) && !failed) failed = 3;
step(rolled, 120);
if (bits(rolled) != expected && !failed) failed = 4;

Scene threaded;
make(threaded, layers, 3);
step(threaded, 180);
if (bits(threaded) != expected && !failed) failed = 5;
if ((jolt::cMaxPhysicsJobs != 2048 || jolt::sAllSubShapeTypes.size() != static_cast<std::size_t>(jolt::NumSubShapeTypes)) && !failed) failed = 6;
std::println("Jolt Physics 5.6.0 through import mcpplibs.mwrap.jolt: the stack knocked down, restored and run again to {:#018x}, the same with 3 workers{}",
expected, failed ? std::format(" — check {} failed", failed) : "");
}
JPH::UnregisterTypes();
delete JPH::Factory::sInstance;
JPH::Factory::sInstance = nullptr;
return failed;
}
Loading