Files
kam-mobile/code/GameDiagnosticsReport.pas
T

353 lines
12 KiB
ObjectPascal
Raw Normal View History

2026-09-15 10:16:31 +05:00
unit GameDiagnosticsReport;
{$mode objfpc}{$H+}{$codepage utf8}
interface
type
TDiagnosticStatus = (dsNotRun, dsPass, dsFail);
TDiagnosticStageId = (
dsiResourceExists,
dsiResourceOpen,
dsiCallerStreamLoad,
dsiStreamState,
dsiSeekReadConsistency
);
2026-09-17 13:48:56 +05:00
TTerrainReadinessDiagnosticStageId = (
trsiRxTilesLoad,
trsiMapStreamLoad,
trsiMapHeader,
trsiTileRecords
);
2026-09-15 10:16:31 +05:00
TOptionalString = record
HasValue: Boolean;
Value: string;
end;
TOptionalInt64 = record
HasValue: Boolean;
Value: Int64;
end;
TOptionalBoolean = record
HasValue: Boolean;
Value: Boolean;
end;
TOptionalDiagnosticStageId = record
HasValue: Boolean;
Value: TDiagnosticStageId;
end;
2026-09-17 13:48:56 +05:00
TOptionalTerrainReadinessDiagnosticStageId = record
HasValue: Boolean;
Value: TTerrainReadinessDiagnosticStageId;
end;
2026-09-15 10:16:31 +05:00
TDiagnosticError = record
Present: Boolean;
ClassName: TOptionalString;
MessageText: TOptionalString;
end;
TFileIODiagnosticStreamResult = record
Size: TOptionalInt64;
PositionAfterLoad: TOptionalInt64;
ReadOffset: TOptionalInt64;
RequestedReadCount: TOptionalInt64;
FirstReadCount: TOptionalInt64;
PositionAfterFirstRead: TOptionalInt64;
SeekBackPosition: TOptionalInt64;
SecondReadCount: TOptionalInt64;
PositionAfterSecondRead: TOptionalInt64;
RepeatEqual: TOptionalBoolean;
end;
TFileIODiagnosticResult = record
Status: TDiagnosticStatus;
FailureStage: TOptionalDiagnosticStageId;
Error: TDiagnosticError;
Stages: array[TDiagnosticStageId] of TDiagnosticStatus;
Stream: TFileIODiagnosticStreamResult;
end;
2026-09-17 13:48:56 +05:00
TTerrainReadinessDiagnosticEvidence = record
RxTilesLoaded: TOptionalBoolean;
GeneratedTransitionsReady: TOptionalBoolean;
MapX: TOptionalInt64;
MapY: TOptionalInt64;
GameRev: TOptionalInt64;
ExpectedTileCount: TOptionalInt64;
CompletedTileCount: TOptionalInt64;
end;
TTerrainReadinessDiagnosticResult = record
Status: TDiagnosticStatus;
FailureStage: TOptionalTerrainReadinessDiagnosticStageId;
Error: TDiagnosticError;
Stages: array[TTerrainReadinessDiagnosticStageId] of TDiagnosticStatus;
Evidence: TTerrainReadinessDiagnosticEvidence;
end;
2026-09-15 10:16:31 +05:00
TDiagnosticApplicationMetadata = record
Name: string;
PackageName: string;
Version: string;
VersionCode: Int64;
BuildId: TOptionalString;
end;
TDiagnosticRuntimeMetadata = record
Platform: string;
OsVersion: TOptionalString;
DeviceModel: TOptionalString;
CpuArch: TOptionalString;
end;
TDiagnosticSelectedMapMetadata = record
RelativePath: string;
Url: string;
end;
TDiagnosticReportState = record
ApplicationInfo: TDiagnosticApplicationMetadata;
RuntimeInfo: TDiagnosticRuntimeMetadata;
SelectedMap: TDiagnosticSelectedMapMetadata;
OverallStatus: TDiagnosticStatus;
FileIO: TFileIODiagnosticResult;
2026-09-17 13:48:56 +05:00
TerrainReadiness: TTerrainReadinessDiagnosticResult;
2026-09-15 10:16:31 +05:00
end;
function CurrentDiagnosticReport: TDiagnosticReportState;
procedure ReplaceFileIODiagnosticResult(const AResult: TFileIODiagnosticResult);
2026-09-17 13:48:56 +05:00
procedure ReplaceTerrainReadinessDiagnosticResult(
const AResult: TTerrainReadinessDiagnosticResult);
2026-09-15 10:16:31 +05:00
function DiagnosticStatusToken(const AStatus: TDiagnosticStatus): string;
function DiagnosticStageIdToken(const AStage: TDiagnosticStageId): string;
2026-09-17 13:48:56 +05:00
function TerrainReadinessDiagnosticStageIdToken(
const AStage: TTerrainReadinessDiagnosticStageId): string;
2026-09-15 10:16:31 +05:00
function DiagnosticReportJsonSnapshot: string;
implementation
uses
fpjson, GameSelectedMap;
var
CurrentFileIO: TFileIODiagnosticResult;
2026-09-17 13:48:56 +05:00
CurrentTerrainReadiness: TTerrainReadinessDiagnosticResult;
2026-09-15 10:16:31 +05:00
function DiagnosticStatusToken(const AStatus: TDiagnosticStatus): string;
begin
case AStatus of
dsNotRun: Result := 'NOT_RUN';
dsPass: Result := 'PASS';
dsFail: Result := 'FAIL';
end;
end;
function DiagnosticStageIdToken(const AStage: TDiagnosticStageId): string;
begin
case AStage of
dsiResourceExists: Result := 'resource_exists';
dsiResourceOpen: Result := 'resource_open';
dsiCallerStreamLoad: Result := 'caller_stream_load';
dsiStreamState: Result := 'stream_state';
dsiSeekReadConsistency: Result := 'seek_read_consistency';
end;
end;
2026-09-17 13:48:56 +05:00
function TerrainReadinessDiagnosticStageIdToken(
const AStage: TTerrainReadinessDiagnosticStageId): string;
begin
case AStage of
trsiRxTilesLoad: Result := 'rx_tiles_load';
trsiMapStreamLoad: Result := 'map_stream_load';
trsiMapHeader: Result := 'map_header';
trsiTileRecords: Result := 'tile_records';
end;
end;
2026-09-15 10:16:31 +05:00
function CurrentDiagnosticReport: TDiagnosticReportState;
begin
Result.ApplicationInfo.Name := 'kam_mobile';
Result.ApplicationInfo.PackageName := 'org.kammobile.kamremake';
Result.ApplicationInfo.Version := '0.4.0';
Result.ApplicationInfo.VersionCode := 30;
Result.ApplicationInfo.BuildId.HasValue := False;
Result.RuntimeInfo.Platform := 'android';
Result.RuntimeInfo.OsVersion.HasValue := False;
Result.RuntimeInfo.DeviceModel.HasValue := False;
Result.RuntimeInfo.CpuArch.HasValue := False;
Result.SelectedMap.RelativePath := SelectedMapRelativePath;
Result.SelectedMap.Url := SelectedMapUrl;
Result.FileIO := CurrentFileIO;
2026-09-17 13:48:56 +05:00
Result.TerrainReadiness := CurrentTerrainReadiness;
if (Result.FileIO.Status = dsFail)
or (Result.TerrainReadiness.Status = dsFail) then
Result.OverallStatus := dsFail
else if (Result.FileIO.Status = dsPass)
and (Result.TerrainReadiness.Status = dsPass) then
Result.OverallStatus := dsPass
else
Result.OverallStatus := dsNotRun;
2026-09-15 10:16:31 +05:00
end;
procedure ReplaceFileIODiagnosticResult(const AResult: TFileIODiagnosticResult);
begin
CurrentFileIO := AResult;
end;
2026-09-17 13:48:56 +05:00
procedure ReplaceTerrainReadinessDiagnosticResult(
const AResult: TTerrainReadinessDiagnosticResult);
begin
CurrentTerrainReadiness := AResult;
end;
2026-09-15 10:16:31 +05:00
function OptionalStringJson(const AValue: TOptionalString): TJSONData;
begin
if AValue.HasValue then
Result := TJSONString.Create(AValue.Value)
else
Result := TJSONNull.Create;
end;
function OptionalInt64Json(const AValue: TOptionalInt64): TJSONData;
begin
if AValue.HasValue then
Result := TJSONIntegerNumber.Create(AValue.Value)
else
Result := TJSONNull.Create;
end;
function OptionalBooleanJson(const AValue: TOptionalBoolean): TJSONData;
begin
if AValue.HasValue then
Result := TJSONBoolean.Create(AValue.Value)
else
Result := TJSONNull.Create;
end;
function DiagnosticReportJsonSnapshot: string;
var
State: TDiagnosticReportState;
Root, ObjectValue, Action, Stage, Stream: TJSONObject;
Actions, Stages: TJSONArray;
StageId: TDiagnosticStageId;
2026-09-17 13:48:56 +05:00
TerrainStageId: TTerrainReadinessDiagnosticStageId;
Evidence: TJSONObject;
2026-09-15 10:16:31 +05:00
begin
State := CurrentDiagnosticReport;
Root := TJSONObject.Create;
try
Root.Add('schema', 'kam-mobile-diagnostics/1');
ObjectValue := TJSONObject.Create;
ObjectValue.Add('name', State.ApplicationInfo.Name);
ObjectValue.Add('package', State.ApplicationInfo.PackageName);
ObjectValue.Add('version', State.ApplicationInfo.Version);
ObjectValue.Add('version_code', State.ApplicationInfo.VersionCode);
ObjectValue.Add('build_id', OptionalStringJson(State.ApplicationInfo.BuildId));
Root.Add('application', ObjectValue);
ObjectValue := TJSONObject.Create;
ObjectValue.Add('platform', State.RuntimeInfo.Platform);
ObjectValue.Add('os_version', OptionalStringJson(State.RuntimeInfo.OsVersion));
ObjectValue.Add('device_model', OptionalStringJson(State.RuntimeInfo.DeviceModel));
ObjectValue.Add('cpu_arch', OptionalStringJson(State.RuntimeInfo.CpuArch));
Root.Add('runtime', ObjectValue);
ObjectValue := TJSONObject.Create;
ObjectValue.Add('relative_path', State.SelectedMap.RelativePath);
ObjectValue.Add('url', State.SelectedMap.Url);
Root.Add('selected_map', ObjectValue);
Root.Add('overall_status', DiagnosticStatusToken(State.OverallStatus));
Actions := TJSONArray.Create;
Action := TJSONObject.Create;
Action.Add('id', 'file_io');
Action.Add('status', DiagnosticStatusToken(State.FileIO.Status));
if State.FileIO.FailureStage.HasValue then
Action.Add('failure_stage', DiagnosticStageIdToken(State.FileIO.FailureStage.Value))
else
Action.Add('failure_stage', TJSONNull.Create);
if State.FileIO.Error.Present then
begin
ObjectValue := TJSONObject.Create;
ObjectValue.Add('class', OptionalStringJson(State.FileIO.Error.ClassName));
ObjectValue.Add('message', OptionalStringJson(State.FileIO.Error.MessageText));
Action.Add('error', ObjectValue);
end else
Action.Add('error', TJSONNull.Create);
Stages := TJSONArray.Create;
for StageId := Low(TDiagnosticStageId) to High(TDiagnosticStageId) do
begin
Stage := TJSONObject.Create;
Stage.Add('id', DiagnosticStageIdToken(StageId));
Stage.Add('status', DiagnosticStatusToken(State.FileIO.Stages[StageId]));
Stages.Add(Stage);
end;
Action.Add('stages', Stages);
Stream := TJSONObject.Create;
Stream.Add('size', OptionalInt64Json(State.FileIO.Stream.Size));
Stream.Add('position_after_load', OptionalInt64Json(State.FileIO.Stream.PositionAfterLoad));
Stream.Add('read_offset', OptionalInt64Json(State.FileIO.Stream.ReadOffset));
Stream.Add('requested_read_count', OptionalInt64Json(State.FileIO.Stream.RequestedReadCount));
Stream.Add('first_read_count', OptionalInt64Json(State.FileIO.Stream.FirstReadCount));
Stream.Add('position_after_first_read', OptionalInt64Json(State.FileIO.Stream.PositionAfterFirstRead));
Stream.Add('seek_back_position', OptionalInt64Json(State.FileIO.Stream.SeekBackPosition));
Stream.Add('second_read_count', OptionalInt64Json(State.FileIO.Stream.SecondReadCount));
Stream.Add('position_after_second_read', OptionalInt64Json(State.FileIO.Stream.PositionAfterSecondRead));
Stream.Add('repeat_equal', OptionalBooleanJson(State.FileIO.Stream.RepeatEqual));
Action.Add('stream', Stream);
Actions.Add(Action);
2026-09-17 13:48:56 +05:00
Action := TJSONObject.Create;
Action.Add('id', 'terrain_readiness');
Action.Add('status', DiagnosticStatusToken(State.TerrainReadiness.Status));
if State.TerrainReadiness.FailureStage.HasValue then
Action.Add('failure_stage', TerrainReadinessDiagnosticStageIdToken(
State.TerrainReadiness.FailureStage.Value))
else
Action.Add('failure_stage', TJSONNull.Create);
if State.TerrainReadiness.Error.Present then
begin
ObjectValue := TJSONObject.Create;
ObjectValue.Add('class', OptionalStringJson(
State.TerrainReadiness.Error.ClassName));
ObjectValue.Add('message', OptionalStringJson(
State.TerrainReadiness.Error.MessageText));
Action.Add('error', ObjectValue);
end else
Action.Add('error', TJSONNull.Create);
Stages := TJSONArray.Create;
for TerrainStageId := Low(TTerrainReadinessDiagnosticStageId)
to High(TTerrainReadinessDiagnosticStageId) do
begin
Stage := TJSONObject.Create;
Stage.Add('id', TerrainReadinessDiagnosticStageIdToken(TerrainStageId));
Stage.Add('status', DiagnosticStatusToken(
State.TerrainReadiness.Stages[TerrainStageId]));
Stages.Add(Stage);
end;
Action.Add('stages', Stages);
Evidence := TJSONObject.Create;
Evidence.Add('rx_tiles_loaded', OptionalBooleanJson(
State.TerrainReadiness.Evidence.RxTilesLoaded));
Evidence.Add('generated_transitions_ready', OptionalBooleanJson(
State.TerrainReadiness.Evidence.GeneratedTransitionsReady));
Evidence.Add('map_x', OptionalInt64Json(State.TerrainReadiness.Evidence.MapX));
Evidence.Add('map_y', OptionalInt64Json(State.TerrainReadiness.Evidence.MapY));
Evidence.Add('game_rev', OptionalInt64Json(State.TerrainReadiness.Evidence.GameRev));
Evidence.Add('expected_tile_count', OptionalInt64Json(
State.TerrainReadiness.Evidence.ExpectedTileCount));
Evidence.Add('completed_tile_count', OptionalInt64Json(
State.TerrainReadiness.Evidence.CompletedTileCount));
Action.Add('evidence', Evidence);
Actions.Add(Action);
2026-09-15 10:16:31 +05:00
Root.Add('actions', Actions);
Result := Root.AsJSON;
finally
Root.Free;
end;
end;
end.