Turns out that it was a field ID naming issue with the way that collection IDs render in a variable length list. The validator was attempting to name the element ID of the dependent property with the expected statement of:
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(Attribute.DependentProperty);
I analyzed the HTML viewsource (posted in my comment, above), and actually, [ and ] characters are not output in the HTML of the collection-index elements... they're replaced with _... so, when I changed my CustomValidator.cs to have the dependent property set to:
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(Attribute.DependentProperty).Replace("[", "_").Replace("]", "_");
... then the client-side validator works since the name matches. I'll have to dig deeper to see WHY the ID is getting renamed in Sanderson's collection index method, below...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Sendz.WebUI.Helpers
{
    public static class HtmlPrefixScopeExtensions
    {
        private const string IdsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";
    public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
    {
        var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
        var itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
        // autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
        html.ViewContext.Writer.WriteLine(
            string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />",
                          collectionName, html.Encode(itemIndex)));
        return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
    }
    public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
    {
        return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
    }
    private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
    {
        // We need to use the same sequence of IDs following a server-side validation failure,  
        // otherwise the framework won't render the validation error messages next to each item.
        var key = IdsToReuseKey + collectionName;
        var queue = (Queue<string>)httpContext.Items[key];
        if (queue == null)
        {
            httpContext.Items[key] = queue = new Queue<string>();
            var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
            if (!string.IsNullOrEmpty(previouslyUsedIds))
                foreach (var previouslyUsedId in previouslyUsedIds.Split(','))
                    queue.Enqueue(previouslyUsedId);
        }
        return queue;
    }
    #region Nested type: HtmlFieldPrefixScope
    private class HtmlFieldPrefixScope : IDisposable
    {
        private readonly string _previousHtmlFieldPrefix;
        private readonly TemplateInfo _templateInfo;
        public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
        {
            _templateInfo = templateInfo;
            _previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
            templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
        }
        #region IDisposable Members
        public void Dispose()
        {
            _templateInfo.HtmlFieldPrefix = _previousHtmlFieldPrefix;
        }
        #endregion
    }
    #endregion
}
}
A complete validator / attribute reference...
public class RequiredIfAttribute : ValidationAttribute
{
    private RequiredAttribute innerAttribute = new RequiredAttribute();
    public string DependentProperty { get; set; }
    public object TargetValue { get; set; }
    public RequiredIfAttribute(string dependentProperty, object targetValue)
    {
        this.DependentProperty = dependentProperty;
        this.TargetValue = targetValue;
    }
    public override bool IsValid(object value)
    {
        return innerAttribute.IsValid(value);
    }
}
    public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
        : base(metadata, context, attribute) { }
    public override IEnumerable<ModelValidationResult> Validate(object container)
    {
        // Get a reference to the property this validation depends upon
        var field = Metadata.ContainerType.GetProperty(Attribute.DependentProperty);
        if (field != null)
        {
            // Get the value of the dependent property
            var value = field.GetValue(container, null);
            // Compare the value against the target value
            if ((value == null && Attribute.TargetValue == null) || 
                (value != null && value.ToString().ToLowerInvariant().Equals(Attribute.TargetValue.ToString().ToLowerInvariant())))
            {
                // A match => means we should try validating this field
                if (!Attribute.IsValid(Metadata.Model))
                    // Validation failed - return an error
                    yield return new ModelValidationResult { Message = ErrorMessage };
            }
        }
    }
    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
    {
        var rule = new ModelClientValidationRule()
        {
            ErrorMessage = ErrorMessage,
            ValidationType = "requiredif"
        };
        var viewContext = (ControllerContext as ViewContext);
        var depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(Attribute.DependentProperty).Replace("[", "_").Replace("]", "_");
        rule.ValidationParameters.Add("dependentProperty", depProp);
        rule.ValidationParameters.Add("targetValue", Attribute.TargetValue.ToString());
        yield return rule;
    }