A 2026 paper at the SISO Simulation Innovation Workshop (SIW) by Crues and Dexter of NASA Johnson Space Center sets out how the time resolutions of a simulation executive, its scheduled jobs and an HLA federation have to fit together. It passes on an observation from a 2011 SIW paper: an integer representation of executive time is preferred over a purely floating-point one. This page takes that at its word. The rules are written as a small program in cint, a C family language whose arithmetic is all integer and whose plain operators are all checked, and they come out as a few lines of modular arithmetic. The line to watch is the last one, where a one-year scenario at one picosecond does not fit a 64-bit integer and the program stops with a fault record instead of a wrong number.
A simulation executive counts time in whole ticks. Trick does, at a multiplier of ticks per second that the simulation chooses. Every job rate the executive schedules has to be a whole number of ticks, so the multiplier must be a common multiple of the job rates. An HLA federation using integer logical time counts in whole ticks too, in its own unit, which the SpaceFOM fixes at microseconds. A federate's multiplier has to be at least the federation's and a whole multiple of it, so that every federation tick lands on a federate tick. Where the federate is finer, an event it sends has to sit on a federation tick as well, or it cannot be sent at the time it happened. The paper writes these as equations in tick multipliers: Mp for the smallest compatible executive multiplier, Mf for a federate, Mhlt for the federation, and Rf for their ratio.
There is a second constraint, which TrickHLA states beside each base time unit it offers: range. A 64-bit integer holds about 9.2 x 1018 ticks. At one microsecond that is about 292,000 years. At one picosecond it is about 106 days, which is shorter than many scenarios. A multiplier chosen for resolution can silently cost the range the scenario needs.
I64, at each base time unit, in Julian years of 31,557,600 seconds. Only the picosecond falls short of a year. Computed from the two constants, nothing measured.| unit | ticks per second | range |
|---|---|---|
| 1 ms | 1,000 | 292,271,023 years |
| 1 µs | 1,000,000 | 292,271 years |
| 100 ns | 10,000,000 | 29,227 years |
| 10 ns | 100,000,000 | 2,923 years |
| 1 ns | 1,000,000,000 | 292 years |
| 100 ps | 10,000,000,000 | 29 years |
| 10 ps | 100,000,000,000 | 2.92 years |
| 1 ps | 1,000,000,000,000 | 106.75 days |
gcd and lcm by Euclid, then the three checks as functions, then a main that runs the paper's own numbers: jobs at 128, 100, 30 and 10 Hz, a microsecond federation, federates at one microsecond, 100 nanoseconds and 1,920,000 ticks per second, a multiplier the paper uses as an example, and two events. The last three lines ask how many ticks a year is at a microsecond, at 10 nanoseconds and at a picosecond. The multiply in ticks_in is an ordinary *. In cint every * on an I64 is checked. The two test blocks at the bottom run under cint test.
time_compat.ci Run the first Run loads Python, about 12 MB, once
The first eight lines of output are the checks passing and failing as the paper says they should: Mp = 9600, the microsecond and 100 nanosecond federates compatible with Rf of 1 and 10, the 1,920,000 federate not, one event on a tick and one between ticks with its remainder. The ninth line never prints. The record names the operation, mul.checked.i64, both operands, the exact result 31,557,600,000,000,000,000, the limit 9,223,372,036,854,775,807, and the position in the source, line 42 column 23. In C the same multiply on int64_t is undefined behavior, and on common compilers wraps to a negative tick count that the rest of the program would carry on using. Here the program stops, the eight lines it already wrote stay in stdout, and the record says exactly which quantity did not fit and by how much.
Change 1 ps to 10 ps and run again: the year fits, at about 3.2 x 1018 ticks. Make it ten years and it does not. The record follows whichever line stops fitting.
The common multiple can run out of room before the scenario does. Change the line that sets mp so that it starts at 1 and then, in a loop for hz in 1..51, becomes lcm(mp, hz), an executive with a job at every whole rate from 1 to 50 Hz, and the program stops before it prints a line. The multiplier for every rate up to 42 Hz is 219,060,189,739,591,200 ticks a second; at 43 Hz the multiply inside lcm, line 18, would make it 9,419,588,158,802,421,600, and the record carries both operands, so it says which rate broke it. Even the multiplier that fits is no use: at that many ticks a second a 64-bit count lasts 42 seconds.
None of this needs cint, and the claim here is not that only cint can make the check. In C, GCC and Clang offer __builtin_mul_overflow, and C23 has the same thing as ckd_mul: each reports whether the product fit, and the program has to ask at every multiply and decide what to do when it did not. Rust's checked_mul returns None on overflow, while its plain *, by default, panics in a debug build and wraps in a release build. Ada's rules raise Constraint_Error unless the check has been suppressed. The claim is narrower: in cint the plain * is the checked one, in every build, so the line in ticks_in is an ordinary multiply with nothing around it, and the failure is a record, the same bytes from the reference and from the compiled C, which can be compared and hashed like any other output.
The compiled program and the reference write the same stdout, byte for byte, and stop at the same fault with the same record. The one emitted C file was built with four compilers on three machines, each at -O2 (/O2 for MSVC), and run once on each; every build stopped at the fault with exit status 1, the status a fault gives:
| run | machine | stdout SHA-256 |
|---|---|---|
| cint_ref | this page | 443aea258e753c7d27bcce3937834ed8b44461c7bbc3db5deb99c368c96fb3b4 |
| clang 18.1.3 | x86-64 Linux | 443aea258e753c7d27bcce3937834ed8b44461c7bbc3db5deb99c368c96fb3b4 |
| gcc 13.3.0 | x86-64 Linux | 443aea258e753c7d27bcce3937834ed8b44461c7bbc3db5deb99c368c96fb3b4 |
| MSVC 19.44 | x86-64 Windows 11 | 443aea258e753c7d27bcce3937834ed8b44461c7bbc3db5deb99c368c96fb3b4 |
| Apple Clang 21.0.0 | arm64 macOS 27 | 443aea258e753c7d27bcce3937834ed8b44461c7bbc3db5deb99c368c96fb3b4 |
The box below is the C that cintc, the cint compiler, emitted for the program as shipped. The two checked multiplies are calls to cint_mul_i64 that jump to the fault path when the result does not fit.
This is one program and four rules. It is not a federate, it links no RTI, and it says nothing about how Trick or TrickHLA implement their own checks, which the paper says they are adding. The base time units named here are illustrations; which units a given middleware offers is for its documentation to say. The year is 31,557,600 seconds, a Julian year, and the picosecond case is chosen to show the range rule, not to describe any federation. The rules are the paper's; any slip in restating them is mine. Nothing here is a compliance claim and no one cited has reviewed it.
__builtin_mul_overflow, also provided by Clang.<stdckdint.h> and ckd_mul.checked_mul.Constraint_Error.Harriett Little. Time resolution compatibility as checked integer arithmetic. integerc.dev, 2026. https://integerc.dev/papers/time-resolution/
Harriett Little
October 2026