現行の機能・画面仕様: 見た目、配置、コンポーネント追加の判断には、最上位規範である「Drogger Tablet UIデザイン指針」を併用し、矛盾時は同指針を優先する。
Issue #38「P6: トラクター耕耘アシストアプリ UI仕様策定とスパイラル開発」の仕様書。P5(Issue #21、closed)で確立した技術baseline(NTRIP/RTCM3/Bluetooth SPP/測位品質判定/診断ログ/記録再生/バックグラウンド常駐)の上に、実際に使えるUIを「仕様策定→最小実装→試用→仕様修正」の反復(スパイラル開発)で作る。
P6-13(Track #56)では、ライブ作業と保存記録閲覧の混同、および機能追加のたびに増えた地図上ボタンを解消するため、画面構造と操作レイアウトを再設計した。外部デザイン相談へ渡した前提・必須状態・評価基準は「P6 UI再設計 デザインブリーフ」、今後の機能追加で守る具体的な実装・レビュー基準は「Drogger Tablet UIデザイン指針」に集約する。
P6はIssue本文だけで判断・仕様を追うと、サイクルを重ねるほど散らばって追いにくくなる。このドキュメントを判断・仕様の集約先とし、各サイクル(Track)の実施記録・判断はIssueとTrack Ledgerへ、確定した仕様・方針はこのドキュメントへ書く。
画面全体を貫く構造として次の分割を採用する。具体的なパラメータがどのレイヤーに属するかは、各サイクルでそのパラメータを扱う時に都度判断する(事前に全パラメータを割り付けない)。
以前の「全筆を常時監視して圃場外→内の境界横断を検出する」方式、および直近2点だけから進行方向を
求めていた方式は廃止した。以下は現行の「筆決定時、現在位置とその時点までの共通軌跡から入口候補を
1回だけ生成する」方式の記述。横断前の予測入口は
過去の移動方向から推定せず、筆外の現在位置から筆境界への幾何学的な最短経路で決める。入口候補は
筆決定時に1回だけ生成し、fix受信ごとの自動更新は行わない。
横断前の予測入口: 現在位置が選択済みの筆の外側にある場合、現在位置から筆polygon境界上の
最近傍点までの直線を最短の予測経路として表示する。最近傍点を予測入口、現在位置から同点へ向かう
方位を予測進入方向とする。過去の測位点列や停止中の測位揺らぎを方向入力に使わない。複数の境界点が
同じ最短距離になる場合は、幾何演算が返すいずれか1点を採用してよい。一意性判定、浮動小数点誤差を
含む同距離判定、距離差の製品閾値は設けない。提示された入口が利用者の意図と異なる場合は、実際に筆へ
進入した後で改めて計画作成を開始する。
筆内での実横断入口復元: 筆決定時に現在位置が筆内なら、その時点までの参照用軌跡を遡り、最後に
観測した外→内横断の交点と実際の進行方向を入口候補にする。計画開始後のfixを監視して候補を更新しない。
入口横断判定の最大点間距離: 既定値5m。登録入口がないとき、選択済みの筆に対して境界横断までの
連続した測位点列から実横断入口を復元する際、隣接する2測位点を同じ移動系列として扱えるかどうかの
上限として使う。隣接点間の距離がこの値を超えた場合、それより古い点は接続しない。
値は0より大きい有限値とし、不正値を黙って補正しない。「既定値に戻す」では5mへ戻す。
保存値が破損している(数値へ変換できない・非有限・0以下)場合は、横断前予測と実横断復元を含む自動入口候補
producerを実行しない(利用者が保存し直すか初期化するまで、破損値をデフォルト値へ置き換えて動作を
継続することはしない)。登録入口・利用者による
明示的な方向指定は、この破損の影響を受けない。レイヤー0設定画面と、計画UI(入口確認シート)の両方に、
producerが停止していることを表示する。
これはアプリ全体の低頻度なproducer設定であり、Profileや一過性設定には置かない。既存のレイヤー0設定画面へ、
Android標準設定画面と同じ行+編集ダイアログの形式で追加し、作業地図へ操作を増やさない。
連続点列の各点は受信時点で測位品質を満たす必要がある。受信後の経過時間そのものでは候補を拒否しない
(点間距離だけを条件にする)。受信停止をまたいで離れた点を結ばない。
横断前予測・実横断復元は筆決定時のスナップショット処理であり、fix受信ごとに再実行しない。利用者が
候補を取り消した後、同じ計画フロー中に自動復活させない。再計算は新しい計画作成または筆変更で行う。
入口確認UIに反対側境界の参考用出口候補を表示しない。Pattern 1の計画出口は、確認済み入口と同じ境界点・
反対方向としてplannerが導出する。
登録入口、横断前予測、実横断復元、利用者方向指定の由来を内部状態または診断記録に保持する場合は、
実際の生成経路と一致させる。横断前予測を実横断として扱わない。一方、由来は入口確定後の計画生成や
利用者操作に使わないため、確定画面を含む利用者向けUIには表示しない。
この値は入口候補producerだけが消費し、EntranceSnapshot、TillagePlanRequest、経路planner、確定計画へ
planner入力として渡さない。
Pattern 1の計画作成・調整は、筆選択から仮計画の受け渡しまでを一続きの製品workflowとして扱う。
筆・入口選択と後段の計画生成を別々に進行させず、利用者から見た進行状態を単一の排他的な状態機械で
管理する。「入口未確定なのに計画済み」「変更前の筆に対する計画が残る」等の矛盾状態を型・遷移上
作れないようにする。
利用者の操作順は次のとおり。
centerTillageDirectionCandidates、defaultCenterTillageHeadingDeg)」規則は廃止した。初期仮方向は筆と入口が確定した時点で1回だけ決定し、初期仮方向、代表辺選択、自由回転はCenterDirectionSelection内で由来を区別し、初期仮方向を
「利用者が辺を選んだ」「利用者が自由回転した」と扱わない。plan requestは筆、入口、Profile、中央方向の
4要素を維持し、共通plannerは由来によらず渡されたheadingDegだけを使用する。
登録済み入口、保存済み設定、位置履歴、軌跡、走行記録は、この一時的な編集状態とは独立して保持する。
計画開始可否は、筆、確認済み入口、有効な作業機設定、初期仮方向がそろっているかだけで判断する。
圃場内外や停車中かどうかを条件にせず、GNSS速度・移動履歴・現在位置から停車を推定する処理を追加しない。
予測入口を確認できれば圃場へ入る前でも作成でき、圃場進入後に復元した入口でも作成できる。
active Profileに必要な値が不足または不正な場合は計算を開始せず、問題のある項目を利用者の言葉で示す。
主操作は「作業機設定を変更する」、副操作は「計画作成を終了する」とする。標準値による黙示補完、丸め、
別設定への自動切替は行わない。設定画面から同じアプリ処理へ戻った場合は筆・入口・確認済み外周方向を
保持し、新しい設定を再検証する。
地図と仮計画を主役にし、地図を大きく覆うbottom sheetは使わない。画面下部のコンパクトな段階別
計画編集パネルへ「辺方向へ回転」「自由回転」「この計画を使う」「条件を変更する」「計画を終了する」等、
その段階で必要な操作を置く。強調する主操作は常に1つ以下とし、固定画面骨格を維持して地図上の常設ボタンを
増やさない。
段階別計画編集パネルは通常作業ドックと同じ高さ定数を共有し、通常時・計画時とも固定104dpとする
(2026-08-29確定)。heightIn等による可変高さを使わない。状態遷移で地図領域の高さも主操作の位置も
動かさない。主操作は右端の固定枠へ置き操作数で位置を動かさない。状態文言は1行を基本とし、長い場合は
省略する。操作は1行に収める。主操作は64dp以上、補助操作は48dp以上を維持する。低頻度操作は三点メニューへ
置く。長い失敗理由は「詳細を見る」からダイアログへ表示し、詳細表示で下部領域を伸ばさない。計算中・入口
確認中・失敗時なども同じ104dpを維持する。通常WorkDockを含め下部領域を恒常的に高くしない
(docs/standards/ui-design-guidelines.mdは変更しない)。
完成仮計画では概ね次の配置とする。状態文言「完成仮計画・初期方向 ○°」、操作行
[⋮] [辺方向] [自由回転] [この計画を使う]。
「辺方向へ回転」はcenterTillageDirectionCandidatesが返す代表辺候補を既存順に巡回する。初期仮方向として
選ばれた候補の次から既存候補順で巡回し(末尾の次は先頭へ戻り、重複候補もそのまま通過)、候補の重複を
除去せず、方位角順・辺長順等の新しい並べ替え・選択規則を加えない。「自由回転」では地図上の
方向線または専用ハンドルだけをドラッグ対象とし、通常の地図ドラッグと区別する。指を離した角度で
1回だけ再計算する。
完成仮計画には、筆境界、確認済み入口、経路全体、TILL区間、DEADHEAD区間、進行方向、現在位置、
既存軌跡を表示する。TILLとDEADHEADは色・線種等で区別する。計算途中、非採用候補、内部Stage、
WorkArea候補、診断図形、失敗した部分経路を使用可能な計画として表示しない。初回は筆と経路全体を
見せるが、利用者が地図を移動・拡大した後は、再計算のたびに無条件で表示位置を戻さない。
完成仮計画の地図には、説明のない色付きmarkerだけを置かない。中央耕開始、中央耕終了、仕上げ外周
3列目開始を形の異なるmarkerで示し、同じ地図上の不透明な凡例へ農作業上の名称を併記する。工程別の
TILL線も中央耕・外周3列目・外周1列目・外周2列目で色を分け、凡例へ
「中央耕 → 外周3列目 → 外周1列目 → 外周2列目 → 入口から退出」の実行順を明記する。
DEADHEADは工程色と混同しない灰色破線のまま維持する。確認済み入口markerは最終退出位置も兼ねることを
凡例で示す。これにより、利用者は色の意味を事前に暗記せず、Tablet画面だけから各地点・工程・実行順を
読み取れるようにする。方向変更後の再計算中など完成経路を表示しない状態では、この凡例も表示しない。
利用者向けの最上位分類は次の2種類だけとする。
両分類に「詳細」を設け、具体的な理由、変更可能な項目、戻る段階、対象外/未実装の区別、問い合わせ用の
短い原因コードを表示する。内部例外文、スタックトレース、生の診断detail、内部Stage名だけの説明は表示しない。
共通計算処理は自由文解析を必要としない構造化原因を返し、Tablet側が原因コード・種別・起因入力から
2分類、安全な詳細文言、戻り先へ変換する。
判定基準は画面が見えるかではなく、同じアプリ処理が生きているかとする。設定画面往復、別アプリへの
一時移動、画面消灯、Androidによる画面再作成では、未承認計画と編集状態をプロセス内共有状態へ保持して
再表示する。画面非表示や一定時間の経過だけで破棄しない。
アプリ処理終了、強制終了、新規起動、端末再起動では未承認計画を復元しない。終了コールバックに依存せず、
未承認計画、仮方向、計算中状態、結果待ち、編集識別子、再計算番号を永続化しない。登録済み入口、保存済み
設定、位置履歴、軌跡、走行記録は削除しない。正式承認済み計画の永続化・復元は後続工程が所有する。
測位系画面(地図)に表示する情報は、性質によって表示の重みを変える(P6-2の実機試用フィードバックより)。
常時表示とパネル/診断画面の混在は「画面が邪魔」「監視・操作・診断が混在する意味があるか」という実機試用フィードバックを受けて導入した区分(P6-2)。
測位品質バッジ(dev.drogger.tablet.ui.PositionQualityBadge、P6-3で実装・P6-4で配置確定)。P5製品要求「RTK FIX喪失、古い測位、Bluetooth切断等を明確に表示し、不正確な誘導を継続しない」に対応する、安全に直結する情報。P6-13(Track #56)以降は上部ステータス(WorkStatusBar)の左端に、プロファイル・記録状態と横並びで常時表示する(中央だと実運用時に地図の見たい場所と被るという実機試用フィードバックを受け、P6-4で中央から移動し、Track #56で上部ステータスへ統合した)。
WorkMenu)の「バージョン情報」から、製品バージョン・build識別子(yyyyMMdd-HHmmss + versionCode)・git短縮SHA・端末モデルをダイアログ表示する(dev.drogger.tablet.ui.VersionInfo / VersionInfoDialog)。採番・生成規則はアプリのバージョン採番・表示・実機試験記録の規約。WorkStatusBar(64dpに解決)とWorkMenu(内側fillMaxHeight()で72dpに解決)の高さが不一致で三点メニューのSurfaceが上部バーから下へはみ出していたのを修正した。両者を共有定数WorkChromeBarHeight(72dp固定)で必ず一致させる。| 状態 | バッジ | 条件 |
|---|---|---|
| 固定解 | 🟢 固定解 | PositionQuality.FIX |
| 浮動解 | 🟡 浮動解 | PositionQuality.FLOAT |
| 差分測位 | 🟠 差分測位 | PositionQuality.DGNSS |
| 補正受信中... | 🔵 補正受信中... | PositionQuality.SINGLE かつ NTRIP接続済み(補正は届いているが未収束) |
| 単独測位 | 🔴 単独測位 | PositionQuality.SINGLE かつ NTRIP未接続(補正の当てがない) |
| 測位無効 | ⚫ 測位無効 | PositionQuality.INVALID(SINGLEより深刻、座標自体が信用できないため単独と別色) |
| 情報が古い | ⚪ 情報が古い | PositionQuality.STALE(質を問わず一律。positionQualityOfが以前の品質を保持しない設計に合わせた) |
| 不明 | ❔ 不明 | PositionQuality.UNKNOWN |
判定ロジックはdev.drogger.tablet.model.PositionQuality(positionQualityOf()/FixState.positionQuality())として実装済みだったが、2026-08-02時点でどの画面にも配線されていないことが判明し、今回初めてUIへつないだ。
精度指標(GST実測誤差優先、無ければHDOPへフォールバック。有効な測位を持つ状態全て): 固定解/浮動解/差分測位/単独測位(補正受信中含む)で、GSTのstdLat/stdLongから求めた実測水平誤差を"固定解 1.4cm"のように付記する(FixState.gstHorizontalErrorM、P6-8で導入。「誤差」という接頭語は冗長との利用者フィードバックを受け付けない、単位のみ表示する)。GSTが未受信・鮮度切れの間はGGAのHDOPへフォールバックする(FixState.hdop、P6-6で導入、"固定解 HDOP0.8")。
UBX-NAV-PVTのhAcc(水平精度推定値、mm)をcm換算して付記する方式だったが、P6-5(Track #43)の実機再検証で撤回した: 「CFG-VALSET送信直後の1回だけ届き、その後継続的にストリーミングされない」という現象は、実際にはNAV-PVTがBluetooth SPP経由では原則として一切届かないという、P4(Issue #30/#28、docs/decisions/p4-vrsc-dependency-deferred.md)で既に確定していた既知の制限の再発現だった。DG-PRO1RWSのBluetoothブリッジは、u-bloxチップのCFG-MSGOUT rate設定とは独立に、特定message ID(NMEA GGA/RMC、RXM-SFRBX、NAV-TIMEGPS)のみを通す固定フィルタを持つ(公式APK静的解析(Issue #28)と複数の実機実験(Issue #30)で確定済み)。NAV-PVTはこのフィルタを通らないため、CFG-VALGETでUART1のMSGOUT rateが1と読み取れても、単発pollを送っても、実際には一切出力されない。原因はアンテナ設置場所等の受信環境要因ではなく、DG-PRO1RWS本体のBluetoothブリッジ実装に起因する固定的な制限であり、設定変更や再接続では解消しない。P6-4で一時的に行った「実際には届く(1件確認)」という訂正も、この再検証(サービス完全再起動を伴う)で再現しなかったため撤回した。NmeaParser.decodeGgaは元々GGAのHDOPフィールド(フィールド8)を抽出していたが、FixRepository.ingestGgaがその値をFixStateへ渡さずに捨てていた(利用者からの「精度情報は取れているはず」という指摘で発覚)。GGAはP4で確認済みの「ブリッジを通過するmessage ID」に含まれ毎秒継続的に届くため、FixState.hdopを追加してこの値を保持し、PositionQualityBadgeの精度表示をNAV-PVT hAcc依存からHDOP依存へ切り替えた。表示はcm換算せずHDOP値をそのまま示す(HDOPは距離ではなく幾何学的な精度低下率であるため)。nmea.bin、root不要でadb pull可能)を直接解析した。結果、NMEAはGGA/RMCに限らずVTG/GLL/GSA/GSV(GP/GL/GA/GB全系統)まで広く継続的に届いており、P4の「NMEA全般が絞られている」という理解は誤りで、実際は「特定のUBXバイナリ(NAV-PVT)のみが遮断される」が正しいと訂正した(docs/decisions/p4-vrsc-dependency-deferred.md参照)。GSTは既定値0(無効)で当時は一度も出現しなかった。NmeaParser.decodeGstでstdLat/stdLongを取得、sqrt(stdLat^2+stdLong^2)で水平誤差を算出してFixState.gstHorizontalErrorMへ保持、バッジに「固定解 1.4cm」として実機表示できることを確認した。GSTはGGAと独立した受信タイミングを持つため専用の鮮度判定(gstLastReceivedAt、5秒閾値)を設け、鮮度切れの間はHDOPへフォールバックする(NAV-PVT hAccで踏んだ「鮮度チェックなしに古い値を出し続ける」バグを再発させない設計)。GST出力はRAM限定・非永続のため、受信機の電源断や再接続では無効化前提に戻る。測位系画面の4主任務(地図表示・軌跡表示・基準線指定・ナビゲート)のうち、地図表示に続いて軌跡表示を実装した(P6-2〜P6-8は測位品質バッジ・設定/診断画面分離といった周辺UIが中心で、この4主任務自体には未着手だった)。
FixRepository.ingestGga)。RMCもほぼ同時刻に同じ位置を運ぶため、両方から記録すると重複点が増えるだけで意味が薄い。TrajectoryMemoryが直近15分は間引かずフル解像度で保持し、それより古い点はアーカイブへ移す。アーカイブが2万点を超えるたびにPolylineSimplifier(Ramer-Douglas-Peucker法)で単純化する(許容誤差30cmから開始し、上限を超え続ける場合は倍々に緩めてやり直す)。数時間の連続稼働でもメモリを圧迫しない設計(利用者フィードバック「メモリには上限を設けて、一定時間経過後に古いものから最適化」)。
FileTrajectoryStoreが全点(間引きなし)をtrack/trajectory.jsonl(外部ストレージ、1行1JSON、単一ファイルへ追記)へ保存していたが、P6-10で.dtrk形式(1セグメント=1ファイル、[docs/specifications/p6-trajectory-file-format.md])へ置き換えた。アプリ・受信機の再起動をまたいで記録状態は継続するが、Issue #96以降は破損fileへの追記を避けるため、再起動後は同じfileへresumeせず新しいsegment/fileを開始する。WorkControls、画面左下)でメモリ・永続化ファイルの両方をリセットする。実機でタップ→ファイルが空になることを確認した。FixRepository.isRecording(既定値はサービス層で決定)を追加した。記録中/停止中の状態はSharedPrefsTrajectoryRecordingStoreで永続化し、既定は停止中(初回起動時は利用者が明示的に開始するまで何も記録しない)。
TrajectoryMemory(表示用)・RecentFixHistory(入口検出用、Issue #107)・外部記録の3系統で個別に管理していた構造を、共通のTrajectoryRepository(track-core)へ統合した。参照用軌跡(TrajectoryReferenceHistory、旧TrajectoryMemoryを改称・拡張)は記録ON/OFFに関わらず常に更新される単一のsource of truthであり、地図表示・入口検出候補producerの両方がここから読む。記録ON/OFF(isRecording)は外部記録(.dtrkファイルへのTrajectoryRecorder出力)だけを制御する。すなわち以前とは異なり、記録停止中も地図上の軌跡表示は伸び続ける(外部ファイルへの永続化だけが止まる)。参照用軌跡は直近の未圧縮領域(Hot、fixId・測位品質付き)と、古い区間を圧縮した領域(Cold、PolylineSimplifier)から成る。TrajectoryRepositoryは、最新点から軌跡を過去方向へ遡った累積走行距離の指定範囲を返す。距離はCold+Hotとして現在保持している参照用折れ線の隣接点間距離から算出し、segment境界をまたいで線や距離を接続しない。ColdはRDP圧縮後geometryに基づく参照用の近似距離であり、原測位点の厳密な走行距離ではない。実横断判定にはfixId・品質を持つ未圧縮Hotだけを使用する。TrackPoint.segmentIdを導入した。「記録を再開」(直前と同じsegmentとして続きから記録、線がつながる)と「新しい記録を開始」(segmentIdを増分、線がつながらない)を区別できるようにし、地図描画(MapScreenのLineLayer、segmentIdごとに別のLineStringとして描画)・メモリ上のRDP単純化(TrajectoryMemory.simplifyPerSegment、segment境界をまたいで単純化しない)の両方をsegment境界を尊重するよう変更した。一度も記録したことがない状態では「再開」の対象が無いため「軌跡記録を開始」1つだけを表示し、既存の軌跡がある状態で停止中は「記録を再開」/「新しい記録を開始」の2つを出し分ける。FileTrajectoryStoreの永続化形式にもsegmentIdを追加し、旧形式(フォローアップ1時点、segmentId無し)の行は単一セグメント(0)として読み込む後方互換を確保した。プロセス再起動時は永続化済みの軌跡からsegmentIdの最大値を引き継ぐため(restoreTrajectory)、採番が衝突しない。実装中に、segmentが多数の短い区切り(各2点等)で構成される場合、RDPは2点未満へ縮められないためTrajectoryMemoryの間引きループが無限ループしうる設計上の穴を発見し、単純化で点数が減らなくなったら上限超過のまま打ち切るガードを追加した(件数上限の厳守よりsegment境界情報の保持を優先)。実機で、既存軌跡ありの状態での「再開」(同一segmentIdで継続)・「新しい記録を開始」(segmentId増分)・am force-stop後の再起動をまたいだsegmentId継承を確認した。MapControlsをMapDisplayControls(地図/航空写真切替・筆ポリゴン表示・現在地へ戻る、画面右上に維持)とWorkControls(軌跡記録の開始/再開/停止・軌跡をクリア、画面左下へ新設)に分割した。作業制御系ボタンの追加はWorkControls内で完結させる想定。LineLayerで現在地マーカーの下に描画する(MapScreen.ktのaddTrajectoryLayer/updateTrajectoryLine)。2026-09-04の利用者判断により、この線はGPSアンテナの記録位置を示し、アンテナ―作業機オフセットを適用しない。実機は静止状態での確認のため、cm〜mm級のジッターが地図上で線として視認できるほどの移動量にならず、線描画そのものの目視確認はできていない(次回、実際に移動しながらの試用で確認する必要がある)。TrajectoryMemoryの上限設計(件数ベースの間引き)はレート想定に依存しないため実害はないが、GST/HDOP等の鮮度閾値(5秒)や将来の間引き閾値を検討する際は、この実測レートを前提にすること。開始/停止ボタン追加前の検証セッションでは、実機を稼働させたままにしていたためtrajectory.jsonlが約9.7万行(実測レート約9〜10Hzのまま常時記録されていた実例)まで増えており、常時記録の実運用上の問題(圃場までの移動等が混ざる、無制限に増え続ける)を期せずして裏付ける結果になった。P6-9で実装した走行軌跡は、GGA由来のアンテナ位置をそのまま軌跡として記録・表示していた。トラクターでの実運用では、実際に耕耘しているのはアンテナの真下ではなくロータリー(作業機)中心であり、アンテナ―ロータリー間には前後・左右のオフセットがある(利用者指摘「トラクターの軌跡表示に全然手をつけていない。アンテナ位置から、軌跡として記録するロータリーの位置がオフセットされているので、それらの設定が必要」)。この要件自体はP5製品要求に「アンテナから作業機中心までの前後・左右オフセット」として既に記載されていたが、実装は一切着手されていなかった。#47(P6-9)は「移動しながらの実機試用による線描画目視確認」という別の残課題でopenのまま維持し、本節の内容はTrack #49で新規に切り出した(Track Ledgerのreferencesrelationで#47と関連付け)。
このtrackは仕様の大枠を固めるところまでを扱い、実装は次サイクルで行う。移動を伴う実機試用は手間がかかるため、利用者の意向で「ある程度実装が進んでから一度にまとめて実機確認したい」という方針とし、実装前にできる限り仕様レベルで検討を詰めた。
TrackPointは従来通りアンテナ生位置のまま記録する。地図上の中心線もこのアンテナ位置を示し、アンテナ―作業機オフセットを適用しない。耕耘あと帯だけは、記録時の前後・左右オフセットでアンテナ位置から作業機中心へ移したうえで、作業機幅を左右へ広げて描く。したがってオフセットが0でなければ、中心線と帯の中央がずれるのが正しい。COG(方位)・速度は、帯のオフセット方向と旋回区間の判定に使う。旋回区間除外とsegment/file境界は中心線・帯で共通とし、両者の違いは座標へオフセットを適用するかどうかだけにする。P6-11/P6-12で採用した「中心線自体を作業機位置へずらす」表示は、この利用者判断で廃止する。decodeRmcはCOG・速度を一切デコードしていない(P6-11で新規実装が必要)。Profile)に追加し、プロファイル切替で機体構成ごと切り替えられるようにする。COGを方位として信頼できる最低速度・旋回判定用のヨーレート/速度閾値は、機体構成に依存しないチューニング用の暫定値としてレイヤー0(アプリケーション設定)に置き、利用者が調整できるようにする。これはP5製品要求が「アンテナから作業機中心までの前後・左右オフセット」を通常設定(実測値)、「courseを方位として利用できる最低速度」を詳細設定(開発側が安全側の初期値を置き実測で確定する値)に分類していたことと一致する。.dtrkファイル形式のv2拡張: 詳細は軌跡記録バイナリ形式仕様(.dtrk)の「v2形式」節を参照。ヘッダへオフセット値(記録開始時点のプロファイル設定、監査目的)、レコードへCOG・速度を追加する。速度の符号化は当初対数圧縮による1byte化を検討したが、「今回必要なのは低速側(クリープ・旋回判定)の精度であり、高速走行時の結果で何かする仕様は考えにくい」という利用者整理を受け、対数式ではなく線形のまま単位を0.05m/s刻みにし上限12.7m/sで切り捨てる方式へ変更した(非線形の符号化・復号ロジックの実装・検証コストを避けつつ低速域の精度を確保する)。Profileへのオフセット追加(レイヤー1 UI含む)、レイヤー0への閾値設定追加、.dtrk v2読み書き、描画時オフセット変換・旋回区間除外まで実装済み。P6-12では中心線にもオフセットを適用していたが、2026-09-04の利用者判断により、中心線はアンテナ位置、オフセット変換は耕耘あと帯だけの責務へ変更し、#124の後続コミット79fd123でライブ作業画面・保存記録再生・PC検証ツールをこの規則へ統一した。Redmi Pad SE 8.7で位置関係と視認性を確認するまで#124は未完了とする。2026-09-06にRedmi Pad SE 8.7で取得した実走記録をPC軌跡ビューアで確認したところ、直進区間に数m規模の蛇行が見えた。生NMEA照合の結果、記録開始後0〜7分でRTK FIX 41.8%・DGNSS 53.4%・RTK FLOAT 4.8%であり、6:00〜6:30は約97%がDGNSS(GST水平誤差推定の95%点が約2.69m)だった。蛇行はビューアが作った座標ではなく、低品質時のGGA位置が軌跡へそのまま記録されたもの。ところが通常V4(46/13)は位置・COG・速度しか持たず、保存後のファイル単体ではFIXとDGNSSを区別できない。
このため、通常の軌跡記録でも各測位点の品質を後から判定できる形で保存し、Tabletのライブ表示・保存記録再生・PC軌跡ビューアで品質の色分けと表示対象からの除外を行えるようにする。詳細な検討経緯・却下案(V5新設)はdocs/proposals/issue131-trajectory-position-quality-improvement.mdとIssue #131を参照。
確定した方針(Phase 0承認、2026-09-07):
TRAJECTORY_POSITION_QUALITY(debugFormatId=2、version 1、headerLength=50/recordLength=27)で記録する。既存の14byte DtrkPositionSampleを各通常測位frameへ付ける。V5は新設しない。詳細は経路記録データファイルフォーマット「3.9 V4 ID 2」を参照。track-coreへ一元化し、TabletとPCで別実装にしない。UNKNOWN(品質記録なし)とし、FIX等と推測しない。旧ファイルは既定で中心線・帯とも表示する(更新後に帯が突然消えるのを避ける)。除外区間の前後を線・帯で橋渡ししない。品質区分(GGA生品質を一次判定にする):
| GGA値 | 区分 | 表示文言 | 色 |
|---|---|---|---|
| 4 | FIX |
固定解 | 緑 |
| 5 | FLOAT |
浮動解 | 黄 |
| 2 | DGNSS |
差分測位 | 橙 |
| 1 | SINGLE |
単独測位 | 赤 |
| その他の正値 | OTHER |
その他の測位 | 青/紫 |
| sampleなし/0 | UNKNOWN |
品質記録なし | 灰 |
TrackPointを生成しないため、記録済み点をINVALIDに分類しない。無効期間は点の欠落として現れ、前後を接続しない。STALEは受信後の現在時刻に依存するライブ状態であり、保存点の恒久分類に入れない(上部の現在品質バッジは現行責務のまま)。docs/standards/ui-design-guidelines.mdの視認性規則)。色定数はDroggerTheme.ktの意味付きtokenへ集約し、既存PositionQualityBadgeも同じpaletteを参照する。hex値・線幅・帯opacityは航空写真背景・計画線と同時表示して実機確認する。除外UIと初期状態:
Set<TrajectoryQualityClass>で表し、複数Booleanを画面状態の正本にしない。ViewModelのTrajectoryQualityDisplaySettingsとして保持し、保存記録を開いて戻っても暗黙に初期化しない。ライブ作業と保存記録再生で同じ状態を使う。MapDisplayOptionsSheetへ「軌跡の測位品質」を追加する。FIX/FLOAT/DGNSS/SINGLE/その他/品質記録なしを色見本・文言・Switchで列挙し、「中心線」「耕耘幅帯」の大分類と品質filterを区別して表示する。FIX + UNKNOWNを既定表示とし、FLOAT/DGNSS/SINGLEは既定で除外する。耕耘幅帯の既定値の最終判断は実機確認後に行う。FLOATの表示トグルは初弾から用意する。固定解 42% / その他 58%のような短い品質要約を文言で表示する。旧ファイルは品質記録なしと表示し、0% FIXとは表現しない。サムネイルは全区分を色分けして表示するが、行内にfilter操作は置かない。再生画面はライブと同じ表示設定sheetを再利用する。シークしても品質runの境界とfilterを維持する。run分割と#124の責務:
track-coreに純粋関数TrajectoryQualityRuns.partition(transformedPoints, filter)を置く(実装済み)。run境界は「renderGroupId(変換後segmentId)が変わった」「表示品質区分が変わった」「非表示区分の点を1点以上挟んだ」で作る。非表示点をリストから取り除いてから線を作ると前後を直線で橋渡ししてしまうため禁止する。同一renderGroup内で非表示点を挟まない品質変化のときだけ、直前runの末尾点を次runの先頭へ複製(seam)して色違いの線・帯を視覚的につなぐ。counts()は区分ごとの点数のみ返す(緯度経度を持たせない)。apply(0,0)、中心線と帯で旋回区間・記録境界を共通)は本件で変えない。処理順を「①TrajectoryRenderGroupingで記録segmentごとに分ける ②中心線centerline()/帯apply(offsets)を従来どおり適用(旋回除外・renderGroupId採番) ③変換後の点列を品質区分・filter非表示gapで品質runへ分ける ④最終描画キーを(qualityRunId, renderGroupId)相当とし品質run IDを永続segmentIdやrenderGroupIdへ上書きしない」で固定する。これにより品質境界で旋回判定のCOG履歴をリセットしない。runtimeモデル:
dev.drogger.tablet.model.TrajectoryPointQualityを追加し、TrackPoint末尾へquality: TrajectoryPointQuality? = nullを持たせる(位置列と品質列のparallel listが描画経路でずれるのを防ぐ)。初弾のruntimeモデルは実際に使う最小限(生GGA quality・gstHorizontalErrorM・gstAgeMs)に絞り、numSatellites/hdop/altitudeM/rmcAgeMsはwire DTO(DtrkPositionSample)側だけに保持する(DTRKには全項目保存するため情報は失われない。Tabletのメモリ使用量と責務分離を優先)。TrajectoryReferenceHistoryのarchiveはTrackPoint自体を保持するため、RDP圧縮で採用された点の品質も追加配線なしで残る。restore()が復元点へ設定するHotEntry.fixQuality=0は入口検出の品質gate用の生GGA値であり、描画用TrackPoint.qualityとは別責務。ファイルから復元した表示品質をこの値へ流用せず、既存の入口検出が復元点を高品質と誤認しない挙動を維持する。対象外: 低品質になったGNSS受信機・NTRIP・マルチパスそのものの原因解消、生NMEAログの恒久保存方針変更、planner入力・候補選択・Stage依存・field-geometry契約の変更、自動操舵・車両制御。
FixState.lastError)FixState.numSatellites、P6-3でパネルへ追加)FixState.lastReceivedAt、「今もデータが来ているか」の生存確認。P6-3でパネルへ追加。受信センテンス数等の生カウントは診断画面のまま)パネル内のレイアウトは、現状はColumnの単純な縦積みのまま(P6-2/P6-3時点でレイアウト自体の整理は未着手)。ダッシュボード的な整理(グルーピング・アイコン化等)は次サイクルの課題として残る。
P6-10(軌跡データの保存・一覧・再生・端末外への吸い出し)の要件「記録するファイル名はレイヤー1で設定できるのが良い」を受け、レイヤー1(プロファイル設定)を最小実装した。「将来ここが拡張される前提で、かつレイヤー0と同じくAndroidのUIに準拠した方法でレイアウトする」という利用者方針に基づき、レイヤー0(Layer0SettingsScreen)と同じ作法(Scaffold+TopAppBarの「← 戻る」、SectionHeader、ListItemをタップして編集ダイアログを開く、HorizontalDivider区切り)を踏襲した。
WorkControls、画面左下)にプロファイル名そのものをボタンとして設置(当初「プロファイル: ○○」という接頭辞付きだったが、利用者フィードバックを受け接頭辞を削除)。地図表示系ボタン(画面右上)とは別領域(P6-9フォローアップの分離方針を踏襲)。Layer1SettingsScreen)でプロファイルの切替・作成を、詳細画面(ProfileEditScreen)で個別プロファイルの改名・削除を扱う。将来プロファイルへパラメータが増えても、一覧画面ではなく詳細画面側に追加していけばよいようにするための分離。
{値}_yyyyMMdd-HHmmss.dtrk)をプレビュー表示しつつ独立して編集できる。下部に赤字で「このプロファイルを削除」(「すべて初期値に戻す」と同じ見せ方)。Profile(id, name, trajectoryFileNamePrefix)。nameは一覧・切替ボタンでの表示名、trajectoryFileNamePrefixは軌跡記録ファイル名の自由テキスト部分([docs/specifications/p6-trajectory-file-format.md]参照)で、互いに独立して編集できる。
nameのみで表示名とファイル名自由テキストを兼ねる設計だったが、「プロファイル名しか入れられないですけど、ファイル名ルールはどうやって入れるんですか」という指摘を受け、trajectoryFileNamePrefixを独立したフィールドへ分離した。プロファイル作成時はtrajectoryFileNamePrefixをnameと同じ値で初期化する(利用者が明示的に変えるまでは一致させておく方が分かりやすいため)。旧形式(このフィールド追加前に作成されたプロファイル)は読み込み時にnameを既定値として補う後方互換を持つ。SharedPrefsProfileStoreがSharedPreferencesにJSON配列で永続化する。ProfileStore.ensureActiveProfile())。am force-stop後の再起動をまたいだ選択状態の保持を確認した。Issue #60(P6ナビゲーション全体実装)のStage 0として、P6-15(Issue #58、Track Ledgerで直線・曲線ナビゲーションアシストの仕様を確定)のうち、候補経路生成の土台になる「作業ピッチ = 作業機幅 − 重ね幅」をProfileへ追加した。JTS等の幾何ライブラリ、筆polygon活用、A/B操作、経路生成、ナビゲーション状態機械はこのStageの対象外(Stage 1以降)。
Profile.overlapWidthCm: Int?: 重ね幅(cm)。既存のoffsetForwardCm/offsetLateralCm/implementWidthCmは未実測時に0を既定値として補うが、重ね幅は他フィールドと異なり0を既定値にしない。0は「実測して重ねないと決めた」という有効な値であり、「まだ実測していない」という未設定状態と区別する必要があるため、nullを未設定として扱う。JSON永続化でも、未設定の間はoverlapWidthCmキー自体を書かない(0と未設定を書き込み時点でも区別する)。isValidOverlapWidth(implementWidthCm, overlapWidthCm)は、作業機幅が実測済み(> 0)かつ重ね幅が0以上・作業機幅未満の場合のみtrueを返す。computeWorkPitchCm(implementWidthCm, overlapWidthCm)はこれを満たす場合のみ作業機幅 − 重ね幅を返し、それ以外はnull(ナビゲーション開始不可の意味)を返す。作業機幅を後から変更して既存の重ね幅が無効化されるケース(例: 重ね幅100cmを保存した後に作業機幅を80cmへ変更)も、この関数を都度呼び出すことで正しく無効判定される(入力時点で固定した真偽値をどこかに保存する設計にしていない)。ProfileEditScreen(レイヤー1詳細画面)の「作業機幅(cm)」の下に「重ね幅(cm)」行を追加した。表示文言は未設定/作業機幅未設定/無効値/正常値の4状態を出し分ける(overlapWidthSupportingText)。編集ダイアログ(OverlapWidthEditDialog)は、作業機幅が未設定の間は上限を検証できないためその旨を表示しつつ保存自体は許可する(作業機幅の入力を先に強制しない)。作業機幅が設定済みなら、保存時に0以上・作業機幅未満を検証する。.dtrkへ保存する方針へ改訂した。拡張可能なV4 container契約は軌跡記録バイナリ形式仕様(.dtrk)を参照。Issue #60 Stage 1〜2 で実装した直線/曲線 A-B 取得・候補経路生成(CandidateRouteGenerator /
ReferenceRoute)・A-B 用 WorkDock 操作・GuidancePlanState 状態機械・GuidanceAvailability・
pc-tool guidance-plan は、Issue #107 Pattern 1 実装前に撤去した(Issue #112 / Track #113)。当時の
仕様・判断の履歴はP6ナビゲーション基準経路(A-B)機能 実装履歴
を参照する。現行の耕うん計画は「基本耕耘計画・圃場全体ナビゲーション」節と
隣接耕基本経路パターン1 を正本とする。
Stage 2完了後、Stage 3以降のナビゲーション機能をPC上のAndroidエミュレータで検証できるようにする
独立Trackとして実装した。詳細な使い方はdebug専用NMEA走行シミュレータの使い方
を参照。
NmeaLinkFactory(dev.drogger.tablet.bluetooth)より下だけを差し替える。BluetoothNmeaControllerの接続・切断・再接続バックオフ・DisconnectCause分類、NMEA parser、FixRepository、ViewModelは無変更のまま実経路で動く。既存test用FakeNmeaLinkは製品mainへSimulatedNmeaLink/SimulationCapableNmeaLinkFactoryを新設した。src/debug/src/releaseがそれぞれ同名関数createNmeaLinkFactoryを提供し、src/main(RtkForegroundService.kt)はどちらの実装も直接参照しない。releaseは常にAndroidNmeaLinkFactoryのみを返す。release APKのdexへシミュレータ関連クラスが含まれないことをunzip+stringsで確認済み。SimulationControlActivity)からIOExceptionを発生させ、既存のCOMMUNICATION_LOSS経路(即時リトライの再接続)へそのまま入れる。Track #64時点では15秒BluetoothNmeaControllerTestの2件writeToGnss reports Failed.../snapshot succeeds normally...)が、writeToGnss等のwithContext(ioDispatcher)呼び出し中にrunTestのスケジューラが仮想時間を先まで自動進行させ、watchdogTimeoutMsを検証目的に無関係な大きな値へ変更して修正した(5回連続成功で安定確認)。SyntheticNmeaGeneratorTest(疑似GGA/RMCが実parser・FixRepositoryを通ることを検証)、SimulationPathModelTest、SimulatedNmeaLinkTest、SimulationCapableNmeaLinkFactoryTest、SimulationIntegrationTest(BluetoothNmeaController経由の接続→NMEA受信→品質切替→stale→testDebugUnitTest・assembleDebug・assembleReleaseとも成功。Track #64の単一APK版シミュレータ操作画面(SimulationControlActivity)を撤去し、Drogger Tabletとは
別Gradleモジュール・別APK・別applicationIdの開発者専用シミュレータアプリ(dev.drogger.simulator)
へ役割を移した。常時同期型ではなく、シミュレータがDrogger Tabletから取得する走行情報を「現在登録
されている経路の最終地点だけ」に絞った明示的一括送信方式を採用する(当初は現在位置・進捗・実行状態・
revision付きsnapshotを常時同期する設計だったが、利用者レビューで「編集者は単一シミュレータのみ、
共同編集は不要」と整理され方針転換した)。詳細な使い方は
debug専用NMEA走行シミュレータの使い方を参照。
:simulator-protocol(Android非依存のplain Kotlin/JVM、緯度経度・距離・方位・:simulator-ipc:simulator-protocolのsealed型⇄Parcelable envelopeの相互変換)、:simulator-app(シミュレータ本体、:appへは依存しない)。:app側はsrc/debugにだけSimulatorIpcService(AIDL Stubの薄い受け口)とSimulationRuntime(唯一のRegisteredRouteEngineインスタンスと排他ロックを保持)を追加し、debugImplementationで:simulator-ipcへ依存する(release変種には一切リンクしない)。EndPointQueryResult: NoRoute/EndPoint、座標のみでInitialRouteSubmission)、継続経路送信ContinuationRouteSubmission)、受理結果と理由付き拒否(RouteSubmissionResult/RouteSubmissionRejection: 経路無し・最終地点不一致・継続点列先頭不一致・点不足・不正座標・SimulationOneWayCommand)、SIMULATOR_PROTOCOL_VERSION)を定義する。継続送信の受理検証(expectedEndPointRunStateがNotStartedかPausedかを判別できないため、開始と再開をStartOrResumeという単一の一方向操作にNotStartedのままで自動開始せず、Finished状態で継続経路を受理した場合もPausedへ遷移しAppendAfterFinish)、利用者の明示的なStartOrResumeまで再開しない(意図しない自動発進の防止)。dev.drogger.tablet.permission.SIMULATOR_IPCprotectionLevel="signature")で同じ開発署名のシミュレータだけを許可し、SimulatorIpcServiceはexported="true"。:simulator-app側は<queries>でAndroid 11+ package visibilityに対応する。getProtocolVersion()を確認し、不一致ならbindを解除して操作をすべて無効化するSimulatorSessionState.IpcUnavailable、Phase 1の8状態への加法的拡張)。IPC経由の経路操作とadvance()はSimulationRuntimeの単一lockで直列実行し、経路検証から追加完了までをSimulatedNmeaLinkの位置ソースを、固定プリセット歩進SimulationPathModel.stepPosition、撤去済み)からSimulationRuntimeのRegisteredRouteEngineへcurrentPositionがnull)はNMEAを送出しない。stale・疑似通信断はSimulatedNmeaLink.setPaused/simulateCommunicationLoss(Track #64、無変更)へそのまま:simulator-appは緯度経度の直接入力でIPC疎通・受理/拒否・:simulator-protocol(55件)、:simulator-ipc(Parcel envelope⇄ドメイン型の:simulator-app(bind失敗→再接続でクラッシュしないこと、protocol不一致時に:app(SimulatedNmeaLink・SimulationIntegrationTest等、既存Track #64テストを新しい経路登録前提へ更新)。drogger_stage2_testエミュレータへDrogger Tablet・シミュレータのStartOrResume後に経路へ沿って現在地が進むこと、SIMULATOR_PROTOCOL_VERSIONをPhase 2の緯度経度直接入力画面を、実際の地図・筆を見ながら操作する編集画面へ置き換えた。詳細な
操作・表示・データ仕様・実装ノートはDroggerシミュレータ 地図経路編集UI仕様
を正本とする。入力/編集と直線/曲線をそれぞれ1つの2状態トグルで明示的に切り替え、操作対象外からの
ドラッグを地図パン、現在終点付近(48dp以内)からのドラッグを曲線入力、丸い点ハンドルを終点移動、
菱形ハンドルを曲線形状変更に使う。以前のIssue判断条件に入っていた「描画モード切替なし」は利用者指示
ではなく、タブレットではかえってジェスチャーを複雑にするため撤回済み(Phase 3着手前レビュー)。
:simulator-protocolへの追加: DraftRoute/DraftSection(Straight/DrawnCurve)によるDraftRouteEditor(追加・共有点移動・曲線終点移動・菱形CurveSimplification.kt)、DraftHistory)、入力/編集・直線/曲線・選択の排他状態(MapEditUiState)、TouchGeometry.kt)、状態→主操作の対応(SimulatorPrimaryAction)。SimulatorSessionStateはdraft型をDraftRouteへ拡張し、IPC切断時も編集中の未送信経路を破棄せずIpcUnavailable.preserved、再接続後に最終地点が変わっていた場合のEndPointChangedConfirmationを追加した(Phase 2は切断時に編集状態を破棄していたが、この要求により:simulator-appの地図実装: :appへ依存せず独立にMapLibreを実装(SimulatorMapStyle.kt)。apps/android-tablet/shared-map-assets/map/を:appと共有し、コピーは作らない。MapEditGestureController.kt)は48dp以内のヒット時だけジェスチャー全体を横取りし、:simulator-protocolが101件(Phase 2の55件から、Phase 3のドラフト編集・undo・:simulator-appが11件(直線/曲線追加、undo、:app/:simulator-appのdebug APK・:appのrelease APKのビルドに成功し、release APKdrogger_stage2_test(emulator-5554限定)へ2 APKを実インストールし、StartOrResume後に現在地が経路に沿って進むこと)、シミュレータへ戻って新しい最終地点からWorkMenu(:app)のfillMaxHeight()が周囲のRowを画面下端まで引き伸ばし、地図・下部ドックをheightIn(min=64dp, max=72dp)へ修正)。GeoJsonSource(id, url)のimperative指定ではなく、:appと同じくstyle JSON内でのgeojsonソース宣言に統一して修正)。2026-08-21経路仕様の更新: Issue #97で確定した
隣接耕基本経路パターン1を経路ロジックの正本とする。
実装契約は
隣接耕基本経路パターン1 圃場幾何パイプライン実装契約
を現行契約とする。
代表辺候補と自由回転、画面骨格等のUI固有規則は、引き続き本書とUIデザイン指針を正本とする。
TILLとDEADHEADだけとし、作業機の上げ下げ操作は計画しない。以下の箇条書きはTrack #69〜#95時点の実装履歴であり、現行の経路・設定・表示判断には使用しない。
追い詰め、導入外周、安全中心線、両端拘束、parity、1列専用先行移動、DOUBLE_TILL、厳密候補比較、凸形状gateは
上記の現行仕様・契約によって置換された。
詳細な旧経路規則はTrack #69〜#95の記録・archiveにのみ残し、このUI仕様からは除外する。現行UIで継続するのは、
辺ごとの方向候補と自由回転、入口取得元の優先順位、段階別パネルというUI固有の操作規則である。経路・設定・表示内容は
上記の現行仕様と実装契約だけを参照し、旧Trackの型名・action・停止条件を再導入しない。
プロファイルのtrajectoryFileNamePrefixを使い、実際にセグメントを.dtrk形式([docs/specifications/p6-trajectory-file-format.md])で保存する部分を実装した。P6-9のJSON Lines形式(track/trajectory.jsonl、単一ファイルへ全セグメントを追記)を置き換える。
track/records/{trajectoryFileNamePrefix(サニタイズ済み)}_{yyyyMMdd-HHmmss}.dtrk。「新しい記録を開始」の瞬間に新規ファイルを作り、以降そのセグメントの間ずっと逐次追記する(保存操作なし、P6-9の方針を踏襲)。「記録を再開」は同じファイルへの追記を続ける。DtrkFileWriter/DtrkFileReader: P6-10当時はV1(ヘッダ36byte+レコード10byte)を実装した。以降V2(P6-11)、V3(Track #54)、V4(Issue #96)へ拡張しており、現行形式の正本は軌跡ファイル形式仕様とする。1点ごとにflush()し、クラッシュ耐性を保つ。SwitchableTrajectoryRecorder: FixRepositoryはコンストラクタで固定の1TrajectoryRecorderを受け取る設計のため、セグメントが変わるたびに書き込み先ファイルを差し替えられるよう、委譲先を実行時に切り替え可能なラッパーを新設した。DtrkFileWriter.resume()を実装したが、途中書きfileへ追記すると後続recordまでmisalignさせる可能性があるため廃止する。記録中にプロセスが再起動した場合は常に新しいsegment/fileを開始する。旧fileはreaderが末尾の不完全frameだけを切り捨てて回収し、writerは検査・修復・追記しない。records/配下の全.dtrkファイルを読み結合し、これまで通り地図に全セグメントの軌跡を表示する。各ファイルはヘッダに1つのsegmentIdしか持たないため、ファイルをまたいでも線はつながらない(既存のsegment対応描画をそのまま活用)。records/配下の全.dtrkファイルを削除するよう変更した。クリア時に記録中だった場合は、データを失わないよう新しいファイル・新しいsegmentとして記録を続ける。am force-stop後の同一file追記まで実機確認したが、この挙動はIssue #96で廃止する。V4の再起動時新file作成は実装後に改めて実機確認する。46/13)、ONかつSOLVED計画確定済みはNAVIGATION_FIELD_TEST_DEBUG(recordLength=27)を使う。ONでも計画未確定なら通常形式で記録し、SOLVED計画確定時にfile境界を作る。.dtrkと失敗・非実走計画JSONはtrack/records/debug/へ保存し、「軌跡をクリア」の対象外とする。NOT_COMPLETED/INVALID_INPUT、またはfixなしで終了したSOLVED計画は単独.plan.jsonとして保存する。相関不能時のsessionIdはUNLINKEDとする。.pending-plan.jsonでsnapshotを保持する。再起動時回復に使用し、DTRK作成成功後は削除、非実走で終了した場合は正式な.plan.jsonへ切り替える。RtkForegroundServiceが未バインドの間(起動直後・再バインド待ち)も取りこぼさない。ViewModel側で保留し、bind完了後にFIFOで配送する。未バインド中に同一内容が連続した場合と、SOLVED確定・失敗が別内容で連続した場合は最新の1件だけを残し、古い計画で新しい計画を上書きしない(#126)。.dtrkを通常軌跡と同様に表示・再生する。計画JSONは「計画デバッグ」行として時刻、pattern、結果statusを表示し、軌跡サムネイルと再生操作は出さない。.dtrkと計画JSONはいずれも一覧から選択してSAF書き出し・削除できる。書き出し成功後はdebug配下のexported/へ移し、一覧には残す。利用者フィードバック「今、それをUI上で確認する手段がない」(記録した軌跡が実際に保存できているかUI上で確認できない)を受け、記録一覧・サムネイル・再生をまとめて実装した。
WorkControls(作業制御系ボタン領域、画面左下)に「記録一覧」ボタンを追加。TrajectoryThumbnail): 利用者要望「軌跡がある地図上の場所をサムネイル表示にするのが良い」を受け、ファイル名の列挙ではなく実際のオフライン基地図(航空写真・筆ポリゴン込み)を背景にした小さな地図として実装した。一覧の行ごとに独立した小型MapView(操作不可・軌跡の範囲に自動でカメラを合わせる)を持つ。LazyColumnは画面内の行だけを保持するため、同時に生きるインスタンス数は表示行数程度に収まる設計。
newLatLngBounds)が算出するズームが極端に高くなり、オフライン基地図(PMTiles)の提供範囲を超えてベクトルタイルが空白になる不具合を実機で発見した。フィット後のズームが上限(17)を超えたら固定ズームへ再設定するクランプをfitCameraToPoints(サムネイル・再生画面で共通)に追加して解消した。TrajectoryListScreen): 通常・debug .dtrkは各行にサムネイル+開始〜終了時刻(記録中は開始時刻のみ+「記録中...」)+点数を表示し、タップで再生画面を開く。単独.plan.jsonは「計画デバッグ」行として表示し、再生画面は開かない。.dtrk行は「記録中」「記録停止(再開可能)」「確定済み」のいずれか。
isRecordingかつlive writerが所有。「記録中...」を強調色で表示、チェックボックスなし・選択不可。RtkForegroundService)側でも保護対象fileを除外し、UIの選択制御だけに依存しない。保護はruntime状態で永続化しない(プロセス終了後の旧fileは「確定済み」として扱う)。TrajectoryReplayScreen)・再生ロジック(TrajectoryReplayer): 利用者要望「ファイルを選択したら、軌跡を再現する事をしたい。再現速度は可変にしたい」を受け実装した。記録時の実際の点間隔を速度倍率で圧縮/伸長して再現する(録画済みRTCMを実際の受信間隔で再生するRecordedRtcmSourceと同じ考え方)。速度は1/2/5/10/30倍のプリセットをボタンでタップして巡回させる方式(スライダーではなく、既存のボタン中心のUIと統一)。前後の記録点を線形補間して滑らかに動かす。再生ロジック自体はUIに依存しないTrajectoryReplayerとして切り出しテスト可能にした。MapScreenと同じ描画方式(OfflineMapStyle.kt)を再利用し、記録操作(WorkControls)は持たない読み取り専用の画面にした。RtkForegroundService.listTrajectoryRecords()がcurrentFilePath(記録を停止した後も「記録を再開」で同じファイルへ戻れるようあえて残している値)の有無だけで「記録中」を判定しており、isRecording(実際に記録が動いているか)を見ていないバグだったと判明した。停止済みのファイルまで「記録中...」と表示され続けていた。isRecordingがtrueの場合のみcurrentFilePathを有効として渡すよう修正した。P6-10の最後の要件「記録を吸い出して消去する部分」を実装した。方式はユーザーとの discussion により以下で確定した。
転送方式: Android標準のフォルダアクセス(Storage Access Framework、ACTION_OPEN_DOCUMENT_TREE)で、利用者がGoogle Drive・OneDriveなど「フォルダを選べるアプリ」のフォルダを指定する方式にした。USBは対象外(ユーザー判断: 「USBってのは、あんまり考えてないです」)。アプリ側にOAuth・クラウドAPIとの連携は一切不要で、DocumentFile/ContentResolverだけで完結する。
TrajectoryExportDestination抽象: 「うちでしか使わないなら自宅サーバー(keinasystem、main.keinafarm.net)組み込みが良いが、汎用性も欲しい」というユーザーの整理を受け、書き出し先をTrajectoryExportDestinationインターフェース(isConfigured()/export(file))として切り出した。今回はSAF実装(SafTrajectoryExportDestination)のみを実装し、keinasystem向けのHTTP実装等は将来この抽象の別実装として追加できるようにしてある。
原形式のまま書き出す: P6-10当時のGPX比(10byte対約96byte)はV1の試算であり現行V4のbyte数ではないが、端末では変換せず.dtrkまたは.plan.jsonのまま書き出す結論は維持する。
records/exported/サブフォルダによる状態管理: 「書き出す」操作は選択された(記録中のファイルを除く)未書き出し.dtrkをTrajectoryExportDestination.export()でコピーし、成功したものだけrecords/exported/へ移動する(失敗したものは直下に残し、再度選んで書き出せば再試行できる)。一覧・サムネイル・再生はexported/配下も引き続き対象に含めて表示する(過去の全セグメントを表示し続ける既存方針を維持)。「削除」操作は選択されたファイルを、書き出し済みかどうかに関わらず端末から削除する。書き出し先のデータ自体は消えない。
「軌跡をクリア」との関係: 「軌跡をクリア」はrecords/直下の通常.dtrkだけを削除し、exported/とrecords/debug/には触れない。debug成果物の削除は記録一覧の選択式「削除」に限定する。
選択式の書き出し・削除への変更(フォローアップ): 当初は「未書き出し全件を一括で書き出す」「書き出し済み全件を一括で削除する」方式だったが、利用者フィードバック「一括の書き出し/削除は書き込み手順が不便」を受け、記録一覧の各行にチェックボックスを追加し、選択した記録だけを対象に書き出す・削除する方式へ変更した。記録中の行はチェックボックスを表示せず選択不可にしている。RtkForegroundServiceのAPIもexportPendingTrajectoryRecords()/deleteExportedTrajectoryRecords()(引数なし・全件対象)からexportTrajectoryRecords(filePaths: Set<String>)/deleteTrajectoryRecords(filePaths: Set<String>)(選択されたファイルパス集合を受け取る)へ置き換えた。
書き出し先フォルダ選択の確認ダイアログ(フォローアップ): レイヤー0設定の「軌跡の書き出し先フォルダ」行は、当初タップすると即座にSAFピッカー(別アプリ)が開く設計だったが、利用者から「元の画面に戻る方法がわからない」という指摘を受けた。SAFピッカーは別アプリのActivityであり、深い階層まで移動していると端末の「戻る」操作だけでは階層の数だけ戻る必要があり迷いやすい。「別アプリのせいで片付けるのは無責任」という利用者の指摘を受け、タップ直後にこの画面内で完結する確認ダイアログ(「選び直す」/「キャンセル」)を挟み、そこに「フォルダを選ばずに戻りたいときは、戻るを階層の数だけ押すか、最近使ったアプリからDroggerに切り替えれば設定は変わらない」という案内を先に載せる設計にした。これにより、確認だけしたい・誤ってタップしただけの場合はこの画面内の「キャンセル」一回で完結する。
既知の制限: Google DriveアプリはSAF経由の新規フォルダ作成に対応していない: 実機検証で、Drogger(や他のアプリ)のフォルダ選択画面からGoogle Drive内に「新規フォルダを作成」しようとすると、文字入力や日本語IME変換とは無関係にFileNotFoundException: not supportedで毎回失敗することを確認した(DriveアプリのDocumentsProvider.createDocument実装側の制限)。回避策は、Google Driveアプリ本体(またはdrive.google.com)側であらかじめ書き出し用フォルダを作成しておき、Droggerのフォルダ選択画面ではその既存フォルダを選ぶ(「開く」だけなら問題なく動作する)。
実機テスト時の注意(教訓): 実機でのSAFピッカー操作中、フォルダ階層を辿っている途中の誤タップにより、テスト用の.dtrkファイルが利用者の実際のGoogle Driveフォルダ(実務で使っている「Develop」フォルダ)へ書き出されてしまう事故が本セッション中に発生した。原因は、SAFピッカーの「キャンセル」操作の過程で実在フォルダを誤って確定してしまったこと。実機のGoogle Drive等、実データを持つ外部アカウントに接続した状態でのUI自動操作テストは、誤操作が実データに影響しうることを踏まえ、慎重にスクリーンショットで選択内容を確認しながら進める必要がある(該当ファイルはDrive側で削除・復旧済み)。
UI: レイヤー0設定画面に「軌跡の書き出し先フォルダ」項目を追加(未設定/フォルダ名表示、確認ダイアログ経由でのSAFピッカー起動、設定解除)。記録一覧画面の上部バーに選択状態に応じて有効/無効が切り替わる「書き出す」「削除」ボタンを追加し、各行にチェックボックスと、書き出し済みを示す「書き出し済み」バッジを追加した。
実機で、SAFピッカーでのフォルダ選択(アプリ名「Drogger Tablet」が正しく表示されることを含む)・恒久権限付与・レイヤー0設定への反映・記録一覧での選択式書き出し(選択した1件のみが書き出されexported/へ移動しバッジが反映されること)・選択式削除(確認ダイアログ、削除後も書き出し先のファイルは残ることの確認)・アプリ完全再起動後もフォルダ設定が保持されることを確認した。testDebugUnitTest全件成功、assembleRelease成功。
NAV-PVTはDG-PRO1RWSのBluetoothブリッジの固定フィルタにより恒久的に届かない(NAV-PVTはBluetooth SPP経由で恒久的に届かない、P6-5で確定、再調査の余地なし)。精度指標はGST(実測誤差)優先・HDOPフォールバックへ切り替え済み(P6-6/P6-8)。
GST出力有効化はRAM限定・非永続だが、RtkForegroundServiceが接続確立(BluetoothLinkState.Connected)のたびに自動送信するため、利用者の手動操作は不要(P6-8フォローアップ)。診断画面の手動ボタンは動作確認・任意タイミングでの再送信用に残している。
軌跡表示(P6-9)は実装済みだが、実機での動作確認は静止状態に限られ、実際に移動しながらの線描画の目視確認はできていない。次回の実機試用で確認する。
アンテナ―作業機(ロータリー)オフセットの記録と描画時変換は、P6-11で仕様策定・P6-12(Track #50)で実装した。2026-09-04の利用者判断以降、この変換は耕耘あと帯だけに適用し、中心線はアンテナ位置を示す。旋回判定の閾値調整・実機確認は未実施。「アンテナ―作業機オフセット」節を参照。
耕耘あと(作業機幅を反映した帯)表示は、Track #54でPC側ツール(pc-tool、PC側 耕耘あと表示検証ツール参照)を先行開発した上で、タブレット側にも再生画面(記録一覧から選んだ1ファイルの再生)へ実装・実機確認済み。実際のフィールドデータ(V2)をV3化し現実的な作業機幅(150cm)でも実機確認し、帯の視認性は表示縮尺に強く依存する(全体表示では見えないほど細いが、ズームすると正しい太さ・位置で描画される)ことを確認した。作業機幅はProfile.implementWidthCm(レイヤー1)として持ち、.dtrk V3/V4ヘッダへ記録する(軌跡記録バイナリ形式仕様参照)。ライブ作業画面(MapScreen)への反映は#124で実装した(下記「ライブ作業画面の耕耘あと帯表示(#124)」参照)。自己交差・切り返し箇所の重なり表示は既知の残課題としてpc-trajectory-viewer.mdに記載。
WireGuardトンネルの起動忘れによるNTRIP接続失敗が実機フィールドテスト(2026-08-05)で発生し、疎通確認・警告表示をIssue #51として起票、Track #55で検知方式・警告UIの仕様策定とレビューを経て実装した(下記Track実施記録参照)。1回目の実機試験(2026-08-07)は検知ロジック自体は合格したが、長文警告時に「再接続」ボタンが画面外へ出て操作不能になる・警告バナーがバージョン表示/測位品質バッジと重なるというUI不具合、およびVPN capability喪失経路での消失デバウンス再評価漏れが見つかり不合格だった。3点とも修正済み(testDebugUnitTest・assembleRelease成功)だが、修正後の実機再試験はまだ実施していない。
軌跡記録(.dtrk)を開始しないまま走行してしまう操作ミスが同日発生し、事実ベースで原因を追跡できるよう、利用者操作の診断ログ記録をTrack #52・#53で実装した(下記Track実施記録参照)。
測位系画面の耕うん計画は、隣接耕基本経路パターン1(Issue #107/#111)として実装中。走行中の
ナビゲート/誘導(action進捗、逸脱判定、作業機操作、音声・画面誘導、完了判定)は後続Issueで扱う。
開閉パネルのダッシュボードレイアウト(グルーピング・アイコン化等)は未着手。ユーザー判断により、機能が増えて整理対象が増えてから着手する方針(P6-9)。
軌跡データの保存・一覧・再生・端末外への吸い出しはP6-10で完了した(Track #48)。.dtrk形式での保存(1セグメント=1ファイル)、記録一覧・サムネイル(基地図背景)・再生(速度可変)、SAF(Drive/OneDrive等)への書き出しと書き出し済み記録の消去まで実装済み。keinasystem(自宅サーバー)向けの書き出し先実装、PC側のGPX変換ツールは将来の別タスクとして残っている。
レイヤー2の中身は未着手。レイヤー1は最小実装(プロファイル名・ファイル名ルール)が完了(P6-10)。
Track #54でPC・タブレット再生画面へ実装した耕耘あと帯を、ライブ作業画面(MapScreen)へも表示するようにし
(先行コミットeda11bd)、続けて2026-09-04の利用者判断に沿って中心線と帯の表示上の意味を確定・統一した
(後続コミット)。デフォルトプロファイルへ耕耘幅を設定して記録した実データが、アプリ再起動後のライブ作業画面で
中心線だけになり帯が出ていなかった不具合の解消と、中心線がアクティブプロファイルの位置調整でずれていた挙動の是正。
trajectoryは、起動時に復元した複数記録の点とTrajectoryRenderGrouping(track-core)がsegmentId連続区間ごとに分割し、.dtrkヘッダ由来のDtrkOffsets(前後・左右オフセット・作業機幅)を対応づける。現在のアクティブTrajectoryRecordingCoordinatorがrecords/(+exported/)の全.dtrkヘッダをMap<Long, DtrkOffsets>(segmentId→設定)をキャッシュし、ファイル境界操作(記録開始・クリア・.dtrkは最初のfixまでディスクへ書かれないため、DtrkFileReader.readHeaderSummaryはヘッダのsegmentId+オフセットだけを読み、点列は読まない。.dtrkへ保存されたGPSアンテナ生位置を前後・左右へTrajectoryOffsetTransform.centerline)。帯だけを記録単位のヘッダ値(記録中は記録開始時点のTrajectoryOffsetTransform.apply)、そこから作業機幅の半分ずつ左右へ広げる。TrajectoryTurnDetectionSettings)・segment/file境界を使う。ライブ作業画面(MapScreen)・保存記録再生TrajectoryReplayScreen)・PC検証ツール(pc-tool、--offset-*-cmは帯だけへ作用)で意味を統一する。MapScreenは中心線用に現在のプロファイルオフセットを渡す引数・配線を廃止した。MapScreenのstyle構築でaddSwathLayerを中心線(addTrajectoryLayer)より先に追加し、#ffd600、opacity 0.5)を再利用する。implementWidthCm <= 0・旧形式・ヘッダを読めない記録はSwathGeometryBuilderがP6-1(Track #39、adopted): レイヤー0設定画面(Bluetooth接続先・NTRIP接続先)を実装。バージョン表示への7回タップで呼び出す導線、Android標準の設定画面に準拠したUI(上部バー・セクション見出し・ListItem・編集ダイアログ)、全初期化機能を実機確認。
P6-2(Track #40、adopted): 測位系画面の監視/操作/診断を分離。診断専用要素(UBX問い合わせ、VRSC診断、詳細カウンタ)を新設のDiagnosticsScreenへ移し、レイヤー0から呼び出せるようにした。残った監視+運用操作パネルは既定非表示にし、ボタンで表示切替できるようにした。実機試用で「測位品質が見えない」「レイアウトの余白が多い」というフィードバックを受け、このドキュメントの「運用中に確認したい情報の整理」節へ反映した。
P6-3(Track #41、adopted): 測位品質バッジを実装し常時表示にした(8状態)。Bluetooth接続先ボタンの表示バグを修正(接続済み時は非表示)。開閉パネルへ衛星数・最終受信時刻を追加。
P6-4(Track #42、adopted): バッジを画面中央からバージョン表示の真下(左上)へ移動し、7回タップの対象領域をバージョン表示+バッジの1つのクラスタへ統合した。ユーザーからの「1.4cmは何の値か」という質問をきっかけに、受信したhAccを鮮度チェックなしに保持し続け87分前の値を現在の値のように表示し続けていた実バグを発見・修正した(DEFAULT_STALE_THRESHOLD基準の鮮度チェックを追加)。本trackで行った「NAV-PVTは実際には届く(1件確認)」という訂正は、P6-5での再検証により撤回された。
P6-5(Track #43、baseline): NAV-PVTがCFG-VALSET送信直後の1回しか届かない原因を調査し、DG-PRO1RWSのBluetoothブリッジが持つ固定message IDフィルタ(P4/Issue #30で確定済み、NAV-PVTは対象外)による恒久的な制限であると確定した。環境要因・設定・再接続起因ではない。以後のP6設計判断の前提(baseline)とする。
P6-6(Track #44、adopted): 精度指標をNAV-PVT hAcc依存からGGAのHDOP依存へ切り替えた。NmeaParser.decodeGgaが抽出していたHDOPをFixRepositoryが捨てていたことが判明し、FixState.hdopを新設して配線した。PositionQualityBadgeはFIX/FLOAT限定のcm表示から、有効な測位を持つ全状態(FIX/FLOAT/DGNSS/SINGLE)でのHDOP直接表示("HDOP0.8"形式)へ変更した。実機で表示を確認済み。
P6-7(Track #45、needs-review): 第三者からの「GNGST/PUBX,00で実測cm誤差を取得すべき」という指摘を検証するため、実機の生NMEAログ(nmea.bin)を直接解析した。NMEAはGGA/RMCに限らずVTG/GLL/GSA/GSVまで広く届いており、P4の「NMEA全般が絞られている」という理解は誤りで「特定のUBXバイナリ(NAV-PVT)のみが遮断される」が正しいと訂正した。GSTは既定値0で当時は未出現。次の実装範囲(PDOP/VDOP追加のみか、GST有効化まで踏み込むか)はユーザー判断を仰ぎ、P6-8でGST有効化を選択した。
P6-9(Track #47、adopted): 測位系画面の4主任務のうち未着手だった軌跡表示を実装した。開閉パネルのダッシュボードレイアウト整理・レイヤー1/2着手を次候補としていたが、ユーザー判断で「機能実装が増えてから整理する方が良い」として保留し、代わりに測位系コア機能(地図表示・軌跡表示・基準線指定・ナビゲート)の実装状況を調査した結果、地図表示以外が未着手と判明。ユーザーが軌跡表示を選択。実装前に保持方針(メモリ上限+古いものから間引き、ストレージは全部記録)・クリア操作の要否(含める)・永続化要否(する)を確認してから着手した。TrackPointモデル、trackパッケージ(TrajectoryRecorder/FileTrajectoryStore/TrajectoryMemory)を新設し、FixRepositoryにGGA由来の軌跡記録・クリア・復元を追加、RtkForegroundServiceが起動時に永続化ファイルから復元するよう配線、MapScreenにLineLayerでの描画と「軌跡をクリア」ボタンを追加した。実機で、軌跡ファイルへの継続的な追記・クリア操作によるファイルの空化・am force-stop後の再起動をまたいだ記録継続(クラッシュなし)を確認した。実機が静止状態だったため線描画そのものの目視確認はできておらず、次回の移動を伴う実機試用で確認する。副次的な発見として、実機のGGA受信レートがP6-7の想定(約1Hz)と異なり実測約9〜10Hzだったことが判明した。フォローアップ: 当初のアーカイブ間引きはindexベースの単純な間引き(1つ飛ばし)だったが、「トラクターの走行はほぼ直進とUターンなので、この間引きだと古い軌跡がおかしくなる」という利用者指摘を受け、Ramer-Douglas-Peucker法による形状維持型の単純化へ置き換えた(詳細は上の「走行軌跡(P6-9)」節)。testDebugUnitTest(299件)全件成功、assembleRelease成功。
P6-8(Track #46、adopted): CFG-MSGOUT-NMEA_ID_GST_UART1(0x209100d4、一次資料確認済み)をRAM限定CFG-VALSETで有効化する診断操作を追加し、実機で送信したところGSTが継続的に届くことを確認した。NmeaParser.decodeGstでstdLat/stdLongを取得し水平誤差(sqrt(stdLat2+stdLong2))を算出、FixState.gstHorizontalErrorMへ保持、PositionQualityBadgeをGST優先・HDOPフォールバック表示へ変更した。GST専用の鮮度判定(5秒閾値)を設け、NAV-PVT hAccで踏んだ鮮度バグを再発させない設計にした。実機で「固定解 1.4cm」の表示を確認し、20秒以上経過後も鮮度ゲートを通過して表示が継続することを確認した(=継続受信の証拠、NAV-PVTの1回きり受信とは異なる)。ユーザーフィードバックを受け、cm表示から「誤差」という接頭語を削除した。さらに「手動操作なしでアプリ側から設定できないか」という要望を受け、RtkForegroundServiceがBluetoothLinkState.Connected遷移のたびにsendGstUart1EnableDiagnostic()を自動送信するようにした(BluetoothNmeaController本体とその既存テストスイートは変更せず、Service層の既存の状態監視処理を拡張する形で実装)。実機でアプリ完全再起動→診断画面を開かずに自動でGSTが有効化され「固定解 1.4cm」が表示されることを確認した。
P6-11(Track #49、adopted): 走行軌跡へのアンテナ―作業機オフセット反映について仕様を策定した(このtrackでは実装しない)。「トラクターの軌跡表示にはアンテナ―ロータリー間のオフセットが未反映」という利用者指摘を受けて着手し、#47(P6-9)とは別のtrackとして切り出した(referencesrelationで関連付け)。オフセット適用は記録時ではなく描画・再生時に行う方式へ転換し(旋回中は作業していないため軌跡表示に意味がないという利用者の実運用知見による)、TrackPointはアンテナ生位置のまま維持しつつCOG・速度を新たに記録する。COG無効時(低速・停止)は直前の方位を保持、旋回時(ヨーレート・速度による自動検出)はその区間の線を描画しない方針とした。.dtrk形式をv2へ拡張(ヘッダにオフセット、レコードにCOG・速度を追加)。詳細は「アンテナ―作業機オフセット」節および軌跡記録バイナリ形式仕様(.dtrk)を参照。旋回判定の具体的閾値・旋回区間の表示方法・実機でのRMC表記精度確認は、実装・試走データ取得後に確定する残課題として残っている。
P6-12(Track #50、adopted): P6-11で確定した仕様を実装した。NmeaParser.decodeRmcにCOG・速度のデコードを追加し、FixRepositoryがRMC受信のたびに保持してGGA由来のTrackPoint生成時にノット→m/s変換の上で付与するようにした。DtrkFormat/DtrkFileWriter/DtrkFileReaderをV1・V2両対応に拡張し、ドキュメントレビューで発覚した「V1のoffset6-7(reserved、実機記録済みファイルは常に0)とV2のheaderLengthは同じ位置だが意味が異なり、V1ファイルではこの値を長さとして信用してはならない」という制約を、V1は常にヘッダ長36をハードコード・V2のみheaderLengthを実読みする非対称設計として反映した。ProfileにoffsetForwardCm/offsetLateralCmを追加してレイヤー1のプロファイル編集画面に設定UIを追加し、レイヤー0にCOG信頼最低速度・旋回判定閾値(暫定値)の設定を追加した。新規TrajectoryOffsetTransform(track package、純粋関数)がメカニズム1(方位の穴埋め)・メカニズム2(旋回区間の除外)を実装し、地図画面・再生画面へ統合した。テスト作成中、旋回判定(メカニズム2)のヨーレート計算にメカニズム1で穴埋め済みの「有効方位」を使うと、低速で方位が信頼できないと判定されている間は方位が凍結して見え、実際のUターン開始(減速とともに方位が変わり始める瞬間)を検出し損ねる設計バグを発見し、RMCの生COG値を直接使う別系統のヨーレート計算に分離して修正した。testDebugUnitTest(353件)・assembleReleaseとも成功。実機での動作確認・閾値調整はTrack #49の決定通り未実施のまま次サイクルへ残した。
軌跡記録操作の診断ログ記録(Track #52、adopted): P6-12実装後の実機フィールドテスト(2026-08-05)で、1回目のトリップ後にNTRIP接続失敗(WireGuard起動忘れ、Issue #51として起票)、2回目のトリップでは軌跡記録(.dtrk)を一度も開始しないまま走行してしまう操作ミスが発生した。後者について「なぜ記録されていなかったか」を推測(「軌跡をクリア」が押されたのではないか)でしか説明できなかったことを受け、利用者要望「推測じゃなくて事実で追えるように」により、RtkForegroundServiceの軌跡記録の開始・再開・停止・クリア操作をDiagnosticRecorderへ記録するようにした。実機で3操作すべてがevents.jsonlへ記録されること、「軌跡をクリア」が書き出し済み(exported/)ファイルに影響しないことを確認した。
全ユーザー操作の診断ログ記録(Track #53、adopted): Track #52は軌跡記録の4操作のみを対象にしていたが、利用者から「実装前に相談してほしかった」「範囲を私が判断するのではなく、すべてのユーザー操作を記録できるようにしておくべき」という2点の指摘を受けた。今回は実装着手前にBluetoothGpsViewModelの全公開関数を洗い出して提示し、ログ量への懸念について利用者と設計を詰めてから実装した。DiagnosticRecorderへEventLevel(DEBUG/INFO)とminEventLevel(実行中変更可能なvarプロパティ)を追加し、FileDiagnosticRecorderがフィルタする。BluetoothGpsViewModel.logUserAction(category, message, level)を公開ヘルパーとして新設し、プロファイルCRUD・NTRIP設定・エクスポート/削除・接続/切断・NTRIP再接続・診断ボタン等の全操作、およびMapScreen/MainActivityの画面のみの操作(背景/航空写真/筆ポリゴン切替、主要画面遷移)から呼ぶようにした。データ・設定・接続に影響する操作はINFO(既定で常時記録)、画面表示のみの操作と診断専用ボタンはDEBUG(既定オフ)という基準で振り分けた。レイヤー0設定に「詳細ログ(DEBUG)を記録する」スイッチを追加し、SharedPrefsDiagnosticLoggingSettingsStoreで永続化しつつ実行中のDiagnosticRecorderへ即座に反映(アプリ再起動不要)されるようにした。実機で、既定OFF時にDEBUGイベントが記録されないこと、スイッチをONにした直後(再起動なし)に同じ操作がDEBUGとして記録されることを確認した。testDebugUnitTest・assembleReleaseとも成功。
WireGuard起動忘れ検知・警告表示(Track #55、実機試験1回目不合格→修正済み・再試験待ち): Issue #51(2026-08-05の実機フィールドテストでWireGuardトンネル起動忘れによりNTRIP補正なしのまま記録が進んでしまった事象)を受け、独立したping/TCPプローブを追加せず、既存NTRIP接続結果(NtripLinkState)とAndroidのVPN transport有無(NetworkCapabilities.TRANSPORT_VPN)の組み合わせで警告種別を導出する方式を採用した。実装前レビューで指摘された3点(自動テストと実機テストの境界の不整合、コールドスタート時の誤警告フラッシュ、全network監視方式の未確定によるちらつきリスク)を設計へ反映してから実装した。VPN有無はUnknown(初期走査未完了、警告抑制)/Present/Absentの三値(VpnPresence)とし、RtkForegroundServiceは既存のregisterDefaultNetworkCallback(NTRIP再試行トリガー用)とは別に、NET_CAPABILITY_NOT_VPNを除いた専用NetworkRequestで全networkを監視、callback登録直後にallNetworksを同期走査して初期状態を確定する(VpnPresenceReducer.completeInitialScan)。最後のVPN network消失だけ500ms保留し、その間に再出現したら消失を取り消すデバウンス(VpnPresenceReducer)でWireGuard再構成時の短い張り替えによる警告ちらつきを防ぐ。警告種別導出(deriveNtripWireGuardWarning)・private IPv4判定(isPrivateIpv4Host)・VPN集合reducerはAndroid非依存の純粋関数/クラスとしてntripパッケージに実装し単体テストを追加した(NtripWireGuardWarningTest・VpnPresenceReducerTest)。
1回目の実機試験(Redmi Pad SE 8.7、800×1340、release APK、2026-08-07)で、split-tunnel検出・コールドスタート時の誤警告フラッシュなし・WireGuard OFF後約500msでの警告表示・ON復帰での解除・NTRIP自動再試行・到達不能分類・アプリ/Service/GNSS受信の継続とも合格したが、UIレイアウトで不合格と判定された。長い警告文言(Unreachable)をRowで表示すると、Rowが子をmain軸方向に無制約(intrinsic width)で測るため「再接続」ボタンが画面外へ押し出され操作不能になり、さらにTopCenterの警告バナーがTopStartのバージョン表示・測位品質バッジと重なり両方読めなくなった。加えてコードレビューで、vpnNetworkCallback.onCapabilitiesChangedのVPN capability喪失経路(networkは残ったままVPN capabilityだけ外れる場合)には500ms消失デバウンス後の再評価が予約されておらず、onLost経路とデバウンス再評価の実装が分岐していた不具合も見つかった。
これを受け、NtripWireGuardWarningBannerをRowからColumnへ変更し文言を1行目・「再接続」ボタンを2行目(Modifier.align(Alignment.End))に分離して文言の長さによらずボタンが画面内に収まるようにし、バナーとバージョン表示+測位品質バッジ(TopStartStatusCluster)を同一Column内で縦に積んで重なりを解消した。RtkForegroundServiceのVPN消失処理はhandleVpnNetworkLost(network)へ共通化し、onLost・onCapabilitiesChangedのcapability喪失経路の両方から呼ぶことでデバウンス再評価の予約漏れを無くした。testDebugUnitTest・assembleReleaseとも成功。
修正後の800×1340実機での短文・長文表示確認、および同一実機でのWireGuard OFF→ON→到達不能の全遷移再試験はまだ実施していない(実機作業が必要なため)。また試験中に判明した「VPNはONだがunderlying networkが無い(Wi-Fi/テザリング未接続)」状態は現行のUnreachable分類のままで、より具体的な文言(「インターネット接続を確認してください」等)への改善はIssue #51の後続課題として残す(今回の修正要求には含まれていない)。