175 lines
14 KiB
Markdown
175 lines
14 KiB
Markdown
# Guider til opsætning (marker + flows) — implementeringsplan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:executing-plans (native; brugeren har givet fuld autonomi). Trin bruger `- [ ]`.
|
||
|
||
**Goal:** Fields- og Flows-fanen får en oversigt med resumé (klik = ret ét trin) og en trin-for-trin-guide ("Opsæt mark" / "Opsæt flow") på én fælles guide-ramme.
|
||
|
||
**Architecture:** En ren guide-motor (`adGuide.lua`) styrer trin, synlighed, tælling, næste/tilbage/spring. Oven på den to rene trin-definitioner (`adFieldGuide.lua`, `adFlowGuide.lua`) der ud fra en kladde + et `context` (det spil-afhængige, hentet af GUI'et) giver rækker (som data), valg, standarder, resumé og gem-resultat. GUI-laget (`gui/SmartPickupGuide.lua` fælles trin-skærm, `gui/SmartPickupFieldGuide.lua`, `gui/SmartPickupFlowGuide.lua`) er tyndt: bygger context, tegner rækkerne, router klik og gemmer via de eksisterende `ADFieldJobs`/`ADRunsController`-funktioner.
|
||
|
||
**Tech Stack:** Lua 5.1 (FS25), luajit til tests, FS25 GUI (klonede prefabs).
|
||
|
||
**Spec:** `docs/superpowers/specs/2026-09-26-guider-design.md`
|
||
|
||
## Global Constraints
|
||
|
||
- Lua 5.1: ingen goto, ingen coroutines, ingen `#` på tabeller med huller; pcall om spil-API.
|
||
- Gemt format (`ADSmartPickup_fields.xml`, `ADSmartPickup_runs.xml`) ændres ikke. Guiderne skriver kun via `ADFieldJobs.setTarget/setPlan` og `ADRunsController.createRun/updateRun/assignVehicle/unassignVehicle`.
|
||
- Intet gemmes før [Gem]/[Kør automatisk]/[Start flow]. [Annullér], fanebyt og lukket menu kasserer kladden.
|
||
- Rene moduler returnerer nye tabeller (kladden kopieres ved hvert valg).
|
||
- Filer under ~800 linjer; `SmartPickupFrame.lua` (971) får kun små routing-ændringer.
|
||
- Nye .lua i modDesc (rene moduler) eller `source()` i adSmartPickup.lua (GUI-filer), i rigtig rækkefølge.
|
||
- Al ny tekst i både `l10n_da.xml` og `l10n_en.xml`.
|
||
- Guide-rækker laves KUN med `ingredientPrefab` (Ja/Nej, flueben) og `productPrefab` (valg) med `kind = "guideRow"`, routet i `onClickIngredient`/`onClickProduct`; klikbare tekstlinjer via `makeRowClickable` + pendingClick. Ingen guide-rækker på `vehiclePrefab` (v1.30.4-fælden).
|
||
- Bundknapper: kun MENU_ACTIVATE, MENU_EXTRA_1, MENU_EXTRA_2, MENU_CANCEL (+ MENU_BACK) — andre findes ikke sikkert i FS25.
|
||
- Tests: `for t in tests/test_*.lua; do luajit $t | tail -1; done` — alle grønne.
|
||
|
||
## Review Focus
|
||
|
||
1. Klik på guide-rækker gemmes aldrig (forkert routing) → alle nye kinds routes via `guideRow`; testplan-punkt in-game.
|
||
2. Kladde muteres ved valg (Annullér gemmer alligevel) → test: `choose` ændrer ikke den gamle guide/kladde.
|
||
3. Trin forsvinder/dukker op efter et valg (fx mål-afgrøde græs på kornmark) → tælling og næste følger de synlige trin; test.
|
||
4. [Ret] fra oversigten må kun gemme det ene trin og bevare Auto/Manuel → test `result(guide, nil)` bevarer mode.
|
||
5. Faste maskiner: maskine fast på anden mark flyttes kun hvis brugeren vælger den (skjult i listen) → test `applyRigs`.
|
||
|
||
---
|
||
|
||
## Beslutninger (uklarheder i spec'en)
|
||
|
||
- **[Opsæt mark] uden valgt mark** vises også (åbner trin 1 "Mark"); ellers kunne trin 1 aldrig nås.
|
||
- **"Så igen bagefter"** (plan.sow uden mål-afgrøde) står i trin 2 under "Behold det der gror" for høst-/bare marker — ellers forsvinder muligheden.
|
||
- **Kalk**: i Klargøring ("Vælg selv"); når Klargøring ikke vises (ren græsmark) står Kalk i Pleje.
|
||
- **Klargøring "Automatisk"** = jord auto, kalk ja, mulch nej, tromle nej (anvendes kun når trinnet er synligt).
|
||
- **Maskiner-trinnet**: antal ad gangen + aflæsningsvogne først, så "Faste maskiner?" + listen (listen kan være lang). Aflæsningsvogne 1–3 (modellens `MAX_UNLOADERS`), ikke 1–5.
|
||
- **Mark uden markør**: gemmes med den valgte tilstand; job-løkken venter allerede på markøren (dagens regel). Oversigten viser "sæt markøren".
|
||
- **Mål-afgrøde** tvinger Auto (som i dag); på Oversigt-trinnet vises da kun [Kør automatisk].
|
||
- **[Ret] fra trin 8** vender tilbage til Oversigt-trinnet (kladden bevares); [Tilbage] i et [Ret]-trin fortryder det ene trins ændringer.
|
||
- **Flows**: bundlinjen har plads til 4 knapper → [Start/Stop] [Opsæt flow] [Nyt flow] [Slet]; Omdøb ligger på resuméets Navn-linje; Start alle/Stop alle bliver på fanen Oversigt.
|
||
- **Halm**: `context.straw` = afgrøder med halm (FS25 `fruitType.hasWindrow/windrowName`); kan det ikke aflæses (tom), vises halm-valget for alle høstafgrøder som i dag.
|
||
|
||
---
|
||
|
||
# Etape A — fælles ramme + Marker (v1.31.0.0)
|
||
|
||
### Task A1: Guide-motoren `adGuide.lua`
|
||
|
||
**Files:** Create `FS25_ADSmartPickup/adGuide.lua`, `tests/test_adGuide.lua`; Modify `modDesc.xml` (før adFieldModel.lua).
|
||
|
||
**Interfaces (Produces):**
|
||
```lua
|
||
ADGuide.new(def, draft, context, options) -> guide
|
||
-- def = {order = {stepId...}, isVisible = function(stepId, guide) -> bool}
|
||
-- options = {step = id|nil, single = bool, returnTo = "close"|"summary"}
|
||
-- guide = {def, draft, context, step, single, returnTo, snapshot}
|
||
ADGuide.visibleSteps(guide) -> {stepId...}
|
||
ADGuide.position(guide) -> index|nil, total
|
||
ADGuide.isLast(guide) -> bool
|
||
ADGuide.next(guide) -> guide, event -- event nil | "done"
|
||
ADGuide.back(guide) -> guide, event -- event nil | "cancel"
|
||
ADGuide.jump(guide, stepId, returnTo) -> guide (single, snapshot = kopi af kladden)
|
||
ADGuide.withDraft(guide, draft) / ADGuide.withContext(guide, context) -> guide
|
||
ADGuide.deepCopy(value) ; ADGuide.toggleItem(list, item, on) -> ny liste
|
||
ADGuide.labelText(label, getText) -> string -- label = {key, args} | {text}
|
||
ADGuide.lineText(line, getText) -> "Titel: del, del" -- line = {step, title = label, parts = {label...}}
|
||
```
|
||
|
||
- [ ] Test (RED): næste/tilbage springer skjulte trin over; tælling "3 af 5" over synlige; `next` på sidste = "done"; `back` på første = "cancel"; `jump(..., "summary")` + `next` lander på `summary` med single=false; `jump(..., "close")` + `next` = "done"; `back` i spring gendanner snapshot; `withDraft` returnerer ny guide (gammel urørt); trin der bliver usynligt efter et valg: `next` går til næste synlige i `order`; `toggleItem` uden dubletter; `labelText` med args og manglende args (pcall).
|
||
- [ ] Kør `luajit tests/test_adGuide.lua` → FAIL (fil mangler).
|
||
- [ ] Implementér; kør → ALLE TESTS OK.
|
||
|
||
### Task A2: Markguidens trin `adFieldGuide.lua`
|
||
|
||
**Files:** Create `FS25_ADSmartPickup/adFieldGuide.lua`, `tests/test_adFieldGuide.lua`; Modify `modDesc.xml` (efter adFieldView.lua).
|
||
|
||
**Interfaces:**
|
||
- Consumes: `ADGuide.*`, `ADFieldModel.product/MAX_RIGS/MAX_UNLOADERS`, `ADFieldFleet.visibleForField`, `ADFlowView.naturalLess`.
|
||
- Produces:
|
||
```lua
|
||
ADFieldGuide.ORDER = {"field","crop","cropChoice","prep","care","machines","store","summary"}
|
||
ADFieldGuide.open(field, context, options) -> guide -- context.info påkrævet
|
||
ADFieldGuide.openPicker(context) -> guide -- trin "field"
|
||
ADFieldGuide.pickField(guide, field, context) -> guide -- videre til trinnet efter "field"
|
||
ADFieldGuide.isVisible(stepId, guide) -> bool
|
||
ADFieldGuide.rows(guide) -> {row...}
|
||
-- row = {type="choice", id, title, options={{label, value}}, value}
|
||
-- | {type="toggle", id, title, off, on, value}
|
||
-- | {type="check", id, item, title, value}
|
||
-- | {type="text", label} | {type="link", id, value, label}
|
||
ADFieldGuide.choose(guide, id, value) -> guide
|
||
ADFieldGuide.summary(guide) -> {line...}
|
||
ADFieldGuide.result(guide, start) -> {fieldId, targetFruit, targetChanged, plan, rigIds}
|
||
ADFieldGuide.applyRigs(rigIdsByField, fieldId, rigIds) -> ny {[fieldId] = ids}
|
||
ADFieldGuide.stepKey(stepId) / hintKey(stepId) -> l10n-nøgler
|
||
```
|
||
- context: `{info, sowable={{name,title}}, mowable={[name]=true}, straw={[name]=true}|nil, rigs={{id,text,role}}, rigIdsByField, fieldSilos={{id,text}}, stores={{id,text}}|nil, storeHidden, fields={{id,text}}}`.
|
||
- kladde: `{fieldId, plan (kopi), originalTarget, prepAuto, useDedicated, storeMode ("auto"|"manual"|"fieldSilo"), storeIds, pickField}`.
|
||
|
||
- [ ] Test (RED): synlige trin for (a) græs uden mål: crop, cropChoice, care, machines, store, summary (ingen prep); (b) høst m. halm: + prep, cropChoice (halm); (c) høst uden halm (`straw` sat, afgrøden ikke med): ingen cropChoice; (d) bar mark uden mål: crop, prep, machines, summary (ingen care/store); (e) kornmark med mål græs: cropChoice (ensilage/hø) + prep; picker: kun "field". Standarder: ny mark → prepAuto, useDedicated=false, storeMode auto; plan med soil=plow → prepAuto=false; plan med rigIds → useDedicated; storeIds lig marksilo → fieldSilo. `choose` ændrer ikke gammel guide; `rigIds`-check toggler; `result`: prepAuto overskriver soil/mulch/lime/roll kun når prep er synlig; mode auto/manual efter start, uændret ved start=nil, auto ved mål; storeBuildingIds pr. storeMode; rigIds tom når useDedicated=false; targetChanged. `applyRigs` fjerner maskinen fra anden mark. Rækker: maskiner-trinnet skjuler maskiner faste på andre marker + tekst med antal; marksilo-spørgsmål kun når fieldSilos findes; "Vælg selv" viser siloerne. Resumé: linjer kun for synlige trin, med rigtige nøgler (fx `spu_guide_sum_dedicated` args {3}).
|
||
- [ ] Kør → FAIL; implementér; kør → OK. Hele suiten grøn.
|
||
|
||
### Task A3: Fælles trin-skærm `gui/SmartPickupGuide.lua`
|
||
|
||
**Files:** Create `gui/SmartPickupGuide.lua`; Modify `adSmartPickup.lua` (source efter SmartPickupFlowTab.lua), `gui/SmartPickupFrame.lua` (initGuideButtons i new, routing `guideRow` i onClickIngredient, refreshButtons → `getGuideButtons`, switchTab/onFrameClose lukker guiden, rebuild for faner med åben guide), `gui/SmartPickupOutboundTab.lua` (onClickProduct → guideRow).
|
||
|
||
**Interfaces (Produces):**
|
||
```lua
|
||
SmartPickupFrame.GUIDE_HOOKS[kind] = {model, tab, context(frame, adEnv, guide) -> context, save(frame, guide, start), choose(frame, id, value) -> handled}
|
||
SmartPickupFrame:initGuideButtons(i18n)
|
||
SmartPickupFrame:openGuide(kind, guide) SmartPickupFrame:closeGuide()
|
||
SmartPickupFrame:fillGuideRows(adEnv) -- overskrift "Trin 3 af 7 — …", forklaring, rækker
|
||
SmartPickupFrame:guideText(label) -> string
|
||
SmartPickupFrame:addLinkRow(text, action)
|
||
SmartPickupFrame:onClickGuideRow(state, data)
|
||
SmartPickupFrame:getGuideButtons() -> knapliste
|
||
SmartPickupFrame:onClickGuideNext/Back/Cancel/SaveOnly()
|
||
```
|
||
- [ ] Skriv filen; `luajit -bl` syntakstjek.
|
||
|
||
### Task A4: Marker-fanen: oversigt, markflåde, guide
|
||
|
||
**Files:** Create `gui/SmartPickupFieldGuide.lua` (context, gem, knapper for fanen); Modify `gui/SmartPickupFieldTab.lua` (fillFieldRows → oversigt/markflåde/guide; fjern den lange detaljeliste), l10n da/en.
|
||
|
||
- Oversigt: hint, tabel, for valgt mark: overskrift, resumé-linjer (klik = `ADGuide.jump(step, "close")`), fuld status, markør-linje, marksilo-advarsel. Knapper: [Opsæt mark] (EXTRA_1), [Start nu/Stop] (ACTIVATE), [Markflåde] (EXTRA_2).
|
||
- Markflåde: `fillFleetRows(adEnv, nil)` (alle maskiner). Knap [Til marker] (EXTRA_2).
|
||
- Gem: `setTarget` hvis ændret → `setPlan(plan)` → `applyRigs` → `setPlan(fid, {rigIds})` for ændrede marker.
|
||
- [ ] Skriv, syntakstjek, suiten grøn.
|
||
|
||
### Task A5: Docs + ship v1.31.0.0
|
||
|
||
- [ ] TESTPLAN-markarbejde.md: afsnit "Guider (v1.31)"; BRUGERMANUAL/USER_MANUAL: Marker-afsnit kort.
|
||
- [ ] Ship-procedure (version 1.30.4.0 → 1.31.0.0), commit `feat(gui): guide "Opsæt mark" …`, push gitea.
|
||
|
||
# Etape B — Flows på samme ramme (v1.32.0.0)
|
||
|
||
### Task B1: Flowguidens trin `adFlowGuide.lua`
|
||
|
||
**Files:** Create `FS25_ADSmartPickup/adFlowGuide.lua`, `tests/test_adFlowGuide.lua`; modDesc (efter adFlowView.lua).
|
||
|
||
**Interfaces (Produces):**
|
||
```lua
|
||
ADFlowGuide.ORDER = {"name","direction","buildings","goods","store","rules","wait","tractors","summary"}
|
||
ADFlowGuide.open(run, context, options) -> guide -- run = eksisterende run
|
||
ADFlowGuide.openNew(name, context) -> guide -- nyt flow (trin "name" synligt)
|
||
ADFlowGuide.isVisible(stepId, guide); ADFlowGuide.rows(guide); ADFlowGuide.choose(guide, id, value)
|
||
ADFlowGuide.summary(guide) -> {line...}
|
||
ADFlowGuide.result(guide) -> {isNew, runId, name, kind, changes, vehicleIds}
|
||
ADFlowGuide.vehicleChanges(oldIds, newIds) -> added, removed
|
||
ADFlowGuide.previewRun(draft) -> run-formet tabel til controllerens opslag
|
||
```
|
||
- context: `{buildings={{id,name,label,hasMarker,text}}, fillTypes={{name,title}}, products={{name,title}}, storeBuildings={{id,text}}, waitPools={{group,count}}, vehicles={{id,text,otherRunId,otherRunName,blocked}}, sellAtSteps, storeAboveSteps, returnBelowSteps, maxLoops}`.
|
||
- kladde: kopi af run-felterne + `isNew`, `showAllBuildings`, `storeAuto`.
|
||
- [ ] Test (RED): name-trin kun ved nyt flow; store-trin kun ved udkørsel med en vare på Lager/Salg-ellers-lager; retning skifter bygnings-rolle; varer: forsyning auto/vælg (ingredients toggles), udkørsel politik pr. vare (products); regler pr. retning; traktorer i andre flows/blokerede skjult + tekst; resumé pr. retning; result.changes; vehicleChanges; choose immutabelt.
|
||
- [ ] FAIL → implementér → OK.
|
||
|
||
### Task B2: Flows-fanen i GUI'et
|
||
|
||
**Files:** Create `gui/SmartPickupFlowGuide.lua`; Modify `gui/SmartPickupFrame.lua` (rebuild for Flows-fanen: tavle + resumé, statiske rækker skjult; refreshLive/refreshButtons), l10n.
|
||
|
||
- Oversigt: tavlen (`fillOverviewRows`), valgt flow: resumé (klik = ret ét trin; Navn-linjen = omdøb-dialog), status-linje (live). Knapper: [Start/Stop] (ACTIVATE), [Opsæt flow] (EXTRA_1), [Nyt flow] (EXTRA_2), [Slet] (CANCEL).
|
||
- Gem: nyt → `createRun`; `updateRun(changes)`; traktorer via `vehicleChanges` + `assignVehicle/unassignVehicle`; `resolveMarkers`; `save()`; [Start flow] → `startRun`.
|
||
- [ ] Skriv, syntakstjek, suiten grøn.
|
||
|
||
### Task B3: Docs + ship v1.32.0.0; selv-review
|
||
|
||
- [ ] TESTPLAN + manualer (Flows-afsnit), ship 1.31.0.0 → 1.32.0.0.
|
||
- [ ] Code-review-agent på hele diffen; ret CRITICAL/HIGH; evt. v1.32.1.0.
|