IsOnTile will likely never return true for a solid tile because sprites generally don't overlap solid tiles. That's what IsOnTile is for -- checking what tiles the sprite is overlapping. The Blocked function is better for determining if the player is able to jump. That's what the sample game uses for determining if the player should be able to jump (in combination with IsRidingPlatform). Also, I wrote a function for my own project called "IsAgainstTile" for checking tiles around the sprite rather than overlapping the sprite. You could add this to a new file in your project's "SourceCode" folder and then it would be available for you to use in the sprite rule editor too if you want it:
public abstract partial class SpriteBase : GeneralRules
{
/// <summary>
/// Determines if a tile at the sprite's current position is a member of the specified category.
/// </summary>
/// <param name="Category">Tile category against which the tile will be checked.</param>
/// <param name="RelativePosition">The sprite may be on multiple tiles at once. This parameter
/// indicates which part of the sprite to look at, and gets the tile from the layer at
/// the specified position.</param>
/// <returns>True if the specified point in the sprite is on a tile in the specified category, false otherwise.</returns>
[Description("Examines the tile on the layer at the sprite's current position and determines if it is a member of the specified category. The RelativePosition parameter determines which part of the sprite to use when identifying a location on the layer, and Direction determines which direction from that point to check. (TouchTiles is not necessary for this function.)")]
public bool IsAgainstTile(TileCategoryName Category, RelativePosition RelativePosition, Direction Direction)
{
Debug.Assert(this.isActive, "Attempted to execute IsAgainstTile on an inactive sprite");
System.Drawing.Point rp = GetRelativePosition(RelativePosition);
switch(Direction)
{
case Direction.Up:
rp.Offset(0, -1);
break;
case Direction.Right:
rp.Offset(1, 0);
break;
case Direction.Down:
rp.Offset(0, 1);
break;
case Direction.Left:
rp.Offset(-1, 0);
break;
}
return layer.GetTile((int)(rp.X / layer.Tileset.TileWidth), (int)(rp.Y / layer.Tileset.TileHeight)).IsMember(Category);
}
}
In my project, SnapToGround is after ReactToSolidity and before MoveByVelocity. If you don't have it there, or if you have other rules in between, they might interfere with SnapToGround's ability to function properly.
Have you looked at how the player sprite in the sample game works to see what you might learn from that?