feat: polish & windviz & deploy

This commit is contained in:
Anatoly Antonov 2026-05-30 06:29:39 +09:00
parent 81b8e763bd
commit 465ad00f7b
78 changed files with 20622 additions and 2154 deletions

View file

@ -72,7 +72,7 @@ type BuiltModel struct {
}
var (
regMu sync.RWMutex
regMu sync.RWMutex
constraintFactories = map[string]ConstraintFactory{}
modelFactories = map[string]ModelFactory{}
)
@ -202,7 +202,7 @@ func buildPolygon(spec ConstraintSpec, _ BuildDeps) (Constraint, error) {
default:
return nil, fmt.Errorf("polygon: unknown mode %q", spec.Mode)
}
return Polygon{Vertices: spec.Vertices, Mode: mode, On: act, Label: spec.Label}, nil
return NewPolygon(spec.Vertices, mode, act, spec.Label), nil
}
func buildConstantRate(spec ModelSpec, _ BuildDeps) (BuiltModel, error) {
@ -224,34 +224,19 @@ func buildWind(_ ModelSpec, deps BuildDeps) (BuiltModel, error) {
}
func buildPiecewise(spec ModelSpec, deps BuildDeps) (BuiltModel, error) {
needsCtx := false
for _, seg := range spec.Segments {
if seg.Reference == "propagator_start" {
needsCtx = true
break
for _, s := range spec.Segments {
switch s.Reference {
case "", "absolute", "profile_start", "propagator_start":
default:
return BuiltModel{}, fmt.Errorf("piecewise: unknown segment reference %q", s.Reference)
}
}
if !needsCtx {
// Eager build: resolve any "profile_start" relative segments using
// the launch time we know at build time only when we have one.
// Without context, treat profile_start the same as absolute (the
// caller is expected to pre-resolve), and absolute as absolute.
segs := make([]RateSegment, 0, len(spec.Segments))
for _, s := range spec.Segments {
if s.Reference == "profile_start" {
return BuiltModel{}, fmt.Errorf("piecewise: profile_start reference requires a stage context — supply via lazy build")
}
segs = append(segs, RateSegment{Until: s.Until, Rate: s.Rate})
}
base := Piecewise(segs)
return BuiltModel{Model: maybeAddWind(base, spec.IncludeWind, deps)}, nil
}
// Lazy build — captures spec into a closure.
// Always build lazily: the profile runner supplies a StageContext before
// each stage, which is what resolves absolute / profile-relative /
// propagator-relative segment times uniformly.
return BuiltModel{
Build: func(ctx StageContext) Model {
segs := resolveSegments(spec.Segments, ctx)
base := Piecewise(segs)
return maybeAddWind(base, spec.IncludeWind, deps)
return maybeAddWind(Piecewise(resolveSegments(spec.Segments, ctx)), spec.IncludeWind, deps)
},
}, nil
}