Custom lighting model: Vertex spotlights

I have noticed something very strange with writing custom lighting using surface shaders in Unity. I’m trying to write a character shader that uses Half-Lambert (aka Diffuse Wrap) shading. I decided for speed and simplicity, I’d set ‘noforwardadd’ so that only the main directional light is computed per-pixel and everything else is per vertex.
It works great for point lights.
However, the really strange part is that for some reason it treats spotlights as if they were point lights! This is completely unacceptable, but I can’t for the life of me figure out what the hell I’m doing wrong.

Here’s the shader code:

Shader "CSP/HalfLambert" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _SpecTint ("Specular Color", Color) = (1,1,1,1)
        _SpecPower ("Specular Power", Range(0,100)) = 48.0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        
        CGPROGRAM
        #pragma surface surf DiffuseWrap approxview noforwardadd

        sampler2D _MainTex;
        float3 _SpecTint;
        float _SpecPower;
        
        half4 LightingDiffuseWrap( SurfaceOutput s, half3 lightDir, half3 viewDir, half atten )
        {
            half3 h = normalize( lightDir + viewDir );
            half NdotL = dot(s.Normal, lightDir);
            half NdotH = saturate( dot(s.Normal, h) );
            float diff = NdotL * 0.5 + 0.5;
            float diff_sqr = diff * diff;
            float spec = pow( NdotH, _SpecPower );
            half4 c;
            c.rgb = (s.Albedo * _LightColor0.rgb * diff_sqr + _LightColor0.rgb * spec * _SpecTint) * (atten * 2);
            c.a = s.Alpha;
            return c;
        }

        struct Input {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutput o) {
            half4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    } 
    FallBack "Diffuse"
}

Repro steps: Put this on an object and then shine a point light at it. As you move the point light around, it acts exactly like a point light (except it turns off when the cone isn’t pointed at the object)

You’ve got noforwardadd in there, so it won’t do any spotlights per-pixel - it’ll count it as one of the 4 vertex lights.

Spotlights don’t have spots in vertex mode, so that’s the issue.

Well that answers my question, sort of.
Another question: Why do spotlights work as they should in the builtin VertexLit shader?

EDIT: OK, so I think I’m going to change tactics.
My original intentions for having vertex lighting was that I could scale the pixel lights in my surface shader by an arbitrary value (such as values sampled from the lightmap at the character’s feet), and the vertex lights wouldn’t be affected.
I’m probably just going to figure out some other way to scale the contribution of the directional light, since pixel lighting looks a hell of a lot better anyway.
I’m basically trying to figure out how to emulate the character shading in the Source Engine.

OK, so for those who are curious here’s my final solution.
First, the character shader - it’s essentially a simple textured shader with a half-lambert lighting model: ( ndotl * 0.5 + 0.5 )^2
I also added a light tint color property to the shader, so light colors are multiplied by a user-defined value (setting this value to black means the object will be lit by pure ambient light)

Shader "Custom/Diffuse Wrapped"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Texture", 2D) = "white" {}
        _LightTint ("Light Tint", Color) = (1,1,1,1)
    }

    SubShader
    {
        Tags { "RenderType" = "Opaque" }

        CGPROGRAM

        #pragma surface surf WrapLambert

        half3 _LightTint;
        half4 _Color;

        half4 LightingWrapLambert(SurfaceOutput s, half3 lightDir, half atten)
        {
            half NdotL = dot( s.Normal, lightDir );
            half diff = NdotL * 0.5 + 0.5;
            half4 c;
            c.rgb = s.Albedo * _LightColor0.rgb * _LightTint * (diff * diff * atten * 2);
            c.a = s.Alpha;
            return c;
        }

        struct Input
        {
            float2 uv_MainTex;
        };

        sampler2D _MainTex;
        void surf(Input IN, inout SurfaceOutput o)
        {
            o.Albedo = tex2D( _MainTex, IN.uv_MainTex ).rgb * _Color;
        }

        ENDCG
    }
    Fallback "Diffuse"
}

My second part of the solution involves something similar to what Unreal Engine 3 does - I keep track of a list of lights in the scene (a component is added to shadow-casting lights - on awake it adds the light to the list, on destroy it removes the light. this is for speed and memory efficiency), and for each light, I perform a raycast to determine if there is an occluder in the way or not (plus some early out tests like range tests for point lights and angle tests for spotlights). If all lights are either out of range or occluded, I lerp the _LightTint material property to black, otherwise if there is a light affecting this object, I lerp the _LightTint material property towards white.
Not perfect, but it seems to work fairly well and does everything I need it to do.