Ptable ReferenceacType ReferenceunitTest ReferenceThis guide collects the rules, conventions and hard-won lessons for changing the V5 code on the develop branch with an AI coding agent. It is written for both people and agents: each rule says what to do and why, so an agent can apply it and a person can judge when it doesn't fit.
To use it, point your agent at this file at the start of a session. For example:
Before making any change, read
version5/doc/dev/aiGuide.mdand follow it. Also read the documents it links to when a task touches their area.
Agents that read a project instruction file find this guide on their own: CLAUDE.md in the repository root imports it for Claude Code, and AGENTS.md points other agents to it. When version5/ moves to the repository root, drop the version5/ prefix from these paths and from both files.
Developers: you are expected to have good coding skills and to know this codebase and its rules. AI agents are your development partners. They know general best practices widely, but their working memory doesn't hold the whole codebase, so they sometimes patch a symptom where a higher-level change is needed. Review all AI-generated code before you check it in.
Agents: act as a trusted development partner, not just an order-taker. Treat the rules in this document as project policy: where one conflicts with a general habit of yours (for example committing after every change, or adding explanatory comments), the rule here wins. If a request conflicts with this guide or with general good practice, or a fix looks like a local patch for a problem that belongs at a higher level, say so before acting: name the rule or practice, explain the risk and suggest an alternative. Raise real problems, not matters of taste. The developer has the final say. Once they decide, do it their way without reopening the question, and never quietly do something different. If they overrule a rule in this guide, mention it in your report so a reviewer can see it.
version5/ and is the product. version4/ is the legacy code base; change it only when asked. Once every app has shipped on V5, version4/ will be deleted and the contents of version5/ will move to the repository root. Paths in this guide are relative to the V5 project root, currently version5/.source/app/<App>/buildStamp/ plus the shared source/app/common/. Native iOS and Android apps are built from the same web image with Cordova. See Directory Layout.tml templates. Start with the MVCS Overview.develop branch. Don't create a feature branch or a separate worktree unless the developer asks for one.git diff on each first: the developer may be editing the same files at the same time, so commit only your own hunks, or ask.V5 <App>: <summary>, for example V5 TBM: Fix enroute data errors found in PIM audit. Use V5 All: for a change to every app, a comma list for a few (V5 Baron,C310:), V5 Doc: for documentation and Common: for common code. Keep the message to the title and a few short bullets.The aircraft apps share near-duplicate files: page/aircraft.js, page/weightBalance.js, page/enroute.js, aircraftData/typeData.js and their siblings exist in each app with small differences. A fix in one app's file almost always belongs in the others.
source/app/common/, grep the same code anchor across source/app/*/buildStamp/ to learn the real scope.baseMethods instead of inline).Aircraft data lives in source/app/<App>/buildStamp/aircraftData/. The main reference is How to Make a New Aircraft App.
A reviewer must be able to open the POH page and see the same digits in the code.
typeData.js sets with Ptable.defaultUnits(), declare them on the Ptable axis or output (units:'mph', units:'dF', units:'pph'). The Ptable converts at query time. Don't pre-convert values to knots or Celsius, and don't add // 60 mph comments next to converted literals.inputs: ['flaps', {key:'ias', limit:'offset', units:'mph'}],
output: {key:'cas', rndMult:1, units:'mph'},
{p:1600, v:mphToKt(55 - 6)}, // 55 mph CAS, POH p. 3-9, less 6 mph = IAS
not {p:1600, v:42.6}.interpolate(x, x1, y1, x2, y2) with the POH values as its arguments:{p:1870, v:interpolate(50, 0, 1870, 41, 7000)}, // extrapolated
tasNoFairingAdj: mphToKt(-2)) may use conversion functions.Each aircraft type's typeData.js holds a set of base methods for each phase of flight (tko.roll, climb.rate, cruise.tas and so on). They fit a large set of the type's models, usually the newer ones, and every model gets them without declaring them. See Create the type data and Type-wide data.
typeData.js....parentData, fuel: otherData.fuel) to inherit a few individual items or tables.modelDataMergeUnder(child, parent) (child wins) or modelDataMergeOver from the app's typeData.js to inherit whole sub-objects such as wb, tko, climb, cruise or ldg.acType.createModel by reference. Never pass objectCopy(data): other files (for example circuit-breaker panels) attach to the data object after the model is created.climb or cruise data must declare maxAltitude explicitly rather than inherit it.maxAltitude must not exceed the extent of either the climb or the cruise tables. If it does, the enroute climb to cruise altitude has no data and every cruise output goes blank, with no error.Ptable detailsinterpolate() return an error value (test it with Ptable.isPtableError(); it displays as a struck-through "POH"). It throws only when asked to, with interpolateT() or the throwError option. See Range errors. An axis limit changes what happens out of range:clamp pins both ends; clampLo and clampHi pin one end and return an error on the other.offset extrapolates by parallel shift on both ends. It is only valid when the output key matches the first input key (adjustment tables, IAS-to-CAS), because the shift is applied in the input's units. For a weight-to-speed table use clamp.weight and pressureAltitude input gets limit:'clampLo' (or clamp), so a lighter weight or lower altitude than the POH charts uses its lowest line instead of failing.rndMult rounds after conversion to the app's internal unit. A units:'pph' fuel-flow table with rndMult:1 rounds to whole gallons per hour; use rndMult:0.1. Never put a coarse rndMult on a multiplicative factor table.units on its output is read in the app's default fuel-flow units (gph). Check the POH's units against the defaults in typeData.js for every table.sv.ac_model.value === 'C310Q'). The next model with the same characteristics will be missed. Derive it from the model's data (table limits, input keys, row counts), or add a small declared data key, and write "the POH" rather than a model name in user-visible text.acData.hasStaticAirTemperature (and acData.satOffset on the TBM). A model merged under a model with static air temperature inherits the flag and must set it to false explicitly if its panel shows total air temperature.optionOn (selectable on matching serials) or installedOn (fitted by the model), or both. Their values are applicability strings, described in Applicability string. The model part is a RegExp, so it can match more models than intended when one model ID contains another; anchor it with ^ and $ when that happens. A malformed string matches nothing, silently, so test that the option appears where it should.installedOn, or pulled in by another option's includes). A greyed-out row is only for a real choice that another selection ruled out (optClass, incompatibleWith, a removed enabledBy). options.permanent is the set of hidden options; if you add a mechanism that installs an option behind the user's back, add it to permanent.Follow the Coding Style Guide. The points agents most often miss:
return (value);, a space before the parameters in a function definition and none in a call, case aligned with switch, + without spaces for string concatenation.upgradeSavedState(), upgradeAircraftToV5(), idTranslations and the like). Elsewhere, describe the V5 rule without its history.common/script/localDevice.js only detects the platform. Code that acts on it, such as adding a class to <html>, goes in each app's app.js.{{id}}, hasChanged= and hasValue= in a tml template must name a declared SV. make lint runs tmlLint.js to check this. If it reports an id as unexpandable, teach tmlLint.js the new declaration form; never add a wildcard.<When> or option-only card only fails when that condition first renders, which is why the lint check exists.tml text, write & for a literal ampersand. A bare & followed by word characters and a later ; in the same string is parsed as a character entity.tml trims leading and trailing whitespace from text, so a multi-line template string (a dataSource, say) needs no trimming and its whitespace can't cause a layout problem.option="..." (or option="!...") attribute. A <ClConditionalGroup> or <ClBody> without one always shows.SV.infoBasedOn() deep-copies the base info. To change a default for a whole app, mutate the exported base info at app.js module scope before startup creates the SVs.dflt works, but top-level min and max are ignored; set them per unit.assert() (in util.js) logs and alerts but does not throw, so code after a failed assert keeps running. Don't rely on it to stop bad state.InputSv.userChange (which sets ctl.userChangedSv) for user actions, not valSync.These matter even for web-only changes because the same code ships natively.
config.xml. Saved user data lives in localStorage, which is scoped to the origin, so a change silently loses every user's data on update. V5 iOS is app://localhost, Android is https://localhost. V5 cannot run on file:// at all (ES modules don't load).html.nativeAndroid handles it).prefers-reduced-motion to no animation for a progress or activity indicator: a frozen spinner reads as a hang. Use a cross-fade instead.Verify every change before reporting it as done, and say exactly what you checked and what you could not.
Run these from the V5 project root:
| Command | Purpose |
|---|---|
make lint |
ESLint plus tmlLint.js for every app. Run before handing back any code change. |
make lintClean |
Clears the incremental lint cache. Use it when a lint error looks stale (for example "assigned but never used" for a variable that is used). |
make <App>.test |
Builds the app and runs its self-test suite in headless Chrome; prints <App>: PASS or FAIL and exits non-zero on failure. Also fails on any app assert(). |
make test |
The same for every app with a suite. |
make <App>.tableCoverage |
Lists Ptables that no calculation reads, in build/tableCoverage/<App>.json. An unread table is a dead-data bug or a test gap. |
make <App>.ptables model=<model> |
Lists a model's Ptables with their data paths, titles, inputs and outputs. |
make checkMdLinks |
Checks documentation links after editing doc/. |
build/web survives a rebuild; remove build/web/<App> to force one.selfTestRun.js on a failing run: failed startSession and setSavedInput requests and a PAGEERROR: Response. A passing run prints one line.make serve running. It rebuilds whenever anything under source/ changes, including generated files such as airport databases. Don't write scratch output under source/, and check for a running make serve before running builds that could race it.Tests are written with unitTest. A complete aircraft model has all three kinds:
setAc(), selectAcOption(), sv.x.valSync()) and check output SVs. Most expensive; covers the most.acData.tko.roll(conditions)).Ptable.interpolate() directly. Cheapest.Rules for tests:
acData. Never assert only options.installed.includes(...): that passes even when the option's data failed to merge.page/aircraft.js helpers setAc(), selectAcOption() and unselectAcOption(), not acType.setAcData(), which skips the merge.unitTest.resetScenario() so they are independent of group order.Until version4/ is removed, tools/upgradeToV5/*Diff.js (for example node tools/upgradeToV5/baronDiff.js --all) runs the same scenarios through the V4 and V5 engines and passes when every difference is on its KNOWN list of deliberate changes. When a harness and V5 disagree, check the harness's V4 expression against the V4 page code before blaming V5. A V4 installedOn option that became optionOn in V5 is drift that the self-tests can't see.
make serve, then http://localhost:8000), not in device simulators, unless the change is native-specific.?splashAfter=<ms>, ?splashHold=<ms> and ?splashOff=1 hooks in index.html instead.doc/ in the same change as the behavior it describes. Follow the Markdown style.make doc regenerates each file's table of contents and the MVCS index in place. Commit those generated changes with the edit that caused them, and run make checkMdLinks.Don't report these as findings:
make lint is clean and make <App>.test passes for each app you touched.Each prompt assumes the agent has already read this guide. Replace the angle-bracket parts.
Some POH tasks are also skills, in .claude/skills/ at the repository root:
digitize-poh builds a Digitizer project from a POH: /digitize-poh <POH pdf> <App> <reference model>.ppdig-to-model makes a model data file from a checked project: /ppdig-to-model <ppdig> <App> <reference model>.compare-pohs reports which graphs and tables two POHs of one type share and which differ: /compare-pohs <POH A pdf> <POH B pdf> <App> <reference model>.model-to-ppdig re-verifies a model's data: it builds a Digitizer project from the POH whose samples and rows are the model's own values, and reports where they miss the page: /model-to-ppdig <POH pdf> <App> <model>.Run them in Claude Code as shown. Tell any other agent to read and follow the skill's SKILL.md.
Read
version5/doc/dev/aiGuide.mdand the documents it links to for this task. Summarize the rules that apply before you start, then wait for my go-ahead.
In the V5
<App>app,<describe symptom and how to reproduce>. Find the root cause before changing anything and explain it. Then check whether the same code exists in the other aircraft apps and fix it everywhere it applies. Add a self-test that fails without the fix, runmake lintandmake test, and report which apps you changed. Don't commit.
First have the agent list what the POH has and map it to a reference model's data paths, then correct the list it makes and give it as <list>. See Starting an agent for that prompt. The digitize-poh skill runs both steps and stops for your corrections in between.
Build the Digitizer project
digitized/<App>/<document>.ppdigfrom the POH at<path>, followingdoc/dev/aiDigitizing.md. Calibrate and sample the graphs in this list, with the data paths it gives, and leave out the items it marks to leave out:<list>. Give each graph its POH title and source, and look at appdig renderof each one. Runppdig check, and report your confidence in each graph and anything you left out. Don't changeaircraftData.
Transcribe the tables in this list, with the data paths it gives, from the POH at
<path>into the Digitizer projectdigitized/<App>/<document>.ppdig, followingdoc/dev/aiDigitizing.md:<list>. Runppdig check, then compare the project with the<model>model usingmake <App>.ppdigCompare. List each difference with the POH page, the POH value and the code value. Don't change anything else.
Add the
<model>to the V5<App>app from the POH at<path>, followingdoc/dev/newAircraft.md. Take graph data only from a Digitizer project the developer has checked; if there isn't one, stop and digitize the graphs first. Keep every number as printed in the POH withunits:on thePtableaxes, cite the figure or page on each table, and usemodelDataMergeUnderonly for data that is identical in both POHs. Where the model computes differently from the base methods intypeData.js, override them in the model file rather than adding cases to the base methods. DeclaremaxAltitudeexplicitly within the climb and cruise table extents. Add system, method and table tests with expected values taken from the POH examples, then runmake <App>.testandmake <App>.tableCoverage.
When the graphs and tables are in a checked Digitizer project, the ppdig-to-model skill starts the model file from it with ppdig model, as described in Making a model file.
Add the
<option>option to the V5<App>app for serials<range>, from<POH supplement>. Decide whether it isoptionOnorinstalledOn, and how it relates to existing options (optClass,includes,incompatibleWith,enabledBy). Test it by looking up values that differ from the base model throughacData, not by checkingoptions.installed.
Check every table in
<model file>against the POH at<path>, digit by digit. List each discrepancy with the POH page, the POH value and the code value. Don't change anything yet.
Review the uncommitted changes against
doc/dev/aiGuide.md. Look for correctness bugs, missed sibling apps, data that doesn't match the POH, model-specific cases added to base methods, missing tests and style violations. Verify each finding before reporting it, and rank them by severity.
Update the documentation in
doc/for<change>, following the Markdown style indoc/dev/style.md. Runmake docandmake checkMdLinks.