Investigate timeline solver cycle and add trigger conflict validation

- Add trigger conflict validation in show.go to detect overlapping or
  mutually exclusive row constraints.
- Implement Trigger.String() for improved error reporting.
- Add detailed logging to timeline.go to trace iterative solver
  non-convergence and empty row optimization cycles.
- Extend mock show generator with experimental cross-track triggers.
This commit is contained in:
Ian Gulliver
2026-02-21 19:14:14 -08:00
parent 4e42b6ea60
commit 34747e3ff8
3 changed files with 71 additions and 6 deletions

View File

@@ -26,6 +26,14 @@ type Trigger struct {
Targets []TriggerTarget `json:"targets"`
}
func (t *Trigger) String() string {
s := fmt.Sprintf("%s/%s ->", t.Source.Block, t.Source.Signal)
for _, target := range t.Targets {
s += fmt.Sprintf(" %s/%s", target.Block, target.Hook)
}
return s
}
type TriggerSource struct {
Block string `json:"block"`
Signal string `json:"signal"`
@@ -92,11 +100,33 @@ func (show *Show) Validate() error {
hookTargeted := map[blockEvent]bool{}
startTargeted := map[string]bool{}
sourceUsed := map[blockEvent]bool{}
signalTargetedBy := map[blockEvent]*Trigger{}
for _, trigger := range show.Triggers {
for _, target := range trigger.Targets {
signalTargetedBy[blockEvent{target.Block, target.Hook}] = trigger
}
}
for _, trigger := range show.Triggers {
sourceBlock := blocksByID[trigger.Source.Block]
if sourceBlock == nil {
return fmt.Errorf("trigger source block %q not found", trigger.Source.Block)
}
targetedTracks := map[string]string{}
for _, target := range trigger.Targets {
targetBlock := blocksByID[target.Block]
if prev, ok := targetedTracks[targetBlock.Track]; ok {
return fmt.Errorf("trigger conflict: %s targets multiple blocks on track %q (%q and %q)",
trigger, targetBlock.Track, prev, target.Block)
}
targetedTracks[targetBlock.Track] = target.Block
}
if t, ok := signalTargetedBy[blockEvent{trigger.Source.Block, trigger.Source.Signal}]; ok {
return fmt.Errorf("trigger conflict: %s vs %s", t, trigger)
}
if !isValidEventForBlock(sourceBlock, trigger.Source.Signal) {
return fmt.Errorf("trigger source signal %q is invalid for block %q", trigger.Source.Signal, trigger.Source.Block)
}