Add bounded aircraft control and verify current packaged runtime closure

This commit is contained in:
2026-09-19 13:31:51 +02:00
parent 4125018d04
commit a880e0485c
695 changed files with 79317 additions and 44 deletions
+157
View File
@@ -0,0 +1,157 @@
#!/usr/bin/env python3
"""Read the installed native -logStats format; export coarse records, not frames."""
from __future__ import annotations
import argparse
import hashlib
import json
import math
from pathlib import Path
import re
import sys
MAX_INPUT_BYTES = 64 * 1024 * 1024
MAX_LINE_BYTES = 16 * 1024
MAX_RECORDS = 100_000
MAX_COUNT = (1 << 63) - 1
_NUMBER = r'([^\s,()]+)'
_COUNT = r'([0-9]+)'
_PATTERN = re.compile(
r'([0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3})[ \t]+DEFAULT[ \t]*:[ \t]*'
r'FPS: ' + _NUMBER + r', frame time \(avg: ' + _NUMBER +
r' ms, min: ' + _NUMBER + r' ms, max: ' + _NUMBER +
r' ms, mean: ' + _NUMBER + r' ms, median: ' + _NUMBER +
r' ms\), Mem: ' + _COUNT + r' kB, Player: ' + _COUNT +
r', AI: ' + _COUNT + r', AIChar: ' + _COUNT + r', Veh: ' + _COUNT +
r' \(' + _COUNT + r'\), Proj \(S: ' + _COUNT + r', M: ' + _COUNT +
r', G: ' + _COUNT + r' \| ' + _COUNT + r'\)' +
r'(?:, Freezes\(cnt: ' + _COUNT + r', max: ' + _NUMBER + r' ms, len: ' + _COUNT + r'\))?' +
r', Streaming\(Dynam: ' + _COUNT +
r', Static: ' + _COUNT + r', Disabled: ' + _COUNT + r' \| New: ' + _COUNT +
r', Del: ' + _COUNT + r', Bump: ' + _COUNT + r'\)'
)
# Only the native DEFAULT category is accepted. A changed/truncated native stats
# record must fail the export, rather than disappearing among unrelated log lines.
_CANDIDATE = re.compile(r'\bDEFAULT\b[^\r\n]*(?:\bFPS\b|\bframe time\b)')
_DECIMAL = re.compile(r'[0-9]+(?:\.[0-9]+)?')
class StatsError(ValueError):
"""Unsupported or malformed telemetry; no partial successful export."""
def _float(text: str, line: int) -> float:
if len(text) > 64 or not _DECIMAL.fullmatch(text):
raise StatsError(f'line {line}: expected finite nonnegative decimal, got {text!r}')
value = float(text)
if not math.isfinite(value):
raise StatsError(f'line {line}: nonfinite telemetry')
return value
def _count(text: str, line: int) -> int:
if len(text) > 19 or int(text) > MAX_COUNT:
raise StatsError(f'line {line}: count exceeds supported integer range')
return int(text)
def parse_bytes(data: bytes, source: str = '<memory>') -> dict:
if len(data) > MAX_INPUT_BYTES:
raise StatsError('input exceeds 64 MiB limit; split logs outside this parser')
try:
text = data.decode('utf-8-sig')
except UnicodeDecodeError as error:
raise StatsError('input is not UTF-8 text') from error
records = []
for line_number, raw_line in enumerate(text.splitlines(), 1):
if len(raw_line.encode('utf-8')) > MAX_LINE_BYTES:
raise StatsError(f'line {line_number}: line exceeds 16 KiB limit')
if not _CANDIDATE.search(raw_line):
continue
match = _PATTERN.fullmatch(raw_line)
if not match:
raise StatsError(f'line {line_number}: malformed or unsupported native statistics record')
fields = match.groups()
timestamp = fields[0]
hour, minute, second = map(int, (timestamp[:2], timestamp[3:5], timestamp[6:8]))
if hour > 23 or minute > 59 or second > 59:
raise StatsError(f'line {line_number}: invalid native clock timestamp')
numbers = [_float(value, line_number) for value in fields[1:7]]
fps, avg, minimum, maximum, mean, median = numbers
if minimum > maximum or any(value < minimum or value > maximum for value in (avg, mean, median)):
raise StatsError(f'line {line_number}: inconsistent frame-time summary bounds')
counts = [_count(value, line_number) for value in fields[7:17]]
memory, player, ai, ai_char, vehicle, vehicle_parenthesized, s, m, g, projectile_trailing = counts
freezes = None
if fields[17] is not None:
freezes = {'cnt': _count(fields[17], line_number),
'max_ms': _float(fields[18], line_number),
'len': _count(fields[19], line_number)}
dynamic, static, disabled, new, deleted, bump = [_count(value, line_number) for value in fields[20:]]
records.append({
'line_number': line_number,
'timestamp': timestamp,
'window_duration_ms': None,
'fps': fps,
'frame_time_summary_ms': dict(avg=avg, min=minimum, max=maximum, mean=mean, median=median),
'memory_kB': memory,
'native_populations': {'Player': player, 'AI': ai, 'AIChar': ai_char,
'Veh': {'value': vehicle, 'parenthesized': vehicle_parenthesized}},
'native_projectiles': {'S': s, 'M': m, 'G': g, 'trailing': projectile_trailing},
'native_freezes': freezes,
'native_streaming': {'Dynam': dynamic, 'Static': static, 'Disabled': disabled,
'New': new, 'Del': deleted, 'Bump': bump},
'ai_only_duration_ms': None,
'raw_line': raw_line,
})
if len(records) > MAX_RECORDS:
raise StatsError('statistics record limit exceeded')
if not records:
raise StatsError('no supported native statistics records found')
return {
'schema_version': 1,
'kind': 'native-server-coarse-statistics',
'source': {'path': source, 'sha256': hashlib.sha256(data).hexdigest(), 'bytes': len(data)},
'performance_acceptance': 'not_evaluated',
'unavailable': {
'ai_only_duration_ms': 'Native -logStats records contain no AI-only execution duration.',
'raw_frame_samples': 'Each record contains coarse summaries, not individual frames.',
'percentiles': 'Slow-tail percentiles cannot be recovered from these summaries.',
'window_duration_ms': 'The line does not identify its exact sampling window.',
'calendar_date': 'Record timestamps contain only the native clock time; no date inferred.',
},
'limitations': [
'Native labels are preserved; AI and AIChar are not equated or treated as AI execution time.',
'Vehicle parenthesized and projectile trailing numbers are retained without inferred scope.',
'Streaming counts are not treated as a complete loaded-world entity census.',
'An absent Freezes block is null, not zero; cnt and len retain native labels without inferred duration or frame counts.',
'Records retain source order, including clock rollover; no elapsed intervals or missing windows are invented.',
'Parser success does not check engine/script errors, source provenance, equivalent workloads or release qualification.',
'This output is not compare_performance.py v1 frame CSV input.',
],
'record_count': len(records),
'records': records,
}
def parse_file(path: Path) -> dict:
with path.open('rb') as handle:
data = handle.read(MAX_INPUT_BYTES + 1)
return parse_bytes(data, str(path.resolve()))
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('log', type=Path, help='One existing UTF-8 native console log; read only')
args = parser.parse_args(argv)
try:
result = parse_file(args.log)
except (OSError, StatsError) as error:
print(f'native statistics export refused: {error}', file=sys.stderr)
return 1
print(json.dumps(result, indent=2, sort_keys=True, allow_nan=False))
return 0
if __name__ == '__main__':
raise SystemExit(main())