docs: implementation plan for suitbuilder CD-tier filter
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f7fd6415a9
commit
dfdfd41882
1 changed files with 522 additions and 0 deletions
522
docs/plans/2026-06-25-suitbuilder-cd-tier-filter-plan.md
Normal file
522
docs/plans/2026-06-25-suitbuilder-cd-tier-filter-plan.md
Normal file
|
|
@ -0,0 +1,522 @@
|
||||||
|
# Suitbuilder CD-tier filter — Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Let a suitbuilder search restrict which crit-damage tiers (CD0/CD1/CD2) are allowed on armor pieces, so the user can build e.g. all-CD1 suits — while the default (all allowed) stays byte-identical to today.
|
||||||
|
|
||||||
|
**Architecture:** Add an `allowed_crit_damage` constraint. In the live Go solver (`inventory-go`), drop armor items whose CD tier isn't allowed during item loading, before the domination pre-filter. "Prefer highest allowed tier" needs no new code — it falls out of the existing scoring and CD-descending armor sort. Frontend swaps the dead Crit-Damage min/max inputs for three CD checkboxes.
|
||||||
|
|
||||||
|
**Tech Stack:** Go 1.25 (`go-services/inventory-go`), vanilla JS/HTML (`static/suitbuilder.*`), Docker on the server (no local Go toolchain).
|
||||||
|
|
||||||
|
**Spec:** `docs/plans/2026-06-25-suitbuilder-cd-tier-filter-design.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conventions for this plan
|
||||||
|
|
||||||
|
- **Source-of-truth edits** happen in the local repo at `C:/Users/erikn/source/repos/dereth-workspace/MosswartOverlord`, on branch `suitbuilder-cd-tier-filter`. Commit there.
|
||||||
|
- **No local Go toolchain.** Build & test run on the server (`overlord.snakedesert.se`) inside Docker.
|
||||||
|
- **Fast unit-test loop** (run from the local MosswartOverlord dir after copying changed files to the host — see Task 6 for the copy command):
|
||||||
|
```bash
|
||||||
|
ssh erik@overlord.snakedesert.se "docker run --rm \
|
||||||
|
-v /home/erik/MosswartOverlord/go-services/inventory-go:/src -w /src \
|
||||||
|
golang:1.25-bookworm sh -c 'go mod tidy >/dev/null 2>&1 && go test ./... -v'"
|
||||||
|
```
|
||||||
|
(Mounts the host's inventory-go source into a throwaway golang container. `go mod tidy` writes go.sum into that untracked dir — harmless.)
|
||||||
|
- The live container is `inventory-go` (image `inventory-go:local`, `127.0.0.1:8772`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File structure
|
||||||
|
|
||||||
|
- `go-services/inventory-go/suit_model.go` — **modify**: constraint field.
|
||||||
|
- `go-services/inventory-go/suit_cd.go` — **create**: pure CD-tier helpers (one responsibility, DB-free, unit-testable).
|
||||||
|
- `go-services/inventory-go/suit_cd_test.go` — **create**: unit tests for the helpers.
|
||||||
|
- `go-services/inventory-go/suit_solver.go` — **modify**: solver field + wire filter into `loadItems`.
|
||||||
|
- `go-services/inventory-go/Dockerfile` — **modify**: add a `go test` build gate (mirrors tracker-go).
|
||||||
|
- `static/suitbuilder.html` — **modify**: CD checkboxes replace min/max inputs.
|
||||||
|
- `static/suitbuilder.js` — **modify**: gather/validate/send `allowed_crit_damage`.
|
||||||
|
- `static/suitbuilder.css` — **modify**: minor styling for the toggles.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: Add the `allowed_crit_damage` constraint field
|
||||||
|
|
||||||
|
**Files:** Modify `go-services/inventory-go/suit_model.go`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Replace the dead crit min/max fields**
|
||||||
|
|
||||||
|
In `SearchConstraints`, replace these two lines:
|
||||||
|
|
||||||
|
```go
|
||||||
|
MinCritDamage *int `json:"min_crit_damage"`
|
||||||
|
MaxCritDamage *int `json:"max_crit_damage"`
|
||||||
|
```
|
||||||
|
|
||||||
|
with:
|
||||||
|
|
||||||
|
```go
|
||||||
|
AllowedCritDamage []int `json:"allowed_crit_damage"`
|
||||||
|
```
|
||||||
|
|
||||||
|
(The `Min/MaxCritDamage` fields were never referenced by the solver — confirmed by grep. The other `Min/Max*` fields stay untouched.)
|
||||||
|
|
||||||
|
- [ ] **Step 2: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /c/Users/erikn/source/repos/dereth-workspace/MosswartOverlord
|
||||||
|
git add go-services/inventory-go/suit_model.go
|
||||||
|
git commit -m "feat(suitbuilder): add allowed_crit_damage constraint field"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: CD-tier helpers + unit tests (TDD)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `go-services/inventory-go/suit_cd.go`
|
||||||
|
- Create: `go-services/inventory-go/suit_cd_test.go`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing tests**
|
||||||
|
|
||||||
|
Create `go-services/inventory-go/suit_cd_test.go`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestCritTier(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
rating, want int
|
||||||
|
}{{-1, 0}, {0, 0}, {1, 1}, {2, 2}, {3, 2}, {5, 2}}
|
||||||
|
for _, c := range cases {
|
||||||
|
if got := critTier(c.rating); got != c.want {
|
||||||
|
t.Errorf("critTier(%d) = %d, want %d", c.rating, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllowedCritSet(t *testing.T) {
|
||||||
|
for _, vals := range [][]int{nil, {}, {0, 1, 2}, {0, 1, 3}} {
|
||||||
|
if allowedCritSet(vals) != nil {
|
||||||
|
t.Errorf("allowedCritSet(%v) should be nil (inactive)", vals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s := allowedCritSet([]int{1}); s == nil || !s[1] || s[0] || s[2] {
|
||||||
|
t.Errorf("allowedCritSet({1}) = %v, want only tier 1", s)
|
||||||
|
}
|
||||||
|
if s := allowedCritSet([]int{0, 1}); s == nil || !s[0] || !s[1] || s[2] {
|
||||||
|
t.Errorf("allowedCritSet({0,1}) = %v, want tiers 0,1", s)
|
||||||
|
}
|
||||||
|
if s := allowedCritSet([]int{3}); s == nil || !s[2] || s[0] || s[1] {
|
||||||
|
t.Errorf("allowedCritSet({3}) = %v, want only tier 2 (normalized)", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsArmorSlot(t *testing.T) {
|
||||||
|
for _, s := range []string{"Chest", "Head", "Feet", "Chest, Abdomen", "Upper Legs, Lower Legs"} {
|
||||||
|
if !isArmorSlot(s) {
|
||||||
|
t.Errorf("isArmorSlot(%q) = false, want true", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range []string{"Neck", "Left Ring", "Left Wrist", "Trinket", "Shirt", "Pants", "Unknown", ""} {
|
||||||
|
if isArmorSlot(s) {
|
||||||
|
t.Errorf("isArmorSlot(%q) = true, want false", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cdItem(slot string, cd int) *SuitItem {
|
||||||
|
return &SuitItem{Slot: slot, Ratings: map[string]int{"crit_damage_rating": cd}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterArmorByCD(t *testing.T) {
|
||||||
|
items := []*SuitItem{
|
||||||
|
cdItem("Chest", 0), cdItem("Head", 1), cdItem("Feet", 2),
|
||||||
|
cdItem("Chest, Abdomen", 2), // multi-coverage armor, CD2
|
||||||
|
cdItem("Neck", 0), // jewelry — never filtered
|
||||||
|
cdItem("Shirt", 0), // clothing — never filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := filterArmorByCD(items, nil); len(got) != len(items) {
|
||||||
|
t.Errorf("nil filter dropped items: got %d, want %d", len(got), len(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
got := filterArmorByCD(items, map[int]bool{1: true})
|
||||||
|
keep := map[string]bool{"Head": true, "Neck": true, "Shirt": true}
|
||||||
|
if len(got) != 3 {
|
||||||
|
t.Fatalf("allowed{1}: got %d items, want 3", len(got))
|
||||||
|
}
|
||||||
|
for _, it := range got {
|
||||||
|
if !keep[it.Slot] {
|
||||||
|
t.Errorf("allowed{1}: unexpected slot %q survived", it.Slot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
got = filterArmorByCD(items, map[int]bool{0: true, 1: true})
|
||||||
|
if len(got) != 4 { // Chest(0), Head(1), Neck, Shirt
|
||||||
|
t.Errorf("allowed{0,1}: got %d items, want 4", len(got))
|
||||||
|
}
|
||||||
|
for _, it := range got {
|
||||||
|
if isArmorSlot(it.Slot) && it.Ratings["crit_damage_rating"] >= 2 {
|
||||||
|
t.Errorf("allowed{0,1}: CD2 armor %q should have been dropped", it.Slot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the tests to confirm they fail to build**
|
||||||
|
|
||||||
|
Copy only the test file to the host (the implementation doesn't exist yet):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /c/Users/erikn/source/repos/dereth-workspace/MosswartOverlord
|
||||||
|
scp go-services/inventory-go/suit_cd_test.go \
|
||||||
|
erik@overlord.snakedesert.se:/home/erik/MosswartOverlord/go-services/inventory-go/
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run the fast test loop (see Conventions).
|
||||||
|
Expected: FAIL — `undefined: critTier`, `allowedCritSet`, `isArmorSlot`, `filterArmorByCD`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Write the implementation**
|
||||||
|
|
||||||
|
Create `go-services/inventory-go/suit_cd.go`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// CD-tier filtering for the suitbuilder. The allowed_crit_damage constraint
|
||||||
|
// restricts which crit-damage tiers are permitted on ARMOR pieces; jewelry and
|
||||||
|
// clothing are never affected. "Prefer the highest allowed tier" is NOT done
|
||||||
|
// here — it falls out of the existing scoring (CritDamage2 > CritDamage1) and
|
||||||
|
// the CD-descending armor sort once disallowed tiers are removed.
|
||||||
|
|
||||||
|
// critTier normalizes a raw crit_damage_rating into a tier in {0,1,2}. Rare
|
||||||
|
// high-crit gear (rating >= 2, including 3+) collapses to tier 2 so it counts
|
||||||
|
// as "CD2" rather than being silently excluded.
|
||||||
|
func critTier(rating int) int {
|
||||||
|
switch {
|
||||||
|
case rating <= 0:
|
||||||
|
return 0
|
||||||
|
case rating == 1:
|
||||||
|
return 1
|
||||||
|
default:
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isArmorSlot reports whether a slot name denotes an armor coverage slot,
|
||||||
|
// including comma-joined multi-coverage slots like "Chest, Abdomen".
|
||||||
|
func isArmorSlot(slot string) bool {
|
||||||
|
if armorSlotSet[slot] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.Contains(slot, ", ") {
|
||||||
|
for _, p := range strings.Split(slot, ", ") {
|
||||||
|
if armorSlotSet[strings.TrimSpace(p)] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// allowedCritSet normalizes the constraint's allowed crit-damage tiers into a
|
||||||
|
// set, or returns nil when the filter is INACTIVE: no values, or all three
|
||||||
|
// tiers {0,1,2} present (== default). A nil result means "no filter" and keeps
|
||||||
|
// the default search path byte-identical to the unfiltered solver.
|
||||||
|
func allowedCritSet(vals []int) map[int]bool {
|
||||||
|
if len(vals) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
set := map[int]bool{}
|
||||||
|
for _, v := range vals {
|
||||||
|
set[critTier(v)] = true
|
||||||
|
}
|
||||||
|
if set[0] && set[1] && set[2] {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterArmorByCD drops armor items whose crit-damage tier is not in allowed.
|
||||||
|
// Non-armor items (jewelry, clothing, unknown) always pass through. When
|
||||||
|
// allowed is nil the input is returned unchanged.
|
||||||
|
func filterArmorByCD(items []*SuitItem, allowed map[int]bool) []*SuitItem {
|
||||||
|
if allowed == nil {
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
out := make([]*SuitItem, 0, len(items))
|
||||||
|
for _, it := range items {
|
||||||
|
if isArmorSlot(it.Slot) && !allowed[critTier(it.Ratings["crit_damage_rating"])] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, it)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run the tests to confirm they pass**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scp go-services/inventory-go/suit_cd.go \
|
||||||
|
erik@overlord.snakedesert.se:/home/erik/MosswartOverlord/go-services/inventory-go/
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the fast test loop. Expected: PASS (`ok` — 4 tests).
|
||||||
|
|
||||||
|
- [ ] **Step 5: Add the `go test` build gate to the Dockerfile**
|
||||||
|
|
||||||
|
In `go-services/inventory-go/Dockerfile`, after `RUN go mod tidy` add:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
RUN go test ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
(Mirrors `tracker-go/Dockerfile`; from now on every image build runs the tests.)
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add go-services/inventory-go/suit_cd.go go-services/inventory-go/suit_cd_test.go go-services/inventory-go/Dockerfile
|
||||||
|
git commit -m "feat(suitbuilder): CD-tier filter helpers + tests; gate inventory-go build on go test"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3: Wire the filter into the solver
|
||||||
|
|
||||||
|
**Files:** Modify `go-services/inventory-go/suit_solver.go`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add the precomputed set to the Solver struct**
|
||||||
|
|
||||||
|
In the `Solver` struct, after `armorBucketsItems int`, add:
|
||||||
|
|
||||||
|
```go
|
||||||
|
allowedCD map[int]bool // nil == no CD filter (default / all tiers)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Populate it in `newSolver`**
|
||||||
|
|
||||||
|
In `newSolver`, after the line `sv.neededSpellBitmap = sv.spellIndex.getBitmap(c.RequiredSpells)`, add:
|
||||||
|
|
||||||
|
```go
|
||||||
|
sv.allowedCD = allowedCritSet(c.AllowedCritDamage)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Apply the filter in `loadItems` before domination**
|
||||||
|
|
||||||
|
In `loadItems`, find:
|
||||||
|
|
||||||
|
```go
|
||||||
|
filtered := removeSurpassedItems(items)
|
||||||
|
```
|
||||||
|
|
||||||
|
and immediately ABOVE it insert:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Drop armor whose CD tier is disallowed BEFORE domination, so a CD2 piece
|
||||||
|
// can't surpass-and-remove an allowed CD1 piece we'd then exclude.
|
||||||
|
items = filterArmorByCD(items, sv.allowedCD)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Verify it still builds and all tests pass**
|
||||||
|
|
||||||
|
Copy the changed solver file and run the test loop:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scp go-services/inventory-go/suit_solver.go \
|
||||||
|
erik@overlord.snakedesert.se:/home/erik/MosswartOverlord/go-services/inventory-go/
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the fast test loop. Expected: PASS, and the package compiles (the wiring type-checks; `go test` builds the whole `main` package).
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add go-services/inventory-go/suit_solver.go
|
||||||
|
git commit -m "feat(suitbuilder): apply CD-tier filter in loadItems (before domination)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4: Frontend — CD checkboxes
|
||||||
|
|
||||||
|
**Files:** Modify `static/suitbuilder.html`, `static/suitbuilder.js`, `static/suitbuilder.css`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Replace the Crit Damage inputs with checkboxes**
|
||||||
|
|
||||||
|
In `static/suitbuilder.html`, replace this block:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="filter-group">
|
||||||
|
<label>Crit Damage:</label>
|
||||||
|
<input type="number" id="minCritDmg" placeholder="Min" min="0" max="999">
|
||||||
|
<span>-</span>
|
||||||
|
<input type="number" id="maxCritDmg" placeholder="Max" min="0" max="999">
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
with:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="filter-group">
|
||||||
|
<label>Allowed Crit Damage:</label>
|
||||||
|
<label class="cd-toggle"><input type="checkbox" id="allowCD0" checked> CD0</label>
|
||||||
|
<label class="cd-toggle"><input type="checkbox" id="allowCD1" checked> CD1</label>
|
||||||
|
<label class="cd-toggle"><input type="checkbox" id="allowCD2" checked> CD2</label>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Build `allowed_crit_damage` in `gatherConstraints()`**
|
||||||
|
|
||||||
|
In `static/suitbuilder.js`, replace these two lines:
|
||||||
|
|
||||||
|
```js
|
||||||
|
min_crit_damage: document.getElementById('minCritDmg').value || null,
|
||||||
|
max_crit_damage: document.getElementById('maxCritDmg').value || null,
|
||||||
|
```
|
||||||
|
|
||||||
|
with:
|
||||||
|
|
||||||
|
```js
|
||||||
|
allowed_crit_damage: [
|
||||||
|
document.getElementById('allowCD0').checked ? 0 : null,
|
||||||
|
document.getElementById('allowCD1').checked ? 1 : null,
|
||||||
|
document.getElementById('allowCD2').checked ? 2 : null,
|
||||||
|
].filter(v => v !== null),
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Drop the deleted field from validation**
|
||||||
|
|
||||||
|
In `validateConstraints()`, change:
|
||||||
|
|
||||||
|
```js
|
||||||
|
!constraints.min_armor && !constraints.min_crit_damage && !constraints.min_damage_rating) {
|
||||||
|
```
|
||||||
|
|
||||||
|
to:
|
||||||
|
|
||||||
|
```js
|
||||||
|
!constraints.min_armor && !constraints.min_damage_rating) {
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Send `allowed_crit_damage` in the request body**
|
||||||
|
|
||||||
|
In `streamOptimalSuits()`, replace these two lines:
|
||||||
|
|
||||||
|
```js
|
||||||
|
min_crit_damage: constraints.min_crit_damage ? parseInt(constraints.min_crit_damage) : null,
|
||||||
|
max_crit_damage: constraints.max_crit_damage ? parseInt(constraints.max_crit_damage) : null,
|
||||||
|
```
|
||||||
|
|
||||||
|
with:
|
||||||
|
|
||||||
|
```js
|
||||||
|
allowed_crit_damage: constraints.allowed_crit_damage,
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Style the toggles**
|
||||||
|
|
||||||
|
Append to `static/suitbuilder.css`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.cd-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
margin-right: 10px;
|
||||||
|
font-weight: normal;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.cd-toggle input { margin: 0; }
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add static/suitbuilder.html static/suitbuilder.js static/suitbuilder.css
|
||||||
|
git commit -m "feat(suitbuilder): CD0/CD1/CD2 allowed-tier checkboxes (replace dead crit min/max)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 5: Deploy to the server & verify end-to-end
|
||||||
|
|
||||||
|
- [ ] **Step 1: Copy changed backend files to the host build context**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /c/Users/erikn/source/repos/dereth-workspace/MosswartOverlord
|
||||||
|
scp go-services/inventory-go/suit_model.go go-services/inventory-go/suit_cd.go \
|
||||||
|
go-services/inventory-go/suit_cd_test.go go-services/inventory-go/suit_solver.go \
|
||||||
|
go-services/inventory-go/Dockerfile \
|
||||||
|
erik@overlord.snakedesert.se:/home/erik/MosswartOverlord/go-services/inventory-go/
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Build the image (runs `go test` as part of the build)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh erik@overlord.snakedesert.se 'cd /home/erik/MosswartOverlord && \
|
||||||
|
docker compose -f docker-compose.yml -f go-services/docker-compose.go.yml \
|
||||||
|
build inventory-go'
|
||||||
|
```
|
||||||
|
Expected: build succeeds; the `RUN go test ./...` layer passes.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Recreate the container with the cutover override**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh erik@overlord.snakedesert.se 'cd /home/erik/MosswartOverlord && \
|
||||||
|
docker compose -f docker-compose.yml -f go-services/docker-compose.go.yml \
|
||||||
|
-f go-services/docker-compose.cutover.yml up -d --no-deps inventory-go'
|
||||||
|
```
|
||||||
|
Expected: `inventory-go` recreated; `docker ps` shows it healthy on :8772.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Copy the changed static files (bind-mounted; live immediately)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scp static/suitbuilder.html static/suitbuilder.js static/suitbuilder.css \
|
||||||
|
erik@overlord.snakedesert.se:/home/erik/MosswartOverlord/static/
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Verify default search is unchanged + CD1-only works**
|
||||||
|
|
||||||
|
Manual, in the browser at the suitbuilder page (hard-refresh to bust cache):
|
||||||
|
- With **all three CD boxes checked**, run a search (a primary set + a character with armor). Confirm results look like before.
|
||||||
|
- Check **only CD1**, run the same search. Confirm in the Network tab the request body has `"allowed_crit_damage":[1]`, and every armor piece in the returned suits shows **CD1** (jewelry/clothing unaffected; slots with no CD1 piece may be empty).
|
||||||
|
- Check **CD1 + CD0**, confirm no CD2 armor appears and CD1 is preferred where available.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 6: Finalize the local feature commit
|
||||||
|
|
||||||
|
- [ ] **Step 1: Confirm the branch state**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /c/Users/erikn/source/repos/dereth-workspace/MosswartOverlord
|
||||||
|
git log --oneline -6
|
||||||
|
git status
|
||||||
|
```
|
||||||
|
Expected: clean tree; the spec + plan + Tasks 1-4 feature commits on `suitbuilder-cd-tier-filter`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Reconcile host git + push to Gitea (separate, after the feature is verified live)
|
||||||
|
|
||||||
|
> ⚠ Pushing to the **public** Gitea is outward-facing and partly irreversible. Investigate state and decide a strategy BEFORE any push; surface the chosen strategy to the user first. Never `git add` the host's `.env` (secrets).
|
||||||
|
|
||||||
|
- [ ] **Step 1: Establish the true state of all three gits**
|
||||||
|
- Local `MosswartOverlord` HEAD (`9911edbf`, has go-services committed).
|
||||||
|
- Host `/home/erik/MosswartOverlord` HEAD (`6a0bb9fe`, go-services untracked, has server-only commits like rickroll/midsummer).
|
||||||
|
- Gitea `origin/master` — fetch and inspect; determine whether local's go-services history and/or the host's server-only commits are already on the remote.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Decide a reconciliation strategy** (depends on Step 1 findings):
|
||||||
|
- Get the host's server-only commits into the canonical local history (cherry-pick or merge), and get the local go-services history onto the host — so a single `master` contains both, with this feature on top.
|
||||||
|
- Plan must avoid clobbering the host's untracked `.env`/backups and avoid a destructive force-push unless explicitly chosen.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Execute the chosen reconciliation, then `git pull` on the host** so the host runs tracked code, and push the unified `master` to Gitea. Confirm `docker compose build` still uses the now-tracked go-services.
|
||||||
|
|
||||||
|
(Phase 2 steps are deliberately high-level — the exact git commands depend on Step 1's findings and a strategy choice. Do not pre-bake destructive commands.)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue