fix(07-03): #06-04-1 round-trip negative int64 and renumber param type codes

- Correct the int64 decode multiplier to 2147483648
- Encode negative int64 as dedicated type 5 with magnitude payload
- Shift float/bigint/long-string/short-string type codes to 6/7/9/length+9
- Un-skip the int64 round-trip case and add negative int64 focused cases
This commit is contained in:
unanmed 2026-09-15 18:35:18 +08:00
parent c7e5a643c2
commit 528b8c91a5
2 changed files with 91 additions and 48 deletions

View File

@ -79,7 +79,7 @@ function expectStepTyped(
expect(step.index).toBe(index);
}
// 整数与浮点参数用例:值 → 期望参数类型 tokenint64 见 #06-04-1 跳过
// 整数与浮点参数用例:值 → 期望参数类型 tokenint64 见下方专门用例
const paramCases: ReadonlyArray<readonly [ReplayParamValue, number]> = [
[-128, 1],
[127, 1],
@ -91,8 +91,8 @@ const paramCases: ReadonlyArray<readonly [ReplayParamValue, number]> = [
[-32769, 3],
[-2147483648, 3],
[2147483647, 3],
[1.5, 5],
[-1.5, 5]
[1.5, 6],
[-1.5, 6]
];
// 异质命令序列:参数个数与类型各不相同(含 boolean、多位宽整数、float、string、bigint
@ -271,7 +271,7 @@ describe('ReplayArray param codec', () => {
array.add(0, [100n]);
expect(array.get(0).params).toEqual([100n]);
expect(firstParamToken(array)).toBe(6);
expect(firstParamToken(array)).toBe(7);
});
// 疑似 bugbigint 编码循环缺少按字节右移,多字节 bigint 只能还原最低字节,详见 06-TEST-FINDINGS.md #06-04-2修复后取消 skip
@ -283,13 +283,43 @@ describe('ReplayArray param codec', () => {
expect(array.get(0).params).toEqual([value]);
});
// 疑似 bugint64 解码乘数误用 2147483647导致 int64 参数无法按写入值读回,详见 06-TEST-FINDINGS.md #06-04-1修复后取消 skip
it.skip('round-trips int64 values above the int32 range', () => {
// 验证超过 int32 范围的非负 int64 参数经 type 4 精确读回
it('round-trips int64 values above the int32 range', () => {
const array = createArray();
array.add(0, [2147483648]);
expect(array.get(0).params).toEqual([2147483648]);
});
// 负 int64 使用独立类型码 5载荷为幅值 |n|解码后取负A8小负值仍走更窄的位宽类型
it('round-trips negative int64 values through the dedicated type', () => {
const negativeCases: ReadonlyArray<readonly [number, number]> = [
[-1, 1],
[-2147483649, 5],
[-4294967297, 5]
];
for (const [value, token] of negativeCases) {
const array = createArray();
array.add(0, [value]);
expectParamTyped(array.get(0).params[0], value);
expect(firstParamToken(array)).toBe(token);
}
});
// 非负 int64 仍为 type 4上界 2^53 - 1 精确读回,幅值编码逐位不变
it('round-trips non-negative int64 values through type 4', () => {
const nonNegativeCases: readonly number[] = [
2147483648, 9007199254740991
];
for (const value of nonNegativeCases) {
const array = createArray();
array.add(0, [value]);
expectParamTyped(array.get(0).params[0], value);
expect(firstParamToken(array)).toBe(4);
}
});
// 疑似 bug多字节 bigint 与超 int32 的 int64 混在同一步时同样失真,详见 06-TEST-FINDINGS.md #06-04-1/#06-04-2修复两个编码与解码缺陷后取消 skip
it.skip('round-trips a heterogeneous step mixing a multi-byte bigint and an int64 value', () => {
const array = createArray();
@ -303,32 +333,32 @@ describe('ReplayArray param codec', () => {
]);
});
// 验证短字符串参数使用内联类型 token 并读回一致
// 验证短字符串参数使用内联类型 token(长度 + 9并读回一致
it('round-trips a short string with an inline type token', () => {
const array = createArray();
array.add(0, ['hi']);
expect(array.get(0).params).toEqual(['hi']);
expect(firstParamToken(array)).toBe(9);
expect(firstParamToken(array)).toBe(11);
});
// 验证超过内联长度的字符串参数使用 type 7 并读回一致
// 验证超过内联长度的字符串参数使用 type 9 并读回一致
it('round-trips a long string through the length-prefixed type', () => {
const array = createArray();
const value = 'a'.repeat(300);
array.add(0, [value]);
expect(array.get(0).params).toEqual([value]);
expect(firstParamToken(array)).toBe(7);
expect(firstParamToken(array)).toBe(9);
});
// 验证空字符串参数回退到 type 7 并读回为空串
// 验证空字符串参数回退到 type 9 并读回为空串
it('round-trips an empty string', () => {
const array = createArray();
array.add(0, ['']);
expect(array.get(0).params).toEqual(['']);
expect(firstParamToken(array)).toBe(7);
expect(firstParamToken(array)).toBe(9);
});
// 验证单条录像步的多个不同类型参数按顺序完整读回

View File

@ -16,11 +16,13 @@ interface INormalizedParam {
* - 1: int8
* - 2: int16
* - 3: int32
* - 4: int64
* - 5: float
* - 6: bigint
* - 7: string
* - 8 ~ 255: n - 7
* - 4: 非负 int64
* - 5: int64
* - 6: float
* - 7: 非负 bigint
* - 8: bigint
* - 9: string int32
* - 10 ~ 255: n - 9
*/
readonly paramType: number;
@ -237,14 +239,18 @@ export class ReplayArray implements IReplayArray {
// 3 - int32
type = 3;
byte = 5;
} else {
// 4 - int64
} else if (param >= 2147483648) {
// 4 - 非负 int64
type = 4;
byte = 9;
} else {
// 5 - 负 int64载荷为幅值
type = 5;
byte = 9;
}
} else {
// 5 - float
type = 5;
// 6 - float
type = 6;
byte = 9;
}
return {
@ -253,7 +259,7 @@ export class ReplayArray implements IReplayArray {
byteLength: byte
};
} else if (typeof param === 'bigint') {
// 6 - bigint
// 7 - bigint
const wall = 2n ** 2047n;
if (param > wall - 1n || param < -wall) {
logger.warn(152);
@ -269,23 +275,23 @@ export class ReplayArray implements IReplayArray {
arr[i] = Number(remain);
}
return {
paramType: 6,
paramType: 7,
paramValue: arr,
byteLength: arr.length + 2
};
} else if (typeof param === 'string') {
const arr = this.textEncoder.encode(param);
if (arr.length > 0 && arr.length <= 248) {
// 8 ~ 255 - string
if (arr.length > 0 && arr.length <= 246) {
// 10 ~ 255 - string
return {
paramType: arr.length + 7,
paramType: arr.length + 9,
paramValue: arr,
byteLength: arr.length + 1
};
} else {
// 7 - string
// 9 - string
return {
paramType: 7,
paramType: 9,
paramValue: arr,
byteLength: arr.length + 5
};
@ -364,25 +370,26 @@ export class ReplayArray implements IReplayArray {
} else if (param.paramType === 3) {
// 3 - int32
this.paramView.setInt32(index + 1, num);
} else if (param.paramType === 4) {
// 4 - int64
const high = Math.floor(num / 2147483648);
const low = num % 2147483648;
} else if (param.paramType === 4 || param.paramType === 5) {
// 4 - 非负 int64 / 5 - 负 int64均以幅值拆分高低 32 位
const magnitude = num < 0 ? -num : num;
const high = Math.floor(magnitude / 2147483648);
const low = magnitude - high * 2147483648;
this.paramView.setInt32(index + 1, low);
this.paramView.setInt32(index + 5, high);
} else if (param.paramType === 5) {
// 5 - float
this.paramView.setFloat64(index + 1, num);
} else if (param.paramType === 6) {
// 6 - bigint
// 6 - float
this.paramView.setFloat64(index + 1, num);
} else if (param.paramType === 7) {
// 7 - bigint
this.paramArray[index + 1] = arr.length;
this.paramArray.set(arr, index + 2);
} else if (param.paramType === 7) {
// 7 - string
} else if (param.paramType === 9) {
// 9 - string
this.paramView.setInt32(index + 1, arr.length);
this.paramArray.set(arr, index + 5);
} else {
// 8 ~ 256 - string
// 10 ~ 255 - string
this.paramArray.set(arr, index + 1);
}
index += param.byteLength;
@ -614,17 +621,23 @@ export class ReplayArray implements IReplayArray {
byte = 5;
value = this.paramView.getInt32(startIndex + 1);
} else if (type === 4) {
// 4 - int64
// 4 - 非负 int64
const low = this.paramView.getInt32(startIndex + 1);
const high = this.paramView.getInt32(startIndex + 5);
byte = 9;
value = low + high * 2147483647;
value = low + high * 2147483648;
} else if (type === 5) {
// 5 - float
// 5 - 负 int64读回幅值后取负
const low = this.paramView.getInt32(startIndex + 1);
const high = this.paramView.getInt32(startIndex + 5);
byte = 9;
value = -(low + high * 2147483648);
} else if (type === 6) {
// 6 - float
byte = 9;
value = this.paramView.getFloat64(startIndex + 1);
} else if (type === 6) {
// 6 - bigint
} else if (type === 7) {
// 7 - bigint
const length = this.paramView.getInt8(startIndex + 1);
let base = 0n;
for (let i = 0; i < length; i++) {
@ -633,16 +646,16 @@ export class ReplayArray implements IReplayArray {
}
byte = length + 2;
value = base;
} else if (type === 7) {
// 7 - string
} else if (type === 9) {
// 9 - string
const length = this.paramView.getInt32(startIndex + 1);
const endIndex = startIndex + 5 + length;
const arr = this.paramArray.slice(startIndex + 5, endIndex);
byte = length + 5;
value = this.textDecoder.decode(arr);
} else {
// 8 ~ 255 - string
const length = type - 7;
// 10 ~ 255 - string
const length = type - 9;
const endIndex = startIndex + 1 + length;
const arr = this.paramArray.slice(startIndex + 1, endIndex);
byte = length + 1;