Skip to main content
Critical Path Partners · Engine Validation

CPM Engine Cross-Validation Results

Two implementations of the same CPM engine by one author, run against the same fixtures, compared field by field. Every fixture is listed. Every gap is listed. The results are reproducible from a public repository.

What this proves, and what it does not

Read this before quoting any number below

Two validation layers, stated separately. First: a JavaScript implementation and a second Python implementation of the same CPM algorithm, both written by the same author, agree on all 22189 field comparisons the harness runs across 233 fixtures, covering calendars, all four relationship types, leads and lags, constraints, both P6 scheduling modes, in-progress work, out-of-sequence progress, the retained-logic pass-through, the unexpired part of a lag off started and completed work, the Must Finish By (applied under "opened projects" too), suspend and resume dates, links between completed activities, constraints dated on time the calendar does not work, As Late As Possible work, external early start and late finish dates, the dates a milestone prints, zero-length tasks and progress override's finish links, the hours pass on P6's own hours, resource dependent work on its resources' calendars, the float and critical set P6 prints, cycle refusal and far-future date arithmetic. None is skipped: the 100 comparisons skipped through v2.10.0, on the two signed free-float fields of completed activities where neither implementation emitted them, run since both began emitting 0 there, and the 58 one-sided skips that were a real port gap closed when the has-successors branch was ported. Counted, agreement is 22189 of 22189. Two ports by one author catch transcription and refactor drift; they cannot catch a shared misreading of P6, which is what the second layer below is for.

Second, and stronger: validation against Primavera P6 itself. On 2026-08-11 the 13 comparison cases below were built inside P6 Professional 23.12 by an automated import, scheduled with a single F9, and P6's computed dates and float were read back and compared. The first capture returned 6 of 13. The seven failures resolved into five named divergence families; each was fixed in both implementations against P6's pinned answers, with the fix history public in the repository. The matrix now stands at 13 of 13. The engine makes no P6 parity claim beyond the ground these cases cover. Re-capture is not automatic: it costs one import and one F9 in P6 by a human operator. The capture sheet the applier reads sits outside the public repository, so the matrix cannot be regenerated end to end from a clean clone. The committed import XER and the per-case comparison CSVs preserve P6's answers, so the capture stays inspectable. The honest reading is that these thirteen behaviours are now correct, not that the next thirteen would pass first time. The third layer tests exactly that.

Third: a blind check on schedules the engine was never fitted to. On 13 October 2026, twelve new synthetic schedules were generated from a public randomness-beacon round. The rules of the check were committed before that round was published, so nobody could know the schedules in advance. The schedules were published, then each was scheduled in P6 Professional 23.12.1 on its own, and engine v2.10.1 was scored against P6 once, with no engine change before or after. 195 of 206 unfinished activities match P6 on all seven fields compared: early and late start and finish to the minute, total and free float in hours, and the critical flag. Seven of the twelve schedules match on every field. The eleven that differ fall in four situations, listed below. The check is held out, not independent: CPP wrote the generator and the scorer and ran P6. What makes it blind is the beacon seed and the order of events, and every step is public in the held-out folder.

Known limitations are published, not buried. Since v2.10.0 the engine schedules P6's hours to the minute; where the hours pass cannot run, sub-day lags round with a fatal-in-strict-mode alert. The driving predecessor and the time-impact figure are still worked in whole days. Resource levelling is not modelled. Free-float parity carries one documented asymmetry noted below. Two cases that P6 cannot construct by design live in a separate engine-limitations folder and carry no P6 verdict.

Results

233
Fixtures
22189
Field checks
0
Failures
0
Skipped, not compared
1524
Unit tests
13 / 13
P6-native cases, fitted
first blind run 6 of 13
195 / 206
P6 held-out activities, blind
all seven fields, scored once

Every fixture, and its check count

The Skip column counts comparisons the harness does not perform because at least one port does not emit the field. The harness counts them: 0 fall on ff_signed_working_days and 0 on ff_signed, giving 0 in total across 0 of the 233 fixtures. All 0 are cases where NEITHER implementation emits the field, on completed activities, so there is nothing to verify and no port gap behind them. None is a case where this engine returns a computed value and the Python reference returns none: that class of skip numbered 58 until the has-successors branch was ported, and now numbers zero.

IDFixtureChecksFailSkip
F1A→B→C linear, no cal3300
F2A→B→C linear, MonFri3300
F3A→B→C + A→X (off-CP), MonFri4200
F4Mixed FS/SS/FF/SF + lags5100
F5MonFri + holidays2400
F67-day calendar (no weekends)2400
F6.5SF + FS mix3300
F7Diamond network5100
F8Numeric codes interleaved with alpha codes6000
F9FS-3 lead2400
F10Completed + uncompleted mix3300
F11MonFri + 7-day calendars mixed3300
F12early_start pin ahead of logic3300
F13SNET primary constraint2400
F14MS_Start + FNLT combo3300
F15ALAP consumes float4200
F16SNLT primary (forward ALERT + backward LF clamp)2400
F17FNET pushes EF forward (warn)1500
F18MS_Finish LF pin (forward warn + backward clamp)2400
F19Secondary constraint pair (SNET + FNLT window)2400
F20Out-of-sequence (completed B before A starts)2400
F21ALAP slide suppressed by actual_start4200
F22Calendar fallback (missing clndr_id triggers ALERT)2400
F23Cycle detection (both engines refuse)100
F24Free-float parity DOCUMENTED GAP (no FF in Python ref)2400
F26Calendar fallback, 3 distinct missing clndr_ids3300
F27actual_start AFTER data_date pins ES (P6 forward-pass semantics)2400
F28ALAP primary + FNLT secondary compound4200
F29Mixed FF + SS predecessors converge on same successor3300
F30Negative lag FS-2 (no calendar, ordinal arithmetic)2400
F31Cycle in sub-network (A→B→C clean + D↔E cycle)100
F32Far-future date arithmetic (2037-12-15 + 100d, post-Y2038)1500
F33MS_Start primary pins LS=ES, TF=0 (v2.9.12 T1.1)3300
F34MS_Start suppressed by actual_start (v2.9.12 T1.2)1500
F35unrecognized constraint token (v2.9.12 T1.6)1500
F36empty work_days falls back to MonFri (v2.9.12 T2.16)1500
F37CS_MANSTART alias (v2.9.12 T1.7)1500
F38CS_MANFINISH alias (v2.9.12 T1.7)1500
F43actual_finish without actual_start (v2.9.12 T4.25)1500
F44ALAP on secondary slot (v2.9.12 T4.26)2400
F45in-progress retained-logic LF=EF pin (F1-Bug1/F1-Bug2)3300
F46single in-progress activity retained-logic EF (F1-Bug2)1500
F47stored early_start NOT a SNET floor (F1-Bug5)1500
F48- out-of-sequence retained logic: remaining restarts behind pred (case 10)2400
F49- out-of-sequence progress_override: remaining continues from data date2400
F50special_workdays: a forced-ON Saturday and a forced-OFF Monday2400
F51pass-through: completed C carries unfinished P's date to X3300
F52progress_override ignores the pass-through (same topology as F51)3300
F53pass-through carries the lag into C; the lag out of C adds its unexpired part3300
F54pass-through survives a chain of two completed activities4200
F55SS_U: restart wins over actual_start+lag2400
F56SS_U: actual_start+lag wins when it exceeds the restart2400
F57PO_SNAP: progress_override restart snaps off a weekend data date1500
F58SSL: SS off a started predecessor whose restart is held past the data date3300
F59SSL: a used-up lag lays nothing; X starts on the held restart3300
F60SSL: SF off a started predecessor whose restart is held3300
F61SSL: an SS link off started work lays no lag into a completed activity4200
F62SSL: a future actual start uses up none of the lag2400
F63SSL: six-day predecessor, Mon-Fri successor, predecessor lag calendar3300
F64SSL: a started successor restarts at the held restart + unused lag3300
F65FA: FS+0 off an actual finish recorded weeks after the data date, beside an unfinished predecessor4200
F66FA: FS+2 off an actual finish after the data date is laid from the data date2400
F67FA: SS+3 off an actual start after the data date2400
F68FA: FF+2 off an actual finish after the data date2400
F69FA: the unexpired lag rides on a carried date3300
F70FA: the backward pass takes the unexpired lag off the carried bound4200
F71FA: a started successor restarts at the data date plus the unexpired lag2400
F72FA: progress override lays the lag from the data date too2400
F73FA: without a data date the actual dates still drive2400
F74FA: six-day predecessor, Mon-Fri successor, predecessor lag calendar2400
F75FA: a completed-to-completed link carries its unexpired lag3300
F76SSL measured: SS under lag from Actual Start is laid from the data date3300
F77SSL measured: under Actual Start the successor can start before the restart3300
F78SSL measured: a used-up lag under Actual Start starts on the data date (flag N)3300
F79SSL measured: Actual Start, six-day predecessor, Mon-Fri successor3300
F80SSL measured: under Actual Start a started successor restarts at the data date + unused lag3300
F81SSL measured: SF ignores lag from Actual Start3300
F82SSL measured: no lag from started work into a completed activity; float through it5100
F83SSL measured: into a completed activity under Actual Start the data date is carried5100
F84SSL measured: SF into a completed activity carries the restart under Actual Start4200
F85SSL measured: other links into completed work keep their lag9600
F86SSL measured: an unknown lag-from option alerts and keeps Early Start3300
F87MFB: an earlier Must Finish By seeds negative float4200
F88MFB: a later Must Finish By seeds positive float4200
F89MFB: a Must Finish By inside the working day4200
F90MFB: a Must Finish By on a non-working day4200
F91MFB: a bare-date Must Finish By4200
F92MFB: the Must Finish By on each activity's own calendar3300
F93MFB: an ordinal (calendar-less) network2400
F94MFB: an unparseable Must Finish By4200
F95MFB: a finish constraint beside the Must Finish By4200
F96RES: a completed activity resumed after the data date holds its successors4200
F97RES: a suspended activity restarts on its resume date2400
F98RES: a resume date at or before the data date2400
F99RES: progress override does not apply a resume date3300
F100CC: completed to completed, with and without a resume date5100
F101FF: free float to a completed successor's stamp3300
F102MFB + RES + CC together6000
F103RES: a resume date without a suspend date is not applied4200
F104MFB under "opened projects": the measured shape at a midnight data date10500
F105MFB under "opened projects": the measured shape at the 17:00 close10500
F106XF: with "Use Expected Finish Dates" on, an expected finish re-sizes the remaining work (stretch, shrink, logic, weekend, FF)15900
F107XF: the same network with the expected-finish option off15900
F108UW: finish constraints on work already under way10500
F109XF: an expected finish behind FF logic, retained logic15900
F110XF: an expected finish behind FF logic, progress override15900
F111XA: an expected finish that leaves no working time zeroes only unassigned work (data date Wed 17:00)33000
F112XA: the 704 h activity on a seven-day calendar keeps its remaining work4200
F113OW: start constraints on a Saturday, a holiday and after the close11400
F114OW: finish constraints on a Sunday (zero duration and two days)5100
F115AL: ALAP by free float (FS, a chain, SS, no free float, an open end)17700
F116FM: a finish milestone held at Friday 17:00 drives seven-day work from Saturday7800
F117SR: start float of work under way, start float, retained logic6900
F118SR: start float of work under way, smallest float, progress override6900
F119EX: external early start and external late finish, applied20400
F120EX: the same with relationships to other projects ignored20400
F121MD: milestone instants at a Monday opening32100
F122MD: milestone instants on the parity probe shapes18600
F123MD: a Friday-close data date and a close off the milestone's own calendar4200
F124WD: zero-length tasks close late, and hand the close back14100
F125WD: progress override honours the links into a started activity's finish10500
F126HR: a half-day lag and a half-day activity, to the minute3700
F127HR: work a pipeline inserted (no hours) or re-sized (days moved) is sized from its days3700
F128HR: SS lag off suspended work, SF off started work under "lag from Actual Start"5700
F129HR: an SF requirement after lunch, an FF requirement off working time5700
F130HR: ALAP by free float, a Start On off working time, a timed constraint7700
F131HR: progress override, completed work after the data date, an exception day with hours6700
F132RD: assignments on their resources' calendars; a lag delays its assignment; a non-driving one drives nothing4700
F133RD: the start takes the activity calendar's opening; the late side its close; a start-no-later-than caps it5700
F134RQ: FF and SF requirements on resource dependent work, on an opening and inside working time6700
F135XR / XZ: an expected finish re-sizes each assignment; work it zeroes in this pass stays at its start4700
F136PF: the printed float is the hours float in working days; critical is total float <= 0 in hours6700
F137PF: under FT_Min both floats follow the hours pass, on a seven-day calendar beside a five-day one5700
F138float on work no calendar reaches is counted on the 7-day days it was scheduled on3300
F139a completed activity finished on a later day than the data date: the alert in both engines, and every free-float field3300
F140a constraint handed back normalized (date + time_minutes, as getTasks() carries it) keeps its time5700
F141SC: Start On or Before, Start On and Mandatory Start on work under way drop from the late side, whole days7800
F142SC: the same constraints in hours; a Start On or Before or Mandatory Start takes the other constraint with it8700
F143AS: As Late As Possible under way, past its late finish and as an open end, whole days8700
F144AS: the same in hours9700
F145AS: under progress override an SS link out of started ALAP work holds nothing back (hours)9700
F146FL: free float across a lag on the successor’s calendar, seven-day work into five-day work9700
F147FL: free float across a lag on the 24-hour calendar9700
F148MD: a Mandatory Start or Finish before the data date on work not started starts at the data date, whole days9600
F149MD: the same in hours10700
F150MD: the same in hours, under progress override10700
F151SR: start-to-start free float out of work under way runs from the restart, lag from Actual Start, whole days15000
F152SR: the same in hours16700
F153SO: under progress override an SS or SF link out of work under way holds no free float, whole days15000
F154SO: the same in hours, lag from Actual Start16700
F155SO: the same in hours, lag from Early Start16700
F156LC: a lead landing on a close keeps it as the late start, and its tie with the late finish, in hours22700
F157MC: a milestone applies no constraint of its other end, whole days19500
F158MC: the same in hours21700
F159PD: under progress override an SS link out of work under way is laid from the data date, whole days5100
F160PD: the same in hours, lag from Early Start, the data date at the end of the day11700
F161PD: the same on a 24-hour lag calendar11700
F162XM / LC: an expected finish on milestones, and a lead tied with a finish-to-finish successor, in hours16700
F163FL: a lag handed on in days, under the successor lag calendar, from a 10-hour-day predecessor onto 6-hour-day successors, in hours3700
F164AP: As Late As Possible work and an FS or SS link into a successor under way, under progress override, in hours13700
F165AP: the same, whole days12300
F166MP: a Mandatory Start or Finish against an earlier link bound, a weekend tie, both pins, Start On or Before controls, in hours21700
F167EP: a schedule dated 2013 with work under way, completed work and a constraint, in whole days6900
F168EP: the same, in hours7700
F169AU: As Late As Possible work under way over start-to-start and start-to-finish lags, in hours16700
F170AU: the same, whole days15000
F171PS: SS lag from the Actual Start: a start-to-start link out of completed work carries no date, in hours17700
F172PS: the same, whole days15900
F173RO: under progress override a resume date after the data date holds work under way, in hours12700
F174RO: the same, whole days11400
F175SU: an SS / SF lag off a suspended predecessor, the suspension left out forward and counted backward, in hours11700
F176SU: the same, whole days10500
F177ZP: resource dependent work whose start a finish requirement sets, an assignment with no work after it keeping its own time, in hours12700
F178RL: resource dependent work with an assignment that has no work and no bound taken keeps its late finish where its work ends, in hours3700
F179XL: no expected finish on As Late As Possible work, under way or not; it still applies with no constraint and with another one, in hours13700
F180XL: the same, in whole days12300
F181OC: open-ended work takes no later finish than its own early finish, a Must Finish By still capping it, in hours14700
F182OC: the same network under progress override and no Must Finish By, in whole days13200
F183OC: completed work with no successor is an open end under retained logic; a level of effort or WBS summary successor makes work not open-ended, in hours13700
F184OC: the same network, in whole days12300
F185XC: an expected finish drops the early side of a finish constraint on work not yet started, in hours14700
F186XC: the same, in whole days13200
F187The four relationship checks both engines raise: a self-loop, an unknown type, a lag that is not a number, a link to no activity3300
F188XP, MR and XC on finish milestones: a Mandatory Finish under an expected finish caps the late finish, and resource dependent work takes a mandatory pin, in hours29700
F189XP, MR and XC: the same, in whole days, where a milestone with an expected finish keeps zero duration26700
F190XP and MR: the same network with "Use Expected Finish Dates" off, where every mandatory date pins, in hours29700
F191XP and MR: the same with the option off, in whole days26700
F192PC: a mandatory date beside a second constraint: only the primary pins and the second caps, in whole days25800
F193PC: the same, in hours28700
F194Input checks both engines raise: a null activity, a missing code, a padded code, a duplicate code, a control character in a name5100
F195Input checks: an empty network600
F196Input checks: an invalid constraint date, an invalid activity date, inverted actuals, a remaining duration longer than the duration6900
F197SL, MG, MX and FZ: a mandatory date in the second slot is a floor, a Mandatory Start ignores finish-to-finish logic and external dates, and As Late As Possible work holds no free float, in hours36700
F198SL, MG, MX and FZ: the same, in whole days33000
F199XO, XE and ZB: an expected finish with a finish-to-finish requirement off working time, resource dependent work under an expected finish, and a lagged assignment with no work under a start-to-start bound, in hours14700
F200AV: As Late As Possible resource dependent work under way, its slid restart off its calendar, in hours5700
F201FP: F197 under "Calculate multiple float paths", in hours36700
F202FP: F198 under "Calculate multiple float paths", in whole days33000
F203SL: a primary Mandatory Start beside a later or earlier second constraint, in hours20700
F204SL: the same, in whole days18600
F205QS: work a finish-to-finish requirement places starts where its resource first works after the activity calendar opens, in hours11700
F206ZL: a lagged assignment with no work whose lag lands off the activity calendar, in hours11700
F207XW: an expected finish sizes resource dependent work once, from its first placement, in hours11700
F208ZR: an assignment with no duration and no lag runs back from the late finish under a start-to-start bound, in hours23700
F209ZW: an assignment with no units and no units per hour keeps the hours between its own dates, in hours21700
F210ZF and BM: a lagged assignment with no work under a start-to-start bound that another assignment can outlast, in hours26700
F211AG: As Late As Possible resource dependent work with no working time to gain does not slide, in hours17700
F212FK: As Late As Possible work with the float paths ending at an activity it does not lead into holds no free float, in hours36700
F213FK: the same, in whole days33000
F214FK: the float paths ending at an activity the work leads into, which keeps its free float, in hours36700
F215FK: an end that is not an activity of the network changes nothing, in whole days33000
F216MZ: a finish milestone at the 24:00 close of a day before one not worked, in hours5700
F217UM: a Finish On or Before and a Mandatory Finish on work under way both leave the late side, in hours14700
F218UM: the same, in whole days13200
F219ZU: the lag of an assignment with no units on work under way counts from the actual start, in hours11700
F220As Late As Possible resource dependent work under a start-to-start or start-to-finish bound, in hours12700
F221The same under "Start Float": the start float of work so placed, in hours12700
F222ZQ: an assignment with no work and a lag bounds the start of work placed back from a finish requirement, in hours9700
F223As Late As Possible resource dependent work that would finish off the activity calendar, in hours10700
F224AW: the shapes of P6 probe round 16, in hours10700
F225AW: As Late As Possible resource dependent work placed back twice from a start bound, in hours28700
F226AT: F225 under "Start Float" and progress override, start float from the bound, in hours28700
F227AW: F226 with "Calculate multiple float paths", start float from the start, in hours28700
F228AZ: As Late As Possible resource dependent work with no work left sits at its resources' next opening, in hours10700
F229MW and LR: finish floors compared by start, and the late pin placed on the resources first, in hours10700
F230FT: FT_Start with the float paths ending at the finish milestone, in hours7700
F231FT: the same with the paths ending at a branch the resource dependent work does not lead into, in hours7700
F232FT: F230 under "Smallest", in hours7700
F233FT: F230 under FT_SS, the code P6's dialog writes for "Start Float", where RT is set aside, in hours7700
F234FT: FT_SS under retained logic, with no end and no multiple float paths, in hours7700
F235FT: F230 written FT_Total with no end, read as FT_Start, in hours7700
F236AZ and MO: work with no work left kept at its resource's opening, and a Mandatory Start that pins the span of its work, in hours15700
F237MO: F236 under "Start Float" with lags on 24 hours, in hours15700

Fixture identifiers are not contiguous. F6.5 was inserted between F6 and F7, and F25 and F39 through F42 are absent because fixtures were consolidated across earlier audit rounds. The set is 233 fixtures as listed. Cycle-detection fixtures F23 and F31 carry a single check each, because the only correct behaviour is that both implementations refuse the input.

Detail

Run manifestVersions, the pinned reference hash, and how to reproduce this page
ItemValue
Engine versioncpm-engine v2.10.17
Run date2026-11-09
Node.jsv22.19.0
Python3.12.10
Python referencepython_reference/cpm.py, 331779 bytes
Reference SHA-25612a8f55a2f94f3ae367e7b045a89533178188ece3c844679f254b4b2846b1b62
LicenceMIT
Repositorygithub.com/danafitkowski/cpp-cpm-engine

The Python reference is pinned by SHA-256 and the hash is printed at the start of every run, so an external auditor can confirm the reference has not drifted between runs. To reproduce:

git clone https://github.com/danafitkowski/cpp-cpm-engine
cd cpp-cpm-engine
git checkout v2.10.17
npm install
npm run test:all

The tag is pinned deliberately. This page records a run performed on 9 November 2026 at engine v2.10.17, and the hashes and dates below are that run's. Checking out the tag is what makes them reproducible; v2.10.17 is the current release, and the 1,524 unit tests pass in the same run. The agreement figure moved from the 931 of 995 recorded at v2.9.41 when v2.9.42 corrected constraint pairing and restored SS/SF late-finish conversion; it has held at 1009 of 1015 through v2.9.43 and v2.9.44. v2.9.44 changed the arithmetic where a relationship crosses calendars: a successor now starts from its predecessor's finish instant, placed on the successor's own calendar, and the backward pass mirrors it. Every fixture except the mixed-calendar F11 computed the same values before and after that change, and on F11 the two implementations agree on the new reading. It grew to 1167 of 1183 when v2.9.46 added seven fixtures for the retained-logic pass-through, and to 1957 of 2011 when v2.9.47 added twenty-nine for the unexpired part of a lag off started and completed work; the fixtures that existed before each change agree in both implementations after it, F53 moving one working day to the measured value in both. It grew again, to 2465 of 2539, when v2.9.49 added seventeen (F87 to F103) for the Must Finish By, suspend and resume dates and links between completed activities, to 2623 of 2705 when v2.9.50 added two (F104 and F105) for the Must Finish By under "opened projects", measured in P6, to 3202 of 3288 when v2.9.52 added five (F106 to F110) for P6's "Use Expected Finish Dates" and for finish constraints on work already under way, both measured in P6, to 3482 of 3580 when v2.9.53 added two (F111 and F112) for an expected finish that leaves no working time, where P6 keeps the remaining work of an activity with a resource assignment, also measured in P6, and to 3814 of 3912 when v2.9.54 added four (F113 to F116) for constraints dated on time the calendar does not work, As Late As Possible work that slides by its free float, and a finish milestone held at a Friday close, all measured in P6, and to 3924 of 4022 when v2.9.55 added two (F117 and F118), the first to hand both implementations a total float method, for the start float of work under way, which P6 measures from the restart rather than the actual start, also measured in P6, and to 4244 of 4342 when v2.9.57 added two (F119 and F120) for an activity's external early start and external late finish, applied and ignored, also measured in P6, and to 4675 of 4773 when v2.9.58 added three (F121 to F123) for the dates a milestone prints, measured on P6's own output, and the dates check began carrying the printed finish dates, and to 4869 of 4967 when v2.9.59 added two (F124 and F125) for zero-length tasks closing late and progress override's links into a started activity's finish, measured on P6's own output, and to 5133 of 5233 when v2.10.0 added six (F126 to F131) that hand both implementations P6's hours and compare the hours pass as well, the engine now scheduling in hours and minutes, measured on P6's own output, and to 6994 of 6994 when v2.10.1 added nine (F132 to F140) for resource dependent work, the float and critical set P6 prints, float on work no calendar reaches, a future actual finish and constraints handed back normalized, every activity's start and finish float joined the comparison, and the harness stopped skipping anything; it reached 7634 of 7634 when v2.10.2 added seven (F141 to F147) for start constraints on work under way, As Late As Possible and free float across a lag, and 8745 of 8745 when v2.10.3 added eight (F148 to F155) for a mandatory date before the data date, start-to-start free float out of work under way, and start-to-start and start-to-finish links out of work under way under progress override, and 9836 of 9836 when v2.10.4 added seven (F156 to F162) for start-to-start links out of work under way under progress override laid from the data date, a constraint of a milestone's other end, a lead that lands on a close, and an expected finish on a milestone, and 9873 of 9873 when v2.10.5 added one (F163) for a lag handed on in days under the successor lag calendar, and 10350 of 10350 when v2.10.6 added three (F164 to F166) for As Late As Possible work past a successor under way and a mandatory date against an earlier link bound, and 11149 of 11149 when v2.10.7 added six (F167 to F172) for a schedule dated before 2020, As Late As Possible work under way over a start-to-start or start-to-finish lag, and a start-to-start link out of completed work under "Actual Start", and 11390 of 11390 when v2.10.8 added two (F173 and F174) for a resume date after the data date under progress override, and 11612 of 11612 when v2.10.9 added two (F175 and F176) for a lag off a suspended predecessor, and 11776 of 11776 when v2.10.10 added two (F177 and F178) for the finish of resource dependent work under a finish requirement and for RL in the Python engine, and 12887 of 12887 when v2.10.11 added nine (F179 to F187) for its three rules and the Python reference's relationship checks, and 17926 of 17926 when v2.10.12 added twenty-four (F188 to F211) for its twenty-one rules and the Python reference's input checks, and 22189 of 22189 when v2.10.13 added twenty-six (F212 to F237) for its twelve rules. v2.10.14 added none: what it changes, the driver the engine records for work under way, is not a compared field. v2.10.15 added none: what it changes, how the XER parser reads a blank lag, is not on the harness's path. v2.10.16 added none: what it changes, the driver the engine records, is not a compared field. v2.10.17 added none: it changes no code in this engine. The run prints the reference path, byte count and SHA-256 before the first fixture, then one line per field check. The final block prints the fixture and check totals shown on this page.

What is compared on each fixtureThe enumerated field set, with the number of checks each contributes

Comparison is field by field rather than on a single summary value. A fixture passes only if every enumerated field matches between the two implementations.

FieldChecksWhat it is
node <id>20721Per-activity values: early start, early finish, late start, late finish, total float, the rendered dates, total float in working days, free float, and free float in working days; where P6's hours are handed on, also the instants to the minute, the float in hours and the day-pass dates
project_finish_num231Project finish as an ordinal day number
project_finish231Project finish as a rendered date
critical_codes231The set of activity codes on the critical path
topo_order231Topological sort order
alert_count231Number of alerts raised
alert_severity_counts231Alert counts broken down by severity
hours80The hours pass, on the fixtures that hand both implementations P6's hours: whether it ran, the project finish instant and the activities sized from their days (the per-activity instants and float in hours are counted under node <id>)
threw (both engines)2Both implementations refuse the input and raise, on cycle-detection fixtures

Per-activity checks dominate the count because every activity in every fixture contributes its early and late dates, total float, free float, the rendered date strings, and both float values converted to working days on that activity's own calendar.

Documented gaps and disclosed limitationsThree items, stated plainly, each with the fixture or case that exercises it

1. Free-float parity is a documented gap

Fixture F24 was labelled a documented gap in the suite itself when the Python reference did not compute free float. That gap closed when the free-float port landed: ff and ff_working_days are cross-validated on every fixture, and the signed variants on every activity: since v2.10.1 both engines emit them on completed work too, so nothing remains uncompared.

2. Part-day work without the hours pass, and two whole-day results

P6 stores durations and lags in hours. Since v2.10.0 the engine schedules those hours to the minute when they are handed on, as the XER parser does. Where they are not, or there is no data date or calendar, the whole-day pass runs alone: a sub-day lag raises a SUB_DAY_LAG_ROUNDED alert and rounds, and in forensic strict mode that alert is fatal, so the engine refuses to produce a result rather than quietly rounding. The case is validation/engine-limitations/cases/01-fractional-lag-engine-rounds/. Two results are still worked in whole days even with the hours pass: the driving predecessor, with the longest path traced through it, and the impact figure of a time impact analysis. On part-day work those can name the wrong driver or round a few hours of delay up to a day. Since v2.10.16 each is checked against the hours pass and withheld, with an alert, where the two disagree; nothing is worked out in its place, so on part-day work the result can be missing. The check does not cover a driver held by the data date or a constraint, and it does not catch every wrong driver: on the 380 exports compared with P6's own longest-path flags, 130 drivers still disagree with P6 and are not withheld.

3. Two cases are known to diverge from P6 by construction

Fractional lag and dangling relationships test inputs P6 itself cannot produce. Rather than leave them in the P6 comparison matrix where they would look like failures or be silently dropped, they were moved to validation/engine-limitations/cases/ and will never carry a P6 verdict. A dangling relationship, meaning one that references an activity not present in the file, raises an alert and the relationship is dropped; in forensic strict mode it is fatal.

P6-native comparison matrix: 13/13 validatedCaptured from Primavera P6 Professional 23.12 on 2026-08-11; first capture 6/13, five divergence families fixed against the pinned answers, now 13/13

The engine's answers were published before P6 was asked the same questions, and the capture itself was blind: an automated harness imported the 13 mini-projects into P6 Professional 23.12, a human pressed F9 once, and P6's stored answers were read back from the database. The first capture scored 6 of 13. The seven failures were diagnosed into five divergence families (working-day float units, open-end late-date seeding, mandatory-finish semantics, retained logic on out-of-sequence progress, and free-float conventions), fixed in both engine implementations against P6's pinned numbers, and the matrix re-verified at 13 of 13. Every fix is a public commit citing the capture.

#CaseEngine project finishAlertsP6 verdictWhat it tests
0101-fs-chain2026-01-190PASSBaseline FS chain. Should match P6 exactly.
0202-ss-with-lag2026-01-190PASSSS+5 with parallel-finish behaviour
0303-ff-with-lag2026-01-150PASSFF+3 forces B finish behind A finish
0404-sf-edge-case2026-01-120PASSLeast-common relationship type; SF behaviour varies with the P6 progress-override setting
0505-negative-float2026-01-211PASSFNLT constraint produces negative total float
0606-multiple-calendars2026-01-190PASSActivity-specific calendars, 5-day against 6-day
0707-ontario-holidays2026-05-130PASSLong activity across Ontario statutory holidays
0808-in-progress-retained-logic2026-01-280PASSPredecessor in progress; successor anchored to projected early finish
0909-completed-successor2026-01-122PASSBackward-pass skip; no pull-back through a historical finish
1010-out-of-sequence-progress2026-01-291PASSOut-of-sequence alert path
1111-mandatory-start-finish2026-01-302PASSMandatory Start and Mandatory Finish hard pins
1212-snet-fnlt2026-02-031PASSStart-No-Earlier-Than and Finish-No-Later-Than, the two most common P6 constraints
1313-alap2026-01-220PASSAs-Late-As-Possible secondary constraint

Alert counts include informational alerts. The remaining alerts are by design: the completed-successor backward-pass skip on case 09, out-of-sequence progress on case 10, and constraint-application alerts on cases 05, 11 and 12.

Two cases formerly numbered 14 and 15 are excluded by construction and live under validation/engine-limitations/. See the gaps drawer above.

Held-out P6 check H1: 195 of 206 activities on every fieldTwelve beacon-seeded schedules, published before P6 saw them, scored once on 13 October 2026

The order of events is what makes this check blind. The rules, the generator, the scorer and the P6 read-back were committed first, naming drand round 6549295. The twelve schedules were generated from that round's randomness after it was published and pushed before P6 saw any of them. Each was then imported into P6 Professional 23.12.1 and scheduled on its own, P6's answers were read back from its database read-only, and engine v2.10.1 was scored against them once. Nothing was changed between the freeze and the score.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H1-012929every field
H1-0221183 differ
H1-0314131 differ
H1-042323every field
H1-0566every field
H1-0666every field
H1-072929every field
H1-082222every field
H1-091515every field
H1-10752 differ
H1-1111101 differ
H1-1223194 differ
Total206195

The eleven activities that differ fall in four situations, each published field by field:

  • a Start On or Before constraint on work already started (H1-02, three activities);
  • As Late As Possible on work already started (H1-03, one activity);
  • an expected finish earlier than the data date on started work with a Start On constraint (H1-12, four activities);
  • free float measured across a lag on another calendar, with every date the same (H1-10 and H1-11, three activities).

The engine is not changed against this set. A later release that changes any of these is scored on a new held-out set, seeded the same way, and this result stays as published. The full record is in the repository's H1 result.

Held-out P6 check H2: 169 of 171 activities on every field, 174 of 176 with the read-back correctedThe engine changed to the rules H1 led to, then scored once on twelve new beacon-seeded schedules, 13 October 2026

The differences H1 found came down to three rules in P6: start constraints on work under way, As Late As Possible under way and past its late finish, and free float across a lag on another calendar. P6 probes measured those rules, and engine v2.10.2 was changed to reproduce them. The probes are therefore fitted. H2 is the held-out test of that change: the v2.10.2 bytes and the rules of the check were committed first, naming drand round 6549541, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and v2.10.2 was scored once.

169 of 171 unfinished activities match P6 on all seven fields, on the eleven schedules the read-back kept. It left out H2-09 on a false alarm: P6 stamps its schedule run in milliseconds and writes the rows it changes in whole seconds, and the check read those rows as edited after the run. Read back with that corrected, all twelve schedules score 174 of 176. Both results are published; the table shows all twelve, with H2-09 read back that way.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H2-0188every field
H2-021818every field
H2-0315141 differ
H2-0477every field
H2-051919every field
H2-06761 differ
H2-072929every field
H2-081414every field
H2-0955every field (read-back corrected)
H2-102626every field
H2-111111every field
H2-121717every field
Total176174

H2's random draw tested only the free-float rule, and the engine moved exactly two values in the whole set, both onto P6's. No schedule had started work under Start On or Before, Start On or Mandatory Start, or As Late As Possible work the new rule moves, so those two rules still rest on the fitted probes. The two activities that differ were already different in v2.10.1:

  • a Mandatory Start dated before the data date on work not started (H2-06): P6 starts the work at the data date and keeps the mandatory date on the late side;
  • free float to a start-to-start successor out of work under way, with lag counted from the actual start (H2-03): P6 measures it from the restart, one hour less than the engine.

The engine is not changed against H2. A later release that changes either situation is scored on a new held-out set. The full record is in the repository's H2 result.

Held-out P6 check H3: 244 of 253 activities on every fieldThe engine changed to the rules H2 led to, then scored once on twelve new beacon-seeded schedules, 13 October 2026

H2's two differences, a Mandatory Start dated before the data date on work not started and free float to a start-to-start successor out of work under way, were measured on six P6 probe projects, which also showed a third rule, for start-to-start and start-to-finish links out of work under way under progress override. Engine v2.10.3 was changed to reproduce all three, so the probes are fitted. H3 is the held-out test of that change: the v2.10.3 bytes and the rules of the check were committed first, naming drand round 6549889, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and v2.10.3 was scored once.

244 of 253 unfinished activities match P6 on all seven fields, and nine of the twelve schedules match on every field. Run on the same schedules after the score, as a diagnosis, v2.10.2 matches 226 of 253.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H3-011717every field
H3-023232every field
H3-032727every field
H3-0429263 differ
H3-051414every field
H3-062323every field
H3-071313every field
H3-082323every field
H3-091515every field
H3-101515every field
H3-1122175 differ
H3-1223221 differ
Total253244

Against v2.10.2 the engine moved 71 values on 18 activities, every one onto P6's and none away. H3 confirmed the mandatory-date rule on one activity and the progress-override rule on two schedules. It gave the free-float rule nothing to move, so that rule still rests on the fitted probes. The nine activities that differ were already different in v2.10.2, and they come from three situations:

  • under progress override with lag counted from Early Start, a start-to-start link out of work under way where the data date falls at the end of the predecessor's working day (H3-04): P6 starts the successor at the data date, the engine at the predecessor's restart. This falls inside the progress-override rule and counts against it;
  • a Start On or Before on a finish milestone (H3-11): P6 keeps the constraint on the activity but does not apply it;
  • a late start set by a start-to-start successor with a negative lag, landing exactly at a lunch break (H3-11, H3-12): P6 prints the end of the morning and the engine the start of the afternoon, the same working time on the activity's calendar but half an hour on a predecessor that works around the clock.

The engine is not changed against H3. A later release that changes any of these situations is scored on a new held-out set. The full record is in the repository's H3 result.

Held-out P6 check H4: 233 of 237 activities on every fieldThe engine changed to the rules H3 led to, then scored once on twelve new beacon-seeded schedules, 14 October 2026

H3's three differences were measured on eighteen P6 probe projects, and checking the changed engine against every project P6 had scheduled on the measuring machine showed two more rules, which the probes measured too: under progress override a start-to-start or start-to-finish link out of work under way is laid from the data date; a milestone keeps but never applies a constraint of its other end; a lead that lands on a close stays there as the late start; and a milestone sits no earlier than its expected finish. Engine v2.10.4 was changed to reproduce them, so the probes are fitted. H4 is the held-out test of that change: the v2.10.4 bytes and the rules of the check were committed first, naming drand round 6551257, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and v2.10.4 was scored once.

233 of 237 unfinished activities match P6 on all seven fields, and ten of the twelve schedules match on every field. Run on the same schedules after the score, as a diagnosis, v2.10.3 also matches 233 of 237, and none of the 1,659 compared values moved.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H4-011515every field
H4-023030every field
H4-032525every field
H4-041717every field
H4-052020every field
H4-062222every field
H4-0713103 differ
H4-0821201 differ
H4-091111every field
H4-102222every field
H4-112121every field
H4-122020every field
Total237233

H4 drew three of the progress-override links and four leads, but none where the old and new rules disagree, and no milestone with a constraint of its other end or an expected finish. So it neither confirms nor contradicts the four rules: they rest on the fitted probes, the check against the measuring machine's P6 database, and P6's own F9 of three real schedules the release moved, where v2.10.4 matches P6 on 1,747 of 1,749 activities and v2.10.3 on 657. The four activities that differ were already different in v2.10.3, and they come from two situations:

  • As Late As Possible work under way under progress override whose finish-to-start successor has started (H4-08): P6 slides its remaining work to its late dates, the engine holds it at the data date;
  • a Mandatory Start tied in working time with a start-to-start bound that falls on a weekend (H4-07): P6 prints the mandatory date as the late start, the engine the close before the weekend, which its seven-day predecessors carry as more negative float.

The engine is not changed against H4. A later release that changes either situation is scored on a new held-out set. The full record is in the repository's H4 result.

No held-out P6 check for v2.10.5The release changes a kind of relationship no held-out schedule can carry, 14 October 2026

Engine v2.10.5 changes one rule. A relationship that reaches the engine with a lag in days and no lag in hours, the way a time impact analysis hands its fragnet ties on, is now given its hours on the predecessor's calendar under every lag calendar, as P6 converts a lag typed in days. The P6 23.12 help says so, and two probe projects measured it in P6 Professional 23.12.1: a lag typed as one day was stored as the predecessor's ten hours under the successor and the project-default lag calendars alike. The probes are fitted.

No held-out set was drawn for this release. A held-out case is a P6 XER file, and every relationship in an XER already carries its lag in hours, so no case could exercise the change. Instead, nothing read from an XER was shown to move: no row moved in the measuring machine's P6 database (321 projects), and none of 678 XER files on the measuring machine, every H1 to H4 case among them, moved a value. H4's result stands for these bytes on its cases. The record is in the repository's changelog.

Held-out P6 check H5: 205 of 205 activities on every fieldThe engine changed to the rules H4 led to, then scored once on twelve new beacon-seeded schedules, 14 October 2026

H4's two differences were measured on four P6 probe projects: under progress override an FS or SS link into a successor already under way holds As Late As Possible work back no more than it binds the late dates, and a Mandatory Start or Mandatory Finish on work not started sets its own late date whatever its successors need, the earlier pin governing where an activity carries both. Engine v2.10.6 was changed to reproduce them, so the probes are fitted. H5 is the held-out test of that change: the v2.10.6 bytes and the rules of the check were committed first, naming drand round 6552540, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and v2.10.6 was scored once.

205 of 205 unfinished activities match P6 on all seven fields, and all twelve schedules match on every field: the first held-out set with no difference. Run on the same schedules after the score, as a diagnosis, v2.10.5 also matches 205 of 205.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H5-0199every field
H5-021212every field
H5-031616every field
H5-041414every field
H5-052121every field
H5-062525every field
H5-072626every field
H5-082121every field
H5-091919every field
H5-101717every field
H5-111010every field
H5-121515every field
Total205205

H5 drew one As Late As Possible activity and one Mandatory Start with a successor, but neither where the old and new rules disagree. So it neither confirms nor contradicts the two rules: they rest on the fitted probes and the check against the measuring machine's P6 database, where no row outside the probes moved. The engine is not changed against H5. The full record is in the repository's H5 result.

Held-out P6 check H6: 255 of 260 activities on every field, dated before 2020The day-number change scored once on twelve beacon-seeded schedules dated before or across 1 January 2020, 16 October 2026

Engine v2.10.7 counts its day numbers from 1 January 1900, so its whole-day pass keeps the dates of a schedule dated before 2020; until then that pass read every earlier date as none. H1 to H5 were all dated in 2026 and 2027, so H6 was drawn before 2020: six schedules lie wholly before it, with data dates between February 2013 and April 2017, and six started in December 2019 and run across 1 January 2020. The candidate's bytes and the rules of the check were committed first, naming drand round 6557819, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and the candidate was scored once.

255 of 260 unfinished activities match P6 on all seven fields, and eleven of the twelve schedules match on every field. The five that differ are all in H6-07 and were already there in v2.10.6. They came from two situations that eight P6 probe projects then measured and the released v2.10.7 reproduces, so H6 is fitted for the release; H7 below tests it. In the whole-day pass, where the day-number change lies, the candidate put 147 of the 260 early starts on P6's calendar day; v2.10.6, run on the same schedules after the score as a diagnosis, put 55 there, and none on the six schedules dated wholly before 2020.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H6-012424every field
H6-022626every field
H6-031212every field
H6-042828every field
H6-052525every field
H6-062323every field
H6-0721165 differ
H6-083030every field
H6-092222every field
H6-102525every field
H6-1177every field
H6-121717every field
Total260255

The full record is in the repository's H6 result.

Held-out P6 check H7: 242 of 242 activities on every field, dated before 2020The released v2.10.7, with the two rules H6 led to, scored once on twelve new beacon-seeded schedules, 16 October 2026

H6's five differences came from two situations, measured on eight P6 probe projects: As Late As Possible work under way slides over a start-to-start or start-to-finish link out of it by only the unexpired part of the lag, and under "Calculate start-to-start lag from: Actual Start" a start-to-start link out of completed work carries no date that work carries from unfinished predecessors. Engine v2.10.7 was changed to reproduce them, so the probes are fitted. H7 is the held-out test of the released bytes: they and the rules of the check were committed first, naming drand round 6558070, and the twelve schedules, drawn as H6's were, were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and v2.10.7 was scored once.

242 of 242 unfinished activities match P6 on all seven fields, and all twelve schedules match on every field. The draw gave the start-to-start rule eight activities to decide, all in one schedule: v2.10.7 matches P6 on all eight, where v2.10.6, run on the same schedules after the score as a diagnosis, matches 234 of 242. Another schedule had the rule's other side, under "Early Start", where P6 keeps the carried date, as v2.10.7 does. H7 drew no case for the As Late As Possible rule, which rests on the probes.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H7-011515every field
H7-022929every field
H7-0388every field
H7-041414every field
H7-051919every field
H7-062727every field
H7-072727every field
H7-083737every field
H7-092525every field
H7-102121every field
H7-111313every field
H7-1277every field
Total242242

The engine is not changed against H7. The full record is in the repository's H7 result.

Held-out P6 checks H8 to H15: the sets that found what v2.10.8 settlesEight sets drawn wider, each scored once, each difference measured in P6 before the next set

H8 to H15 were drawn wider than H1 to H7: resource dependent work, level of effort, WBS summary, suspended work and external dates, with about half the schedules under progress override. H8 scored the released v2.10.7; each later set scored the v2.10.8 candidate as it then stood. Each was committed before its drand round was published, drawn from that round, and scored once against P6 Professional 23.12.1's own F9 of the twelve schedules.

Every difference was then measured on synthetic P6 probe projects, 41 in all, and the engine was changed to reproduce them before the next set was drawn. So these sets are fitted for the released engine; H16 below is the held-out test of it. One rule taken on the way moved a row v2.10.7 had right; H15 caught it, and the release restores it.

SetEngine scoredMatch on all seven fieldsWhat its differences led to
H8v2.10.7, released247 of 248resource dependent work under way (RU, RF)
H9first v2.10.8 candidate247 of 280a resume date under progress override (RO), completed successors (FC, CL), total float (RT), bounds and requirements on resource dependent work (RL, RQ)
H10v2.10.8 candidate236 of 238finish requirements and leads on resource dependent work (RQ in full, RW, LC)
H11v2.10.8 candidate230 of 231start float of resource dependent work under way under retained logic (RT narrowed)
H12v2.10.8 candidate274 of 279a requirement after an expected finish (RX), the Must Finish By against a mandatory pin (MC), a bound held against the activity (RA)
H13v2.10.8 candidate255 of 259an assignment with no work left (RZ), a bound on a resource's day off (RG)
H14v2.10.8 candidate197 of 198As Late As Possible work before a completed successor (AC)
H15v2.10.8 candidate216 of 217a bound before the activity calendar opens (RB)

Each set's full record is in the repository's held-out folder.

Held-out P6 check H16: 217 of 217 activities on every fieldThe released v2.10.8 scored once on twelve new beacon-seeded schedules

H16 is the held-out test of the released v2.10.8. The engine's bytes and the rules of the check were committed first, naming drand round 6562357, and the twelve schedules, drawn as H8 to H15 were, were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and v2.10.8 was scored once.

217 of 217 unfinished activities match P6 on all seven fields, and all twelve schedules match on every field. v2.10.7, run on the same schedules after the score as a diagnosis, matches 136: the rules the release adds moved 81 activities onto P6 and none away.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H16-0166every field
H16-021515every field
H16-032020every field
H16-042222every field
H16-053030every field
H16-0677every field
H16-072222every field
H16-082222every field
H16-091414every field
H16-102525every field
H16-111818every field
H16-121616every field
Total217217

The engine is not changed against H16. The full record is in the repository's H16 result.

No new held-out P6 check for v2.10.9One rule measured on P6 probes; the sixteen held-out sets re-scored, 18 October 2026

Engine v2.10.9 changes one rule: how both passes count the part of a start-to-start or start-to-finish lag that a suspended predecessor has used up. P6 counts the working time from the predecessor's actual start to the data date less the time it sat suspended, and its backward pass counts the whole of it. Five synthetic probe projects measured the shape in P6 Professional 23.12.1, 92 of 92 rows; the probes are fitted.

No new held-out set was drawn for this release. The sixteen sets already scored were run again on the release bytes instead: all 3,741 unfinished activities still match P6 on all seven fields. A first draft of the rule moved four late dates in H13 away from P6's own F9, and the release follows P6 there. The record is in the repository's changelog.

Held-out P6 check H17: 207 of 208 activities on every fieldThe released v2.10.9 scored once; its one difference measured on P6 probes and settled in v2.10.10

H17 is the held-out test of the released v2.10.9. The engine's bytes and the rules of the check were committed first, naming drand round 6563706, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and v2.10.9 was scored once.

207 of 208 unfinished activities match P6 on all seven fields, and eleven of the twelve schedules match on every field. The one difference is an early finish an hour late: resource dependent work whose start a finish requirement sets, where an assignment with no work sits after the requirement. Seven synthetic probe projects measured the shape in P6, and v2.10.10 follows P6 there.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H17-011616every field
H17-0299every field
H17-032929every field
H17-042121every field
H17-051919every field
H17-061212every field
H17-071111every field
H17-0899every field
H17-091919every field
H17-1024231 differs
H17-111111every field
H17-122828every field
Total208207

The engine is not changed against H17. The full record is in the repository's H17 result.

Held-out P6 check H18: 208 of 208 activities on every fieldThe v2.10.10 candidate scored once on twelve new beacon-seeded schedules

H18 is the first held-out test of the v2.10.10 candidate. The engine's bytes and the rules of the check were committed first, naming drand round 6563835, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and the engine was scored once.

208 of 208 unfinished activities match P6 on all seven fields, and all twelve schedules match on every field. v2.10.9, run on the same schedules after the score as a diagnosis, also matches 208: the draw gave the new rule nothing to decide. One schedule was scheduled twice in a row in P6, with nothing changed between; the result reports it.

After the score, the release checks ran the Python engine the analysis tools compute with over every project in P6's database. On one H18 activity it printed a late finish half an hour early, where P6 and this engine agree: in two releases the Python engine alone had run one rule, RL, where no bound was taken. The Python engine was corrected, and a new cross-validation fixture, F178, pins the case.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H18-011515every field
H18-021212every field
H18-032929every field
H18-042828every field
H18-0599every field
H18-0688every field
H18-071212every field
H18-082525every field
H18-092626every field
H18-101010every field
H18-1188every field
H18-122626every field
Total208208

The engine is not changed against H18. The full record is in the repository's H18 result.

Held-out P6 check H19: 216 of 216 activities on every fieldThe v2.10.10 release scored once on twelve schedules dated before or across 1 January 2020

H19 is the held-out test of the v2.10.10 release, drawn after both of its changes. The engine's bytes and the rules of the check were committed first, naming drand round 6563983, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. It is the first set to draw resource dependent work, level of effort, WBS summary, suspended work and external dates on schedules dated before or across 1 January 2020. P6 Professional 23.12.1 scheduled each on its own, exactly once, and the engine was scored once.

216 of 216 unfinished activities match P6 on all seven fields, and all twelve schedules match on every field. v2.10.9, run on the same schedules after the score as a diagnosis, also matches 216, and the Python engine matches P6 on all 216 of its activities.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H19-012222every field
H19-021919every field
H19-032525every field
H19-041919every field
H19-0566every field
H19-061111every field
H19-072727every field
H19-081313every field
H19-091010every field
H19-103030every field
H19-111111every field
H19-122323every field
Total216216

The engine is not changed against H19. The full record is in the repository's H19 result.

Held-out P6 check H20: 195 of 198 activities on every fieldThe released v2.10.10 scored once; its one difference measured on P6 probes and settled in v2.10.11

H20 is the held-out test of the released v2.10.10. The engine's bytes and the rules of the check were committed first, naming drand round 6564238, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and v2.10.10 was scored once.

195 of 198 unfinished activities match P6 on all seven fields, and eleven of the twelve schedules match on every field. The three differences are one: work under way with an expected finish and an As Late As Possible constraint, where P6 keeps the stored remaining duration and the engine re-sized it to the expected finish, and the two activities it drives. Three synthetic probe projects measured the shape in P6, and v2.10.11 follows P6 there.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H20-011414every field
H20-021717every field
H20-031212every field
H20-0488every field
H20-051414every field
H20-062727every field
H20-071010every field
H20-081414every field
H20-091010every field
H20-103030every field
H20-1127243 differ
H20-121515every field
Total198195

The engine is not changed against H20. The full record is in the repository's H20 result.

Held-out P6 check H21: 205 of 205 activities on every fieldThe v2.10.11 release scored once on twelve schedules, nine of them under "Make open-ended activities critical"

H21 is the held-out test of the v2.10.11 release, drawn after all three of its rules. The engine's bytes and the rules of the check were committed first, naming drand round 6564613, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. It is the first set to draw P6's "Make open-ended activities critical", an option no earlier version of the engine read. P6 Professional 23.12.1 scheduled each on its own, exactly once, and the engine was scored once.

205 of 205 unfinished activities match P6 on all seven fields, and all twelve schedules match on every field. v2.10.10, run on the same schedules after the score as a diagnosis, matches 190: the fifteen that moved onto P6 are open ends under the option and the work leading to them. The Python engine the analysis tools compute with matches P6 on all 205. In the capture one project outside the set was opened by mistake and closed unscheduled, and one schedule was opened a second time after its F9 and not scheduled again; the result reports both.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H21-011818every field
H21-0277every field
H21-031919every field
H21-042828every field
H21-051313every field
H21-061212every field
H21-071515every field
H21-081010every field
H21-091616every field
H21-103232every field
H21-111414every field
H21-122121every field
Total205205

The draw gave two of the rules, no expected finish on As Late As Possible work and an expected finish against a finish constraint on work not yet started, nothing to decide; they rest on their probes. The engine is not changed against H21. The full record is in the repository's H21 result.

Held-out P6 check H22: 268 of 271 activities on every fieldThe released v2.10.11 scored once; its three differences are one shape, measured on P6 probes and settled in v2.10.12

H22 is the held-out test of the released v2.10.11, drawn to give the shapes H21 left undecided something to decide. The engine's bytes and the rules of the check were committed first, naming drand round 6564899, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and the engine was scored once.

268 of 271 unfinished activities match P6 on all seven fields, and eleven of the twelve schedules match on every field. The three differences are one: work not yet started with a Mandatory Finish and an expected finish, under "Use Expected Finish Dates", whose successor needs it finished a working day before the Mandatory Finish date. P6 takes that earlier date as its late finish, where v2.10.11 kept the Mandatory Finish date, and the two activities leading to it follow. Every shape H21 gave nothing to decide is drawn here, and outside that one difference every activity they decide matches P6. Synthetic probe projects measured the shape in P6, and v2.10.12 follows P6 there.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H22-011212every field
H22-021515every field
H22-032323every field
H22-042525every field
H22-053232every field
H22-0614113 differ
H22-072626every field
H22-082727every field
H22-092525every field
H22-101515every field
H22-112222every field
H22-123535every field
Total271268

Its result stays as published. The rules taken after it are fitted on the probes and on H22, and H23 is their held-out test. The full record is in the repository's H22 result.

Held-out P6 check H23: 262 of 289 activities on every fieldThe first v2.10.12 candidate scored once; seven shapes found and measured on P6 probes

H23 is the held-out test of the first v2.10.12 candidate, drawn after its first four rules (XP, XC on finish milestones, MR and PC). The engine's bytes and the rules of the check were committed first, naming drand round 6565800, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and the engine was scored once.

262 of 289 unfinished activities match P6 on all seven fields, and five of the twelve schedules match on every field. The four rules decided 43 activities, and 39 match P6. The 27 differences come from seven shapes, and 15 of them from one activity: resource dependent work whose resources first work on a day its own calendar has off. The released v2.10.11, run on the same schedules after the score as a diagnosis, matches 217.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H23-011717every field
H23-0231301 differs
H23-0331283 differ
H23-0428253 differ
H23-0525241 differs
H23-061414every field
H23-0723221 differs
H23-0835323 differ
H23-091212every field
H23-102121every field
H23-112626every field
H23-12261115 differ
Total289262

Its result stays as published. Synthetic probe projects measured the seven shapes; the rules taken from them are fitted on the probes and on H23, and H24 is their held-out test. The full record is in the repository's H23 result.

Held-out P6 check H24: 267 of 278 activities on every fieldThe second v2.10.12 candidate scored once; five resource dependent shapes found and measured on P6 probes

H24 is the held-out test of the second v2.10.12 candidate, drawn after the rules H23's shapes gave: a constraint in each slot, a Mandatory Start against finish-to-finish logic, mandatory dates against external dates, the free float of As Late As Possible work with and without "Calculate multiple float paths", and four rules in the hours pass, three of them for resource dependent work. The engine's bytes and the rules of the check were committed first, naming drand round 6566664, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and the engine was scored once.

267 of 278 unfinished activities match P6 on all seven fields, and eight of the twelve schedules match on every field. The 11 differences come from five shapes, all resource dependent work. Outside them every activity the rules' shapes drew matches P6, and the free float of all 34 unfinished As Late As Possible activities matches, under the option and without it. v2.10.11, run on the same schedules after the score, matches 178.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H24-012525every field
H24-021616every field
H24-0328271 differs
H24-043636every field
H24-052525every field
H24-061818every field
H24-072323every field
H24-0899every field
H24-0933321 differs
H24-1032275 differ
H24-111919every field
H24-1214104 differ
Total278267

Its result stays as published. Synthetic probe projects measured the five shapes; the rules taken from them are fitted on the probes and on H24, and H25 is their held-out test. The full record is in the repository's H24 result.

Held-out P6 check H25: 384 of 391 activities on every fieldThe third v2.10.12 candidate scored once; two resource dependent shapes found and measured on P6 probes

H25 is the held-out test of the third v2.10.12 candidate, drawn after the rules H24's shapes gave, all for resource dependent work on its resources' own hours. The engine's bytes and the rules of the check were committed first, naming drand round 6566986, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and the engine was scored once.

384 of 391 unfinished activities match P6 on all seven fields, and nine of the twelve schedules match on every field. The 7 differences come from two shapes, both resource dependent work: an assignment with no work and a lag under a start-to-start bound, beside another assignment that sets a later late finish, and As Late As Possible work whose finish already sits at its slide's bound. v2.10.11, run on the same schedules after the score, matches 282.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H25-0134322 differ
H25-0245414 differ
H25-033131every field
H25-042323every field
H25-054242every field
H25-062929every field
H25-073535every field
H25-0821201 differs
H25-093636every field
H25-103535every field
H25-113030every field
H25-123030every field
Total391384

Its result stays as published. Synthetic probe projects measured both shapes; the three rules taken from them (ZF, BM and AG) are fitted on the probes and on H25, and H26 is their held-out test. The full record is in the repository's H25 result.

Held-out P6 check H26: 407 of 407 activities on every fieldThe v2.10.12 release scored once, drawn after all twenty-one of its rules, through one deviation committed before any value was compared

H26 is the held-out test of the v2.10.12 release, drawn after all twenty-one of its rules. The engine's bytes and the rules of the check were committed first, naming drand round 6567266, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and the engine was scored once.

407 of 407 unfinished activities match P6 on all seven fields, and all twelve schedules match on every field. One schedule needed a deviation from the protocol, committed before any value was compared: the generator drew the same resource twice on one activity, P6's import kept only the first of the two assignments, and that schedule (37 of 37) is scored on the schedule P6 holds. The other eleven (370 of 370) are scored exactly as frozen. Run on the same schedules after the score, the candidate before the last three rules matches 391 and v2.10.11 217. The Python engine the analysis tools compute with matches P6 on all 407 once the sizing P6's F9 rewrote is set back to its import.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H26-012525every field
H26-022727every field
H26-033131every field
H26-043434every field
H26-055151every field
H26-063939every field
H26-073232every field
H26-083737every field
H26-093232every field
H26-103333every field
H26-113737every field
H26-122929every field
Total407407

The engine is not changed against H26, and its result stays as published. The full record is in the repository's H26 result, with the deviation.

Held-out P6 check H27: 507 of 524 activities on every fieldThe released v2.10.12 scored once, drawn on the shapes it listed as not measured; five causes found and measured on P6 probes

H27 is the held-out test of the released v2.10.12, drawn on the shapes its validation listed as not measured. The engine's bytes and the rules of the check were committed first, naming drand round 6568106, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and the engine was scored once.

507 of 524 unfinished activities match P6 on all seven fields, and five of the twelve schedules match on every field. All 17 start milestones with an expected finish match. The 17 differences fall under five causes: a Finish On or Before and a Mandatory Finish on work under way, an assignment with no units on work under way, a finish milestone at a 24-hour day's close before a day off, As Late As Possible resource dependent work led out start-to-start, and resource dependent work after a lagged start-to-finish link. v2.10.11, run on the same schedules after the score as a diagnosis, matches 337.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H27-014141every field
H27-024848every field
H27-034040every field
H27-0449481 differs
H27-052626every field
H27-0634331 differs
H27-0745432 differ
H27-0851492 differ
H27-0948399 differ
H27-1036351 differs
H27-1156551 differs
H27-125050every field
Total524507

Its result stays as published. Synthetic probe projects measured the five causes; the rules taken from them are fitted on the probes and on H27, and H28 is their held-out test. The full record is in the repository's H27 result.

Held-out P6 check H28: 537 of 563 activities on every fieldThe first v2.10.13 candidate scored once; six causes found and measured on P6 probes

H28 is the held-out test of the first v2.10.13 candidate, drawn after the rules H27's causes gave and the rule for the activity "Calculate multiple float paths" ends at, which was set in P6 for the five schedules that name one. The engine's bytes and the rules of the check were committed first, naming drand round 6568526, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and the engine was scored once.

537 of 563 unfinished activities match P6 on all seven fields, and six of the twelve schedules match on every field. The rules taken after H27 hold on every activity they govern but one, which differs by another cause, and no activity differs by the end of the float paths. Of the 26 differences, 16 are the start float of resource dependent work in one schedule, whose options P6's own dialog had stored as "Start Float" under a code of its own, and 5 are As Late As Possible resource dependent work under a start bound, where the first reading of that rule does not hold. v2.10.12, run on the same schedules after the score, matches 501.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H28-015757every field
H28-0241392 differ
H28-0337352 differ
H28-044242every field
H28-054040every field
H28-065555every field
H28-07553817 differ
H28-084646every field
H28-0955532 differ
H28-103131every field
H28-1153512 differ
H28-1251501 differs
Total563537

Its result stays as published. Synthetic probe projects measured the six causes; the rules taken from them are fitted on the probes and on H28, and H29 is their held-out test. The full record is in the repository's H28 result.

Held-out P6 check H29: 673 of 676 activities on every fieldThe second v2.10.13 candidate scored once; two causes found and measured on P6 probes

H29 is the held-out test of the second v2.10.13 candidate, drawn after the rules H28's causes gave, with the end of the float paths set in P6 for the three schedules that name one. The engine's bytes and the rules of the check were committed first, naming drand round 6569006, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and the engine was scored once.

673 of 676 unfinished activities match P6 on all seven fields, and nine of the twelve schedules match on every field. The new placement of As Late As Possible resource dependent work under a start bound and the finish-floor rule hold on every activity they govern. The 3 differences fall under two shapes no probe had measured: As Late As Possible work with no work left on an activity calendar that does not work the day its resource opens, and a Mandatory Start on resource dependent work whose resource is off around it. v2.10.12, run on the same schedules after the score, matches 579.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H29-015656every field
H29-027171every field
H29-035050every field
H29-044343every field
H29-0564631 differs
H29-0670691 differs
H29-075858every field
H29-085454every field
H29-095353every field
H29-104848every field
H29-115353every field
H29-1256551 differs
Total676673

Its result stays as published. Synthetic probe projects measured both shapes and the float-type codes P6 reads; the rules taken from them are fitted on the probes and on H29, and H30 is their held-out test. The full record is in the repository's H29 result.

Held-out P6 check H30: 675 of 675 activities on every fieldThe v2.10.13 release scored once, drawn after all twelve of its rules

H30 is the held-out test of the v2.10.13 release, drawn after all twelve of its rules, with the end of the float paths set in P6 for the four schedules that name one. The engine's bytes and the rules of the check were committed first, naming drand round 6572006, and the twelve schedules were generated from that round after it was published and pushed before P6 saw them. P6 Professional 23.12.1 scheduled each on its own, and the engine was scored once.

675 of 675 unfinished activities match P6 on all seven fields, and all twelve schedules match on every field, with no deviation in what was scored. Every shape the draw gave the last rules matches P6: 12 As Late As Possible activities with no work left, 16 Mandatory Starts on resource dependent work, and every activity of the six schedules whose float type is the code P6's dialog writes for "Start Float" or the code it reads as "Start Float". Run on the same schedules after the score, the candidate H29 scored matches 625 and v2.10.12 559. The Python engine the analysis tools compute with matches P6 on all 675 once the sizing P6's F9 rewrote is set back to its import.

ScheduleUnfinished activitiesMatch on all seven fieldsResult
H30-014848every field
H30-026161every field
H30-034646every field
H30-046767every field
H30-055656every field
H30-064949every field
H30-077171every field
H30-085252every field
H30-096565every field
H30-104141every field
H30-116666every field
H30-125353every field
Total675675

The engine is not changed against H30, and its result stays as published. The full record is in the repository's H30 result.

No held-out P6 check for v2.10.14The release changes the driver the engine records for work under way, not a date, 25 October 2026

Engine v2.10.14 changes what the engine records as the driver of an activity already under way. It now names what holds the remaining work, the same way in the JavaScript engine and the Python reference; before, it named the predecessor that would have driven the actual start, and the two engines broke ties differently. A driver trace or a longest-path chain through work under way can change. No date, float or critical flag moved in any measurement.

No held-out set was drawn for this release. A held-out case scores dates and floats, and none moved: every one is identical to v2.10.13 on 949 projects in the measuring machine's P6 database, and held-out sets H1 to H30, re-scored on these bytes in diagnosis mode, match P6 on all 8,850 unfinished activities, as before. The two engines agree on the recorded driver of all 193,868 activities of those 949 projects. The record is in the repository's changelog.

No held-out P6 check for v2.10.17The release changes no code in this engine; the skills' own Python engine counts working days faster, 9 November 2026

Engine v2.10.17 changes no code in the engine this page validates: cpm-engine.js and its Python reference differ from v2.10.16 only in their version string. The release records the Python engine that CPP's analysis skills and hosted tools run counting working days by whole weeks instead of day by day, with every computed value identical: the same count on 8,206 and 15,000 randomised cases, every date and float the same on 560 schedules from real P6 exports, and this page's 233 fixtures run against that engine at 22189 of 22189. Those checks were made outside the repository, on code that is not in it.

No held-out set was drawn for this release: nothing computed moves. Held-out sets H1 to H30, re-scored in diagnosis mode, match P6 on 8,850 of 8,850 unfinished activities, as before. One thing a report can show differently: the DCMA-14 Critical Path Test in those skills stops at a time limit, and a test that stopped on v2.10.16 can now finish. The record is in the repository's changelog.

No held-out P6 check for v2.10.16The release withholds the driver, the longest path and the TIA impact where the hours pass disagrees; no date moves, 6 November 2026

Engine v2.10.16 checks three results that were still read off the whole-day pass, the driving predecessor, the longest path traced through the drivers and the impact figure of a time impact analysis, against the hours pass. Where the two disagree on part-day work, the result is withheld with an alert; nothing is worked out in its place. The TFM set, the activities with no float, now reads the float the hours pass prints.

No held-out set was drawn for this release: no date, float or critical flag moves. Of the 1,502 exports that schedule in the measuring machine's working folders and OneDrive, compared field by field against v2.10.15, per activity only the recorded driver moved, on 305 activities in 204 files. The longest path is withheld in 149 of them, and the TFM set changed in 339, where it now matches the critical flags. Held-out sets H1 to H30, re-scored in diagnosis mode, match P6 on 8,850 of 8,850 unfinished activities, as before; the driver is not among the fields they compare. The record is in the repository's changelog.

No held-out P6 check for v2.10.15The release reads a blank lag in an XER as a zero lag; no file on hand moves, 1 November 2026

Engine v2.10.15 changes how the XER parser reads a relationship whose lag field is blank. A P6 6.0 export leaves zero lags blank. Since v2.9.12 the parser had dropped each such relationship, with an alert naming it; it now reads the blank as a zero lag, as it did before v2.9.12. An outside user reported it, from an export the parser had cut 2,614 of 3,235 relationships from.

No held-out set was drawn for this release. None of the 1,477 exports in the measuring machine's working folders carries a blank lag, and the 1,465 of them that schedule give identical results on v2.10.14 and v2.10.15. Held-out sets H1 to H30, re-scored on these bytes in diagnosis mode, match P6 on all 8,850 unfinished activities, as before. The record is in the repository's changelog.

The other gate suitesEight checks that run alongside the cross-validation, including a client-name guard
  1. Unit suite. 1524 unit tests across calendar arithmetic, the forward and backward pass, topological sort, cycle detection and the public API. 0 failures.
  2. No fabricated citations. Scans markdown, JavaScript, HTML and Python for standards citations that do not resolve.
  3. No client names. Scans tracked source for client and project identifiers, so no real engagement can reach a public commit.
  4. No truncation. Confirms engine surfaces return complete data rather than a capped subset.
  5. No stale version references. Scanned 16 files, 3876 lines and 464 version references against the current engine version.
  6. Procedure validator. Four fixtures exercising the documented analysis procedure.
  7. Signing and attestation. Seven sub-suites over the cryptographic sign-off path.
  8. P6 comparison validator and corpus topology. Seven scenarios, one real and six synthetic, plus a topology fixture confirming the diamond-cascade shape of case 13.

The release-evidence packet for v2.9.39 was built from the release commit and passed its strict gate with all ten required files present; the v2.9.43 packet did the same (Sigstore Rekor logIndex 2685194682), the v2.9.44 packet did (CI-signed witness, Sigstore Rekor logIndex 2854361810), the v2.10.7 packet did (CI-signed witness, Sigstore Rekor logIndex 3286607165), the v2.10.8 packet did (CI-signed witness, Sigstore Rekor logIndex 3309924126), the v2.10.9 packet did (CI-signed witness, Sigstore Rekor logIndex 3312757577), the v2.10.10 packet did (CI-signed witness, Sigstore Rekor logIndex 3316054053), the v2.10.11 packet did (CI-signed witness, Sigstore Rekor logIndex 3319146213), the v2.10.12 packet did (CI-signed witness, Sigstore Rekor logIndex 3330023845), the v2.10.13 packet did (CI-signed witness, Sigstore Rekor logIndex 3386066936), the v2.10.14 packet did (CI-signed witness, Sigstore Rekor logIndex 3462156401), the v2.10.15 packet did (CI-signed witness, Sigstore Rekor logIndex 3635570997), and the v2.10.16 packet did (CI-signed witness, Sigstore Rekor logIndex 3711479435), and the v2.10.17 packet does (CI-signed witness, Sigstore Rekor logIndex 3761547375).