fix: close STEP-075 projection identity audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,41 @@
|
|||||||
|
# STEP-075 — QBIN2 V4 Binding 与 Projection Identity v2
|
||||||
|
|
||||||
|
状态:完成
|
||||||
|
|
||||||
|
日期:2026-09-13
|
||||||
|
|
||||||
|
## 目标与基线
|
||||||
|
|
||||||
|
- 基于 STEP-075 v1(`3616daec30079395847cc6a538fbd963641b7e12`)完成独立审计收口。
|
||||||
|
- 仅修改 provider-neutral Core Binding/Projection、其离线单元测试与本 Step 文档;没有进入 STEP-076。
|
||||||
|
|
||||||
|
## 必做项
|
||||||
|
|
||||||
|
- [x] 新增 `ProjectionFrameCoordinate`,只以 `fieldID + validTimeMs` 表达 T0/T1 window coordinate;完整 `FrameContentIdentity` 仍保留在 resolved frame 中作 revision/runtime 诊断。
|
||||||
|
- [x] Color 和 Particle identity 均统一比较 binding、T0/T1 coordinate、各自 selected plane content identity 及 T0/T1 shared validity content identity。
|
||||||
|
- [x] 移除 Resolver 公共 `InterpolationIncompatible` 错误码;`FieldSeries::submit` 已拒绝不兼容相邻 frame,`resolve` 只返回已验证 window。
|
||||||
|
- [x] 通过 Store 的 same-valid-time `ReplacedRevision` 测试 Scalar-only、U/V-only、validity-only revision:分别为 Color-only change、Particle-only change、两者均 change。
|
||||||
|
- [x] 保持各 binding 独立 fieldKey/cadence、MIX 不进入 identity、in-window storage stable,以及 DirectionAngle success/failure coverage。
|
||||||
|
|
||||||
|
## 允许修改
|
||||||
|
|
||||||
|
- `include/mbgl/particle/field/field_binding.hpp`
|
||||||
|
- `src/mbgl/particle/field/field_binding.cpp`
|
||||||
|
- `test/particle/field_binding.test.cpp`
|
||||||
|
- 本 Step v2、完成报告 v2 与 `粒子开发记录.md`
|
||||||
|
|
||||||
|
## 禁止修改
|
||||||
|
|
||||||
|
- 未修改 Decoder、FieldSeries、Profile Validator、Renderer、Host/JNI/RN、Android/iOS 或网络数据路径。
|
||||||
|
- 未执行 Android Gradle/APK/ADB/Jest、iOS/Metal、Renderer/GPU 或真实 QBIN Golden 数据。
|
||||||
|
|
||||||
|
## 验证与当前结果
|
||||||
|
|
||||||
|
- Linux OpenGL `cmake --build build-linux-opengl --target mbgl-test-runner -j3` 真实重建 PASS(输出节流)。
|
||||||
|
- 规定 GTest XML `/tmp/step075-gtest.xml`:50 tests、0 failures、0 errors。Binding 4;Runtime/Profile 11;QBIN Decoder/Ingress 24;V3 Source 11。
|
||||||
|
- `git diff --check` PASS。
|
||||||
|
|
||||||
|
## 阻塞与下一步
|
||||||
|
|
||||||
|
- 无实现阻塞。Android/Jest/iOS/Metal 等禁止范围没有验证,不能视为 PASS。
|
||||||
|
- STEP-075 已关闭;按任务书停止,不进入 STEP-076。
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# STEP-075 QBIN2 V4 Binding 与 Projection Identity Codex 完成报告 v2
|
||||||
|
|
||||||
|
日期:2026-09-13
|
||||||
|
|
||||||
|
## 已完成
|
||||||
|
|
||||||
|
- Projection content identity 改为显式 `ProjectionFrameCoordinate(fieldID, validTimeMs)` 加上 binding 实际消费的 T0/T1 selected plane 和 shared validity content identity。完整 generation/dataVersion 不再污染任一 projection identity。
|
||||||
|
- 对称 revision 隔离已由 Store 的 `ReplacedRevision` 公共路径验证:Scalar-only 仅改变 Color,U/V-only 仅改变 Particle,validity-only 同时改变 Color 与 Particle。
|
||||||
|
- `InterpolationIncompatible` 已从 Resolver 公共错误语义移除;FieldSeries submit 已 fail-closed 拒绝不兼容相邻 frame,合法公开 resolve 路径不可形成该状态。
|
||||||
|
- 独立 fieldKey/cadence、MIX identity/storage stability、DirectionAngle success/failure 与既有 V4/V3 回归均保持。
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- Linux OpenGL `mbgl-test-runner` 真实重建 PASS(输出节流)。
|
||||||
|
- 规定 GTest XML:50 tests、0 failures、0 errors;Binding 4、Runtime/Profile 11、QBIN Decoder/Ingress 24、V3 Source 11。
|
||||||
|
- `git diff --check` PASS。
|
||||||
|
|
||||||
|
## 未完成或无法验证
|
||||||
|
|
||||||
|
- Android Gradle/APK/ADB/Jest、iOS/Metal、Renderer/GPU、Host/JNI/RN、网络和真实 QBIN Golden 数据均按任务禁止或不在范围内,未执行且不能标记 PASS。
|
||||||
|
- 无已知实现阻塞;本任务不进入 STEP-076。
|
||||||
|
|
||||||
|
## Git
|
||||||
|
|
||||||
|
- 本报告随本 Step scoped commit 提交;提交与 `origin/particle/main` push 结果在最终 Git 回填提交中记录。
|
||||||
@@ -37,10 +37,20 @@ struct FieldSampleState {
|
|||||||
float mix = 0.0f;
|
float mix = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The stable coordinate of a FieldSeries interpolation endpoint. Revisions
|
||||||
|
// are deliberately excluded: projection identities add only the content that
|
||||||
|
// their binding actually consumes.
|
||||||
|
struct ProjectionFrameCoordinate {
|
||||||
|
std::string fieldID;
|
||||||
|
uint64_t validTimeMs = 0;
|
||||||
|
|
||||||
|
bool operator==(const ProjectionFrameCoordinate&) const;
|
||||||
|
};
|
||||||
|
|
||||||
struct ColorProjectionContentIdentity {
|
struct ColorProjectionContentIdentity {
|
||||||
ColorBinding binding;
|
ColorBinding binding;
|
||||||
FrameContentIdentity t0Frame;
|
ProjectionFrameCoordinate t0Coordinate;
|
||||||
FrameContentIdentity t1Frame;
|
ProjectionFrameCoordinate t1Coordinate;
|
||||||
std::string t0Plane;
|
std::string t0Plane;
|
||||||
std::string t1Plane;
|
std::string t1Plane;
|
||||||
std::string t0Validity;
|
std::string t0Validity;
|
||||||
@@ -51,8 +61,8 @@ struct ColorProjectionContentIdentity {
|
|||||||
|
|
||||||
struct ParticleProjectionContentIdentity {
|
struct ParticleProjectionContentIdentity {
|
||||||
ParticleBinding binding;
|
ParticleBinding binding;
|
||||||
FrameContentIdentity t0Frame;
|
ProjectionFrameCoordinate t0Coordinate;
|
||||||
FrameContentIdentity t1Frame;
|
ProjectionFrameCoordinate t1Coordinate;
|
||||||
std::string t0FirstPlane;
|
std::string t0FirstPlane;
|
||||||
std::string t1FirstPlane;
|
std::string t1FirstPlane;
|
||||||
std::string t0SecondPlane;
|
std::string t0SecondPlane;
|
||||||
@@ -97,7 +107,6 @@ enum class BindingProjectionError : uint8_t {
|
|||||||
PlaneUnitMismatch,
|
PlaneUnitMismatch,
|
||||||
PlaneComponentMismatch,
|
PlaneComponentMismatch,
|
||||||
PlaneGroupMismatch,
|
PlaneGroupMismatch,
|
||||||
InterpolationIncompatible,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ColorProjectionResult {
|
struct ColorProjectionResult {
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ bool validKey(const FieldPlaneKey& key) { return key.semanticID != 0 && validCom
|
|||||||
bool sameSelector(const FieldPlaneKey& a, const FieldPlaneKey& b) {
|
bool sameSelector(const FieldPlaneKey& a, const FieldPlaneKey& b) {
|
||||||
return a.semanticID == b.semanticID && a.groupID == b.groupID;
|
return a.semanticID == b.semanticID && a.groupID == b.groupID;
|
||||||
}
|
}
|
||||||
|
ProjectionFrameCoordinate coordinate(const DecodedFieldFrame& frame) {
|
||||||
|
return {frame.frameContentIdentity.fieldID, frame.frameContentIdentity.validTimeMs};
|
||||||
|
}
|
||||||
const DecodedFieldPlane* findPlane(const DecodedFieldFrame& frame, const FieldPlaneKey& selector) {
|
const DecodedFieldPlane* findPlane(const DecodedFieldFrame& frame, const FieldPlaneKey& selector) {
|
||||||
for (const auto& plane : frame.planes) if (sameSelector(plane.key, selector)) return &plane;
|
for (const auto& plane : frame.planes) if (sameSelector(plane.key, selector)) return &plane;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -50,7 +53,9 @@ BindingProjectionError validateSelected(const DecodedFieldPlane* t0,
|
|||||||
if (t0->unitID != unit || t1->unitID != unit) return BindingProjectionError::PlaneUnitMismatch;
|
if (t0->unitID != unit || t1->unitID != unit) return BindingProjectionError::PlaneUnitMismatch;
|
||||||
if (t0->key.component != component || t1->key.component != component)
|
if (t0->key.component != component || t1->key.component != component)
|
||||||
return BindingProjectionError::PlaneComponentMismatch;
|
return BindingProjectionError::PlaneComponentMismatch;
|
||||||
if (!t0->isInterpolationCompatibleWith(*t1)) return BindingProjectionError::InterpolationIncompatible;
|
// FieldSeries::submit rejects incompatible neighbours, and resolve only
|
||||||
|
// returns a validated window. Resolver has no public incompatible-window
|
||||||
|
// state, so do not expose an unreachable error code here.
|
||||||
return BindingProjectionError::Accepted;
|
return BindingProjectionError::Accepted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,21 +70,16 @@ bool ParticleBinding::operator==(const ParticleBinding& other) const {
|
|||||||
sameSelector(vPlane, other.vPlane) && sameSelector(directionPlane, other.directionPlane) &&
|
sameSelector(vPlane, other.vPlane) && sameSelector(directionPlane, other.directionPlane) &&
|
||||||
expectedUnitID == other.expectedUnitID;
|
expectedUnitID == other.expectedUnitID;
|
||||||
}
|
}
|
||||||
|
bool ProjectionFrameCoordinate::operator==(const ProjectionFrameCoordinate& other) const {
|
||||||
|
return fieldID == other.fieldID && validTimeMs == other.validTimeMs;
|
||||||
|
}
|
||||||
bool ColorProjectionContentIdentity::operator==(const ColorProjectionContentIdentity& other) const {
|
bool ColorProjectionContentIdentity::operator==(const ColorProjectionContentIdentity& other) const {
|
||||||
return binding == other.binding && t0Frame == other.t0Frame && t1Frame == other.t1Frame &&
|
return binding == other.binding && t0Coordinate == other.t0Coordinate && t1Coordinate == other.t1Coordinate &&
|
||||||
t0Plane == other.t0Plane && t1Plane == other.t1Plane && t0Validity == other.t0Validity &&
|
t0Plane == other.t0Plane && t1Plane == other.t1Plane && t0Validity == other.t0Validity &&
|
||||||
t1Validity == other.t1Validity;
|
t1Validity == other.t1Validity;
|
||||||
}
|
}
|
||||||
bool ParticleProjectionContentIdentity::operator==(const ParticleProjectionContentIdentity& other) const {
|
bool ParticleProjectionContentIdentity::operator==(const ParticleProjectionContentIdentity& other) const {
|
||||||
// A FieldFrame revision can carry an unrelated plane update. Projection
|
return binding == other.binding && t0Coordinate == other.t0Coordinate && t1Coordinate == other.t1Coordinate &&
|
||||||
// identity therefore uses the selected frame time/field as its window
|
|
||||||
// coordinate; selected plane and validity content identities carry the
|
|
||||||
// revision-sensitive content part. This preserves a Particle projection
|
|
||||||
// across a scalar-only revision in the same frame window.
|
|
||||||
const auto sameWindowFrame = [](const FrameContentIdentity& a, const FrameContentIdentity& b) {
|
|
||||||
return a.fieldID == b.fieldID && a.validTimeMs == b.validTimeMs;
|
|
||||||
};
|
|
||||||
return binding == other.binding && sameWindowFrame(t0Frame, other.t0Frame) && sameWindowFrame(t1Frame, other.t1Frame) &&
|
|
||||||
t0FirstPlane == other.t0FirstPlane && t1FirstPlane == other.t1FirstPlane &&
|
t0FirstPlane == other.t0FirstPlane && t1FirstPlane == other.t1FirstPlane &&
|
||||||
t0SecondPlane == other.t0SecondPlane && t1SecondPlane == other.t1SecondPlane &&
|
t0SecondPlane == other.t0SecondPlane && t1SecondPlane == other.t1SecondPlane &&
|
||||||
t0Validity == other.t0Validity && t1Validity == other.t1Validity;
|
t0Validity == other.t0Validity && t1Validity == other.t1Validity;
|
||||||
@@ -98,7 +98,7 @@ ColorProjectionResult BindingProjectionResolver::resolve(const EncodedFieldStore
|
|||||||
t0 ? t0->key.component : FieldComponent::None);
|
t0 ? t0->key.component : FieldComponent::None);
|
||||||
error != BindingProjectionError::Accepted)
|
error != BindingProjectionError::Accepted)
|
||||||
return {error, {}};
|
return {error, {}};
|
||||||
ColorProjection projection{{binding, window->t0->frameContentIdentity, window->t1->frameContentIdentity,
|
ColorProjection projection{{binding, coordinate(*window->t0), coordinate(*window->t1),
|
||||||
t0->contentIdentity, t1->contentIdentity, window->t0->validityContentIdentity,
|
t0->contentIdentity, t1->contentIdentity, window->t0->validityContentIdentity,
|
||||||
window->t1->validityContentIdentity},
|
window->t1->validityContentIdentity},
|
||||||
{requested, window->mix}, window->t0, window->t1, t0->storage, t1->storage,
|
{requested, window->mix}, window->t0, window->t1, t0->storage, t1->storage,
|
||||||
@@ -135,7 +135,7 @@ ParticleProjectionResult BindingProjectionResolver::resolve(const EncodedFieldSt
|
|||||||
if (t0First->key.groupID != t0Second->key.groupID || t1First->key.groupID != t1Second->key.groupID)
|
if (t0First->key.groupID != t0Second->key.groupID || t1First->key.groupID != t1Second->key.groupID)
|
||||||
return {BindingProjectionError::PlaneGroupMismatch, {}};
|
return {BindingProjectionError::PlaneGroupMismatch, {}};
|
||||||
}
|
}
|
||||||
ParticleProjection projection{{binding, window->t0->frameContentIdentity, window->t1->frameContentIdentity,
|
ParticleProjection projection{{binding, coordinate(*window->t0), coordinate(*window->t1),
|
||||||
t0First->contentIdentity, t1First->contentIdentity,
|
t0First->contentIdentity, t1First->contentIdentity,
|
||||||
t0Second ? t0Second->contentIdentity : std::string{},
|
t0Second ? t0Second->contentIdentity : std::string{},
|
||||||
t1Second ? t1Second->contentIdentity : std::string{},
|
t1Second ? t1Second->contentIdentity : std::string{},
|
||||||
|
|||||||
@@ -73,10 +73,10 @@ TEST(ParticleFieldBinding, ResolvesIndependentFieldKeyCadence) {
|
|||||||
ASSERT_TRUE(c.accepted()); ASSERT_TRUE(p.accepted());
|
ASSERT_TRUE(c.accepted()); ASSERT_TRUE(p.accepted());
|
||||||
EXPECT_FLOAT_EQ(c.projection->sampleState.mix, 0.5f);
|
EXPECT_FLOAT_EQ(c.projection->sampleState.mix, 0.5f);
|
||||||
EXPECT_FLOAT_EQ(p.projection->sampleState.mix, 0.5f);
|
EXPECT_FLOAT_EQ(p.projection->sampleState.mix, 0.5f);
|
||||||
EXPECT_EQ(c.projection->contentIdentity.t0Frame.validTimeMs, Base);
|
EXPECT_EQ(c.projection->contentIdentity.t0Coordinate.validTimeMs, Base);
|
||||||
EXPECT_EQ(c.projection->contentIdentity.t1Frame.validTimeMs, Base + 3 * Hour);
|
EXPECT_EQ(c.projection->contentIdentity.t1Coordinate.validTimeMs, Base + 3 * Hour);
|
||||||
EXPECT_EQ(p.projection->contentIdentity.t0Frame.validTimeMs, Base + Hour);
|
EXPECT_EQ(p.projection->contentIdentity.t0Coordinate.validTimeMs, Base + Hour);
|
||||||
EXPECT_EQ(p.projection->contentIdentity.t1Frame.validTimeMs, Base + 2 * Hour);
|
EXPECT_EQ(p.projection->contentIdentity.t1Coordinate.validTimeMs, Base + 2 * Hour);
|
||||||
EXPECT_NE(c.projection->t0Frame, p.projection->t0Frame);
|
EXPECT_NE(c.projection->t0Frame, p.projection->t0Frame);
|
||||||
EXPECT_NE(c.projection->t0ValidityStorage, p.projection->t0ValidityStorage);
|
EXPECT_NE(c.projection->t0ValidityStorage, p.projection->t0ValidityStorage);
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ TEST(ParticleFieldBinding, MixChangesSampleStateButNotContentIdentityOrStorage)
|
|||||||
EXPECT_EQ(at0.projection->t0PlaneStorage, at3.projection->t0PlaneStorage);
|
EXPECT_EQ(at0.projection->t0PlaneStorage, at3.projection->t0PlaneStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ParticleFieldBinding, ScalarRevisionChangesOnlyColorIdentity) {
|
TEST(ParticleFieldBinding, ProjectionIdentityIsolatedBySelectedRevision) {
|
||||||
particle::EncodedFieldStore store;
|
particle::EncodedFieldStore store;
|
||||||
submit(store, "weather.wind", makeFrame("wind", Base, 1, "0"));
|
submit(store, "weather.wind", makeFrame("wind", Base, 1, "0"));
|
||||||
submit(store, "weather.wind", makeFrame("wind", Base + Hour, 1, "1"));
|
submit(store, "weather.wind", makeFrame("wind", Base + Hour, 1, "1"));
|
||||||
@@ -118,6 +118,34 @@ TEST(ParticleFieldBinding, ScalarRevisionChangesOnlyColorIdentity) {
|
|||||||
EXPECT_EQ(beforeParticle.projection->contentIdentity, afterParticle.projection->contentIdentity);
|
EXPECT_EQ(beforeParticle.projection->contentIdentity, afterParticle.projection->contentIdentity);
|
||||||
EXPECT_EQ(beforeParticle.projection->t0FirstPlaneStorage, afterParticle.projection->t0FirstPlaneStorage);
|
EXPECT_EQ(beforeParticle.projection->t0FirstPlaneStorage, afterParticle.projection->t0FirstPlaneStorage);
|
||||||
EXPECT_EQ(beforeParticle.projection->t0ValidityStorage, afterParticle.projection->t0ValidityStorage);
|
EXPECT_EQ(beforeParticle.projection->t0ValidityStorage, afterParticle.projection->t0ValidityStorage);
|
||||||
|
|
||||||
|
auto vectorRevision = *afterParticle.projection->t0Frame;
|
||||||
|
vectorRevision.frameContentIdentity.generation = 3;
|
||||||
|
vectorRevision.frameContentIdentity.dataVersion = 3;
|
||||||
|
vectorRevision.planes[0].contentIdentity = "u-revised";
|
||||||
|
vectorRevision.planes[1].contentIdentity = "v-revised";
|
||||||
|
ASSERT_EQ(store.submit("weather.wind", std::make_shared<const particle::DecodedFieldFrame>(std::move(vectorRevision))),
|
||||||
|
particle::FieldSeriesSubmitResult::ReplacedRevision);
|
||||||
|
const auto afterVectorColor = particle::BindingProjectionResolver::resolve(store, color("weather.wind"), Base + Hour / 2);
|
||||||
|
const auto afterVectorParticle = particle::BindingProjectionResolver::resolve(store, wind(), Base + Hour / 2);
|
||||||
|
ASSERT_TRUE(afterVectorColor.accepted()); ASSERT_TRUE(afterVectorParticle.accepted());
|
||||||
|
EXPECT_EQ(afterColor.projection->contentIdentity, afterVectorColor.projection->contentIdentity);
|
||||||
|
EXPECT_NE(afterParticle.projection->contentIdentity, afterVectorParticle.projection->contentIdentity);
|
||||||
|
|
||||||
|
auto validityRevision = *afterVectorParticle.projection->t0Frame;
|
||||||
|
validityRevision.frameContentIdentity.generation = 4;
|
||||||
|
validityRevision.frameContentIdentity.dataVersion = 4;
|
||||||
|
validityRevision.validityContentIdentity = "valid-revised";
|
||||||
|
validityRevision.validityBits = std::make_shared<const std::vector<uint8_t>>(std::vector<uint8_t>{14});
|
||||||
|
validityRevision.validityPlane.contentIdentity = validityRevision.validityContentIdentity;
|
||||||
|
validityRevision.validityPlane.storage = validityRevision.validityBits;
|
||||||
|
ASSERT_EQ(store.submit("weather.wind", std::make_shared<const particle::DecodedFieldFrame>(std::move(validityRevision))),
|
||||||
|
particle::FieldSeriesSubmitResult::ReplacedRevision);
|
||||||
|
const auto afterValidityColor = particle::BindingProjectionResolver::resolve(store, color("weather.wind"), Base + Hour / 2);
|
||||||
|
const auto afterValidityParticle = particle::BindingProjectionResolver::resolve(store, wind(), Base + Hour / 2);
|
||||||
|
ASSERT_TRUE(afterValidityColor.accepted()); ASSERT_TRUE(afterValidityParticle.accepted());
|
||||||
|
EXPECT_NE(afterVectorColor.projection->contentIdentity, afterValidityColor.projection->contentIdentity);
|
||||||
|
EXPECT_NE(afterVectorParticle.projection->contentIdentity, afterValidityParticle.projection->contentIdentity);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ParticleFieldBinding, FailsClosedAndSupportsDirectionAngle) {
|
TEST(ParticleFieldBinding, FailsClosedAndSupportsDirectionAngle) {
|
||||||
|
|||||||
@@ -740,3 +740,9 @@ Android V3-08 单次综合验收已完成并关闭。Native `13.4.1-particle.2`
|
|||||||
- 新增 provider-neutral Color/Particle Binding 与 Resolver;每个 binding 按自己的 fieldKey 解析 FieldSeries Window,SampleState 单独保存 requested time/mix,Content Identity 不包含 mix 或视觉/播放状态。
|
- 新增 provider-neutral Color/Particle Binding 与 Resolver;每个 binding 按自己的 fieldKey 解析 FieldSeries Window,SampleState 单独保存 requested time/mix,Content Identity 不包含 mix 或视觉/播放状态。
|
||||||
- Vector U/V 与 DirectionAngle 都严格按 selected plane assertion fail-closed;Scalar-only revision 保持 Particle U/V/validity storage 与 identity 不变。
|
- Vector U/V 与 DirectionAngle 都严格按 selected plane assertion fail-closed;Scalar-only revision 保持 Particle U/V/validity storage 与 identity 不变。
|
||||||
- Linux runner 真实重建、规定 Core filter 50/50 PASS、`git diff --check` PASS;未执行 Android Gradle/APK/ADB/Jest/iOS/Metal,未进入 Renderer/GPU/Host/JNI/RN,STEP-075 关闭并停止。
|
- Linux runner 真实重建、规定 Core filter 50/50 PASS、`git diff --check` PASS;未执行 Android Gradle/APK/ADB/Jest/iOS/Metal,未进入 Renderer/GPU/Host/JNI/RN,STEP-075 关闭并停止。
|
||||||
|
|
||||||
|
### 2026-09-13 STEP-075 v2 Binding/Projection Identity 审计收口
|
||||||
|
|
||||||
|
- Color 与 Particle identity 统一以 `ProjectionFrameCoordinate(fieldID, validTimeMs)` 定位 T0/T1 window,只纳入各 binding 实际 selected plane 与 shared validity content identity;无关 plane 的 generation/dataVersion revision 不再污染另一 projection。
|
||||||
|
- Store public-path `ReplacedRevision` 对称覆盖:Scalar-only 仅改变 Color、U/V-only 仅改变 Particle、validity-only 同时改变两者;MIX 仍不进入 identity,in-window storage 仍稳定。
|
||||||
|
- Resolver 删除不可达 `InterpolationIncompatible`;相邻 frame compatibility 继续由既有 `FieldSeries::submit` fail-closed 保证。真实 Linux runner rebuild、规定过滤 50/50、`git diff --check` 全部 PASS;未执行 Android Gradle/APK/ADB/Jest,按范围关闭并停止,不进入 STEP-076。
|
||||||
|
|||||||
Reference in New Issue
Block a user