From 6efab4f41144964eac73272d04e08ca1c4cf8d75 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 20:19:49 -0700 Subject: [PATCH 1/7] validator: annotate MapCheck fields --- validation/src/check.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/validation/src/check.rs b/validation/src/check.rs index 0b428fa..5c5c3de 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -260,16 +260,33 @@ pub struct MapInfoOwned{ // crazy! pub struct MapCheck<'a>{ + // === METADATA CHECKS === + // The root must be of class Model model_class:StringCheck<'a,(),&'a str>, + // Model's name must be in snake case model_name:StringCheck<'a,(),String>, + // Map must have a StringValue named DisplayName. + // Value must not be empty, must be in title case. display_name:Result>,StringValueError>, + // Map must have a StringValue named Creator. + // Value must not be empty. creator:Result,StringValueError>, + // The prefix of the model's name must match the game it was submitted for. + // bhop_ for bhop, and surf_ for surf game_id:Result, + + // === MODE CHECKS === + // MapStart must exist mapstart:Result<(),()>, + // No duplicate map starts (including bonuses) mode_start_counts:DuplicateCheck, + // At least one finish zone for each start zone, and no finishes with no start mode_finish_counts:AtLeastOneMatchingAndNoExtraCheck, + // Spawn1 must exist spawn1:Result<(),()>, + // No duplicate Spawn# spawn_counts:DuplicateCheck, + // No duplicate WormholeOut# (duplicate WormholeIn# ok) wormhole_out_counts:DuplicateCheck, } -- 2.49.1 From ccf07c5931f58e0e13d98f51ccd0033f4d96fb4c Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 20:37:37 -0700 Subject: [PATCH 2/7] validator: rename AtLeastOneMatchingAndNoExtraCheck to SetDifferenceCheck --- validation/src/check.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index 5c5c3de..402cce2 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -169,7 +169,7 @@ pub fn get_model_info(dom:&rbx_dom_weak::WeakDom)->Result(Result>); pub struct StringCheckContext<'a,Str>{ observed:&'a str, @@ -219,13 +219,13 @@ impl DuplicateCheckContext{ } } -// check that there is at least one -pub struct AtLeastOneMatchingAndNoExtraCheckContext{ +// check that there is at least one matching item for each item in a reference set, and no extra items +pub struct SetDifferenceCheckContext{ extra:HashMap, missing:HashSet, } -pub struct AtLeastOneMatchingAndNoExtraCheck(Result<(),AtLeastOneMatchingAndNoExtraCheckContext>); -impl AtLeastOneMatchingAndNoExtraCheckContext{ +pub struct SetDifferenceCheck(Result<(),SetDifferenceCheckContext>); +impl SetDifferenceCheckContext{ fn new(initial_set:HashMap)->Self{ Self{ extra:initial_set, @@ -233,8 +233,8 @@ impl AtLeastOneMatchingAndNoExtraCheckContext{ } } } -impl AtLeastOneMatchingAndNoExtraCheckContext{ - fn check(self,reference_set:&HashMap)->AtLeastOneMatchingAndNoExtraCheck{ +impl SetDifferenceCheckContext{ + fn check(self,reference_set:&HashMap)->SetDifferenceCheck{ let Self{mut extra,mut missing}=self; // remove correct entries for (id,_) in reference_set{ @@ -245,9 +245,9 @@ impl AtLeastOneMatchingAndNoExtraCheckContext{ } // if any entries remain, they are incorrect if extra.is_empty()&&missing.is_empty(){ - AtLeastOneMatchingAndNoExtraCheck(Ok(())) + SetDifferenceCheck(Ok(())) }else{ - AtLeastOneMatchingAndNoExtraCheck(Err(Self{extra,missing})) + SetDifferenceCheck(Err(Self{extra,missing})) } } } @@ -281,7 +281,7 @@ pub struct MapCheck<'a>{ // No duplicate map starts (including bonuses) mode_start_counts:DuplicateCheck, // At least one finish zone for each start zone, and no finishes with no start - mode_finish_counts:AtLeastOneMatchingAndNoExtraCheck, + mode_finish_counts:SetDifferenceCheck, // Spawn1 must exist spawn1:Result<(),()>, // No duplicate Spawn# @@ -341,7 +341,7 @@ impl<'a> ModelInfo<'a>{ }; // check that at least one end zone exists for each start zone. - let mode_finish_counts=AtLeastOneMatchingAndNoExtraCheckContext::new(self.counts.mode_finish_counts) + let mode_finish_counts=SetDifferenceCheckContext::new(self.counts.mode_finish_counts) .check(&self.counts.mode_start_counts); // there must be exactly one start zone for every mode in the map. @@ -380,7 +380,7 @@ impl<'a> MapCheck<'a>{ game_id:Ok(game_id), mapstart:Ok(()), mode_start_counts:DuplicateCheck(Ok(())), - mode_finish_counts:AtLeastOneMatchingAndNoExtraCheck(Ok(())), + mode_finish_counts:SetDifferenceCheck(Ok(())), spawn1:Ok(()), spawn_counts:DuplicateCheck(Ok(())), wormhole_out_counts:DuplicateCheck(Ok(())), @@ -448,7 +448,7 @@ impl<'a> std::fmt::Display for MapCheck<'a>{ })?; writeln!(f,"")?; } - if let AtLeastOneMatchingAndNoExtraCheck(Err(context))=&self.mode_finish_counts{ + if let SetDifferenceCheck(Err(context))=&self.mode_finish_counts{ // perhaps there are extra end zones (context.extra) if !context.extra.is_empty(){ write!(f,"Extra finish zones with no matching start zone: ")?; -- 2.49.1 From c57a53692dbbb9a2c31c7b476b9bfd32cd9f02ab Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 20:54:47 -0700 Subject: [PATCH 3/7] validator: code tweaks --- validation/src/check.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index 402cce2..ad74463 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -262,7 +262,7 @@ pub struct MapInfoOwned{ pub struct MapCheck<'a>{ // === METADATA CHECKS === // The root must be of class Model - model_class:StringCheck<'a,(),&'a str>, + model_class:StringCheck<'a,(),&'static str>, // Model's name must be in snake case model_name:StringCheck<'a,(),String>, // Map must have a StringValue named DisplayName. @@ -470,18 +470,16 @@ impl<'a> std::fmt::Display for MapCheck<'a>{ } if let DuplicateCheck(Err(DuplicateCheckContext(context)))=&self.spawn_counts{ write!(f,"Duplicate spawn zones: ")?; - comma_separated(f,context.iter(),|f,(SpawnID(spawn_id),count)|{ - write!(f,"Spawn{spawn_id}({count} duplicates)")?; - Ok(()) - })?; + comma_separated(f,context.iter(),|f,(SpawnID(spawn_id),count)| + write!(f,"Spawn{spawn_id}({count} duplicates)") + )?; writeln!(f,"")?; } if let DuplicateCheck(Err(DuplicateCheckContext(context)))=&self.wormhole_out_counts{ write!(f,"Duplicate wormhole out: ")?; - comma_separated(f,context.iter(),|f,(WormholeOutID(wormhole_out_id),count)|{ - write!(f,"WormholeOut{wormhole_out_id}({count} duplicates)")?; - Ok(()) - })?; + comma_separated(f,context.iter(),|f,(WormholeOutID(wormhole_out_id),count)| + write!(f,"WormholeOut{wormhole_out_id}({count} duplicates)") + )?; writeln!(f,"")?; } Ok(()) -- 2.49.1 From 12bfbfb0a032a551e2042653fe0bda907fe3993a Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 20:54:27 -0700 Subject: [PATCH 4/7] openapi: add updated info to validator-submitted --- openapi-internal.yaml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/openapi-internal.yaml b/openapi-internal.yaml index 664295a..1cde29e 100644 --- a/openapi-internal.yaml +++ b/openapi-internal.yaml @@ -80,6 +80,25 @@ paths: type: integer format: int64 minimum: 0 + - name: DisplayName + in: query + required: true + schema: + type: string + maxLength: 128 + - name: Creator + in: query + required: true + schema: + type: string + maxLength: 128 + - name: GameID + in: query + required: true + schema: + type: integer + format: int32 + minimum: 0 responses: "204": description: Successful response @@ -266,6 +285,25 @@ paths: type: integer format: int64 minimum: 0 + - name: DisplayName + in: query + required: true + schema: + type: string + maxLength: 128 + - name: Creator + in: query + required: true + schema: + type: string + maxLength: 128 + - name: GameID + in: query + required: true + schema: + type: integer + format: int32 + minimum: 0 responses: "204": description: Successful response -- 2.49.1 From ee6c37ab9d4c24bf8274a19aa2a788fd4cfb3e72 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 20:55:27 -0700 Subject: [PATCH 5/7] openapi: generate --- pkg/internal/oas_client_gen.go | 84 +++++++ pkg/internal/oas_handlers_gen.go | 24 ++ pkg/internal/oas_parameters_gen.go | 362 +++++++++++++++++++++++++++++ 3 files changed, 470 insertions(+) diff --git a/pkg/internal/oas_client_gen.go b/pkg/internal/oas_client_gen.go index c6fce31..f7e2439 100644 --- a/pkg/internal/oas_client_gen.go +++ b/pkg/internal/oas_client_gen.go @@ -500,6 +500,48 @@ func (c *Client) sendActionMapfixSubmitted(ctx context.Context, params ActionMap return res, errors.Wrap(err, "encode query") } } + { + // Encode "DisplayName" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "DisplayName", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.StringToString(params.DisplayName)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "Creator" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "Creator", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.StringToString(params.Creator)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "GameID" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "GameID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.Int32ToString(params.GameID)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } u.RawQuery = q.Values().Encode() stage = "EncodeRequest" @@ -1118,6 +1160,48 @@ func (c *Client) sendActionSubmissionSubmitted(ctx context.Context, params Actio return res, errors.Wrap(err, "encode query") } } + { + // Encode "DisplayName" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "DisplayName", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.StringToString(params.DisplayName)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "Creator" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "Creator", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.StringToString(params.Creator)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "GameID" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "GameID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.Int32ToString(params.GameID)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } u.RawQuery = q.Values().Encode() stage = "EncodeRequest" diff --git a/pkg/internal/oas_handlers_gen.go b/pkg/internal/oas_handlers_gen.go index e8fe7a4..638edd6 100644 --- a/pkg/internal/oas_handlers_gen.go +++ b/pkg/internal/oas_handlers_gen.go @@ -438,6 +438,18 @@ func (s *Server) handleActionMapfixSubmittedRequest(args [1]string, argsEscaped Name: "ModelVersion", In: "query", }: params.ModelVersion, + { + Name: "DisplayName", + In: "query", + }: params.DisplayName, + { + Name: "Creator", + In: "query", + }: params.Creator, + { + Name: "GameID", + In: "query", + }: params.GameID, }, Raw: r, } @@ -1348,6 +1360,18 @@ func (s *Server) handleActionSubmissionSubmittedRequest(args [1]string, argsEsca Name: "ModelVersion", In: "query", }: params.ModelVersion, + { + Name: "DisplayName", + In: "query", + }: params.DisplayName, + { + Name: "Creator", + In: "query", + }: params.Creator, + { + Name: "GameID", + In: "query", + }: params.GameID, }, Raw: r, } diff --git a/pkg/internal/oas_parameters_gen.go b/pkg/internal/oas_parameters_gen.go index 558c0ea..f041c7f 100644 --- a/pkg/internal/oas_parameters_gen.go +++ b/pkg/internal/oas_parameters_gen.go @@ -308,6 +308,9 @@ type ActionMapfixSubmittedParams struct { // The unique identifier for a submission. MapfixID int64 ModelVersion int64 + DisplayName string + Creator string + GameID int32 } func unpackActionMapfixSubmittedParams(packed middleware.Parameters) (params ActionMapfixSubmittedParams) { @@ -325,6 +328,27 @@ func unpackActionMapfixSubmittedParams(packed middleware.Parameters) (params Act } params.ModelVersion = packed[key].(int64) } + { + key := middleware.ParameterKey{ + Name: "DisplayName", + In: "query", + } + params.DisplayName = packed[key].(string) + } + { + key := middleware.ParameterKey{ + Name: "Creator", + In: "query", + } + params.Creator = packed[key].(string) + } + { + key := middleware.ParameterKey{ + Name: "GameID", + In: "query", + } + params.GameID = packed[key].(int32) + } return params } @@ -445,6 +469,163 @@ func decodeActionMapfixSubmittedParams(args [1]string, argsEscaped bool, r *http Err: err, } } + // Decode query: DisplayName. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "DisplayName", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + params.DisplayName = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 128, + MaxLengthSet: true, + Email: false, + Hostname: false, + Regex: nil, + }).Validate(string(params.DisplayName)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + return err + } + } else { + return err + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "DisplayName", + In: "query", + Err: err, + } + } + // Decode query: Creator. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "Creator", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + params.Creator = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 128, + MaxLengthSet: true, + Email: false, + Hostname: false, + Regex: nil, + }).Validate(string(params.Creator)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + return err + } + } else { + return err + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "Creator", + In: "query", + Err: err, + } + } + // Decode query: GameID. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "GameID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + params.GameID = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.GameID)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return err + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "GameID", + In: "query", + Err: err, + } + } return params, nil } @@ -1051,6 +1232,9 @@ type ActionSubmissionSubmittedParams struct { // The unique identifier for a submission. SubmissionID int64 ModelVersion int64 + DisplayName string + Creator string + GameID int32 } func unpackActionSubmissionSubmittedParams(packed middleware.Parameters) (params ActionSubmissionSubmittedParams) { @@ -1068,6 +1252,27 @@ func unpackActionSubmissionSubmittedParams(packed middleware.Parameters) (params } params.ModelVersion = packed[key].(int64) } + { + key := middleware.ParameterKey{ + Name: "DisplayName", + In: "query", + } + params.DisplayName = packed[key].(string) + } + { + key := middleware.ParameterKey{ + Name: "Creator", + In: "query", + } + params.Creator = packed[key].(string) + } + { + key := middleware.ParameterKey{ + Name: "GameID", + In: "query", + } + params.GameID = packed[key].(int32) + } return params } @@ -1188,6 +1393,163 @@ func decodeActionSubmissionSubmittedParams(args [1]string, argsEscaped bool, r * Err: err, } } + // Decode query: DisplayName. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "DisplayName", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + params.DisplayName = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 128, + MaxLengthSet: true, + Email: false, + Hostname: false, + Regex: nil, + }).Validate(string(params.DisplayName)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + return err + } + } else { + return err + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "DisplayName", + In: "query", + Err: err, + } + } + // Decode query: Creator. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "Creator", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + params.Creator = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 128, + MaxLengthSet: true, + Email: false, + Hostname: false, + Regex: nil, + }).Validate(string(params.Creator)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + return err + } + } else { + return err + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "Creator", + In: "query", + Err: err, + } + } + // Decode query: GameID. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "GameID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + params.GameID = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.GameID)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return err + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "GameID", + In: "query", + Err: err, + } + } return params, nil } -- 2.49.1 From 3789755a1927aed26d69e07280f4e061058195c1 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 20:56:53 -0700 Subject: [PATCH 6/7] submissions: add updated info to validator-submitted --- pkg/service_internal/mapfixes.go | 3 +++ pkg/service_internal/submissions.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go index 0785aeb..e6a4e74 100644 --- a/pkg/service_internal/mapfixes.go +++ b/pkg/service_internal/mapfixes.go @@ -85,6 +85,9 @@ func (svc *Service) ActionMapfixSubmitted(ctx context.Context, params internal.A smap := datastore.Optional() smap.Add("status_id", target_status) smap.Add("asset_version", params.ModelVersion) + smap.Add("display_name", params.DisplayName) + smap.Add("creator", params.Creator) + smap.Add("game_id", params.GameID) err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitting}, smap) if err != nil { return err diff --git a/pkg/service_internal/submissions.go b/pkg/service_internal/submissions.go index 5e6abd0..ddbe54f 100644 --- a/pkg/service_internal/submissions.go +++ b/pkg/service_internal/submissions.go @@ -84,6 +84,9 @@ func (svc *Service) ActionSubmissionSubmitted(ctx context.Context, params intern smap := datastore.Optional() smap.Add("status_id", target_status) smap.Add("asset_version", params.ModelVersion) + smap.Add("display_name", params.DisplayName) + smap.Add("creator", params.Creator) + smap.Add("game_id", params.GameID) err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitting}, smap) if err != nil { return err -- 2.49.1 From 57db5f738eb7c1a3300c770d3ef294aa4e7b9629 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 21:03:57 -0700 Subject: [PATCH 7/7] todo --- validation/src/check.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/validation/src/check.rs b/validation/src/check.rs index ad74463..1a59124 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -282,6 +282,7 @@ pub struct MapCheck<'a>{ mode_start_counts:DuplicateCheck, // At least one finish zone for each start zone, and no finishes with no start mode_finish_counts:SetDifferenceCheck, + // TODO: check for dangling MapAnticheat zones (no associated MapStart) // Spawn1 must exist spawn1:Result<(),()>, // No duplicate Spawn# -- 2.49.1