fix: close STEP-073 validator runner 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,45 @@
|
|||||||
|
# STEP-073 — QBIN2 V4 Core 与通用 Field Profile Validator 最终关闭修正 v4
|
||||||
|
|
||||||
|
状态:完成
|
||||||
|
|
||||||
|
日期:2026-09-13
|
||||||
|
|
||||||
|
## 基线
|
||||||
|
|
||||||
|
- 开始 HEAD:`0b66a5cd30e91423cbc864a327a53bed06cae91c`。
|
||||||
|
- 开始工作区含 STEP-062 文档修改及多项历史 untracked 文件;本 Step 未修改、暂存或提交它们。
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
关闭独立审计在实际重链 `mbgl-test-runner` 后发现的三项问题:Frame 的唯一共享 validity domain、实际已知 Core semantic 的错误优先级,以及 ForecastClock 测试的 pause/resume 语义。
|
||||||
|
|
||||||
|
## 完成项
|
||||||
|
|
||||||
|
- [x] `DecodedFieldFrame` 允许空 numeric `planes[]`,完整 designated validity 仍结构合法;所缺 required numeric plane 由 Validator 返回 `MissingRequiredPlane`。
|
||||||
|
- [x] designated validity 强制 `Validity/Bitset/NONE/group0/None/REQUIRED`、cell count 与 bit payload 长度一致,并与 `validityBits` 使用同一 `shared_ptr`;任一违反均为 `InvalidFrame`。
|
||||||
|
- [x] 禁止 `planes[]` 出现第二张 Validity plane,确保一帧只有唯一共享 validity domain。
|
||||||
|
- [x] 保留静态数据表驱动的 Core semantic contract,并在 required-plane descriptor 匹配前检查实际 plane;semantic 1 Scalar 伪装、semantic 10 component/group 冲突均返回 `SemanticContractMismatch`。
|
||||||
|
- [x] unknown Scalar 及 opaque unit 的可扩展行为保持;Direction circular quantization v3 规则未变。
|
||||||
|
- [x] ForecastClock 测试在 `pause()` 后显式 `play()`,未改变生产 `pause()` 语义。
|
||||||
|
|
||||||
|
## 允许修改
|
||||||
|
|
||||||
|
仅修改 Field Runtime Core、通用 Validator、对应 C++ 测试和本 Step 文档/报告/开发记录。
|
||||||
|
|
||||||
|
## 禁止修改确认
|
||||||
|
|
||||||
|
未修改 QBIN2 Decoder、Binding/Projection、Renderer、shader、JNI、`platform/android/**`、`packages/maplibre-particle-rn/**`、`integration/react-native/**`、Style JSON/source schema 或 V3 `submitField(...)` 路径;未执行 Android Gradle、APK、ADB、真机、Jest 或 iOS/Metal。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
构建/测试完整输出均落入 `/tmp` 限流日志(仓库未提供 build-output-throttle 指定的 helper script,仅在失败时读取有限末尾窗口)。
|
||||||
|
|
||||||
|
- [x] `cmake --build build-linux-opengl --target mbgl-test-runner -j3`:实际重编译并重链 PASS。
|
||||||
|
- [x] `mbgl-test-runner --gtest_list_tests`:确认包含 `ParticleFieldProfileValidator.` 与 `ParticleFieldRuntime.`。
|
||||||
|
- [x] `mbgl-test-runner --gtest_filter='ParticleFieldRuntime.*:ParticleFieldProfileValidator.*'`:实际运行 11 tests,11 PASS,0 failed;绝非 0 tests。
|
||||||
|
- [x] `mbgl-test-runner --gtest_filter='Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*'`:实际运行 11 tests,11 PASS,0 failed。
|
||||||
|
- [x] `git diff --check`:PASS。
|
||||||
|
|
||||||
|
## 当前结果、阻塞与下一步
|
||||||
|
|
||||||
|
STEP-073 v4 的关闭条件已满足,无范围内阻塞。停止于此,等待 ChatGPT 独立复验;不进入 STEP-074。
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# STEP-073 QBIN2 V4 Core 与通用 Field Profile Validator 完成报告 v4
|
||||||
|
|
||||||
|
日期:2026-09-13
|
||||||
|
|
||||||
|
## 结果
|
||||||
|
|
||||||
|
STEP-073 最终关闭修正完成。Frame Core 现在明确维护 single shared validity invariant;空 numeric plane list 不再被结构层误判。Validator 在 required descriptor 匹配前拒绝实际保留 Core semantic 的物理含义冲突,unknown Scalar opaque 行为与 Direction v3 量化规则保持。
|
||||||
|
|
||||||
|
## 实际修改
|
||||||
|
|
||||||
|
- `DecodedFieldFrame::isStructurallyValid()` 对 designated validity 强制 `Validity`、`Bitset`、`NONE`、group 0、`None` component、`REQUIRED`、正确 cells/bit payload,并要求 descriptor storage 与 `validityBits` 是同一共享引用;常规 `planes[]` 不允许任何 Validity plane。
|
||||||
|
- 删除空 `planes[]` 的结构拒绝。于是完整 validity-only Frame 保持结构有效,Generic Validator 对缺失 Profile numeric required plane 返回 `MissingRequiredPlane`。
|
||||||
|
- 以既有静态协议表先核验 actual planes,故 semantic 10 的 component/group 错误,以及 semantic 1 伪装成 Scalar,都优先返回 `SemanticContractMismatch`,而不是 `MissingRequiredPlane` 或 `PlaneDescriptorMismatch`。
|
||||||
|
- 测试新增 second validity、designated validity descriptor 各字段、`requiredFlag=false` 与 storage identity 的 fail-closed 覆盖;修正 ForecastClock pause 后的测试恢复播放。
|
||||||
|
|
||||||
|
## 独立 runner 验证
|
||||||
|
|
||||||
|
`mbgl-test-runner` 本体已重新构建和链接,并通过 `--gtest_list_tests` 确认加载 `ParticleFieldProfileValidator.`、`ParticleFieldRuntime.`。
|
||||||
|
|
||||||
|
- V4 filter:实际 `Running 11 tests`,`PASSED 11 tests`,failed=0。
|
||||||
|
- V3 定向回归:实际 `Running 11 tests`,`PASSED 11 tests`,failed=0。
|
||||||
|
- `git diff --check`:PASS。
|
||||||
|
|
||||||
|
构建/测试输出按节流要求写入 `/tmp` 日志;仓库当前不存在 skill 所指的 `scripts/run_limited.py` helper。首次 sandbox 构建因只读 ccache 失败,获准后以同一受限日志模式重跑并成功,不涉及源代码失败。
|
||||||
|
|
||||||
|
## 范围与未完成项
|
||||||
|
|
||||||
|
未触及 QBIN2 Decoder、Binding/Projection、Renderer、JNI、Android、RN、Style schema 或 V3 `submitField(...)`。未执行 Android Gradle、APK、ADB、真机、Jest、iOS/Metal。没有范围内未完成项;按任务要求停止,等待独立复验,不进入 STEP-074。
|
||||||
@@ -99,6 +99,15 @@ FieldProfileValidationResult FieldProfileValidator::validate(const ResolvedField
|
|||||||
actual.push_back(&frame.validityPlane);
|
actual.push_back(&frame.validityPlane);
|
||||||
for (const auto& plane : frame.planes) actual.push_back(&plane);
|
for (const auto& plane : frame.planes) actual.push_back(&plane);
|
||||||
|
|
||||||
|
// Inspect actual reserved Core semantics before profile matching. Otherwise
|
||||||
|
// an invalid known semantic can be hidden behind a missing/mismatched
|
||||||
|
// profile descriptor instead of reporting its protocol violation.
|
||||||
|
for (const auto plane : actual) {
|
||||||
|
if (!matchesCoreSemanticContract(plane->key.semanticID, plane->valueClass, plane->unitID,
|
||||||
|
plane->key.groupID, plane->key.component))
|
||||||
|
return result(FieldProfileValidationCode::SemanticContractMismatch, "actual Core semantic descriptor");
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto& required : contract.requiredPlanes) {
|
for (const auto& required : contract.requiredPlanes) {
|
||||||
const auto same = std::find_if(actual.begin(), actual.end(), [&required](const PlaneRef plane) {
|
const auto same = std::find_if(actual.begin(), actual.end(), [&required](const PlaneRef plane) {
|
||||||
return sameKey(required, *plane);
|
return sameKey(required, *plane);
|
||||||
@@ -109,9 +118,6 @@ FieldProfileValidationResult FieldProfileValidator::validate(const ResolvedField
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const auto plane : actual) {
|
for (const auto plane : actual) {
|
||||||
if (!matchesCoreSemanticContract(plane->key.semanticID, plane->valueClass, plane->unitID, plane->key.groupID,
|
|
||||||
plane->key.component))
|
|
||||||
return result(FieldProfileValidationCode::SemanticContractMismatch, "actual Core semantic descriptor");
|
|
||||||
const auto declared = std::find_if(contract.requiredPlanes.begin(), contract.requiredPlanes.end(),
|
const auto declared = std::find_if(contract.requiredPlanes.begin(), contract.requiredPlanes.end(),
|
||||||
[plane](const FieldPlaneRequirement& requirement) {
|
[plane](const FieldPlaneRequirement& requirement) {
|
||||||
return matches(requirement, *plane);
|
return matches(requirement, *plane);
|
||||||
|
|||||||
@@ -99,16 +99,22 @@ bool DecodedFieldGrid::operator==(const DecodedFieldGrid& other) const {
|
|||||||
|
|
||||||
bool DecodedFieldFrame::isStructurallyValid() const {
|
bool DecodedFieldFrame::isStructurallyValid() const {
|
||||||
if (!frameContentIdentity.isValid() || referenceTimeMs == 0 || !grid.isValid() || !validityBits ||
|
if (!frameContentIdentity.isValid() || referenceTimeMs == 0 || !grid.isValid() || !validityBits ||
|
||||||
validityBits->empty() || validityContentIdentity.empty() || planes.empty()) return false;
|
validityBits->empty() || validityContentIdentity.empty()) return false;
|
||||||
const uint64_t cells = static_cast<uint64_t>(grid.width) * grid.height;
|
const uint64_t cells = static_cast<uint64_t>(grid.width) * grid.height;
|
||||||
const uint64_t expectedValidityBytes = (cells + 7u) / 8u;
|
const uint64_t expectedValidityBytes = (cells + 7u) / 8u;
|
||||||
if (validityBits->size() != expectedValidityBytes) return false;
|
if (validityBits->size() != expectedValidityBytes) return false;
|
||||||
if (!validityPlane.isStructurallyValid(cells) || validityPlane.contentIdentity != validityContentIdentity ||
|
if (!validityPlane.isStructurallyValid(cells) || validityPlane.valueClass != FieldValueClass::Validity ||
|
||||||
validityPlane.storage != validityBits) return false;
|
validityPlane.dtype != FieldDType::Bitset || validityPlane.unitID != FieldUnitNone ||
|
||||||
|
validityPlane.key.groupID != 0 || validityPlane.key.component != FieldComponent::None ||
|
||||||
|
!validityPlane.requiredFlag || validityPlane.contentIdentity != validityContentIdentity ||
|
||||||
|
validityPlane.storage != validityBits)
|
||||||
|
return false;
|
||||||
std::set<FieldPlaneKey> keys;
|
std::set<FieldPlaneKey> keys;
|
||||||
if (!keys.insert(validityPlane.key).second) return false;
|
if (!keys.insert(validityPlane.key).second) return false;
|
||||||
for (const auto& plane : planes) {
|
for (const auto& plane : planes) {
|
||||||
if (!plane.isStructurallyValid(cells) || !keys.insert(plane.key).second) return false;
|
if (!plane.isStructurallyValid(cells) || plane.valueClass == FieldValueClass::Validity ||
|
||||||
|
!keys.insert(plane.key).second)
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ TEST(ParticleFieldProfileValidator, AcceptsGenericProfileAndOpaqueScalarUnit) {
|
|||||||
TEST(ParticleFieldProfileValidator, EnforcesRequiredPlanesAndAdditionalPlanePolicy) {
|
TEST(ParticleFieldProfileValidator, EnforcesRequiredPlanesAndAdditionalPlanePolicy) {
|
||||||
auto c = contract(); auto f = frame();
|
auto c = contract(); auto f = frame();
|
||||||
f.planes.clear();
|
f.planes.clear();
|
||||||
|
EXPECT_TRUE(f.isStructurallyValid());
|
||||||
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::MissingRequiredPlane);
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::MissingRequiredPlane);
|
||||||
f = frame(); f.planes.front().unitID = 99;
|
f = frame(); f.planes.front().unitID = 99;
|
||||||
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::PlaneDescriptorMismatch);
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::PlaneDescriptorMismatch);
|
||||||
@@ -85,6 +86,42 @@ TEST(ParticleFieldProfileValidator, EnforcesRequiredPlanesAndAdditionalPlanePoli
|
|||||||
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::Accepted);
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::Accepted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ParticleFieldProfileValidator, EnforcesSingleSharedValidityStructuralInvariant) {
|
||||||
|
auto f = frame();
|
||||||
|
const auto numbers = f.planes.front().storage;
|
||||||
|
|
||||||
|
f.planes.push_back(plane(901, particle::FieldValueClass::Validity, particle::FieldUnitNone, 0,
|
||||||
|
particle::FieldComponent::None, particle::FieldDType::Bitset, false, numbers, "second-validity"));
|
||||||
|
EXPECT_FALSE(f.isStructurallyValid());
|
||||||
|
EXPECT_EQ(validate(contract(), f), particle::FieldProfileValidationCode::InvalidFrame);
|
||||||
|
|
||||||
|
f = frame();
|
||||||
|
f.validityPlane.requiredFlag = false;
|
||||||
|
EXPECT_FALSE(f.isStructurallyValid());
|
||||||
|
EXPECT_EQ(validate(contract(), f), particle::FieldProfileValidationCode::InvalidFrame);
|
||||||
|
|
||||||
|
f = frame(); f.validityPlane.valueClass = particle::FieldValueClass::Scalar;
|
||||||
|
EXPECT_FALSE(f.isStructurallyValid());
|
||||||
|
EXPECT_EQ(validate(contract(), f), particle::FieldProfileValidationCode::InvalidFrame);
|
||||||
|
f = frame(); f.validityPlane.dtype = particle::FieldDType::UInt8;
|
||||||
|
EXPECT_FALSE(f.isStructurallyValid());
|
||||||
|
EXPECT_EQ(validate(contract(), f), particle::FieldProfileValidationCode::InvalidFrame);
|
||||||
|
f = frame(); f.validityPlane.unitID = particle::FieldUnitMetersPerSecond;
|
||||||
|
EXPECT_FALSE(f.isStructurallyValid());
|
||||||
|
EXPECT_EQ(validate(contract(), f), particle::FieldProfileValidationCode::InvalidFrame);
|
||||||
|
f = frame(); f.validityPlane.key.groupID = 1;
|
||||||
|
EXPECT_FALSE(f.isStructurallyValid());
|
||||||
|
EXPECT_EQ(validate(contract(), f), particle::FieldProfileValidationCode::InvalidFrame);
|
||||||
|
f = frame(); f.validityPlane.key.component = particle::FieldComponent::UEast;
|
||||||
|
EXPECT_FALSE(f.isStructurallyValid());
|
||||||
|
EXPECT_EQ(validate(contract(), f), particle::FieldProfileValidationCode::InvalidFrame);
|
||||||
|
|
||||||
|
f = frame();
|
||||||
|
f.validityPlane.storage = std::make_shared<const std::vector<uint8_t>>(*f.validityBits);
|
||||||
|
EXPECT_FALSE(f.isStructurallyValid());
|
||||||
|
EXPECT_EQ(validate(contract(), f), particle::FieldProfileValidationCode::InvalidFrame);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(ParticleFieldProfileValidator, EnforcesVelocityDirectionAndValidityCoreRules) {
|
TEST(ParticleFieldProfileValidator, EnforcesVelocityDirectionAndValidityCoreRules) {
|
||||||
EXPECT_EQ(particle::FieldUnitNone, 0);
|
EXPECT_EQ(particle::FieldUnitNone, 0);
|
||||||
EXPECT_EQ(particle::FieldUnitMetersPerSecond, 1);
|
EXPECT_EQ(particle::FieldUnitMetersPerSecond, 1);
|
||||||
@@ -121,11 +158,11 @@ TEST(ParticleFieldProfileValidator, EnforcesVelocityDirectionAndValidityCoreRule
|
|||||||
c.requiredPlanes.front().unitID = 2;
|
c.requiredPlanes.front().unitID = 2;
|
||||||
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::SemanticContractMismatch);
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::SemanticContractMismatch);
|
||||||
c = contract(); f = frame(); f.validityPlane.dtype = particle::FieldDType::UInt8;
|
c = contract(); f = frame(); f.validityPlane.dtype = particle::FieldDType::UInt8;
|
||||||
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit);
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidFrame);
|
||||||
f = frame(); f.validityPlane.unitID = 1;
|
f = frame(); f.validityPlane.unitID = 1;
|
||||||
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit);
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidFrame);
|
||||||
f = frame(); f.validityPlane.valueClass = particle::FieldValueClass::Scalar;
|
f = frame(); f.validityPlane.valueClass = particle::FieldValueClass::Scalar;
|
||||||
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit);
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ParticleFieldProfileValidator, EnforcesReservedCoreSemanticContractsFromProtocolTable) {
|
TEST(ParticleFieldProfileValidator, EnforcesReservedCoreSemanticContractsFromProtocolTable) {
|
||||||
@@ -149,7 +186,7 @@ TEST(ParticleFieldProfileValidator, EnforcesReservedCoreSemanticContractsFromPro
|
|||||||
c.requiredPlanes.back().valueClass = particle::FieldValueClass::Scalar;
|
c.requiredPlanes.back().valueClass = particle::FieldValueClass::Scalar;
|
||||||
f.validityPlane.key.semanticID = 3;
|
f.validityPlane.key.semanticID = 3;
|
||||||
f.validityPlane.valueClass = particle::FieldValueClass::Scalar;
|
f.validityPlane.valueClass = particle::FieldValueClass::Scalar;
|
||||||
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::SemanticContractMismatch);
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ParticleFieldProfileValidator, EnforcesDirectionCircularQuantizationContract) {
|
TEST(ParticleFieldProfileValidator, EnforcesDirectionCircularQuantizationContract) {
|
||||||
@@ -264,6 +301,7 @@ TEST(ParticleFieldRuntime, ForecastClockPreservesHalfMillisecondRemainder) {
|
|||||||
|
|
||||||
ASSERT_TRUE(clock.setRequestedForecastTime(3'000));
|
ASSERT_TRUE(clock.setRequestedForecastTime(3'000));
|
||||||
ASSERT_TRUE(clock.setPlaybackSpeed(1.0f));
|
ASSERT_TRUE(clock.setPlaybackSpeed(1.0f));
|
||||||
|
clock.play();
|
||||||
EXPECT_TRUE(clock.advance(3));
|
EXPECT_TRUE(clock.advance(3));
|
||||||
EXPECT_EQ(clock.requestedForecastTime(), 3'003);
|
EXPECT_EQ(clock.requestedForecastTime(), 3'003);
|
||||||
ASSERT_TRUE(clock.setPlaybackSpeed(2.0f));
|
ASSERT_TRUE(clock.setPlaybackSpeed(2.0f));
|
||||||
|
|||||||
@@ -704,3 +704,9 @@ Android V3-08 单次综合验收已完成并关闭。Native `13.4.1-particle.2`
|
|||||||
- Generic Validator 新增静态数据表驱动的 Native Core semantic contract:`1/2` 固定 U/V velocity、`3` 固定 shared validity、`10/11` 固定 Direction DEGREE/NONE/group0;Contract 与 Frame 的冲突均结构化拒绝为 `SemanticContractMismatch`,不依赖任何 profile 或产品名称。
|
- Generic Validator 新增静态数据表驱动的 Native Core semantic contract:`1/2` 固定 U/V velocity、`3` 固定 shared validity、`10/11` 固定 Direction DEGREE/NONE/group0;Contract 与 Frame 的冲突均结构化拒绝为 `SemanticContractMismatch`,不依赖任何 profile 或产品名称。
|
||||||
- `DIRECTION_ANGLE` 通用 Circular Quantization 现强制 `DEGREE=4`、NONE、group0、UINT16、offset=0、`scale=360/65536`(极小 float32 tolerance);opaque unknown Scalar unit 保持可扩展。
|
- `DIRECTION_ANGLE` 通用 Circular Quantization 现强制 `DEGREE=4`、NONE、group0、UINT16、offset=0、`scale=360/65536`(极小 float32 tolerance);opaque unknown Scalar unit 保持可扩展。
|
||||||
- Linux OpenGL `mbgl-test`、Validator/Runtime 过滤测试和 V3 Field/Timeline 过滤回归均 PASS,`git diff --check` PASS;未执行 Android Gradle/APK/ADB/真机/Jest/iOS-Metal,未进入 QBIN Decoder、Binding/Projection、Renderer、JNI、Android 或 RN。
|
- Linux OpenGL `mbgl-test`、Validator/Runtime 过滤测试和 V3 Field/Timeline 过滤回归均 PASS,`git diff --check` PASS;未执行 Android Gradle/APK/ADB/真机/Jest/iOS-Metal,未进入 QBIN Decoder、Binding/Projection、Renderer、JNI、Android 或 RN。
|
||||||
|
|
||||||
|
### 2026-09-13 STEP-073 v4 最终关闭修正
|
||||||
|
|
||||||
|
- `DecodedFieldFrame` 固化 single shared validity domain:designated validity 必须为 `Validity/Bitset/NONE/group0/None/REQUIRED`,与 `validityBits` 同一共享引用;普通 `planes[]` 禁止第二张 Validity。空 numeric `planes[]` 仍结构有效,缺 Profile numeric requirement 正确返回 `MissingRequiredPlane`。
|
||||||
|
- Validator 在 required descriptor 匹配前检查 actual reserved Core semantic;semantic 1 Scalar 伪装、semantic 10 的 component/group 冲突优先为 `SemanticContractMismatch`。unknown Scalar opaque 与 Direction circular quantization 规则保持。
|
||||||
|
- ForecastClock 测试在 pause 后显式恢复 `play()`,生产 pause 语义未改。实际重建并重链 `mbgl-test-runner` 后,V4 filter 11/11 PASS、V3 定向回归 11/11 PASS、`git diff --check` PASS;未进入 Decoder/Renderer/JNI/Android/RN,停止等待独立复验。
|
||||||
|
|||||||
Reference in New Issue
Block a user